Merge "Create DeviceDefault.DocumentUI theme."
diff --git a/Android.bp b/Android.bp
index f032e62..ab7d709 100644
--- a/Android.bp
+++ b/Android.bp
@@ -191,6 +191,10 @@
"core/java/android/hardware/input/IInputDevicesChangedListener.aidl",
"core/java/android/hardware/input/ITabletModeChangedListener.aidl",
"core/java/android/hardware/iris/IIrisService.aidl",
+ "core/java/android/hardware/location/IActivityRecognitionHardware.aidl",
+ "core/java/android/hardware/location/IActivityRecognitionHardwareClient.aidl",
+ "core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl",
+ "core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl",
"core/java/android/hardware/location/IGeofenceHardware.aidl",
"core/java/android/hardware/location/IGeofenceHardwareCallback.aidl",
"core/java/android/hardware/location/IGeofenceHardwareMonitorCallback.aidl",
@@ -207,7 +211,6 @@
"core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl",
"core/java/android/hardware/usb/IUsbManager.aidl",
"core/java/android/hardware/usb/IUsbSerialReader.aidl",
- "core/java/android/net/ICaptivePortal.aidl",
"core/java/android/net/IConnectivityManager.aidl",
"core/java/android/hardware/ISensorPrivacyListener.aidl",
"core/java/android/hardware/ISensorPrivacyManager.aidl",
@@ -729,10 +732,13 @@
"ext",
],
+ jarjar_rules: ":framework-hidl-jarjar",
+
static_libs: [
"apex_aidl_interface-java",
"networkstack-aidl-interfaces-java",
"framework-protos",
+ "game-driver-protos",
"mediaplayer2-protos",
"android.hidl.base-V1.0-java",
"android.hardware.cas-V1.0-java",
@@ -762,8 +768,6 @@
required: [
// TODO: remove gps_debug when the build system propagates "required" properly.
"gps_debug.conf",
- // Loaded with System.loadLibrary by android.view.textclassifier
- "libmedia2_jni",
],
dxflags: [
@@ -789,6 +793,11 @@
],
}
+filegroup {
+ name: "framework-hidl-jarjar",
+ srcs: ["jarjar_rules_hidl.txt"],
+}
+
java_library {
name: "framework",
defaults: ["framework-defaults"],
@@ -799,11 +808,7 @@
name: "framework-annotation-proc",
defaults: ["framework-defaults"],
// Use UsedByApps annotation processor
- annotation_processors: ["unsupportedappusage-annotation-processor"],
- // b/25860419: annotation processors must be explicitly specified for grok
- annotation_processor_classes: [
- "android.processor.unsupportedappusage.UsedByAppsProcessor",
- ],
+ plugins: ["unsupportedappusage-annotation-processor"],
}
// A host library including just UnsupportedAppUsage.java so that the annotation
@@ -882,20 +887,29 @@
name: "networkstack-aidl-interfaces",
local_include_dir: "core/java",
srcs: [
+ "core/java/android/net/ApfCapabilitiesParcelable.aidl",
+ "core/java/android/net/DhcpResultsParcelable.aidl",
+ "core/java/android/net/ICaptivePortal.aidl",
"core/java/android/net/INetworkMonitor.aidl",
"core/java/android/net/INetworkMonitorCallbacks.aidl",
"core/java/android/net/IIpMemoryStore.aidl",
"core/java/android/net/INetworkStackConnector.aidl",
"core/java/android/net/INetworkStackStatusCallback.aidl",
+ "core/java/android/net/InitialConfigurationParcelable.aidl",
"core/java/android/net/IpPrefixParcelable.aidl",
"core/java/android/net/LinkAddressParcelable.aidl",
"core/java/android/net/LinkPropertiesParcelable.aidl",
+ "core/java/android/net/NetworkParcelable.aidl",
"core/java/android/net/PrivateDnsConfigParcel.aidl",
+ "core/java/android/net/ProvisioningConfigurationParcelable.aidl",
"core/java/android/net/ProxyInfoParcelable.aidl",
"core/java/android/net/RouteInfoParcelable.aidl",
+ "core/java/android/net/StaticIpConfigurationParcelable.aidl",
"core/java/android/net/dhcp/DhcpServingParamsParcel.aidl",
"core/java/android/net/dhcp/IDhcpServer.aidl",
"core/java/android/net/dhcp/IDhcpServerCallbacks.aidl",
+ "core/java/android/net/ip/IIpClient.aidl",
+ "core/java/android/net/ip/IIpClientCallbacks.aidl",
"core/java/android/net/ipmemorystore/**/*.aidl",
],
api_dir: "aidl/networkstack",
@@ -1253,6 +1267,7 @@
":non_openjdk_javadoc_files",
":android_icu4j_src_files_for_docs",
":conscrypt_public_api_files",
+ ":media2-srcs",
"test-mock/src/**/*.java",
"test-runner/src/**/*.java",
],
@@ -1314,6 +1329,7 @@
":non_openjdk_javadoc_files",
":android_icu4j_src_files_for_docs",
":conscrypt_public_api_files",
+ ":media2-srcs",
],
srcs_lib: "framework",
srcs_lib_whitelist_dirs: frameworks_base_subdirs,
diff --git a/Android.mk b/Android.mk
index 9f7bf99..e405345 100644
--- a/Android.mk
+++ b/Android.mk
@@ -87,14 +87,11 @@
frameworks/base/config/hiddenapi-greylist-max-p.txt \
frameworks/base/config/hiddenapi-greylist-max-o.txt \
frameworks/base/config/hiddenapi-force-blacklist.txt \
- $(INTERNAL_PLATFORM_HIDDENAPI_PUBLIC_LIST) \
- $(INTERNAL_PLATFORM_HIDDENAPI_PRIVATE_LIST) \
+ $(INTERNAL_PLATFORM_HIDDENAPI_STUB_FLAGS) \
$(INTERNAL_PLATFORM_REMOVED_DEX_API_FILE) \
$(SOONG_HIDDENAPI_FLAGS)
frameworks/base/tools/hiddenapi/generate_hiddenapi_lists.py \
- --public $(INTERNAL_PLATFORM_HIDDENAPI_PUBLIC_LIST) \
- --private $(INTERNAL_PLATFORM_HIDDENAPI_PRIVATE_LIST) \
- --csv $(PRIVATE_FLAGS_INPUTS) \
+ --csv $(INTERNAL_PLATFORM_HIDDENAPI_STUB_FLAGS) $(PRIVATE_FLAGS_INPUTS) \
--greylist frameworks/base/config/hiddenapi-greylist.txt \
--greylist-ignore-conflicts $(INTERNAL_PLATFORM_REMOVED_DEX_API_FILE) \
--greylist-max-p frameworks/base/config/hiddenapi-greylist-max-p.txt \
diff --git a/api/current.txt b/api/current.txt
index 6122e52..e1a3608 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3941,7 +3941,8 @@
method public boolean isBackgroundRestricted();
method @Deprecated public boolean isInLockTaskMode();
method public boolean isLowRamDevice();
- method public static boolean isRunningInTestHarness();
+ method @Deprecated public static boolean isRunningInTestHarness();
+ method public static boolean isRunningInUserTestHarness();
method public static boolean isUserAMonkey();
method @RequiresPermission(android.Manifest.permission.KILL_BACKGROUND_PROCESSES) public void killBackgroundProcesses(String);
method @RequiresPermission(android.Manifest.permission.REORDER_TASKS) public void moveTaskToFront(int, int);
@@ -6822,6 +6823,7 @@
field public static final String EXTRA_ADD_EXPLANATION = "android.app.extra.ADD_EXPLANATION";
field public static final String EXTRA_DELEGATION_SCOPES = "android.app.extra.DELEGATION_SCOPES";
field public static final String EXTRA_DEVICE_ADMIN = "android.app.extra.DEVICE_ADMIN";
+ field @RequiresPermission(android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY) public static final String EXTRA_PASSWORD_COMPLEXITY = "android.app.extra.PASSWORD_COMPLEXITY";
field public static final String EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE = "android.app.extra.PROVISIONING_ACCOUNT_TO_MIGRATE";
field public static final String EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE = "android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE";
field public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME = "android.app.extra.PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME";
@@ -9625,7 +9627,7 @@
public abstract class Context {
ctor public Context();
- method public abstract boolean bindIsolatedService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull String);
+ method public boolean bindIsolatedService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull String);
method public abstract boolean bindService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int);
method @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)") public abstract int checkCallingOrSelfPermission(@NonNull String);
method @CheckResult(suggest="#enforceCallingOrSelfUriPermission(Uri,int,String)") public abstract int checkCallingOrSelfUriPermission(android.net.Uri, int);
@@ -9678,7 +9680,7 @@
method public abstract java.io.File getNoBackupFilesDir();
method public abstract java.io.File getObbDir();
method public abstract java.io.File[] getObbDirs();
- method public abstract String getOpPackageName();
+ method public String getOpPackageName();
method public abstract String getPackageCodePath();
method public abstract android.content.pm.PackageManager getPackageManager();
method public abstract String getPackageName();
@@ -9745,7 +9747,7 @@
method public abstract void unbindService(@NonNull android.content.ServiceConnection);
method public void unregisterComponentCallbacks(android.content.ComponentCallbacks);
method public abstract void unregisterReceiver(android.content.BroadcastReceiver);
- method public abstract void updateServiceGroup(@NonNull android.content.ServiceConnection, int, int);
+ method public void updateServiceGroup(@NonNull android.content.ServiceConnection, int, int);
field public static final String ACCESSIBILITY_SERVICE = "accessibility";
field public static final String ACCOUNT_SERVICE = "account";
field public static final String ACTIVITY_SERVICE = "activity";
@@ -9839,7 +9841,6 @@
public class ContextWrapper extends android.content.Context {
ctor public ContextWrapper(android.content.Context);
method protected void attachBaseContext(android.content.Context);
- method public boolean bindIsolatedService(android.content.Intent, android.content.ServiceConnection, int, String);
method public boolean bindService(android.content.Intent, android.content.ServiceConnection, int);
method public int checkCallingOrSelfPermission(String);
method public int checkCallingOrSelfUriPermission(android.net.Uri, int);
@@ -9889,7 +9890,6 @@
method public java.io.File getNoBackupFilesDir();
method public java.io.File getObbDir();
method public java.io.File[] getObbDirs();
- method public String getOpPackageName();
method public String getPackageCodePath();
method public android.content.pm.PackageManager getPackageManager();
method public String getPackageName();
@@ -9945,7 +9945,6 @@
method public boolean stopService(android.content.Intent);
method public void unbindService(android.content.ServiceConnection);
method public void unregisterReceiver(android.content.BroadcastReceiver);
- method public void updateServiceGroup(android.content.ServiceConnection, int, int);
}
@Deprecated public class CursorLoader extends android.content.AsyncTaskLoader<android.database.Cursor> {
@@ -10221,7 +10220,7 @@
field public static final String ACTION_MY_PACKAGE_REPLACED = "android.intent.action.MY_PACKAGE_REPLACED";
field public static final String ACTION_MY_PACKAGE_SUSPENDED = "android.intent.action.MY_PACKAGE_SUSPENDED";
field public static final String ACTION_MY_PACKAGE_UNSUSPENDED = "android.intent.action.MY_PACKAGE_UNSUSPENDED";
- field public static final String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
+ field @Deprecated public static final String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
field public static final String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
field public static final String ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
field public static final String ACTION_PACKAGES_SUSPENDED = "android.intent.action.PACKAGES_SUSPENDED";
@@ -10349,6 +10348,7 @@
field public static final int EXTRA_DOCK_STATE_LE_DESK = 3; // 0x3
field public static final int EXTRA_DOCK_STATE_UNDOCKED = 0; // 0x0
field public static final String EXTRA_DONT_KILL_APP = "android.intent.extra.DONT_KILL_APP";
+ field public static final String EXTRA_DURATION_MILLIS = "android.intent.extra.DURATION_MILLIS";
field public static final String EXTRA_EMAIL = "android.intent.extra.EMAIL";
field public static final String EXTRA_EXCLUDE_COMPONENTS = "android.intent.extra.EXCLUDE_COMPONENTS";
field public static final String EXTRA_FROM_STORAGE = "android.intent.extra.FROM_STORAGE";
@@ -11500,7 +11500,7 @@
method public abstract java.util.List<android.content.pm.PermissionGroupInfo> getAllPermissionGroups(int);
method public abstract android.graphics.drawable.Drawable getApplicationBanner(android.content.pm.ApplicationInfo);
method public abstract android.graphics.drawable.Drawable getApplicationBanner(String) throws android.content.pm.PackageManager.NameNotFoundException;
- method public abstract int getApplicationEnabledSetting(String);
+ method public abstract int getApplicationEnabledSetting(@NonNull String);
method public abstract android.graphics.drawable.Drawable getApplicationIcon(android.content.pm.ApplicationInfo);
method public abstract android.graphics.drawable.Drawable getApplicationIcon(String) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.pm.ApplicationInfo getApplicationInfo(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -11508,7 +11508,7 @@
method public abstract android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo);
method public abstract android.graphics.drawable.Drawable getApplicationLogo(String) throws android.content.pm.PackageManager.NameNotFoundException;
method @Nullable public abstract android.content.pm.ChangedPackages getChangedPackages(@IntRange(from=0) int);
- method public abstract int getComponentEnabledSetting(android.content.ComponentName);
+ method public abstract int getComponentEnabledSetting(@NonNull android.content.ComponentName);
method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
method public abstract android.graphics.drawable.Drawable getDrawable(String, @DrawableRes int, android.content.pm.ApplicationInfo);
method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
@@ -11574,8 +11574,8 @@
method public abstract android.content.pm.ProviderInfo resolveContentProvider(String, int);
method public abstract android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
method public abstract void setApplicationCategoryHint(@NonNull String, int);
- method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setApplicationEnabledSetting(String, int, int);
- method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setComponentEnabledSetting(android.content.ComponentName, int, int);
+ method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setApplicationEnabledSetting(@NonNull String, int, int);
+ method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setComponentEnabledSetting(@NonNull android.content.ComponentName, int, int);
method public abstract void setInstallerPackageName(String, String);
method public abstract void updateInstantAppCookie(@Nullable byte[]);
method public abstract void verifyPendingInstall(int, int);
@@ -11655,6 +11655,7 @@
field public static final String FEATURE_SCREEN_LANDSCAPE = "android.hardware.screen.landscape";
field public static final String FEATURE_SCREEN_PORTRAIT = "android.hardware.screen.portrait";
field public static final String FEATURE_SECURELY_REMOVES_USERS = "android.software.securely_removes_users";
+ field public static final String FEATURE_SECURE_LOCK_SCREEN = "android.software.secure_lock_screen";
field public static final String FEATURE_SENSOR_ACCELEROMETER = "android.hardware.sensor.accelerometer";
field public static final String FEATURE_SENSOR_AMBIENT_TEMPERATURE = "android.hardware.sensor.ambient_temperature";
field public static final String FEATURE_SENSOR_BAROMETER = "android.hardware.sensor.barometer";
@@ -14618,8 +14619,8 @@
public class Picture {
ctor public Picture();
ctor public Picture(android.graphics.Picture);
- method public android.graphics.Canvas beginRecording(int, int);
- method public void draw(android.graphics.Canvas);
+ method @NonNull public android.graphics.Canvas beginRecording(int, int);
+ method public void draw(@NonNull android.graphics.Canvas);
method public void endRecording();
method public int getHeight();
method public int getWidth();
@@ -14871,7 +14872,7 @@
}
public final class RenderNode {
- ctor public RenderNode(String);
+ ctor public RenderNode(@Nullable String);
method public int computeApproximateMemoryUsage();
method public void discardDisplayList();
method public void endRecording();
@@ -21650,7 +21651,7 @@
ctor public JapaneseCalendar(int, int, int, int);
ctor public JapaneseCalendar(int, int, int);
ctor public JapaneseCalendar(int, int, int, int, int, int);
- field public static final int CURRENT_ERA;
+ field @Deprecated public static final int CURRENT_ERA;
field public static final int HEISEI;
field public static final int MEIJI;
field public static final int SHOWA;
@@ -22256,7 +22257,7 @@
method public void onUpdateExtractingViews(android.view.inputmethod.EditorInfo);
method public void onUpdateExtractingVisibility(android.view.inputmethod.EditorInfo);
method public void onUpdateSelection(int, int, int, int, int, int);
- method public void onViewClicked(boolean);
+ method @Deprecated public void onViewClicked(boolean);
method public void onWindowHidden();
method public void onWindowShown();
method public void requestHideSelf(int);
@@ -24533,6 +24534,7 @@
ctor public MediaController2(@NonNull android.content.Context, @NonNull android.media.Session2Token, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaController2.ControllerCallback);
method public void cancelSessionCommand(@NonNull Object);
method public void close();
+ method public android.media.Session2Token getConnectedSessionToken();
method public boolean isPlaybackActive();
method @NonNull public Object sendSessionCommand(@NonNull android.media.Session2Command, @Nullable android.os.Bundle);
}
@@ -24637,6 +24639,7 @@
method @android.media.MediaDrm.SecurityLevel public int getSecurityLevel(@NonNull byte[]);
method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID);
method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String);
+ method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String, @android.media.MediaDrm.SecurityLevel int);
method @NonNull public byte[] openSession() throws android.media.NotProvisionedException, android.media.ResourceBusyException;
method @NonNull public byte[] openSession(@android.media.MediaDrm.SecurityLevel int) throws android.media.NotProvisionedException, android.media.ResourceBusyException;
method @Nullable public byte[] provideKeyResponse(@NonNull byte[], @NonNull byte[]) throws android.media.DeniedByServerException, android.media.NotProvisionedException;
@@ -25914,6 +25917,23 @@
method @Nullable public android.media.Session2Command.Result onSessionCommand(@NonNull android.media.MediaSession2, @NonNull android.media.MediaSession2.ControllerInfo, @NonNull android.media.Session2Command, @Nullable android.os.Bundle);
}
+ public abstract class MediaSession2Service extends android.app.Service {
+ ctor public MediaSession2Service();
+ method public final void addSession(@NonNull android.media.MediaSession2);
+ method @NonNull public final java.util.List<android.media.MediaSession2> getSessions();
+ method @CallSuper @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
+ method @NonNull public abstract android.media.MediaSession2 onGetPrimarySession();
+ method @Nullable public abstract android.media.MediaSession2Service.MediaNotification onUpdateNotification(@NonNull android.media.MediaSession2);
+ method public final void removeSession(@NonNull android.media.MediaSession2);
+ field public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service";
+ }
+
+ public static class MediaSession2Service.MediaNotification {
+ ctor public MediaSession2Service.MediaNotification(int, @NonNull android.app.Notification);
+ method @NonNull public android.app.Notification getNotification();
+ method public int getNotificationId();
+ }
+
public final class MediaSync {
ctor public MediaSync();
method @NonNull public android.view.Surface createInputSurface();
@@ -26576,12 +26596,9 @@
field public static final int SUCCESS = 0; // 0x0
}
- public static final class AudioEffect.Descriptor implements android.os.Parcelable {
+ public static class AudioEffect.Descriptor {
ctor public AudioEffect.Descriptor();
ctor public AudioEffect.Descriptor(String, String, String, String, String);
- method public int describeContents();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.media.audiofx.AudioEffect.Descriptor> CREATOR;
field public String connectMode;
field public String implementor;
field public String name;
@@ -27286,21 +27303,6 @@
package android.media.session {
- public final class ControllerCallbackLink implements android.os.Parcelable {
- method public int describeContents();
- method @NonNull public android.os.IBinder getBinder();
- method public void notifyEvent(@NonNull String, @Nullable android.os.Bundle);
- method public void notifyExtrasChanged(@Nullable android.os.Bundle);
- method public void notifyMetadataChanged(@Nullable android.media.MediaMetadata);
- method public void notifyPlaybackStateChanged(@Nullable android.media.session.PlaybackState);
- method public void notifyQueueChanged(@Nullable java.util.List<android.media.session.MediaSession.QueueItem>);
- method public void notifyQueueTitleChanged(@Nullable CharSequence);
- method public void notifySessionDestroyed();
- method public void notifyVolumeInfoChanged(@NonNull android.media.session.MediaController.PlaybackInfo);
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.media.session.ControllerCallbackLink> CREATOR;
- }
-
public final class MediaController {
ctor public MediaController(@NonNull android.content.Context, @NonNull android.media.session.MediaSession.Token);
method public void adjustVolume(int, int);
@@ -27440,18 +27442,26 @@
public final class MediaSessionManager {
method public void addOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, @Nullable android.content.ComponentName);
method public void addOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, @Nullable android.content.ComponentName, @Nullable android.os.Handler);
+ method public void addOnSession2TokensChangedListener(@NonNull android.media.session.MediaSessionManager.OnSession2TokensChangedListener);
+ method public void addOnSession2TokensChangedListener(@NonNull android.media.session.MediaSessionManager.OnSession2TokensChangedListener, @NonNull android.os.Handler);
method @NonNull public java.util.List<android.media.session.MediaController> getActiveSessions(@Nullable android.content.ComponentName);
+ method @NonNull public java.util.List<android.media.Session2Token> getSession2Tokens();
method public boolean isTrustedForMediaControl(@NonNull android.media.session.MediaSessionManager.RemoteUserInfo);
+ method public void notifySession2Created(@NonNull android.media.Session2Token);
method public void removeOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener);
+ method public void removeOnSession2TokensChangedListener(@NonNull android.media.session.MediaSessionManager.OnSession2TokensChangedListener);
}
public static interface MediaSessionManager.OnActiveSessionsChangedListener {
method public void onActiveSessionsChanged(@Nullable java.util.List<android.media.session.MediaController>);
}
+ public static interface MediaSessionManager.OnSession2TokensChangedListener {
+ method public void onSession2TokensChanged(@NonNull java.util.List<android.media.Session2Token>);
+ }
+
public static final class MediaSessionManager.RemoteUserInfo {
ctor public MediaSessionManager.RemoteUserInfo(@NonNull String, int, int);
- ctor public MediaSessionManager.RemoteUserInfo(String, int, int, android.os.IBinder);
method public String getPackageName();
method public int getPid();
method public int getUid();
@@ -27535,36 +27545,6 @@
method public android.media.session.PlaybackState.CustomAction.Builder setExtras(android.os.Bundle);
}
- public final class SessionCallbackLink implements android.os.Parcelable {
- method public int describeContents();
- method @NonNull public android.os.IBinder getBinder();
- method public void notifyAdjustVolume(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, int);
- method public void notifyCommand(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle, @Nullable android.os.ResultReceiver);
- method public void notifyCustomAction(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
- method public void notifyFastForward(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
- method public void notifyMediaButton(@NonNull String, int, int, @NonNull android.content.Intent, int, @Nullable android.os.ResultReceiver);
- method public void notifyMediaButtonFromController(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull android.content.Intent);
- method public void notifyNext(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
- method public void notifyPause(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
- method public void notifyPlay(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
- method public void notifyPlayFromMediaId(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
- method public void notifyPlayFromSearch(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
- method public void notifyPlayFromUri(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull android.net.Uri, @Nullable android.os.Bundle);
- method public void notifyPrepare(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
- method public void notifyPrepareFromMediaId(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
- method public void notifyPrepareFromSearch(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
- method public void notifyPrepareFromUri(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull android.net.Uri, @Nullable android.os.Bundle);
- method public void notifyPrevious(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
- method public void notifyRate(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull android.media.Rating);
- method public void notifyRewind(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
- method public void notifySeekTo(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, long);
- method public void notifySetVolumeTo(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, int);
- method public void notifySkipToTrack(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, long);
- method public void notifyStop(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.media.session.SessionCallbackLink> CREATOR;
- }
-
}
package android.media.tv {
@@ -28599,6 +28579,28 @@
field public int serverAddress;
}
+ public final class DnsResolver {
+ method public static android.net.DnsResolver getInstance();
+ method public void query(@Nullable android.net.Network, @NonNull byte[], int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.RawAnswerListener) throws android.system.ErrnoException;
+ method public void query(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.RawAnswerListener) throws android.system.ErrnoException;
+ method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.InetAddressAnswerListener) throws android.system.ErrnoException;
+ field public static final int CLASS_IN = 1; // 0x1
+ field public static final int FLAG_EMPTY = 0; // 0x0
+ field public static final int FLAG_NO_CACHE_LOOKUP = 4; // 0x4
+ field public static final int FLAG_NO_CACHE_STORE = 2; // 0x2
+ field public static final int FLAG_NO_RETRY = 1; // 0x1
+ field public static final int TYPE_A = 1; // 0x1
+ field public static final int TYPE_AAAA = 28; // 0x1c
+ }
+
+ public static interface DnsResolver.InetAddressAnswerListener {
+ method public void onAnswer(@NonNull java.util.List<java.net.InetAddress>);
+ }
+
+ public static interface DnsResolver.RawAnswerListener {
+ method public void onAnswer(@Nullable byte[]);
+ }
+
public class InetAddresses {
method public static boolean isNumericAddress(String);
method public static java.net.InetAddress parseNumericAddress(String);
@@ -28940,30 +28942,31 @@
method public android.net.IpPrefix getDestination();
method public java.net.InetAddress getGateway();
method public String getInterface();
+ method public boolean hasGateway();
method public boolean isDefaultRoute();
method public boolean matches(java.net.InetAddress);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.net.RouteInfo> CREATOR;
}
- public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory {
+ @Deprecated public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory {
ctor @Deprecated public SSLCertificateSocketFactory(int);
- method public java.net.Socket createSocket(java.net.Socket, String, int, boolean) throws java.io.IOException;
- method public java.net.Socket createSocket(java.net.InetAddress, int, java.net.InetAddress, int) throws java.io.IOException;
- method public java.net.Socket createSocket(java.net.InetAddress, int) throws java.io.IOException;
- method public java.net.Socket createSocket(String, int, java.net.InetAddress, int) throws java.io.IOException;
- method public java.net.Socket createSocket(String, int) throws java.io.IOException;
- method public static javax.net.SocketFactory getDefault(int);
- method public static javax.net.ssl.SSLSocketFactory getDefault(int, android.net.SSLSessionCache);
- method public String[] getDefaultCipherSuites();
- method public static javax.net.ssl.SSLSocketFactory getInsecure(int, android.net.SSLSessionCache);
- method public byte[] getNpnSelectedProtocol(java.net.Socket);
- method public String[] getSupportedCipherSuites();
- method public void setHostname(java.net.Socket, String);
- method public void setKeyManagers(javax.net.ssl.KeyManager[]);
- method public void setNpnProtocols(byte[][]);
- method public void setTrustManagers(javax.net.ssl.TrustManager[]);
- method public void setUseSessionTickets(java.net.Socket, boolean);
+ method @Deprecated public java.net.Socket createSocket(java.net.Socket, String, int, boolean) throws java.io.IOException;
+ method @Deprecated public java.net.Socket createSocket(java.net.InetAddress, int, java.net.InetAddress, int) throws java.io.IOException;
+ method @Deprecated public java.net.Socket createSocket(java.net.InetAddress, int) throws java.io.IOException;
+ method @Deprecated public java.net.Socket createSocket(String, int, java.net.InetAddress, int) throws java.io.IOException;
+ method @Deprecated public java.net.Socket createSocket(String, int) throws java.io.IOException;
+ method @Deprecated public static javax.net.SocketFactory getDefault(int);
+ method @Deprecated public static javax.net.ssl.SSLSocketFactory getDefault(int, android.net.SSLSessionCache);
+ method @Deprecated public String[] getDefaultCipherSuites();
+ method @Deprecated public static javax.net.ssl.SSLSocketFactory getInsecure(int, android.net.SSLSessionCache);
+ method @Deprecated public byte[] getNpnSelectedProtocol(java.net.Socket);
+ method @Deprecated public String[] getSupportedCipherSuites();
+ method @Deprecated public void setHostname(java.net.Socket, String);
+ method @Deprecated public void setKeyManagers(javax.net.ssl.KeyManager[]);
+ method @Deprecated public void setNpnProtocols(byte[][]);
+ method @Deprecated public void setTrustManagers(javax.net.ssl.TrustManager[]);
+ method @Deprecated public void setUseSessionTickets(java.net.Socket, boolean);
}
public final class SSLSessionCache {
@@ -29152,6 +29155,8 @@
public class VpnService extends android.app.Service {
ctor public VpnService();
+ method public final boolean isAlwaysOn();
+ method public final boolean isLockdownEnabled();
method public android.os.IBinder onBind(android.content.Intent);
method public void onRevoke();
method public static android.content.Intent prepare(android.content.Context);
@@ -34907,10 +34912,13 @@
method public static final void setThreadPriority(int, int) throws java.lang.IllegalArgumentException, java.lang.SecurityException;
method public static final void setThreadPriority(int) throws java.lang.IllegalArgumentException, java.lang.SecurityException;
method @Deprecated public static final boolean supportsProcesses();
+ field public static final int BLUETOOTH_UID = 1002; // 0x3ea
field public static final int FIRST_APPLICATION_UID = 10000; // 0x2710
field public static final int INVALID_UID = -1; // 0xffffffff
field public static final int LAST_APPLICATION_UID = 19999; // 0x4e1f
field public static final int PHONE_UID = 1001; // 0x3e9
+ field public static final int ROOT_UID = 0; // 0x0
+ field public static final int SHELL_UID = 2000; // 0x7d0
field public static final int SIGNAL_KILL = 9; // 0x9
field public static final int SIGNAL_QUIT = 3; // 0x3
field public static final int SIGNAL_USR1 = 10; // 0xa
@@ -37925,18 +37933,18 @@
method public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentInterface, android.net.Uri) throws java.io.FileNotFoundException;
method @Deprecated public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
method public static String getDocumentId(android.net.Uri);
- method public static android.os.Bundle getDocumentMetadata(android.content.ContentInterface, android.net.Uri) throws java.io.FileNotFoundException;
+ method @Nullable public static android.os.Bundle getDocumentMetadata(@NonNull android.content.ContentInterface, @NonNull android.net.Uri) throws java.io.FileNotFoundException;
method @Deprecated public static android.os.Bundle getDocumentMetadata(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentInterface, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
method @Deprecated public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
method public static String getRootId(android.net.Uri);
method public static String getSearchDocumentsQuery(android.net.Uri);
method public static String getTreeDocumentId(android.net.Uri);
- method public static boolean isChildDocument(android.content.ContentInterface, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
+ method public static boolean isChildDocument(@NonNull android.content.ContentInterface, @NonNull android.net.Uri, @NonNull android.net.Uri) throws java.io.FileNotFoundException;
method @Deprecated public static boolean isChildDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
method public static boolean isDocumentUri(android.content.Context, @Nullable android.net.Uri);
- method public static boolean isRootUri(android.content.Context, @Nullable android.net.Uri);
- method public static boolean isRootsUri(android.content.Context, @Nullable android.net.Uri);
+ method public static boolean isRootUri(@NonNull android.content.Context, @Nullable android.net.Uri);
+ method public static boolean isRootsUri(@NonNull android.content.Context, @Nullable android.net.Uri);
method public static boolean isTreeUri(android.net.Uri);
method public static android.net.Uri moveDocument(android.content.ContentInterface, android.net.Uri, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
method @Deprecated public static android.net.Uri moveDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
@@ -38007,6 +38015,7 @@
field public static final String COLUMN_FLAGS = "flags";
field public static final String COLUMN_ICON = "icon";
field public static final String COLUMN_MIME_TYPES = "mime_types";
+ field public static final String COLUMN_QUERY_ARGS = "query_args";
field public static final String COLUMN_ROOT_ID = "root_id";
field public static final String COLUMN_SUMMARY = "summary";
field public static final String COLUMN_TITLE = "title";
@@ -38029,7 +38038,7 @@
method public void deleteDocument(String) throws java.io.FileNotFoundException;
method public void ejectRoot(String);
method public android.provider.DocumentsContract.Path findDocumentPath(@Nullable String, String) throws java.io.FileNotFoundException;
- method @Nullable public android.os.Bundle getDocumentMetadata(String) throws java.io.FileNotFoundException;
+ method @Nullable public android.os.Bundle getDocumentMetadata(@NonNull String) throws java.io.FileNotFoundException;
method public String[] getDocumentStreamTypes(String, String);
method public String getDocumentType(String) throws java.io.FileNotFoundException;
method public final String getType(android.net.Uri);
@@ -38054,7 +38063,7 @@
method public android.database.Cursor queryRecentDocuments(String, String[], @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException;
method public abstract android.database.Cursor queryRoots(String[]) throws java.io.FileNotFoundException;
method public android.database.Cursor querySearchDocuments(String, String, String[]) throws java.io.FileNotFoundException;
- method public android.database.Cursor querySearchDocuments(String, String[], android.os.Bundle) throws java.io.FileNotFoundException;
+ method public android.database.Cursor querySearchDocuments(@NonNull String, @Nullable String[], @NonNull android.os.Bundle) throws java.io.FileNotFoundException;
method public void removeDocument(String, String) throws java.io.FileNotFoundException;
method public String renameDocument(String, String) throws java.io.FileNotFoundException;
method public final void revokeDocumentPermission(String);
@@ -42273,6 +42282,8 @@
method public static String gaiName(int);
field public static final int AF_INET;
field public static final int AF_INET6;
+ field public static final int AF_NETLINK;
+ field public static final int AF_PACKET;
field public static final int AF_UNIX;
field public static final int AF_UNSPEC;
field public static final int AI_ADDRCONFIG;
@@ -42282,6 +42293,7 @@
field public static final int AI_NUMERICSERV;
field public static final int AI_PASSIVE;
field public static final int AI_V4MAPPED;
+ field public static final int ARPHRD_ETHER;
field public static final int CAP_AUDIT_CONTROL;
field public static final int CAP_AUDIT_WRITE;
field public static final int CAP_BLOCK_SUSPEND;
@@ -42405,6 +42417,10 @@
field public static final int ESPIPE;
field public static final int ESRCH;
field public static final int ESTALE;
+ field public static final int ETH_P_ALL;
+ field public static final int ETH_P_ARP;
+ field public static final int ETH_P_IP;
+ field public static final int ETH_P_IPV6;
field public static final int ETIME;
field public static final int ETIMEDOUT;
field public static final int ETXTBSY;
@@ -42502,6 +42518,8 @@
field public static final int MS_ASYNC;
field public static final int MS_INVALIDATE;
field public static final int MS_SYNC;
+ field public static final int NETLINK_INET_DIAG;
+ field public static final int NETLINK_ROUTE;
field public static final int NI_DGRAM;
field public static final int NI_NAMEREQD;
field public static final int NI_NOFQDN;
@@ -42538,6 +42556,7 @@
field public static final int PR_GET_DUMPABLE;
field public static final int PR_SET_DUMPABLE;
field public static final int PR_SET_NO_NEW_PRIVS;
+ field public static final int RTMGRP_NEIGH;
field public static final int RT_SCOPE_HOST;
field public static final int RT_SCOPE_LINK;
field public static final int RT_SCOPE_NOWHERE;
@@ -43617,6 +43636,7 @@
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts();
method public android.telecom.PhoneAccountHandle getSimCallManager();
method public String getSystemDialerPackage();
+ method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telecom.PhoneAccountHandle getUserSelectedOutgoingPhoneAccount();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailNumber(android.telecom.PhoneAccountHandle);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String, android.telecom.PhoneAccountHandle);
@@ -44176,6 +44196,7 @@
public final class CellSignalStrengthGsm extends android.telephony.CellSignalStrength implements android.os.Parcelable {
method public int describeContents();
method public int getAsuLevel();
+ method public int getBitErrorRate();
method public int getDbm();
method public int getLevel();
method public int getTimingAdvance();
@@ -44477,16 +44498,16 @@
public class SignalStrength implements android.os.Parcelable {
method public int describeContents();
- method public int getCdmaDbm();
- method public int getCdmaEcio();
+ method @Deprecated public int getCdmaDbm();
+ method @Deprecated public int getCdmaEcio();
method @NonNull public java.util.List<android.telephony.CellSignalStrength> getCellSignalStrengths();
- method public int getEvdoDbm();
- method public int getEvdoEcio();
- method public int getEvdoSnr();
- method public int getGsmBitErrorRate();
- method public int getGsmSignalStrength();
+ method @Deprecated public int getEvdoDbm();
+ method @Deprecated public int getEvdoEcio();
+ method @Deprecated public int getEvdoSnr();
+ method @Deprecated public int getGsmBitErrorRate();
+ method @Deprecated public int getGsmSignalStrength();
method public int getLevel();
- method public boolean isGsm();
+ method @Deprecated public boolean isGsm();
method public void writeToParcel(android.os.Parcel, int);
field public static final int INVALID = 2147483647; // 0x7fffffff
}
@@ -45044,7 +45065,9 @@
field public static final int PROTOCOL_IP = 0; // 0x0
field public static final int PROTOCOL_IPV4V6 = 2; // 0x2
field public static final int PROTOCOL_IPV6 = 1; // 0x1
+ field public static final int PROTOCOL_NON_IP = 4; // 0x4
field public static final int PROTOCOL_PPP = 3; // 0x3
+ field public static final int PROTOCOL_UNSTRUCTURED = 5; // 0x5
field public static final int TYPE_CBS = 128; // 0x80
field public static final int TYPE_DEFAULT = 17; // 0x11
field public static final int TYPE_DUN = 8; // 0x8
@@ -46827,15 +46850,15 @@
method public void setFlags(int);
method public void updateDrawState(android.text.TextPaint);
method public void writeToParcel(android.os.Parcel, int);
- field public static final String ACTION_SUGGESTION_PICKED = "android.text.style.SUGGESTION_PICKED";
+ field @Deprecated public static final String ACTION_SUGGESTION_PICKED = "android.text.style.SUGGESTION_PICKED";
field public static final android.os.Parcelable.Creator<android.text.style.SuggestionSpan> CREATOR;
field public static final int FLAG_AUTO_CORRECTION = 4; // 0x4
field public static final int FLAG_EASY_CORRECT = 1; // 0x1
field public static final int FLAG_MISSPELLED = 2; // 0x2
field public static final int SUGGESTIONS_MAX_SIZE = 5; // 0x5
- field public static final String SUGGESTION_SPAN_PICKED_AFTER = "after";
- field public static final String SUGGESTION_SPAN_PICKED_BEFORE = "before";
- field public static final String SUGGESTION_SPAN_PICKED_HASHCODE = "hashcode";
+ field @Deprecated public static final String SUGGESTION_SPAN_PICKED_AFTER = "after";
+ field @Deprecated public static final String SUGGESTION_SPAN_PICKED_BEFORE = "before";
+ field @Deprecated public static final String SUGGESTION_SPAN_PICKED_HASHCODE = "hashcode";
}
public class SuperscriptSpan extends android.text.style.MetricAffectingSpan implements android.text.ParcelableSpan {
@@ -49693,6 +49716,7 @@
}
public class Surface implements android.os.Parcelable {
+ ctor public Surface(android.view.SurfaceControl);
ctor public Surface(android.graphics.SurfaceTexture);
method public int describeContents();
method public boolean isValid();
@@ -49715,6 +49739,38 @@
ctor public Surface.OutOfResourcesException(String);
}
+ public final class SurfaceControl implements android.os.Parcelable {
+ method public int describeContents();
+ method public boolean isValid();
+ method public void readFromParcel(android.os.Parcel);
+ method public void release();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.SurfaceControl> CREATOR;
+ }
+
+ public static class SurfaceControl.Builder {
+ ctor public SurfaceControl.Builder();
+ method public android.view.SurfaceControl build();
+ method public android.view.SurfaceControl.Builder setBufferSize(@IntRange(from=0) int, @IntRange(from=0) int);
+ method @NonNull public android.view.SurfaceControl.Builder setFormat(int);
+ method public android.view.SurfaceControl.Builder setName(String);
+ method @NonNull public android.view.SurfaceControl.Builder setOpaque(boolean);
+ method @NonNull public android.view.SurfaceControl.Builder setParent(@Nullable android.view.SurfaceControl);
+ }
+
+ public static class SurfaceControl.Transaction implements java.io.Closeable {
+ ctor public SurfaceControl.Transaction();
+ method public void apply();
+ method public void close();
+ method @NonNull public android.view.SurfaceControl.Transaction merge(@NonNull android.view.SurfaceControl.Transaction);
+ method @NonNull public android.view.SurfaceControl.Transaction reparent(@NonNull android.view.SurfaceControl, @Nullable android.view.SurfaceControl);
+ method @NonNull public android.view.SurfaceControl.Transaction setAlpha(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0, to=1.0) float);
+ method @NonNull public android.view.SurfaceControl.Transaction setBufferSize(@NonNull android.view.SurfaceControl, @IntRange(from=0) int, @IntRange(from=0) int);
+ method @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int);
+ method @NonNull public android.view.SurfaceControl.Transaction setLayer(@NonNull android.view.SurfaceControl, @IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) int);
+ method @NonNull public android.view.SurfaceControl.Transaction setVisibility(@NonNull android.view.SurfaceControl, boolean);
+ }
+
public interface SurfaceHolder {
method public void addCallback(android.view.SurfaceHolder.Callback);
method public android.view.Surface getSurface();
@@ -49759,6 +49815,7 @@
ctor public SurfaceView(android.content.Context, android.util.AttributeSet, int, int);
method public boolean gatherTransparentRegion(android.graphics.Region);
method public android.view.SurfaceHolder getHolder();
+ method public android.view.SurfaceControl getSurfaceControl();
method public void setSecure(boolean);
method public void setZOrderMediaOverlay(boolean);
method public void setZOrderOnTop(boolean);
@@ -52580,12 +52637,12 @@
method @NonNull public final android.view.contentcapture.ContentCaptureSession createContentCaptureSession(@NonNull android.view.contentcapture.ContentCaptureContext);
method public final void destroy();
method public final android.view.contentcapture.ContentCaptureSessionId getContentCaptureSessionId();
- method @NonNull public android.view.autofill.AutofillId newAutofillId(@NonNull android.view.autofill.AutofillId, int);
- method @NonNull public final android.view.ViewStructure newVirtualViewStructure(@NonNull android.view.autofill.AutofillId, int);
+ method @NonNull public android.view.autofill.AutofillId newAutofillId(@NonNull android.view.autofill.AutofillId, long);
+ method @NonNull public final android.view.ViewStructure newVirtualViewStructure(@NonNull android.view.autofill.AutofillId, long);
method public final void notifyViewAppeared(@NonNull android.view.ViewStructure);
method public final void notifyViewDisappeared(@NonNull android.view.autofill.AutofillId);
method public final void notifyViewTextChanged(@NonNull android.view.autofill.AutofillId, @Nullable CharSequence, int);
- method public final void notifyViewsDisappeared(@NonNull android.view.autofill.AutofillId, @NonNull int[]);
+ method public final void notifyViewsDisappeared(@NonNull android.view.autofill.AutofillId, @NonNull long[]);
}
public final class ContentCaptureSessionId implements android.os.Parcelable {
@@ -52943,7 +53000,7 @@
method public void updateCursorAnchorInfo(android.view.View, android.view.inputmethod.CursorAnchorInfo);
method public void updateExtractedText(android.view.View, int, android.view.inputmethod.ExtractedText);
method public void updateSelection(android.view.View, int, int, int, int);
- method public void viewClicked(android.view.View);
+ method @Deprecated public void viewClicked(android.view.View);
field public static final int HIDE_IMPLICIT_ONLY = 1; // 0x1
field public static final int HIDE_NOT_ALWAYS = 2; // 0x2
field public static final int RESULT_HIDDEN = 3; // 0x3
@@ -56790,7 +56847,7 @@
method public boolean isCursorVisible();
method public boolean isElegantTextHeight();
method public boolean isFallbackLineSpacing();
- method public final boolean isHorizontallyScrolling();
+ method public final boolean isHorizontallyScrollable();
method public boolean isInputMethodTarget();
method public boolean isSingleLine();
method public boolean isSuggestionsEnabled();
diff --git a/api/removed.txt b/api/removed.txt
index 2c567e0..9f4b041 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -239,8 +239,8 @@
}
public class Picture {
- method @Deprecated public static android.graphics.Picture createFromStream(java.io.InputStream);
- method @Deprecated public void writeToStream(java.io.OutputStream);
+ method @Deprecated public static android.graphics.Picture createFromStream(@NonNull java.io.InputStream);
+ method @Deprecated public void writeToStream(@NonNull java.io.OutputStream);
}
@Deprecated public class PixelXorXfermode extends android.graphics.Xfermode {
@@ -325,7 +325,7 @@
@IntDef({0x0, 0xa, 0x14, 0x1e}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface NetworkBadging.Badging {
}
- public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory {
+ @Deprecated public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory {
method @Deprecated public static org.apache.http.conn.ssl.SSLSocketFactory getHttpSocketFactory(int, android.net.SSLSessionCache);
}
diff --git a/api/system-current.txt b/api/system-current.txt
index ac51374..9537e57 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1096,6 +1096,8 @@
public static final class UsageEvents.Event {
method public int getInstanceId();
method public String getNotificationChannelId();
+ method @Nullable public String getTaskRootClassName();
+ method @Nullable public String getTaskRootPackageName();
field public static final int NOTIFICATION_INTERRUPTION = 12; // 0xc
field public static final int NOTIFICATION_SEEN = 10; // 0xa
field public static final int SLICE_PINNED = 14; // 0xe
@@ -1110,6 +1112,7 @@
public final class UsageStatsManager {
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(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);
@@ -1125,6 +1128,8 @@
field public static final String EXTRA_TIME_USED = "android.app.usage.extra.TIME_USED";
field public static final int STANDBY_BUCKET_EXEMPTED = 5; // 0x5
field public static final int STANDBY_BUCKET_NEVER = 50; // 0x32
+ field public static final int USAGE_SOURCE_CURRENT_ACTIVITY = 2; // 0x2
+ field public static final int USAGE_SOURCE_TASK_ROOT_ACTIVITY = 1; // 0x1
}
}
@@ -1137,19 +1142,46 @@
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean enableNoAutoConnect();
method public boolean isBleScanAlwaysAvailable();
method public boolean isLeEnabled();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean registerMetadataListener(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothAdapter.MetadataListener, android.os.Handler);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean unregisterMetadataListener(android.bluetooth.BluetoothDevice);
field public static final String ACTION_BLE_STATE_CHANGED = "android.bluetooth.adapter.action.BLE_STATE_CHANGED";
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 {
+ ctor public BluetoothAdapter.MetadataListener();
+ method public void onMetadataChanged(android.bluetooth.BluetoothDevice, int, String);
+ }
+
public final class BluetoothDevice implements android.os.Parcelable {
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean cancelBondProcess();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public String getMetadata(int);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isConnected();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isEncrypted();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean removeBond();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMetadata(int, String);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setPhonebookAccessPermission(int);
field public static final int ACCESS_ALLOWED = 1; // 0x1
field public static final int ACCESS_REJECTED = 2; // 0x2
field public static final int ACCESS_UNKNOWN = 0; // 0x0
+ field public static final int METADATA_COMPANION_APP = 4; // 0x4
+ field public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; // 0x10
+ field public static final int METADATA_HARDWARE_VERSION = 3; // 0x3
+ field public static final int METADATA_IS_UNTHETHERED_HEADSET = 6; // 0x6
+ field public static final int METADATA_MAIN_ICON = 5; // 0x5
+ field public static final int METADATA_MANUFACTURER_NAME = 0; // 0x0
+ field public static final int METADATA_MAX_LENGTH = 2048; // 0x800
+ field public static final int METADATA_MODEL_NAME = 1; // 0x1
+ field public static final int METADATA_SOFTWARE_VERSION = 2; // 0x2
+ field public static final int METADATA_UNTHETHERED_CASE_BATTERY = 12; // 0xc
+ field public static final int METADATA_UNTHETHERED_CASE_CHARGING = 15; // 0xf
+ field public static final int METADATA_UNTHETHERED_CASE_ICON = 9; // 0x9
+ field public static final int METADATA_UNTHETHERED_LEFT_BATTERY = 10; // 0xa
+ field public static final int METADATA_UNTHETHERED_LEFT_CHARGING = 13; // 0xd
+ field public static final int METADATA_UNTHETHERED_LEFT_ICON = 7; // 0x7
+ field public static final int METADATA_UNTHETHERED_RIGHT_BATTERY = 11; // 0xb
+ field public static final int METADATA_UNTHETHERED_RIGHT_CHARGING = 14; // 0xe
+ field public static final int METADATA_UNTHETHERED_RIGHT_ICON = 8; // 0x8
}
public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile {
@@ -1323,6 +1355,7 @@
public class OverlayManager {
method public java.util.List<android.content.om.OverlayInfo> getOverlayInfosForTarget(@Nullable String, int);
+ method public boolean setEnabled(@Nullable String, boolean, int);
method public boolean setEnabledExclusiveInCategory(@Nullable String, int);
}
@@ -1645,22 +1678,17 @@
package android.content.rollback {
public final class PackageRollbackInfo implements android.os.Parcelable {
- ctor public PackageRollbackInfo(String, android.content.rollback.PackageRollbackInfo.PackageVersion, android.content.rollback.PackageRollbackInfo.PackageVersion);
method public int describeContents();
+ method public String getPackageName();
+ method public android.content.pm.VersionedPackage getVersionRolledBackFrom();
+ method public android.content.pm.VersionedPackage getVersionRolledBackTo();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.content.rollback.PackageRollbackInfo> CREATOR;
- field public final android.content.rollback.PackageRollbackInfo.PackageVersion higherVersion;
- field public final android.content.rollback.PackageRollbackInfo.PackageVersion lowerVersion;
- field public final String packageName;
- }
-
- public static class PackageRollbackInfo.PackageVersion {
- ctor public PackageRollbackInfo.PackageVersion(long);
- field public final long versionCode;
}
public final class RollbackInfo implements android.os.Parcelable {
method public int describeContents();
+ method public int getRollbackId();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.content.rollback.RollbackInfo> CREATOR;
field public final android.content.rollback.PackageRollbackInfo targetPackage;
@@ -1777,8 +1805,13 @@
}
public final class ColorDisplayManager {
+ method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public int getTransformCapabilities();
method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setAppSaturationLevel(@NonNull String, @IntRange(from=0, to=100) int);
method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setSaturationLevel(@IntRange(from=0, to=100) int);
+ field public static final int CAPABILITY_HARDWARE_ACCELERATION_GLOBAL = 2; // 0x2
+ field public static final int CAPABILITY_HARDWARE_ACCELERATION_PER_APP = 4; // 0x4
+ field public static final int CAPABILITY_NONE = 0; // 0x0
+ field public static final int CAPABILITY_PROTECTED_CONTENT = 1; // 0x1
}
public final class DisplayManager {
@@ -3205,12 +3238,14 @@
method public int getQuality();
method public float getSmallestDisplacement();
method public android.os.WorkSource getWorkSource();
+ method public boolean isLocationSettingsIgnored();
method public boolean isLowPowerMode();
method public android.location.LocationRequest setExpireAt(long);
method public android.location.LocationRequest setExpireIn(long);
method public android.location.LocationRequest setFastestInterval(long);
method public void setHideFromAppOps(boolean);
method public android.location.LocationRequest setInterval(long);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LocationRequest setLocationSettingsIgnored(boolean);
method public android.location.LocationRequest setLowPowerMode(boolean);
method public android.location.LocationRequest setNumUpdates(int);
method public android.location.LocationRequest setProvider(String);
@@ -3360,6 +3395,15 @@
method @NonNull public android.media.TimedMetaData.Builder setTimedMetaData(long, @NonNull byte[]);
}
+ public abstract class VolumeProvider {
+ method public void setCallback(android.media.VolumeProvider.Callback);
+ }
+
+ public abstract static class VolumeProvider.Callback {
+ ctor public VolumeProvider.Callback();
+ method public abstract void onVolumeChanged(android.media.VolumeProvider);
+ }
+
}
package android.media.audiopolicy {
@@ -3449,7 +3493,20 @@
package android.media.session {
public final class ControllerCallbackLink implements android.os.Parcelable {
- ctor public ControllerCallbackLink(@NonNull android.media.session.ControllerCallbackLink.CallbackStub);
+ ctor public ControllerCallbackLink(@NonNull android.content.Context, @NonNull android.media.session.ControllerCallbackLink.CallbackStub);
+ ctor public ControllerCallbackLink(android.os.IBinder);
+ method public int describeContents();
+ method @NonNull public android.os.IBinder getBinder();
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyEvent(@NonNull String, @Nullable android.os.Bundle);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyExtrasChanged(@Nullable android.os.Bundle);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyMetadataChanged(@Nullable android.media.MediaMetadata);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyPlaybackStateChanged(@Nullable android.media.session.PlaybackState);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyQueueChanged(@Nullable java.util.List<android.media.session.MediaSession.QueueItem>);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyQueueTitleChanged(@Nullable CharSequence);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifySessionDestroyed();
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyVolumeInfoChanged(@NonNull android.media.session.MediaController.PlaybackInfo);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.media.session.ControllerCallbackLink> CREATOR;
}
public abstract static class ControllerCallbackLink.CallbackStub {
@@ -3464,6 +3521,134 @@
method public void onVolumeInfoChanged(@NonNull android.media.session.MediaController.PlaybackInfo);
}
+ public final class ControllerLink implements android.os.Parcelable {
+ ctor public ControllerLink(@NonNull android.media.session.ControllerLink.ControllerStub);
+ ctor public ControllerLink(android.os.IBinder);
+ method public int describeContents();
+ method @NonNull public android.os.IBinder getBinder();
+ method @Nullable public android.os.Bundle getExtras();
+ method @Nullable public android.media.MediaMetadata getMetadata();
+ method @Nullable public android.media.session.PlaybackState getPlaybackState();
+ method @Nullable public java.util.List<android.media.session.MediaSession.QueueItem> getQueue();
+ method @Nullable public CharSequence getQueueTitle();
+ method public int getRatingType();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.media.session.ControllerLink> CREATOR;
+ }
+
+ public abstract static class ControllerLink.ControllerStub {
+ ctor public ControllerLink.ControllerStub();
+ method public void adjustVolume(@NonNull String, @NonNull String, @NonNull android.media.session.ControllerCallbackLink, boolean, int, int);
+ method public void fastForward(@NonNull String, @NonNull android.media.session.ControllerCallbackLink);
+ method @Nullable public android.os.Bundle getExtras();
+ method public long getFlags();
+ method @Nullable public android.app.PendingIntent getLaunchPendingIntent();
+ method @Nullable public android.media.MediaMetadata getMetadata();
+ method @NonNull public String getPackageName();
+ method @Nullable public android.media.session.PlaybackState getPlaybackState();
+ method @Nullable public java.util.List<android.media.session.MediaSession.QueueItem> getQueue();
+ method @Nullable public CharSequence getQueueTitle();
+ method public int getRatingType();
+ method @NonNull public String getTag();
+ method @NonNull public android.media.session.MediaController.PlaybackInfo getVolumeAttributes();
+ method public void next(@NonNull String, @NonNull android.media.session.ControllerCallbackLink);
+ method public void pause(@NonNull String, @NonNull android.media.session.ControllerCallbackLink);
+ method public void play(@NonNull String, @NonNull android.media.session.ControllerCallbackLink);
+ method public void playFromMediaId(@NonNull String, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
+ method public void playFromSearch(@NonNull String, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
+ method public void playFromUri(@NonNull String, @NonNull android.media.session.ControllerCallbackLink, @NonNull android.net.Uri, @Nullable android.os.Bundle);
+ method public void prepare(@NonNull String, @NonNull android.media.session.ControllerCallbackLink);
+ method public void prepareFromMediaId(@NonNull String, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
+ method public void prepareFromSearch(@NonNull String, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
+ method public void prepareFromUri(@NonNull String, @NonNull android.media.session.ControllerCallbackLink, @NonNull android.net.Uri, @Nullable android.os.Bundle);
+ method public void previous(@NonNull String, @NonNull android.media.session.ControllerCallbackLink);
+ method public void rate(@NonNull String, @NonNull android.media.session.ControllerCallbackLink, @NonNull android.media.Rating);
+ method public void registerCallback(@NonNull String, @NonNull android.media.session.ControllerCallbackLink);
+ method public void rewind(@NonNull String, @NonNull android.media.session.ControllerCallbackLink);
+ method public void seekTo(@NonNull String, @NonNull android.media.session.ControllerCallbackLink, long);
+ method public void sendCommand(@NonNull String, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle, @Nullable android.os.ResultReceiver);
+ method public void sendCustomAction(@NonNull String, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
+ method public boolean sendMediaButton(@NonNull String, @NonNull android.media.session.ControllerCallbackLink, boolean, @NonNull android.view.KeyEvent);
+ method public void setVolumeTo(@NonNull String, @NonNull String, @NonNull android.media.session.ControllerCallbackLink, int, int);
+ method public void skipToQueueItem(@NonNull String, @NonNull android.media.session.ControllerCallbackLink, long);
+ method public void stop(@NonNull String, @NonNull android.media.session.ControllerCallbackLink);
+ method public void unregisterCallback(@NonNull android.media.session.ControllerCallbackLink);
+ }
+
+ public abstract static class MediaSession.Callback {
+ method public void onSetMediaButtonEventDelegate(@NonNull android.media.session.MediaSessionEngine.MediaButtonEventDelegate);
+ }
+
+ public static final class MediaSession.Token implements android.os.Parcelable {
+ method public android.media.session.ControllerLink getControllerLink();
+ }
+
+ public final class MediaSessionEngine implements java.lang.AutoCloseable {
+ ctor public MediaSessionEngine(@NonNull android.content.Context, @NonNull android.media.session.SessionLink, @NonNull android.media.session.SessionCallbackLink, @NonNull android.media.session.MediaSessionEngine.CallbackStub, int);
+ method public void close();
+ method public String getCallingPackage();
+ method @NonNull public android.media.session.MediaController getController();
+ method @NonNull public android.media.session.MediaSessionManager.RemoteUserInfo getCurrentControllerInfo();
+ method @NonNull public android.media.session.MediaSession.Token getSessionToken();
+ method public boolean isActive();
+ method public static boolean isActiveState(int);
+ method public void sendSessionEvent(@NonNull String, @Nullable android.os.Bundle);
+ method public void setActive(boolean);
+ method public void setCallback(@Nullable android.media.session.MediaSession.Callback);
+ method public void setCallback(@Nullable android.media.session.MediaSession.Callback, @NonNull android.os.Handler);
+ method public void setExtras(@Nullable android.os.Bundle);
+ method public void setFlags(int);
+ method public void setMediaButtonReceiver(@Nullable android.app.PendingIntent);
+ method public void setMetadata(@Nullable android.media.MediaMetadata);
+ method public void setPlaybackState(@Nullable android.media.session.PlaybackState);
+ method public void setPlaybackToLocal(android.media.AudioAttributes);
+ method public void setPlaybackToRemote(@NonNull android.media.VolumeProvider);
+ method public void setQueue(@Nullable java.util.List<android.media.session.MediaSession.QueueItem>);
+ method public void setQueueTitle(@Nullable CharSequence);
+ method public void setRatingType(int);
+ method public void setSessionActivity(@Nullable android.app.PendingIntent);
+ }
+
+ public static final class MediaSessionEngine.CallbackStub {
+ ctor public MediaSessionEngine.CallbackStub();
+ method public void onAdjustVolume(String, int, int, android.media.session.ControllerCallbackLink, int);
+ method public void onCommand(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle, android.os.ResultReceiver);
+ method public void onCustomAction(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle);
+ method public void onFastForward(String, int, int, android.media.session.ControllerCallbackLink);
+ method public void onMediaButton(String, int, int, android.content.Intent, int, android.os.ResultReceiver);
+ method public void onMediaButtonFromController(String, int, int, android.media.session.ControllerCallbackLink, android.content.Intent);
+ method public void onNext(String, int, int, android.media.session.ControllerCallbackLink);
+ method public void onPause(String, int, int, android.media.session.ControllerCallbackLink);
+ method public void onPlay(String, int, int, android.media.session.ControllerCallbackLink);
+ method public void onPlayFromMediaId(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle);
+ method public void onPlayFromSearch(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle);
+ method public void onPlayFromUri(String, int, int, android.media.session.ControllerCallbackLink, android.net.Uri, android.os.Bundle);
+ method public void onPrepare(String, int, int, android.media.session.ControllerCallbackLink);
+ method public void onPrepareFromMediaId(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle);
+ method public void onPrepareFromSearch(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle);
+ method public void onPrepareFromUri(String, int, int, android.media.session.ControllerCallbackLink, android.net.Uri, android.os.Bundle);
+ method public void onPrevious(String, int, int, android.media.session.ControllerCallbackLink);
+ method public void onRate(String, int, int, android.media.session.ControllerCallbackLink, android.media.Rating);
+ method public void onRewind(String, int, int, android.media.session.ControllerCallbackLink);
+ method public void onSeekTo(String, int, int, android.media.session.ControllerCallbackLink, long);
+ method public void onSetVolumeTo(String, int, int, android.media.session.ControllerCallbackLink, int);
+ method public void onSkipToTrack(String, int, int, android.media.session.ControllerCallbackLink, long);
+ method public void onStop(String, int, int, android.media.session.ControllerCallbackLink);
+ }
+
+ public static interface MediaSessionEngine.MediaButtonEventDelegate {
+ method public boolean onMediaButtonIntent(android.content.Intent);
+ }
+
+ public static final class MediaSessionEngine.QueueItem {
+ ctor public MediaSessionEngine.QueueItem(android.media.MediaDescription, long);
+ ctor public MediaSessionEngine.QueueItem(android.os.Parcel);
+ method public android.media.MediaDescription getDescription();
+ method public long getQueueId();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final int UNKNOWN_ID = -1; // 0xffffffff
+ }
+
public final class MediaSessionManager {
method @RequiresPermission(android.Manifest.permission.SET_MEDIA_KEY_LISTENER) public void setOnMediaKeyListener(android.media.session.MediaSessionManager.OnMediaKeyListener, @Nullable android.os.Handler);
method @RequiresPermission(android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER) public void setOnVolumeKeyLongPressListener(android.media.session.MediaSessionManager.OnVolumeKeyLongPressListener, @Nullable android.os.Handler);
@@ -3477,6 +3662,66 @@
method public void onVolumeKeyLongPress(android.view.KeyEvent);
}
+ public final class SessionCallbackLink implements android.os.Parcelable {
+ ctor public SessionCallbackLink(android.os.IBinder);
+ method public int describeContents();
+ method @NonNull public android.os.IBinder getBinder();
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyAdjustVolume(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, int);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyCommand(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle, @Nullable android.os.ResultReceiver);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyCustomAction(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyFastForward(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyMediaButton(@NonNull String, int, int, @NonNull android.content.Intent, int, @Nullable android.os.ResultReceiver);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyMediaButtonFromController(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull android.content.Intent);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyNext(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyPause(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyPlay(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyPlayFromMediaId(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyPlayFromSearch(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyPlayFromUri(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull android.net.Uri, @Nullable android.os.Bundle);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyPrepare(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyPrepareFromMediaId(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyPrepareFromSearch(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull String, @Nullable android.os.Bundle);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyPrepareFromUri(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull android.net.Uri, @Nullable android.os.Bundle);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyPrevious(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyRate(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, @NonNull android.media.Rating);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyRewind(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifySeekTo(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, long);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifySetVolumeTo(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, int);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifySkipToTrack(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink, long);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyStop(@NonNull String, int, int, @NonNull android.media.session.ControllerCallbackLink);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.media.session.SessionCallbackLink> CREATOR;
+ }
+
+ public final class SessionLink implements android.os.Parcelable {
+ ctor public SessionLink(@NonNull android.media.session.SessionLink.SessionStub);
+ ctor public SessionLink(android.os.IBinder);
+ method public int describeContents();
+ method @NonNull public android.os.IBinder getBinder();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.media.session.SessionLink> CREATOR;
+ }
+
+ public abstract static class SessionLink.SessionStub {
+ ctor public SessionLink.SessionStub();
+ method public void destroySession();
+ method @NonNull public android.media.session.ControllerLink getController();
+ method public void sendEvent(@NonNull String, @Nullable android.os.Bundle);
+ method public void setActive(boolean);
+ method public void setCurrentVolume(int);
+ method public void setExtras(@Nullable android.os.Bundle);
+ method public void setFlags(int);
+ method public void setLaunchPendingIntent(@Nullable android.app.PendingIntent);
+ method public void setMediaButtonReceiver(@Nullable android.app.PendingIntent);
+ method public void setMetadata(@Nullable android.media.MediaMetadata, long, @Nullable String);
+ method public void setPlaybackState(@Nullable android.media.session.PlaybackState);
+ method public void setPlaybackToLocal(@NonNull android.media.AudioAttributes);
+ method public void setPlaybackToRemote(int, int);
+ method public void setQueue(@Nullable java.util.List<android.media.session.MediaSession.QueueItem>);
+ method public void setQueueTitle(@Nullable CharSequence);
+ method public void setRatingType(int);
+ }
+
}
package android.media.soundtrigger {
@@ -3768,13 +4013,24 @@
package android.net {
+ public class CaptivePortal implements android.os.Parcelable {
+ ctor public CaptivePortal(android.os.IBinder);
+ method public void useNetwork();
+ field public static final int APP_RETURN_DISMISSED = 0; // 0x0
+ field public static final int APP_RETURN_UNWANTED = 1; // 0x1
+ field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2
+ }
+
public class ConnectivityManager {
+ 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();
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void setAirplaneMode(boolean);
method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback);
method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback, android.os.Handler);
method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int);
+ field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC";
+ field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT";
field public static final int TETHERING_BLUETOOTH = 2; // 0x2
field public static final int TETHERING_USB = 1; // 0x1
field public static final int TETHERING_WIFI = 0; // 0x0
@@ -3820,22 +4076,48 @@
public class LinkAddress implements android.os.Parcelable {
ctor public LinkAddress(java.net.InetAddress, int);
ctor public LinkAddress(String);
+ method public boolean isGlobalPreferred();
+ method public boolean isIPv4();
+ method public boolean isIPv6();
+ method public boolean isSameAddressAs(android.net.LinkAddress);
}
public final class LinkProperties implements android.os.Parcelable {
ctor public LinkProperties();
+ method public boolean addDnsServer(java.net.InetAddress);
method public boolean addRoute(android.net.RouteInfo);
method public void clear();
+ method public String getTcpBufferSizes();
+ method public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers();
+ method public boolean hasGlobalIPv6Address();
+ method public boolean hasIPv4Address();
+ method public boolean hasIPv6DefaultRoute();
+ method public boolean isIPv4Provisioned();
+ method public boolean isIPv6Provisioned();
+ method public boolean isProvisioned();
+ method public boolean isReachable(java.net.InetAddress);
+ method public boolean removeDnsServer(java.net.InetAddress);
+ method public boolean removeRoute(android.net.RouteInfo);
method public void setDnsServers(java.util.Collection<java.net.InetAddress>);
method public void setDomains(String);
method public void setHttpProxy(android.net.ProxyInfo);
method public void setInterfaceName(String);
method public void setLinkAddresses(java.util.Collection<android.net.LinkAddress>);
method public void setMtu(int);
+ method public void setPrivateDnsServerName(@Nullable String);
+ method public void setTcpBufferSizes(String);
+ method public void setUsePrivateDns(boolean);
+ method public void setValidatedPrivateDnsServers(java.util.Collection<java.net.InetAddress>);
+ }
+
+ public class Network implements android.os.Parcelable {
+ method public android.net.Network getPrivateDnsBypassingCopy();
}
public final class NetworkCapabilities implements android.os.Parcelable {
method public int getSignalStrength();
+ method public int[] getTransportTypes();
+ method public boolean satisfiedByNetworkCapabilities(android.net.NetworkCapabilities);
field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16
}
@@ -3875,6 +4157,13 @@
field public static final String EXTRA_PACKAGE_NAME = "packageName";
}
+ public final class RouteInfo implements android.os.Parcelable {
+ method public int getType();
+ field public static final int RTN_THROW = 9; // 0x9
+ field public static final int RTN_UNICAST = 1; // 0x1
+ field public static final int RTN_UNREACHABLE = 7; // 0x7
+ }
+
public class RssiCurve implements android.os.Parcelable {
ctor public RssiCurve(int, int, byte[]);
ctor public RssiCurve(int, int, byte[], int);
@@ -3931,6 +4220,158 @@
}
+package android.net.metrics {
+
+ public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event {
+ }
+
+ public static class ApfProgramEvent.Builder {
+ ctor public ApfProgramEvent.Builder();
+ method public android.net.metrics.ApfProgramEvent build();
+ method public android.net.metrics.ApfProgramEvent.Builder setActualLifetime(long);
+ method public android.net.metrics.ApfProgramEvent.Builder setCurrentRas(int);
+ method public android.net.metrics.ApfProgramEvent.Builder setFilteredRas(int);
+ method public android.net.metrics.ApfProgramEvent.Builder setFlags(boolean, boolean);
+ method public android.net.metrics.ApfProgramEvent.Builder setLifetime(long);
+ method public android.net.metrics.ApfProgramEvent.Builder setProgramLength(int);
+ }
+
+ public final class ApfStats implements android.net.metrics.IpConnectivityLog.Event {
+ }
+
+ public static class ApfStats.Builder {
+ ctor public ApfStats.Builder();
+ method public android.net.metrics.ApfStats build();
+ method public android.net.metrics.ApfStats.Builder setDroppedRas(int);
+ method public android.net.metrics.ApfStats.Builder setDurationMs(long);
+ method public android.net.metrics.ApfStats.Builder setMatchingRas(int);
+ method public android.net.metrics.ApfStats.Builder setMaxProgramSize(int);
+ method public android.net.metrics.ApfStats.Builder setParseErrors(int);
+ method public android.net.metrics.ApfStats.Builder setProgramUpdates(int);
+ method public android.net.metrics.ApfStats.Builder setProgramUpdatesAll(int);
+ method public android.net.metrics.ApfStats.Builder setProgramUpdatesAllowingMulticast(int);
+ method public android.net.metrics.ApfStats.Builder setReceivedRas(int);
+ method public android.net.metrics.ApfStats.Builder setZeroLifetimeRas(int);
+ }
+
+ public final class DhcpClientEvent implements android.net.metrics.IpConnectivityLog.Event {
+ }
+
+ public static class DhcpClientEvent.Builder {
+ ctor public DhcpClientEvent.Builder();
+ method public android.net.metrics.DhcpClientEvent build();
+ method public android.net.metrics.DhcpClientEvent.Builder setDurationMs(int);
+ method public android.net.metrics.DhcpClientEvent.Builder setMsg(String);
+ }
+
+ public final class DhcpErrorEvent implements android.net.metrics.IpConnectivityLog.Event {
+ ctor public DhcpErrorEvent(int);
+ method public static int errorCodeWithOption(int, int);
+ field public static final int BOOTP_TOO_SHORT;
+ field public static final int BUFFER_UNDERFLOW;
+ field public static final int DHCP_BAD_MAGIC_COOKIE;
+ field public static final int DHCP_ERROR = 4; // 0x4
+ field public static final int DHCP_INVALID_OPTION_LENGTH;
+ field public static final int DHCP_NO_COOKIE;
+ field public static final int DHCP_NO_MSG_TYPE;
+ field public static final int DHCP_UNKNOWN_MSG_TYPE;
+ field public static final int L2_ERROR = 1; // 0x1
+ field public static final int L2_TOO_SHORT;
+ field public static final int L2_WRONG_ETH_TYPE;
+ field public static final int L3_ERROR = 2; // 0x2
+ field public static final int L3_INVALID_IP;
+ field public static final int L3_NOT_IPV4;
+ field public static final int L3_TOO_SHORT;
+ field public static final int L4_ERROR = 3; // 0x3
+ field public static final int L4_NOT_UDP;
+ field public static final int L4_WRONG_PORT;
+ field public static final int MISC_ERROR = 5; // 0x5
+ field public static final int PARSING_ERROR;
+ field public static final int RECEIVE_ERROR;
+ }
+
+ public class 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);
+ method public boolean log(int, int[], android.net.metrics.IpConnectivityLog.Event);
+ method public boolean log(android.net.metrics.IpConnectivityLog.Event);
+ }
+
+ public static interface IpConnectivityLog.Event extends android.os.Parcelable {
+ }
+
+ public final class IpManagerEvent implements android.net.metrics.IpConnectivityLog.Event {
+ ctor public IpManagerEvent(int, long);
+ field public static final int COMPLETE_LIFECYCLE = 3; // 0x3
+ field public static final int ERROR_INTERFACE_NOT_FOUND = 8; // 0x8
+ field public static final int ERROR_INVALID_PROVISIONING = 7; // 0x7
+ field public static final int ERROR_STARTING_IPREACHABILITYMONITOR = 6; // 0x6
+ field public static final int ERROR_STARTING_IPV4 = 4; // 0x4
+ field public static final int ERROR_STARTING_IPV6 = 5; // 0x5
+ field public static final int PROVISIONING_FAIL = 2; // 0x2
+ field public static final int PROVISIONING_OK = 1; // 0x1
+ }
+
+ public final class IpReachabilityEvent implements android.net.metrics.IpConnectivityLog.Event {
+ ctor public IpReachabilityEvent(int);
+ field public static final int NUD_FAILED = 512; // 0x200
+ field public static final int NUD_FAILED_ORGANIC = 1024; // 0x400
+ field public static final int PROBE = 256; // 0x100
+ field public static final int PROVISIONING_LOST = 768; // 0x300
+ field public static final int PROVISIONING_LOST_ORGANIC = 1280; // 0x500
+ }
+
+ public final class NetworkEvent implements android.net.metrics.IpConnectivityLog.Event {
+ ctor public NetworkEvent(int, long);
+ ctor public NetworkEvent(int);
+ field public static final int NETWORK_CAPTIVE_PORTAL_FOUND = 4; // 0x4
+ field public static final int NETWORK_CONNECTED = 1; // 0x1
+ field public static final int NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND = 12; // 0xc
+ field public static final int NETWORK_DISCONNECTED = 7; // 0x7
+ field public static final int NETWORK_FIRST_VALIDATION_PORTAL_FOUND = 10; // 0xa
+ field public static final int NETWORK_FIRST_VALIDATION_SUCCESS = 8; // 0x8
+ field public static final int NETWORK_LINGER = 5; // 0x5
+ field public static final int NETWORK_REVALIDATION_PORTAL_FOUND = 11; // 0xb
+ field public static final int NETWORK_REVALIDATION_SUCCESS = 9; // 0x9
+ field public static final int NETWORK_UNLINGER = 6; // 0x6
+ field public static final int NETWORK_VALIDATED = 2; // 0x2
+ field public static final int NETWORK_VALIDATION_FAILED = 3; // 0x3
+ }
+
+ 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
+ field public static final int DNS_SUCCESS = 1; // 0x1
+ field public static final int PROBE_DNS = 0; // 0x0
+ field public static final int PROBE_FALLBACK = 4; // 0x4
+ field public static final int PROBE_HTTP = 1; // 0x1
+ field public static final int PROBE_HTTPS = 2; // 0x2
+ field public static final int PROBE_PAC = 3; // 0x3
+ field public static final int PROBE_PRIVDNS = 5; // 0x5
+ }
+
+ public static class ValidationProbeEvent.Builder {
+ ctor public ValidationProbeEvent.Builder();
+ method public android.net.metrics.ValidationProbeEvent build();
+ method public android.net.metrics.ValidationProbeEvent.Builder setDurationMs(long);
+ method public android.net.metrics.ValidationProbeEvent.Builder setProbeType(int, boolean);
+ method public android.net.metrics.ValidationProbeEvent.Builder setReturnCode(int);
+ }
+
+}
+
+package android.net.util {
+
+ public class SocketUtils {
+ method public static void bindSocketToInterface(java.io.FileDescriptor, String) throws android.system.ErrnoException;
+ method public static java.net.SocketAddress makeNetlinkSocketAddress(int, int);
+ method public static java.net.SocketAddress makePacketSocketAddress(short, int);
+ method public static java.net.SocketAddress makePacketSocketAddress(int, byte[]);
+ }
+
+}
+
package android.net.wifi {
public abstract class EasyConnectStatusCallback {
@@ -4172,7 +4613,6 @@
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);
- method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void disableEphemeralNetwork(String);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void forget(int, android.net.wifi.WifiManager.ActionListener);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.util.Pair<android.net.wifi.WifiConfiguration,java.util.Map<java.lang.Integer,java.util.List<android.net.wifi.ScanResult>>>> getAllMatchingWifiConfigs(@NonNull java.util.List<android.net.wifi.ScanResult>);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,java.util.List<android.net.wifi.ScanResult>> getMatchingOsuProviders(java.util.List<android.net.wifi.ScanResult>);
@@ -4406,7 +4846,7 @@
method public abstract void onProvisioningStatus(int);
field public static final int OSU_FAILURE_ADD_PASSPOINT_CONFIGURATION = 22; // 0x16
field public static final int OSU_FAILURE_AP_CONNECTION = 1; // 0x1
- field public static final int OSU_FAILURE_INVALID_SERVER_URL = 8; // 0x8
+ field public static final int OSU_FAILURE_INVALID_URL_FORMAT_FOR_OSU = 8; // 0x8
field public static final int OSU_FAILURE_NO_AAA_SERVER_TRUST_ROOT_NODE = 17; // 0x11
field public static final int OSU_FAILURE_NO_AAA_TRUST_ROOT_CERTIFICATE = 21; // 0x15
field public static final int OSU_FAILURE_NO_OSU_ACTIVITY_FOUND = 14; // 0xe
@@ -4764,6 +5204,12 @@
method public void onResult(android.os.Bundle);
}
+ public class ServiceSpecificException extends java.lang.RuntimeException {
+ ctor public ServiceSpecificException(int, String);
+ ctor public ServiceSpecificException(int);
+ field public final int errorCode;
+ }
+
public final class StatsDimensionsValue implements android.os.Parcelable {
method public int describeContents();
method public boolean getBooleanValue();
@@ -5116,12 +5562,20 @@
field public static final String NAMESPACE_CONTENT_CAPTURE = "content_capture";
field public static final String NAMESPACE_GAME_DRIVER = "game_driver";
field public static final String NAMESPACE_INPUT_NATIVE_BOOT = "input_native_boot";
+ field public static final String NAMESPACE_NETD_NATIVE = "netd_native";
+ field public static final String NAMESPACE_NOTIFICATION_ASSISTANT = "notification_assistant";
}
public static interface DeviceConfig.OnPropertyChangedListener {
method public void onPropertyChanged(String, String, String);
}
+ public static interface DeviceConfig.Telephony {
+ field public static final String NAMESPACE = "telephony";
+ field public static final String PROPERTY_ENABLE_RAMPING_RINGER = "enable_ramping_ringer";
+ field public static final String PROPERTY_RAMPING_RINGER_DURATION = "ramping_duration";
+ }
+
public final class DocumentsContract {
method public static boolean isManageMode(android.net.Uri);
method public static android.net.Uri setManageMode(android.net.Uri);
@@ -5250,8 +5704,23 @@
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void resetToDefaults(@NonNull android.content.ContentResolver, @Nullable String);
field public static final String APP_STANDBY_ENABLED = "app_standby_enabled";
field public static final String AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES = "autofill_compat_mode_allowed_packages";
+ field public static final String CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS = "captive_portal_fallback_probe_specs";
+ field public static final String CAPTIVE_PORTAL_FALLBACK_URL = "captive_portal_fallback_url";
+ field public static final String CAPTIVE_PORTAL_HTTPS_URL = "captive_portal_https_url";
+ field public static final String CAPTIVE_PORTAL_HTTP_URL = "captive_portal_http_url";
+ field public static final String CAPTIVE_PORTAL_MODE = "captive_portal_mode";
+ field public static final int CAPTIVE_PORTAL_MODE_AVOID = 2; // 0x2
+ field public static final int CAPTIVE_PORTAL_MODE_IGNORE = 0; // 0x0
+ field public static final int CAPTIVE_PORTAL_MODE_PROMPT = 1; // 0x1
+ field public static final String CAPTIVE_PORTAL_OTHER_FALLBACK_URLS = "captive_portal_other_fallback_urls";
+ field public static final String CAPTIVE_PORTAL_USER_AGENT = "captive_portal_user_agent";
+ field public static final String CAPTIVE_PORTAL_USE_HTTPS = "captive_portal_use_https";
field public static final String CARRIER_APP_NAMES = "carrier_app_names";
field public static final String CARRIER_APP_WHITELIST = "carrier_app_whitelist";
+ field public static final String DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD = "data_stall_consecutive_dns_timeout_threshold";
+ field public static final String DATA_STALL_EVALUATION_TYPE = "data_stall_evaluation_type";
+ field public static final String DATA_STALL_MIN_EVALUATE_INTERVAL = "data_stall_min_evaluate_interval";
+ field public static final String DATA_STALL_VALID_DNS_TIME_THRESHOLD = "data_stall_valid_dns_time_threshold";
field public static final String DEFAULT_SM_DP_PLUS = "default_sm_dp_plus";
field public static final String DEVICE_DEMO_MODE = "device_demo_mode";
field public static final String DEVICE_PROVISIONING_MOBILE_DATA_ENABLED = "device_provisioning_mobile_data";
@@ -5563,10 +6032,7 @@
method public int getTaskId();
}
- public final class FillResponse 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.service.autofill.augmented.FillResponse> CREATOR;
+ public final class FillResponse {
}
public static final class FillResponse.Builder {
@@ -5580,7 +6046,6 @@
ctor public FillWindow();
method public void destroy();
method public boolean update(@NonNull android.service.autofill.augmented.PresentationParams.Area, @NonNull android.view.View, long);
- field public static final long FLAG_METADATA_ADDRESS = 1L; // 0x1L
}
public abstract class PresentationParams {
@@ -5742,12 +6207,15 @@
method public abstract int onSwitchToSubscription(int, @Nullable String, boolean);
method public abstract int onUpdateSubscriptionNickname(int, String, String);
field public static final String ACTION_BIND_CARRIER_PROVISIONING_SERVICE = "android.service.euicc.action.BIND_CARRIER_PROVISIONING_SERVICE";
+ field public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
field public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
field public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
+ field public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED";
field @Deprecated public static final String ACTION_RESOLVE_CONFIRMATION_CODE = "android.service.euicc.action.RESOLVE_CONFIRMATION_CODE";
field public static final String ACTION_RESOLVE_DEACTIVATE_SIM = "android.service.euicc.action.RESOLVE_DEACTIVATE_SIM";
field public static final String ACTION_RESOLVE_NO_PRIVILEGES = "android.service.euicc.action.RESOLVE_NO_PRIVILEGES";
field public static final String ACTION_RESOLVE_RESOLVABLE_ERRORS = "android.service.euicc.action.RESOLVE_RESOLVABLE_ERRORS";
+ field public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED";
field public static final String CATEGORY_EUICC_UI = "android.service.euicc.category.EUICC_UI";
field public static final String EUICC_SERVICE_INTERFACE = "android.service.euicc.EuiccService";
field public static final String EXTRA_RESOLUTION_ALLOW_POLICY_RULES = "android.service.euicc.extra.RESOLUTION_ALLOW_POLICY_RULES";
@@ -6325,6 +6793,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean isInEmergencyCall();
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging();
method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.WRITE_SECURE_SETTINGS}) public boolean setDefaultDialer(String);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUserSelectedOutgoingPhoneAccount(android.telecom.PhoneAccountHandle);
field public static final String EXTRA_CALL_BACK_INTENT = "android.telecom.extra.CALL_BACK_INTENT";
field public static final String EXTRA_CLEAR_MISSED_CALLS_INTENT = "android.telecom.extra.CLEAR_MISSED_CALLS_INTENT";
field public static final String EXTRA_CONNECTION_SERVICE = "android.telecom.extra.CONNECTION_SERVICE";
@@ -6409,42 +6878,211 @@
}
public final class DataFailCause {
+ field public static final int ACCESS_ATTEMPT_ALREADY_IN_PROGRESS = 2219; // 0x8ab
+ field public static final int ACCESS_BLOCK = 2087; // 0x827
+ field public static final int ACCESS_BLOCK_ALL = 2088; // 0x828
+ field public static final int ACCESS_CLASS_DSAC_REJECTION = 2108; // 0x83c
+ field public static final int ACCESS_CONTROL_LIST_CHECK_FAILURE = 2128; // 0x850
+ field public static final int ACTIVATION_REJECTED_BCM_VIOLATION = 48; // 0x30
field public static final int ACTIVATION_REJECT_GGSN = 30; // 0x1e
field public static final int ACTIVATION_REJECT_UNSPECIFIED = 31; // 0x1f
field public static final int ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED = 65; // 0x41
+ field public static final int APN_DISABLED = 2045; // 0x7fd
+ field public static final int APN_DISALLOWED_ON_ROAMING = 2059; // 0x80b
+ field public static final int APN_MISMATCH = 2054; // 0x806
+ field public static final int APN_PARAMETERS_CHANGED = 2060; // 0x80c
+ field public static final int APN_PENDING_HANDOVER = 2041; // 0x7f9
field public static final int APN_TYPE_CONFLICT = 112; // 0x70
field public static final int AUTH_FAILURE_ON_EMERGENCY_CALL = 122; // 0x7a
+ field public static final int BEARER_HANDLING_NOT_SUPPORTED = 60; // 0x3c
+ field public static final int CALL_DISALLOWED_IN_ROAMING = 2068; // 0x814
+ field public static final int CALL_PREEMPT_BY_EMERGENCY_APN = 127; // 0x7f
+ field public static final int CANNOT_ENCODE_OTA_MESSAGE = 2159; // 0x86f
+ field public static final int CDMA_ALERT_STOP = 2077; // 0x81d
+ field public static final int CDMA_INCOMING_CALL = 2076; // 0x81c
+ field public static final int CDMA_INTERCEPT = 2073; // 0x819
+ field public static final int CDMA_LOCK = 2072; // 0x818
+ field public static final int CDMA_RELEASE_DUE_TO_SO_REJECTION = 2075; // 0x81b
+ field public static final int CDMA_REORDER = 2074; // 0x81a
+ field public static final int CDMA_RETRY_ORDER = 2086; // 0x826
+ field public static final int CHANNEL_ACQUISITION_FAILURE = 2078; // 0x81e
+ field public static final int CLOSE_IN_PROGRESS = 2030; // 0x7ee
+ field public static final int COLLISION_WITH_NETWORK_INITIATED_REQUEST = 56; // 0x38
field public static final int COMPANION_IFACE_IN_USE = 118; // 0x76
+ field public static final int CONCURRENT_SERVICES_INCOMPATIBLE = 2083; // 0x823
+ field public static final int CONCURRENT_SERVICES_NOT_ALLOWED = 2091; // 0x82b
+ field public static final int CONCURRENT_SERVICE_NOT_SUPPORTED_BY_BASE_STATION = 2080; // 0x820
field public static final int CONDITIONAL_IE_ERROR = 100; // 0x64
+ field public static final int CONGESTION = 2106; // 0x83a
+ field public static final int CONNECTION_RELEASED = 2113; // 0x841
+ field public static final int CS_DOMAIN_NOT_AVAILABLE = 2181; // 0x885
+ field public static final int CS_FALLBACK_CALL_ESTABLISHMENT_NOT_ALLOWED = 2188; // 0x88c
+ field public static final int DATA_PLAN_EXPIRED = 2198; // 0x896
+ field public static final int DATA_ROAMING_SETTINGS_DISABLED = 2064; // 0x810
+ field public static final int DATA_SETTINGS_DISABLED = 2063; // 0x80f
+ field public static final int DBM_OR_SMS_IN_PROGRESS = 2211; // 0x8a3
+ field public static final int DDS_SWITCHED = 2065; // 0x811
+ field public static final int DDS_SWITCH_IN_PROGRESS = 2067; // 0x813
+ field public static final int DRB_RELEASED_BY_RRC = 2112; // 0x840
+ field public static final int DS_EXPLICIT_DEACTIVATION = 2125; // 0x84d
+ field public static final int DUAL_SWITCH = 2227; // 0x8b3
+ field public static final int DUN_CALL_DISALLOWED = 2056; // 0x808
+ field public static final int DUPLICATE_BEARER_ID = 2118; // 0x846
+ field public static final int EHRPD_TO_HRPD_FALLBACK = 2049; // 0x801
+ field public static final int EMBMS_NOT_ENABLED = 2193; // 0x891
+ field public static final int EMBMS_REGULAR_DEACTIVATION = 2195; // 0x893
field public static final int EMERGENCY_IFACE_ONLY = 116; // 0x74
+ field public static final int EMERGENCY_MODE = 2221; // 0x8ad
field public static final int EMM_ACCESS_BARRED = 115; // 0x73
field public static final int EMM_ACCESS_BARRED_INFINITE_RETRY = 121; // 0x79
+ field public static final int EMM_ATTACH_FAILED = 2115; // 0x843
+ field public static final int EMM_ATTACH_STARTED = 2116; // 0x844
+ field public static final int EMM_DETACHED = 2114; // 0x842
+ field public static final int EMM_T3417_EXPIRED = 2130; // 0x852
+ field public static final int EMM_T3417_EXT_EXPIRED = 2131; // 0x853
+ field public static final int EPS_SERVICES_AND_NON_EPS_SERVICES_NOT_ALLOWED = 2178; // 0x882
+ field public static final int EPS_SERVICES_NOT_ALLOWED_IN_PLMN = 2179; // 0x883
field public static final int ERROR_UNSPECIFIED = 65535; // 0xffff
+ field public static final int ESM_BAD_OTA_MESSAGE = 2122; // 0x84a
+ field public static final int ESM_BEARER_DEACTIVATED_TO_SYNC_WITH_NETWORK = 2120; // 0x848
+ field public static final int ESM_COLLISION_SCENARIOS = 2119; // 0x847
+ field public static final int ESM_CONTEXT_TRANSFERRED_DUE_TO_IRAT = 2124; // 0x84c
+ field public static final int ESM_DOWNLOAD_SERVER_REJECTED_THE_CALL = 2123; // 0x84b
+ field public static final int ESM_FAILURE = 2182; // 0x886
field public static final int ESM_INFO_NOT_RECEIVED = 53; // 0x35
+ field public static final int ESM_LOCAL_CAUSE_NONE = 2126; // 0x84e
+ field public static final int ESM_NW_ACTIVATED_DED_BEARER_WITH_ID_OF_DEF_BEARER = 2121; // 0x849
+ field public static final int ESM_PROCEDURE_TIME_OUT = 2155; // 0x86b
+ field public static final int ESM_UNKNOWN_EPS_BEARER_CONTEXT = 2111; // 0x83f
+ field public static final int EVDO_CONNECTION_DENY_BY_BILLING_OR_AUTHENTICATION_FAILURE = 2201; // 0x899
+ field public static final int EVDO_CONNECTION_DENY_BY_GENERAL_OR_NETWORK_BUSY = 2200; // 0x898
+ field public static final int EVDO_HDR_CHANGED = 2202; // 0x89a
+ field public static final int EVDO_HDR_CONNECTION_SETUP_TIMEOUT = 2206; // 0x89e
+ field public static final int EVDO_HDR_EXITED = 2203; // 0x89b
+ field public static final int EVDO_HDR_NO_SESSION = 2204; // 0x89c
+ field public static final int EVDO_USING_GPS_FIX_INSTEAD_OF_HDR_CALL = 2205; // 0x89d
+ field public static final int FADE = 2217; // 0x8a9
+ field public static final int FAILED_TO_ACQUIRE_COLOCATED_HDR = 2207; // 0x89f
field public static final int FEATURE_NOT_SUPP = 40; // 0x28
field public static final int FILTER_SEMANTIC_ERROR = 44; // 0x2c
field public static final int FILTER_SYTAX_ERROR = 45; // 0x2d
+ field public static final int FORBIDDEN_APN_NAME = 2066; // 0x812
field public static final int GPRS_REGISTRATION_FAIL = -2; // 0xfffffffe
+ field public static final int GPRS_SERVICES_AND_NON_GPRS_SERVICES_NOT_ALLOWED = 2097; // 0x831
+ field public static final int GPRS_SERVICES_NOT_ALLOWED = 2098; // 0x832
+ field public static final int GPRS_SERVICES_NOT_ALLOWED_IN_THIS_PLMN = 2103; // 0x837
+ field public static final int HANDOFF_PREFERENCE_CHANGED = 2251; // 0x8cb
+ field public static final int HDR_ACCESS_FAILURE = 2213; // 0x8a5
+ field public static final int HDR_FADE = 2212; // 0x8a4
+ field public static final int HDR_NO_LOCK_GRANTED = 2210; // 0x8a2
field public static final int IFACE_AND_POL_FAMILY_MISMATCH = 120; // 0x78
field public static final int IFACE_MISMATCH = 117; // 0x75
+ field public static final int ILLEGAL_ME = 2096; // 0x830
+ field public static final int ILLEGAL_MS = 2095; // 0x82f
+ field public static final int IMEI_NOT_ACCEPTED = 2177; // 0x881
+ field public static final int IMPLICITLY_DETACHED = 2100; // 0x834
+ field public static final int IMSI_UNKNOWN_IN_HOME_SUBSCRIBER_SERVER = 2176; // 0x880
+ field public static final int INCOMING_CALL_REJECTED = 2092; // 0x82c
field public static final int INSUFFICIENT_RESOURCES = 26; // 0x1a
+ field public static final int INTERFACE_IN_USE = 2058; // 0x80a
field public static final int INTERNAL_CALL_PREEMPT_BY_HIGH_PRIO_APN = 114; // 0x72
+ field public static final int INTERNAL_EPC_NONEPC_TRANSITION = 2057; // 0x809
+ field public static final int INVALID_CONNECTION_ID = 2156; // 0x86c
+ field public static final int INVALID_DNS_ADDR = 123; // 0x7b
+ field public static final int INVALID_EMM_STATE = 2190; // 0x88e
field public static final int INVALID_MANDATORY_INFO = 96; // 0x60
+ field public static final int INVALID_MODE = 2223; // 0x8af
field public static final int INVALID_PCSCF_ADDR = 113; // 0x71
+ field public static final int INVALID_PCSCF_OR_DNS_ADDRESS = 124; // 0x7c
+ field public static final int INVALID_PRIMARY_NSAPI = 2158; // 0x86e
+ field public static final int INVALID_SIM_STATE = 2224; // 0x8b0
field public static final int INVALID_TRANSACTION_ID = 81; // 0x51
+ field public static final int IPV6_ADDRESS_TRANSFER_FAILED = 2047; // 0x7ff
+ field public static final int IPV6_PREFIX_UNAVAILABLE = 2250; // 0x8ca
field public static final int IP_ADDRESS_MISMATCH = 119; // 0x77
+ field public static final int IP_VERSION_MISMATCH = 2055; // 0x807
+ field public static final int IRAT_HANDOVER_FAILED = 2194; // 0x892
+ field public static final int IS707B_MAX_ACCESS_PROBES = 2089; // 0x829
+ field public static final int LIMITED_TO_IPV4 = 2234; // 0x8ba
+ field public static final int LIMITED_TO_IPV6 = 2235; // 0x8bb
field public static final int LLC_SNDCP = 25; // 0x19
+ field public static final int LOCAL_END = 2215; // 0x8a7
+ field public static final int LOCATION_AREA_NOT_ALLOWED = 2102; // 0x836
field public static final int LOST_CONNECTION = 65540; // 0x10004
+ field public static final int LOWER_LAYER_REGISTRATION_FAILURE = 2197; // 0x895
+ field public static final int LOW_POWER_MODE_OR_POWERING_DOWN = 2044; // 0x7fc
+ field public static final int LTE_NAS_SERVICE_REQUEST_FAILED = 2117; // 0x845
+ field public static final int LTE_THROTTLING_NOT_REQUIRED = 2127; // 0x84f
+ field public static final int MAC_FAILURE = 2183; // 0x887
+ field public static final int MAXIMIUM_NSAPIS_EXCEEDED = 2157; // 0x86d
+ field public static final int MAXINUM_SIZE_OF_L2_MESSAGE_EXCEEDED = 2166; // 0x876
+ field public static final int MAX_ACCESS_PROBE = 2079; // 0x81f
+ field public static final int MAX_IPV4_CONNECTIONS = 2052; // 0x804
+ field public static final int MAX_IPV6_CONNECTIONS = 2053; // 0x805
+ field public static final int MAX_PPP_INACTIVITY_TIMER_EXPIRED = 2046; // 0x7fe
field public static final int MESSAGE_INCORRECT_SEMANTIC = 95; // 0x5f
field public static final int MESSAGE_TYPE_UNSUPPORTED = 97; // 0x61
+ field public static final int MIP_CONFIG_FAILURE = 2050; // 0x802
+ field public static final int MIP_FA_ADMIN_PROHIBITED = 2001; // 0x7d1
+ field public static final int MIP_FA_DELIVERY_STYLE_NOT_SUPPORTED = 2012; // 0x7dc
+ field public static final int MIP_FA_ENCAPSULATION_UNAVAILABLE = 2008; // 0x7d8
+ field public static final int MIP_FA_HOME_AGENT_AUTHENTICATION_FAILURE = 2004; // 0x7d4
+ field public static final int MIP_FA_INSUFFICIENT_RESOURCES = 2002; // 0x7d2
+ field public static final int MIP_FA_MALFORMED_REPLY = 2007; // 0x7d7
+ field public static final int MIP_FA_MALFORMED_REQUEST = 2006; // 0x7d6
+ field public static final int MIP_FA_MISSING_CHALLENGE = 2017; // 0x7e1
+ field public static final int MIP_FA_MISSING_HOME_ADDRESS = 2015; // 0x7df
+ field public static final int MIP_FA_MISSING_HOME_AGENT = 2014; // 0x7de
+ field public static final int MIP_FA_MISSING_NAI = 2013; // 0x7dd
+ field public static final int MIP_FA_MOBILE_NODE_AUTHENTICATION_FAILURE = 2003; // 0x7d3
+ field public static final int MIP_FA_REASON_UNSPECIFIED = 2000; // 0x7d0
+ field public static final int MIP_FA_REQUESTED_LIFETIME_TOO_LONG = 2005; // 0x7d5
+ field public static final int MIP_FA_REVERSE_TUNNEL_IS_MANDATORY = 2011; // 0x7db
+ field public static final int MIP_FA_REVERSE_TUNNEL_UNAVAILABLE = 2010; // 0x7da
+ field public static final int MIP_FA_STALE_CHALLENGE = 2018; // 0x7e2
+ field public static final int MIP_FA_UNKNOWN_CHALLENGE = 2016; // 0x7e0
+ field public static final int MIP_FA_VJ_HEADER_COMPRESSION_UNAVAILABLE = 2009; // 0x7d9
+ field public static final int MIP_HA_ADMIN_PROHIBITED = 2020; // 0x7e4
+ field public static final int MIP_HA_ENCAPSULATION_UNAVAILABLE = 2029; // 0x7ed
+ field public static final int MIP_HA_FOREIGN_AGENT_AUTHENTICATION_FAILURE = 2023; // 0x7e7
+ field public static final int MIP_HA_INSUFFICIENT_RESOURCES = 2021; // 0x7e5
+ field public static final int MIP_HA_MALFORMED_REQUEST = 2025; // 0x7e9
+ field public static final int MIP_HA_MOBILE_NODE_AUTHENTICATION_FAILURE = 2022; // 0x7e6
+ field public static final int MIP_HA_REASON_UNSPECIFIED = 2019; // 0x7e3
+ field public static final int MIP_HA_REGISTRATION_ID_MISMATCH = 2024; // 0x7e8
+ field public static final int MIP_HA_REVERSE_TUNNEL_IS_MANDATORY = 2028; // 0x7ec
+ field public static final int MIP_HA_REVERSE_TUNNEL_UNAVAILABLE = 2027; // 0x7eb
+ field public static final int MIP_HA_UNKNOWN_HOME_AGENT_ADDRESS = 2026; // 0x7ea
field public static final int MISSING_UNKNOWN_APN = 27; // 0x1b
+ field public static final int MODEM_APP_PREEMPTED = 2032; // 0x7f0
+ field public static final int MODEM_RESTART = 2037; // 0x7f5
+ field public static final int MSC_TEMPORARILY_NOT_REACHABLE = 2180; // 0x884
field public static final int MSG_AND_PROTOCOL_STATE_UNCOMPATIBLE = 101; // 0x65
field public static final int MSG_TYPE_NONCOMPATIBLE_STATE = 98; // 0x62
+ field public static final int MS_IDENTITY_CANNOT_BE_DERIVED_BY_THE_NETWORK = 2099; // 0x833
+ field public static final int MULTIPLE_PDP_CALL_NOT_ALLOWED = 2192; // 0x890
field public static final int MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED = 55; // 0x37
+ field public static final int NAS_LAYER_FAILURE = 2191; // 0x88f
+ field public static final int NAS_REQUEST_REJECTED_BY_NETWORK = 2167; // 0x877
field public static final int NAS_SIGNALLING = 14; // 0xe
field public static final int NETWORK_FAILURE = 38; // 0x26
+ field public static final int NETWORK_INITIATED_DETACH_NO_AUTO_REATTACH = 2154; // 0x86a
+ field public static final int NETWORK_INITIATED_DETACH_WITH_AUTO_REATTACH = 2153; // 0x869
+ field public static final int NETWORK_INITIATED_TERMINATION = 2031; // 0x7ef
field public static final int NONE = 0; // 0x0
+ field public static final int NON_IP_NOT_SUPPORTED = 2069; // 0x815
+ field public static final int NORMAL_RELEASE = 2218; // 0x8aa
+ field public static final int NO_CDMA_SERVICE = 2084; // 0x824
+ field public static final int NO_COLLOCATED_HDR = 2225; // 0x8b1
+ field public static final int NO_EPS_BEARER_CONTEXT_ACTIVATED = 2189; // 0x88d
+ field public static final int NO_GPRS_CONTEXT = 2094; // 0x82e
+ field public static final int NO_HYBRID_HDR_SERVICE = 2209; // 0x8a1
+ field public static final int NO_PDP_CONTEXT_ACTIVATED = 2107; // 0x83b
+ field public static final int NO_RESPONSE_FROM_BASE_STATION = 2081; // 0x821
+ field public static final int NO_SERVICE = 2216; // 0x8a8
+ field public static final int NO_SERVICE_ON_GATEWAY = 2093; // 0x82d
field public static final int NSAPI_IN_USE = 35; // 0x23
+ field public static final int NULL_APN_DISALLOWED = 2061; // 0x80d
field public static final int OEM_DCFAILCAUSE_1 = 4097; // 0x1001
field public static final int OEM_DCFAILCAUSE_10 = 4106; // 0x100a
field public static final int OEM_DCFAILCAUSE_11 = 4107; // 0x100b
@@ -6460,33 +7098,126 @@
field public static final int OEM_DCFAILCAUSE_7 = 4103; // 0x1007
field public static final int OEM_DCFAILCAUSE_8 = 4104; // 0x1008
field public static final int OEM_DCFAILCAUSE_9 = 4105; // 0x1009
+ field public static final int ONLY_IPV4V6_ALLOWED = 57; // 0x39
field public static final int ONLY_IPV4_ALLOWED = 50; // 0x32
field public static final int ONLY_IPV6_ALLOWED = 51; // 0x33
+ field public static final int ONLY_NON_IP_ALLOWED = 58; // 0x3a
field public static final int ONLY_SINGLE_BEARER_ALLOWED = 52; // 0x34
field public static final int OPERATOR_BARRED = 8; // 0x8
+ field public static final int OTASP_COMMIT_IN_PROGRESS = 2208; // 0x8a0
field public static final int PDN_CONN_DOES_NOT_EXIST = 54; // 0x36
+ field public static final int PDN_INACTIVITY_TIMER_EXPIRED = 2051; // 0x803
+ field public static final int PDN_IPV4_CALL_DISALLOWED = 2033; // 0x7f1
+ field public static final int PDN_IPV4_CALL_THROTTLED = 2034; // 0x7f2
+ field public static final int PDN_IPV6_CALL_DISALLOWED = 2035; // 0x7f3
+ field public static final int PDN_IPV6_CALL_THROTTLED = 2036; // 0x7f4
+ field public static final int PDN_NON_IP_CALL_DISALLOWED = 2071; // 0x817
+ field public static final int PDN_NON_IP_CALL_THROTTLED = 2070; // 0x816
+ field public static final int PDP_ACTIVATE_MAX_RETRY_FAILED = 2109; // 0x83d
+ field public static final int PDP_DUPLICATE = 2104; // 0x838
+ field public static final int PDP_ESTABLISH_TIMEOUT_EXPIRED = 2161; // 0x871
+ field public static final int PDP_INACTIVE_TIMEOUT_EXPIRED = 2163; // 0x873
+ field public static final int PDP_LOWERLAYER_ERROR = 2164; // 0x874
+ field public static final int PDP_MODIFY_COLLISION = 2165; // 0x875
+ field public static final int PDP_MODIFY_TIMEOUT_EXPIRED = 2162; // 0x872
+ field public static final int PDP_PPP_NOT_SUPPORTED = 2038; // 0x7f6
field public static final int PDP_WITHOUT_ACTIVE_TFT = 46; // 0x2e
+ field public static final int PHONE_IN_USE = 2222; // 0x8ae
+ field public static final int PHYSICAL_LINK_CLOSE_IN_PROGRESS = 2040; // 0x7f8
+ field public static final int PLMN_NOT_ALLOWED = 2101; // 0x835
+ field public static final int PPP_AUTH_FAILURE = 2229; // 0x8b5
+ field public static final int PPP_CHAP_FAILURE = 2232; // 0x8b8
+ field public static final int PPP_CLOSE_IN_PROGRESS = 2233; // 0x8b9
+ field public static final int PPP_OPTION_MISMATCH = 2230; // 0x8b6
+ field public static final int PPP_PAP_FAILURE = 2231; // 0x8b7
+ field public static final int PPP_TIMEOUT = 2228; // 0x8b4
field public static final int PREF_RADIO_TECH_CHANGED = -4; // 0xfffffffc
+ field public static final int PROFILE_BEARER_INCOMPATIBLE = 2042; // 0x7fa
field public static final int PROTOCOL_ERRORS = 111; // 0x6f
field public static final int QOS_NOT_ACCEPTED = 37; // 0x25
+ field public static final int RADIO_ACCESS_BEARER_FAILURE = 2110; // 0x83e
+ field public static final int RADIO_ACCESS_BEARER_SETUP_FAILURE = 2160; // 0x870
field public static final int RADIO_NOT_AVAILABLE = 65537; // 0x10001
field public static final int RADIO_POWER_OFF = -5; // 0xfffffffb
+ field public static final int REDIRECTION_OR_HANDOFF_IN_PROGRESS = 2220; // 0x8ac
field public static final int REGISTRATION_FAIL = -1; // 0xffffffff
field public static final int REGULAR_DEACTIVATION = 36; // 0x24
+ field public static final int REJECTED_BY_BASE_STATION = 2082; // 0x822
+ field public static final int RRC_CONNECTION_ABORTED_AFTER_HANDOVER = 2173; // 0x87d
+ field public static final int RRC_CONNECTION_ABORTED_AFTER_IRAT_CELL_CHANGE = 2174; // 0x87e
+ field public static final int RRC_CONNECTION_ABORTED_DUE_TO_IRAT_CHANGE = 2171; // 0x87b
+ field public static final int RRC_CONNECTION_ABORTED_DURING_IRAT_CELL_CHANGE = 2175; // 0x87f
+ field public static final int RRC_CONNECTION_ABORT_REQUEST = 2151; // 0x867
+ field public static final int RRC_CONNECTION_ACCESS_BARRED = 2139; // 0x85b
+ field public static final int RRC_CONNECTION_ACCESS_STRATUM_FAILURE = 2137; // 0x859
+ field public static final int RRC_CONNECTION_ANOTHER_PROCEDURE_IN_PROGRESS = 2138; // 0x85a
+ field public static final int RRC_CONNECTION_CELL_NOT_CAMPED = 2144; // 0x860
+ field public static final int RRC_CONNECTION_CELL_RESELECTION = 2140; // 0x85c
+ field public static final int RRC_CONNECTION_CONFIG_FAILURE = 2141; // 0x85d
+ field public static final int RRC_CONNECTION_INVALID_REQUEST = 2168; // 0x878
+ field public static final int RRC_CONNECTION_LINK_FAILURE = 2143; // 0x85f
+ field public static final int RRC_CONNECTION_NORMAL_RELEASE = 2147; // 0x863
+ field public static final int RRC_CONNECTION_OUT_OF_SERVICE_DURING_CELL_REGISTER = 2150; // 0x866
+ field public static final int RRC_CONNECTION_RADIO_LINK_FAILURE = 2148; // 0x864
+ field public static final int RRC_CONNECTION_REESTABLISHMENT_FAILURE = 2149; // 0x865
+ field public static final int RRC_CONNECTION_REJECT_BY_NETWORK = 2146; // 0x862
+ field public static final int RRC_CONNECTION_RELEASED_SECURITY_NOT_ACTIVE = 2172; // 0x87c
+ field public static final int RRC_CONNECTION_RF_UNAVAILABLE = 2170; // 0x87a
+ field public static final int RRC_CONNECTION_SYSTEM_INFORMATION_BLOCK_READ_ERROR = 2152; // 0x868
+ field public static final int RRC_CONNECTION_SYSTEM_INTERVAL_FAILURE = 2145; // 0x861
+ field public static final int RRC_CONNECTION_TIMER_EXPIRED = 2142; // 0x85e
+ field public static final int RRC_CONNECTION_TRACKING_AREA_ID_CHANGED = 2169; // 0x879
+ field public static final int RRC_UPLINK_CONNECTION_RELEASE = 2134; // 0x856
+ field public static final int RRC_UPLINK_DATA_TRANSMISSION_FAILURE = 2132; // 0x854
+ field public static final int RRC_UPLINK_DELIVERY_FAILED_DUE_TO_HANDOVER = 2133; // 0x855
+ field public static final int RRC_UPLINK_ERROR_REQUEST_FROM_NAS = 2136; // 0x858
+ field public static final int RRC_UPLINK_RADIO_LINK_FAILURE = 2135; // 0x857
+ field public static final int RUIM_NOT_PRESENT = 2085; // 0x825
+ field public static final int SECURITY_MODE_REJECTED = 2186; // 0x88a
+ field public static final int SERVICE_NOT_ALLOWED_ON_PLMN = 2129; // 0x851
field public static final int SERVICE_OPTION_NOT_SUBSCRIBED = 33; // 0x21
field public static final int SERVICE_OPTION_NOT_SUPPORTED = 32; // 0x20
field public static final int SERVICE_OPTION_OUT_OF_ORDER = 34; // 0x22
field public static final int SIGNAL_LOST = -3; // 0xfffffffd
+ field public static final int SIM_CARD_CHANGED = 2043; // 0x7fb
+ field public static final int SYNCHRONIZATION_FAILURE = 2184; // 0x888
+ field public static final int TEST_LOOPBACK_REGULAR_DEACTIVATION = 2196; // 0x894
field public static final int TETHERED_CALL_ACTIVE = -6; // 0xfffffffa
field public static final int TFT_SEMANTIC_ERROR = 41; // 0x29
field public static final int TFT_SYTAX_ERROR = 42; // 0x2a
+ field public static final int THERMAL_EMERGENCY = 2090; // 0x82a
+ field public static final int THERMAL_MITIGATION = 2062; // 0x80e
+ field public static final int TRAT_SWAP_FAILED = 2048; // 0x800
+ field public static final int UE_INITIATED_DETACH_OR_DISCONNECT = 128; // 0x80
+ field public static final int UE_IS_ENTERING_POWERSAVE_MODE = 2226; // 0x8b2
+ field public static final int UE_RAT_CHANGE = 2105; // 0x839
+ field public static final int UE_SECURITY_CAPABILITIES_MISMATCH = 2185; // 0x889
+ field public static final int UMTS_HANDOVER_TO_IWLAN = 2199; // 0x897
field public static final int UMTS_REACTIVATION_REQ = 39; // 0x27
+ field public static final int UNACCEPTABLE_NON_EPS_AUTHENTICATION = 2187; // 0x88b
field public static final int UNKNOWN = 65536; // 0x10000
field public static final int UNKNOWN_INFO_ELEMENT = 99; // 0x63
field public static final int UNKNOWN_PDP_ADDRESS_TYPE = 28; // 0x1c
field public static final int UNKNOWN_PDP_CONTEXT = 43; // 0x2b
+ field public static final int UNPREFERRED_RAT = 2039; // 0x7f7
+ field public static final int UNSUPPORTED_1X_PREV = 2214; // 0x8a6
field public static final int UNSUPPORTED_APN_IN_CURRENT_PLMN = 66; // 0x42
+ field public static final int UNSUPPORTED_QCI_VALUE = 59; // 0x3b
field public static final int USER_AUTHENTICATION = 29; // 0x1d
+ field public static final int VSNCP_ADMINISTRATIVELY_PROHIBITED = 2245; // 0x8c5
+ field public static final int VSNCP_APN_UNATHORIZED = 2238; // 0x8be
+ field public static final int VSNCP_GEN_ERROR = 2237; // 0x8bd
+ field public static final int VSNCP_INSUFFICIENT_PARAMETERS = 2243; // 0x8c3
+ field public static final int VSNCP_NO_PDN_GATEWAY_ADDRESS = 2240; // 0x8c0
+ field public static final int VSNCP_PDN_EXISTS_FOR_THIS_APN = 2248; // 0x8c8
+ field public static final int VSNCP_PDN_GATEWAY_REJECT = 2242; // 0x8c2
+ field public static final int VSNCP_PDN_GATEWAY_UNREACHABLE = 2241; // 0x8c1
+ field public static final int VSNCP_PDN_ID_IN_USE = 2246; // 0x8c6
+ field public static final int VSNCP_PDN_LIMIT_EXCEEDED = 2239; // 0x8bf
+ field public static final int VSNCP_RECONNECT_NOT_ALLOWED = 2249; // 0x8c9
+ field public static final int VSNCP_RESOURCE_UNAVAILABLE = 2244; // 0x8c4
+ field public static final int VSNCP_SUBSCRIBER_LIMITATION = 2247; // 0x8c7
+ field public static final int VSNCP_TIMEOUT = 2236; // 0x8bc
}
public class DisconnectCause {
@@ -6926,6 +7657,7 @@
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public int getRadioPowerState();
method public int getSimApplicationState();
method public int getSimCardState();
+ method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getSimLocale();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSupportedRadioAccessFamily();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.UiccCardInfo[] getUiccCardsInfo();
@@ -7228,9 +7960,12 @@
method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDefaultDownloadableSubscriptionList(android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDownloadableSubscriptionMetadata(android.telephony.euicc.DownloadableSubscription, android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public int getOtaStatus();
+ field public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
field @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public static final String ACTION_OTA_STATUS_CHANGED = "android.telephony.euicc.action.OTA_STATUS_CHANGED";
field public static final String ACTION_PROFILE_SELECTION = "android.telephony.euicc.action.PROFILE_SELECTION";
field public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.telephony.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
+ field public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED";
+ field public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED";
field public static final int EUICC_ACTIVATION_TYPE_BACKUP = 2; // 0x2
field public static final int EUICC_ACTIVATION_TYPE_DEFAULT = 1; // 0x1
field public static final int EUICC_ACTIVATION_TYPE_TRANSFER = 3; // 0x3
@@ -7241,7 +7976,10 @@
field public static final int EUICC_OTA_SUCCEEDED = 3; // 0x3
field public static final String EXTRA_ACTIVATION_TYPE = "android.telephony.euicc.extra.ACTIVATION_TYPE";
field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS";
+ field public static final String EXTRA_ENABLE_SUBSCRIPTION = "android.telephony.euicc.extra.ENABLE_SUBSCRIPTION";
field public static final String EXTRA_FORCE_PROVISION = "android.telephony.euicc.extra.FORCE_PROVISION";
+ field public static final String EXTRA_SUBSCRIPTION_ID = "android.telephony.euicc.extra.SUBSCRIPTION_ID";
+ field public static final String EXTRA_SUBSCRIPTION_NICKNAME = "android.telephony.euicc.extra.SUBSCRIPTION_NICKNAME";
}
@IntDef(prefix={"EUICC_OTA_"}, value={android.telephony.euicc.EuiccManager.EUICC_OTA_IN_PROGRESS, android.telephony.euicc.EuiccManager.EUICC_OTA_FAILED, android.telephony.euicc.EuiccManager.EUICC_OTA_SUCCEEDED, android.telephony.euicc.EuiccManager.EUICC_OTA_NOT_NEEDED, android.telephony.euicc.EuiccManager.EUICC_OTA_STATUS_UNAVAILABLE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccManager.OtaStatus {
@@ -8296,6 +9034,22 @@
package android.util {
+ public class DocumentsStatsLog {
+ method public static void logActivityLaunch(int, boolean, int, int);
+ method public static void logFileOperation(int, int);
+ method public static void logFileOperationCanceled(int);
+ method public static void logFileOperationCopyMoveMode(int, int);
+ method public static void logFileOperationFailure(int, int);
+ method public static void logFilePick(int, long, int, boolean, int, int, int);
+ method public static void logInvalidScopedAccessRequest(int);
+ method public static void logPickerLaunchedFrom(String);
+ method public static void logRootVisited(int, int);
+ method public static void logSearchMode(int);
+ method public static void logSearchType(int);
+ method public static void logStartupMs(int);
+ method public static void logUserAction(int);
+ }
+
public class EventLog {
method public static void readEventsOnWrapping(int[], long, java.util.Collection<android.util.EventLog.Event>) throws java.io.IOException;
}
@@ -8336,6 +9090,18 @@
}
+package android.view.autofill {
+
+ public final class AutofillManager {
+ method @NonNull public java.util.Set<android.content.ComponentName> getAugmentedAutofillDisabledActivities();
+ method @NonNull public java.util.Set<java.lang.String> getAugmentedAutofillDisabledPackages();
+ method public void setActivityAugmentedAutofillEnabled(@NonNull android.content.ComponentName, boolean);
+ method public void setAugmentedAutofillWhitelist(@Nullable java.util.List<java.lang.String>, @Nullable java.util.List<android.content.ComponentName>);
+ method public void setPackageAugmentedAutofillEnabled(@NonNull String, boolean);
+ }
+
+}
+
package android.view.contentcapture {
public final class ContentCaptureContext implements android.os.Parcelable {
@@ -8791,6 +9557,10 @@
field public final android.content.pm.Signature[] signatures;
}
+ public abstract class WebViewRenderer {
+ ctor public WebViewRenderer();
+ }
+
public final class WebViewUpdateService {
method public static android.webkit.WebViewProviderInfo[] getAllWebViewPackages();
method public static String getCurrentWebViewPackageName();
diff --git a/api/test-current.txt b/api/test-current.txt
index fb25d13..c746882 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -352,6 +352,10 @@
method public boolean isReservedSupported(@NonNull java.util.UUID);
}
+ public final class UsageStatsManager {
+ method public void forceUsageSourceSettingRead();
+ }
+
}
package android.bluetooth {
@@ -772,6 +776,11 @@
field public static final java.util.UUID EFFECT_TYPE_NULL;
}
+ public static class AudioEffect.Descriptor {
+ ctor public AudioEffect.Descriptor(android.os.Parcel);
+ method public void writeToParcel(android.os.Parcel);
+ }
+
public static interface AudioEffect.OnParameterChangeListener {
method public void onParameterChange(android.media.audiofx.AudioEffect, int, byte[], byte[]);
}
@@ -780,13 +789,64 @@
package android.net {
+ public class CaptivePortal implements android.os.Parcelable {
+ ctor public CaptivePortal(android.os.IBinder);
+ method public void useNetwork();
+ field public static final int APP_RETURN_DISMISSED = 0; // 0x0
+ field public static final int APP_RETURN_UNWANTED = 1; // 0x1
+ field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2
+ }
+
+ public class ConnectivityManager {
+ field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC";
+ field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT";
+ }
+
public final class IpSecManager {
field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
}
+ public class LinkAddress implements android.os.Parcelable {
+ method public boolean isGlobalPreferred();
+ method public boolean isIPv4();
+ method public boolean isIPv6();
+ method public boolean isSameAddressAs(android.net.LinkAddress);
+ }
+
+ public final class LinkProperties implements android.os.Parcelable {
+ method public boolean addDnsServer(java.net.InetAddress);
+ method public String getTcpBufferSizes();
+ method public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers();
+ method public boolean hasGlobalIPv6Address();
+ method public boolean hasIPv4Address();
+ method public boolean hasIPv6DefaultRoute();
+ method public boolean isIPv4Provisioned();
+ method public boolean isIPv6Provisioned();
+ method public boolean isProvisioned();
+ method public boolean isReachable(java.net.InetAddress);
+ method public boolean removeDnsServer(java.net.InetAddress);
+ method public boolean removeRoute(android.net.RouteInfo);
+ method public void setPrivateDnsServerName(@Nullable String);
+ method public void setTcpBufferSizes(String);
+ method public void setUsePrivateDns(boolean);
+ method public void setValidatedPrivateDnsServers(java.util.Collection<java.net.InetAddress>);
+ }
+
+ public class Network implements android.os.Parcelable {
+ method public android.net.Network getPrivateDnsBypassingCopy();
+ }
+
public final class NetworkCapabilities implements android.os.Parcelable {
method public int[] getCapabilities();
method public int[] getTransportTypes();
+ method public boolean satisfiedByNetworkCapabilities(android.net.NetworkCapabilities);
+ }
+
+ public final class RouteInfo implements android.os.Parcelable {
+ method public int getType();
+ field public static final int RTN_THROW = 9; // 0x9
+ field public static final int RTN_UNICAST = 1; // 0x1
+ field public static final int RTN_UNREACHABLE = 7; // 0x7
}
public class TrafficStats {
@@ -798,6 +858,147 @@
}
+package android.net.metrics {
+
+ public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event {
+ }
+
+ public static class ApfProgramEvent.Builder {
+ ctor public ApfProgramEvent.Builder();
+ method public android.net.metrics.ApfProgramEvent build();
+ method public android.net.metrics.ApfProgramEvent.Builder setActualLifetime(long);
+ method public android.net.metrics.ApfProgramEvent.Builder setCurrentRas(int);
+ method public android.net.metrics.ApfProgramEvent.Builder setFilteredRas(int);
+ method public android.net.metrics.ApfProgramEvent.Builder setFlags(boolean, boolean);
+ method public android.net.metrics.ApfProgramEvent.Builder setLifetime(long);
+ method public android.net.metrics.ApfProgramEvent.Builder setProgramLength(int);
+ }
+
+ public final class ApfStats implements android.net.metrics.IpConnectivityLog.Event {
+ }
+
+ public static class ApfStats.Builder {
+ ctor public ApfStats.Builder();
+ method public android.net.metrics.ApfStats build();
+ method public android.net.metrics.ApfStats.Builder setDroppedRas(int);
+ method public android.net.metrics.ApfStats.Builder setDurationMs(long);
+ method public android.net.metrics.ApfStats.Builder setMatchingRas(int);
+ method public android.net.metrics.ApfStats.Builder setMaxProgramSize(int);
+ method public android.net.metrics.ApfStats.Builder setParseErrors(int);
+ method public android.net.metrics.ApfStats.Builder setProgramUpdates(int);
+ method public android.net.metrics.ApfStats.Builder setProgramUpdatesAll(int);
+ method public android.net.metrics.ApfStats.Builder setProgramUpdatesAllowingMulticast(int);
+ method public android.net.metrics.ApfStats.Builder setReceivedRas(int);
+ method public android.net.metrics.ApfStats.Builder setZeroLifetimeRas(int);
+ }
+
+ public final class DhcpClientEvent implements android.net.metrics.IpConnectivityLog.Event {
+ }
+
+ public static class DhcpClientEvent.Builder {
+ ctor public DhcpClientEvent.Builder();
+ method public android.net.metrics.DhcpClientEvent build();
+ method public android.net.metrics.DhcpClientEvent.Builder setDurationMs(int);
+ method public android.net.metrics.DhcpClientEvent.Builder setMsg(String);
+ }
+
+ public final class DhcpErrorEvent implements android.net.metrics.IpConnectivityLog.Event {
+ ctor public DhcpErrorEvent(int);
+ method public static int errorCodeWithOption(int, int);
+ field public static final int BOOTP_TOO_SHORT;
+ field public static final int BUFFER_UNDERFLOW;
+ field public static final int DHCP_BAD_MAGIC_COOKIE;
+ field public static final int DHCP_ERROR = 4; // 0x4
+ field public static final int DHCP_INVALID_OPTION_LENGTH;
+ field public static final int DHCP_NO_COOKIE;
+ field public static final int DHCP_NO_MSG_TYPE;
+ field public static final int DHCP_UNKNOWN_MSG_TYPE;
+ field public static final int L2_ERROR = 1; // 0x1
+ field public static final int L2_TOO_SHORT;
+ field public static final int L2_WRONG_ETH_TYPE;
+ field public static final int L3_ERROR = 2; // 0x2
+ field public static final int L3_INVALID_IP;
+ field public static final int L3_NOT_IPV4;
+ field public static final int L3_TOO_SHORT;
+ field public static final int L4_ERROR = 3; // 0x3
+ field public static final int L4_NOT_UDP;
+ field public static final int L4_WRONG_PORT;
+ field public static final int MISC_ERROR = 5; // 0x5
+ field public static final int PARSING_ERROR;
+ field public static final int RECEIVE_ERROR;
+ }
+
+ public class 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);
+ method public boolean log(int, int[], android.net.metrics.IpConnectivityLog.Event);
+ method public boolean log(android.net.metrics.IpConnectivityLog.Event);
+ }
+
+ public static interface IpConnectivityLog.Event extends android.os.Parcelable {
+ }
+
+ public final class IpManagerEvent implements android.net.metrics.IpConnectivityLog.Event {
+ ctor public IpManagerEvent(int, long);
+ field public static final int COMPLETE_LIFECYCLE = 3; // 0x3
+ field public static final int ERROR_INTERFACE_NOT_FOUND = 8; // 0x8
+ field public static final int ERROR_INVALID_PROVISIONING = 7; // 0x7
+ field public static final int ERROR_STARTING_IPREACHABILITYMONITOR = 6; // 0x6
+ field public static final int ERROR_STARTING_IPV4 = 4; // 0x4
+ field public static final int ERROR_STARTING_IPV6 = 5; // 0x5
+ field public static final int PROVISIONING_FAIL = 2; // 0x2
+ field public static final int PROVISIONING_OK = 1; // 0x1
+ }
+
+ public final class IpReachabilityEvent implements android.net.metrics.IpConnectivityLog.Event {
+ ctor public IpReachabilityEvent(int);
+ field public static final int NUD_FAILED = 512; // 0x200
+ field public static final int NUD_FAILED_ORGANIC = 1024; // 0x400
+ field public static final int PROBE = 256; // 0x100
+ field public static final int PROVISIONING_LOST = 768; // 0x300
+ field public static final int PROVISIONING_LOST_ORGANIC = 1280; // 0x500
+ }
+
+ public final class NetworkEvent implements android.net.metrics.IpConnectivityLog.Event {
+ ctor public NetworkEvent(int, long);
+ ctor public NetworkEvent(int);
+ field public static final int NETWORK_CAPTIVE_PORTAL_FOUND = 4; // 0x4
+ field public static final int NETWORK_CONNECTED = 1; // 0x1
+ field public static final int NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND = 12; // 0xc
+ field public static final int NETWORK_DISCONNECTED = 7; // 0x7
+ field public static final int NETWORK_FIRST_VALIDATION_PORTAL_FOUND = 10; // 0xa
+ field public static final int NETWORK_FIRST_VALIDATION_SUCCESS = 8; // 0x8
+ field public static final int NETWORK_LINGER = 5; // 0x5
+ field public static final int NETWORK_REVALIDATION_PORTAL_FOUND = 11; // 0xb
+ field public static final int NETWORK_REVALIDATION_SUCCESS = 9; // 0x9
+ field public static final int NETWORK_UNLINGER = 6; // 0x6
+ field public static final int NETWORK_VALIDATED = 2; // 0x2
+ field public static final int NETWORK_VALIDATION_FAILED = 3; // 0x3
+ }
+
+ 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
+ field public static final int DNS_SUCCESS = 1; // 0x1
+ field public static final int PROBE_DNS = 0; // 0x0
+ field public static final int PROBE_FALLBACK = 4; // 0x4
+ field public static final int PROBE_HTTP = 1; // 0x1
+ field public static final int PROBE_HTTPS = 2; // 0x2
+ field public static final int PROBE_PAC = 3; // 0x3
+ field public static final int PROBE_PRIVDNS = 5; // 0x5
+ }
+
+ public static class ValidationProbeEvent.Builder {
+ ctor public ValidationProbeEvent.Builder();
+ method public android.net.metrics.ValidationProbeEvent build();
+ method public android.net.metrics.ValidationProbeEvent.Builder setDurationMs(long);
+ method public android.net.metrics.ValidationProbeEvent.Builder setProbeType(int, boolean);
+ method public android.net.metrics.ValidationProbeEvent.Builder setReturnCode(int);
+ }
+
+}
+
package android.os {
public class Build {
@@ -1277,6 +1478,21 @@
field public static final String AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES = "autofill_compat_mode_allowed_packages";
field public static final String AUTOFILL_SMART_SUGGESTION_EMULATION_FLAGS = "autofill_smart_suggestion_emulation_flags";
field public static final String AUTOMATIC_POWER_SAVER_MODE = "automatic_power_saver_mode";
+ field public static final String CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS = "captive_portal_fallback_probe_specs";
+ field public static final String CAPTIVE_PORTAL_FALLBACK_URL = "captive_portal_fallback_url";
+ field public static final String CAPTIVE_PORTAL_HTTPS_URL = "captive_portal_https_url";
+ field public static final String CAPTIVE_PORTAL_HTTP_URL = "captive_portal_http_url";
+ field public static final String CAPTIVE_PORTAL_MODE = "captive_portal_mode";
+ field public static final int CAPTIVE_PORTAL_MODE_AVOID = 2; // 0x2
+ field public static final int CAPTIVE_PORTAL_MODE_IGNORE = 0; // 0x0
+ field public static final int CAPTIVE_PORTAL_MODE_PROMPT = 1; // 0x1
+ field public static final String CAPTIVE_PORTAL_OTHER_FALLBACK_URLS = "captive_portal_other_fallback_urls";
+ field public static final String CAPTIVE_PORTAL_USER_AGENT = "captive_portal_user_agent";
+ field public static final String CAPTIVE_PORTAL_USE_HTTPS = "captive_portal_use_https";
+ field public static final String DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD = "data_stall_consecutive_dns_timeout_threshold";
+ field public static final String DATA_STALL_EVALUATION_TYPE = "data_stall_evaluation_type";
+ field public static final String DATA_STALL_MIN_EVALUATE_INTERVAL = "data_stall_min_evaluate_interval";
+ field public static final String DATA_STALL_VALID_DNS_TIME_THRESHOLD = "data_stall_valid_dns_time_threshold";
field public static final String DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD = "dynamic_power_savings_disable_threshold";
field public static final String DYNAMIC_POWER_SAVINGS_ENABLED = "dynamic_power_savings_enabled";
field public static final String HIDDEN_API_BLACKLIST_EXEMPTIONS = "hidden_api_blacklist_exemptions";
@@ -1472,10 +1688,7 @@
method public int getTaskId();
}
- public final class FillResponse 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.service.autofill.augmented.FillResponse> CREATOR;
+ public final class FillResponse {
}
public static final class FillResponse.Builder {
@@ -1489,7 +1702,6 @@
ctor public FillWindow();
method public void destroy();
method public boolean update(@NonNull android.service.autofill.augmented.PresentationParams.Area, @NonNull android.view.View, long);
- field public static final long FLAG_METADATA_ADDRESS = 1L; // 0x1L
}
public abstract class PresentationParams {
@@ -2061,6 +2273,10 @@
method public static int getLongPressTooltipHideTimeout();
}
+ public class ViewDebug {
+ method @Nullable public static AutoCloseable startRenderingCommandsCapture(android.view.View, java.util.concurrent.Executor, java.util.function.Function<android.graphics.Picture,java.lang.Boolean>);
+ }
+
public interface WindowManager extends android.view.ViewManager {
method public default void setShouldShowIme(int, boolean);
method public default void setShouldShowSystemDecors(int, boolean);
@@ -2121,6 +2337,12 @@
}
public final class AutofillManager {
+ method @NonNull public java.util.Set<android.content.ComponentName> getAugmentedAutofillDisabledActivities();
+ method @NonNull public java.util.Set<java.lang.String> getAugmentedAutofillDisabledPackages();
+ method public void setActivityAugmentedAutofillEnabled(@NonNull android.content.ComponentName, boolean);
+ method public void setAugmentedAutofillWhitelist(@Nullable java.util.List<java.lang.String>, @Nullable java.util.List<android.content.ComponentName>);
+ method public void setPackageAugmentedAutofillEnabled(@NonNull String, boolean);
+ field public static final int FLAG_SMART_SUGGESTION_SYSTEM = 1; // 0x1
field public static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 120000; // 0x1d4c0
}
diff --git a/cmds/bootanimation/bootanim.rc b/cmds/bootanimation/bootanim.rc
index 469c964..1b3c32b 100644
--- a/cmds/bootanimation/bootanim.rc
+++ b/cmds/bootanimation/bootanim.rc
@@ -2,6 +2,7 @@
class core animation
user graphics
group graphics audio
+ updatable
disabled
oneshot
writepid /dev/stune/top-app/tasks
diff --git a/cmds/media/src/com/android/commands/media/Media.java b/cmds/media/src/com/android/commands/media/Media.java
index d160b73..1e915f8 100644
--- a/cmds/media/src/com/android/commands/media/Media.java
+++ b/cmds/media/src/com/android/commands/media/Media.java
@@ -17,18 +17,18 @@
package com.android.commands.media;
-import android.app.ActivityManager;
+import android.app.ActivityThread;
import android.content.Context;
import android.media.MediaMetadata;
-import android.media.session.ControllerCallbackLink;
-import android.media.session.ISessionController;
import android.media.session.ISessionManager;
+import android.media.session.MediaController;
import android.media.session.MediaController.PlaybackInfo;
import android.media.session.MediaSession.QueueItem;
+import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.os.Bundle;
import android.os.HandlerThread;
-import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -48,6 +48,8 @@
public class Media extends BaseCommand {
// This doesn't belongs to any package. Setting the package name to empty string.
private static final String PACKAGE_NAME = "";
+ private static ActivityThread sThread;
+ private static MediaSessionManager sMediaSessionManager;
private ISessionManager mSessionService;
/**
@@ -80,6 +82,13 @@
@Override
public void onRun() throws Exception {
+ if (sThread == null) {
+ Looper.prepareMainLooper();
+ sThread = ActivityThread.systemMain();
+ Context context = sThread.getSystemContext();
+ sMediaSessionManager =
+ (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
+ }
mSessionService = ISessionManager.Stub.asInterface(ServiceManager.checkService(
Context.MEDIA_SESSION_SERVICE));
if (mSessionService == null) {
@@ -117,12 +126,11 @@
showError("Error: must include a session id");
return;
}
+
boolean success = false;
try {
- List<IBinder> sessions = mSessionService
- .getSessions(null, ActivityManager.getCurrentUser());
- for (IBinder session : sessions) {
- ISessionController controller = ISessionController.Stub.asInterface(session);
+ List<MediaController> controllers = sMediaSessionManager.getActiveSessions(null);
+ for (MediaController controller : controllers) {
try {
if (controller != null && id.equals(controller.getTag())) {
ControllerMonitor monitor = new ControllerMonitor(controller);
@@ -178,14 +186,14 @@
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD));
}
- class ControllerCallbackStub extends ControllerCallbackLink.CallbackStub {
+ class ControllerCallback extends MediaController.Callback {
@Override
public void onSessionDestroyed() {
System.out.println("onSessionDestroyed. Enter q to quit.");
}
@Override
- public void onEvent(String event, Bundle extras) {
+ public void onSessionEvent(String event, Bundle extras) {
System.out.println("onSessionEvent event=" + event + ", extras=" + extras);
}
@@ -218,25 +226,25 @@
}
@Override
- public void onVolumeInfoChanged(PlaybackInfo info) {
- System.out.println("onVolumeInfoChanged " + info);
+ public void onAudioInfoChanged(PlaybackInfo info) {
+ System.out.println("onAudioInfoChanged " + info);
}
}
private class ControllerMonitor {
- private final ISessionController mController;
- private final ControllerCallbackLink mControllerCallbackLink;
+ private final MediaController mController;
+ private final ControllerCallback mControllerCallback;
- ControllerMonitor(ISessionController controller) {
+ ControllerMonitor(MediaController controller) {
mController = controller;
- mControllerCallbackLink = new ControllerCallbackLink(new ControllerCallbackStub());
+ mControllerCallback = new ControllerCallback();
}
void printUsageMessage() {
try {
System.out.println("V2Monitoring session " + mController.getTag()
+ "... available commands: play, pause, next, previous");
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
System.out.println("Error trying to monitor session!");
}
System.out.println("(q)uit: finish monitoring");
@@ -248,8 +256,8 @@
@Override
protected void onLooperPrepared() {
try {
- mController.registerCallbackListener(PACKAGE_NAME, mControllerCallbackLink);
- } catch (RemoteException e) {
+ mController.registerCallback(mControllerCallback);
+ } catch (RuntimeException e) {
System.out.println("Error registering monitor callback");
}
}
@@ -291,7 +299,7 @@
} finally {
cbThread.getLooper().quit();
try {
- mController.unregisterCallbackListener(mControllerCallbackLink);
+ mController.unregisterCallback(mControllerCallback);
} catch (Exception e) {
// ignoring
}
@@ -305,9 +313,9 @@
KeyEvent up = new KeyEvent(now, now, KeyEvent.ACTION_UP, keyCode, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD);
try {
- mController.sendMediaButton(PACKAGE_NAME, null, false, down);
- mController.sendMediaButton(PACKAGE_NAME, null, false, up);
- } catch (RemoteException e) {
+ mController.dispatchMediaButtonEvent(down);
+ mController.dispatchMediaButtonEvent(up);
+ } catch (RuntimeException e) {
System.out.println("Failed to dispatch " + keyCode);
}
}
@@ -316,16 +324,13 @@
private void runListSessions() {
System.out.println("Sessions:");
try {
- List<IBinder> sessions = mSessionService
- .getSessions(null, ActivityManager.getCurrentUser());
- for (IBinder session : sessions) {
-
- ISessionController controller = ISessionController.Stub.asInterface(session);
+ List<MediaController> controllers = sMediaSessionManager.getActiveSessions(null);
+ for (MediaController controller : controllers) {
if (controller != null) {
try {
System.out.println(" tag=" + controller.getTag()
+ ", package=" + controller.getPackageName());
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
// ignore
}
}
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 3107b4d..820da55 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -1089,6 +1089,14 @@
return hardware::Void();
}
+hardware::Return<void> StatsService::reportUsbPortOverheatEvent(
+ const UsbPortOverheatEvent& usbPortOverheatEvent) {
+ LogEvent event(getWallClockSec() * NS_PER_SEC, getElapsedRealtimeNs(), usbPortOverheatEvent);
+ 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 135a3c9..e9b3d4f 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -199,6 +199,12 @@
virtual Return<void> reportBatteryCausedShutdown(
const BatteryCausedShutdown& batteryCausedShutdown) override;
+ /**
+ * Binder call to get UsbPortOverheatEvent atom.
+ */
+ virtual Return<void> reportUsbPortOverheatEvent(
+ const UsbPortOverheatEvent& usbPortOverheatEvent) 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 ef3eac0..60b2e25 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -27,6 +27,7 @@
import "frameworks/base/core/proto/android/bluetooth/enums.proto";
import "frameworks/base/core/proto/android/bluetooth/hci/enums.proto";
import "frameworks/base/core/proto/android/bluetooth/hfp/enums.proto";
+import "frameworks/base/core/proto/android/debug/enums.proto";
import "frameworks/base/core/proto/android/net/networkcapabilities.proto";
import "frameworks/base/core/proto/android/os/enums.proto";
import "frameworks/base/core/proto/android/server/connectivity/data_stall_event.proto";
@@ -91,7 +92,7 @@
ChargingStateChanged charging_state_changed = 31;
PluggedStateChanged plugged_state_changed = 32;
InteractiveStateChanged interactive_state_changed = 33;
- // 34 is available
+ TouchEventReported touch_event_reported = 34;
WakeupAlarmOccurred wakeup_alarm_occurred = 35;
KernelWakeupReported kernel_wakeup_reported = 36;
WifiLockStateChanged wifi_lock_state_changed = 37;
@@ -193,6 +194,18 @@
LowStorageStateChanged low_storage_state_changed = 130;
GnssNfwNotificationReported gnss_nfw_notification_reported = 131;
GnssConfigurationReported gnss_configuration_reported = 132;
+ UsbPortOverheatEvent usb_port_overheat_event_reported = 133;
+ NfcErrorOccurred nfc_error_occurred = 134;
+ NfcStateChanged nfc_state_changed = 135;
+ NfcBeamOccurred nfc_beam_occurred = 136;
+ NfcCardemulationOccurred nfc_cardemulation_occurred = 137;
+ NfcTagOccurred nfc_tag_occurred = 138;
+ NfcHceTransactionOccurred nfc_hce_transaction_occurred = 139;
+ SeStateChanged se_state_changed = 140;
+ SeOmapiReported se_omapi_reported = 141;
+ BroadcastDispatchLatencyReported broadcast_dispatch_latency_reported = 142;
+ AttentionManagerServiceResultReported attention_manager_service_result_reported = 143;
+ AdbConnectionChanged adb_connection_changed = 144;
}
// Pulled events will start at field 10000.
@@ -243,6 +256,7 @@
ProcessMemoryHighWaterMark process_memory_high_water_mark = 10042;
BatteryLevel battery_level = 10043;
BuildInformation build_information = 10044;
+ BatteryCycleCount battery_cycle_count = 10045;
}
// DO NOT USE field numbers above 100,000 in AOSP.
@@ -1735,6 +1749,33 @@
}
/**
+ * Logs basic timing information about touch events.
+ * Reported at most every 5 minutes while device is being interacted with.
+ *
+ * Logged from:
+ * frameworks/native/services/inputflinger
+ */
+message TouchEventReported {
+ /**
+ * The fields latency_{min|max|mean|stdev} represent minimum, maximum, mean,
+ * and the standard deviation of latency between the kernel and framework
+ * for touchscreen events. The units are microseconds.
+ *
+ * The number is measured as the difference between the time at which
+ * the input event was received in the evdev driver,
+ * and the time at which the input event was received in EventHub.
+ */
+ // Minimum value
+ optional float latency_min_micros = 1;
+ // Maximum value
+ optional float latency_max_micros = 2;
+ // Average value
+ optional float latency_mean_micros = 3;
+ // Standard deviation
+ optional float latency_stdev_micros = 4;
+}
+
+/**
* Logs that a setting was updated.
* Logged from:
* frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -2468,6 +2509,37 @@
optional State state = 3;
}
+/** Represents USB port overheat event. */
+message UsbPortOverheatEvent {
+ /* Temperature of USB port at USB plug event, in 1/10ths of degree C. */
+ optional int32 plug_temperature_deci_c = 1;
+
+ /* Maximum temperature of USB port during overheat event, in 1/10ths of degree C. */
+ optional int32 max_temperature_deci_c = 2;
+
+ /* Time between USB plug event and overheat threshold trip, in seconds. */
+ optional int32 time_to_overheat_secs = 3;
+
+ /* Time between overheat threshold trip and hysteresis, in seconds. */
+ optional int32 time_to_hysteresis_secs = 4;
+
+ /* Time between hysteresis and active mitigation ending, in seconds. */
+ optional int32 time_to_inactive_secs = 5;
+};
+
+/**
+ * Logs total effective full charge and discharge cycles on a battery.
+ * Here are some examples of one effective cycle:
+ * 1) the battery charges from 0% to 100% and drains back to 0%,
+ * 2) charging from 50% to 100% and draining back to 50% twice.
+ * Pulled from:
+ * frameworks/base/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp
+ */
+message BatteryCycleCount {
+ /* Number of total charge and discharge cycles on the system battery. */
+ optional int32 cycle_count = 1;
+}
+
/*
* Logs when a connection becomes available and lost.
* Logged in StatsCompanionService.java
@@ -4263,3 +4335,189 @@
// with spaces as separators.
optional string enabled_proxy_app_package_name_list = 13;
}
+
+/**
+ * Logs when a NFC device's error occurred.
+ * Logged from:
+ * system/nfc/src/nfc/nfc/nfc_ncif.cc
+ * packages/apps/Nfc/src/com/android/nfc/cardemulation/AidRoutingManager.java
+ */
+message NfcErrorOccurred {
+ enum Type {
+ UNKNOWN = 0;
+ CMD_TIMEOUT = 1;
+ ERROR_NOTIFICATION = 2;
+ AID_OVERFLOW = 3;
+ }
+ optional Type type = 1;
+ // If it's nci cmd timeout, log the timeout command.
+ optional uint32 nci_cmd = 2;
+
+ optional uint32 error_ntf_status_code = 3;
+}
+
+/**
+ * Logs when a NFC device's state changed event
+ * Logged from:
+ * packages/apps/Nfc/src/com/android/nfc/NfcService.java
+ */
+message NfcStateChanged {
+ enum State {
+ UNKNOWN = 0;
+ OFF = 1;
+ ON = 2;
+ ON_LOCKED = 3; // Secure Nfc enabled.
+ CRASH_RESTART = 4; // NfcService watchdog timeout restart.
+ }
+ optional State state = 1;
+}
+
+/**
+ * Logs when a NFC Beam Transaction occurred.
+ * Logged from:
+ * packages/apps/Nfc/src/com/android/nfc/P2pLinkManager.java
+ */
+message NfcBeamOccurred {
+ enum Operation {
+ UNKNOWN = 0;
+ SEND = 1;
+ RECEIVE = 2;
+ }
+ optional Operation operation = 1;
+}
+
+/**
+ * Logs when a NFC Card Emulation Transaction occurred.
+ * Logged from:
+ * packages/apps/Nfc/src/com/android/nfc/cardemulation/HostEmulationManager.java
+ * packages/apps/Nfc/src/com/android/nfc/cardemulation/HostNfcFEmulationManager.java
+ */
+message NfcCardemulationOccurred {
+ enum Category {
+ UNKNOWN = 0;
+ HCE_PAYMENT = 1;
+ HCE_OTHER = 2;
+ OFFHOST = 3;
+ }
+ // Transaction belongs to HCE payment or HCE other category, or offhost.
+ optional Category category = 1;
+ // SeName from transaction: SIMx, eSEx, HCE, HCEF.
+ optional string se_name = 2;
+}
+
+/**
+ * Logs when a NFC Tag event occurred.
+ * Logged from:
+ * packages/apps/Nfc/src/com/android/nfc/NfcDispatcher.java
+ */
+message NfcTagOccurred {
+ enum Type {
+ UNKNOWN = 0;
+ URL = 1;
+ BT_PAIRING = 2;
+ PROVISION = 3;
+ WIFI_CONNECT = 4;
+ APP_LAUNCH = 5;
+ OTHERS = 6;
+ }
+ optional Type type = 1;
+}
+
+/**
+ * Logs when Hce transaction triggered
+ * Logged from:
+ * system/nfc/src/nfc/nfc/nfc_ncif.cc
+ */
+message NfcHceTransactionOccurred {
+ // The latency period(in microseconds) it took for the first HCE data
+ // exchange.
+ optional uint32 latency_micros = 1;
+}
+
+/**
+ * Logs when SecureElement state event changed
+ * Logged from:
+ * packages/apps/SecureElement/src/com/android/se/Terminal.java
+ */
+message SeStateChanged {
+ enum State {
+ UNKNOWN = 0;
+ INITIALIZED = 1;
+ DISCONNECTED = 2;
+ CONNECTED = 3;
+ HALCRASH = 4;
+ }
+ optional State state = 1;
+
+ optional string state_change_reason = 2;
+ // SIMx or eSEx.
+ optional string terminal = 3;
+}
+
+/**
+ * Logs when Omapi API used
+ * Logged from:
+ * packages/apps/SecureElement/src/com/android/se/Terminal.java
+ */
+message SeOmapiReported {
+ enum Operation {
+ UNKNOWN = 0;
+ OPEN_CHANNEL = 1;
+ }
+ optional Operation operation = 1;
+ // SIMx or eSEx.
+ optional string terminal = 2;
+
+ optional string package_name = 3;
+}
+
+/**
+ * Logs the dispatch latency of a broadcast during processing of BOOT_COMPLETED.
+ * The dispatch latency is the dispatchClockTime - enqueueClockTime.
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java
+ */
+message BroadcastDispatchLatencyReported {
+ optional int64 dispatch_latency_millis = 1;
+}
+
+/**
+ * Logs AttentionManagerService attention check result.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/attention/AttentionManagerService.java
+ */
+message AttentionManagerServiceResultReported {
+ // See core/java/android/service/attention/AttentionService.java
+ enum AttentionCheckResult {
+ UNKNOWN = 20;
+ ATTENTION_SUCCESS_ABSENT = 0;
+ ATTENTION_SUCCESS_PRESENT = 1;
+ ATTENTION_FAILURE_PREEMPTED = 2;
+ ATTENTION_FAILURE_TIMED_OUT = 3;
+ ATTENTION_FAILURE_UNKNOWN = 4;
+ }
+ optional AttentionCheckResult attention_check_result = 1 [default = UNKNOWN];
+}
+
+/**
+ * Logs when an adb connection changes state.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+ */
+message AdbConnectionChanged {
+ // The last time this system connected via adb, or 0 if the 'always allow' option was not
+ // previously selected for this system.
+ optional int64 last_connection_time_millis = 1;
+
+ // The time in ms within which a subsequent connection from an 'always allow' system is allowed
+ // to reconnect via adb without user interaction.
+ optional int64 auth_window_millis = 2;
+
+ // The state of the adb connection from frameworks/base/core/proto/android/debug/enums.proto.
+ optional android.debug.AdbConnectionStateEnum state = 3;
+
+ // True if the 'always allow' option was selected for this system.
+ optional bool always_allow = 4;
+}
diff --git a/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp b/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp
index b878652..75b63f4 100644
--- a/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp
+++ b/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp
@@ -24,16 +24,16 @@
#include "ResourceHealthManagerPuller.h"
#include "logd/LogEvent.h"
-#include "statslog.h"
#include "stats_log_util.h"
+#include "statslog.h"
using android::hardware::hidl_vec;
+using android::hardware::Return;
+using android::hardware::Void;
using android::hardware::health::V2_0::get_health_service;
using android::hardware::health::V2_0::HealthInfo;
using android::hardware::health::V2_0::IHealth;
using android::hardware::health::V2_0::Result;
-using android::hardware::Return;
-using android::hardware::Void;
using std::make_shared;
using std::shared_ptr;
@@ -75,35 +75,41 @@
}
if (mTagId == android::util::REMAINING_BATTERY_CAPACITY) {
auto ptr = make_shared<LogEvent>(android::util::REMAINING_BATTERY_CAPACITY,
- wallClockTimestampNs, elapsedTimestampNs);
+ wallClockTimestampNs, elapsedTimestampNs);
ptr->write(v.legacy.batteryChargeCounter);
ptr->init();
data->push_back(ptr);
} else if (mTagId == android::util::FULL_BATTERY_CAPACITY) {
auto ptr = make_shared<LogEvent>(android::util::FULL_BATTERY_CAPACITY,
- wallClockTimestampNs, elapsedTimestampNs);
+ wallClockTimestampNs, elapsedTimestampNs);
ptr->write(v.legacy.batteryFullCharge);
ptr->init();
data->push_back(ptr);
} else if (mTagId == android::util::BATTERY_VOLTAGE) {
- auto ptr = make_shared<LogEvent>(android::util::BATTERY_VOLTAGE,
- wallClockTimestampNs, elapsedTimestampNs);
+ auto ptr = make_shared<LogEvent>(android::util::BATTERY_VOLTAGE, wallClockTimestampNs,
+ elapsedTimestampNs);
ptr->write(v.legacy.batteryVoltage);
ptr->init();
data->push_back(ptr);
} else if (mTagId == android::util::BATTERY_LEVEL) {
- auto ptr = make_shared<LogEvent>(android::util::BATTERY_LEVEL,
- wallClockTimestampNs, elapsedTimestampNs);
- ptr->write(v.legacy.batteryLevel);
- ptr->init();
- data->push_back(ptr);
+ auto ptr = make_shared<LogEvent>(android::util::BATTERY_LEVEL, wallClockTimestampNs,
+ elapsedTimestampNs);
+ ptr->write(v.legacy.batteryLevel);
+ ptr->init();
+ data->push_back(ptr);
+ } else if (mTagId == android::util::BATTERY_CYCLE_COUNT) {
+ auto ptr = make_shared<LogEvent>(android::util::BATTERY_CYCLE_COUNT,
+ wallClockTimestampNs, elapsedTimestampNs);
+ ptr->write(v.legacy.batteryCycleCount);
+ ptr->init();
+ data->push_back(ptr);
} else {
ALOGE("Unsupported tag in ResourceHealthManagerPuller: %d", mTagId);
}
});
if (!result_success || !ret.isOk()) {
ALOGE("getHealthHal() failed: health HAL service not available. Description: %s",
- ret.description().c_str());
+ ret.description().c_str());
if (!ret.isOk() && ret.isDeadObject()) {
gHealthHal = nullptr;
}
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 4a716cf..19a7389 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -131,9 +131,12 @@
// battery_voltage
{android::util::BATTERY_VOLTAGE,
{.puller = new ResourceHealthManagerPuller(android::util::BATTERY_VOLTAGE)}},
- // battery_voltage
+ // battery_level
{android::util::BATTERY_LEVEL,
{.puller = new ResourceHealthManagerPuller(android::util::BATTERY_LEVEL)}},
+ // battery_cycle_count
+ {android::util::BATTERY_CYCLE_COUNT,
+ {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_CYCLE_COUNT)}},
// process_memory_state
{android::util::PROCESS_MEMORY_STATE,
{.additiveFields = {4, 5, 6, 7, 8, 9},
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 2ff8aa1..78a75c5 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -276,6 +276,24 @@
Value(batteryCausedShutdown.voltageMicroV)));
}
+LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
+ const UsbPortOverheatEvent& usbPortOverheatEvent) {
+ mLogdTimestampNs = wallClockTimestampNs;
+ mElapsedTimestampNs = elapsedTimestampNs;
+ mTagId = android::util::USB_PORT_OVERHEAT_EVENT_REPORTED;
+
+ mValues.push_back(FieldValue(Field(mTagId, getSimpleField(1)),
+ Value(usbPortOverheatEvent.plugTemperatureDeciC)));
+ mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)),
+ Value(usbPortOverheatEvent.maxTemperatureDeciC)));
+ mValues.push_back(FieldValue(Field(mTagId, getSimpleField(3)),
+ Value(usbPortOverheatEvent.timeToOverheat)));
+ mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)),
+ Value(usbPortOverheatEvent.timeToHysteresis)));
+ mValues.push_back(FieldValue(Field(mTagId, getSimpleField(5)),
+ Value(usbPortOverheatEvent.timeToInactive)));
+}
+
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 43e6e4f..3f47b7e 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -118,6 +118,9 @@
explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
const BatteryCausedShutdown& batteryCausedShutdown);
+ explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
+ const UsbPortOverheatEvent& usbPortOverheatEvent);
+
~LogEvent();
/**
diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
index 4174ad7..1b7fbfe 100644
--- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java
+++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
@@ -23,8 +23,10 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.telecom.Log;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
+import android.text.TextUtils;
import com.android.internal.os.BaseCommand;
import com.android.internal.telecom.ITelecomService;
@@ -45,6 +47,8 @@
private static final String COMMAND_SET_PHONE_ACCOUNT_ENABLED = "set-phone-account-enabled";
private static final String COMMAND_SET_PHONE_ACCOUNT_DISABLED = "set-phone-account-disabled";
private static final String COMMAND_REGISTER_PHONE_ACCOUNT = "register-phone-account";
+ private static final String COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT =
+ "set-user-selected-outgoing-phone-account";
private static final String COMMAND_REGISTER_SIM_PHONE_ACCOUNT = "register-sim-phone-account";
private static final String COMMAND_SET_TEST_CALL_REDIRECTION_APP = "set-test-call-redirection-app";
private static final String COMMAND_SET_TEST_CALL_SCREENING_APP = "set-test-call-screening-app";
@@ -70,6 +74,8 @@
+ "usage: telecom set-phone-account-enabled <COMPONENT> <ID> <USER_SN>\n"
+ "usage: telecom set-phone-account-disabled <COMPONENT> <ID> <USER_SN>\n"
+ "usage: telecom register-phone-account <COMPONENT> <ID> <USER_SN> <LABEL>\n"
+ + "usage: telecom set-user-selected-outgoing-phone-account <COMPONENT> <ID> "
+ + "<USER_SN>\n"
+ "usage: telecom set-test-call-redirection-app <PACKAGE>\n"
+ "usage: telecom set-test-call-screening-app <PACKAGE>\n"
+ "usage: telecom set-test-auto-mode-app <PACKAGE>\n"
@@ -104,16 +110,18 @@
mTelecomService = ITelecomService.Stub.asInterface(
ServiceManager.getService(Context.TELECOM_SERVICE));
if (mTelecomService == null) {
+ Log.w(this, "onRun: Can't access telecom manager.");
showError("Error: Could not access the Telecom Manager. Is the system running?");
return;
}
mUserManager = IUserManager.Stub
.asInterface(ServiceManager.getService(Context.USER_SERVICE));
if (mUserManager == null) {
+ Log.w(this, "onRun: Can't access user manager.");
showError("Error: Could not access the User Manager. Is the system running?");
return;
}
-
+ Log.i(this, "onRun: parsing command.");
String command = nextArgRequired();
switch (command) {
case COMMAND_SET_PHONE_ACCOUNT_ENABLED:
@@ -143,6 +151,9 @@
case COMMAND_REGISTER_SIM_PHONE_ACCOUNT:
runRegisterSimPhoneAccount();
break;
+ case COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT:
+ runSetUserSelectedOutgoingPhoneAccount();
+ break;
case COMMAND_UNREGISTER_PHONE_ACCOUNT:
runUnregisterPhoneAccount();
break;
@@ -159,6 +170,7 @@
runWaitOnHandler();
break;
default:
+ Log.w(this, "onRun: unknown command: %s", command);
throw new IllegalArgumentException ("unknown command '" + command + "'");
}
}
@@ -227,6 +239,13 @@
mTelecomService.setTestPhoneAcctSuggestionComponent(componentName);
}
+ private void runSetUserSelectedOutgoingPhoneAccount() throws RemoteException {
+ Log.i(this, "runSetUserSelectedOutgoingPhoneAccount");
+ final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
+ mTelecomService.setUserSelectedOutgoingPhoneAccount(handle);
+ System.out.println("Success - " + handle + " set as default outgoing account.");
+ }
+
private void runUnregisterPhoneAccount() throws RemoteException {
final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
mTelecomService.unregisterPhoneAccount(handle);
@@ -256,7 +275,10 @@
}
- private PhoneAccountHandle getPhoneAccountHandleFromArgs() throws RemoteException{
+ private PhoneAccountHandle getPhoneAccountHandleFromArgs() throws RemoteException {
+ if (TextUtils.isEmpty(mArgs.peekNextArg())) {
+ return null;
+ }
final ComponentName component = parseComponentName(nextArgRequired());
final String accountId = nextArgRequired();
final String userSnInStr = nextArgRequired();
@@ -265,6 +287,7 @@
final int userSn = Integer.parseInt(userSnInStr);
userHandle = UserHandle.of(mUserManager.getUserHandle(userSn));
} catch (NumberFormatException ex) {
+ Log.w(this, "getPhoneAccountHandleFromArgs - invalid user %s", userSnInStr);
throw new IllegalArgumentException ("Invalid user serial number " + userSnInStr);
}
return new PhoneAccountHandle(component, accountId, userHandle);
@@ -277,4 +300,4 @@
}
return cn;
}
-}
\ No newline at end of file
+}
diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt
index 3ec0db4..c2e441b 100644
--- a/config/boot-image-profile.txt
+++ b/config/boot-image-profile.txt
@@ -2768,6 +2768,11 @@
HPLandroid/hardware/location/GeofenceHardwareService$1;->registerForMonitorStateChangeCallback(ILandroid/hardware/location/IGeofenceHardwareMonitorCallback;)Z
HPLandroid/hardware/location/GeofenceHardwareService$1;->removeGeofence(II)Z
HPLandroid/hardware/location/GeofenceHardwareService;->checkPermission(III)V
+HPLandroid/hardware/location/IActivityRecognitionHardwareClient$Stub$Proxy;->onAvailabilityChanged(ZLandroid/hardware/location/IActivityRecognitionHardware;)V
+HPLandroid/hardware/location/IActivityRecognitionHardwareClient$Stub;-><init>()V
+HPLandroid/hardware/location/IActivityRecognitionHardwareClient$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/location/IActivityRecognitionHardwareClient;
+HPLandroid/hardware/location/IActivityRecognitionHardwareClient$Stub;->onTransact(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z
+HPLandroid/hardware/location/IActivityRecognitionHardwareClient;->onAvailabilityChanged(ZLandroid/hardware/location/IActivityRecognitionHardware;)V
HPLandroid/hardware/location/IContextHubCallback$Stub;->asBinder()Landroid/os/IBinder;
HPLandroid/hardware/location/IContextHubCallback$Stub;->onTransact(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z
HPLandroid/hardware/location/IContextHubService$Stub$Proxy;->findNanoAppOnHub(ILandroid/hardware/location/NanoAppFilter;)[I
@@ -21777,6 +21782,7 @@
HSPLandroid/hardware/input/TouchCalibration$1;-><init>()V
HSPLandroid/hardware/input/TouchCalibration;-><init>()V
HSPLandroid/hardware/input/TouchCalibration;->getAffineTransform()[F
+HSPLandroid/hardware/location/ActivityRecognitionHardware;->isSupported()Z
HSPLandroid/hardware/location/ContextHubInfo$1;-><init>()V
HSPLandroid/hardware/location/ContextHubInfo;-><init>(Landroid/hardware/contexthub/V1_0/ContextHub;)V
HSPLandroid/hardware/location/ContextHubMessage$1;-><init>()V
@@ -21796,6 +21802,13 @@
HSPLandroid/hardware/location/GeofenceHardwareService;-><init>()V
HSPLandroid/hardware/location/GeofenceHardwareService;->onBind(Landroid/content/Intent;)Landroid/os/IBinder;
HSPLandroid/hardware/location/GeofenceHardwareService;->onCreate()V
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->disableActivityEvent(Ljava/lang/String;I)Z
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->enableActivityEvent(Ljava/lang/String;IJ)Z
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->flush()Z
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->getSupportedActivities()[Ljava/lang/String;
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->isActivitySupported(Ljava/lang/String;)Z
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->registerSink(Landroid/hardware/location/IActivityRecognitionHardwareSink;)Z
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->unregisterSink(Landroid/hardware/location/IActivityRecognitionHardwareSink;)Z
HSPLandroid/hardware/location/IContextHubCallback$Stub$Proxy;->asBinder()Landroid/os/IBinder;
HSPLandroid/hardware/location/IContextHubCallback$Stub$Proxy;->onMessageReceipt(IILandroid/hardware/location/ContextHubMessage;)V
HSPLandroid/hardware/location/IContextHubCallback;->onMessageReceipt(IILandroid/hardware/location/ContextHubMessage;)V
@@ -55637,6 +55650,7 @@
Landroid/hardware/input/KeyboardLayout$1;
Landroid/hardware/input/TouchCalibration$1;
Landroid/hardware/input/TouchCalibration;
+Landroid/hardware/location/ActivityRecognitionHardware;
Landroid/hardware/location/ContextHubInfo$1;
Landroid/hardware/location/ContextHubInfo;
Landroid/hardware/location/ContextHubManager;
@@ -55652,6 +55666,11 @@
Landroid/hardware/location/GeofenceHardwareRequestParcelable$1;
Landroid/hardware/location/GeofenceHardwareService$1;
Landroid/hardware/location/GeofenceHardwareService;
+Landroid/hardware/location/IActivityRecognitionHardware$Stub;
+Landroid/hardware/location/IActivityRecognitionHardware;
+Landroid/hardware/location/IActivityRecognitionHardwareClient$Stub$Proxy;
+Landroid/hardware/location/IActivityRecognitionHardwareClient$Stub;
+Landroid/hardware/location/IActivityRecognitionHardwareClient;
Landroid/hardware/location/IContextHubCallback$Stub$Proxy;
Landroid/hardware/location/IContextHubCallback;
Landroid/hardware/location/IContextHubClient$Stub;
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index 13f8dd9..fc47f67 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -462,6 +462,8 @@
Landroid/hardware/input/IInputManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/input/IInputManager;
Landroid/hardware/input/IInputManager$Stub;->TRANSACTION_injectInputEvent:I
Landroid/hardware/input/IInputManager;->injectInputEvent(Landroid/view/InputEvent;I)Z
+Landroid/hardware/location/IActivityRecognitionHardwareClient$Stub;-><init>()V
+Landroid/hardware/location/IActivityRecognitionHardwareClient;->onAvailabilityChanged(ZLandroid/hardware/location/IActivityRecognitionHardware;)V
Landroid/hardware/location/IContextHubService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/location/IContextHubService;
Landroid/hardware/usb/IUsbManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
Landroid/hardware/usb/IUsbManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/usb/IUsbManager;
@@ -921,7 +923,6 @@
Landroid/os/PowerManager;->mService:Landroid/os/IPowerManager;
Landroid/os/PowerManager;->validateWakeLockParameters(ILjava/lang/String;)V
Landroid/os/PowerManager;->wakeUp(JLjava/lang/String;)V
-Landroid/os/Process;->BLUETOOTH_UID:I
Landroid/os/Process;->DRM_UID:I
Landroid/os/Process;->getFreeMemory()J
Landroid/os/Process;->getParentPid(I)I
@@ -948,10 +949,8 @@
Landroid/os/Process;->PROC_ZERO_TERM:I
Landroid/os/Process;->readProcFile(Ljava/lang/String;[I[Ljava/lang/String;[J[F)Z
Landroid/os/Process;->readProcLines(Ljava/lang/String;[Ljava/lang/String;[J)V
-Landroid/os/Process;->ROOT_UID:I
Landroid/os/Process;->setArgV0(Ljava/lang/String;)V
Landroid/os/Process;->setProcessGroup(II)V
-Landroid/os/Process;->SHELL_UID:I
Landroid/os/Process;->VPN_UID:I
Landroid/os/Process;->WIFI_UID:I
Landroid/os/RecoverySystem;->verifyPackageCompatibility(Ljava/io/InputStream;)Z
@@ -993,8 +992,6 @@
Landroid/os/ServiceManagerNative;->asInterface(Landroid/os/IBinder;)Landroid/os/IServiceManager;
Landroid/os/ServiceManagerProxy;->getService(Ljava/lang/String;)Landroid/os/IBinder;
Landroid/os/ServiceManagerProxy;->mRemote:Landroid/os/IBinder;
-Landroid/os/ServiceSpecificException;-><init>(ILjava/lang/String;)V
-Landroid/os/ServiceSpecificException;->errorCode:I
Landroid/os/SharedMemory;->getFd()I
Landroid/os/ShellCommand;->peekNextArg()Ljava/lang/String;
Landroid/os/StatFs;->mStat:Landroid/system/StructStatVfs;
@@ -1419,11 +1416,6 @@
Landroid/service/euicc/IRetainSubscriptionsForFactoryResetCallback;->onComplete(I)V
Landroid/service/euicc/ISwitchToSubscriptionCallback;->onComplete(I)V
Landroid/service/euicc/IUpdateSubscriptionNicknameCallback;->onComplete(I)V
-Landroid/service/media/IMediaBrowserServiceCallbacks$Stub;->asInterface(Landroid/os/IBinder;)Landroid/service/media/IMediaBrowserServiceCallbacks;
-Landroid/service/media/IMediaBrowserServiceCallbacks;->onConnect(Ljava/lang/String;Landroid/media/session/MediaSession$Token;Landroid/os/Bundle;)V
-Landroid/service/media/IMediaBrowserServiceCallbacks;->onConnectFailed()V
-Landroid/service/media/IMediaBrowserServiceCallbacks;->onLoadChildren(Ljava/lang/String;Landroid/content/pm/ParceledListSlice;)V
-Landroid/service/media/IMediaBrowserServiceCallbacks;->onLoadChildrenWithOptions(Ljava/lang/String;Landroid/content/pm/ParceledListSlice;Landroid/os/Bundle;)V
Landroid/service/notification/INotificationListener$Stub;-><init>()V
Landroid/service/persistentdata/IPersistentDataBlockService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/service/persistentdata/IPersistentDataBlockService;
Landroid/service/vr/IVrManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/service/vr/IVrManager;
diff --git a/config/preloaded-classes b/config/preloaded-classes
index cd798ad..c8a2a9c 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -1405,7 +1405,10 @@
android.hardware.input.InputManager$InputDeviceListener
android.hardware.input.InputManager$InputDeviceListenerDelegate
android.hardware.input.InputManager$InputDevicesChangedListener
+android.hardware.location.ActivityRecognitionHardware
android.hardware.location.ContextHubManager
+android.hardware.location.IActivityRecognitionHardware
+android.hardware.location.IActivityRecognitionHardware$Stub
android.hardware.radio.RadioManager
android.hardware.soundtrigger.SoundTrigger
android.hardware.soundtrigger.SoundTrigger$ConfidenceLevel
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 35098a0..cebe6e12 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -37,6 +37,7 @@
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
+import android.view.Display;
import android.view.KeyEvent;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
@@ -382,7 +383,8 @@
void init(int connectionId, IBinder windowToken);
boolean onGesture(int gestureId);
boolean onKeyEvent(KeyEvent event);
- void onMagnificationChanged(@NonNull Region region,
+ /** Magnification changed callbacks for different displays */
+ void onMagnificationChanged(int displayId, @NonNull Region region,
float scale, float centerX, float centerY);
void onSoftKeyboardShowModeChanged(int showMode);
void onPerformGestureResult(int sequence, boolean completedSuccessfully);
@@ -452,7 +454,9 @@
private WindowManager mWindowManager;
- private MagnificationController mMagnificationController;
+ /** List of magnification controllers, mapping from displayId -> MagnificationController. */
+ private final SparseArray<MagnificationController> mMagnificationControllers =
+ new SparseArray<>(0);
private SoftKeyboardController mSoftKeyboardController;
private AccessibilityButtonController mAccessibilityButtonController;
@@ -483,8 +487,10 @@
* client code.
*/
private void dispatchServiceConnected() {
- if (mMagnificationController != null) {
- mMagnificationController.onServiceConnected();
+ synchronized (mLock) {
+ for (int i = 0; i < mMagnificationControllers.size(); i++) {
+ mMagnificationControllers.valueAt(i).onServiceConnectedLocked();
+ }
}
if (mSoftKeyboardController != null) {
mSoftKeyboardController.onServiceConnected();
@@ -652,11 +658,34 @@
*/
@NonNull
public final MagnificationController getMagnificationController() {
+ return getMagnificationController(Display.DEFAULT_DISPLAY);
+ }
+
+ /**
+ * Returns the magnification controller of specified logical display, which may be used to
+ * query and modify the state of display magnification.
+ * <p>
+ * <strong>Note:</strong> In order to control magnification, your service
+ * must declare the capability by setting the
+ * {@link android.R.styleable#AccessibilityService_canControlMagnification}
+ * property in its meta-data. For more information, see
+ * {@link #SERVICE_META_DATA}.
+ *
+ * @param displayId The logic display id, use {@link Display#DEFAULT_DISPLAY} for
+ * default display.
+ * @return the magnification controller
+ *
+ * @hide
+ */
+ @NonNull
+ public final MagnificationController getMagnificationController(int displayId) {
synchronized (mLock) {
- if (mMagnificationController == null) {
- mMagnificationController = new MagnificationController(this, mLock);
+ MagnificationController controller = mMagnificationControllers.get(displayId);
+ if (controller == null) {
+ controller = new MagnificationController(this, mLock, displayId);
+ mMagnificationControllers.put(displayId, controller);
}
- return mMagnificationController;
+ return controller;
}
}
@@ -765,11 +794,14 @@
}
}
- private void onMagnificationChanged(@NonNull Region region, float scale,
+ private void onMagnificationChanged(int displayId, @NonNull Region region, float scale,
float centerX, float centerY) {
- if (mMagnificationController != null) {
- mMagnificationController.dispatchMagnificationChanged(
- region, scale, centerX, centerY);
+ MagnificationController controller;
+ synchronized (mLock) {
+ controller = mMagnificationControllers.get(displayId);
+ }
+ if (controller != null) {
+ controller.dispatchMagnificationChanged(region, scale, centerX, centerY);
}
}
@@ -794,6 +826,7 @@
*/
public static final class MagnificationController {
private final AccessibilityService mService;
+ private final int mDisplayId;
/**
* Map of listeners to their handlers. Lazily created when adding the
@@ -802,19 +835,19 @@
private ArrayMap<OnMagnificationChangedListener, Handler> mListeners;
private final Object mLock;
- MagnificationController(@NonNull AccessibilityService service, @NonNull Object lock) {
+ MagnificationController(@NonNull AccessibilityService service, @NonNull Object lock,
+ int displayId) {
mService = service;
mLock = lock;
+ mDisplayId = displayId;
}
/**
* Called when the service is connected.
*/
- void onServiceConnected() {
- synchronized (mLock) {
- if (mListeners != null && !mListeners.isEmpty()) {
- setMagnificationCallbackEnabled(true);
- }
+ void onServiceConnectedLocked() {
+ if (mListeners != null && !mListeners.isEmpty()) {
+ setMagnificationCallbackEnabled(true);
}
}
@@ -891,7 +924,7 @@
mService.mConnectionId);
if (connection != null) {
try {
- connection.setMagnificationCallbackEnabled(enabled);
+ connection.setMagnificationCallbackEnabled(mDisplayId, enabled);
} catch (RemoteException re) {
throw new RuntimeException(re);
}
@@ -952,7 +985,7 @@
mService.mConnectionId);
if (connection != null) {
try {
- return connection.getMagnificationScale();
+ return connection.getMagnificationScale(mDisplayId);
} catch (RemoteException re) {
Log.w(LOG_TAG, "Failed to obtain scale", re);
re.rethrowFromSystemServer();
@@ -981,7 +1014,7 @@
mService.mConnectionId);
if (connection != null) {
try {
- return connection.getMagnificationCenterX();
+ return connection.getMagnificationCenterX(mDisplayId);
} catch (RemoteException re) {
Log.w(LOG_TAG, "Failed to obtain center X", re);
re.rethrowFromSystemServer();
@@ -1010,7 +1043,7 @@
mService.mConnectionId);
if (connection != null) {
try {
- return connection.getMagnificationCenterY();
+ return connection.getMagnificationCenterY(mDisplayId);
} catch (RemoteException re) {
Log.w(LOG_TAG, "Failed to obtain center Y", re);
re.rethrowFromSystemServer();
@@ -1044,7 +1077,7 @@
mService.mConnectionId);
if (connection != null) {
try {
- return connection.getMagnificationRegion();
+ return connection.getMagnificationRegion(mDisplayId);
} catch (RemoteException re) {
Log.w(LOG_TAG, "Failed to obtain magnified region", re);
re.rethrowFromSystemServer();
@@ -1073,7 +1106,7 @@
mService.mConnectionId);
if (connection != null) {
try {
- return connection.resetMagnification(animate);
+ return connection.resetMagnification(mDisplayId, animate);
} catch (RemoteException re) {
Log.w(LOG_TAG, "Failed to reset", re);
re.rethrowFromSystemServer();
@@ -1101,7 +1134,7 @@
mService.mConnectionId);
if (connection != null) {
try {
- return connection.setMagnificationScaleAndCenter(
+ return connection.setMagnificationScaleAndCenter(mDisplayId,
scale, Float.NaN, Float.NaN, animate);
} catch (RemoteException re) {
Log.w(LOG_TAG, "Failed to set scale", re);
@@ -1133,7 +1166,7 @@
mService.mConnectionId);
if (connection != null) {
try {
- return connection.setMagnificationScaleAndCenter(
+ return connection.setMagnificationScaleAndCenter(mDisplayId,
Float.NaN, centerX, centerY, animate);
} catch (RemoteException re) {
Log.w(LOG_TAG, "Failed to set center", re);
@@ -1624,9 +1657,10 @@
}
@Override
- public void onMagnificationChanged(@NonNull Region region,
+ public void onMagnificationChanged(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
- AccessibilityService.this.onMagnificationChanged(region, scale, centerX, centerY);
+ AccessibilityService.this.onMagnificationChanged(displayId, region, scale,
+ centerX, centerY);
}
@Override
@@ -1729,13 +1763,15 @@
mCaller.sendMessage(message);
}
- public void onMagnificationChanged(@NonNull Region region,
+ /** Magnification changed callbacks for different displays */
+ public void onMagnificationChanged(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
final SomeArgs args = SomeArgs.obtain();
args.arg1 = region;
args.arg2 = scale;
args.arg3 = centerX;
args.arg4 = centerY;
+ args.argi1 = displayId;
final Message message = mCaller.obtainMessageO(DO_ON_MAGNIFICATION_CHANGED, args);
mCaller.sendMessage(message);
@@ -1865,7 +1901,10 @@
final float scale = (float) args.arg2;
final float centerX = (float) args.arg3;
final float centerY = (float) args.arg4;
- mCallback.onMagnificationChanged(region, scale, centerX, centerY);
+ final int displayId = args.argi1;
+ args.recycle();
+ mCallback.onMagnificationChanged(displayId, region, scale,
+ centerX, centerY);
}
} return;
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index 4e96b8f..1dae4fc 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -41,7 +41,7 @@
void onKeyEvent(in KeyEvent event, int sequence);
- void onMagnificationChanged(in Region region, float scale, float centerX, float centerY);
+ void onMagnificationChanged(int displayId, in Region region, float scale, float centerX, float centerY);
void onSoftKeyboardShowModeChanged(int showMode);
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 276131f..8c38fe4 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -70,20 +70,20 @@
oneway void setOnKeyEventResult(boolean handled, int sequence);
- float getMagnificationScale();
+ float getMagnificationScale(int displayId);
- float getMagnificationCenterX();
+ float getMagnificationCenterX(int displayId);
- float getMagnificationCenterY();
+ float getMagnificationCenterY(int displayId);
- Region getMagnificationRegion();
+ Region getMagnificationRegion(int displayId);
- boolean resetMagnification(boolean animate);
+ boolean resetMagnification(int displayId, boolean animate);
- boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
+ boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX, float centerY,
boolean animate);
- void setMagnificationCallbackEnabled(boolean enabled);
+ void setMagnificationCallbackEnabled(int displayId, boolean enabled);
boolean setSoftKeyboardShowMode(int showMode);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 836627e..92f47e7 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -74,6 +74,7 @@
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.StrictMode;
import android.os.SystemProperties;
+import android.os.Trace;
import android.os.UserHandle;
import android.text.Selection;
import android.text.SpannableStringBuilder;
@@ -1049,33 +1050,56 @@
@Retention(RetentionPolicy.SOURCE)
@interface ContentCaptureNotificationType{}
-
- private void notifyContentCaptureManagerIfNeeded(@ContentCaptureNotificationType int type) {
- final ContentCaptureManager cm = getContentCaptureManager();
- if (cm == null) return;
-
+ private String getContentCaptureTypeAsString(@ContentCaptureNotificationType int type) {
switch (type) {
case CONTENT_CAPTURE_START:
- //TODO(b/111276913): decide whether the InteractionSessionId should be
- // saved / restored in the activity bundle - probably not
- int flags = 0;
- if ((getWindow().getAttributes().flags
- & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
- flags |= ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE;
- }
- cm.onActivityStarted(mToken, getComponentName(), flags);
- break;
+ return "START";
case CONTENT_CAPTURE_PAUSE:
- cm.flush(ContentCaptureSession.FLUSH_REASON_ACTIVITY_PAUSED);
- break;
+ return "PAUSE";
case CONTENT_CAPTURE_RESUME:
- cm.flush(ContentCaptureSession.FLUSH_REASON_ACTIVITY_RESUMED);
- break;
+ return "RESUME";
case CONTENT_CAPTURE_STOP:
- cm.onActivityStopped();
- break;
+ return "STOP";
default:
- Log.wtf(TAG, "Invalid @ContentCaptureNotificationType: " + type);
+ return "UNKNOW-" + type;
+ }
+ }
+
+ private void notifyContentCaptureManagerIfNeeded(@ContentCaptureNotificationType int type) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "notifyContentCapture(" + getContentCaptureTypeAsString(type) + ") for "
+ + mComponent.toShortString());
+ }
+ try {
+ final ContentCaptureManager cm = getContentCaptureManager();
+ if (cm == null) return;
+
+ switch (type) {
+ case CONTENT_CAPTURE_START:
+ //TODO(b/111276913): decide whether the InteractionSessionId should be
+ // saved / restored in the activity bundle - probably not
+ int flags = 0;
+ if ((getWindow().getAttributes().flags
+ & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
+ flags |= ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE;
+ }
+ cm.onActivityStarted(mToken, getComponentName(), flags);
+ break;
+ case CONTENT_CAPTURE_PAUSE:
+ cm.flush(ContentCaptureSession.FLUSH_REASON_ACTIVITY_PAUSED);
+ break;
+ case CONTENT_CAPTURE_RESUME:
+ cm.flush(ContentCaptureSession.FLUSH_REASON_ACTIVITY_RESUMED);
+ break;
+ case CONTENT_CAPTURE_STOP:
+ cm.onActivityStopped();
+ break;
+ default:
+ Log.wtf(TAG, "Invalid @ContentCaptureNotificationType: " + type);
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -8194,10 +8218,10 @@
final AutofillId autofillId = autofillIds[i];
final View view = autofillClientFindViewByAutofillIdTraversal(autofillId);
if (view != null) {
- if (!autofillId.isVirtual()) {
+ if (!autofillId.isVirtualInt()) {
visible[i] = view.isVisibleToUser();
} else {
- visible[i] = view.isVisibleToUserForAutofill(autofillId.getVirtualChildId());
+ visible[i] = view.isVisibleToUserForAutofill(autofillId.getVirtualChildIntId());
}
}
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index e0b8d78..1045b7a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3529,12 +3529,32 @@
/**
* Returns "true" if device is running in a test harness.
+ *
+ * @deprecated this method is false for all user builds. Users looking to check if their device
+ * is running in a device farm should see {@link #isRunningInUserTestHarness()}.
*/
+ @Deprecated
public static boolean isRunningInTestHarness() {
return SystemProperties.getBoolean("ro.test_harness", false);
}
/**
+ * Returns "true" if the device is running in Test Harness Mode.
+ *
+ * <p>Test Harness Mode is a feature that allows devices to run without human interaction in a
+ * device farm/testing harness (such as Firebase Test Lab). You should check this method if you
+ * want your app to behave differently when running in a test harness to skip setup screens that
+ * would impede UI testing. e.g. a keyboard application that has a full screen setup page for
+ * the first time it is launched.
+ *
+ * <p>Note that you should <em>not</em> use this to determine whether or not your app is running
+ * an instrumentation test, as it is not set for a standard device running a test.
+ */
+ public static boolean isRunningInUserTestHarness() {
+ return SystemProperties.getBoolean("persist.sys.test_harness", false);
+ }
+
+ /**
* Unsupported compiled sdk warning should always be shown for the intput activity
* even in cases where the system would normally not show the warning. E.g. when running in a
* test harness.
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 5cac048..86e658d 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -219,9 +219,11 @@
* @param userId
* @param event
* @param appToken ActivityRecord's appToken.
+ * @param taskRoot TaskRecord's root
*/
public abstract void updateActivityUsageStats(
- ComponentName activity, int userId, int event, IBinder appToken);
+ ComponentName activity, int userId, int event, IBinder appToken,
+ ComponentName taskRoot);
public abstract void updateForegroundTimeIfOnBattery(
String packageName, int uid, long cpuTimeDiff);
public abstract void sendForegroundProfileChanged(int userId);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 7767f04..a3243a5 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5939,6 +5939,11 @@
Binder.enableTracing();
}
+ // Initialize heap profiling.
+ if (isAppProfileable || Build.IS_DEBUGGABLE) {
+ nInitZygoteChildHeapProfiling();
+ }
+
// Allow renderer debugging features if we're debuggable.
boolean isAppDebuggable = (data.appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
HardwareRenderer.setDebuggingEnabled(isAppDebuggable || Build.IS_DEBUGGABLE);
@@ -6965,4 +6970,5 @@
// ------------------ Regular JNI ------------------------
private native void nPurgePendingResources();
private native void nDumpGraphicsInfo(FileDescriptor fd);
+ private native void nInitZygoteChildHeapProfiling();
}
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index ab8f234..4d3711a 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -328,7 +328,7 @@
}
} else {
mTmpTransaction.reparent(mRootSurfaceControl,
- mSurfaceView.getSurfaceControl().getHandle()).apply();
+ mSurfaceView.getSurfaceControl()).apply();
}
if (mVirtualDisplay != null) {
@@ -390,7 +390,7 @@
.build();
try {
- wm.reparentDisplayContent(displayId, mRootSurfaceControl.getHandle());
+ wm.reparentDisplayContent(displayId, mRootSurfaceControl);
wm.dontOverrideDisplayInfo(displayId);
if (mSingleTaskInstance) {
mActivityTaskManager.setDisplayToSingleTaskInstance(displayId);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index e155fe2..ab2430c 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -27,6 +27,7 @@
import android.annotation.UnsupportedAppUsage;
import android.app.usage.UsageStatsManager;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.database.ContentObserver;
import android.media.AudioAttributes.AttributeUsage;
@@ -41,9 +42,9 @@
import android.os.UserManager;
import android.provider.Settings;
import android.util.ArrayMap;
+import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import android.util.SparseArray;
import com.android.internal.app.IAppOpsActiveCallback;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsNotedCallback;
@@ -60,8 +61,8 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
/**
@@ -1489,7 +1490,7 @@
AppOpsManager.MODE_ALLOWED, // READ_ICC_SMS
AppOpsManager.MODE_ALLOWED, // WRITE_ICC_SMS
AppOpsManager.MODE_DEFAULT, // WRITE_SETTINGS
- AppOpsManager.MODE_DEFAULT, // SYSTEM_ALERT_WINDOW
+ getSystemAlertWindowDefault(), // SYSTEM_ALERT_WINDOW
AppOpsManager.MODE_ALLOWED, // ACCESS_NOTIFICATIONS
AppOpsManager.MODE_ALLOWED, // CAMERA
AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO
@@ -1731,6 +1732,15 @@
}
/**
+ * Retrieve a non-localized public name for the operation.
+ *
+ * @hide
+ */
+ public static @NonNull String opToPublicName(int op) {
+ return sOpToString[op];
+ }
+
+ /**
* @hide
*/
public static int strDebugOpToOp(String op) {
@@ -4807,4 +4817,21 @@
}
}
}
+
+ private static int getSystemAlertWindowDefault() {
+ final Context context = ActivityThread.currentApplication();
+ if (context == null) {
+ return AppOpsManager.MODE_DEFAULT;
+ }
+
+ // system alert window is disable on low ram phones starting from Q
+ final PackageManager pm = context.getPackageManager();
+ // TVs are constantly plugged in and has less concern for memory/power
+ if (ActivityManager.isLowRamDeviceStatic()
+ && !pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK, 0)) {
+ return AppOpsManager.MODE_IGNORED;
+ }
+
+ return AppOpsManager.MODE_DEFAULT;
+ }
}
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 1622c06..853b45e 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -1324,7 +1324,8 @@
* @param description the description that would appear for this file in Downloads App.
* @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files
* scanned by MediaScanner appear in the applications used to view media (for example,
- * Gallery app).
+ * Gallery app). Starting from {@link android.os.Build.VERSION_CODES#Q}, this argument is
+ * ignored and the file is always scanned by MediaScanner.
* @param mimeType mimetype of the file.
* @param path absolute pathname to the file. The file should be world-readable, so that it can
* be managed by the Downloads App and any other app that is used to read it (for example,
@@ -1353,7 +1354,8 @@
* @param description the description that would appear for this file in Downloads App.
* @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files
* scanned by MediaScanner appear in the applications used to view media (for example,
- * Gallery app).
+ * Gallery app). Starting from {@link android.os.Build.VERSION_CODES#Q}, this argument is
+ * ignored and the file is always scanned by MediaScanner.
* @param mimeType mimetype of the file.
* @param path absolute pathname to the file. The file should be world-readable, so that it can
* be managed by the Downloads App and any other app that is used to read it (for example,
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 1ae0f52..75c9054 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -19,6 +19,7 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -124,6 +125,7 @@
*
* @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;
Intent intent = new Intent(ACTION_CONFIRM_DEVICE_CREDENTIAL);
@@ -176,6 +178,7 @@
* @throws IllegalStateException if the device has already been provisioned
* @hide
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
@SystemApi
public Intent createConfirmFactoryResetCredentialIntent(
CharSequence title, CharSequence description, CharSequence alternateButtonLabel) {
@@ -231,6 +234,7 @@
* secure notifications cannot be shown if {@code false}
* @hide
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
@RequiresPermission(Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)
@SystemApi
public void setPrivateNotificationsAllowed(boolean allow) {
@@ -249,6 +253,7 @@
* By default, private notifications are allowed.
* @hide
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
@RequiresPermission(Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)
@SystemApi
public boolean getPrivateNotificationsAllowed() {
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 3f9627e..a021e3c 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -1240,7 +1240,7 @@
}
@Override
- public void onMagnificationChanged(@NonNull Region region,
+ public void onMagnificationChanged(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
/* do nothing */
}
diff --git a/core/java/android/app/VrManager.java b/core/java/android/app/VrManager.java
index 5b87de4..5f1a94c 100644
--- a/core/java/android/app/VrManager.java
+++ b/core/java/android/app/VrManager.java
@@ -215,19 +215,12 @@
}
/**
- * Start VR Input method for the packageName in {@link ComponentName}.
- * This method notifies InputMethodManagerService to use VR IME instead of
- * regular phone IME.
- * @param componentName ComponentName of a VR InputMethod that should be set as selected
- * input by InputMethodManagerService.
+ * This method is not implemented.
+ *
+ * @param componentName not used
*/
@RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
public void setVrInputMethod(ComponentName componentName) {
- try {
- mService.setVrInputMethod(componentName);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
}
/**
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 6d7c547..55a3acb 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1362,16 +1362,23 @@
public static final String EXTRA_RESTRICTION = "android.app.extra.RESTRICTION";
/**
- * Activity action: have the user enter a new password. This activity should
- * be launched after using {@link #setPasswordQuality(ComponentName, int)},
- * or {@link #setPasswordMinimumLength(ComponentName, int)} to have the user
- * enter a new password that meets the current requirements. You can use
- * {@link #isActivePasswordSufficient()} to determine whether you need to
- * have the user select a new password in order to meet the current
- * constraints. Upon being resumed from this activity, you can check the new
+ * Activity action: have the user enter a new password.
+ *
+ * <p>For admin apps, this activity should be launched after using {@link
+ * #setPasswordQuality(ComponentName, int)}, or {@link
+ * #setPasswordMinimumLength(ComponentName, int)} to have the user enter a new password that
+ * meets the current requirements. You can use {@link #isActivePasswordSufficient()} to
+ * determine whether you need to have the user select a new password in order to meet the
+ * current constraints. Upon being resumed from this activity, you can check the new
* password characteristics to see if they are sufficient.
*
- * If the intent is launched from within a managed profile with a profile
+ * <p>Non-admin apps can use {@link #getPasswordComplexity()} to check the current screen lock
+ * complexity, and use this activity with extra {@link #EXTRA_PASSWORD_COMPLEXITY} to suggest
+ * to users how complex the app wants the new screen lock to be. Note that both {@link
+ * #getPasswordComplexity()} and the extra {@link #EXTRA_PASSWORD_COMPLEXITY} require the
+ * calling app to have the permission {@link permission#GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY}.
+ *
+ * <p>If the intent is launched from within a managed profile with a profile
* owner built against {@link android.os.Build.VERSION_CODES#M} or before,
* this will trigger entering a new password for the parent of the profile.
* For all other cases it will trigger entering a new password for the user
@@ -1384,6 +1391,24 @@
= "android.app.action.SET_NEW_PASSWORD";
/**
+ * An integer indicating the complexity level of the new password an app would like the user to
+ * set when launching the action {@link #ACTION_SET_NEW_PASSWORD}.
+ *
+ * <p>Must be one of
+ * <ul>
+ * <li>{@link #PASSWORD_COMPLEXITY_HIGH}
+ * <li>{@link #PASSWORD_COMPLEXITY_MEDIUM}
+ * <li>{@link #PASSWORD_COMPLEXITY_LOW}
+ * <li>{@link #PASSWORD_COMPLEXITY_NONE}
+ * </ul>
+ *
+ * <p>If an invalid value is used, it will be treated as {@link #PASSWORD_COMPLEXITY_NONE}.
+ */
+ @RequiresPermission(android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY)
+ public static final String EXTRA_PASSWORD_COMPLEXITY =
+ "android.app.extra.PASSWORD_COMPLEXITY";
+
+ /**
* Constant for {@link #getPasswordComplexity()}: no password.
*
* <p>Note that these complexity constants are ordered so that higher values are more complex.
@@ -2513,6 +2538,9 @@
* requested quality constant (between the policy set here, the user's preference, and any other
* considerations) is the one that is in effect.
* <p>
+ * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ * <p>
* The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
* not, a security exception will be thrown.
@@ -2548,6 +2576,9 @@
* returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
* restrictions on the parent profile.
*
+ * <p>Note: on devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature,
+ * the password is always treated as empty.
+ *
* @param admin The name of the admin component to check, or {@code null} to aggregate
* all admins.
*/
@@ -2580,6 +2611,9 @@
* {@link #PASSWORD_QUALITY_ALPHANUMERIC}, or {@link #PASSWORD_QUALITY_COMPLEX} with
* {@link #setPasswordQuality}.
* <p>
+ * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ * <p>
* The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
* not, a security exception will be thrown.
@@ -2609,11 +2643,13 @@
* restrictions on this user and its participating profiles. Restrictions on profiles that have
* a separate challenge are not taken into account.
*
+ * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ *
* <p>This method can be called on the {@link DevicePolicyManager} instance
* returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
* restrictions on the parent profile.
*
- * user and its profiles or a particular one.
* @param admin The name of the admin component to check, or {@code null} to aggregate
* all admins.
*/
@@ -2644,6 +2680,9 @@
* setting this value. This constraint is only imposed if the administrator has also requested
* {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 0.
* <p>
+ * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ * <p>
* The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
* not, a security exception will be thrown.
@@ -2678,6 +2717,9 @@
* and only applies when the password quality is
* {@link #PASSWORD_QUALITY_COMPLEX}.
*
+ * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ *
* <p>This method can be called on the {@link DevicePolicyManager} instance
* returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
* restrictions on the parent profile.
@@ -2714,6 +2756,9 @@
* setting this value. This constraint is only imposed if the administrator has also requested
* {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 0.
* <p>
+ * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ * <p>
* The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
* not, a security exception will be thrown.
@@ -2748,6 +2793,9 @@
* and only applies when the password quality is
* {@link #PASSWORD_QUALITY_COMPLEX}.
*
+ * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ *
* <p>This method can be called on the {@link DevicePolicyManager} instance
* returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
* restrictions on the parent profile.
@@ -2784,6 +2832,9 @@
* only imposed if the administrator has also requested {@link #PASSWORD_QUALITY_COMPLEX} with
* {@link #setPasswordQuality}. The default value is 1.
* <p>
+ * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ * <p>
* The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
* not, a security exception will be thrown.
@@ -2818,6 +2869,9 @@
* and only applies when the password quality is
* {@link #PASSWORD_QUALITY_COMPLEX}.
*
+ * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ *
* <p>This method can be called on the {@link DevicePolicyManager} instance
* returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
* restrictions on the parent profile.
@@ -2853,6 +2907,9 @@
* setting this value. This constraint is only imposed if the administrator has also requested
* {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 1.
* <p>
+ * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ * <p>
* The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
* not, a security exception will be thrown.
@@ -2887,6 +2944,9 @@
* and only applies when the password quality is
* {@link #PASSWORD_QUALITY_COMPLEX}.
*
+ * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ *
* <p>This method can be called on the {@link DevicePolicyManager} instance
* returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
* restrictions on the parent profile.
@@ -2922,6 +2982,9 @@
* only imposed if the administrator has also requested {@link #PASSWORD_QUALITY_COMPLEX} with
* {@link #setPasswordQuality}. The default value is 1.
* <p>
+ * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ * <p>
* The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
* not, a security exception will be thrown.
@@ -2955,6 +3018,9 @@
* and only applies when the password quality is
* {@link #PASSWORD_QUALITY_COMPLEX}.
*
+ * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ *
* <p>This method can be called on the {@link DevicePolicyManager} instance
* returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
* restrictions on the parent profile.
@@ -2990,6 +3056,9 @@
* setting this value. This constraint is only imposed if the administrator has also requested
* {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 0.
* <p>
+ * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ * <p>
* The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
* not, a security exception will be thrown.
@@ -3024,6 +3093,9 @@
* and only applies when the password quality is
* {@link #PASSWORD_QUALITY_COMPLEX}.
*
+ * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ *
* <p>This method can be called on the {@link DevicePolicyManager} instance
* returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
* restrictions on the parent profile.
@@ -3060,6 +3132,9 @@
* , {@link #PASSWORD_QUALITY_NUMERIC_COMPLEX} {@link #PASSWORD_QUALITY_ALPHABETIC}, or
* {@link #PASSWORD_QUALITY_ALPHANUMERIC} with {@link #setPasswordQuality}.
* <p>
+ * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ * <p>
* The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
* not, a security exception will be thrown.
@@ -3112,6 +3187,7 @@
* @throws SecurityException if {@code admin} is not an active administrator or {@code admin}
* does not use {@link DeviceAdminInfo#USES_POLICY_EXPIRE_PASSWORD}
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public void setPasswordExpirationTimeout(@NonNull ComponentName admin, long timeout) {
if (mService != null) {
try {
@@ -3136,6 +3212,7 @@
* @param admin The name of the admin component to check, or {@code null} to aggregate all admins.
* @return The timeout for the given admin or the minimum of all timeouts
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public long getPasswordExpirationTimeout(@Nullable ComponentName admin) {
if (mService != null) {
try {
@@ -3160,6 +3237,7 @@
* @param admin The name of the admin component to check, or {@code null} to aggregate all admins.
* @return The password expiration time, in milliseconds since epoch.
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public long getPasswordExpiration(@Nullable ComponentName admin) {
if (mService != null) {
try {
@@ -3184,12 +3262,14 @@
* all admins.
* @return The length of the password history
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public int getPasswordHistoryLength(@Nullable ComponentName admin) {
return getPasswordHistoryLength(admin, myUserId());
}
/** @hide per-user version */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public int getPasswordHistoryLength(@Nullable ComponentName admin, int userHandle) {
if (mService != null) {
try {
@@ -3204,10 +3284,16 @@
/**
* Return the maximum password length that the device supports for a
* particular password quality.
+ * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always empty.
* @param quality The quality being interrogated.
* @return Returns the maximum length that the user can enter.
*/
public int getPasswordMaximumLength(int quality) {
+ PackageManager pm = mContext.getPackageManager();
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ID_ATTESTATION)) {
+ return 0;
+ }
// Kind-of arbitrary.
return 16;
}
@@ -3218,6 +3304,10 @@
* user and its participating profiles. Restrictions on profiles that have a separate challenge
* are not taken into account. The user must be unlocked in order to perform the check.
* <p>
+ * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty - i.e. this method will always return false on such
+ * devices, provided any password requirements were set.
+ * <p>
* The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
* not, a security exception will be thrown.
@@ -3250,6 +3340,9 @@
* explicitly querying the parent profile screen lock complexity via {@link
* #getParentProfileInstance}.
*
+ * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+ * password is always treated as empty.
+ *
* @throws IllegalStateException if the user is not unlocked.
* @throws SecurityException if the calling application does not have the permission
* {@link permission#GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY}
@@ -3329,6 +3422,7 @@
* @throws SecurityException if the calling application does not own an active administrator
* that uses {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN}
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public int getCurrentFailedPasswordAttempts() {
return getCurrentFailedPasswordAttempts(myUserId());
}
@@ -3396,6 +3490,7 @@
* both {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} and
* {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}.
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public void setMaximumFailedPasswordsForWipe(@NonNull ComponentName admin, int num) {
if (mService != null) {
try {
@@ -3419,12 +3514,14 @@
* @param admin The name of the admin component to check, or {@code null} to aggregate
* all admins.
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public int getMaximumFailedPasswordsForWipe(@Nullable ComponentName admin) {
return getMaximumFailedPasswordsForWipe(admin, myUserId());
}
/** @hide per-user version */
@UnsupportedAppUsage
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public int getMaximumFailedPasswordsForWipe(@Nullable ComponentName admin, int userHandle) {
if (mService != null) {
try {
@@ -3444,6 +3541,7 @@
* user passed in.
* @hide Used only by Keyguard
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public int getProfileWithMinimumFailedPasswordsForWipe(int userHandle) {
if (mService != null) {
try {
@@ -3514,6 +3612,7 @@
* that uses {@link DeviceAdminInfo#USES_POLICY_RESET_PASSWORD}
* @throws IllegalStateException if the calling user is locked or has a managed profile.
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public boolean resetPassword(String password, int flags) {
throwIfParentInstance("resetPassword");
if (mService != null) {
@@ -3557,6 +3656,7 @@
* @throws SecurityException if admin is not a device or profile owner.
* @throws IllegalArgumentException if the supplied token is invalid.
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public boolean setResetPasswordToken(ComponentName admin, byte[] token) {
throwIfParentInstance("setResetPasswordToken");
if (mService != null) {
@@ -3576,6 +3676,7 @@
* @return true if the operation is successful, false otherwise.
* @throws SecurityException if admin is not a device or profile owner.
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public boolean clearResetPasswordToken(ComponentName admin) {
throwIfParentInstance("clearResetPasswordToken");
if (mService != null) {
@@ -3596,6 +3697,7 @@
* @throws SecurityException if admin is not a device or profile owner.
* @throws IllegalStateException if no token has been set.
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public boolean isResetPasswordTokenActive(ComponentName admin) {
throwIfParentInstance("isResetPasswordTokenActive");
if (mService != null) {
@@ -3637,6 +3739,7 @@
* @throws SecurityException if admin is not a device or profile owner.
* @throws IllegalStateException if the provided token is not valid.
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public boolean resetPasswordWithToken(@NonNull ComponentName admin, String password,
byte[] token, int flags) {
throwIfParentInstance("resetPassword");
@@ -3742,6 +3845,7 @@
*
* @throws SecurityException if {@code admin} is not a device or profile owner.
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public void setRequiredStrongAuthTimeout(@NonNull ComponentName admin,
long timeoutMs) {
if (mService != null) {
@@ -3766,12 +3870,14 @@
* across all participating admins.
* @return The timeout in milliseconds or 0 if not configured for the provided admin.
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public long getRequiredStrongAuthTimeout(@Nullable ComponentName admin) {
return getRequiredStrongAuthTimeout(admin, myUserId());
}
/** @hide per-user version */
@UnsupportedAppUsage
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public long getRequiredStrongAuthTimeout(@Nullable ComponentName admin, @UserIdInt int userId) {
if (mService != null) {
try {
@@ -5350,6 +5456,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public void setActivePasswordState(PasswordMetrics metrics, int userHandle) {
if (mService != null) {
try {
@@ -5363,6 +5470,7 @@
/**
* @hide
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public void reportPasswordChanged(@UserIdInt int userId) {
if (mService != null) {
try {
@@ -5377,6 +5485,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public void reportFailedPasswordAttempt(int userHandle) {
if (mService != null) {
try {
@@ -5391,6 +5500,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public void reportSuccessfulPasswordAttempt(int userHandle) {
if (mService != null) {
try {
@@ -5404,6 +5514,7 @@
/**
* @hide
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public void reportFailedBiometricAttempt(int userHandle) {
if (mService != null) {
try {
@@ -5417,6 +5528,7 @@
/**
* @hide
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public void reportSuccessfulBiometricAttempt(int userHandle) {
if (mService != null) {
try {
@@ -6383,6 +6495,7 @@
* @throws SecurityException if {@code admin} is not an active administrator or does not use
* {@link DeviceAdminInfo#USES_POLICY_DISABLE_KEYGUARD_FEATURES}
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public void setTrustAgentConfiguration(@NonNull ComponentName admin,
@NonNull ComponentName target, PersistableBundle configuration) {
if (mService != null) {
@@ -6412,6 +6525,7 @@
* @param agent Which component to get enabled features for.
* @return configuration for the given trust agent.
*/
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public @Nullable List<PersistableBundle> getTrustAgentConfiguration(
@Nullable ComponentName admin, @NonNull ComponentName agent) {
return getTrustAgentConfiguration(admin, agent, myUserId());
@@ -6419,6 +6533,7 @@
/** @hide per-user version */
@UnsupportedAppUsage
+ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public @Nullable List<PersistableBundle> getTrustAgentConfiguration(
@Nullable ComponentName admin, @NonNull ComponentName agent, int userHandle) {
if (mService != null) {
diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java
index 8b41755..e5df2c7 100644
--- a/core/java/android/app/admin/PasswordMetrics.java
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -20,6 +20,11 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -27,6 +32,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -85,6 +92,101 @@
nonLetter = in.readInt();
}
+ /** Returns the min quality allowed by {@code complexityLevel}. */
+ public static int complexityLevelToMinQuality(@PasswordComplexity int complexityLevel) {
+ // this would be the quality of the first metrics since mMetrics is sorted in ascending
+ // order of quality
+ return PasswordComplexityBucket
+ .complexityLevelToBucket(complexityLevel).mMetrics[0].quality;
+ }
+
+ /**
+ * Returns a merged minimum {@link PasswordMetrics} requirements that a new password must meet
+ * to fulfil {@code requestedQuality}, {@code requiresNumeric} and {@code
+ * requiresLettersOrSymbols}, which are derived from {@link DevicePolicyManager} requirements,
+ * and {@code complexityLevel}.
+ *
+ * <p>Note that we are taking {@code userEnteredPasswordQuality} into account because there are
+ * more than one set of metrics to meet the minimum complexity requirement and inspecting what
+ * the user has entered can help determine whether the alphabetic or alphanumeric set of metrics
+ * should be used. For example, suppose minimum complexity requires either ALPHABETIC(8+), or
+ * ALPHANUMERIC(6+). If the user has entered "a", the length requirement displayed on the UI
+ * would be 8. Then the user appends "1" to make it "a1". We now know the user is entering
+ * an alphanumeric password so we would update the min complexity required min length to 6.
+ */
+ public static PasswordMetrics getMinimumMetrics(@PasswordComplexity int complexityLevel,
+ int userEnteredPasswordQuality, int requestedQuality, boolean requiresNumeric,
+ boolean requiresLettersOrSymbols) {
+ int targetQuality = Math.max(
+ userEnteredPasswordQuality,
+ getActualRequiredQuality(
+ requestedQuality, requiresNumeric, requiresLettersOrSymbols));
+ return getTargetQualityMetrics(complexityLevel, targetQuality);
+ }
+
+ /**
+ * Returns the {@link PasswordMetrics} at {@code complexityLevel} which the metrics quality
+ * is the same as {@code targetQuality}.
+ *
+ * <p>If {@code complexityLevel} does not allow {@code targetQuality}, returns the metrics
+ * with the min quality at {@code complexityLevel}.
+ */
+ // TODO(bernardchau): update tests to test getMinimumMetrics and change this to be private
+ @VisibleForTesting
+ public static PasswordMetrics getTargetQualityMetrics(
+ @PasswordComplexity int complexityLevel, int targetQuality) {
+ PasswordComplexityBucket targetBucket =
+ PasswordComplexityBucket.complexityLevelToBucket(complexityLevel);
+ for (PasswordMetrics metrics : targetBucket.mMetrics) {
+ if (targetQuality == metrics.quality) {
+ return metrics;
+ }
+ }
+ // none of the metrics at complexityLevel has targetQuality, return metrics with min quality
+ // see test case testGetMinimumMetrics_actualRequiredQualityStricter for an example, where
+ // min complexity allows at least NUMERIC_COMPLEX, user has not entered anything yet, and
+ // requested quality is NUMERIC
+ return targetBucket.mMetrics[0];
+ }
+
+ /**
+ * Finds out the actual quality requirement based on whether quality is {@link
+ * DevicePolicyManager#PASSWORD_QUALITY_COMPLEX} and whether digits, letters or symbols are
+ * required.
+ */
+ @VisibleForTesting
+ // TODO(bernardchau): update tests to test getMinimumMetrics and change this to be private
+ public static int getActualRequiredQuality(
+ int requestedQuality, boolean requiresNumeric, boolean requiresLettersOrSymbols) {
+ if (requestedQuality != PASSWORD_QUALITY_COMPLEX) {
+ return requestedQuality;
+ }
+
+ // find out actual password quality from complex requirements
+ if (requiresNumeric && requiresLettersOrSymbols) {
+ return PASSWORD_QUALITY_ALPHANUMERIC;
+ }
+ if (requiresLettersOrSymbols) {
+ return PASSWORD_QUALITY_ALPHABETIC;
+ }
+ if (requiresNumeric) {
+ // cannot specify numeric complex using complex quality so this must be numeric
+ return PASSWORD_QUALITY_NUMERIC;
+ }
+
+ // reaching here means dpm sets quality to complex without specifying any requirements
+ return PASSWORD_QUALITY_UNSPECIFIED;
+ }
+
+ /**
+ * Returns {@code complexityLevel} or {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE}
+ * if {@code complexityLevel} is not valid.
+ */
+ @PasswordComplexity
+ public static int sanitizeComplexityLevel(@PasswordComplexity int complexityLevel) {
+ return PasswordComplexityBucket.complexityLevelToBucket(complexityLevel).mComplexityLevel;
+ }
+
public boolean isDefault() {
return quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED
&& length == 0 && letters == 0 && upperCase == 0 && lowerCase == 0
@@ -280,7 +382,7 @@
@PasswordComplexity
public int determineComplexity() {
for (PasswordComplexityBucket bucket : PasswordComplexityBucket.BUCKETS) {
- if (satisfiesBucket(bucket.getMetrics())) {
+ if (satisfiesBucket(bucket.mMetrics)) {
return bucket.mComplexityLevel;
}
}
@@ -290,7 +392,7 @@
/**
* Requirements in terms of {@link PasswordMetrics} for each {@link PasswordComplexity}.
*/
- public static class PasswordComplexityBucket {
+ private static class PasswordComplexityBucket {
/**
* Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_HIGH} in terms of
* {@link PasswordMetrics}.
@@ -299,12 +401,13 @@
new PasswordComplexityBucket(
PASSWORD_COMPLEXITY_HIGH,
new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 6),
+ DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
+ 8),
new PasswordMetrics(
DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 6),
new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
- 8));
+ DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */
+ 6));
/**
* Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_MEDIUM} in terms of
@@ -314,11 +417,12 @@
new PasswordComplexityBucket(
PASSWORD_COMPLEXITY_MEDIUM,
new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 4),
+ DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
+ 4),
new PasswordMetrics(
DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 4),
new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
+ DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */
4));
/**
@@ -328,11 +432,11 @@
private static final PasswordComplexityBucket LOW =
new PasswordComplexityBucket(
PASSWORD_COMPLEXITY_LOW,
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX),
+ new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING),
new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING));
+ new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX),
+ new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC),
+ new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC));
/**
* A special bucket to represent {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE}.
@@ -348,19 +452,27 @@
private final int mComplexityLevel;
private final PasswordMetrics[] mMetrics;
+ /**
+ * @param metricsArray must be sorted in ascending order of {@link #quality}.
+ */
private PasswordComplexityBucket(@PasswordComplexity int complexityLevel,
- PasswordMetrics... metrics) {
- this.mComplexityLevel = complexityLevel;
- this.mMetrics = metrics;
- }
+ PasswordMetrics... metricsArray) {
+ int previousQuality = PASSWORD_QUALITY_UNSPECIFIED;
+ for (PasswordMetrics metrics : metricsArray) {
+ if (metrics.quality < previousQuality) {
+ throw new IllegalArgumentException("metricsArray must be sorted in ascending"
+ + " order of quality");
+ }
+ previousQuality = metrics.quality;
+ }
- /** Returns the {@link PasswordMetrics} that meet the min requirements of this bucket. */
- public PasswordMetrics[] getMetrics() {
- return mMetrics;
+ this.mMetrics = metricsArray;
+ this.mComplexityLevel = complexityLevel;
+
}
/** Returns the bucket that {@code complexityLevel} represents. */
- public static PasswordComplexityBucket complexityLevelToBucket(
+ private static PasswordComplexityBucket complexityLevelToBucket(
@PasswordComplexity int complexityLevel) {
for (PasswordComplexityBucket bucket : BUCKETS) {
if (bucket.mComplexityLevel == complexityLevel) {
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 7d03f00..6006ad2 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -894,7 +894,7 @@
}
if (mAutofillId != null) {
autofillFlags |= AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID;
- if (mAutofillId.isVirtual()) {
+ if (mAutofillId.isVirtualInt()) {
autofillFlags |= AUTOFILL_FLAGS_HAS_AUTOFILL_VIRTUAL_VIEW_ID;
}
}
@@ -961,8 +961,9 @@
if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID) != 0) {
out.writeInt(mAutofillId.getViewId());
if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIRTUAL_VIEW_ID) != 0) {
- out.writeInt(mAutofillId.getVirtualChildId());
+ out.writeInt(mAutofillId.getVirtualChildIntId());
}
+ // TODO(b/113593220): write session id as well
}
if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_TYPE) != 0) {
out.writeInt(mAutofillType);
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index c983d4f..24580b4 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -16,6 +16,7 @@
package android.app.backup;
+import android.annotation.Nullable;
import android.app.IBackupAgent;
import android.app.QueuedWork;
import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags;
@@ -181,6 +182,8 @@
Handler mHandler = null;
+ @Nullable private UserHandle mUser;
+
Handler getHandler() {
if (mHandler == null) {
mHandler = new Handler(Looper.getMainLooper());
@@ -232,6 +235,8 @@
*/
public void onCreate(UserHandle user) {
onCreate();
+
+ mUser = user;
}
/**
@@ -528,6 +533,10 @@
public void onQuotaExceeded(long backupDataBytes, long quotaBytes) {
}
+ private int getBackupUserId() {
+ return mUser == null ? super.getUserId() : mUser.getIdentifier();
+ }
+
/**
* Check whether the xml yielded any <include/> tag for the provided <code>domainToken</code>.
* If so, perform a {@link #fullBackupFileTree} which backs up the file or recurses if the path
@@ -1033,7 +1042,7 @@
Binder.restoreCallingIdentity(ident);
try {
- callbackBinder.opComplete(token, 0);
+ callbackBinder.opCompleteForUser(getBackupUserId(), token, 0);
} catch (RemoteException e) {
// we'll time out anyway, so we're safe
}
@@ -1082,7 +1091,7 @@
Binder.restoreCallingIdentity(ident);
try {
- callbackBinder.opComplete(token, 0);
+ callbackBinder.opCompleteForUser(getBackupUserId(), token, 0);
} catch (RemoteException e) {
// we'll time out anyway, so we're safe
}
@@ -1112,7 +1121,8 @@
} finally {
Binder.restoreCallingIdentity(ident);
try {
- callbackBinder.opComplete(token, measureOutput.getSize());
+ callbackBinder.opCompleteForUser(getBackupUserId(), token,
+ measureOutput.getSize());
} catch (RemoteException e) {
// timeout, so we're safe
}
@@ -1137,7 +1147,7 @@
Binder.restoreCallingIdentity(ident);
try {
- callbackBinder.opComplete(token, 0);
+ callbackBinder.opCompleteForUser(getBackupUserId(), token, 0);
} catch (RemoteException e) {
// we'll time out anyway, so we're safe
}
@@ -1162,7 +1172,7 @@
Binder.restoreCallingIdentity(ident);
try {
- callbackBinder.opComplete(token, 0);
+ callbackBinder.opCompleteForUser(getBackupUserId(), token, 0);
} catch (RemoteException e) {
// we'll time out anyway, so we're safe
}
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index f8c5a81..eda8981 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -549,6 +549,19 @@
/**
* Notify the backup manager that a BackupAgent has completed the operation
+ * corresponding to the given token and user id.
+ *
+ * @param userId User id for which the operation has been completed.
+ * @param token The transaction token passed to the BackupAgent method being
+ * invoked.
+ * @param result In the case of a full backup measure operation, the estimated
+ * total file size that would result from the operation. Unused in all other
+ * cases.
+ */
+ void opCompleteForUser(int userId, int token, long result);
+
+ /**
+ * Notify the backup manager that a BackupAgent has completed the operation
* corresponding to the given token.
*
* @param token The transaction token passed to the BackupAgent method being
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index bbae7d3..d2934b9 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -59,4 +59,6 @@
void reportPastUsageStart(in IBinder activity, String token, long timeAgoMs,
String callingPackage);
void reportUsageStop(in IBinder activity, String token, String callingPackage);
+ int getUsageSource();
+ void forceUsageSourceSettingRead();
}
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 2c5fe04..451f44b 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -16,6 +16,7 @@
package android.app.usage;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.content.res.Configuration;
@@ -286,7 +287,6 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public String mClass;
-
/**
* {@hide}
*/
@@ -295,6 +295,16 @@
/**
* {@hide}
*/
+ public String mTaskRootPackage;
+
+ /**
+ * {@hide}
+ */
+ public String mTaskRootClass;
+
+ /**
+ * {@hide}
+ */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public long mTimeStamp;
@@ -373,6 +383,8 @@
mPackage = orig.mPackage;
mClass = orig.mClass;
mInstanceId = orig.mInstanceId;
+ mTaskRootPackage = orig.mTaskRootPackage;
+ mTaskRootClass = orig.mTaskRootClass;
mTimeStamp = orig.mTimeStamp;
mEventType = orig.mEventType;
mConfiguration = orig.mConfiguration;
@@ -411,6 +423,28 @@
}
/**
+ * The package name of the task root when this event was reported.
+ * Or {@code null} for queries from apps without {@link
+ * android.Manifest.permission#PACKAGE_USAGE_STATS}
+ * @hide
+ */
+ @SystemApi
+ public @Nullable String getTaskRootPackageName() {
+ return mTaskRootPackage;
+ }
+
+ /**
+ * The class name of the task root when this event was reported.
+ * Or {@code null} for queries from apps without {@link
+ * android.Manifest.permission#PACKAGE_USAGE_STATS}
+ * @hide
+ */
+ @SystemApi
+ public @Nullable String getTaskRootClassName() {
+ return mTaskRootClass;
+ }
+
+ /**
* The time at which this event occurred, measured in milliseconds since the epoch.
* <p/>
* See {@link System#currentTimeMillis()}.
@@ -522,6 +556,9 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private int mIndex = 0;
+ // Only used when parceling events. If false, task roots will be omitted from the parcel
+ private final boolean mIncludeTaskRoots;
+
/*
* In order to save space, since ComponentNames will be duplicated everywhere,
* we use a map and index into it.
@@ -552,6 +589,7 @@
mParcel.setDataSize(mParcel.dataPosition());
mParcel.setDataPosition(positionInParcel);
}
+ mIncludeTaskRoots = true;
}
/**
@@ -560,16 +598,27 @@
*/
UsageEvents() {
mEventCount = 0;
+ mIncludeTaskRoots = true;
+ }
+
+ /**
+ * Construct the iterator in preparation for writing it to a parcel.
+ * Defaults to excluding task roots from the parcel.
+ * {@hide}
+ */
+ public UsageEvents(List<Event> events, String[] stringPool) {
+ this(events, stringPool, false);
}
/**
* Construct the iterator in preparation for writing it to a parcel.
* {@hide}
*/
- public UsageEvents(List<Event> events, String[] stringPool) {
+ public UsageEvents(List<Event> events, String[] stringPool, boolean includeTaskRoots) {
mStringPool = stringPool;
mEventCount = events.size();
mEventsToWrite = events;
+ mIncludeTaskRoots = includeTaskRoots;
}
/**
@@ -645,9 +694,25 @@
} else {
classIndex = -1;
}
+
+ final int taskRootPackageIndex;
+ if (mIncludeTaskRoots && event.mTaskRootPackage != null) {
+ taskRootPackageIndex = findStringIndex(event.mTaskRootPackage);
+ } else {
+ taskRootPackageIndex = -1;
+ }
+
+ final int taskRootClassIndex;
+ if (mIncludeTaskRoots && event.mTaskRootClass != null) {
+ taskRootClassIndex = findStringIndex(event.mTaskRootClass);
+ } else {
+ taskRootClassIndex = -1;
+ }
p.writeInt(packageIndex);
p.writeInt(classIndex);
p.writeInt(event.mInstanceId);
+ p.writeInt(taskRootPackageIndex);
+ p.writeInt(taskRootClassIndex);
p.writeInt(event.mEventType);
p.writeLong(event.mTimeStamp);
@@ -691,6 +756,21 @@
eventOut.mClass = null;
}
eventOut.mInstanceId = p.readInt();
+
+ final int taskRootPackageIndex = p.readInt();
+ if (taskRootPackageIndex >= 0) {
+ eventOut.mTaskRootPackage = mStringPool[taskRootPackageIndex];
+ } else {
+ eventOut.mTaskRootPackage = null;
+ }
+
+ final int taskRootClassIndex = p.readInt();
+ if (taskRootClassIndex >= 0) {
+ eventOut.mTaskRootClass = mStringPool[taskRootClassIndex];
+ } else {
+ eventOut.mTaskRootClass = null;
+ }
+
eventOut.mEventType = p.readInt();
eventOut.mTimeStamp = p.readLong();
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 605deac..d2de887 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -22,6 +22,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
import android.app.PendingIntent;
@@ -234,6 +235,29 @@
@SystemApi
public static final String EXTRA_TIME_USED = "android.app.usage.extra.TIME_USED";
+
+ /**
+ * App usage observers will consider the task root package the source of usage.
+ * @hide
+ */
+ @SystemApi
+ public static final int USAGE_SOURCE_TASK_ROOT_ACTIVITY = 1;
+
+ /**
+ * App usage observers will consider the visible activity's package the source of usage.
+ * @hide
+ */
+ @SystemApi
+ public static final int USAGE_SOURCE_CURRENT_ACTIVITY = 2;
+
+ /** @hide */
+ @IntDef(prefix = { "USAGE_SOURCE_" }, value = {
+ USAGE_SOURCE_TASK_ROOT_ACTIVITY,
+ USAGE_SOURCE_CURRENT_ACTIVITY,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UsageSource {}
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private static final UsageEvents sEmptyResults = new UsageEvents();
@@ -776,6 +800,38 @@
}
}
+ /**
+ * Get what App Usage Observers will consider the source of usage for an activity. Usage Source
+ * is decided at boot and will not change until next boot.
+ * @see #USAGE_SOURCE_TASK_ROOT_ACTIVITY
+ * @see #USAGE_SOURCE_CURRENT_ACTIVITY
+ *
+ * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission and
+ * is not the profile owner of this user.
+ * @hide
+ */
+ @SystemApi
+ public @UsageSource int getUsageSource() {
+ try {
+ return mService.getUsageSource();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Force the Usage Source be reread from global settings.
+ * @hide
+ */
+ @TestApi
+ public void forceUsageSourceSettingRead() {
+ try {
+ mService.forceUsageSourceSettingRead();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/** @hide */
public static String reasonToString(int standbyReason) {
StringBuilder sb = new StringBuilder();
@@ -845,6 +901,22 @@
return sb.toString();
}
+ /** @hide */
+ public static String usageSourceToString(int usageSource) {
+ switch (usageSource) {
+ case USAGE_SOURCE_TASK_ROOT_ACTIVITY:
+ return "TASK_ROOT_ACTIVITY";
+ case USAGE_SOURCE_CURRENT_ACTIVITY:
+ return "CURRENT_ACTIVITY";
+ default:
+ StringBuilder sb = new StringBuilder();
+ sb.append("UNKNOWN(");
+ sb.append(usageSource);
+ sb.append(")");
+ return sb.toString();
+ }
+ }
+
/**
* {@hide}
* Temporarily whitelist the specified app for a short duration. This is to allow an app
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index cc3ab00..d2d0cf9 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -40,9 +40,11 @@
* {@link UsageEvents}
* @param instanceId For activity, hashCode of ActivityRecord's appToken.
* For non-activity, it is not used.
+ * @param taskRoot For activity, the name of the package at the root of the task
+ * For non-activity, it is not used.
*/
public abstract void reportEvent(ComponentName component, @UserIdInt int userId, int eventType,
- int instanceId);
+ int instanceId, ComponentName taskRoot);
/**
* Reports an event to the UsageStatsManager.
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 1945b2f..97bc079 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -36,6 +36,7 @@
import android.content.Context;
import android.os.BatteryStats;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.RemoteException;
@@ -648,6 +649,32 @@
private final Object mLock = new Object();
private final Map<LeScanCallback, ScanCallback> mLeScanClients;
+ private static final Map<BluetoothDevice, List<Pair<MetadataListener, Handler>>>
+ sMetadataListeners = new HashMap<>();
+
+ /**
+ * Bluetooth metadata listener. Overrides the default BluetoothMetadataListener
+ * implementation.
+ */
+ private static final IBluetoothMetadataListener sBluetoothMetadataListener =
+ new IBluetoothMetadataListener.Stub() {
+ @Override
+ public void onMetadataChanged(BluetoothDevice device, int key, String value) {
+ synchronized (sMetadataListeners) {
+ if (sMetadataListeners.containsKey(device)) {
+ List<Pair<MetadataListener, Handler>> list = sMetadataListeners.get(device);
+ for (Pair<MetadataListener, Handler> pair : list) {
+ MetadataListener listener = pair.first;
+ Handler handler = pair.second;
+ handler.post(() -> {
+ listener.onMetadataChanged(device, key, value);
+ });
+ }
+ }
+ }
+ return;
+ }
+ };
/**
* Get a handle to the default local Bluetooth adapter.
@@ -2607,6 +2634,16 @@
}
}
}
+ synchronized (sMetadataListeners) {
+ sMetadataListeners.forEach((device, pair) -> {
+ try {
+ mService.registerMetadataListener(sBluetoothMetadataListener,
+ device);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register metadata listener", e);
+ }
+ });
+ }
}
public void onBluetoothServiceDown() {
@@ -3090,4 +3127,142 @@
+ "listenUsingInsecureL2capChannel");
return listenUsingInsecureL2capChannel();
}
+
+ /**
+ * Register a {@link #MetadataListener} to receive update about metadata
+ * changes for this {@link BluetoothDevice}.
+ * Registration must be done when Bluetooth is ON and will last until
+ * {@link #unregisterMetadataListener(BluetoothDevice)} is called, even when Bluetooth
+ * restarted in the middle.
+ * All input parameters should not be null or {@link NullPointerException} will be triggered.
+ * The same {@link BluetoothDevice} and {@link #MetadataListener} pair can only be registered
+ * once, double registration would cause {@link IllegalArgumentException}.
+ *
+ * @param device {@link BluetoothDevice} that will be registered
+ * @param listener {@link #MetadataListener} that will receive asynchronous callbacks
+ * @param handler the handler for listener callback
+ * @return true on success, false on error
+ * @throws NullPointerException If one of {@code listener}, {@code device} or {@code handler}
+ * is null.
+ * @throws IllegalArgumentException The same {@link #MetadataListener} and
+ * {@link BluetoothDevice} are registered twice.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public boolean registerMetadataListener(BluetoothDevice device, MetadataListener listener,
+ Handler handler) {
+ if (DBG) Log.d(TAG, "registerMetdataListener()");
+
+ final IBluetooth service = mService;
+ if (service == null) {
+ Log.e(TAG, "Bluetooth is not enabled. Cannot register metadata listener");
+ return false;
+ }
+ if (listener == null) {
+ throw new NullPointerException("listener is null");
+ }
+ if (device == null) {
+ throw new NullPointerException("device is null");
+ }
+ if (handler == null) {
+ throw new NullPointerException("handler is null");
+ }
+
+ synchronized (sMetadataListeners) {
+ List<Pair<MetadataListener, Handler>> listenerList = sMetadataListeners.get(device);
+ if (listenerList == null) {
+ // Create new listener/handler list for registeration
+ listenerList = new ArrayList<>();
+ sMetadataListeners.put(device, listenerList);
+ } else {
+ // Check whether this device was already registed by the lisenter
+ if (listenerList.stream().anyMatch((pair) -> (pair.first.equals(listener)))) {
+ throw new IllegalArgumentException("listener was already regestered"
+ + " for the device");
+ }
+ }
+
+ Pair<MetadataListener, Handler> listenerPair = new Pair(listener, handler);
+ listenerList.add(listenerPair);
+
+ boolean ret = false;
+ try {
+ ret = service.registerMetadataListener(sBluetoothMetadataListener, device);
+ } catch (RemoteException e) {
+ Log.e(TAG, "registerMetadataListener fail", e);
+ } finally {
+ if (!ret) {
+ // Remove listener registered earlier when fail.
+ listenerList.remove(listenerPair);
+ if (listenerList.isEmpty()) {
+ // Remove the device if its listener list is empty
+ sMetadataListeners.remove(device);
+ }
+ }
+ }
+ return ret;
+ }
+ }
+
+ /**
+ * Unregister all {@link MetadataListener} from this {@link BluetoothDevice}.
+ * Unregistration can be done when Bluetooth is either ON or OFF.
+ * {@link #registerMetadataListener(MetadataListener, BluetoothDevice, Handler)} must
+ * be called before unregisteration.
+ * Unregistering a device that is not regestered would cause {@link IllegalArgumentException}.
+ *
+ * @param device {@link BluetoothDevice} that will be unregistered. it
+ * should not be null or {@link NullPointerException} will be triggered.
+ * @return true on success, false on error
+ * @throws NullPointerException If {@code device} is null.
+ * @throws IllegalArgumentException If {@code device} has not been registered before.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public boolean unregisterMetadataListener(BluetoothDevice device) {
+ if (DBG) Log.d(TAG, "unregisterMetdataListener()");
+ if (device == null) {
+ throw new NullPointerException("device is null");
+ }
+
+ synchronized (sMetadataListeners) {
+ if (sMetadataListeners.containsKey(device)) {
+ sMetadataListeners.remove(device);
+ } else {
+ throw new IllegalArgumentException("device was not registered");
+ }
+
+ final IBluetooth service = mService;
+ if (service == null) {
+ // Bluetooth is OFF, do nothing to Bluetooth service.
+ return true;
+ }
+ try {
+ return service.unregisterMetadataListener(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, "unregisterMetadataListener fail", e);
+ return false;
+ }
+ }
+ }
+
+ /**
+ * This abstract class is used to implement {@link BluetoothAdapter} metadata listener.
+ * @hide
+ */
+ @SystemApi
+ public abstract class MetadataListener {
+ /**
+ * Callback triggered if the metadata of {@link BluetoothDevice} registered in
+ * {@link #registerMetadataListener}.
+ *
+ * @param device changed {@link BluetoothDevice}.
+ * @param key changed metadata key, one of BluetoothDevice.METADATA_*.
+ * @param value the new value of metadata.
+ */
+ public void onMetadataChanged(BluetoothDevice device, int key, String value) {
+ }
+ }
}
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 235dc5c..17cf702 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -341,6 +341,137 @@
"android.bluetooth.device.action.SDP_RECORD";
/**
+ * Maximum length of a metadata entry, this is to avoid exploding Bluetooth
+ * disk usage
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_MAX_LENGTH = 2048;
+
+ /**
+ * Manufacturer name of this Bluetooth device
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_MANUFACTURER_NAME = 0;
+
+ /**
+ * Model name of this Bluetooth device
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_MODEL_NAME = 1;
+
+ /**
+ * Software version of this Bluetooth device
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_SOFTWARE_VERSION = 2;
+
+ /**
+ * Hardware version of this Bluetooth device
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_HARDWARE_VERSION = 3;
+
+ /**
+ * Package name of the companion app, if any
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_COMPANION_APP = 4;
+
+ /**
+ * URI to the main icon shown on the settings UI
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_MAIN_ICON = 5;
+
+ /**
+ * Whether this device is an untethered headset with left, right and case
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_IS_UNTHETHERED_HEADSET = 6;
+
+ /**
+ * URI to icon of the left headset
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_UNTHETHERED_LEFT_ICON = 7;
+
+ /**
+ * URI to icon of the right headset
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_UNTHETHERED_RIGHT_ICON = 8;
+
+ /**
+ * URI to icon of the headset charging case
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_UNTHETHERED_CASE_ICON = 9;
+
+ /**
+ * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN}
+ * is invalid, of the left headset
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_UNTHETHERED_LEFT_BATTERY = 10;
+
+ /**
+ * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN}
+ * is invalid, of the right headset
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_UNTHETHERED_RIGHT_BATTERY = 11;
+
+ /**
+ * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN}
+ * is invalid, of the headset charging case
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_UNTHETHERED_CASE_BATTERY = 12;
+
+ /**
+ * Whether the left headset is charging
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_UNTHETHERED_LEFT_CHARGING = 13;
+
+ /**
+ * Whether the right headset is charging
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_UNTHETHERED_RIGHT_CHARGING = 14;
+
+ /**
+ * Whether the headset charging case is charging
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_UNTHETHERED_CASE_CHARGING = 15;
+
+ /**
+ * URI to the enhanced settings UI slice, null or empty String means
+ * the UI does not exist
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16;
+
+ /**
* Broadcast Action: This intent is used to broadcast the {@link UUID}
* wrapped as a {@link android.os.ParcelUuid} of the remote device after it
* has been fetched. This intent is sent only when the UUIDs of the remote
@@ -2026,4 +2157,61 @@
Log.e(TAG, "createL2capCocSocket: PLEASE USE THE OFFICIAL API, createInsecureL2capChannel");
return createInsecureL2capChannel(psm);
}
+
+ /**
+ * Set a keyed metadata of this {@link BluetoothDevice} to a
+ * {@link String} value.
+ * Only bonded devices's metadata will be persisted across Bluetooth
+ * restart.
+ * Metadata will be removed when the device's bond state is moved to
+ * {@link #BOND_NONE}.
+ *
+ * @param key must be within the list of BluetoothDevice.METADATA_*
+ * @param value the string data to set for key. Must be less than
+ * {@link BluetoothAdapter#METADATA_MAX_LENGTH} characters in length
+ * @return true on success, false on error
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public boolean setMetadata(int key, String value) {
+ final IBluetooth service = sService;
+ if (service == null) {
+ Log.e(TAG, "Bluetooth is not enabled. Cannot set metadata");
+ return false;
+ }
+ if (value.length() > METADATA_MAX_LENGTH) {
+ throw new IllegalArgumentException("value length is " + value.length()
+ + ", should not over " + METADATA_MAX_LENGTH);
+ }
+ try {
+ return service.setMetadata(this, key, value);
+ } catch (RemoteException e) {
+ Log.e(TAG, "setMetadata fail", e);
+ return false;
+ }
+ }
+
+ /**
+ * Get a keyed metadata for this {@link BluetoothDevice} as {@link String}
+ *
+ * @param key must be within the list of BluetoothDevice.METADATA_*
+ * @return Metadata of the key as string, null on error or not found
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public String getMetadata(int key) {
+ final IBluetooth service = sService;
+ if (service == null) {
+ Log.e(TAG, "Bluetooth is not enabled. Cannot get metadata");
+ return null;
+ }
+ try {
+ return service.getMetadata(this, key);
+ } catch (RemoteException e) {
+ Log.e(TAG, "getMetadata fail", e);
+ return null;
+ }
+ }
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index cefc700..280f1ac 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -771,7 +771,9 @@
* <p>
* This is not generally intended for third party application developers.
*/
- public abstract String getOpPackageName();
+ public String getOpPackageName() {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
/** Return the full application info for this context's package. */
public abstract ApplicationInfo getApplicationInfo();
@@ -2980,9 +2982,11 @@
*
* @see #bindService
*/
- public abstract boolean bindIsolatedService(@RequiresPermission Intent service,
+ public boolean bindIsolatedService(@RequiresPermission Intent service,
@NonNull ServiceConnection conn, @BindServiceFlags int flags,
- @NonNull String instanceName);
+ @NonNull String instanceName) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
/**
* Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userHandle
@@ -3037,8 +3041,10 @@
* a related groups -- higher importance values will be killed before
* lower ones.
*/
- public abstract void updateServiceGroup(@NonNull ServiceConnection conn, int group,
- int importance);
+ public void updateServiceGroup(@NonNull ServiceConnection conn, int group,
+ int importance) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
/**
* Disconnect from an application service. You will no longer receive
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 8497656..b46203c 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2008,6 +2008,15 @@
"android.intent.extra.PERMISSION_GROUP_NAME";
/**
+ * Intent extra: The number of milliseconds.
+ * <p>
+ * Type: long
+ * </p>
+ */
+ public static final String EXTRA_DURATION_MILLIS =
+ "android.intent.extra.DURATION_MILLIS";
+
+ /**
* Activity action: Launch UI to review app uses of permissions.
* <p>
* Input: {@link #EXTRA_PERMISSION_NAME} specifies the permission name
@@ -2020,11 +2029,16 @@
* {@link #EXTRA_PERMISSION_NAME}.
* </p>
* <p>
+ * Input: {@link #EXTRA_DURATION_MILLIS} specifies the minimum number of milliseconds of recent
+ * activity to show (optional). Must be non-negative.
+ * </p>
+ * <p>
* Output: Nothing.
* </p>
*
* @see #EXTRA_PERMISSION_NAME
* @see #EXTRA_PERMISSION_GROUP_NAME
+ * @see #EXTRA_DURATION_MILLIS
*
* @hide
*/
@@ -2363,7 +2377,6 @@
/**
* Broadcast Action: An existing version of an application package has been
* rolled back to a previous version.
- * The data contains the name of the package.
*
* <p class="note">This is a protected intent that can only be sent
* by the system.
@@ -3192,7 +3205,18 @@
*
* <p class="note">This is a protected intent that can only be sent
* by the system.
+ *
+ * <p class="note">If the user has chosen a {@link android.telecom.CallRedirectionService} to
+ * handle redirection of outgoing calls, this intent will NOT be sent as an ordered broadcast.
+ * This means that attempts to re-write the outgoing call by other apps using this intent will
+ * be ignored.
+ * </p>
+ *
+ * @deprecated Apps that redirect outgoing calls should use the
+ * {@link android.telecom.CallRedirectionService} API. Apps that perform call screening
+ * should use the {@link android.telecom.CallScreeningService} API.
*/
+ @Deprecated
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_NEW_OUTGOING_CALL =
"android.intent.action.NEW_OUTGOING_CALL";
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index 7a2220bf..8e72fa5 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -54,6 +54,7 @@
this(context, IOverlayManager.Stub.asInterface(
ServiceManager.getService(Context.OVERLAY_SERVICE)));
}
+
/**
* Request that an overlay package is enabled and any other overlay packages with the same
* target package and category are disabled.
@@ -75,6 +76,26 @@
}
/**
+ * Request that an overlay package is enabled.
+ *
+ * @param packageName the name of the overlay package to enable.
+ * @param enable {@code false} if the overlay should be turned off.
+ * @param userId The user for which to change the overlay.
+ * @return true if the system successfully registered the request, false otherwise.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean setEnabled(@Nullable final String packageName, final boolean enable,
+ int userId) {
+ try {
+ return mService.setEnabled(packageName, enable, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns information about all overlays for the given target package for
* the specified user. The returned list is ordered according to the
* overlay priority with the highest priority at the end of the list.
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 983ea9f..766c566 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -136,6 +136,17 @@
public static final String EXTRA_PIN_ITEM_REQUEST =
"android.content.pm.extra.PIN_ITEM_REQUEST";
+ /**
+ * Metadata key that specifies vouched certs, so any apps signed by a cert in vouched certs
+ * will not show hidden icon in launcher even it does not have a launcher visible activity.
+ *
+ * If an app has this metadata in manifest, it won't be eligible to hide its icon even if its
+ * cert is in vouched certs list.
+ *
+ * @hide
+ */
+ public static final String VOUCHED_CERTS_KEY = "vouched_certs";
+
private final Context mContext;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private final ILauncherApps mService;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b845673..9e2f316 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2060,6 +2060,14 @@
/**
* Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device has a secure implementation of keyguard, meaning the
+ * device supports PIN, pattern and password as defined in Android CDD
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SECURE_LOCK_SCREEN = "android.software.secure_lock_screen";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device includes an accelerometer.
*/
@SdkConstant(SdkConstantType.FEATURE)
@@ -5764,7 +5772,7 @@
*/
@RequiresPermission(value = android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE,
conditional = true)
- public abstract void setComponentEnabledSetting(ComponentName componentName,
+ public abstract void setComponentEnabledSetting(@NonNull ComponentName componentName,
@EnabledState int newState, @EnabledFlags int flags);
/**
@@ -5778,7 +5786,7 @@
* @return Returns the current enabled state for the component.
*/
public abstract @EnabledState int getComponentEnabledSetting(
- ComponentName componentName);
+ @NonNull ComponentName componentName);
/**
* Set the enabled setting for an application
@@ -5793,7 +5801,7 @@
*/
@RequiresPermission(value = android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE,
conditional = true)
- public abstract void setApplicationEnabledSetting(String packageName,
+ public abstract void setApplicationEnabledSetting(@NonNull String packageName,
@EnabledState int newState, @EnabledFlags int flags);
/**
@@ -5807,7 +5815,7 @@
* @return Returns the current enabled state for the application.
* @throws IllegalArgumentException if the named package does not exist.
*/
- public abstract @EnabledState int getApplicationEnabledSetting(String packageName);
+ public abstract @EnabledState int getApplicationEnabledSetting(@NonNull String packageName);
/**
* Flush the package restrictions for a given user to disk. This forces the package restrictions
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index c7320b0..c9a4c82 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -794,6 +794,12 @@
"android.content.pm.extra.ENABLE_ROLLBACK_INSTALL_FLAGS";
/**
+ * Extra field name for the set of installed users for a given rollback package.
+ */
+ public static final String EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS =
+ "android.content.pm.extra.ENABLE_ROLLBACK_INSTALLED_USERS";
+
+ /**
* Used as the {@code enableRollbackCode} argument for
* {@link PackageManagerInternal#setEnableRollbackCode} to indicate that
* enabling rollback succeeded.
@@ -827,4 +833,10 @@
* Ask the package manager to compile layouts in the given package.
*/
public abstract boolean compileLayouts(String packageName);
+
+ /*
+ * Inform the package manager that the pending package install identified by
+ * {@code token} can be completed.
+ */
+ public abstract void finishPackageInstall(int token, boolean didLaunch);
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index a185c8a..88a240f 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -8494,6 +8494,18 @@
public static PackageInfo generatePackageInfoFromApex(File apexFile, boolean collectCerts)
throws PackageParserException {
PackageInfo pi = new PackageInfo();
+
+ // TODO(b/123086053) properly fill in the ApplicationInfo with data from AndroidManifest
+ // Add ApplicationInfo to the PackageInfo.
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.sourceDir = apexFile.getPath();
+ ai.flags = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
+ ai.enabled = true;
+ ai.targetSdkVersion = 28;
+ ai.targetSandboxVersion = 0;
+ pi.applicationInfo = ai;
+
+
// TODO(b/123052859): We should avoid these repeated calls to parseApkLite each time
// we want to generate information for APEX modules.
PackageParser.ApkLite apk = PackageParser.parseApkLite(apexFile,
diff --git a/core/java/android/content/rollback/IRollbackManager.aidl b/core/java/android/content/rollback/IRollbackManager.aidl
index 7f557cd..420bcb6 100644
--- a/core/java/android/content/rollback/IRollbackManager.aidl
+++ b/core/java/android/content/rollback/IRollbackManager.aidl
@@ -33,6 +33,12 @@
void executeRollback(in RollbackInfo rollback, String callerPackageName,
in IntentSender statusReceiver);
+ // Exposed for use from the system server only. Callback from the package
+ // manager during the install flow when user data can be restored for a given
+ // package.
+ void restoreUserData(String packageName, int userId, int appId, long ceDataInode,
+ String seInfo, int token);
+
// Exposed for test purposes only.
void reloadPersistedData();
diff --git a/core/java/android/content/rollback/PackageRollbackInfo.java b/core/java/android/content/rollback/PackageRollbackInfo.java
index 2040024..4644a83 100644
--- a/core/java/android/content/rollback/PackageRollbackInfo.java
+++ b/core/java/android/content/rollback/PackageRollbackInfo.java
@@ -17,11 +17,10 @@
package android.content.rollback;
import android.annotation.SystemApi;
+import android.content.pm.VersionedPackage;
import android.os.Parcel;
import android.os.Parcelable;
-import java.util.Objects;
-
/**
* Information about a rollback available for a particular package.
*
@@ -29,59 +28,41 @@
*/
@SystemApi
public final class PackageRollbackInfo implements Parcelable {
- /**
- * The name of a package being rolled back.
- */
- public final String packageName;
+
+ private final VersionedPackage mVersionRolledBackFrom;
+ private final VersionedPackage mVersionRolledBackTo;
/**
- * The version the package was rolled back from.
+ * Returns the name of the package to roll back from.
*/
- public final PackageVersion higherVersion;
-
- /**
- * The version the package was rolled back to.
- */
- public final PackageVersion lowerVersion;
-
- /**
- * Represents a version of a package.
- */
- public static class PackageVersion {
- public final long versionCode;
-
- // TODO(b/120200473): Include apk sha or some other way to distinguish
- // between two different apks with the same version code.
- public PackageVersion(long versionCode) {
- this.versionCode = versionCode;
- }
-
- @Override
- public boolean equals(Object other) {
- if (other instanceof PackageVersion) {
- PackageVersion otherVersion = (PackageVersion) other;
- return versionCode == otherVersion.versionCode;
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(versionCode);
- }
+ public String getPackageName() {
+ return mVersionRolledBackFrom.getPackageName();
}
- public PackageRollbackInfo(String packageName,
- PackageVersion higherVersion, PackageVersion lowerVersion) {
- this.packageName = packageName;
- this.higherVersion = higherVersion;
- this.lowerVersion = lowerVersion;
+ /**
+ * Returns the version of the package rolled back from.
+ */
+ public VersionedPackage getVersionRolledBackFrom() {
+ return mVersionRolledBackFrom;
+ }
+
+ /**
+ * Returns the version of the package rolled back to.
+ */
+ public VersionedPackage getVersionRolledBackTo() {
+ return mVersionRolledBackTo;
+ }
+
+ /** @hide */
+ public PackageRollbackInfo(VersionedPackage packageRolledBackFrom,
+ VersionedPackage packageRolledBackTo) {
+ this.mVersionRolledBackFrom = packageRolledBackFrom;
+ this.mVersionRolledBackTo = packageRolledBackTo;
}
private PackageRollbackInfo(Parcel in) {
- this.packageName = in.readString();
- this.higherVersion = new PackageVersion(in.readLong());
- this.lowerVersion = new PackageVersion(in.readLong());
+ this.mVersionRolledBackFrom = VersionedPackage.CREATOR.createFromParcel(in);
+ this.mVersionRolledBackTo = VersionedPackage.CREATOR.createFromParcel(in);
}
@Override
@@ -91,9 +72,8 @@
@Override
public void writeToParcel(Parcel out, int flags) {
- out.writeString(packageName);
- out.writeLong(higherVersion.versionCode);
- out.writeLong(lowerVersion.versionCode);
+ mVersionRolledBackFrom.writeToParcel(out, flags);
+ mVersionRolledBackTo.writeToParcel(out, flags);
}
public static final Parcelable.Creator<PackageRollbackInfo> CREATOR =
diff --git a/core/java/android/content/rollback/RollbackInfo.java b/core/java/android/content/rollback/RollbackInfo.java
index 66df4fe..0803a7c 100644
--- a/core/java/android/content/rollback/RollbackInfo.java
+++ b/core/java/android/content/rollback/RollbackInfo.java
@@ -30,6 +30,11 @@
public final class RollbackInfo implements Parcelable {
/**
+ * A unique identifier for the rollback.
+ */
+ private final int mRollbackId;
+
+ /**
* The package that needs to be rolled back.
*/
public final PackageRollbackInfo targetPackage;
@@ -40,12 +45,21 @@
// staged installs is supported.
/** @hide */
- public RollbackInfo(PackageRollbackInfo targetPackage) {
+ public RollbackInfo(int rollbackId, PackageRollbackInfo targetPackage) {
+ this.mRollbackId = rollbackId;
this.targetPackage = targetPackage;
}
private RollbackInfo(Parcel in) {
- this.targetPackage = PackageRollbackInfo.CREATOR.createFromParcel(in);
+ mRollbackId = in.readInt();
+ targetPackage = PackageRollbackInfo.CREATOR.createFromParcel(in);
+ }
+
+ /**
+ * Returns a unique identifier for this rollback.
+ */
+ public int getRollbackId() {
+ return mRollbackId;
}
@Override
@@ -55,6 +69,7 @@
@Override
public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mRollbackId);
targetPackage.writeToParcel(out, flags);
}
diff --git a/core/java/android/database/TranslatingCursor.java b/core/java/android/database/TranslatingCursor.java
index 58e65b2..d9165b4 100644
--- a/core/java/android/database/TranslatingCursor.java
+++ b/core/java/android/database/TranslatingCursor.java
@@ -39,28 +39,29 @@
public class TranslatingCursor extends CrossProcessCursorWrapper {
public static class Config {
public final Uri baseUri;
- public final String idColumn;
- public final String[] filePathColumns;
+ public final String auxiliaryColumn;
+ public final String[] translateColumns;
- public Config(Uri baseUri, String idColumn, String... filePathColumns) {
+ public Config(Uri baseUri, String auxiliaryColumn, String... translateColumns) {
this.baseUri = baseUri;
- this.idColumn = idColumn;
- this.filePathColumns = filePathColumns;
+ this.auxiliaryColumn = auxiliaryColumn;
+ this.translateColumns = translateColumns;
}
}
public interface Translator {
- String translate(String data, long id);
+ String translate(String data, int auxiliaryColumnIndex,
+ String matchingColumn, Cursor cursor);
}
private final @NonNull Config mConfig;
private final @NonNull Translator mTranslator;
private final boolean mDropLast;
- private final int mIdIndex;
- private final int[] mFilePathColIndices;
+ private final int mAuxiliaryColumnIndex;
+ private final int[] mTranslateColumnIndices;
- private TranslatingCursor(@NonNull Cursor cursor, @NonNull Config config,
+ public TranslatingCursor(@NonNull Cursor cursor, @NonNull Config config,
@NonNull Translator translator, boolean dropLast) {
super(cursor);
@@ -68,10 +69,10 @@
mTranslator = Objects.requireNonNull(translator);
mDropLast = dropLast;
- mIdIndex = cursor.getColumnIndexOrThrow(config.idColumn);
- mFilePathColIndices = new int[config.filePathColumns.length];
- for (int i = mFilePathColIndices.length - 1; i >= 0; --i) {
- mFilePathColIndices[i] = cursor.getColumnIndex(config.filePathColumns[i]);
+ mAuxiliaryColumnIndex = cursor.getColumnIndexOrThrow(config.auxiliaryColumn);
+ mTranslateColumnIndices = new int[config.translateColumns.length];
+ for (int i = 0; i < mTranslateColumnIndices.length; ++i) {
+ mTranslateColumnIndices[i] = cursor.getColumnIndex(config.translateColumns[i]);
}
}
@@ -97,26 +98,27 @@
SQLiteQueryBuilder qb, SQLiteDatabase db, String[] projectionIn, String selection,
String[] selectionArgs, String groupBy, String having, String sortOrder, String limit,
CancellationSignal signal) {
- final boolean requestedId = ArrayUtils.isEmpty(projectionIn)
- || ArrayUtils.contains(projectionIn, config.idColumn);
- final boolean requestedData = ArrayUtils.isEmpty(projectionIn)
- || ArrayUtils.containsAny(projectionIn, config.filePathColumns);
+ final boolean requestedAuxiliaryColumn = ArrayUtils.isEmpty(projectionIn)
+ || ArrayUtils.contains(projectionIn, config.auxiliaryColumn);
+ final boolean requestedTranslateColumns = ArrayUtils.isEmpty(projectionIn)
+ || ArrayUtils.containsAny(projectionIn, config.translateColumns);
- // If caller didn't request data, we have nothing to redirect
- if (!requestedData || !ContentResolver.DEPRECATE_DATA_COLUMNS) {
+ // If caller didn't request any columns that need to be translated,
+ // we have nothing to redirect
+ if (!requestedTranslateColumns) {
return qb.query(db, projectionIn, selection, selectionArgs,
groupBy, having, sortOrder, limit, signal);
}
- // If caller didn't request id, we need to splice it in
- if (!requestedId) {
+ // If caller didn't request auxiliary column, we need to splice it in
+ if (!requestedAuxiliaryColumn) {
projectionIn = ArrayUtils.appendElement(String.class, projectionIn,
- config.idColumn);
+ config.auxiliaryColumn);
}
final Cursor c = qb.query(db, projectionIn, selection, selectionArgs,
groupBy, having, sortOrder);
- return new TranslatingCursor(c, config, translator, !requestedId);
+ return new TranslatingCursor(c, config, translator, !requestedAuxiliaryColumn);
}
@Override
@@ -139,7 +141,7 @@
@Override
public double getDouble(int columnIndex) {
- if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ if (ArrayUtils.contains(mTranslateColumnIndices, columnIndex)) {
throw new IllegalArgumentException();
} else {
return super.getDouble(columnIndex);
@@ -148,7 +150,7 @@
@Override
public float getFloat(int columnIndex) {
- if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ if (ArrayUtils.contains(mTranslateColumnIndices, columnIndex)) {
throw new IllegalArgumentException();
} else {
return super.getFloat(columnIndex);
@@ -157,7 +159,7 @@
@Override
public int getInt(int columnIndex) {
- if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ if (ArrayUtils.contains(mTranslateColumnIndices, columnIndex)) {
throw new IllegalArgumentException();
} else {
return super.getInt(columnIndex);
@@ -166,7 +168,7 @@
@Override
public long getLong(int columnIndex) {
- if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ if (ArrayUtils.contains(mTranslateColumnIndices, columnIndex)) {
throw new IllegalArgumentException();
} else {
return super.getLong(columnIndex);
@@ -175,7 +177,7 @@
@Override
public short getShort(int columnIndex) {
- if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ if (ArrayUtils.contains(mTranslateColumnIndices, columnIndex)) {
throw new IllegalArgumentException();
} else {
return super.getShort(columnIndex);
@@ -184,8 +186,9 @@
@Override
public String getString(int columnIndex) {
- if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
- return mTranslator.translate(super.getString(columnIndex), super.getLong(mIdIndex));
+ if (ArrayUtils.contains(mTranslateColumnIndices, columnIndex)) {
+ return mTranslator.translate(super.getString(columnIndex),
+ mAuxiliaryColumnIndex, getColumnName(columnIndex), this);
} else {
return super.getString(columnIndex);
}
@@ -193,7 +196,7 @@
@Override
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
- if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ if (ArrayUtils.contains(mTranslateColumnIndices, columnIndex)) {
throw new IllegalArgumentException();
} else {
super.copyStringToBuffer(columnIndex, buffer);
@@ -202,7 +205,7 @@
@Override
public byte[] getBlob(int columnIndex) {
- if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ if (ArrayUtils.contains(mTranslateColumnIndices, columnIndex)) {
throw new IllegalArgumentException();
} else {
return super.getBlob(columnIndex);
@@ -211,7 +214,7 @@
@Override
public int getType(int columnIndex) {
- if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ if (ArrayUtils.contains(mTranslateColumnIndices, columnIndex)) {
return Cursor.FIELD_TYPE_STRING;
} else {
return super.getType(columnIndex);
@@ -220,7 +223,7 @@
@Override
public boolean isNull(int columnIndex) {
- if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ if (ArrayUtils.contains(mTranslateColumnIndices, columnIndex)) {
return getString(columnIndex) == null;
} else {
return super.isNull(columnIndex);
diff --git a/core/java/android/hardware/display/ColorDisplayManager.java b/core/java/android/hardware/display/ColorDisplayManager.java
index 70a9f08..fa335c8 100644
--- a/core/java/android/hardware/display/ColorDisplayManager.java
+++ b/core/java/android/hardware/display/ColorDisplayManager.java
@@ -17,6 +17,7 @@
package android.hardware.display;
import android.Manifest;
+import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
@@ -30,6 +31,9 @@
import com.android.internal.R;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Manages the display's color transforms and modes.
*
@@ -39,6 +43,44 @@
@SystemService(Context.COLOR_DISPLAY_SERVICE)
public final class ColorDisplayManager {
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({CAPABILITY_NONE, CAPABILITY_PROTECTED_CONTENT, CAPABILITY_HARDWARE_ACCELERATION_GLOBAL,
+ CAPABILITY_HARDWARE_ACCELERATION_PER_APP})
+ public @interface CapabilityType {}
+
+ /**
+ * The device does not support color transforms.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int CAPABILITY_NONE = 0x0;
+ /**
+ * The device can properly apply transforms over protected content.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int CAPABILITY_PROTECTED_CONTENT = 0x1;
+ /**
+ * The device's hardware can efficiently apply transforms to the entire display.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int CAPABILITY_HARDWARE_ACCELERATION_GLOBAL = 0x2;
+ /**
+ * The device's hardware can efficiently apply transforms to a specific Surface (window) so
+ * that apps can be transformed independently of one another.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int CAPABILITY_HARDWARE_ACCELERATION_PER_APP = 0x4;
+
private final ColorDisplayManagerInternal mManager;
/**
@@ -114,6 +156,17 @@
return context.getResources().getBoolean(R.bool.config_setColorTransformAccelerated);
}
+ /**
+ * Returns the available software and hardware color transform capabilities of this device.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+ public @CapabilityType int getTransformCapabilities() {
+ return mManager.getTransformCapabilities();
+ }
+
private static class ColorDisplayManagerInternal {
private static ColorDisplayManagerInternal sInstance;
@@ -162,5 +215,13 @@
throw e.rethrowFromSystemServer();
}
}
+
+ int getTransformCapabilities() {
+ try {
+ return mCdm.getTransformCapabilities();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
}
diff --git a/core/java/android/hardware/display/IColorDisplayManager.aidl b/core/java/android/hardware/display/IColorDisplayManager.aidl
index 644f510..53cb8db 100644
--- a/core/java/android/hardware/display/IColorDisplayManager.aidl
+++ b/core/java/android/hardware/display/IColorDisplayManager.aidl
@@ -22,4 +22,6 @@
boolean setSaturationLevel(int saturationLevel);
boolean setAppSaturationLevel(String packageName, int saturationLevel);
+
+ int getTransformCapabilities();
}
\ No newline at end of file
diff --git a/core/java/android/hardware/hdmi/HdmiUtils.java b/core/java/android/hardware/hdmi/HdmiUtils.java
index 3081738..8c94b78 100644
--- a/core/java/android/hardware/hdmi/HdmiUtils.java
+++ b/core/java/android/hardware/hdmi/HdmiUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -16,14 +16,18 @@
package android.hardware.hdmi;
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
- * Various utilities to handle HDMI CEC messages.
+ * Various utilities related to HDMI CEC.
*
* TODO(b/110094868): unhide for Q
* @hide
*/
-public class HdmiUtils {
-
+public final class HdmiUtils {
/**
* Return value of {@link #getLocalPortFromPhysicalAddress(int, int)}
*/
@@ -78,4 +82,164 @@
}
return port;
}
+
+ /**
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({HDMI_RELATIVE_POSITION_UNKNOWN, HDMI_RELATIVE_POSITION_DIRECTLY_BELOW,
+ HDMI_RELATIVE_POSITION_BELOW, HDMI_RELATIVE_POSITION_SAME,
+ HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE, HDMI_RELATIVE_POSITION_ABOVE,
+ HDMI_RELATIVE_POSITION_SIBLING, HDMI_RELATIVE_POSITION_DIFFERENT_BRANCH})
+ public @interface HdmiAddressRelativePosition {}
+ /**
+ * HDMI relative position is not determined.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public static final int HDMI_RELATIVE_POSITION_UNKNOWN = 0;
+ /**
+ * HDMI relative position: directly blow the device.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public static final int HDMI_RELATIVE_POSITION_DIRECTLY_BELOW = 1;
+ /**
+ * HDMI relative position: indirectly below the device.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public static final int HDMI_RELATIVE_POSITION_BELOW = 2;
+ /**
+ * HDMI relative position: the same device.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public static final int HDMI_RELATIVE_POSITION_SAME = 3;
+ /**
+ * HDMI relative position: directly above the device.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public static final int HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE = 4;
+ /**
+ * HDMI relative position: indirectly above the device.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public static final int HDMI_RELATIVE_POSITION_ABOVE = 5;
+ /**
+ * HDMI relative position: directly below a same device.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public static final int HDMI_RELATIVE_POSITION_SIBLING = 6;
+ /**
+ * HDMI relative position: different branch.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public static final int HDMI_RELATIVE_POSITION_DIFFERENT_BRANCH = 7;
+
+ private static final int NPOS = -1;
+
+ /**
+ * Check if the given physical address is valid.
+ *
+ * @param address physical address
+ * @return {@code true} if the given address is valid
+ */
+ public static boolean isValidPhysicalAddress(int address) {
+ if (address < 0 || address >= 0xFFFF) {
+ return false;
+ }
+ int mask = 0xF000;
+ boolean hasZero = false;
+ for (int i = 0; i < 4; i++) {
+ if ((address & mask) == 0) {
+ hasZero = true;
+ } else if (hasZero) {
+ // only 0s are valid after a 0.
+ // e.g. 0x1012 is not valid.
+ return false;
+ }
+ mask >>= 4;
+ }
+ return true;
+ }
+
+
+ /**
+ * Returns the relative position of two physical addresses.
+ */
+ @HdmiAddressRelativePosition
+ public static int getHdmiAddressRelativePosition(int src, int dest) {
+ if (src == 0xFFFF || dest == 0xFFFF) {
+ // address not assigned
+ return HDMI_RELATIVE_POSITION_UNKNOWN;
+ }
+ try {
+ int firstDiffPos = physicalAddressFirstDifferentDigitPos(src, dest);
+ if (firstDiffPos == NPOS) {
+ return HDMI_RELATIVE_POSITION_SAME;
+ }
+ int mask = (0xF000 >> (firstDiffPos * 4));
+ int nextPos = firstDiffPos + 1;
+ if ((src & mask) == 0) {
+ // src is above dest
+ if (nextPos == 4) {
+ // last digits are different
+ return HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE;
+ }
+ if (((0xF000 >> (nextPos * 4)) & dest) == 0) {
+ // next digit is 0
+ return HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE;
+ }
+ return HDMI_RELATIVE_POSITION_ABOVE;
+ }
+
+ if ((dest & mask) == 0) {
+ // src is below dest
+ if (nextPos == 4) {
+ // last digits are different
+ return HDMI_RELATIVE_POSITION_DIRECTLY_BELOW;
+ }
+ if (((0xF000 >> (nextPos * 4)) & src) == 0) {
+ // next digit is 0
+ return HDMI_RELATIVE_POSITION_DIRECTLY_BELOW;
+ }
+ return HDMI_RELATIVE_POSITION_BELOW;
+ }
+ if (nextPos == 4) {
+ // last digits are different
+ return HDMI_RELATIVE_POSITION_SIBLING;
+ }
+ if (((0xF000 >> (nextPos * 4)) & src) == 0 && ((0xF000 >> (nextPos * 4)) & dest) == 0) {
+ return HDMI_RELATIVE_POSITION_SIBLING;
+ }
+ return HDMI_RELATIVE_POSITION_DIFFERENT_BRANCH;
+ } catch (IllegalArgumentException e) {
+ // invalid address
+ return HDMI_RELATIVE_POSITION_UNKNOWN;
+ }
+ }
+
+ private static int physicalAddressFirstDifferentDigitPos(int address1, int address2)
+ throws IllegalArgumentException {
+ if (!isValidPhysicalAddress(address1)) {
+ throw new IllegalArgumentException(address1 + " is not a valid address.");
+ }
+ if (!isValidPhysicalAddress(address2)) {
+ throw new IllegalArgumentException(address2 + " is not a valid address.");
+ }
+ int mask = 0xF000;
+ for (int i = 0; i < 4; i++) {
+ if ((address1 & mask) != (address2 & mask)) {
+ return i;
+ }
+ mask = mask >> 4;
+ }
+ return NPOS;
+ }
}
diff --git a/core/java/android/hardware/location/ActivityChangedEvent.aidl b/core/java/android/hardware/location/ActivityChangedEvent.aidl
new file mode 100644
index 0000000..21f2445
--- /dev/null
+++ b/core/java/android/hardware/location/ActivityChangedEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2014 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.hardware.location;
+
+parcelable ActivityChangedEvent;
\ No newline at end of file
diff --git a/core/java/android/hardware/location/ActivityChangedEvent.java b/core/java/android/hardware/location/ActivityChangedEvent.java
new file mode 100644
index 0000000..16cfe6e
--- /dev/null
+++ b/core/java/android/hardware/location/ActivityChangedEvent.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2014 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.hardware.location;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.security.InvalidParameterException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A class representing an event for Activity changes.
+ *
+ * @hide
+ */
+public class ActivityChangedEvent implements Parcelable {
+ private final List<ActivityRecognitionEvent> mActivityRecognitionEvents;
+
+ public ActivityChangedEvent(ActivityRecognitionEvent[] activityRecognitionEvents) {
+ if (activityRecognitionEvents == null) {
+ throw new InvalidParameterException(
+ "Parameter 'activityRecognitionEvents' must not be null.");
+ }
+
+ mActivityRecognitionEvents = Arrays.asList(activityRecognitionEvents);
+ }
+
+ @NonNull
+ public Iterable<ActivityRecognitionEvent> getActivityRecognitionEvents() {
+ return mActivityRecognitionEvents;
+ }
+
+ public static final Creator<ActivityChangedEvent> CREATOR =
+ new Creator<ActivityChangedEvent>() {
+ @Override
+ public ActivityChangedEvent createFromParcel(Parcel source) {
+ int activityRecognitionEventsLength = source.readInt();
+ ActivityRecognitionEvent[] activityRecognitionEvents =
+ new ActivityRecognitionEvent[activityRecognitionEventsLength];
+ source.readTypedArray(activityRecognitionEvents, ActivityRecognitionEvent.CREATOR);
+
+ return new ActivityChangedEvent(activityRecognitionEvents);
+ }
+
+ @Override
+ public ActivityChangedEvent[] newArray(int size) {
+ return new ActivityChangedEvent[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ ActivityRecognitionEvent[] activityRecognitionEventArray =
+ mActivityRecognitionEvents.toArray(new ActivityRecognitionEvent[0]);
+ parcel.writeInt(activityRecognitionEventArray.length);
+ parcel.writeTypedArray(activityRecognitionEventArray, flags);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("[ ActivityChangedEvent:");
+
+ for (ActivityRecognitionEvent event : mActivityRecognitionEvents) {
+ builder.append("\n ");
+ builder.append(event.toString());
+ }
+ builder.append("\n]");
+
+ return builder.toString();
+ }
+}
diff --git a/core/java/android/hardware/location/ActivityRecognitionEvent.java b/core/java/android/hardware/location/ActivityRecognitionEvent.java
new file mode 100644
index 0000000..190030a
--- /dev/null
+++ b/core/java/android/hardware/location/ActivityRecognitionEvent.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2014 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.hardware.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class that represents an Activity Recognition Event.
+ *
+ * @hide
+ */
+public class ActivityRecognitionEvent implements Parcelable {
+ private final String mActivity;
+ private final int mEventType;
+ private final long mTimestampNs;
+
+ public ActivityRecognitionEvent(String activity, int eventType, long timestampNs) {
+ mActivity = activity;
+ mEventType = eventType;
+ mTimestampNs = timestampNs;
+ }
+
+ public String getActivity() {
+ return mActivity;
+ }
+
+ public int getEventType() {
+ return mEventType;
+ }
+
+ public long getTimestampNs() {
+ return mTimestampNs;
+ }
+
+ public static final Creator<ActivityRecognitionEvent> CREATOR =
+ new Creator<ActivityRecognitionEvent>() {
+ @Override
+ public ActivityRecognitionEvent createFromParcel(Parcel source) {
+ String activity = source.readString();
+ int eventType = source.readInt();
+ long timestampNs = source.readLong();
+
+ return new ActivityRecognitionEvent(activity, eventType, timestampNs);
+ }
+
+ @Override
+ public ActivityRecognitionEvent[] newArray(int size) {
+ return new ActivityRecognitionEvent[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString(mActivity);
+ parcel.writeInt(mEventType);
+ parcel.writeLong(mTimestampNs);
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "Activity='%s', EventType=%s, TimestampNs=%s",
+ mActivity,
+ mEventType,
+ mTimestampNs);
+ }
+}
diff --git a/core/java/android/hardware/location/ActivityRecognitionHardware.java b/core/java/android/hardware/location/ActivityRecognitionHardware.java
new file mode 100644
index 0000000..8acd1ff
--- /dev/null
+++ b/core/java/android/hardware/location/ActivityRecognitionHardware.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2014 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.hardware.location;
+
+import android.Manifest;
+import android.content.Context;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * A class that implements an {@link IActivityRecognitionHardware} backed up by the Activity
+ * Recognition HAL.
+ *
+ * @hide
+ */
+public class ActivityRecognitionHardware extends IActivityRecognitionHardware.Stub {
+ private static final String TAG = "ActivityRecognitionHW";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE;
+ private static final String ENFORCE_HW_PERMISSION_MESSAGE = "Permission '"
+ + HARDWARE_PERMISSION + "' not granted to access ActivityRecognitionHardware";
+
+ private static final int INVALID_ACTIVITY_TYPE = -1;
+ private static final int NATIVE_SUCCESS_RESULT = 0;
+ private static final int EVENT_TYPE_DISABLED = 0;
+ private static final int EVENT_TYPE_ENABLED = 1;
+
+ /**
+ * Contains the number of supported Event Types.
+ *
+ * NOTE: increment this counter every time a new EVENT_TYPE_ is added to
+ * com.android.location.provider.ActivityRecognitionProvider
+ */
+ private static final int EVENT_TYPE_COUNT = 3;
+
+ private static ActivityRecognitionHardware sSingletonInstance;
+ private static final Object sSingletonInstanceLock = new Object();
+
+ private final Context mContext;
+ private final int mSupportedActivitiesCount;
+ private final String[] mSupportedActivities;
+ private final int[][] mSupportedActivitiesEnabledEvents;
+ private final SinkList mSinks = new SinkList();
+
+ private static class Event {
+ public int activity;
+ public int type;
+ public long timestamp;
+ }
+
+ private ActivityRecognitionHardware(Context context) {
+ nativeInitialize();
+
+ mContext = context;
+ mSupportedActivities = fetchSupportedActivities();
+ mSupportedActivitiesCount = mSupportedActivities.length;
+ mSupportedActivitiesEnabledEvents = new int[mSupportedActivitiesCount][EVENT_TYPE_COUNT];
+ }
+
+ public static ActivityRecognitionHardware getInstance(Context context) {
+ synchronized (sSingletonInstanceLock) {
+ if (sSingletonInstance == null) {
+ sSingletonInstance = new ActivityRecognitionHardware(context);
+ }
+
+ return sSingletonInstance;
+ }
+ }
+
+ public static boolean isSupported() {
+ return nativeIsSupported();
+ }
+
+ @Override
+ public String[] getSupportedActivities() {
+ checkPermissions();
+ return mSupportedActivities;
+ }
+
+ @Override
+ public boolean isActivitySupported(String activity) {
+ checkPermissions();
+ int activityType = getActivityType(activity);
+ return activityType != INVALID_ACTIVITY_TYPE;
+ }
+
+ @Override
+ public boolean registerSink(IActivityRecognitionHardwareSink sink) {
+ checkPermissions();
+ return mSinks.register(sink);
+ }
+
+ @Override
+ public boolean unregisterSink(IActivityRecognitionHardwareSink sink) {
+ checkPermissions();
+ return mSinks.unregister(sink);
+ }
+
+ @Override
+ public boolean enableActivityEvent(String activity, int eventType, long reportLatencyNs) {
+ checkPermissions();
+
+ int activityType = getActivityType(activity);
+ if (activityType == INVALID_ACTIVITY_TYPE) {
+ return false;
+ }
+
+ int result = nativeEnableActivityEvent(activityType, eventType, reportLatencyNs);
+ if (result == NATIVE_SUCCESS_RESULT) {
+ mSupportedActivitiesEnabledEvents[activityType][eventType] = EVENT_TYPE_ENABLED;
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean disableActivityEvent(String activity, int eventType) {
+ checkPermissions();
+
+ int activityType = getActivityType(activity);
+ if (activityType == INVALID_ACTIVITY_TYPE) {
+ return false;
+ }
+
+ int result = nativeDisableActivityEvent(activityType, eventType);
+ if (result == NATIVE_SUCCESS_RESULT) {
+ mSupportedActivitiesEnabledEvents[activityType][eventType] = EVENT_TYPE_DISABLED;
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean flush() {
+ checkPermissions();
+ int result = nativeFlush();
+ return result == NATIVE_SUCCESS_RESULT;
+ }
+
+ /**
+ * Called by the Activity-Recognition HAL.
+ */
+ private void onActivityChanged(Event[] events) {
+ if (events == null || events.length == 0) {
+ if (DEBUG) Log.d(TAG, "No events to broadcast for onActivityChanged.");
+ return;
+ }
+
+ int eventsLength = events.length;
+ ActivityRecognitionEvent activityRecognitionEventArray[] =
+ new ActivityRecognitionEvent[eventsLength];
+ for (int i = 0; i < eventsLength; ++i) {
+ Event event = events[i];
+ String activityName = getActivityName(event.activity);
+ activityRecognitionEventArray[i] =
+ new ActivityRecognitionEvent(activityName, event.type, event.timestamp);
+ }
+ ActivityChangedEvent activityChangedEvent =
+ new ActivityChangedEvent(activityRecognitionEventArray);
+
+ int size = mSinks.beginBroadcast();
+ for (int i = 0; i < size; ++i) {
+ IActivityRecognitionHardwareSink sink = mSinks.getBroadcastItem(i);
+ try {
+ sink.onActivityChanged(activityChangedEvent);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error delivering activity changed event.", e);
+ }
+ }
+ mSinks.finishBroadcast();
+ }
+
+ private String getActivityName(int activityType) {
+ if (activityType < 0 || activityType >= mSupportedActivities.length) {
+ String message = String.format(
+ "Invalid ActivityType: %d, SupportedActivities: %d",
+ activityType,
+ mSupportedActivities.length);
+ Log.e(TAG, message);
+ return null;
+ }
+
+ return mSupportedActivities[activityType];
+ }
+
+ private int getActivityType(String activity) {
+ if (TextUtils.isEmpty(activity)) {
+ return INVALID_ACTIVITY_TYPE;
+ }
+
+ int supportedActivitiesLength = mSupportedActivities.length;
+ for (int i = 0; i < supportedActivitiesLength; ++i) {
+ if (activity.equals(mSupportedActivities[i])) {
+ return i;
+ }
+ }
+
+ return INVALID_ACTIVITY_TYPE;
+ }
+
+ private void checkPermissions() {
+ mContext.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE);
+ }
+
+ private String[] fetchSupportedActivities() {
+ String[] supportedActivities = nativeGetSupportedActivities();
+ if (supportedActivities != null) {
+ return supportedActivities;
+ }
+
+ return new String[0];
+ }
+
+ private class SinkList extends RemoteCallbackList<IActivityRecognitionHardwareSink> {
+ @Override
+ public void onCallbackDied(IActivityRecognitionHardwareSink callback) {
+ int callbackCount = mSinks.getRegisteredCallbackCount();
+ if (DEBUG) Log.d(TAG, "RegisteredCallbackCount: " + callbackCount);
+ if (callbackCount != 0) {
+ return;
+ }
+ // currently there is only one client for this, so if all its sinks have died, we clean
+ // up after them, this ensures that the AR HAL is not out of sink
+ for (int activity = 0; activity < mSupportedActivitiesCount; ++activity) {
+ for (int event = 0; event < EVENT_TYPE_COUNT; ++event) {
+ disableActivityEventIfEnabled(activity, event);
+ }
+ }
+ }
+
+ private void disableActivityEventIfEnabled(int activityType, int eventType) {
+ if (mSupportedActivitiesEnabledEvents[activityType][eventType] != EVENT_TYPE_ENABLED) {
+ return;
+ }
+
+ int result = nativeDisableActivityEvent(activityType, eventType);
+ mSupportedActivitiesEnabledEvents[activityType][eventType] = EVENT_TYPE_DISABLED;
+ String message = String.format(
+ "DisableActivityEvent: activityType=%d, eventType=%d, result=%d",
+ activityType,
+ eventType,
+ result);
+ Log.e(TAG, message);
+ }
+ }
+
+ // native bindings
+ static { nativeClassInit(); }
+
+ private static native void nativeClassInit();
+ private static native boolean nativeIsSupported();
+
+ private native void nativeInitialize();
+ private native void nativeRelease();
+ private native String[] nativeGetSupportedActivities();
+ private native int nativeEnableActivityEvent(
+ int activityType,
+ int eventType,
+ long reportLatenceNs);
+ private native int nativeDisableActivityEvent(int activityType, int eventType);
+ private native int nativeFlush();
+}
diff --git a/core/java/android/hardware/location/IActivityRecognitionHardware.aidl b/core/java/android/hardware/location/IActivityRecognitionHardware.aidl
new file mode 100644
index 0000000..bc6b183
--- /dev/null
+++ b/core/java/android/hardware/location/IActivityRecognitionHardware.aidl
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2014, 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/license/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.hardware.location;
+
+import android.hardware.location.IActivityRecognitionHardwareSink;
+
+/**
+ * Activity Recognition Hardware provider interface.
+ * This interface can be used to implement hardware based activity recognition.
+ *
+ * @hide
+ */
+interface IActivityRecognitionHardware {
+ /**
+ * Gets an array of supported activities by hardware.
+ */
+ String[] getSupportedActivities();
+
+ /**
+ * Returns true if the given activity is supported, false otherwise.
+ */
+ boolean isActivitySupported(in String activityType);
+
+ /**
+ * Registers a sink with Hardware Activity-Recognition.
+ */
+ boolean registerSink(in IActivityRecognitionHardwareSink sink);
+
+ /**
+ * Unregisters a sink with Hardware Activity-Recognition.
+ */
+ boolean unregisterSink(in IActivityRecognitionHardwareSink sink);
+
+ /**
+ * Enables tracking of a given activity/event type, if the activity is supported.
+ */
+ boolean enableActivityEvent(in String activityType, int eventType, long reportLatencyNs);
+
+ /**
+ * Disables tracking of a given activity/eventy type.
+ */
+ boolean disableActivityEvent(in String activityType, int eventType);
+
+ /**
+ * Requests hardware for all the activity events detected up to the given point in time.
+ */
+ boolean flush();
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/location/IActivityRecognitionHardwareClient.aidl b/core/java/android/hardware/location/IActivityRecognitionHardwareClient.aidl
new file mode 100644
index 0000000..3fe645c
--- /dev/null
+++ b/core/java/android/hardware/location/IActivityRecognitionHardwareClient.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015, 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/license/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.hardware.location;
+
+import android.hardware.location.IActivityRecognitionHardware;
+
+/**
+ * Activity Recognition Hardware client interface.
+ * This interface can be used to receive interfaces to implementations of
+ * {@link IActivityRecognitionHardware}.
+ *
+ * @hide
+ */
+oneway interface IActivityRecognitionHardwareClient {
+ /**
+ * Hardware Activity-Recognition availability event.
+ *
+ * @param isSupported whether the platform has hardware support for the feature
+ * @param instance the available instance to provide access to the feature
+ */
+ void onAvailabilityChanged(in boolean isSupported, in IActivityRecognitionHardware instance);
+}
diff --git a/core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl b/core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl
new file mode 100644
index 0000000..21c8e87
--- /dev/null
+++ b/core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014, 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/license/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.hardware.location;
+
+import android.hardware.location.ActivityChangedEvent;
+
+/**
+ * Activity Recognition Hardware provider Sink interface.
+ * This interface can be used to implement sinks to receive activity notifications.
+ *
+ * @hide
+ */
+interface IActivityRecognitionHardwareSink {
+ /**
+ * Activity changed event.
+ */
+ void onActivityChanged(in ActivityChangedEvent event);
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl b/core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl
new file mode 100644
index 0000000..12e3117
--- /dev/null
+++ b/core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2014, 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/license/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.hardware.location;
+
+import android.hardware.location.IActivityRecognitionHardware;
+
+/**
+ * Activity Recognition Hardware watcher. This interface can be used to receive interfaces to
+ * implementations of {@link IActivityRecognitionHardware}.
+ *
+ * @deprecated use {@link IActivityRecognitionHardwareClient} instead.
+
+ * @hide
+ */
+interface IActivityRecognitionHardwareWatcher {
+ /**
+ * Hardware Activity-Recognition availability event.
+ */
+ void onInstanceChanged(in IActivityRecognitionHardware instance);
+}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index d3509d5..38ddc16 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -2087,7 +2087,14 @@
* protocol, so applications with custom text editing written before this method appeared will
* not call to inform the IME of this interaction.
* @param focusChanged true if the user changed the focused view by this click.
+ * @see InputMethodManager#viewClicked(View)
+ * @deprecated The method may not be called for composite {@link View} that works as a giant
+ * "Canvas", which can host its own UI hierarchy and sub focus state.
+ * {@link android.webkit.WebView} is a good example. Application / IME developers
+ * should not rely on this method. If your goal is just being notified when an
+ * on-going input is interrupted, simply monitor {@link #onFinishInput()}.
*/
+ @Deprecated
public void onViewClicked(boolean focusChanged) {
// Intentionally empty
}
diff --git a/core/java/android/net/ApfCapabilitiesParcelable.aidl b/core/java/android/net/ApfCapabilitiesParcelable.aidl
new file mode 100644
index 0000000..f0645d2
--- /dev/null
+++ b/core/java/android/net/ApfCapabilitiesParcelable.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+parcelable ApfCapabilitiesParcelable {
+ int apfVersionSupported;
+ int maximumApfProgramSize;
+ int apfPacketFormat;
+}
\ No newline at end of file
diff --git a/core/java/android/net/CaptivePortal.java b/core/java/android/net/CaptivePortal.java
index ee05f28..3b01266 100644
--- a/core/java/android/net/CaptivePortal.java
+++ b/core/java/android/net/CaptivePortal.java
@@ -15,6 +15,8 @@
*/
package android.net;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
@@ -28,15 +30,23 @@
*/
public class CaptivePortal implements Parcelable {
/** @hide */
+ @SystemApi
+ @TestApi
public static final int APP_RETURN_DISMISSED = 0;
/** @hide */
+ @SystemApi
+ @TestApi
public static final int APP_RETURN_UNWANTED = 1;
/** @hide */
+ @SystemApi
+ @TestApi
public static final int APP_RETURN_WANTED_AS_IS = 2;
private final IBinder mBinder;
/** @hide */
+ @SystemApi
+ @TestApi
public CaptivePortal(IBinder binder) {
mBinder = binder;
}
@@ -99,6 +109,8 @@
* connectivity for apps because the captive portal is still in place.
* @hide
*/
+ @SystemApi
+ @TestApi
public void useNetwork() {
try {
ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_WANTED_AS_IS);
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index abc00fe..c809cca 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -23,6 +23,7 @@
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.app.PendingIntent;
import android.content.Context;
@@ -255,6 +256,8 @@
* portal login activity.
* {@hide}
*/
+ @SystemApi
+ @TestApi
public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC =
"android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC";
@@ -262,6 +265,8 @@
* Key for passing a user agent string to the captive portal login activity.
* {@hide}
*/
+ @SystemApi
+ @TestApi
public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT =
"android.net.extra.CAPTIVE_PORTAL_USER_AGENT";
@@ -3694,6 +3699,19 @@
}
/**
+ * Determine whether the device is configured to avoid bad wifi.
+ * @hide
+ */
+ @SystemApi
+ public boolean getAvoidBadWifi() {
+ try {
+ return mService.getAvoidBadWifi();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* It is acceptable to briefly use multipath data to provide seamless connectivity for
* time-sensitive user-facing operations when the system default network is temporarily
* unresponsive. The amount of data should be limited (less than one megabyte for every call to
diff --git a/core/java/android/net/DhcpResultsParcelable.aidl b/core/java/android/net/DhcpResultsParcelable.aidl
new file mode 100644
index 0000000..cf5629b
--- /dev/null
+++ b/core/java/android/net/DhcpResultsParcelable.aidl
@@ -0,0 +1,27 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.StaticIpConfigurationParcelable;
+
+parcelable DhcpResultsParcelable {
+ StaticIpConfigurationParcelable baseConfiguration;
+ int leaseDuration;
+ int mtu;
+ String serverAddress;
+ String vendorInfo;
+}
\ No newline at end of file
diff --git a/core/java/android/net/DnsPacket.java b/core/java/android/net/DnsPacket.java
new file mode 100644
index 0000000..458fb34
--- /dev/null
+++ b/core/java/android/net/DnsPacket.java
@@ -0,0 +1,235 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+
+import com.android.internal.util.BitUtils;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.text.DecimalFormat;
+import java.text.FieldPosition;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringJoiner;
+
+/**
+ * Defines basic data for DNS protocol based on RFC 1035.
+ * Subclasses create the specific format used in DNS packet.
+ *
+ * @hide
+ */
+public abstract class DnsPacket {
+ public class DnsHeader {
+ private static final String TAG = "DnsHeader";
+ public final int id;
+ public final int flags;
+ public final int rcode;
+ private final int[] mSectionCount;
+
+ /**
+ * Create a new DnsHeader from a positioned ByteBuffer.
+ *
+ * The ByteBuffer must be in network byte order (which is the default).
+ * Reads the passed ByteBuffer from its current position and decodes a DNS header.
+ * When this constructor returns, the reading position of the ByteBuffer has been
+ * advanced to the end of the DNS header record.
+ * This is meant to chain with other methods reading a DNS response in sequence.
+ *
+ */
+ DnsHeader(@NonNull ByteBuffer buf) throws BufferUnderflowException {
+ id = BitUtils.uint16(buf.getShort());
+ flags = BitUtils.uint16(buf.getShort());
+ rcode = flags & 0xF;
+ mSectionCount = new int[NUM_SECTIONS];
+ for (int i = 0; i < NUM_SECTIONS; ++i) {
+ mSectionCount[i] = BitUtils.uint16(buf.getShort());
+ }
+ }
+
+ /**
+ * Get section count by section type.
+ */
+ public int getSectionCount(int sectionType) {
+ return mSectionCount[sectionType];
+ }
+ }
+
+ public class DnsSection {
+ private static final int MAXNAMESIZE = 255;
+ private static final int MAXLABELSIZE = 63;
+ private static final int MAXLABELCOUNT = 128;
+ private static final int NAME_NORMAL = 0;
+ private static final int NAME_COMPRESSION = 0xC0;
+ private final DecimalFormat byteFormat = new DecimalFormat();
+ private final FieldPosition pos = new FieldPosition(0);
+
+ private static final String TAG = "DnsSection";
+
+ public final String dName;
+ public final int nsType;
+ public final int nsClass;
+ public final long ttl;
+ private final byte[] mRR;
+
+ /**
+ * Create a new DnsSection from a positioned ByteBuffer.
+ *
+ * The ByteBuffer must be in network byte order (which is the default).
+ * Reads the passed ByteBuffer from its current position and decodes a DNS section.
+ * When this constructor returns, the reading position of the ByteBuffer has been
+ * advanced to the end of the DNS header record.
+ * This is meant to chain with other methods reading a DNS response in sequence.
+ *
+ */
+ DnsSection(int sectionType, @NonNull ByteBuffer buf)
+ throws BufferUnderflowException, ParseException {
+ dName = parseName(buf, 0 /* Parse depth */);
+ if (dName.length() > MAXNAMESIZE) {
+ throw new ParseException("Parse name fail, name size is too long");
+ }
+ nsType = BitUtils.uint16(buf.getShort());
+ nsClass = BitUtils.uint16(buf.getShort());
+
+ if (sectionType != QDSECTION) {
+ ttl = BitUtils.uint32(buf.getInt());
+ final int length = BitUtils.uint16(buf.getShort());
+ mRR = new byte[length];
+ buf.get(mRR);
+ } else {
+ ttl = 0;
+ mRR = null;
+ }
+ }
+
+ /**
+ * Get a copy of rr.
+ */
+ @Nullable public byte[] getRR() {
+ return (mRR == null) ? null : mRR.clone();
+ }
+
+ /**
+ * Convert label from {@code byte[]} to {@code String}
+ *
+ * It follows the same converting rule as native layer.
+ * (See ns_name.c in libc)
+ *
+ */
+ private String labelToString(@NonNull byte[] label) {
+ final StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < label.length; ++i) {
+ int b = BitUtils.uint8(label[i]);
+ // Control characters and non-ASCII characters.
+ if (b <= 0x20 || b >= 0x7f) {
+ sb.append('\\');
+ byteFormat.format(b, sb, pos);
+ } else if (b == '"' || b == '.' || b == ';' || b == '\\'
+ || b == '(' || b == ')' || b == '@' || b == '$') {
+ sb.append('\\');
+ sb.append((char) b);
+ } else {
+ sb.append((char) b);
+ }
+ }
+ return sb.toString();
+ }
+
+ private String parseName(@NonNull ByteBuffer buf, int depth) throws
+ BufferUnderflowException, ParseException {
+ if (depth > MAXLABELCOUNT) throw new ParseException("Parse name fails, too many labels");
+ final int len = BitUtils.uint8(buf.get());
+ final int mask = len & NAME_COMPRESSION;
+ if (0 == len) {
+ return "";
+ } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION) {
+ throw new ParseException("Parse name fail, bad label type");
+ } else if (mask == NAME_COMPRESSION) {
+ // Name compression based on RFC 1035 - 4.1.4 Message compression
+ final int offset = ((len & ~NAME_COMPRESSION) << 8) + BitUtils.uint8(buf.get());
+ final int oldPos = buf.position();
+ if (offset >= oldPos - 2) {
+ throw new ParseException("Parse compression name fail, invalid compression");
+ }
+ buf.position(offset);
+ final String pointed = parseName(buf, depth + 1);
+ buf.position(oldPos);
+ return pointed;
+ } else {
+ final byte[] label = new byte[len];
+ buf.get(label);
+ final String head = labelToString(label);
+ if (head.length() > MAXLABELSIZE) {
+ throw new ParseException("Parse name fail, invalid label length");
+ }
+ final String tail = parseName(buf, depth + 1);
+ return TextUtils.isEmpty(tail) ? head : head + "." + tail;
+ }
+ }
+ }
+
+ public static final int QDSECTION = 0;
+ public static final int ANSECTION = 1;
+ public static final int NSSECTION = 2;
+ public static final int ARSECTION = 3;
+ private static final int NUM_SECTIONS = ARSECTION + 1;
+
+ private static final String TAG = DnsPacket.class.getSimpleName();
+
+ protected final DnsHeader mHeader;
+ protected final List<DnsSection>[] mSections;
+
+ public static class ParseException extends Exception {
+ public ParseException(String msg) {
+ super(msg);
+ }
+
+ public ParseException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+ }
+
+ protected DnsPacket(@NonNull byte[] data) throws ParseException {
+ if (null == data) throw new ParseException("Parse header failed, null input data");
+ final ByteBuffer buffer;
+ try {
+ buffer = ByteBuffer.wrap(data);
+ mHeader = new DnsHeader(buffer);
+ } catch (BufferUnderflowException e) {
+ throw new ParseException("Parse Header fail, bad input data", e);
+ }
+
+ mSections = new ArrayList[NUM_SECTIONS];
+
+ for (int i = 0; i < NUM_SECTIONS; ++i) {
+ final int count = mHeader.getSectionCount(i);
+ if (count > 0) {
+ mSections[i] = new ArrayList(count);
+ }
+ for (int j = 0; j < count; ++j) {
+ try {
+ mSections[i].add(new DnsSection(i, buffer));
+ } catch (BufferUnderflowException e) {
+ throw new ParseException("Parse section fail", e);
+ }
+ }
+ }
+ }
+}
diff --git a/core/java/android/net/DnsResolver.java b/core/java/android/net/DnsResolver.java
new file mode 100644
index 0000000..6d54264
--- /dev/null
+++ b/core/java/android/net/DnsResolver.java
@@ -0,0 +1,289 @@
+/*
+ * 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 static android.net.NetworkUtils.resNetworkQuery;
+import static android.net.NetworkUtils.resNetworkResult;
+import static android.net.NetworkUtils.resNetworkSend;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.MessageQueue;
+import android.system.ErrnoException;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+
+/**
+ * Dns resolver class for asynchronous dns querying
+ *
+ */
+public final class DnsResolver {
+ private static final String TAG = "DnsResolver";
+ private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
+ private static final int MAXPACKET = 8 * 1024;
+
+ @IntDef(prefix = { "CLASS_" }, value = {
+ CLASS_IN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface QueryClass {}
+ public static final int CLASS_IN = 1;
+
+ @IntDef(prefix = { "TYPE_" }, value = {
+ TYPE_A,
+ TYPE_AAAA
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface QueryType {}
+ public static final int TYPE_A = 1;
+ public static final int TYPE_AAAA = 28;
+
+ @IntDef(prefix = { "FLAG_" }, value = {
+ FLAG_EMPTY,
+ FLAG_NO_RETRY,
+ FLAG_NO_CACHE_STORE,
+ FLAG_NO_CACHE_LOOKUP
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface QueryFlag {}
+ public static final int FLAG_EMPTY = 0;
+ public static final int FLAG_NO_RETRY = 1 << 0;
+ public static final int FLAG_NO_CACHE_STORE = 1 << 1;
+ public static final int FLAG_NO_CACHE_LOOKUP = 1 << 2;
+
+ private static final int DNS_RAW_RESPONSE = 1;
+
+ private static final int NETID_UNSET = 0;
+
+ private static final DnsResolver sInstance = new DnsResolver();
+
+ /**
+ * listener for receiving raw answers
+ */
+ public interface RawAnswerListener {
+ /**
+ * {@code byte[]} is {@code null} if query timed out
+ */
+ void onAnswer(@Nullable byte[] answer);
+ }
+
+ /**
+ * listener for receiving parsed answers
+ */
+ public interface InetAddressAnswerListener {
+ /**
+ * Will be called exactly once with all the answers to the query.
+ * size of addresses will be zero if no available answer could be parsed.
+ */
+ void onAnswer(@NonNull List<InetAddress> addresses);
+ }
+
+ /**
+ * Get instance for DnsResolver
+ */
+ public static DnsResolver getInstance() {
+ return sInstance;
+ }
+
+ private DnsResolver() {}
+
+ /**
+ * Pass in a blob and corresponding setting,
+ * get a blob back asynchronously with the entire raw answer.
+ *
+ * @param network {@link Network} specifying which network for querying.
+ * {@code null} for query on default network.
+ * @param query blob message
+ * @param flags flags as a combination of the FLAGS_* constants
+ * @param handler {@link Handler} to specify the thread
+ * upon which the {@link RawAnswerListener} will be invoked.
+ * @param listener a {@link RawAnswerListener} which will be called to notify the caller
+ * of the result of dns query.
+ */
+ public void query(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags,
+ @NonNull Handler handler, @NonNull RawAnswerListener listener) throws ErrnoException {
+ final FileDescriptor queryfd = resNetworkSend((network != null
+ ? network.netId : NETID_UNSET), query, query.length, flags);
+ registerFDListener(handler.getLooper().getQueue(), queryfd,
+ answerbuf -> listener.onAnswer(answerbuf));
+ }
+
+ /**
+ * Pass in a domain name and corresponding setting,
+ * get a blob back asynchronously with the entire raw answer.
+ *
+ * @param network {@link Network} specifying which network for querying.
+ * {@code null} for query on default network.
+ * @param domain domain name for querying
+ * @param nsClass dns class as one of the CLASS_* constants
+ * @param nsType dns resource record (RR) type as one of the TYPE_* constants
+ * @param flags flags as a combination of the FLAGS_* constants
+ * @param handler {@link Handler} to specify the thread
+ * upon which the {@link RawAnswerListener} will be invoked.
+ * @param listener a {@link RawAnswerListener} which will be called to notify the caller
+ * of the result of dns query.
+ */
+ public void query(@Nullable Network network, @NonNull String domain, @QueryClass int nsClass,
+ @QueryType int nsType, @QueryFlag int flags,
+ @NonNull Handler handler, @NonNull RawAnswerListener listener) throws ErrnoException {
+ final FileDescriptor queryfd = resNetworkQuery((network != null
+ ? network.netId : NETID_UNSET), domain, nsClass, nsType, flags);
+ registerFDListener(handler.getLooper().getQueue(), queryfd,
+ answerbuf -> listener.onAnswer(answerbuf));
+ }
+
+ /**
+ * Pass in a domain name and corresponding setting,
+ * get back a set of InetAddresses asynchronously.
+ *
+ * @param network {@link Network} specifying which network for querying.
+ * {@code null} for query on default network.
+ * @param domain domain name for querying
+ * @param flags flags as a combination of the FLAGS_* constants
+ * @param handler {@link Handler} to specify the thread
+ * upon which the {@link InetAddressAnswerListener} will be invoked.
+ * @param listener an {@link InetAddressAnswerListener} which will be called to
+ * notify the caller of the result of dns query.
+ *
+ */
+ public void query(@Nullable Network network, @NonNull String domain, @QueryFlag int flags,
+ @NonNull Handler handler, @NonNull InetAddressAnswerListener listener)
+ throws ErrnoException {
+ final FileDescriptor v4fd = resNetworkQuery((network != null
+ ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_A, flags);
+ final FileDescriptor v6fd = resNetworkQuery((network != null
+ ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_AAAA, flags);
+
+ final InetAddressAnswerAccumulator accmulator =
+ new InetAddressAnswerAccumulator(2, listener);
+ final Consumer<byte[]> consumer = answerbuf ->
+ accmulator.accumulate(parseAnswers(answerbuf));
+
+ registerFDListener(handler.getLooper().getQueue(), v4fd, consumer);
+ registerFDListener(handler.getLooper().getQueue(), v6fd, consumer);
+ }
+
+ private void registerFDListener(@NonNull MessageQueue queue,
+ @NonNull FileDescriptor queryfd, @NonNull Consumer<byte[]> answerConsumer) {
+ queue.addOnFileDescriptorEventListener(
+ queryfd,
+ FD_EVENTS,
+ (fd, events) -> {
+ byte[] answerbuf = null;
+ try {
+ // TODO: Implement result function in Java side instead of using JNI
+ // Because JNI method close fd prior than unregistering fd on
+ // event listener.
+ answerbuf = resNetworkResult(fd);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "resNetworkResult:" + e.toString());
+ }
+ answerConsumer.accept(answerbuf);
+
+ // Unregister this fd listener
+ return 0;
+ });
+ }
+
+ private class DnsAddressAnswer extends DnsPacket {
+ private static final String TAG = "DnsResolver.DnsAddressAnswer";
+ private static final boolean DBG = false;
+
+ private final int mQueryType;
+
+ DnsAddressAnswer(@NonNull byte[] data) throws ParseException {
+ super(data);
+ if ((mHeader.flags & (1 << 15)) == 0) {
+ throw new ParseException("Not an answer packet");
+ }
+ if (mHeader.rcode != 0) {
+ throw new ParseException("Response error, rcode:" + mHeader.rcode);
+ }
+ if (mHeader.getSectionCount(ANSECTION) == 0) {
+ throw new ParseException("No available answer");
+ }
+ if (mHeader.getSectionCount(QDSECTION) == 0) {
+ throw new ParseException("No question found");
+ }
+ // Assume only one question per answer packet. (RFC1035)
+ mQueryType = mSections[QDSECTION].get(0).nsType;
+ }
+
+ public @NonNull List<InetAddress> getAddresses() {
+ final List<InetAddress> results = new ArrayList<InetAddress>();
+ for (final DnsSection ansSec : mSections[ANSECTION]) {
+ // Only support A and AAAA, also ignore answers if query type != answer type.
+ int nsType = ansSec.nsType;
+ if (nsType != mQueryType || (nsType != TYPE_A && nsType != TYPE_AAAA)) {
+ continue;
+ }
+ try {
+ results.add(InetAddress.getByAddress(ansSec.getRR()));
+ } catch (UnknownHostException e) {
+ if (DBG) {
+ Log.w(TAG, "rr to address fail");
+ }
+ }
+ }
+ return results;
+ }
+ }
+
+ private @Nullable List<InetAddress> parseAnswers(@Nullable byte[] data) {
+ try {
+ return (data == null) ? null : new DnsAddressAnswer(data).getAddresses();
+ } catch (DnsPacket.ParseException e) {
+ Log.e(TAG, "Parse answer fail " + e.getMessage());
+ return null;
+ }
+ }
+
+ private class InetAddressAnswerAccumulator {
+ private final List<InetAddress> mAllAnswers;
+ private final InetAddressAnswerListener mAnswerListener;
+ private final int mTargetAnswerCount;
+ private int mReceivedAnswerCount = 0;
+
+ InetAddressAnswerAccumulator(int size, @NonNull InetAddressAnswerListener listener) {
+ mTargetAnswerCount = size;
+ mAllAnswers = new ArrayList<>();
+ mAnswerListener = listener;
+ }
+
+ public void accumulate(@Nullable List<InetAddress> answer) {
+ if (null != answer) {
+ mAllAnswers.addAll(answer);
+ }
+ if (++mReceivedAnswerCount == mTargetAnswerCount) {
+ mAnswerListener.onAnswer(mAllAnswers);
+ }
+ }
+ }
+}
diff --git a/core/java/android/net/ICaptivePortal.aidl b/core/java/android/net/ICaptivePortal.aidl
index a013e79..56ae57d 100644
--- a/core/java/android/net/ICaptivePortal.aidl
+++ b/core/java/android/net/ICaptivePortal.aidl
@@ -20,7 +20,6 @@
* Interface to inform NetworkMonitor of decisions of app handling captive portal.
* @hide
*/
-interface ICaptivePortal
-{
- oneway void appResponse(int response);
+oneway interface ICaptivePortal {
+ void appResponse(int response);
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index da5d96e..131925e 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -165,6 +165,7 @@
void setAvoidUnvalidated(in Network network);
void startCaptivePortalApp(in Network network);
+ boolean getAvoidBadWifi();
int getMultipathPreference(in Network Network);
NetworkRequest getDefaultRequest();
@@ -187,4 +188,6 @@
byte[] getNetworkWatchlistConfigHash();
int getConnectionOwnerUid(in ConnectionInfo connectionInfo);
+ boolean isCallerCurrentAlwaysOnVpnApp();
+ boolean isCallerCurrentAlwaysOnVpnLockdownApp();
}
diff --git a/core/java/android/net/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl
index b7af374..f0fe92e 100644
--- a/core/java/android/net/INetworkManagementEventObserver.aidl
+++ b/core/java/android/net/INetworkManagementEventObserver.aidl
@@ -24,7 +24,7 @@
*
* @hide
*/
-interface INetworkManagementEventObserver {
+oneway interface INetworkManagementEventObserver {
/**
* Interface configuration status has changed.
*
diff --git a/core/java/android/net/INetworkStackConnector.aidl b/core/java/android/net/INetworkStackConnector.aidl
index 2df8ab7..8b64f1c 100644
--- a/core/java/android/net/INetworkStackConnector.aidl
+++ b/core/java/android/net/INetworkStackConnector.aidl
@@ -18,10 +18,12 @@
import android.net.INetworkMonitorCallbacks;
import android.net.dhcp.DhcpServingParamsParcel;
import android.net.dhcp.IDhcpServerCallbacks;
+import android.net.ip.IIpClientCallbacks;
/** @hide */
oneway interface INetworkStackConnector {
void makeDhcpServer(in String ifName, in DhcpServingParamsParcel params,
in IDhcpServerCallbacks cb);
void makeNetworkMonitor(int netId, String name, in INetworkMonitorCallbacks cb);
+ void makeIpClient(in String ifName, in IIpClientCallbacks callbacks);
}
\ No newline at end of file
diff --git a/core/java/android/net/InitialConfigurationParcelable.aidl b/core/java/android/net/InitialConfigurationParcelable.aidl
new file mode 100644
index 0000000..bdda355
--- /dev/null
+++ b/core/java/android/net/InitialConfigurationParcelable.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.net.IpPrefixParcelable;
+import android.net.LinkAddressParcelable;
+
+parcelable InitialConfigurationParcelable {
+ LinkAddressParcelable[] ipAddresses;
+ IpPrefixParcelable[] directlyConnectedRoutes;
+ String[] dnsServers;
+ String gateway;
+}
\ No newline at end of file
diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java
index b40f15a..a536d08 100644
--- a/core/java/android/net/LinkAddress.java
+++ b/core/java/android/net/LinkAddress.java
@@ -26,6 +26,7 @@
import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
@@ -117,7 +118,8 @@
* @return true if the address is IPv6.
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public boolean isIPv6() {
return address instanceof Inet6Address;
}
@@ -126,6 +128,8 @@
* @return true if the address is IPv4 or is a mapped IPv4 address.
* @hide
*/
+ @TestApi
+ @SystemApi
public boolean isIPv4() {
return address instanceof Inet4Address;
}
@@ -263,7 +267,8 @@
* otherwise.
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public boolean isSameAddressAs(LinkAddress other) {
return address.equals(other.address) && prefixLength == other.prefixLength;
}
@@ -310,6 +315,8 @@
* Returns true if this {@code LinkAddress} is global scope and preferred.
* @hide
*/
+ @TestApi
+ @SystemApi
public boolean isGlobalPreferred() {
/**
* Note that addresses flagged as IFA_F_OPTIMISTIC are
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index c2963fd..21b6a8e 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
@@ -368,7 +369,8 @@
* @return true if the DNS server was added, false if it was already present.
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public boolean addDnsServer(InetAddress dnsServer) {
if (dnsServer != null && !mDnses.contains(dnsServer)) {
mDnses.add(dnsServer);
@@ -384,7 +386,8 @@
* @return true if the DNS server was removed, false if it did not exist.
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public boolean removeDnsServer(InetAddress dnsServer) {
if (dnsServer != null) {
return mDnses.remove(dnsServer);
@@ -423,6 +426,8 @@
* @param usePrivateDns The private DNS state.
* @hide
*/
+ @TestApi
+ @SystemApi
public void setUsePrivateDns(boolean usePrivateDns) {
mUsePrivateDns = usePrivateDns;
}
@@ -448,6 +453,8 @@
* @param privateDnsServerName The private DNS server name.
* @hide
*/
+ @TestApi
+ @SystemApi
public void setPrivateDnsServerName(@Nullable String privateDnsServerName) {
mPrivateDnsServerName = privateDnsServerName;
}
@@ -510,6 +517,8 @@
* object.
* @hide
*/
+ @TestApi
+ @SystemApi
public void setValidatedPrivateDnsServers(Collection<InetAddress> dnsServers) {
mValidatedPrivateDnses.clear();
for (InetAddress dnsServer: dnsServers) {
@@ -525,6 +534,8 @@
* DNS servers on this link.
* @hide
*/
+ @TestApi
+ @SystemApi
public List<InetAddress> getValidatedPrivateDnsServers() {
return Collections.unmodifiableList(mValidatedPrivateDnses);
}
@@ -636,7 +647,8 @@
*
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public void setTcpBufferSizes(String tcpBufferSizes) {
mTcpBufferSizes = tcpBufferSizes;
}
@@ -648,7 +660,8 @@
*
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public String getTcpBufferSizes() {
return mTcpBufferSizes;
}
@@ -699,7 +712,8 @@
*
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public boolean removeRoute(RouteInfo route) {
return route != null &&
Objects.equals(mIfaceName, route.getInterface()) &&
@@ -960,7 +974,8 @@
* @return {@code true} if there is an IPv4 address, {@code false} otherwise.
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public boolean hasIPv4Address() {
for (LinkAddress address : mLinkAddresses) {
if (address.getAddress() instanceof Inet4Address) {
@@ -988,7 +1003,8 @@
* @return {@code true} if there is a global preferred IPv6 address, {@code false} otherwise.
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public boolean hasGlobalIPv6Address() {
for (LinkAddress address : mLinkAddresses) {
if (address.getAddress() instanceof Inet6Address && address.isGlobalPreferred()) {
@@ -1020,7 +1036,8 @@
* @return {@code true} if there is an IPv6 default route, {@code false} otherwise.
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public boolean hasIPv6DefaultRoute() {
for (RouteInfo r : mRoutes) {
if (r.isIPv6Default()) {
@@ -1099,6 +1116,8 @@
* @return {@code true} if the link is provisioned, {@code false} otherwise.
* @hide
*/
+ @TestApi
+ @SystemApi
public boolean isIPv4Provisioned() {
return (hasIPv4Address() &&
hasIPv4DefaultRoute() &&
@@ -1112,7 +1131,8 @@
* @return {@code true} if the link is provisioned, {@code false} otherwise.
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public boolean isIPv6Provisioned() {
return (hasGlobalIPv6Address() &&
hasIPv6DefaultRoute() &&
@@ -1126,7 +1146,8 @@
* @return {@code true} if the link is provisioned, {@code false} otherwise.
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public boolean isProvisioned() {
return (isIPv4Provisioned() || isIPv6Provisioned());
}
@@ -1138,7 +1159,8 @@
* {@code false} otherwise.
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @SystemApi
public boolean isReachable(InetAddress ip) {
final List<RouteInfo> allRoutes = getAllRoutes();
// If we don't have a route to this IP address, it's not reachable.
diff --git a/core/java/android/net/LinkPropertiesParcelable.aidl b/core/java/android/net/LinkPropertiesParcelable.aidl
index b153dc7..6b52239 100644
--- a/core/java/android/net/LinkPropertiesParcelable.aidl
+++ b/core/java/android/net/LinkPropertiesParcelable.aidl
@@ -35,5 +35,4 @@
int mtu;
String tcpBufferSizes;
IpPrefixParcelable nat64Prefix;
- LinkPropertiesParcelable[] stackedLinks;
}
\ No newline at end of file
diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java
index 4e551756..77d83f5 100644
--- a/core/java/android/net/MacAddress.java
+++ b/core/java/android/net/MacAddress.java
@@ -52,6 +52,8 @@
/**
* The MacAddress zero MAC address.
+ *
+ * <p>Not publicly exposed or treated specially since the OUI 00:00:00 is registered.
* @hide
*/
@UnsupportedAppUsage
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index bf2344d..2c831de 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -16,6 +16,8 @@
package android.net;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -157,6 +159,8 @@
*
* @hide
*/
+ @TestApi
+ @SystemApi
public Network getPrivateDnsBypassingCopy() {
return new Network(netId, true);
}
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 1b44c92..7e9bda1 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -712,6 +712,7 @@
* @hide
*/
@TestApi
+ @SystemApi
public @Transport int[] getTransportTypes() {
return BitUtils.unpackBits(mTransportTypes);
}
@@ -1312,6 +1313,8 @@
*
* @hide
*/
+ @TestApi
+ @SystemApi
public boolean satisfiedByNetworkCapabilities(NetworkCapabilities nc) {
return satisfiedByNetworkCapabilities(nc, false);
}
diff --git a/core/java/android/net/NetworkParcelable.aidl b/core/java/android/net/NetworkParcelable.aidl
new file mode 100644
index 0000000..c26352e
--- /dev/null
+++ b/core/java/android/net/NetworkParcelable.aidl
@@ -0,0 +1,22 @@
+/*
+**
+** 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;
+
+parcelable NetworkParcelable {
+ long networkHandle;
+}
diff --git a/core/java/android/net/NetworkStack.java b/core/java/android/net/NetworkStack.java
index 2eac6de..d277034 100644
--- a/core/java/android/net/NetworkStack.java
+++ b/core/java/android/net/NetworkStack.java
@@ -27,6 +27,7 @@
import android.content.ServiceConnection;
import android.net.dhcp.DhcpServingParamsParcel;
import android.net.dhcp.IDhcpServerCallbacks;
+import android.net.ip.IIpClientCallbacks;
import android.os.Binder;
import android.os.IBinder;
import android.os.Process;
@@ -50,6 +51,8 @@
public static final String NETWORKSTACK_PACKAGE_NAME = "com.android.mainline.networkstack";
+ private static final int NETWORKSTACK_TIMEOUT_MS = 10_000;
+
@NonNull
@GuardedBy("mPendingNetStackRequests")
private final ArrayList<NetworkStackCallback> mPendingNetStackRequests = new ArrayList<>();
@@ -57,6 +60,8 @@
@GuardedBy("mPendingNetStackRequests")
private INetworkStackConnector mConnector;
+ private volatile boolean mNetworkStackStartRequested = false;
+
private interface NetworkStackCallback {
void onNetworkStackConnected(INetworkStackConnector connector);
}
@@ -80,6 +85,21 @@
}
/**
+ * Create an IpClient on the specified interface.
+ *
+ * <p>The IpClient will be returned asynchronously through the provided callbacks.
+ */
+ public void makeIpClient(String ifName, IIpClientCallbacks cb) {
+ requestConnector(connector -> {
+ try {
+ connector.makeIpClient(ifName, cb);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ });
+ }
+
+ /**
* Create a NetworkMonitor.
*
* <p>The INetworkMonitor will be returned asynchronously through the provided callbacks.
@@ -134,6 +154,7 @@
* started.
*/
public void start(Context context) {
+ mNetworkStackStartRequested = true;
// Try to bind in-process if the library is available
IBinder connector = null;
try {
@@ -170,15 +191,54 @@
}
}
- // TODO: use this method to obtain the connector when implementing network stack operations
+ /**
+ * For non-system server clients, get the connector registered by the system server.
+ */
+ private INetworkStackConnector getRemoteConnector() {
+ // Block until the NetworkStack connector is registered in ServiceManager.
+ // <p>This is only useful for non-system processes that do not have a way to be notified of
+ // registration completion. Adding a callback system would be too heavy weight considering
+ // that the connector is registered on boot, so it is unlikely that a client would request
+ // it before it is registered.
+ // TODO: consider blocking boot on registration and simplify much of the logic in this class
+ IBinder connector;
+ try {
+ final long before = System.currentTimeMillis();
+ while ((connector = ServiceManager.getService(Context.NETWORK_STACK_SERVICE)) == null) {
+ Thread.sleep(20);
+ if (System.currentTimeMillis() - before > NETWORKSTACK_TIMEOUT_MS) {
+ Slog.e(TAG, "Timeout waiting for NetworkStack connector");
+ return null;
+ }
+ }
+ } catch (InterruptedException e) {
+ Slog.e(TAG, "Error waiting for NetworkStack connector", e);
+ return null;
+ }
+
+ return INetworkStackConnector.Stub.asInterface(connector);
+ }
+
private void requestConnector(@NonNull NetworkStackCallback request) {
// TODO: PID check.
- if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ final int caller = Binder.getCallingUid();
+ if (caller != Process.SYSTEM_UID && caller != Process.BLUETOOTH_UID) {
// Don't even attempt to obtain the connector and give a nice error message
throw new SecurityException(
"Only the system server should try to bind to the network stack.");
}
+ if (!mNetworkStackStartRequested) {
+ // The network stack is not being started in this process, e.g. this process is not
+ // the system server. Get a remote connector registered by the system server.
+ final INetworkStackConnector connector = getRemoteConnector();
+ synchronized (mPendingNetStackRequests) {
+ mConnector = connector;
+ }
+ request.onNetworkStackConnected(connector);
+ return;
+ }
+
final INetworkStackConnector connector;
synchronized (mPendingNetStackRequests) {
connector = mConnector;
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 4eab49c..c996d01 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -39,6 +39,8 @@
import java.util.Locale;
import java.util.TreeSet;
+import android.system.ErrnoException;
+
/**
* Native methods for managing network interfaces.
*
@@ -138,6 +140,32 @@
public native static boolean queryUserAccess(int uid, int netId);
/**
+ * DNS resolver series jni method.
+ * Issue the query {@code msg} on the network designated by {@code netId}.
+ * {@code flags} is an additional config to control actual querying behavior.
+ * @return a file descriptor to watch for read events
+ */
+ public static native FileDescriptor resNetworkSend(
+ int netId, byte[] msg, int msglen, int flags) throws ErrnoException;
+
+ /**
+ * DNS resolver series jni method.
+ * Look up the {@code nsClass} {@code nsType} Resource Record (RR) associated
+ * with Domain Name {@code dname} on the network designated by {@code netId}.
+ * {@code flags} is an additional config to control actual querying behavior.
+ * @return a file descriptor to watch for read events
+ */
+ public static native FileDescriptor resNetworkQuery(
+ int netId, String dname, int nsClass, int nsType, int flags) throws ErrnoException;
+
+ /**
+ * DNS resolver series jni method.
+ * Read a result for the query associated with the {@code fd}.
+ * @return a byte array containing blob answer
+ */
+ public static native byte[] resNetworkResult(FileDescriptor fd) throws ErrnoException;
+
+ /**
* Add an entry into the ARP cache.
*/
public static void addArpEntry(Inet4Address ipv4Addr, MacAddress ethAddr, String ifname,
diff --git a/core/java/android/net/ProvisioningConfigurationParcelable.aidl b/core/java/android/net/ProvisioningConfigurationParcelable.aidl
new file mode 100644
index 0000000..2a144f2
--- /dev/null
+++ b/core/java/android/net/ProvisioningConfigurationParcelable.aidl
@@ -0,0 +1,38 @@
+/*
+**
+** 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.net.ApfCapabilitiesParcelable;
+import android.net.InitialConfigurationParcelable;
+import android.net.NetworkParcelable;
+import android.net.StaticIpConfigurationParcelable;
+
+parcelable ProvisioningConfigurationParcelable {
+ boolean enableIPv4;
+ boolean enableIPv6;
+ boolean usingMultinetworkPolicyTracker;
+ boolean usingIpReachabilityMonitor;
+ int requestedPreDhcpActionMs;
+ InitialConfigurationParcelable initialConfig;
+ StaticIpConfigurationParcelable staticIpConfig;
+ ApfCapabilitiesParcelable apfCapabilities;
+ int provisioningTimeoutMs;
+ int ipv6AddrGenMode;
+ NetworkParcelable network;
+ String displayName;
+}
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index 37ab9ff..6bf2c67 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -16,16 +16,17 @@
package android.net;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
-import java.net.UnknownHostException;
-import java.net.InetAddress;
import java.net.Inet4Address;
import java.net.Inet6Address;
-
+import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.util.Collection;
import java.util.Objects;
@@ -67,12 +68,18 @@
/** Unicast route. @hide */
+ @SystemApi
+ @TestApi
public static final int RTN_UNICAST = 1;
/** Unreachable route. @hide */
+ @SystemApi
+ @TestApi
public static final int RTN_UNREACHABLE = 7;
/** Throw route. @hide */
+ @SystemApi
+ @TestApi
public static final int RTN_THROW = 9;
/**
@@ -317,6 +324,8 @@
*
* @hide
*/
+ @TestApi
+ @SystemApi
public int getType() {
return mType;
}
@@ -362,9 +371,7 @@
* ({@code false}).
*
* @return {@code true} if a gateway is specified
- * @hide
*/
- @UnsupportedAppUsage
public boolean hasGateway() {
return mHasGateway;
}
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index abc1cac..90dccb5 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -86,7 +86,16 @@
* <p>On development devices, "setprop socket.relaxsslcheck yes" bypasses all
* SSL certificate and hostname checks for testing purposes. This setting
* requires root access.
+ *
+ * @deprecated This class has less error-prone replacements using standard APIs. To create an
+ * {@code SSLSocket}, obtain an {@link SSLSocketFactory} from {@link SSLSocketFactory#getDefault()}
+ * or {@link javax.net.ssl.SSLContext#getSocketFactory()}. To verify hostnames, pass
+ * {@code "HTTPS"} to
+ * {@link javax.net.ssl.SSLParameters#setEndpointIdentificationAlgorithm(String)}. To enable ALPN,
+ * use {@link javax.net.ssl.SSLParameters#setApplicationProtocols(String[])}. To enable SNI,
+ * use {@link javax.net.ssl.SSLParameters#setServerNames(java.util.List)}.
*/
+@Deprecated
public class SSLCertificateSocketFactory extends SSLSocketFactory {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private static final String TAG = "SSLCertificateSocketFactory";
diff --git a/core/java/android/net/StaticIpConfigurationParcelable.aidl b/core/java/android/net/StaticIpConfigurationParcelable.aidl
new file mode 100644
index 0000000..45dc021
--- /dev/null
+++ b/core/java/android/net/StaticIpConfigurationParcelable.aidl
@@ -0,0 +1,27 @@
+/*
+**
+** 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.net.LinkAddressParcelable;
+
+parcelable StaticIpConfigurationParcelable {
+ LinkAddressParcelable ipAddress;
+ String gateway;
+ String[] dnsServers;
+ String domains;
+}
diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java
index f0c0462..37bf3a7 100644
--- a/core/java/android/net/VpnService.java
+++ b/core/java/android/net/VpnService.java
@@ -368,6 +368,29 @@
}
/**
+ * Returns whether the service is running in always-on VPN mode.
+ */
+ public final boolean isAlwaysOn() {
+ try {
+ return getService().isCallerCurrentAlwaysOnVpnApp();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns whether the service is running in always-on VPN mode blocking connections without
+ * VPN.
+ */
+ public final boolean isLockdownEnabled() {
+ try {
+ return getService().isCallerCurrentAlwaysOnVpnLockdownApp();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Return the communication interface to the service. This method returns
* {@code null} on {@link Intent}s other than {@link #SERVICE_INTERFACE}
* action. Applications overriding this method must identify the intent
diff --git a/core/java/android/net/apf/ApfCapabilities.java b/core/java/android/net/apf/ApfCapabilities.java
new file mode 100644
index 0000000..f28cdc9
--- /dev/null
+++ b/core/java/android/net/apf/ApfCapabilities.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2016 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.apf;
+
+/**
+ * APF program support capabilities.
+ *
+ * @hide
+ */
+public class ApfCapabilities {
+ /**
+ * Version of APF instruction set supported for packet filtering. 0 indicates no support for
+ * packet filtering using APF programs.
+ */
+ public final int apfVersionSupported;
+
+ /**
+ * Maximum size of APF program allowed.
+ */
+ public final int maximumApfProgramSize;
+
+ /**
+ * Format of packets passed to APF filter. Should be one of ARPHRD_*
+ */
+ public final int apfPacketFormat;
+
+ public ApfCapabilities(
+ int apfVersionSupported, int maximumApfProgramSize, int apfPacketFormat) {
+ this.apfVersionSupported = apfVersionSupported;
+ this.maximumApfProgramSize = maximumApfProgramSize;
+ this.apfPacketFormat = apfPacketFormat;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s{version: %d, maxSize: %d, format: %d}", getClass().getSimpleName(),
+ apfVersionSupported, maximumApfProgramSize, apfPacketFormat);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ApfCapabilities)) return false;
+ final ApfCapabilities other = (ApfCapabilities) obj;
+ return apfVersionSupported == other.apfVersionSupported
+ && maximumApfProgramSize == other.maximumApfProgramSize
+ && apfPacketFormat == other.apfPacketFormat;
+ }
+
+ /**
+ * Returns true if the APF interpreter advertises support for the data buffer access opcodes
+ * LDDW and STDW.
+ *
+ * Full LDDW and STDW support is present from APFv4 on.
+ */
+ public boolean hasDataAccess() {
+ return apfVersionSupported >= 4;
+ }
+}
diff --git a/core/java/android/net/ip/IIpClient.aidl b/core/java/android/net/ip/IIpClient.aidl
new file mode 100644
index 0000000..7769ec2
--- /dev/null
+++ b/core/java/android/net/ip/IIpClient.aidl
@@ -0,0 +1,32 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+package android.net.ip;
+
+import android.net.ProxyInfoParcelable;
+import android.net.ProvisioningConfigurationParcelable;
+
+/** @hide */
+oneway interface IIpClient {
+ void completedPreDhcpAction();
+ void confirmConfiguration();
+ void readPacketFilterComplete(in byte[] data);
+ void shutdown();
+ void startProvisioning(in ProvisioningConfigurationParcelable req);
+ void stop();
+ void setTcpBufferSizes(in String tcpBufferSizes);
+ void setHttpProxy(in ProxyInfoParcelable proxyInfo);
+ void setMulticastFilter(boolean enabled);
+}
diff --git a/core/java/android/net/ip/IIpClientCallbacks.aidl b/core/java/android/net/ip/IIpClientCallbacks.aidl
new file mode 100644
index 0000000..f077e3b
--- /dev/null
+++ b/core/java/android/net/ip/IIpClientCallbacks.aidl
@@ -0,0 +1,66 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+package android.net.ip;
+
+import android.net.LinkPropertiesParcelable;
+import android.net.ip.IIpClient;
+import android.net.DhcpResultsParcelable;
+
+/** @hide */
+oneway interface IIpClientCallbacks {
+ void onIpClientCreated(in IIpClient ipClient);
+
+ void onPreDhcpAction();
+ void onPostDhcpAction();
+
+ // This is purely advisory and not an indication of provisioning
+ // success or failure. This is only here for callers that want to
+ // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
+ // DHCPv4 or static IPv4 configuration failure or success can be
+ // determined by whether or not the passed-in DhcpResults object is
+ // null or not.
+ void onNewDhcpResults(in DhcpResultsParcelable dhcpResults);
+
+ void onProvisioningSuccess(in LinkPropertiesParcelable newLp);
+ void onProvisioningFailure(in LinkPropertiesParcelable newLp);
+
+ // Invoked on LinkProperties changes.
+ void onLinkPropertiesChange(in LinkPropertiesParcelable newLp);
+
+ // Called when the internal IpReachabilityMonitor (if enabled) has
+ // detected the loss of a critical number of required neighbors.
+ void onReachabilityLost(in String logMsg);
+
+ // Called when the IpClient state machine terminates.
+ void onQuit();
+
+ // Install an APF program to filter incoming packets.
+ void installPacketFilter(in byte[] filter);
+
+ // Asynchronously read back the APF program & data buffer from the wifi driver.
+ // Due to Wifi HAL limitations, the current implementation only supports dumping the entire
+ // buffer. In response to this request, the driver returns the data buffer asynchronously
+ // by sending an IpClient#EVENT_READ_PACKET_FILTER_COMPLETE message.
+ void startReadPacketFilter();
+
+ // If multicast filtering cannot be accomplished with APF, this function will be called to
+ // actuate multicast filtering using another means.
+ void setFallbackMulticastFilter(boolean enabled);
+
+ // Enabled/disable Neighbor Discover offload functionality. This is
+ // called, for example, whenever 464xlat is being started or stopped.
+ void setNeighborDiscoveryOffload(boolean enable);
+}
\ No newline at end of file
diff --git a/core/java/android/net/ip/IpClientCallbacks.java b/core/java/android/net/ip/IpClientCallbacks.java
new file mode 100644
index 0000000..db01ae4
--- /dev/null
+++ b/core/java/android/net/ip/IpClientCallbacks.java
@@ -0,0 +1,119 @@
+/*
+ * 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.ip;
+
+import android.net.DhcpResults;
+import android.net.LinkProperties;
+
+/**
+ * Callbacks for handling IpClient events.
+ *
+ * This is a convenience class to allow clients not to override all methods of IIpClientCallbacks,
+ * and avoid unparceling arguments.
+ * These methods are called asynchronously on a Binder thread, as IpClient lives in a different
+ * process.
+ * @hide
+ */
+public class IpClientCallbacks {
+
+ /**
+ * Callback called upon IpClient creation.
+ *
+ * @param ipClient The Binder token to communicate with IpClient.
+ */
+ public void onIpClientCreated(IIpClient ipClient) {}
+
+ /**
+ * Callback called prior to DHCP discovery/renewal.
+ *
+ * <p>In order to receive onPreDhcpAction(), call #withPreDhcpAction() when constructing a
+ * ProvisioningConfiguration.
+ *
+ * <p>Implementations of onPreDhcpAction() must call IpClient#completedPreDhcpAction() to
+ * indicate that DHCP is clear to proceed.
+ */
+ public void onPreDhcpAction() {}
+
+ /**
+ * Callback called after DHCP discovery/renewal.
+ */
+ public void onPostDhcpAction() {}
+
+ /**
+ * Callback called when new DHCP results are available.
+ *
+ * <p>This is purely advisory and not an indication of provisioning success or failure. This is
+ * only here for callers that want to expose DHCPv4 results to other APIs
+ * (e.g., WifiInfo#setInetAddress).
+ *
+ * <p>DHCPv4 or static IPv4 configuration failure or success can be determined by whether or not
+ * the passed-in DhcpResults object is null.
+ */
+ public void onNewDhcpResults(DhcpResults dhcpResults) {}
+
+ /**
+ * Indicates that provisioning was successful.
+ */
+ public void onProvisioningSuccess(LinkProperties newLp) {}
+
+ /**
+ * Indicates that provisioning failed.
+ */
+ public void onProvisioningFailure(LinkProperties newLp) {}
+
+ /**
+ * Invoked on LinkProperties changes.
+ */
+ public void onLinkPropertiesChange(LinkProperties newLp) {}
+
+ /**Called when the internal IpReachabilityMonitor (if enabled) has
+ * detected the loss of a critical number of required neighbors.
+ */
+ public void onReachabilityLost(String logMsg) {}
+
+ /**
+ * Called when the IpClient state machine terminates.
+ */
+ public void onQuit() {}
+
+ /**
+ * Called to indicate that a new APF program must be installed to filter incoming packets.
+ */
+ public void installPacketFilter(byte[] filter) {}
+
+ /**
+ * Called to indicate that the APF Program & data buffer must be read asynchronously from the
+ * wifi driver.
+ *
+ * <p>Due to Wifi HAL limitations, the current implementation only supports dumping the entire
+ * buffer. In response to this request, the driver returns the data buffer asynchronously
+ * by sending an IpClient#EVENT_READ_PACKET_FILTER_COMPLETE message.
+ */
+ public void startReadPacketFilter() {}
+
+ /**
+ * If multicast filtering cannot be accomplished with APF, this function will be called to
+ * actuate multicast filtering using another means.
+ */
+ public void setFallbackMulticastFilter(boolean enabled) {}
+
+ /**
+ * Enabled/disable Neighbor Discover offload functionality. This is called, for example,
+ * whenever 464xlat is being started or stopped.
+ */
+ public void setNeighborDiscoveryOffload(boolean enable) {}
+}
diff --git a/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl b/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl
index 57f59a1..fb4ca3b 100644
--- a/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl
+++ b/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl
@@ -25,6 +25,6 @@
* Network attributes were fetched for the specified L2 key. While the L2 key will never
* be null, the attributes may be if no data is stored about this L2 key.
*/
- void onL2KeyResponse(in StatusParcelable status, in String l2Key,
+ void onNetworkAttributesRetrieved(in StatusParcelable status, in String l2Key,
in NetworkAttributesParcelable attributes);
}
diff --git a/core/java/android/net/ipmemorystore/NetworkAttributes.java b/core/java/android/net/ipmemorystore/NetworkAttributes.java
index b932d21..6a9eae0 100644
--- a/core/java/android/net/ipmemorystore/NetworkAttributes.java
+++ b/core/java/android/net/ipmemorystore/NetworkAttributes.java
@@ -37,27 +37,57 @@
public class NetworkAttributes {
private static final boolean DBG = true;
+ // Weight cutoff for grouping. To group, a similarity score is computed with the following
+ // algorithm : if both fields are non-null and equals() then add their assigned weight, else if
+ // both are null then add a portion of their assigned weight (see NULL_MATCH_WEIGHT),
+ // otherwise add nothing.
+ // As a guideline, this should be something like 60~75% of the total weights in this class. The
+ // design states "in essence a reader should imagine that if two important columns don't match,
+ // or one important and several unimportant columns don't match then the two records are
+ // considered a different group".
+ private static final float TOTAL_WEIGHT_CUTOFF = 520.0f;
+ // The portion of the weight that is earned when scoring group-sameness by having both columns
+ // being null. This is because some networks rightfully don't have some attributes (e.g. a
+ // V6-only network won't have an assigned V4 address) and both being null should count for
+ // something, but attributes may also be null just because data is unavailable.
+ private static final float NULL_MATCH_WEIGHT = 0.25f;
+
// The v4 address that was assigned to this device the last time it joined this network.
// This typically comes from DHCP but could be something else like static configuration.
// This does not apply to IPv6.
// TODO : add a list of v6 prefixes for the v6 case.
@Nullable
public final Inet4Address assignedV4Address;
+ private static final float WEIGHT_ASSIGNEDV4ADDR = 300.0f;
// Optionally supplied by the client if it has an opinion on L3 network. For example, this
// could be a hash of the SSID + security type on WiFi.
@Nullable
public final String groupHint;
+ private static final float WEIGHT_GROUPHINT = 300.0f;
// The list of DNS server addresses.
@Nullable
public final List<InetAddress> dnsAddresses;
+ private static final float WEIGHT_DNSADDRESSES = 200.0f;
// The mtu on this network.
@Nullable
public final Integer mtu;
+ private static final float WEIGHT_MTU = 50.0f;
- NetworkAttributes(
+ // The sum of all weights in this class. Tests ensure that this stays equal to the total of
+ // all weights.
+ /** @hide */
+ @VisibleForTesting
+ public static final float TOTAL_WEIGHT = WEIGHT_ASSIGNEDV4ADDR
+ + WEIGHT_GROUPHINT
+ + WEIGHT_DNSADDRESSES
+ + WEIGHT_MTU;
+
+ /** @hide */
+ @VisibleForTesting
+ public NetworkAttributes(
@Nullable final Inet4Address assignedV4Address,
@Nullable final String groupHint,
@Nullable final List<InetAddress> dnsAddresses,
@@ -126,6 +156,34 @@
return parcelable;
}
+ private float samenessContribution(final float weight,
+ @Nullable final Object o1, @Nullable final Object o2) {
+ if (null == o1) {
+ return (null == o2) ? weight * NULL_MATCH_WEIGHT : 0f;
+ }
+ return Objects.equals(o1, o2) ? weight : 0f;
+ }
+
+ /** @hide */
+ public float getNetworkGroupSamenessConfidence(@NonNull final NetworkAttributes o) {
+ final float samenessScore =
+ samenessContribution(WEIGHT_ASSIGNEDV4ADDR, assignedV4Address, o.assignedV4Address)
+ + samenessContribution(WEIGHT_GROUPHINT, groupHint, o.groupHint)
+ + samenessContribution(WEIGHT_DNSADDRESSES, dnsAddresses, o.dnsAddresses)
+ + samenessContribution(WEIGHT_MTU, mtu, o.mtu);
+ // The minimum is 0, the max is TOTAL_WEIGHT and should be represented by 1.0, and
+ // TOTAL_WEIGHT_CUTOFF should represent 0.5, but there is no requirement that
+ // TOTAL_WEIGHT_CUTOFF would be half of TOTAL_WEIGHT (indeed, it should not be).
+ // So scale scores under the cutoff between 0 and 0.5, and the scores over the cutoff
+ // between 0.5 and 1.0.
+ if (samenessScore < TOTAL_WEIGHT_CUTOFF) {
+ return samenessScore / (TOTAL_WEIGHT_CUTOFF * 2);
+ } else {
+ return (samenessScore - TOTAL_WEIGHT_CUTOFF) / (TOTAL_WEIGHT - TOTAL_WEIGHT_CUTOFF) / 2
+ + 0.5f;
+ }
+ }
+
/** @hide */
public static class Builder {
@Nullable
@@ -194,6 +252,12 @@
}
}
+ /** @hide */
+ public boolean isEmpty() {
+ return (null == assignedV4Address) && (null == groupHint)
+ && (null == dnsAddresses) && (null == mtu);
+ }
+
@Override
public boolean equals(@Nullable final Object o) {
if (!(o instanceof NetworkAttributes)) return false;
diff --git a/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
index d040dcc..291aca8 100644
--- a/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
+++ b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
@@ -91,7 +91,8 @@
return confidence > 0.5 ? NETWORK_SAME : NETWORK_DIFFERENT;
}
- SameL3NetworkResponse(@NonNull final String l2Key1, @NonNull final String l2Key2,
+ /** @hide */
+ public SameL3NetworkResponse(@NonNull final String l2Key1, @NonNull final String l2Key2,
final float confidence) {
this.l2Key1 = l2Key1;
this.l2Key2 = l2Key2;
diff --git a/core/java/android/net/ipmemorystore/Status.java b/core/java/android/net/ipmemorystore/Status.java
index 95e5042..cacd42d 100644
--- a/core/java/android/net/ipmemorystore/Status.java
+++ b/core/java/android/net/ipmemorystore/Status.java
@@ -18,6 +18,8 @@
import android.annotation.NonNull;
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* A parcelable status representing the result of an operation.
* Parcels as StatusParceled.
@@ -26,7 +28,10 @@
public class Status {
public static final int SUCCESS = 0;
- public static final int ERROR_DATABASE_CANNOT_BE_OPENED = -1;
+ public static final int ERROR_GENERIC = -1;
+ public static final int ERROR_ILLEGAL_ARGUMENT = -2;
+ public static final int ERROR_DATABASE_CANNOT_BE_OPENED = -3;
+ public static final int ERROR_STORAGE = -4;
public final int resultCode;
@@ -34,7 +39,8 @@
this.resultCode = resultCode;
}
- Status(@NonNull final StatusParcelable parcelable) {
+ @VisibleForTesting
+ public Status(@NonNull final StatusParcelable parcelable) {
this(parcelable.resultCode);
}
@@ -55,7 +61,12 @@
public String toString() {
switch (resultCode) {
case SUCCESS: return "SUCCESS";
+ case ERROR_GENERIC: return "GENERIC ERROR";
+ case ERROR_ILLEGAL_ARGUMENT: return "ILLEGAL ARGUMENT";
case ERROR_DATABASE_CANNOT_BE_OPENED: return "DATABASE CANNOT BE OPENED";
+ // "DB storage error" is not very helpful but SQLite does not provide specific error
+ // codes upon store failure. Thus this indicates SQLite returned some error upon store
+ case ERROR_STORAGE: return "DATABASE STORAGE ERROR";
default: return "Unknown value ?!";
}
}
diff --git a/core/java/android/net/ipmemorystore/Utils.java b/core/java/android/net/ipmemorystore/Utils.java
index 73d8c83..b361aca 100644
--- a/core/java/android/net/ipmemorystore/Utils.java
+++ b/core/java/android/net/ipmemorystore/Utils.java
@@ -17,18 +17,25 @@
package android.net.ipmemorystore;
import android.annotation.NonNull;
+import android.annotation.Nullable;
/** {@hide} */
public class Utils {
/** Pretty print */
- public static String blobToString(final Blob blob) {
- final StringBuilder sb = new StringBuilder("Blob : [");
- if (blob.data.length <= 24) {
- appendByteArray(sb, blob.data, 0, blob.data.length);
+ public static String blobToString(@Nullable final Blob blob) {
+ return "Blob : " + byteArrayToString(null == blob ? null : blob.data);
+ }
+
+ /** Pretty print */
+ public static String byteArrayToString(@Nullable final byte[] data) {
+ if (null == data) return "null";
+ final StringBuilder sb = new StringBuilder("[");
+ if (data.length <= 24) {
+ appendByteArray(sb, data, 0, data.length);
} else {
- appendByteArray(sb, blob.data, 0, 16);
+ appendByteArray(sb, data, 0, 16);
sb.append("...");
- appendByteArray(sb, blob.data, blob.data.length - 8, blob.data.length);
+ appendByteArray(sb, data, data.length - 8, data.length);
}
sb.append("]");
return sb.toString();
diff --git a/core/java/android/net/metrics/ApfProgramEvent.java b/core/java/android/net/metrics/ApfProgramEvent.java
index 5dabf35..8601005 100644
--- a/core/java/android/net/metrics/ApfProgramEvent.java
+++ b/core/java/android/net/metrics/ApfProgramEvent.java
@@ -17,6 +17,8 @@
package android.net.metrics;
import android.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -36,11 +38,15 @@
* the APF program in place with a new APF program.
* {@hide}
*/
-public final class ApfProgramEvent implements Parcelable {
+@TestApi
+@SystemApi
+public final class ApfProgramEvent implements IpConnectivityLog.Event {
// Bitflag constants describing what an Apf program filters.
// Bits are indexeds from LSB to MSB, starting at index 0.
+ /** @hide */
public static final int FLAG_MULTICAST_FILTER_ON = 0;
+ /** @hide */
public static final int FLAG_HAS_IPV4_ADDRESS = 1;
/** {@hide} */
@@ -48,21 +54,33 @@
@Retention(RetentionPolicy.SOURCE)
public @interface Flags {}
+ /** @hide */
@UnsupportedAppUsage
- public long lifetime; // Maximum computed lifetime of the program in seconds
+ public final long lifetime; // Maximum computed lifetime of the program in seconds
+ /** @hide */
@UnsupportedAppUsage
- public long actualLifetime; // Effective program lifetime in seconds
+ public final long actualLifetime; // Effective program lifetime in seconds
+ /** @hide */
@UnsupportedAppUsage
- public int filteredRas; // Number of RAs filtered by the APF program
+ public final int filteredRas; // Number of RAs filtered by the APF program
+ /** @hide */
@UnsupportedAppUsage
- public int currentRas; // Total number of current RAs at generation time
+ public final int currentRas; // Total number of current RAs at generation time
+ /** @hide */
@UnsupportedAppUsage
- public int programLength; // Length of the APF program in bytes
+ public final int programLength; // Length of the APF program in bytes
+ /** @hide */
@UnsupportedAppUsage
- public int flags; // Bitfield compound of FLAG_* constants
+ public final int flags; // Bitfield compound of FLAG_* constants
- @UnsupportedAppUsage
- public ApfProgramEvent() {
+ private ApfProgramEvent(long lifetime, long actualLifetime, int filteredRas, int currentRas,
+ int programLength, int flags) {
+ this.lifetime = lifetime;
+ this.actualLifetime = actualLifetime;
+ this.filteredRas = filteredRas;
+ this.currentRas = currentRas;
+ this.programLength = programLength;
+ this.flags = flags;
}
private ApfProgramEvent(Parcel in) {
@@ -74,6 +92,75 @@
this.flags = in.readInt();
}
+ /**
+ * Utility to create an instance of {@link ApfProgramEvent}.
+ */
+ public static class Builder {
+ private long mLifetime;
+ private long mActualLifetime;
+ private int mFilteredRas;
+ private int mCurrentRas;
+ private int mProgramLength;
+ private int mFlags;
+
+ /**
+ * Set the maximum computed lifetime of the program in seconds.
+ */
+ public Builder setLifetime(long lifetime) {
+ mLifetime = lifetime;
+ return this;
+ }
+
+ /**
+ * Set the effective program lifetime in seconds.
+ */
+ public Builder setActualLifetime(long lifetime) {
+ mActualLifetime = lifetime;
+ return this;
+ }
+
+ /**
+ * Set the number of RAs filtered by the APF program.
+ */
+ public Builder setFilteredRas(int filteredRas) {
+ mFilteredRas = filteredRas;
+ return this;
+ }
+
+ /**
+ * Set the total number of current RAs at generation time.
+ */
+ public Builder setCurrentRas(int currentRas) {
+ mCurrentRas = currentRas;
+ return this;
+ }
+
+ /**
+ * Set the length of the APF program in bytes.
+ */
+ public Builder setProgramLength(int programLength) {
+ mProgramLength = programLength;
+ return this;
+ }
+
+ /**
+ * Set the flags describing what an Apf program filters.
+ */
+ public Builder setFlags(boolean hasIPv4, boolean multicastFilterOn) {
+ mFlags = flagsFor(hasIPv4, multicastFilterOn);
+ return this;
+ }
+
+ /**
+ * Build a new {@link ApfProgramEvent}.
+ */
+ public ApfProgramEvent build() {
+ return new ApfProgramEvent(mLifetime, mActualLifetime, mFilteredRas, mCurrentRas,
+ mProgramLength, mFlags);
+ }
+ }
+
+ /** @hide */
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeLong(lifetime);
@@ -84,6 +171,7 @@
out.writeInt(flags);
}
+ /** @hide */
@Override
public int describeContents() {
return 0;
@@ -96,6 +184,7 @@
programLength, actualLifetime, lifetimeString, namesOf(flags));
}
+ /** @hide */
public static final Parcelable.Creator<ApfProgramEvent> CREATOR
= new Parcelable.Creator<ApfProgramEvent>() {
public ApfProgramEvent createFromParcel(Parcel in) {
@@ -107,6 +196,7 @@
}
};
+ /** @hide */
@UnsupportedAppUsage
public static @Flags int flagsFor(boolean hasIPv4, boolean multicastFilterOn) {
int bitfield = 0;
diff --git a/core/java/android/net/metrics/ApfStats.java b/core/java/android/net/metrics/ApfStats.java
index bb2a35c..844a134 100644
--- a/core/java/android/net/metrics/ApfStats.java
+++ b/core/java/android/net/metrics/ApfStats.java
@@ -16,6 +16,8 @@
package android.net.metrics;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -24,42 +26,70 @@
* An event logged for an interface with APF capabilities when its IpClient state machine exits.
* {@hide}
*/
-public final class ApfStats implements Parcelable {
+@SystemApi
+@TestApi
+public final class ApfStats implements IpConnectivityLog.Event {
- /** time interval in milliseconds these stastistics covers. */
+ /**
+ * time interval in milliseconds these stastistics covers.
+ * @hide
+ */
@UnsupportedAppUsage
- public long durationMs;
- /** number of received RAs. */
+ public final long durationMs;
+ /**
+ * number of received RAs.
+ * @hide
+ */
@UnsupportedAppUsage
- public int receivedRas;
- /** number of received RAs matching a known RA. */
+ public final int receivedRas;
+ /**
+ * number of received RAs matching a known RA.
+ * @hide
+ */
@UnsupportedAppUsage
- public int matchingRas;
- /** number of received RAs ignored due to the MAX_RAS limit. */
+ public final int matchingRas;
+ /**
+ * number of received RAs ignored due to the MAX_RAS limit.
+ * @hide
+ */
@UnsupportedAppUsage
- public int droppedRas;
- /** number of received RAs with a minimum lifetime of 0. */
+ public final int droppedRas;
+ /**
+ * number of received RAs with a minimum lifetime of 0.
+ * @hide
+ */
@UnsupportedAppUsage
- public int zeroLifetimeRas;
- /** number of received RAs that could not be parsed. */
+ public final int zeroLifetimeRas;
+ /**
+ * number of received RAs that could not be parsed.
+ * @hide
+ */
@UnsupportedAppUsage
- public int parseErrors;
- /** number of APF program updates from receiving RAs.. */
+ public final int parseErrors;
+ /**
+ * number of APF program updates from receiving RAs.
+ * @hide
+ */
@UnsupportedAppUsage
- public int programUpdates;
- /** total number of APF program updates. */
+ public final int programUpdates;
+ /**
+ * total number of APF program updates.
+ * @hide
+ */
@UnsupportedAppUsage
- public int programUpdatesAll;
- /** number of APF program updates from allowing multicast traffic. */
+ public final int programUpdatesAll;
+ /**
+ * number of APF program updates from allowing multicast traffic.
+ * @hide
+ */
@UnsupportedAppUsage
- public int programUpdatesAllowingMulticast;
- /** maximum APF program size advertised by hardware. */
+ public final int programUpdatesAllowingMulticast;
+ /**
+ * maximum APF program size advertised by hardware.
+ * @hide
+ */
@UnsupportedAppUsage
- public int maxProgramSize;
-
- @UnsupportedAppUsage
- public ApfStats() {
- }
+ public final int maxProgramSize;
private ApfStats(Parcel in) {
this.durationMs = in.readLong();
@@ -74,6 +104,130 @@
this.maxProgramSize = in.readInt();
}
+ private ApfStats(long durationMs, int receivedRas, int matchingRas, int droppedRas,
+ int zeroLifetimeRas, int parseErrors, int programUpdates, int programUpdatesAll,
+ int programUpdatesAllowingMulticast, int maxProgramSize) {
+ this.durationMs = durationMs;
+ this.receivedRas = receivedRas;
+ this.matchingRas = matchingRas;
+ this.droppedRas = droppedRas;
+ this.zeroLifetimeRas = zeroLifetimeRas;
+ this.parseErrors = parseErrors;
+ this.programUpdates = programUpdates;
+ this.programUpdatesAll = programUpdatesAll;
+ this.programUpdatesAllowingMulticast = programUpdatesAllowingMulticast;
+ this.maxProgramSize = maxProgramSize;
+ }
+
+ /**
+ * Utility to create an instance of {@link ApfStats}.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static class Builder {
+ private long mDurationMs;
+ private int mReceivedRas;
+ private int mMatchingRas;
+ private int mDroppedRas;
+ private int mZeroLifetimeRas;
+ private int mParseErrors;
+ private int mProgramUpdates;
+ private int mProgramUpdatesAll;
+ private int mProgramUpdatesAllowingMulticast;
+ private int mMaxProgramSize;
+
+ /**
+ * Set the time interval in milliseconds these statistics covers.
+ */
+ public Builder setDurationMs(long durationMs) {
+ mDurationMs = durationMs;
+ return this;
+ }
+
+ /**
+ * Set the number of received RAs.
+ */
+ public Builder setReceivedRas(int receivedRas) {
+ mReceivedRas = receivedRas;
+ return this;
+ }
+
+ /**
+ * Set the number of received RAs matching a known RA.
+ */
+ public Builder setMatchingRas(int matchingRas) {
+ mMatchingRas = matchingRas;
+ return this;
+ }
+
+ /**
+ * Set the number of received RAs ignored due to the MAX_RAS limit.
+ */
+ public Builder setDroppedRas(int droppedRas) {
+ mDroppedRas = droppedRas;
+ return this;
+ }
+
+ /**
+ * Set the number of received RAs with a minimum lifetime of 0.
+ */
+ public Builder setZeroLifetimeRas(int zeroLifetimeRas) {
+ mZeroLifetimeRas = zeroLifetimeRas;
+ return this;
+ }
+
+ /**
+ * Set the number of received RAs that could not be parsed.
+ */
+ public Builder setParseErrors(int parseErrors) {
+ mParseErrors = parseErrors;
+ return this;
+ }
+
+ /**
+ * Set the number of APF program updates from receiving RAs.
+ */
+ public Builder setProgramUpdates(int programUpdates) {
+ mProgramUpdates = programUpdates;
+ return this;
+ }
+
+ /**
+ * Set the total number of APF program updates.
+ */
+ public Builder setProgramUpdatesAll(int programUpdatesAll) {
+ mProgramUpdatesAll = programUpdatesAll;
+ return this;
+ }
+
+ /**
+ * Set the number of APF program updates from allowing multicast traffic.
+ */
+ public Builder setProgramUpdatesAllowingMulticast(int programUpdatesAllowingMulticast) {
+ mProgramUpdatesAllowingMulticast = programUpdatesAllowingMulticast;
+ return this;
+ }
+
+ /**
+ * Set the maximum APF program size advertised by hardware.
+ */
+ public Builder setMaxProgramSize(int maxProgramSize) {
+ mMaxProgramSize = maxProgramSize;
+ return this;
+ }
+
+ /**
+ * Create a new {@link ApfStats}.
+ */
+ public ApfStats build() {
+ return new ApfStats(mDurationMs, mReceivedRas, mMatchingRas, mDroppedRas,
+ mZeroLifetimeRas, mParseErrors, mProgramUpdates, mProgramUpdatesAll,
+ mProgramUpdatesAllowingMulticast, mMaxProgramSize);
+ }
+ }
+
+ /** @hide */
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeLong(durationMs);
@@ -88,6 +242,7 @@
out.writeInt(maxProgramSize);
}
+ /** @hide */
@Override
public int describeContents() {
return 0;
@@ -108,6 +263,7 @@
.toString();
}
+ /** @hide */
public static final Parcelable.Creator<ApfStats> CREATOR = new Parcelable.Creator<ApfStats>() {
public ApfStats createFromParcel(Parcel in) {
return new ApfStats(in);
diff --git a/core/java/android/net/metrics/DhcpClientEvent.java b/core/java/android/net/metrics/DhcpClientEvent.java
index 98a7d7e..2a942ee 100644
--- a/core/java/android/net/metrics/DhcpClientEvent.java
+++ b/core/java/android/net/metrics/DhcpClientEvent.java
@@ -16,6 +16,8 @@
package android.net.metrics;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -24,7 +26,9 @@
* An event recorded when a DhcpClient state machine transitions to a new state.
* {@hide}
*/
-public final class DhcpClientEvent implements Parcelable {
+@SystemApi
+@TestApi
+public final class DhcpClientEvent implements IpConnectivityLog.Event {
// Names for recording DhcpClient pseudo-state transitions.
/** {@hide} Represents transitions from DhcpInitState to DhcpBoundState */
@@ -32,11 +36,13 @@
/** {@hide} Represents transitions from and to DhcpBoundState via DhcpRenewingState */
public static final String RENEWING_BOUND = "RenewingBoundState";
+ /** @hide */
public final String msg;
+ /** @hide */
public final int durationMs;
@UnsupportedAppUsage
- public DhcpClientEvent(String msg, int durationMs) {
+ private DhcpClientEvent(String msg, int durationMs) {
this.msg = msg;
this.durationMs = durationMs;
}
@@ -46,12 +52,45 @@
this.durationMs = in.readInt();
}
+ /**
+ * Utility to create an instance of {@link ApfProgramEvent}.
+ */
+ public static class Builder {
+ private String mMsg;
+ private int mDurationMs;
+
+ /**
+ * Set the message of the event.
+ */
+ public Builder setMsg(String msg) {
+ mMsg = msg;
+ return this;
+ }
+
+ /**
+ * Set the duration of the event in milliseconds.
+ */
+ public Builder setDurationMs(int durationMs) {
+ mDurationMs = durationMs;
+ return this;
+ }
+
+ /**
+ * Create a new {@link DhcpClientEvent}.
+ */
+ public DhcpClientEvent build() {
+ return new DhcpClientEvent(mMsg, mDurationMs);
+ }
+ }
+
+ /** @hide */
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeString(msg);
out.writeInt(durationMs);
}
+ /** @hide */
@Override
public int describeContents() {
return 0;
@@ -62,6 +101,7 @@
return String.format("DhcpClientEvent(%s, %dms)", msg, durationMs);
}
+ /** @hide */
public static final Parcelable.Creator<DhcpClientEvent> CREATOR
= new Parcelable.Creator<DhcpClientEvent>() {
public DhcpClientEvent createFromParcel(Parcel in) {
diff --git a/core/java/android/net/metrics/DhcpErrorEvent.java b/core/java/android/net/metrics/DhcpErrorEvent.java
index c6c06f0..18fde80 100644
--- a/core/java/android/net/metrics/DhcpErrorEvent.java
+++ b/core/java/android/net/metrics/DhcpErrorEvent.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;
import android.util.SparseArray;
@@ -27,48 +28,34 @@
* Event class used to record error events when parsing DHCP response packets.
* {@hide}
*/
-public final class DhcpErrorEvent implements Parcelable {
+@SystemApi
+@TestApi
+public final class DhcpErrorEvent implements IpConnectivityLog.Event {
public static final int L2_ERROR = 1;
public static final int L3_ERROR = 2;
public static final int L4_ERROR = 3;
public static final int DHCP_ERROR = 4;
public static final int MISC_ERROR = 5;
- @UnsupportedAppUsage
public static final int L2_TOO_SHORT = makeErrorCode(L2_ERROR, 1);
- @UnsupportedAppUsage
public static final int L2_WRONG_ETH_TYPE = makeErrorCode(L2_ERROR, 2);
- @UnsupportedAppUsage
public static final int L3_TOO_SHORT = makeErrorCode(L3_ERROR, 1);
- @UnsupportedAppUsage
public static final int L3_NOT_IPV4 = makeErrorCode(L3_ERROR, 2);
- @UnsupportedAppUsage
public static final int L3_INVALID_IP = makeErrorCode(L3_ERROR, 3);
- @UnsupportedAppUsage
public static final int L4_NOT_UDP = makeErrorCode(L4_ERROR, 1);
- @UnsupportedAppUsage
public static final int L4_WRONG_PORT = makeErrorCode(L4_ERROR, 2);
- @UnsupportedAppUsage
public static final int BOOTP_TOO_SHORT = makeErrorCode(DHCP_ERROR, 1);
- @UnsupportedAppUsage
public static final int DHCP_BAD_MAGIC_COOKIE = makeErrorCode(DHCP_ERROR, 2);
- @UnsupportedAppUsage
public static final int DHCP_INVALID_OPTION_LENGTH = makeErrorCode(DHCP_ERROR, 3);
- @UnsupportedAppUsage
public static final int DHCP_NO_MSG_TYPE = makeErrorCode(DHCP_ERROR, 4);
- @UnsupportedAppUsage
public static final int DHCP_UNKNOWN_MSG_TYPE = makeErrorCode(DHCP_ERROR, 5);
- @UnsupportedAppUsage
public static final int DHCP_NO_COOKIE = makeErrorCode(DHCP_ERROR, 6);
- @UnsupportedAppUsage
public static final int BUFFER_UNDERFLOW = makeErrorCode(MISC_ERROR, 1);
- @UnsupportedAppUsage
public static final int RECEIVE_ERROR = makeErrorCode(MISC_ERROR, 2);
- @UnsupportedAppUsage
public static final int PARSING_ERROR = makeErrorCode(MISC_ERROR, 3);
// error code byte format (MSB to LSB):
@@ -76,9 +63,9 @@
// byte 1: error subtype
// byte 2: unused
// byte 3: optional code
+ /** @hide */
public final int errorCode;
- @UnsupportedAppUsage
public DhcpErrorEvent(int errorCode) {
this.errorCode = errorCode;
}
@@ -87,16 +74,19 @@
this.errorCode = in.readInt();
}
+ /** @hide */
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(errorCode);
}
+ /** @hide */
@Override
public int describeContents() {
return 0;
}
+ /** @hide */
public static final Parcelable.Creator<DhcpErrorEvent> CREATOR
= new Parcelable.Creator<DhcpErrorEvent>() {
public DhcpErrorEvent createFromParcel(Parcel in) {
@@ -108,7 +98,6 @@
}
};
- @UnsupportedAppUsage
public static int errorCodeWithOption(int errorCode, int option) {
return (0xFFFF0000 & errorCode) | (0xFF & option);
}
diff --git a/core/java/android/net/metrics/IpConnectivityLog.java b/core/java/android/net/metrics/IpConnectivityLog.java
index 998f4ba..16aea31b 100644
--- a/core/java/android/net/metrics/IpConnectivityLog.java
+++ b/core/java/android/net/metrics/IpConnectivityLog.java
@@ -16,13 +16,17 @@
package android.net.metrics;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.net.ConnectivityMetricsEvent;
import android.net.IIpConnectivityMetrics;
+import android.net.Network;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.BitUtils;
@@ -30,18 +34,28 @@
* Class for logging IpConnectvity events with IpConnectivityMetrics
* {@hide}
*/
+@SystemApi
+@TestApi
public class IpConnectivityLog {
private static final String TAG = IpConnectivityLog.class.getSimpleName();
private static final boolean DBG = false;
+ /** @hide */
public static final String SERVICE_NAME = "connmetrics";
private IIpConnectivityMetrics mService;
+ /**
+ * An event to be logged.
+ */
+ public interface Event extends Parcelable {}
+
+ /** @hide */
@UnsupportedAppUsage
public IpConnectivityLog() {
}
+ /** @hide */
@VisibleForTesting
public IpConnectivityLog(IIpConnectivityMetrics service) {
mService = service;
@@ -67,6 +81,7 @@
* @param ev the event to log. If the event timestamp is 0,
* the timestamp is set to the current time in milliseconds.
* @return true if the event was successfully logged.
+ * @hide
*/
public boolean log(ConnectivityMetricsEvent ev) {
if (!checkLoggerService()) {
@@ -94,7 +109,7 @@
* @param data is a Parcelable instance representing the event.
* @return true if the event was successfully logged.
*/
- public boolean log(long timestamp, Parcelable data) {
+ public boolean log(long timestamp, Event data) {
ConnectivityMetricsEvent ev = makeEv(data);
ev.timestamp = timestamp;
return log(ev);
@@ -106,8 +121,7 @@
* @param data is a Parcelable instance representing the event.
* @return true if the event was successfully logged.
*/
- @UnsupportedAppUsage
- public boolean log(String ifname, Parcelable data) {
+ public boolean log(String ifname, Event data) {
ConnectivityMetricsEvent ev = makeEv(data);
ev.ifname = ifname;
return log(ev);
@@ -115,13 +129,25 @@
/**
* Log an IpConnectivity event.
+ * @param network the network associated with the event.
+ * @param transports the current transports of the network associated with the event, as defined
+ * in NetworkCapabilities.
+ * @param data is a Parcelable instance representing the event.
+ * @return true if the event was successfully logged.
+ */
+ public boolean log(Network network, int[] transports, Event data) {
+ return log(network.netId, transports, data);
+ }
+
+ /**
+ * Log an IpConnectivity event.
* @param netid the id of the network associated with the event.
* @param transports the current transports of the network associated with the event, as defined
* in NetworkCapabilities.
* @param data is a Parcelable instance representing the event.
* @return true if the event was successfully logged.
*/
- public boolean log(int netid, int[] transports, Parcelable data) {
+ public boolean log(int netid, int[] transports, Event data) {
ConnectivityMetricsEvent ev = makeEv(data);
ev.netId = netid;
ev.transports = BitUtils.packBits(transports);
@@ -133,12 +159,11 @@
* @param data is a Parcelable instance representing the event.
* @return true if the event was successfully logged.
*/
- @UnsupportedAppUsage
- public boolean log(Parcelable data) {
+ public boolean log(Event data) {
return log(makeEv(data));
}
- private static ConnectivityMetricsEvent makeEv(Parcelable data) {
+ private static ConnectivityMetricsEvent makeEv(Event data) {
ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent();
ev.data = data;
return ev;
diff --git a/core/java/android/net/metrics/IpManagerEvent.java b/core/java/android/net/metrics/IpManagerEvent.java
index c47650f..013e353 100644
--- a/core/java/android/net/metrics/IpManagerEvent.java
+++ b/core/java/android/net/metrics/IpManagerEvent.java
@@ -17,7 +17,8 @@
package android.net.metrics;
import android.annotation.IntDef;
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.SparseArray;
@@ -28,11 +29,13 @@
import java.lang.annotation.RetentionPolicy;
/**
- * An event recorded by IpManager when IP provisioning completes for a network or
+ * An event recorded by IpClient when IP provisioning completes for a network or
* when a network disconnects.
* {@hide}
*/
-public final class IpManagerEvent implements Parcelable {
+@SystemApi
+@TestApi
+public final class IpManagerEvent implements IpConnectivityLog.Event {
public static final int PROVISIONING_OK = 1;
public static final int PROVISIONING_FAIL = 2;
@@ -43,6 +46,7 @@
public static final int ERROR_INVALID_PROVISIONING = 7;
public static final int ERROR_INTERFACE_NOT_FOUND = 8;
+ /** @hide */
@IntDef(value = {
PROVISIONING_OK, PROVISIONING_FAIL, COMPLETE_LIFECYCLE,
ERROR_STARTING_IPV4, ERROR_STARTING_IPV6, ERROR_STARTING_IPREACHABILITYMONITOR,
@@ -51,10 +55,11 @@
@Retention(RetentionPolicy.SOURCE)
public @interface EventType {}
+ /** @hide */
public final @EventType int eventType;
+ /** @hide */
public final long durationMs;
- @UnsupportedAppUsage
public IpManagerEvent(@EventType int eventType, long duration) {
this.eventType = eventType;
this.durationMs = duration;
@@ -65,17 +70,20 @@
this.durationMs = in.readLong();
}
+ /** @hide */
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(eventType);
out.writeLong(durationMs);
}
+ /** @hide */
@Override
public int describeContents() {
return 0;
}
+ /** @hide */
public static final Parcelable.Creator<IpManagerEvent> CREATOR
= new Parcelable.Creator<IpManagerEvent>() {
public IpManagerEvent createFromParcel(Parcel in) {
diff --git a/core/java/android/net/metrics/IpReachabilityEvent.java b/core/java/android/net/metrics/IpReachabilityEvent.java
index 715c95e..c736297 100644
--- a/core/java/android/net/metrics/IpReachabilityEvent.java
+++ b/core/java/android/net/metrics/IpReachabilityEvent.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;
import android.util.SparseArray;
@@ -28,7 +29,9 @@
* a neighbor probe result.
* {@hide}
*/
-public final class IpReachabilityEvent implements Parcelable {
+@SystemApi
+@TestApi
+public final class IpReachabilityEvent implements IpConnectivityLog.Event {
// Event types.
/** A probe forced by IpReachabilityMonitor. */
@@ -47,9 +50,9 @@
// byte 1: unused
// byte 2: type of event: PROBE, NUD_FAILED, PROVISIONING_LOST
// byte 3: when byte 2 == PROBE, errno code from RTNetlink or IpReachabilityMonitor.
+ /** @hide */
public final int eventType;
- @UnsupportedAppUsage
public IpReachabilityEvent(int eventType) {
this.eventType = eventType;
}
@@ -58,16 +61,19 @@
this.eventType = in.readInt();
}
+ /** @hide */
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(eventType);
}
+ /** @hide */
@Override
public int describeContents() {
return 0;
}
+ /** @hide */
public static final Parcelable.Creator<IpReachabilityEvent> CREATOR
= new Parcelable.Creator<IpReachabilityEvent>() {
public IpReachabilityEvent createFromParcel(Parcel in) {
@@ -79,18 +85,6 @@
}
};
- /**
- * Returns the NUD failure event type code corresponding to the given conditions.
- */
- @UnsupportedAppUsage
- public static int nudFailureEventType(boolean isFromProbe, boolean isProvisioningLost) {
- if (isFromProbe) {
- return isProvisioningLost ? PROVISIONING_LOST : NUD_FAILED;
- } else {
- return isProvisioningLost ? PROVISIONING_LOST_ORGANIC : NUD_FAILED_ORGANIC;
- }
- }
-
@Override
public String toString() {
int hi = eventType & 0xff00;
diff --git a/core/java/android/net/metrics/NetworkEvent.java b/core/java/android/net/metrics/NetworkEvent.java
index cb82fbe..f5b2ff1 100644
--- a/core/java/android/net/metrics/NetworkEvent.java
+++ b/core/java/android/net/metrics/NetworkEvent.java
@@ -17,6 +17,8 @@
package android.net.metrics;
import android.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.SparseArray;
@@ -29,7 +31,9 @@
/**
* {@hide}
*/
-public final class NetworkEvent implements Parcelable {
+@SystemApi
+@TestApi
+public final class NetworkEvent implements IpConnectivityLog.Event {
public static final int NETWORK_CONNECTED = 1;
public static final int NETWORK_VALIDATED = 2;
@@ -46,6 +50,7 @@
public static final int NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND = 12;
+ /** @hide */
@IntDef(value = {
NETWORK_CONNECTED,
NETWORK_VALIDATED,
@@ -63,7 +68,9 @@
@Retention(RetentionPolicy.SOURCE)
public @interface EventType {}
+ /** @hide */
public final @EventType int eventType;
+ /** @hide */
public final long durationMs;
public NetworkEvent(@EventType int eventType, long durationMs) {
@@ -80,17 +87,20 @@
durationMs = in.readLong();
}
+ /** @hide */
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(eventType);
out.writeLong(durationMs);
}
+ /** @hide */
@Override
public int describeContents() {
return 0;
}
+ /** @hide */
public static final Parcelable.Creator<NetworkEvent> CREATOR
= new Parcelable.Creator<NetworkEvent>() {
public NetworkEvent createFromParcel(Parcel in) {
diff --git a/core/java/android/net/metrics/RaEvent.java b/core/java/android/net/metrics/RaEvent.java
index c41881c..d308246 100644
--- a/core/java/android/net/metrics/RaEvent.java
+++ b/core/java/android/net/metrics/RaEvent.java
@@ -24,7 +24,7 @@
* An event logged when the APF packet socket receives an RA packet.
* {@hide}
*/
-public final class RaEvent implements Parcelable {
+public final class RaEvent implements IpConnectivityLog.Event {
public static final long NO_LIFETIME = -1L;
diff --git a/core/java/android/net/metrics/ValidationProbeEvent.java b/core/java/android/net/metrics/ValidationProbeEvent.java
index 12c2305..42e8aa6 100644
--- a/core/java/android/net/metrics/ValidationProbeEvent.java
+++ b/core/java/android/net/metrics/ValidationProbeEvent.java
@@ -17,6 +17,8 @@
package android.net.metrics;
import android.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.SparseArray;
@@ -30,7 +32,9 @@
* An event recorded by NetworkMonitor when sending a probe for finding captive portals.
* {@hide}
*/
-public final class ValidationProbeEvent implements Parcelable {
+@SystemApi
+@TestApi
+public final class ValidationProbeEvent implements IpConnectivityLog.Event {
public static final int PROBE_DNS = 0;
public static final int PROBE_HTTP = 1;
@@ -45,20 +49,27 @@
private static final int FIRST_VALIDATION = 1 << 8;
private static final int REVALIDATION = 2 << 8;
+ /** @hide */
@IntDef(value = {DNS_FAILURE, DNS_SUCCESS})
@Retention(RetentionPolicy.SOURCE)
public @interface ReturnCode {}
- public long durationMs;
+ /** @hide */
+ public final long durationMs;
// probeType byte format (MSB to LSB):
// byte 0: unused
// byte 1: unused
// byte 2: 0 = UNKNOWN, 1 = FIRST_VALIDATION, 2 = REVALIDATION
// byte 3: PROBE_* constant
- public int probeType;
- public @ReturnCode int returnCode;
+ /** @hide */
+ public final int probeType;
+ /** @hide */
+ public final @ReturnCode int returnCode;
- public ValidationProbeEvent() {
+ private ValidationProbeEvent(long durationMs, int probeType, int returnCode) {
+ this.durationMs = durationMs;
+ this.probeType = probeType;
+ this.returnCode = returnCode;
}
private ValidationProbeEvent(Parcel in) {
@@ -67,6 +78,47 @@
returnCode = in.readInt();
}
+ /**
+ * Utility to create an instance of {@link ApfProgramEvent}.
+ */
+ public static class Builder {
+ private long mDurationMs;
+ private int mProbeType;
+ private int mReturnCode;
+
+ /**
+ * Set the duration of the probe in milliseconds.
+ */
+ public Builder setDurationMs(long durationMs) {
+ mDurationMs = durationMs;
+ return this;
+ }
+
+ /**
+ * Set the probe type based on whether it was the first validation.
+ */
+ public Builder setProbeType(int probeType, boolean firstValidation) {
+ mProbeType = makeProbeType(probeType, firstValidation);
+ return this;
+ }
+
+ /**
+ * Set the return code of the probe.
+ */
+ public Builder setReturnCode(int returnCode) {
+ mReturnCode = returnCode;
+ return this;
+ }
+
+ /**
+ * Create a new {@link ValidationProbeEvent}.
+ */
+ public ValidationProbeEvent build() {
+ return new ValidationProbeEvent(mDurationMs, mProbeType, mReturnCode);
+ }
+ }
+
+ /** @hide */
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeLong(durationMs);
@@ -74,11 +126,13 @@
out.writeInt(returnCode);
}
+ /** @hide */
@Override
public int describeContents() {
return 0;
}
+ /** @hide */
public static final Parcelable.Creator<ValidationProbeEvent> CREATOR
= new Parcelable.Creator<ValidationProbeEvent>() {
public ValidationProbeEvent createFromParcel(Parcel in) {
@@ -90,7 +144,7 @@
}
};
- public static int makeProbeType(int probeType, boolean firstValidation) {
+ private static int makeProbeType(int probeType, boolean firstValidation) {
return (probeType & 0xff) | (firstValidation ? FIRST_VALIDATION : REVALIDATION);
}
@@ -98,7 +152,7 @@
return Decoder.constants.get(probeType & 0xff, "PROBE_???");
}
- public static String getValidationStage(int probeType) {
+ private static String getValidationStage(int probeType) {
return Decoder.constants.get(probeType & 0xff00, "UNKNOWN");
}
diff --git a/services/net/java/android/net/util/MultinetworkPolicyTracker.java b/core/java/android/net/util/MultinetworkPolicyTracker.java
similarity index 100%
rename from services/net/java/android/net/util/MultinetworkPolicyTracker.java
rename to core/java/android/net/util/MultinetworkPolicyTracker.java
diff --git a/core/java/android/net/util/SocketUtils.java b/core/java/android/net/util/SocketUtils.java
new file mode 100644
index 0000000..de67cf5
--- /dev/null
+++ b/core/java/android/net/util/SocketUtils.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015 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.util;
+
+import static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_BINDTODEVICE;
+
+import android.annotation.SystemApi;
+import android.net.NetworkUtils;
+import android.system.ErrnoException;
+import android.system.NetlinkSocketAddress;
+import android.system.Os;
+import android.system.PacketSocketAddress;
+
+import java.io.FileDescriptor;
+import java.net.SocketAddress;
+
+/**
+ * Collection of utilities to interact with raw sockets.
+ * @hide
+ */
+@SystemApi
+public class SocketUtils {
+ /**
+ * Create a raw datagram socket that is bound to an interface.
+ *
+ * <p>Data sent through the socket will go directly to the underlying network, ignoring VPNs.
+ */
+ public static void bindSocketToInterface(FileDescriptor socket, String iface)
+ throws ErrnoException {
+ // SO_BINDTODEVICE actually takes a string. This works because the first member
+ // of struct ifreq is a NULL-terminated interface name.
+ // TODO: add a setsockoptString()
+ Os.setsockoptIfreq(socket, SOL_SOCKET, SO_BINDTODEVICE, iface);
+ NetworkUtils.protectFromVpn(socket);
+ }
+
+ /**
+ * Make a socket address to communicate with netlink.
+ */
+ public static SocketAddress makeNetlinkSocketAddress(int portId, int groupsMask) {
+ return new NetlinkSocketAddress(portId, groupsMask);
+ }
+
+ /**
+ * Make a socket address to bind to packet sockets.
+ */
+ public static SocketAddress makePacketSocketAddress(short protocol, int ifIndex) {
+ return new PacketSocketAddress(protocol, ifIndex);
+ }
+
+ /**
+ * Make a socket address to send raw packets.
+ */
+ public static SocketAddress makePacketSocketAddress(int ifIndex, byte[] hwAddr) {
+ return new PacketSocketAddress(ifIndex, hwAddr);
+ }
+
+ private SocketUtils() {}
+}
diff --git a/core/java/android/os/AppZygote.java b/core/java/android/os/AppZygote.java
index 950f381..6daa5b4 100644
--- a/core/java/android/os/AppZygote.java
+++ b/core/java/android/os/AppZygote.java
@@ -103,7 +103,7 @@
String abi = mAppInfo.primaryCpuAbi != null ? mAppInfo.primaryCpuAbi :
Build.SUPPORTED_ABIS[0];
try {
- mZygote = Process.zygoteProcess.startChildZygote(
+ mZygote = Process.ZYGOTE_PROCESS.startChildZygote(
"com.android.internal.os.AppZygoteInit",
mAppInfo.processName + "_zygote",
mZygoteUid,
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index c5a51f1..518528d 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -79,23 +79,27 @@
* Called when taking bugreport resulted in an error.
*
* @param errorCode the error that occurred. Possible values are
- * {@code BUGREPORT_ERROR_INVALID_INPUT}, {@code BUGREPORT_ERROR_RUNTIME}.
+ * {@code BUGREPORT_ERROR_INVALID_INPUT},
+ * {@code BUGREPORT_ERROR_RUNTIME},
+ * {@code BUGREPORT_ERROR_USER_DENIED_CONSENT}.
*/
void onError(@BugreportErrorCode int errorCode);
/**
- * Called when taking bugreport finishes successfully
- *
- * @param durationMs time capturing bugreport took in milliseconds
- * @param title title for the bugreport; helpful in reminding the user why they took it
- * @param description detailed description for the bugreport
+ * Called when taking bugreport finishes successfully.
*/
- void onFinished(long durationMs, @NonNull String title,
- @NonNull String description);
+ void onFinished();
}
/**
- * Starts a bugreport asynchronously.
+ * Starts a bugreport.
+ *
+ * <p>This starts a bugreport in the background. However the call itself can take several
+ * seconds to return in the worst case. {@code listener} will receive progress and status
+ * updates.
+ *
+ * <p>The bugreport artifacts will be copied over to the given file descriptors only if the
+ * user consents to sharing with the calling app.
*
* @param bugreportFd file to write the bugreport. This should be opened in write-only,
* append mode.
@@ -107,7 +111,7 @@
@RequiresPermission(android.Manifest.permission.DUMP)
public void startBugreport(@NonNull FileDescriptor bugreportFd,
@Nullable FileDescriptor screenshotFd,
- @NonNull BugreportParams params, @Nullable BugreportListener listener) {
+ @NonNull BugreportParams params, @NonNull BugreportListener listener) {
// TODO(b/111441001): Enforce android.Manifest.permission.DUMP if necessary.
DumpstateListener dsListener = new DumpstateListener(listener);
@@ -121,6 +125,18 @@
}
}
+ /*
+ * Cancels a currently running bugreport.
+ */
+ @RequiresPermission(android.Manifest.permission.DUMP)
+ public void cancelBugreport() {
+ try {
+ mBinder.cancelBugreport();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private final class DumpstateListener extends IDumpstateListener.Stub
implements DeathRecipient {
private final BugreportListener mListener;
@@ -145,9 +161,13 @@
}
@Override
- public void onFinished(long durationMs, String title, String description)
- throws RemoteException {
- mListener.onFinished(durationMs, title, description);
+ public void onFinished() throws RemoteException {
+ try {
+ mListener.onFinished();
+ } finally {
+ // The bugreport has finished. Let's shutdown the service to minimize its footprint.
+ cancelBugreport();
+ }
}
// Old methods; should go away
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index ca39051..51c3c4c 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -72,6 +72,7 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Comparator;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -831,6 +832,16 @@
return false;
}
+ /** {@hide} */
+ public static boolean contains(Collection<File> dirs, File file) {
+ for (File dir : dirs) {
+ if (contains(dir, file)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Test if a file lives under the given directory, either as a direct child
* or a distant grandchild.
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index f51ba9a..5bf9095 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -23,10 +23,15 @@
import android.content.pm.ResolveInfo;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
+import android.gamedriver.GameDriverProto.Blacklist;
+import android.gamedriver.GameDriverProto.Blacklists;
import android.opengl.EGL14;
import android.provider.Settings;
+import android.util.Base64;
import android.util.Log;
+import com.android.framework.protobuf.InvalidProtocolBufferException;
+
import dalvik.system.VMRuntime;
import java.io.BufferedReader;
@@ -62,6 +67,8 @@
private static final String ANGLE_RULES_FILE = "a4a_rules.json";
private static final String ANGLE_TEMP_RULES = "debug.angle.rules";
private static final String ACTION_ANGLE_FOR_ANDROID = "android.app.action.ANGLE_FOR_ANDROID";
+ private static final String GAME_DRIVER_BLACKLIST_FLAG = "blacklist";
+ private static final int BASE64_FLAGS = Base64.NO_PADDING | Base64.NO_WRAP;
private ClassLoader mClassLoader;
private String mLayerPath;
@@ -480,6 +487,24 @@
return;
}
+ ApplicationInfo driverInfo;
+ try {
+ driverInfo = context.getPackageManager().getApplicationInfo(driverPackageName,
+ PackageManager.MATCH_SYSTEM_ONLY);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "driver package '" + driverPackageName + "' not installed");
+ return;
+ }
+
+ // O drivers are restricted to the sphal linker namespace, so don't try to use
+ // packages unless they declare they're compatible with that restriction.
+ if (driverInfo.targetSdkVersion < Build.VERSION_CODES.O) {
+ if (DEBUG) {
+ Log.w(TAG, "updated driver package is not known to be compatible with O");
+ }
+ return;
+ }
+
// To minimize risk of driver updates crippling the device beyond user repair, never use an
// updated driver for privileged or non-updated system apps. Presumably pre-installed apps
// were tested thoroughly with the pre-installed driver.
@@ -491,7 +516,7 @@
// GUP_DEV_ALL_APPS
// 0: Default (Invalid values fallback to default as well)
- // 1: All apps use Game Update Package
+ // 1: All apps use Game Driver
// 2: All apps use system graphics driver
int gupDevAllApps = coreSettings.getInt(Settings.Global.GUP_DEV_ALL_APPS, 0);
if (gupDevAllApps == 2) {
@@ -510,33 +535,45 @@
}
return;
}
+ boolean isDevOptIn = getGlobalSettingsString(coreSettings,
+ Settings.Global.GUP_DEV_OPT_IN_APPS)
+ .contains(ai.packageName);
- if (!getGlobalSettingsString(coreSettings, Settings.Global.GUP_DEV_OPT_IN_APPS)
- .contains(ai.packageName)
- && !onWhitelist(context, driverPackageName, ai.packageName)) {
+ if (!isDevOptIn && !onWhitelist(context, driverPackageName, ai.packageName)) {
if (DEBUG) {
Log.w(TAG, ai.packageName + " is not on the whitelist.");
}
return;
}
- }
- ApplicationInfo driverInfo;
- try {
- driverInfo = context.getPackageManager().getApplicationInfo(driverPackageName,
- PackageManager.MATCH_SYSTEM_ONLY);
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "driver package '" + driverPackageName + "' not installed");
- return;
- }
-
- // O drivers are restricted to the sphal linker namespace, so don't try to use
- // packages unless they declare they're compatible with that restriction.
- if (driverInfo.targetSdkVersion < Build.VERSION_CODES.O) {
- if (DEBUG) {
- Log.w(TAG, "updated driver package is not known to be compatible with O");
+ if (!isDevOptIn) {
+ // At this point, the application is on the whitelist only, check whether it's
+ // on the blacklist, terminate early when it's on the blacklist.
+ try {
+ // TODO(b/121350991) Switch to DeviceConfig with property listener.
+ String base64String = coreSettings.getString(Settings.Global.GUP_BLACKLIST);
+ if (base64String != null && !base64String.isEmpty()) {
+ Blacklists blacklistsProto = Blacklists.parseFrom(
+ Base64.decode(base64String, BASE64_FLAGS));
+ List<Blacklist> blacklists = blacklistsProto.getBlacklistsList();
+ long driverVersionCode = driverInfo.longVersionCode;
+ for (Blacklist blacklist : blacklists) {
+ if (blacklist.getVersionCode() == driverVersionCode) {
+ for (String packageName : blacklist.getPackageNamesList()) {
+ if (packageName == ai.packageName) {
+ return;
+ }
+ }
+ break;
+ }
+ }
+ }
+ } catch (InvalidProtocolBufferException e) {
+ if (DEBUG) {
+ Log.w(TAG, "Can't parse blacklist, skip and continue...");
+ }
+ }
}
- return;
}
String abi = chooseAbi(driverInfo);
diff --git a/core/java/android/os/HandlerThread.java b/core/java/android/os/HandlerThread.java
index a4d5c6f..92fcbb6 100644
--- a/core/java/android/os/HandlerThread.java
+++ b/core/java/android/os/HandlerThread.java
@@ -20,8 +20,10 @@
import android.annotation.Nullable;
/**
- * Handy class for starting a new thread that has a looper. The looper can then be
- * used to create handler classes. Note that start() must still be called.
+ * A {@link Thread} that has a {@link Looper}.
+ * The {@link Looper} can then be used to create {@link Handler}s.
+ * <p>
+ * Note that just like with a regular {@link Thread}, {@link #start()} must still be called.
*/
public class HandlerThread extends Thread {
int mPriority;
diff --git a/core/java/android/os/IDeviceIdleController.aidl b/core/java/android/os/IDeviceIdleController.aidl
index 8271701..fe17c6b 100644
--- a/core/java/android/os/IDeviceIdleController.aidl
+++ b/core/java/android/os/IDeviceIdleController.aidl
@@ -45,4 +45,6 @@
void exitIdle(String reason);
boolean registerMaintenanceActivityListener(IMaintenanceActivityListener listener);
void unregisterMaintenanceActivityListener(IMaintenanceActivityListener listener);
+ int setPreIdleTimeoutMode(int Mode);
+ void resetPreIdleTimeoutMode();
}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 0942d97..4ce760f 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1724,6 +1724,25 @@
= "android.os.action.SCREEN_BRIGHTNESS_BOOST_CHANGED";
/**
+ * Constant for PreIdleTimeout normal mode (default mode, not short nor extend timeout) .
+ * @hide
+ */
+ public static final int PRE_IDLE_TIMEOUT_MODE_NORMAL = 0;
+
+ /**
+ * Constant for PreIdleTimeout long mode (extend timeout to keep in inactive mode
+ * longer).
+ * @hide
+ */
+ public static final int PRE_IDLE_TIMEOUT_MODE_LONG = 1;
+
+ /**
+ * Constant for PreIdleTimeout short mode (short timeout to go to doze mode quickly)
+ * @hide
+ */
+ public static final int PRE_IDLE_TIMEOUT_MODE_SHORT = 2;
+
+ /**
* A wake lock is a mechanism to indicate that your application needs
* to have the device stay on.
* <p>
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 760fef7..07c4933 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -32,23 +32,12 @@
private static final String LOG_TAG = "Process";
/**
- * @hide for internal use only.
- */
- public static final String ZYGOTE_SOCKET = "zygote";
-
- /**
- * @hide for internal use only.
- */
- public static final String SECONDARY_ZYGOTE_SOCKET = "zygote_secondary";
-
- /**
* An invalid UID value.
*/
public static final int INVALID_UID = -1;
/**
* Defines the root UID.
- * @hide
*/
public static final int ROOT_UID = 0;
@@ -64,7 +53,6 @@
/**
* Defines the UID/GID for the user shell.
- * @hide
*/
public static final int SHELL_UID = 2000;
@@ -118,7 +106,6 @@
/**
* Defines the UID/GID for the Bluetooth service process.
- * @hide
*/
public static final int BLUETOOTH_UID = 1002;
@@ -482,8 +469,7 @@
* State associated with the zygote process.
* @hide
*/
- public static final ZygoteProcess zygoteProcess =
- new ZygoteProcess(ZYGOTE_SOCKET, SECONDARY_ZYGOTE_SOCKET);
+ public static final ZygoteProcess ZYGOTE_PROCESS = new ZygoteProcess();
/**
* Start a new process.
@@ -541,7 +527,7 @@
@Nullable String[] packagesForUid,
@Nullable String[] visibleVols,
@Nullable String[] zygoteArgs) {
- return zygoteProcess.start(processClass, niceName, uid, gid, gids,
+ return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, packageName,
packagesForUid, visibleVols, zygoteArgs);
diff --git a/core/java/android/os/ServiceSpecificException.java b/core/java/android/os/ServiceSpecificException.java
index 3e0f6da..3b0f26ae 100644
--- a/core/java/android/os/ServiceSpecificException.java
+++ b/core/java/android/os/ServiceSpecificException.java
@@ -15,6 +15,8 @@
*/
package android.os;
+import android.annotation.SystemApi;
+
/**
* An exception specific to a service.
*
@@ -28,6 +30,7 @@
*
* @hide
*/
+@SystemApi
public class ServiceSpecificException extends RuntimeException {
public final int errorCode;
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index 2299ab2..76fe560 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -140,6 +140,21 @@
return mUids[index];
}
+ /**
+ * Return the UID to which this WorkSource should be attributed to, i.e, the UID that
+ * initiated the work and not the UID performing it. If the WorkSource has no UIDs, returns -1
+ * instead.
+ *
+ * @hide
+ */
+ public int getAttributionUid() {
+ if (isEmpty()) {
+ return -1;
+ }
+
+ return mNum > 0 ? mUids[0] : mChains.get(0).getAttributionUid();
+ }
+
/** @hide */
@TestApi
public String getName(int index) {
@@ -912,17 +927,18 @@
/**
* Return the UID to which this WorkChain should be attributed to, i.e, the UID that
- * initiated the work and not the UID performing it.
+ * initiated the work and not the UID performing it. Returns -1 if the chain is empty.
*/
public int getAttributionUid() {
- return mUids[0];
+ return mSize > 0 ? mUids[0] : -1;
}
/**
* Return the tag associated with the attribution UID. See (@link #getAttributionUid}.
+ * Returns null if the chain is empty.
*/
public String getAttributionTag() {
- return mTags[0];
+ return mTags.length > 0 ? mTags[0] : null;
}
// TODO: The following three trivial getters are purely for testing and will be removed
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index ec77821..4cb9c5b 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -62,87 +62,119 @@
* {@hide}
*/
public class ZygoteProcess {
+
+ /**
+ * @hide for internal use only.
+ */
+ public static final String ZYGOTE_SOCKET_NAME = "zygote";
+
+ /**
+ * @hide for internal use only.
+ */
+ public static final String ZYGOTE_SECONDARY_SOCKET_NAME = "zygote_secondary";
+
+ /**
+ * @hide for internal use only
+ */
private static final String LOG_TAG = "ZygoteProcess";
/**
* The name of the socket used to communicate with the primary zygote.
*/
- private final LocalSocketAddress mSocket;
+ private final LocalSocketAddress mZygoteSocketAddress;
/**
* The name of the secondary (alternate ABI) zygote socket.
*/
- private final LocalSocketAddress mSecondarySocket;
+ private final LocalSocketAddress mZygoteSecondarySocketAddress;
- public ZygoteProcess(String primarySocket, String secondarySocket) {
- this(new LocalSocketAddress(primarySocket, LocalSocketAddress.Namespace.RESERVED),
- new LocalSocketAddress(secondarySocket, LocalSocketAddress.Namespace.RESERVED));
+ public ZygoteProcess() {
+ mZygoteSocketAddress =
+ new LocalSocketAddress(ZYGOTE_SOCKET_NAME, LocalSocketAddress.Namespace.RESERVED);
+ mZygoteSecondarySocketAddress =
+ new LocalSocketAddress(ZYGOTE_SECONDARY_SOCKET_NAME,
+ LocalSocketAddress.Namespace.RESERVED);
}
- public ZygoteProcess(LocalSocketAddress primarySocket, LocalSocketAddress secondarySocket) {
- mSocket = primarySocket;
- mSecondarySocket = secondarySocket;
+ public ZygoteProcess(LocalSocketAddress primarySocketAddress,
+ LocalSocketAddress secondarySocketAddress) {
+ mZygoteSocketAddress = primarySocketAddress;
+ mZygoteSecondarySocketAddress = secondarySocketAddress;
}
public LocalSocketAddress getPrimarySocketAddress() {
- return mSocket;
+ return mZygoteSocketAddress;
}
/**
* State for communicating with the zygote process.
*/
public static class ZygoteState {
- final LocalSocket socket;
- final DataInputStream inputStream;
- final BufferedWriter writer;
- final List<String> abiList;
+ final LocalSocketAddress mZygoteSocketAddress;
- boolean mClosed;
+ private final LocalSocket mZygoteSessionSocket;
- private ZygoteState(LocalSocket socket, DataInputStream inputStream,
- BufferedWriter writer, List<String> abiList) {
- this.socket = socket;
- this.inputStream = inputStream;
- this.writer = writer;
- this.abiList = abiList;
+ final DataInputStream mZygoteInputStream;
+ final BufferedWriter mZygoteOutputWriter;
+
+ private final List<String> mABIList;
+
+ private boolean mClosed;
+
+ private ZygoteState(LocalSocketAddress zygoteSocketAddress,
+ LocalSocket zygoteSessionSocket,
+ DataInputStream zygoteInputStream,
+ BufferedWriter zygoteOutputWriter,
+ List<String> abiList) {
+ this.mZygoteSocketAddress = zygoteSocketAddress;
+ this.mZygoteSessionSocket = zygoteSessionSocket;
+ this.mZygoteInputStream = zygoteInputStream;
+ this.mZygoteOutputWriter = zygoteOutputWriter;
+ this.mABIList = abiList;
}
- public static ZygoteState connect(LocalSocketAddress address) throws IOException {
+ /**
+ * Create a new ZygoteState object by connecting to the given Zygote socket.
+ *
+ * @param zygoteSocketAddress Zygote socket to connect to
+ * @return A new ZygoteState object containing a session socket for the given Zygote socket
+ * address
+ * @throws IOException
+ */
+ public static ZygoteState connect(LocalSocketAddress zygoteSocketAddress)
+ throws IOException {
+
DataInputStream zygoteInputStream = null;
- BufferedWriter zygoteWriter = null;
- final LocalSocket zygoteSocket = new LocalSocket();
+ BufferedWriter zygoteOutputWriter = null;
+ final LocalSocket zygoteSessionSocket = new LocalSocket();
try {
- zygoteSocket.connect(address);
-
- zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream());
-
- zygoteWriter = new BufferedWriter(new OutputStreamWriter(
- zygoteSocket.getOutputStream()), 256);
+ zygoteSessionSocket.connect(zygoteSocketAddress);
+ zygoteInputStream = new DataInputStream(zygoteSessionSocket.getInputStream());
+ zygoteOutputWriter =
+ new BufferedWriter(
+ new OutputStreamWriter(zygoteSessionSocket.getOutputStream()),
+ 256);
} catch (IOException ex) {
try {
- zygoteSocket.close();
- } catch (IOException ignore) {
- }
+ zygoteSessionSocket.close();
+ } catch (IOException ignore) { }
throw ex;
}
- String abiListString = getAbiList(zygoteWriter, zygoteInputStream);
- Log.i("Zygote", "Process: zygote socket " + address.getNamespace() + "/"
- + address.getName() + " opened, supported ABIS: " + abiListString);
-
- return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter,
- Arrays.asList(abiListString.split(",")));
+ return new ZygoteState(zygoteSocketAddress,
+ zygoteSessionSocket, zygoteInputStream, zygoteOutputWriter,
+ getAbiList(zygoteOutputWriter, zygoteInputStream));
}
boolean matches(String abi) {
- return abiList.contains(abi);
+ return mABIList.contains(abi);
}
public void close() {
try {
- socket.close();
+ mZygoteSessionSocket.close();
} catch (IOException ex) {
Log.e(LOG_TAG,"I/O exception on routine close", ex);
}
@@ -241,7 +273,7 @@
try {
return startViaZygote(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
- abi, instructionSet, appDataDir, invokeWith, false /* startChildZygote */,
+ abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/false,
packageName, packagesForUid, visibleVols, zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
Log.e(LOG_TAG,
@@ -260,7 +292,7 @@
* @throws ZygoteStartFailedEx if the query failed.
*/
@GuardedBy("mLock")
- private static String getAbiList(BufferedWriter writer, DataInputStream inputStream)
+ private static List<String> getAbiList(BufferedWriter writer, DataInputStream inputStream)
throws IOException {
// Each query starts with the argument count (1 in this case)
writer.write("1");
@@ -276,7 +308,9 @@
byte[] bytes = new byte[numBytes];
inputStream.readFully(bytes);
- return new String(bytes, StandardCharsets.US_ASCII);
+ String rawList = new String(bytes, StandardCharsets.US_ASCII);
+
+ return Arrays.asList(rawList.split(","));
}
/**
@@ -310,8 +344,8 @@
* the child or -1 on failure, followed by boolean to
* indicate whether a wrapper process was used.
*/
- final BufferedWriter writer = zygoteState.writer;
- final DataInputStream inputStream = zygoteState.inputStream;
+ final BufferedWriter writer = zygoteState.mZygoteOutputWriter;
+ final DataInputStream inputStream = zygoteState.mZygoteInputStream;
writer.write(Integer.toString(args.size()));
writer.newLine();
@@ -528,18 +562,18 @@
ZygoteState state = openZygoteSocketIfNeeded(abi);
// Each query starts with the argument count (1 in this case)
- state.writer.write("1");
+ state.mZygoteOutputWriter.write("1");
// ... followed by a new-line.
- state.writer.newLine();
+ state.mZygoteOutputWriter.newLine();
// ... followed by our only argument.
- state.writer.write("--get-pid");
- state.writer.newLine();
- state.writer.flush();
+ state.mZygoteOutputWriter.write("--get-pid");
+ state.mZygoteOutputWriter.newLine();
+ state.mZygoteOutputWriter.flush();
// The response is a length prefixed stream of ASCII bytes.
- int numBytes = state.inputStream.readInt();
+ int numBytes = state.mZygoteInputStream.readInt();
byte[] bytes = new byte[numBytes];
- state.inputStream.readFully(bytes);
+ state.mZygoteInputStream.readFully(bytes);
return Integer.parseInt(new String(bytes, StandardCharsets.US_ASCII));
}
@@ -593,16 +627,16 @@
return true;
}
try {
- state.writer.write(Integer.toString(mApiBlacklistExemptions.size() + 1));
- state.writer.newLine();
- state.writer.write("--set-api-blacklist-exemptions");
- state.writer.newLine();
+ state.mZygoteOutputWriter.write(Integer.toString(mApiBlacklistExemptions.size() + 1));
+ state.mZygoteOutputWriter.newLine();
+ state.mZygoteOutputWriter.write("--set-api-blacklist-exemptions");
+ state.mZygoteOutputWriter.newLine();
for (int i = 0; i < mApiBlacklistExemptions.size(); ++i) {
- state.writer.write(mApiBlacklistExemptions.get(i));
- state.writer.newLine();
+ state.mZygoteOutputWriter.write(mApiBlacklistExemptions.get(i));
+ state.mZygoteOutputWriter.newLine();
}
- state.writer.flush();
- int status = state.inputStream.readInt();
+ state.mZygoteOutputWriter.flush();
+ int status = state.mZygoteInputStream.readInt();
if (status != 0) {
Slog.e(LOG_TAG, "Failed to set API blacklist exemptions; status " + status);
}
@@ -622,13 +656,13 @@
return;
}
try {
- state.writer.write(Integer.toString(1));
- state.writer.newLine();
- state.writer.write("--hidden-api-log-sampling-rate="
+ state.mZygoteOutputWriter.write(Integer.toString(1));
+ state.mZygoteOutputWriter.newLine();
+ state.mZygoteOutputWriter.write("--hidden-api-log-sampling-rate="
+ Integer.toString(mHiddenApiAccessLogSampleRate));
- state.writer.newLine();
- state.writer.flush();
- int status = state.inputStream.readInt();
+ state.mZygoteOutputWriter.newLine();
+ state.mZygoteOutputWriter.flush();
+ int status = state.mZygoteInputStream.readInt();
if (status != 0) {
Slog.e(LOG_TAG, "Failed to set hidden API log sampling rate; status " + status);
}
@@ -638,22 +672,29 @@
}
/**
- * Tries to open socket to Zygote process if not already open. If
- * already open, does nothing. May block and retry. Requires that mLock be held.
+ * Tries to open a session socket to a Zygote process with a compatible ABI if one is not
+ * already open. If a compatible session socket is already open that session socket is returned.
+ * This function may block and may have to try connecting to multiple Zygotes to find the
+ * appropriate one. Requires that mLock be held.
*/
@GuardedBy("mLock")
- private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {
+ private ZygoteState openZygoteSocketIfNeeded(String abi)
+ throws ZygoteStartFailedEx {
+
Preconditions.checkState(Thread.holdsLock(mLock), "ZygoteProcess lock not held");
if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
try {
- primaryZygoteState = ZygoteState.connect(mSocket);
+ primaryZygoteState =
+ ZygoteState.connect(mZygoteSocketAddress);
} catch (IOException ioe) {
throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);
}
+
maybeSetApiBlacklistExemptions(primaryZygoteState, false);
maybeSetHiddenApiAccessLogSampleRate(primaryZygoteState);
}
+
if (primaryZygoteState.matches(abi)) {
return primaryZygoteState;
}
@@ -661,10 +702,12 @@
// The primary zygote didn't match. Try the secondary.
if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) {
try {
- secondaryZygoteState = ZygoteState.connect(mSecondarySocket);
+ secondaryZygoteState =
+ ZygoteState.connect(mZygoteSecondarySocketAddress);
} catch (IOException ioe) {
throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe);
}
+
maybeSetApiBlacklistExemptions(secondaryZygoteState, false);
maybeSetHiddenApiAccessLogSampleRate(secondaryZygoteState);
}
@@ -685,11 +728,11 @@
IOException {
synchronized (mLock) {
ZygoteState state = openZygoteSocketIfNeeded(abi);
- state.writer.write("2");
- state.writer.newLine();
+ state.mZygoteOutputWriter.write("2");
+ state.mZygoteOutputWriter.newLine();
- state.writer.write("--preload-app");
- state.writer.newLine();
+ state.mZygoteOutputWriter.write("--preload-app");
+ state.mZygoteOutputWriter.newLine();
// Zygote args needs to be strings, so in order to pass ApplicationInfo,
// write it to a Parcel, and base64 the raw Parcel bytes to the other side.
@@ -697,12 +740,12 @@
appInfo.writeToParcel(parcel, 0 /* flags */);
String encodedParcelData = Base64.getEncoder().encodeToString(parcel.marshall());
parcel.recycle();
- state.writer.write(encodedParcelData);
- state.writer.newLine();
+ state.mZygoteOutputWriter.write(encodedParcelData);
+ state.mZygoteOutputWriter.newLine();
- state.writer.flush();
+ state.mZygoteOutputWriter.flush();
- return (state.inputStream.readInt() == 0);
+ return (state.mZygoteInputStream.readInt() == 0);
}
}
@@ -715,27 +758,27 @@
IOException {
synchronized(mLock) {
ZygoteState state = openZygoteSocketIfNeeded(abi);
- state.writer.write("5");
- state.writer.newLine();
+ state.mZygoteOutputWriter.write("5");
+ state.mZygoteOutputWriter.newLine();
- state.writer.write("--preload-package");
- state.writer.newLine();
+ state.mZygoteOutputWriter.write("--preload-package");
+ state.mZygoteOutputWriter.newLine();
- state.writer.write(packagePath);
- state.writer.newLine();
+ state.mZygoteOutputWriter.write(packagePath);
+ state.mZygoteOutputWriter.newLine();
- state.writer.write(libsPath);
- state.writer.newLine();
+ state.mZygoteOutputWriter.write(libsPath);
+ state.mZygoteOutputWriter.newLine();
- state.writer.write(libFileName);
- state.writer.newLine();
+ state.mZygoteOutputWriter.write(libFileName);
+ state.mZygoteOutputWriter.newLine();
- state.writer.write(cacheKey);
- state.writer.newLine();
+ state.mZygoteOutputWriter.write(cacheKey);
+ state.mZygoteOutputWriter.newLine();
- state.writer.flush();
+ state.mZygoteOutputWriter.flush();
- return (state.inputStream.readInt() == 0);
+ return (state.mZygoteInputStream.readInt() == 0);
}
}
@@ -749,13 +792,13 @@
synchronized (mLock) {
ZygoteState state = openZygoteSocketIfNeeded(abi);
// Each query starts with the argument count (1 in this case)
- state.writer.write("1");
- state.writer.newLine();
- state.writer.write("--preload-default");
- state.writer.newLine();
- state.writer.flush();
+ state.mZygoteOutputWriter.write("1");
+ state.mZygoteOutputWriter.newLine();
+ state.mZygoteOutputWriter.write("--preload-default");
+ state.mZygoteOutputWriter.newLine();
+ state.mZygoteOutputWriter.flush();
- return (state.inputStream.readInt() == 0);
+ return (state.mZygoteInputStream.readInt() == 0);
}
}
@@ -763,20 +806,21 @@
* Try connecting to the Zygote over and over again until we hit a time-out.
* @param socketName The name of the socket to connect to.
*/
- public static void waitForConnectionToZygote(String socketName) {
- final LocalSocketAddress address =
- new LocalSocketAddress(socketName, LocalSocketAddress.Namespace.RESERVED);
- waitForConnectionToZygote(address);
+ public static void waitForConnectionToZygote(String zygoteSocketName) {
+ final LocalSocketAddress zygoteSocketAddress =
+ new LocalSocketAddress(zygoteSocketName, LocalSocketAddress.Namespace.RESERVED);
+ waitForConnectionToZygote(zygoteSocketAddress);
}
/**
* Try connecting to the Zygote over and over again until we hit a time-out.
* @param address The name of the socket to connect to.
*/
- public static void waitForConnectionToZygote(LocalSocketAddress address) {
+ public static void waitForConnectionToZygote(LocalSocketAddress zygoteSocketAddress) {
for (int n = 20; n >= 0; n--) {
try {
- final ZygoteState zs = ZygoteState.connect(address);
+ final ZygoteState zs =
+ ZygoteState.connect(zygoteSocketAddress);
zs.close();
return;
} catch (IOException ioe) {
@@ -789,7 +833,8 @@
} catch (InterruptedException ie) {
}
}
- Slog.wtf(LOG_TAG, "Failed to connect to Zygote through socket " + address.getName());
+ Slog.wtf(LOG_TAG, "Failed to connect to Zygote through socket "
+ + zygoteSocketAddress.getName());
}
/**
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index df1a713..714a061 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -35,6 +35,7 @@
import java.io.CharArrayWriter;
import java.io.File;
+import java.util.Locale;
/**
* Information about a shared/external storage volume for a specific user.
@@ -263,6 +264,11 @@
return mFsUuid;
}
+ /** {@hide} */
+ public @Nullable String getNormalizedUuid() {
+ return mFsUuid != null ? mFsUuid.toLowerCase(Locale.US) : null;
+ }
+
/**
* Parse and return volume UUID as FAT volume ID, or return -1 if unable to
* parse or UUID is unknown.
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index 8c3aa17..5d310e1 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -42,6 +42,7 @@
import java.io.CharArrayWriter;
import java.io.File;
import java.util.Comparator;
+import java.util.Locale;
import java.util.Objects;
/**
@@ -254,6 +255,10 @@
return fsUuid;
}
+ public @Nullable String getNormalizedFsUuid() {
+ return fsUuid != null ? fsUuid.toLowerCase(Locale.US) : null;
+ }
+
@UnsupportedAppUsage
public int getMountUserId() {
return mountUserId;
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 9015703..87efbf3 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -86,6 +86,41 @@
@SystemApi
public static final String NAMESPACE_INPUT_NATIVE_BOOT = "input_native_boot";
+ /**
+ * Namespace for all netd related features.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_NETD_NATIVE = "netd_native";
+
+ /**
+ * Namespace for features related to the ExtServices Notification Assistant.
+ * These features are applied immediately.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_NOTIFICATION_ASSISTANT = "notification_assistant";
+
+ /**
+ * Telephony related properties definitions.
+ *
+ * @hide
+ */
+ @SystemApi
+ public interface Telephony {
+ String NAMESPACE = "telephony";
+ /**
+ * Whether to apply ramping ringer on incoming phone calls.
+ */
+ String PROPERTY_ENABLE_RAMPING_RINGER = "enable_ramping_ringer";
+ /**
+ * Ringer ramping time in milliseconds.
+ */
+ String PROPERTY_RAMPING_RINGER_DURATION = "ramping_duration";
+ }
+
private static final Object sLock = new Object();
@GuardedBy("sLock")
private static Map<OnPropertyChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index a323ed1..5f1c560 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -16,7 +16,6 @@
package android.provider;
-import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
import static com.android.internal.util.Preconditions.checkCollectionNotEmpty;
@@ -50,6 +49,8 @@
import android.os.RemoteException;
import android.util.Log;
+import com.android.internal.util.Preconditions;
+
import dalvik.system.VMRuntime;
import java.io.File;
@@ -640,6 +641,28 @@
public static final String COLUMN_MIME_TYPES = "mime_types";
/**
+ * Query arguments supported by this root. This column is optional
+ * and related to {@link #COLUMN_FLAGS} and {@link #FLAG_SUPPORTS_SEARCH}.
+ * If the flags include {@link #FLAG_SUPPORTS_SEARCH}, and the column is
+ * {@code null}, the root is assumed to support {@link #QUERY_ARG_DISPLAY_NAME}
+ * search of {@link Document#COLUMN_DISPLAY_NAME}. Multiple query arguments
+ * can be separated by a newline. For example, a root supporting
+ * {@link #QUERY_ARG_MIME_TYPES} and {@link #QUERY_ARG_DISPLAY_NAME} might
+ * return "android:query-arg-mime-types\nandroid:query-arg-display-name".
+ * <p>
+ * Type: STRING
+ * @see #COLUMN_FLAGS
+ * @see #FLAG_SUPPORTS_SEARCH
+ * @see #QUERY_ARG_DISPLAY_NAME
+ * @see #QUERY_ARG_FILE_SIZE_OVER
+ * @see #QUERY_ARG_LAST_MODIFIED_AFTER
+ * @see #QUERY_ARG_MIME_TYPES
+ * @see DocumentsProvider#querySearchDocuments(String, String[],
+ * Bundle)
+ */
+ public static final String COLUMN_QUERY_ARGS = "query_args";
+
+ /**
* MIME type for a root.
*/
public static final String MIME_TYPE_ITEM = "vnd.android.document/root";
@@ -680,6 +703,8 @@
* String)
* @see DocumentsProvider#querySearchDocuments(String, String,
* String[])
+ * @see DocumentsProvider#querySearchDocuments(String, String[],
+ * Bundle)
*/
public static final int FLAG_SUPPORTS_SEARCH = 1 << 3;
@@ -1109,11 +1134,13 @@
}
/**
- * Test if the given URI represents roots backed by {@link DocumentsProvider}.
+ * Test if the given URI represents all roots of the authority
+ * backed by {@link DocumentsProvider}.
*
* @see #buildRootsUri(String)
*/
- public static boolean isRootsUri(Context context, @Nullable Uri uri) {
+ public static boolean isRootsUri(@NonNull Context context, @Nullable Uri uri) {
+ Preconditions.checkNotNull(context, "context can not be null");
return isRootUri(context, uri, 1 /* pathSize */);
}
@@ -1122,7 +1149,8 @@
*
* @see #buildRootUri(String, String)
*/
- public static boolean isRootUri(Context context, @Nullable Uri uri) {
+ public static boolean isRootUri(@NonNull Context context, @Nullable Uri uri) {
+ Preconditions.checkNotNull(context, "context can not be null");
return isRootUri(context, uri, 2 /* pathSize */);
}
@@ -1215,6 +1243,7 @@
* {@hide}
*/
public static String getSearchDocumentsQuery(@NonNull Bundle bundle) {
+ Preconditions.checkNotNull(bundle, "bundle can not be null");
return bundle.getString(QUERY_ARG_DISPLAY_NAME, "" /* defaultValue */);
}
@@ -1315,8 +1344,12 @@
* @return if given document is a descendant of the given parent.
* @see Root#FLAG_SUPPORTS_IS_CHILD
*/
- public static boolean isChildDocument(ContentInterface content, Uri parentDocumentUri,
- Uri childDocumentUri) throws FileNotFoundException {
+ public static boolean isChildDocument(@NonNull ContentInterface content,
+ @NonNull Uri parentDocumentUri, @NonNull Uri childDocumentUri)
+ throws FileNotFoundException {
+ Preconditions.checkNotNull(content, "content can not be null");
+ Preconditions.checkNotNull(parentDocumentUri, "parentDocumentUri can not be null");
+ Preconditions.checkNotNull(childDocumentUri, "childDocumentUri can not be null");
try {
final Bundle in = new Bundle();
in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
@@ -1325,7 +1358,7 @@
final Bundle out = content.call(parentDocumentUri.getAuthority(),
METHOD_IS_CHILD_DOCUMENT, null, in);
if (out == null) {
- throw new RemoteException("Failed to get a reponse from isChildDocument query.");
+ throw new RemoteException("Failed to get a response from isChildDocument query.");
}
if (!out.containsKey(DocumentsContract.EXTRA_RESULT)) {
throw new RemoteException("Response did not include result field..");
@@ -1559,8 +1592,10 @@
* @param documentUri a Document URI
* @return a Bundle of Bundles.
*/
- public static Bundle getDocumentMetadata(ContentInterface content, Uri documentUri)
- throws FileNotFoundException {
+ public static @Nullable Bundle getDocumentMetadata(@NonNull ContentInterface content,
+ @NonNull Uri documentUri) throws FileNotFoundException {
+ Preconditions.checkNotNull(content, "content can not be null");
+ Preconditions.checkNotNull(documentUri, "documentUri can not be null");
try {
final Bundle in = new Bundle();
in.putParcelable(EXTRA_URI, documentUri);
@@ -1595,8 +1630,6 @@
*/
public static Path findDocumentPath(ContentInterface content, Uri treeUri)
throws FileNotFoundException {
- checkArgument(isTreeUri(treeUri), treeUri + " is not a tree uri.");
-
try {
final Bundle in = new Bundle();
in.putParcelable(DocumentsContract.EXTRA_URI, treeUri);
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 70c84f8..9b9e2de 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -37,6 +37,7 @@
import android.Manifest;
import android.annotation.CallSuper;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AuthenticationRequiredException;
import android.content.ClipDescription;
@@ -63,6 +64,8 @@
import android.provider.DocumentsContract.Root;
import android.util.Log;
+import com.android.internal.util.Preconditions;
+
import libcore.io.IoUtils;
import java.io.FileNotFoundException;
@@ -687,14 +690,17 @@
* extras {@link Bundle} when any QUERY_ARG_* value was honored
* during the preparation of the results.
*
+ * @see Root#COLUMN_QUERY_ARGS
* @see ContentResolver#EXTRA_HONORED_ARGS
* @see DocumentsContract#EXTRA_LOADING
* @see DocumentsContract#EXTRA_INFO
* @see DocumentsContract#EXTRA_ERROR
*/
@SuppressWarnings("unused")
- public Cursor querySearchDocuments(String rootId, String[] projection, Bundle queryArgs)
- throws FileNotFoundException {
+ public Cursor querySearchDocuments(@NonNull String rootId, @Nullable String[] projection,
+ @NonNull Bundle queryArgs) throws FileNotFoundException {
+ Preconditions.checkNotNull(rootId, "rootId can not be null");
+ Preconditions.checkNotNull(queryArgs, "queryArgs can not be null");
return querySearchDocuments(rootId, DocumentsContract.getSearchDocumentsQuery(queryArgs),
projection);
}
@@ -732,7 +738,7 @@
* @return a Bundle of Bundles.
* @see DocumentsContract#getDocumentMetadata(ContentResolver, Uri)
*/
- public @Nullable Bundle getDocumentMetadata(String documentId)
+ public @Nullable Bundle getDocumentMetadata(@NonNull String documentId)
throws FileNotFoundException {
throw new UnsupportedOperationException("Metadata not supported");
}
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 3a49986..487198b 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -70,6 +70,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -1224,7 +1226,7 @@
if (sv.isPrimary()) {
return VOLUME_EXTERNAL;
} else {
- return checkArgumentVolumeName(sv.getUuid());
+ return checkArgumentVolumeName(sv.getNormalizedUuid());
}
}
throw new IllegalStateException("Unknown volume at " + path);
@@ -2919,7 +2921,7 @@
if (vi.isPrimary()) {
volumeNames.add(VOLUME_EXTERNAL);
} else {
- volumeNames.add(vi.getFsUuid());
+ volumeNames.add(vi.getNormalizedFsUuid());
}
}
}
@@ -2953,8 +2955,7 @@
// When not one of the well-known values above, it must be a hex UUID
for (int i = 0; i < volumeName.length(); i++) {
final char c = volumeName.charAt(i);
- if (('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')
- || ('0' <= c && c <= '9') || (c == '-')) {
+ if (('a' <= c && c <= 'f') || ('0' <= c && c <= '9') || (c == '-')) {
continue;
} else {
throw new IllegalArgumentException("Invalid volume name: " + volumeName);
@@ -2963,23 +2964,26 @@
return volumeName;
}
- /** {@hide} */
+ /**
+ * Return path where the given volume is mounted. Not valid for
+ * {@link #VOLUME_INTERNAL}.
+ *
+ * @hide
+ */
public static @NonNull File getVolumePath(@NonNull String volumeName)
throws FileNotFoundException {
if (TextUtils.isEmpty(volumeName)) {
throw new IllegalArgumentException();
}
- if (VOLUME_INTERNAL.equals(volumeName)) {
- return Environment.getDataDirectory();
- } else if (VOLUME_EXTERNAL.equals(volumeName)) {
+ if (VOLUME_EXTERNAL.equals(volumeName)) {
return Environment.getExternalStorageDirectory();
}
final StorageManager sm = AppGlobals.getInitialApplication()
.getSystemService(StorageManager.class);
for (VolumeInfo vi : sm.getVolumes()) {
- if (Objects.equals(vi.getFsUuid(), volumeName)) {
+ if (Objects.equals(vi.getNormalizedFsUuid(), volumeName)) {
final File path = vi.getPathForUser(UserHandle.myUserId());
if (path != null) {
return path;
@@ -2992,6 +2996,33 @@
}
/**
+ * Return paths that should be scanned for the given volume.
+ *
+ * @hide
+ */
+ public static @NonNull Collection<File> getVolumeScanPaths(@NonNull String volumeName)
+ throws FileNotFoundException {
+ if (TextUtils.isEmpty(volumeName)) {
+ throw new IllegalArgumentException();
+ }
+
+ final ArrayList<File> res = new ArrayList<>();
+ if (VOLUME_INTERNAL.equals(volumeName)) {
+ res.add(new File(Environment.getRootDirectory(), "media"));
+ res.add(new File(Environment.getOemDirectory(), "media"));
+ res.add(new File(Environment.getProductDirectory(), "media"));
+ } else {
+ res.add(getVolumePath(volumeName));
+ final UserManager um = AppGlobals.getInitialApplication()
+ .getSystemService(UserManager.class);
+ if (VOLUME_EXTERNAL.equals(volumeName) && um.isDemoUser()) {
+ res.add(Environment.getDataPreloadsMediaDirectory());
+ }
+ }
+ return res;
+ }
+
+ /**
* Uri for querying the state of the media scanner.
*/
public static Uri getMediaScannerUri() {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d5de13c..fa3f54a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3300,6 +3300,14 @@
public static final int SCREEN_BRIGHTNESS_MODE_AUTOMATIC = 1;
/**
+ * Control whether to enable adaptive sleep mode.
+ * @hide
+ */
+ public static final String ADAPTIVE_SLEEP = "adaptive_sleep";
+
+ private static final Validator ADAPTIVE_SLEEP_VALIDATOR = BOOLEAN_VALIDATOR;
+
+ /**
* Control whether the process CPU usage meter should be shown.
*
* @deprecated This functionality is no longer available as of
@@ -4232,6 +4240,7 @@
SCREEN_BRIGHTNESS_MODE,
SCREEN_AUTO_BRIGHTNESS_ADJ,
SCREEN_BRIGHTNESS_FOR_VR,
+ ADAPTIVE_SLEEP,
VIBRATE_INPUT_DEVICES,
MODE_RINGER_STREAMS_AFFECTED,
TEXT_AUTO_REPLACE,
@@ -4307,6 +4316,7 @@
PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS);
PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS_FOR_VR);
PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS_MODE);
+ PUBLIC_SETTINGS.add(ADAPTIVE_SLEEP);
PUBLIC_SETTINGS.add(MODE_RINGER_STREAMS_AFFECTED);
PUBLIC_SETTINGS.add(MUTE_STREAMS_AFFECTED);
PUBLIC_SETTINGS.add(VIBRATE_ON);
@@ -4411,6 +4421,7 @@
VALIDATORS.put(SCREEN_OFF_TIMEOUT, SCREEN_OFF_TIMEOUT_VALIDATOR);
VALIDATORS.put(SCREEN_BRIGHTNESS_FOR_VR, SCREEN_BRIGHTNESS_FOR_VR_VALIDATOR);
VALIDATORS.put(SCREEN_BRIGHTNESS_MODE, SCREEN_BRIGHTNESS_MODE_VALIDATOR);
+ VALIDATORS.put(ADAPTIVE_SLEEP, ADAPTIVE_SLEEP_VALIDATOR);
VALIDATORS.put(MODE_RINGER_STREAMS_AFFECTED, MODE_RINGER_STREAMS_AFFECTED_VALIDATOR);
VALIDATORS.put(MUTE_STREAMS_AFFECTED, MUTE_STREAMS_AFFECTED_VALIDATOR);
VALIDATORS.put(VIBRATE_ON, VIBRATE_ON_VALIDATOR);
@@ -10816,6 +10827,8 @@
*
* @hide
*/
+ @SystemApi
+ @TestApi
public static final int CAPTIVE_PORTAL_MODE_IGNORE = 0;
/**
@@ -10824,6 +10837,8 @@
*
* @hide
*/
+ @SystemApi
+ @TestApi
public static final int CAPTIVE_PORTAL_MODE_PROMPT = 1;
/**
@@ -10832,6 +10847,8 @@
*
* @hide
*/
+ @SystemApi
+ @TestApi
public static final int CAPTIVE_PORTAL_MODE_AVOID = 2;
/**
@@ -10841,6 +10858,8 @@
* The default for this setting is CAPTIVE_PORTAL_MODE_PROMPT.
* @hide
*/
+ @SystemApi
+ @TestApi
public static final String CAPTIVE_PORTAL_MODE = "captive_portal_mode";
/**
@@ -10869,6 +10888,8 @@
*
* @hide
*/
+ @SystemApi
+ @TestApi
public static final String CAPTIVE_PORTAL_HTTPS_URL = "captive_portal_https_url";
/**
@@ -10877,6 +10898,8 @@
*
* @hide
*/
+ @SystemApi
+ @TestApi
public static final String CAPTIVE_PORTAL_HTTP_URL = "captive_portal_http_url";
/**
@@ -10885,6 +10908,8 @@
*
* @hide
*/
+ @SystemApi
+ @TestApi
public static final String CAPTIVE_PORTAL_FALLBACK_URL = "captive_portal_fallback_url";
/**
@@ -10893,6 +10918,8 @@
*
* @hide
*/
+ @SystemApi
+ @TestApi
public static final String CAPTIVE_PORTAL_OTHER_FALLBACK_URLS =
"captive_portal_other_fallback_urls";
@@ -10902,6 +10929,8 @@
* by "@@,@@".
* @hide
*/
+ @SystemApi
+ @TestApi
public static final String CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS =
"captive_portal_fallback_probe_specs";
@@ -10912,6 +10941,8 @@
*
* @hide
*/
+ @SystemApi
+ @TestApi
public static final String CAPTIVE_PORTAL_USE_HTTPS = "captive_portal_use_https";
/**
@@ -10920,6 +10951,8 @@
*
* @hide
*/
+ @SystemApi
+ @TestApi
public static final String CAPTIVE_PORTAL_USER_AGENT = "captive_portal_user_agent";
/**
@@ -10929,6 +10962,8 @@
*
* @hide
*/
+ @SystemApi
+ @TestApi
public static final String DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD =
"data_stall_consecutive_dns_timeout_threshold";
@@ -10937,6 +10972,8 @@
*
* @hide
*/
+ @SystemApi
+ @TestApi
public static final String DATA_STALL_MIN_EVALUATE_INTERVAL =
"data_stall_min_evaluate_interval";
@@ -10946,6 +10983,8 @@
*
* @hide
*/
+ @SystemApi
+ @TestApi
public static final String DATA_STALL_VALID_DNS_TIME_THRESHOLD =
"data_stall_valid_dns_time_threshold";
@@ -10955,6 +10994,8 @@
*
* @hide
*/
+ @SystemApi
+ @TestApi
public static final String DATA_STALL_EVALUATION_TYPE = "data_stall_evaluation_type";
/**
@@ -11103,6 +11144,10 @@
*/
public static final String PRIVATE_DNS_DEFAULT_MODE = "private_dns_default_mode";
+
+ /** {@hide} */
+ public static final String
+ BLUETOOTH_BTSNOOP_DEFAULT_MODE = "bluetooth_btsnoop_default_mode";
/** {@hide} */
public static final String
BLUETOOTH_HEADSET_PRIORITY_PREFIX = "bluetooth_headset_priority_";
@@ -11461,6 +11506,20 @@
public static final String DISPLAY_PANEL_LPM = "display_panel_lpm";
/**
+ * App time limit usage source setting.
+ * This controls which app in a task will be considered the source of usage when
+ * calculating app usage time limits.
+ *
+ * 1 -> task root app
+ * 2 -> current app
+ * Any other value defaults to task root app.
+ *
+ * Need to reboot the device for this setting to take effect.
+ * @hide
+ */
+ public static final String APP_TIME_LIMIT_USAGE_SOURCE = "app_time_limit_usage_source";
+
+ /**
* App standby (app idle) specific settings.
* This is encoded as a key=value list, separated by commas. Ex:
* <p>
@@ -11822,6 +11881,28 @@
public static final String KEEP_PROFILE_IN_BACKGROUND = "keep_profile_in_background";
/**
+ * The default time in ms within which a subsequent connection from an always allowed system
+ * is allowed to reconnect without user interaction.
+ *
+ * @hide
+ */
+ public static final long DEFAULT_ADB_ALLOWED_CONNECTION_TIME = 604800000;
+
+ /**
+ * When the user first connects their device to a system a prompt is displayed to allow
+ * the adb connection with an option to 'Always allow' connections from this system. If the
+ * user selects this always allow option then the connection time is stored for the system.
+ * This setting is the time in ms within which a subsequent connection from an always
+ * allowed system is allowed to reconnect without user interaction.
+ *
+ * Type: long
+ *
+ * @hide
+ */
+ public static final String ADB_ALLOWED_CONNECTION_TIME =
+ "adb_allowed_connection_time";
+
+ /**
* Get the key that retrieves a bluetooth headset's priority.
* @hide
*/
@@ -13883,11 +13964,12 @@
* The following keys are supported:
*
* <pre>
- * enabled (boolean)
- * requires_targeting_p (boolean)
- * max_squeeze_remeasure_attempts (int)
- * edit_choices_before_sending (boolean)
- * show_in_heads_up (boolean)
+ * enabled (boolean)
+ * requires_targeting_p (boolean)
+ * max_squeeze_remeasure_attempts (int)
+ * edit_choices_before_sending (boolean)
+ * show_in_heads_up (boolean)
+ * min_num_system_generated_replies (int)
* </pre>
* @see com.android.systemui.statusbar.policy.SmartReplyConstants
* @hide
@@ -14109,7 +14191,7 @@
* intervalMultiplier (int)
* </pre>
*
- * Ex: "enabled=true,baseIntervalMillis=1000,intervalMultiplier=10"
+ * Ex: "mode=HISTORICAL_MODE_ENABLED_ACTIVE,baseIntervalMillis=1000,intervalMultiplier=10"
*
* @see #APPOP_HISTORY_MODE
* @see #APPOP_HISTORY_BASE_INTERVAL_MILLIS
diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
index aaba85b..8e0f522 100644
--- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
+++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
@@ -67,7 +67,7 @@
private static final String TAG = AugmentedAutofillService.class.getSimpleName();
- // TODO(b/111330312): STOPSHIP use dynamic value, or change to false
+ // TODO(b/123100811): STOPSHIP use dynamic value, or change to false
static final boolean DEBUG = true;
static final boolean VERBOSE = false;
@@ -127,8 +127,6 @@
return false;
}
- // TODO(b/111330312): add methods to disable autofill per app / activity?
-
/**
* Asks the service to handle an "augmented" autofill request.
*
@@ -175,12 +173,11 @@
focusedValue, requestTime, callback);
mAutofillProxies.put(sessionId, proxy);
} else {
- // TODO(b/111330312): figure out if it's ok to reuse the proxy; add logging
- // TODO(b/111330312): also make sure to cover scenario on CTS test
+ // TODO(b/123099468): figure out if it's ok to reuse the proxy; add logging
if (DEBUG) Log.d(TAG, "Reusing proxy for session " + sessionId);
proxy.update(focusedId, focusedValue);
}
- // TODO(b/111330312): set cancellation signal
+ // TODO(b/123101711): set cancellation signal
final CancellationSignal cancellationSignal = null;
onFillRequest(new FillRequest(proxy), cancellationSignal, new FillController(proxy),
new FillCallback(proxy));
@@ -193,7 +190,7 @@
final int sessionId = mAutofillProxies.keyAt(i);
final AutofillProxy proxy = mAutofillProxies.valueAt(i);
if (proxy == null) {
- // TODO(b/111330312): this might be fine, in which case we should logv it
+ // TODO(b/123100811): this might be fine, in which case we should logv it
Log.w(TAG, "No proxy for session " + sessionId);
return;
}
@@ -303,7 +300,7 @@
this.mFocusedId = focusedId;
this.mFocusedValue = focusedValue;
this.mRequestTime = requestTime;
- // TODO(b/111330312): linkToDeath
+ // TODO(b/123099468): linkToDeath
}
@NonNull
@@ -366,7 +363,7 @@
private void update(@NonNull AutofillId focusedId, @NonNull AutofillValue focusedValue) {
synchronized (mLock) {
- // TODO(b/111330312): should we close the popupwindow if the focused id changed?
+ // TODO(b/123099468): should we close the popupwindow if the focused id changed?
mFocusedId = focusedId;
mFocusedValue = focusedValue;
}
@@ -425,7 +422,7 @@
default:
Slog.w(TAG, "invalid event reported: " + event);
}
- // TODO(b/111330312): log metrics as well
+ // TODO(b/122858578): log metrics as well
}
public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
diff --git a/core/java/android/service/autofill/augmented/FillCallback.java b/core/java/android/service/autofill/augmented/FillCallback.java
index bfb4aad..f2a7a35 100644
--- a/core/java/android/service/autofill/augmented/FillCallback.java
+++ b/core/java/android/service/autofill/augmented/FillCallback.java
@@ -59,7 +59,8 @@
if (fillWindow != null) {
fillWindow.show();
}
- // TODO(b/111330312): properly implement on server-side by updating the Session state
- // accordingly (and adding CTS tests)
+ // TODO(b/123099468): must notify the server so it can update the session state to avoid
+ // showing conflicting UIs (for example, if a new request is made to the main autofill
+ // service and it now wants to show something).
}
}
diff --git a/core/java/android/service/autofill/augmented/FillRequest.java b/core/java/android/service/autofill/augmented/FillRequest.java
index dad5067..af9905f 100644
--- a/core/java/android/service/autofill/augmented/FillRequest.java
+++ b/core/java/android/service/autofill/augmented/FillRequest.java
@@ -29,7 +29,7 @@
* @hide
*/
@SystemApi
-// TODO(b/111330312): pass a requestId and/or sessionId
+// TODO(b/123100811): pass a requestId and/or sessionId?
@TestApi
// TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
// in the same package as the test, and that module is compiled with SDK=test_current
diff --git a/core/java/android/service/autofill/augmented/FillResponse.java b/core/java/android/service/autofill/augmented/FillResponse.java
index 5285132..f1e904a 100644
--- a/core/java/android/service/autofill/augmented/FillResponse.java
+++ b/core/java/android/service/autofill/augmented/FillResponse.java
@@ -19,8 +19,6 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.view.autofill.AutofillId;
import java.util.List;
@@ -34,7 +32,7 @@
@TestApi
//TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
//in the same package as the test, and that module is compiled with SDK=test_current
-public final class FillResponse implements Parcelable {
+public final class FillResponse {
private final FillWindow mFillWindow;
@@ -70,8 +68,8 @@
* @return this builder
*/
public Builder setFillWindow(@NonNull FillWindow fillWindow) {
- // TODO(b/111330312): implement / check not null / unit test
- // TODO(b/111330312): throw exception if FillWindow not updated yet
+ // TODO(b/123100712): check not null / unit test / throw exception if FillWindow not
+ // updated yet
mFillWindow = fillWindow;
return this;
}
@@ -85,7 +83,7 @@
* @return this builder
*/
public Builder setIgnoredIds(@NonNull List<AutofillId> ids) {
- // TODO(b/111330312): implement / check not null / unit test
+ // TODO(b/123100695): implement / check not null / unit test
return this;
}
@@ -102,37 +100,10 @@
* @return A built response.
*/
public FillResponse build() {
- // TODO(b/111330312): check conditions / add unit test
+ // TODO(b/123100712): check conditions / add unit test
return new FillResponse(this);
}
-
- // TODO(b/111330312): add methods to disable app / activity, either here or on manager
}
- // TODO(b/111330312): implement to String
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel parcel, int flags) {
- // TODO(b/111330312): implement
- }
-
- public static final Parcelable.Creator<FillResponse> CREATOR =
- new Parcelable.Creator<FillResponse>() {
-
- @Override
- public FillResponse createFromParcel(Parcel parcel) {
- // TODO(b/111330312): implement
- return null;
- }
-
- @Override
- public FillResponse[] newArray(int size) {
- return new FillResponse[size];
- }
- };
+ // TODO(b/123100811): implement to String
}
diff --git a/core/java/android/service/autofill/augmented/FillWindow.java b/core/java/android/service/autofill/augmented/FillWindow.java
index 51b0f01..40e3a12 100644
--- a/core/java/android/service/autofill/augmented/FillWindow.java
+++ b/core/java/android/service/autofill/augmented/FillWindow.java
@@ -20,7 +20,6 @@
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-import android.annotation.LongDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.TestApi;
@@ -42,8 +41,6 @@
import dalvik.system.CloseGuard;
import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
/**
* Handle to a window used to display the augmented autofill UI.
@@ -71,18 +68,6 @@
public final class FillWindow implements AutoCloseable {
private static final String TAG = "FillWindow";
- /** Indicates the data being shown is a physical address */
- public static final long FLAG_METADATA_ADDRESS = 0x1;
-
- // TODO(b/111330312): add more flags
-
- /** @hide */
- @LongDef(prefix = { "FLAG" }, value = {
- FLAG_METADATA_ADDRESS,
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface Flags{}
-
private final Object mLock = new Object();
private final CloseGuard mCloseGuard = CloseGuard.get();
@@ -108,29 +93,22 @@
*
* @param rootView new root view
* @param area coordinates to render the view.
- * @param flags optional flags such as metadata of what will be rendered in the window. The
- * Smart Suggestion host might decide whether or not to render the UI based on them.
+ * @param flags currently not used.
*
* @return boolean whether the window was updated or not.
*
* @throws IllegalArgumentException if the area is not compatible with this window
*/
- public boolean update(@NonNull Area area, @NonNull View rootView, @Flags long flags) {
+ public boolean update(@NonNull Area area, @NonNull View rootView, long flags) {
if (DEBUG) {
Log.d(TAG, "Updating " + area + " + with " + rootView);
}
- // TODO(b/111330312): add test case for null
+ // TODO(b/123100712): add test case for null
Preconditions.checkNotNull(area);
Preconditions.checkNotNull(rootView);
- // TODO(b/111330312): must check the area is a valid object returned by
+ // TODO(b/123100712): must check the area is a valid object returned by
// SmartSuggestionParams, throw IAE if not
- // TODO(b/111330312): must some how pass metadata to the SmartSuggestiongs provider
-
-
- // TODO(b/111330312): use a SurfaceControl approach; for now, we're manually creating
- // the window underneath the existing view.
-
final PresentationParams smartSuggestion = area.proxy.getSmartSuggestionParams();
if (smartSuggestion == null) {
Log.w(TAG, "No SmartSuggestionParams");
@@ -148,12 +126,12 @@
mProxy = area.proxy;
- // TODO(b/111330312): once we have the SurfaceControl approach, we should update the
+ // TODO(b/123227534): once we have the SurfaceControl approach, we should update the
// window instead of destroying. In fact, it might be better to allocate a full window
// initially, which is transparent (and let touches get through) everywhere but in the
// rect boundaries.
- // TODO(b/111330312): make sure all touch events are handled, window is always closed,
+ // TODO(b/123099468): make sure all touch events are handled, window is always closed,
// etc.
mWm = rootView.getContext().getSystemService(WindowManager.class);
@@ -181,7 +159,7 @@
/** @hide */
void show() {
- // TODO(b/111330312): check if updated first / throw exception
+ // TODO(b/123100712): check if updated first / throw exception
if (DEBUG) Log.d(TAG, "show()");
synchronized (mLock) {
checkNotDestroyedLocked();
diff --git a/core/java/android/service/autofill/augmented/IFillCallback.aidl b/core/java/android/service/autofill/augmented/IFillCallback.aidl
index dac7590..2b072664 100644
--- a/core/java/android/service/autofill/augmented/IFillCallback.aidl
+++ b/core/java/android/service/autofill/augmented/IFillCallback.aidl
@@ -24,8 +24,7 @@
* @hide
*/
interface IFillCallback {
- // TODO(b/111330312): add cancellation (after we have CTS tests, so we can test it)
+ // TODO(b/123101711): add cancellation (after we have CTS tests, so we can test it)
// void onCancellable(in ICancellationSignal cancellation);
- // TODO(b/111330312): might need to pass the response (once IME implements Smart Suggestions)
void onSuccess();
}
diff --git a/core/java/android/service/autofill/augmented/PresentationParams.java b/core/java/android/service/autofill/augmented/PresentationParams.java
index b60064e..1fb9032 100644
--- a/core/java/android/service/autofill/augmented/PresentationParams.java
+++ b/core/java/android/service/autofill/augmented/PresentationParams.java
@@ -190,7 +190,7 @@
*/
@Nullable
public Area getSubArea(@NonNull Rect bounds) {
- // TODO(b/111330312): implement / check boundaries / throw IAE / add unit test
+ // TODO(b/123100712): implement / check boundaries / throw IAE / add unit test
return null;
}
diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java
index 4dc10cd..ffb524d 100644
--- a/core/java/android/service/euicc/EuiccService.java
+++ b/core/java/android/service/euicc/EuiccService.java
@@ -103,10 +103,23 @@
*/
public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS =
"android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
+
/** @see android.telephony.euicc.EuiccManager#ACTION_PROVISION_EMBEDDED_SUBSCRIPTION */
public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION =
"android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
+ /** @see android.telephony.euicc.EuiccManager#ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED */
+ public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED =
+ "android.service.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED";
+
+ /** @see android.telephony.euicc.EuiccManager#ACTION_DELETE_SUBSCRIPTION_PRIVILEGED */
+ public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED =
+ "android.service.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
+
+ /** @see android.telephony.euicc.EuiccManager#ACTION_RENAME_SUBSCRIPTION_PRIVILEGED */
+ public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED =
+ "android.service.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED";
+
// LUI resolution actions. These are called by the platform to resolve errors in situations that
// require user interaction.
// TODO(b/33075886): Define extras for any input parameters to these dialogs once they are
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index 63fd563..ad34ab3 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -173,7 +173,8 @@
}
/**
- * Implement this to know when a notification is expanded / collapsed.
+ * Implement this to know when a notification change (expanded / collapsed) is visible to user.
+ *
* @param key the notification key
* @param isUserAction whether the expanded change is caused by user action.
* @param isExpanded whether the notification is expanded.
diff --git a/core/java/android/service/vr/IVrManager.aidl b/core/java/android/service/vr/IVrManager.aidl
index f7acfc5..b0269e3 100644
--- a/core/java/android/service/vr/IVrManager.aidl
+++ b/core/java/android/service/vr/IVrManager.aidl
@@ -110,13 +110,5 @@
* @param standy True if the device is entering standby, false if it's exiting standby.
*/
void setStandbyEnabled(boolean standby);
-
- /**
- * Start VR Input method for the given packageName in {@param componentName}.
- * This method notifies InputMethodManagerService to use VR IME instead of
- * regular phone IME.
- */
- void setVrInputMethod(in ComponentName componentName);
-
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 0d7223d..b197c8a 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -784,8 +784,11 @@
// only internal implementations like ImageWallpaper
DisplayInfo displayInfo = new DisplayInfo();
mDisplay.getDisplayInfo(displayInfo);
- mLayout.width = Math.max(displayInfo.logicalWidth, myWidth);
- mLayout.height = Math.max(displayInfo.logicalHeight, myHeight);
+ final float layoutScale = Math.max(
+ (float) displayInfo.logicalHeight / (float) myHeight,
+ (float) displayInfo.logicalWidth / (float) myWidth);
+ mLayout.height = (int) (myHeight * layoutScale);
+ mLayout.width = (int) (myWidth * layoutScale);
mWindowFlags |= WindowManager.LayoutParams.FLAG_SCALED;
}
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
index 7a58681..dd073e9 100644
--- a/core/java/android/text/style/SuggestionSpan.java
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -21,7 +21,6 @@
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.content.Context;
-import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.Parcel;
@@ -31,7 +30,6 @@
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.Log;
-import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;
import java.util.Arrays;
@@ -72,9 +70,37 @@
*/
public static final int FLAG_AUTO_CORRECTION = 0x0004;
+ /**
+ * This action is deprecated in {@link android.os.Build.VERSION_CODES#Q}.
+ *
+ * @deprecated For IMEs to receive this kind of user interaction signals, implement IMEs' own
+ * suggestion picker UI instead of relying on {@link SuggestionSpan}. To retrieve
+ * bounding boxes for each character of the composing text, use
+ * {@link android.view.inputmethod.CursorAnchorInfo}.
+ */
+ @Deprecated
public static final String ACTION_SUGGESTION_PICKED = "android.text.style.SUGGESTION_PICKED";
+
+ /**
+ * This is deprecated in {@link android.os.Build.VERSION_CODES#Q}.
+ *
+ * @deprecated See {@link #ACTION_SUGGESTION_PICKED}.
+ */
+ @Deprecated
public static final String SUGGESTION_SPAN_PICKED_AFTER = "after";
+ /**
+ * This is deprecated in {@link android.os.Build.VERSION_CODES#Q}.
+ *
+ * @deprecated See {@link #ACTION_SUGGESTION_PICKED}.
+ */
+ @Deprecated
public static final String SUGGESTION_SPAN_PICKED_BEFORE = "before";
+ /**
+ * This is deprecated in {@link android.os.Build.VERSION_CODES#Q}.
+ *
+ * @deprecated See {@link #ACTION_SUGGESTION_PICKED}.
+ */
+ @Deprecated
public static final String SUGGESTION_SPAN_PICKED_HASHCODE = "hashcode";
public static final int SUGGESTIONS_MAX_SIZE = 5;
@@ -97,8 +123,6 @@
private final String mLocaleStringForCompatibility;
@NonNull
private final String mLanguageTag;
- private final String mNotificationTargetClassName;
- private final String mNotificationTargetPackageName;
private final int mHashCode;
@UnsupportedAppUsage
@@ -137,7 +161,9 @@
* {@link SuggestionSpan#SUGGESTIONS_MAX_SIZE} will be considered. Null values not permitted.
* @param flags Additional flags indicating how this span is handled in TextView
* @param notificationTargetClass if not null, this class will get notified when the user
- * selects one of the suggestions.
+ * selects one of the suggestions. On Android
+ * {@link android.os.Build.VERSION_CODES#Q} and later this
+ * parameter is always ignored.
*/
public SuggestionSpan(Context context, Locale locale, String[] suggestions, int flags,
Class<?> notificationTargetClass) {
@@ -156,20 +182,7 @@
}
mLocaleStringForCompatibility = sourceLocale == null ? "" : sourceLocale.toString();
mLanguageTag = sourceLocale == null ? "" : sourceLocale.toLanguageTag();
-
- if (context != null) {
- mNotificationTargetPackageName = context.getPackageName();
- } else {
- mNotificationTargetPackageName = null;
- }
-
- if (notificationTargetClass != null) {
- mNotificationTargetClassName = notificationTargetClass.getCanonicalName();
- } else {
- mNotificationTargetClassName = "";
- }
- mHashCode = hashCodeInternal(mSuggestions, mLanguageTag, mLocaleStringForCompatibility,
- mNotificationTargetClassName);
+ mHashCode = hashCodeInternal(mSuggestions, mLanguageTag, mLocaleStringForCompatibility);
initStyle(context);
}
@@ -215,8 +228,6 @@
mFlags = src.readInt();
mLocaleStringForCompatibility = src.readString();
mLanguageTag = src.readString();
- mNotificationTargetClassName = src.readString();
- mNotificationTargetPackageName = src.readString();
mHashCode = src.readInt();
mEasyCorrectUnderlineColor = src.readInt();
mEasyCorrectUnderlineThickness = src.readFloat();
@@ -260,17 +271,15 @@
}
/**
- * @return The name of the class to notify. The class of the original IME package will receive
- * a notification when the user selects one of the suggestions. The notification will include
- * the original string, the suggested replacement string as well as the hashCode of this span.
- * The class will get notified by an intent that has those information.
- * This is an internal API because only the framework should know the class name.
+ * @return {@code null}.
*
* @hide
+ * @deprecated Do not use. Always returns {@code null}.
*/
+ @Deprecated
@UnsupportedAppUsage
public String getNotificationTargetClassName() {
- return mNotificationTargetClassName;
+ return null;
}
public int getFlags() {
@@ -297,8 +306,6 @@
dest.writeInt(mFlags);
dest.writeString(mLocaleStringForCompatibility);
dest.writeString(mLanguageTag);
- dest.writeString(mNotificationTargetClassName);
- dest.writeString(mNotificationTargetPackageName);
dest.writeInt(mHashCode);
dest.writeInt(mEasyCorrectUnderlineColor);
dest.writeFloat(mEasyCorrectUnderlineThickness);
@@ -332,9 +339,9 @@
}
private static int hashCodeInternal(String[] suggestions, @NonNull String languageTag,
- @NonNull String localeStringForCompatibility, String notificationTargetClassName) {
+ @NonNull String localeStringForCompatibility) {
return Arrays.hashCode(new Object[] {Long.valueOf(SystemClock.uptimeMillis()), suggestions,
- languageTag, localeStringForCompatibility, notificationTargetClassName});
+ languageTag, localeStringForCompatibility});
}
public static final Parcelable.Creator<SuggestionSpan> CREATOR =
@@ -390,39 +397,14 @@
}
/**
- * Notifies a suggestion selection.
+ * Does nothing.
*
+ * @deprecated this is deprecated in {@link android.os.Build.VERSION_CODES#Q}.
* @hide
*/
@UnsupportedAppUsage
+ @Deprecated
public void notifySelection(Context context, String original, int index) {
- final Intent intent = new Intent();
-
- if (context == null || mNotificationTargetClassName == null) {
- return;
- }
- // Ensures that only a class in the original IME package will receive the
- // notification.
- if (mSuggestions == null || index < 0 || index >= mSuggestions.length) {
- Log.w(TAG, "Unable to notify the suggestion as the index is out of range index=" + index
- + " length=" + mSuggestions.length);
- return;
- }
-
- // The package name is not mandatory (legacy from JB), and if the package name
- // is missing, we try to notify the suggestion through the input method manager.
- if (mNotificationTargetPackageName != null) {
- intent.setClassName(mNotificationTargetPackageName, mNotificationTargetClassName);
- intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED);
- intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, original);
- intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, mSuggestions[index]);
- intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, hashCode());
- context.sendBroadcast(intent);
- } else {
- InputMethodManager imm = context.getSystemService(InputMethodManager.class);
- if (imm != null) {
- imm.notifySuggestionPicked(this, original, index);
- }
- }
+ Log.w(TAG, "notifySelection() is deprecated. Does nothing.");
}
}
diff --git a/core/java/android/util/DocumentsStatsLog.java b/core/java/android/util/DocumentsStatsLog.java
new file mode 100644
index 0000000..f483944
--- /dev/null
+++ b/core/java/android/util/DocumentsStatsLog.java
@@ -0,0 +1,168 @@
+/*
+ * 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 android.annotation.SystemApi;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsProvider;
+
+/**
+ * DocumentsStatsLog provides APIs to send DocumentsUI related events to statsd.
+ * @hide
+ */
+@SystemApi
+public class DocumentsStatsLog {
+
+ private DocumentsStatsLog() {}
+
+ /**
+ * Logs when DocumentsUI is started, and how. Call this when DocumentsUI first starts up.
+ *
+ * @param action action that launches DocumentsUI.
+ * @param hasInitialUri is DocumentsUI launched with
+ * {@link DocumentsContract#EXTRA_INITIAL_URI}.
+ * @param mimeType the requested mime type.
+ * @param rootUri the resolved rootUri, or {@code null} if the provider doesn't
+ * support {@link DocumentsProvider#findDocumentPath(String, String)}
+ */
+ public static void logActivityLaunch(
+ int action, boolean hasInitialUri, int mimeType, int rootUri) {
+ StatsLog.write(StatsLog.DOCS_UI_LAUNCH_REPORTED, action, hasInitialUri, mimeType, rootUri);
+ }
+
+ /**
+ * Logs root visited event.
+ *
+ * @param scope whether it's in FILES or PICKER mode.
+ * @param root the root that user visited
+ */
+ public static void logRootVisited(int scope, int root) {
+ StatsLog.write(StatsLog.DOCS_UI_ROOT_VISITED, scope, root);
+ }
+
+ /**
+ * Logs file operation stats. Call this when a file operation has completed.
+ *
+ * @param provider whether it's system or external provider
+ * @param fileOp the file operation
+ */
+ public static void logFileOperation(int provider, int fileOp) {
+ StatsLog.write(StatsLog.DOCS_UI_PROVIDER_FILE_OP, provider, fileOp);
+ }
+
+ /**
+ * Logs file operation stats. Call this when a copy/move operation has completed with a specific
+ * mode.
+ *
+ * @param fileOp copy or move file operation
+ * @param mode the mode for copy and move operation
+ */
+ public static void logFileOperationCopyMoveMode(int fileOp, int mode) {
+ StatsLog.write(StatsLog.DOCS_UI_FILE_OP_COPY_MOVE_MODE_REPORTED, fileOp, mode);
+ }
+
+ /**
+ * Logs file sub operation stats. Call this when a file operation has failed.
+ *
+ * @param authority the authority of the source document
+ * @param subOp the sub-file operation
+ */
+ public static void logFileOperationFailure(int authority, int subOp) {
+ StatsLog.write(StatsLog.DOCS_UI_FILE_OP_FAILURE, authority, subOp);
+ }
+
+ /**
+ * Logs the cancellation of a file operation. Call this when a job is canceled
+ *
+ * @param fileOp the file operation.
+ */
+ public static void logFileOperationCanceled(int fileOp) {
+ StatsLog.write(StatsLog.DOCS_UI_FILE_OP_CANCELED, fileOp);
+ }
+
+ /**
+ * Logs startup time in milliseconds.
+ *
+ * @param startupMs
+ */
+ public static void logStartupMs(int startupMs) {
+ StatsLog.write(StatsLog.DOCS_UI_STARTUP_MS, startupMs);
+ }
+
+ /**
+ * Logs the action that was started by user.
+ *
+ * @param userAction
+ */
+ public static void logUserAction(int userAction) {
+ StatsLog.write(StatsLog.DOCS_UI_USER_ACTION_REPORTED, userAction);
+ }
+
+ /**
+ * Logs the invalid type when invalid scoped access is requested.
+ *
+ * @param type the type of invalid scoped access request.
+ */
+ public static void logInvalidScopedAccessRequest(int type) {
+ StatsLog.write(StatsLog.DOCS_UI_INVALID_SCOPED_ACCESS_REQUEST, type);
+ }
+
+ /**
+ * Logs the package name that launches docsui picker mode.
+ *
+ * @param packageName
+ */
+ public static void logPickerLaunchedFrom(String packageName) {
+ StatsLog.write(StatsLog.DOCS_UI_PICKER_LAUNCHED_FROM_REPORTED, packageName);
+ }
+
+ /**
+ * Logs the search type.
+ *
+ * @param searchType
+ */
+ public static void logSearchType(int searchType) {
+ StatsLog.write(StatsLog.DOCS_UI_SEARCH_TYPE_REPORTED, searchType);
+ }
+
+ /**
+ * Logs the search mode.
+ *
+ * @param searchMode
+ */
+ public static void logSearchMode(int searchMode) {
+ StatsLog.write(StatsLog.DOCS_UI_SEARCH_MODE_REPORTED, searchMode);
+ }
+
+ /**
+ * Logs the pick result information.
+ *
+ * @param actionCount total user action count during pick process.
+ * @param duration total time spent on pick process.
+ * @param fileCount number of picked files.
+ * @param isSearching are the picked files found by search.
+ * @param root the root where the picked files located.
+ * @param mimeType the mime type of the picked file. Only for single-select case.
+ * @param repeatedlyPickTimes number of times that the file has been picked before. Only for
+ * single-select case.
+ */
+ public static void logFilePick(int actionCount, long duration, int fileCount,
+ boolean isSearching, int root, int mimeType, int repeatedlyPickTimes) {
+ StatsLog.write(StatsLog.DOCS_UI_PICK_RESULT_REPORTED, actionCount, duration, fileCount,
+ isSearching, root, mimeType, repeatedlyPickTimes);
+ }
+}
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index 33b3ff4f..7d9ec70 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -237,6 +237,16 @@
private static final int LONG_PRESS = 2;
private static final int TAP = 3;
+ /**
+ * If a MotionEvent has CLASSIFICATION_AMBIGUOUS_GESTURE set, then certain actions, such as
+ * scrolling, will be inhibited. However, to account for the possibility of incorrect
+ * classification, the default scrolling will only be inhibited if the gesture moves beyond
+ * (default touch slop * AMBIGUOUS_GESTURE_MULTIPLIER). Likewise, the default long press
+ * timeout will be increased for some situations where the default behaviour
+ * is to cancel it.
+ */
+ private static final int AMBIGUOUS_GESTURE_MULTIPLIER = 2;
+
private final Handler mHandler;
@UnsupportedAppUsage
private final OnGestureListener mListener;
@@ -292,27 +302,27 @@
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case SHOW_PRESS:
- mListener.onShowPress(mCurrentDownEvent);
- break;
-
- case LONG_PRESS:
- dispatchLongPress();
- break;
-
- case TAP:
- // If the user's finger is still down, do not count it as a tap
- if (mDoubleTapListener != null) {
- if (!mStillDown) {
- mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
- } else {
- mDeferConfirmSingleTap = true;
- }
- }
- break;
+ case SHOW_PRESS:
+ mListener.onShowPress(mCurrentDownEvent);
+ break;
- default:
- throw new RuntimeException("Unknown message " + msg); //never
+ case LONG_PRESS:
+ dispatchLongPress();
+ break;
+
+ case TAP:
+ // If the user's finger is still down, do not count it as a tap
+ if (mDoubleTapListener != null) {
+ if (!mStillDown) {
+ mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
+ } else {
+ mDeferConfirmSingleTap = true;
+ }
+ }
+ break;
+
+ default:
+ throw new RuntimeException("Unknown message " + msg); //never
}
}
}
@@ -427,7 +437,7 @@
if (context == null) {
//noinspection deprecation
touchSlop = ViewConfiguration.getTouchSlop();
- doubleTapTouchSlop = touchSlop; // Hack rather than adding a hiden method for this
+ doubleTapTouchSlop = touchSlop; // Hack rather than adding a hidden method for this
doubleTapSlop = ViewConfiguration.getDoubleTapSlop();
//noinspection deprecation
mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
@@ -605,6 +615,10 @@
if (mInLongPress || mInContextClick) {
break;
}
+
+ final int motionClassification = ev.getClassification();
+ final boolean hasPendingLongPress = mHandler.hasMessages(LONG_PRESS);
+
final float scrollX = mLastFocusX - focusX;
final float scrollY = mLastFocusY - focusY;
if (mIsDoubleTapping) {
@@ -615,6 +629,31 @@
final int deltaY = (int) (focusY - mDownFocusY);
int distance = (deltaX * deltaX) + (deltaY * deltaY);
int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare;
+
+ final boolean ambiguousGesture =
+ motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
+ final boolean shouldInhibitDefaultAction =
+ hasPendingLongPress && ambiguousGesture;
+ if (shouldInhibitDefaultAction) {
+ // Inhibit default long press
+ if (distance > slopSquare) {
+ // The default action here is to remove long press. But if the touch
+ // slop below gets increased, and we never exceed the modified touch
+ // slop while still receiving AMBIGUOUS_GESTURE, we risk that *nothing*
+ // will happen in response to user input. To prevent this,
+ // reschedule long press with a modified timeout.
+ mHandler.removeMessages(LONG_PRESS);
+ final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
+ mHandler.sendEmptyMessageAtTime(LONG_PRESS, ev.getDownTime()
+ + longPressTimeout * AMBIGUOUS_GESTURE_MULTIPLIER);
+ }
+ // Inhibit default scroll. If a gesture is ambiguous, we prevent scroll
+ // until the gesture is resolved.
+ // However, for safety, simply increase the touch slop in case the
+ // classification is erroneous. Since the value is squared, multiply twice.
+ slopSquare *= AMBIGUOUS_GESTURE_MULTIPLIER * AMBIGUOUS_GESTURE_MULTIPLIER;
+ }
+
if (distance > slopSquare) {
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
mLastFocusX = focusX;
@@ -633,6 +672,12 @@
mLastFocusX = focusX;
mLastFocusY = focusY;
}
+ final boolean deepPress =
+ motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
+ if (deepPress && hasPendingLongPress) {
+ mHandler.removeMessages(LONG_PRESS);
+ mHandler.sendEmptyMessage(LONG_PRESS);
+ }
break;
case MotionEvent.ACTION_UP:
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 330d72f..42ac880 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -51,6 +51,7 @@
import android.view.AppTransitionAnimationSpec;
import android.view.WindowContentFrameStats;
import android.view.WindowManager;
+import android.view.SurfaceControl;
/**
* System private interface to the window manager.
@@ -555,8 +556,8 @@
* display content info to any SurfaceControl, as this would be a security issue.
*
* @param displayId The id of the display.
- * @param surfaceControlHandle The SurfaceControl handle that the top level layers for the
+ * @param surfaceControlHandle The SurfaceControl that the top level layers for the
* display should be re-parented to.
*/
- void reparentDisplayContent(int displayId, in IBinder surfaceControlHandle);
+ void reparentDisplayContent(int displayId, in SurfaceControl sc);
}
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index ce71b07..7291d0b 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -122,7 +122,8 @@
mController.scheduleApplyChangeInsets();
}
- void applyChangeInsets(InsetsState state) {
+ @VisibleForTesting
+ public void applyChangeInsets(InsetsState state) {
final Insets offset = Insets.subtract(mShownInsets, mPendingInsets);
ArrayList<SurfaceParams> params = new ArrayList<>();
if (offset.left != 0) {
@@ -161,8 +162,9 @@
private Insets getInsetsFromState(InsetsState state, Rect frame,
@Nullable @InsetSide SparseIntArray typeSideMap) {
return state.calculateInsets(frame, false /* isScreenRound */,
- false /* alwaysConsumerNavBar */, null /* displayCutout */, typeSideMap)
- .getInsets(mTypes);
+ false /* alwaysConsumerNavBar */, null /* displayCutout */,
+ null /* legacyContentInsets */, null /* legacyStableInsets */, typeSideMap)
+ .getInsets(mTypes);
}
private Insets sanitize(Insets insets) {
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index c2ade76..dd6231d 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -56,6 +56,9 @@
private final Runnable mAnimCallback;
+ private final Rect mLastLegacyContentInsets = new Rect();
+ private final Rect mLastLegacyStableInsets = new Rect();
+
public InsetsController(ViewRootImpl viewRoot) {
mViewRoot = viewRoot;
mAnimCallback = () -> {
@@ -70,6 +73,7 @@
}
WindowInsets insets = state.calculateInsets(mFrame, mLastInsets.isRound(),
mLastInsets.shouldAlwaysConsumeNavBar(), mLastInsets.getDisplayCutout(),
+ mLastLegacyContentInsets, mLastLegacyStableInsets,
null /* typeSideMap */);
mViewRoot.mView.dispatchWindowInsetsAnimationProgress(insets);
};
@@ -102,8 +106,12 @@
*/
@VisibleForTesting
public WindowInsets calculateInsets(boolean isScreenRound,
- boolean alwaysConsumeNavBar, DisplayCutout cutout) {
+ boolean alwaysConsumeNavBar, DisplayCutout cutout, Rect legacyContentInsets,
+ Rect legacyStableInsets) {
+ mLastLegacyContentInsets.set(legacyContentInsets);
+ mLastLegacyStableInsets.set(legacyStableInsets);
mLastInsets = mState.calculateInsets(mFrame, isScreenRound, alwaysConsumeNavBar, cutout,
+ legacyContentInsets, legacyStableInsets,
null /* typeSideMap */);
return mLastInsets;
}
@@ -223,15 +231,18 @@
mState.dump(prefix + " ", pw);
}
- void dispatchAnimationStarted(WindowInsetsAnimationListener.InsetsAnimation animation) {
+ @VisibleForTesting
+ public void dispatchAnimationStarted(WindowInsetsAnimationListener.InsetsAnimation animation) {
mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation);
}
- void dispatchAnimationFinished(WindowInsetsAnimationListener.InsetsAnimation animation) {
+ @VisibleForTesting
+ public void dispatchAnimationFinished(WindowInsetsAnimationListener.InsetsAnimation animation) {
mViewRoot.mView.dispatchWindowInsetsAnimationFinished(animation);
}
- void scheduleApplyChangeInsets() {
+ @VisibleForTesting
+ public void scheduleApplyChangeInsets() {
if (!mAnimCallbackScheduled) {
mViewRoot.mChoreographer.postCallback(Choreographer.CALLBACK_INSETS_ANIMATION,
mAnimCallback, null /* token*/);
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index cf8c0707..529776e 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME;
import static android.view.WindowInsets.Type.indexOf;
import android.annotation.IntDef;
@@ -119,11 +120,17 @@
*/
public WindowInsets calculateInsets(Rect frame, boolean isScreenRound,
boolean alwaysConsumeNavBar, DisplayCutout cutout,
+ @Nullable Rect legacyContentInsets, @Nullable Rect legacyStableInsets,
@Nullable @InsetSide SparseIntArray typeSideMap) {
Insets[] typeInsetsMap = new Insets[Type.SIZE];
Insets[] typeMaxInsetsMap = new Insets[Type.SIZE];
final Rect relativeFrame = new Rect(frame);
final Rect relativeFrameMax = new Rect(frame);
+ if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_IME
+ && legacyContentInsets != null && legacyStableInsets != null) {
+ WindowInsets.assignCompatInsets(typeInsetsMap, legacyContentInsets);
+ WindowInsets.assignCompatInsets(typeMaxInsetsMap, legacyStableInsets);
+ }
for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
InsetsSource source = mSources.get(type);
if (source == null) {
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index f3cb376..7fcb2af 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -185,6 +185,18 @@
}
/**
+ * Create a Surface assosciated with a given {@link SurfaceControl}. Buffers submitted to this
+ * surface will be displayed by the system compositor according to the parameters
+ * specified by the control. Multiple surfaces may be constructed from one SurfaceControl,
+ * but only one can be connected (e.g. have an active EGL context) at a time.
+ *
+ * @param from The SurfaceControl to assosciate this Surface with
+ */
+ public Surface(SurfaceControl from) {
+ copyFrom(from);
+ }
+
+ /**
* Create Surface from a {@link SurfaceTexture}.
*
* Images drawn to the Surface will be made available to the {@link
@@ -494,7 +506,6 @@
* in to it.
*
* @param other {@link SurfaceControl} to copy from.
- *
* @hide
*/
@UnsupportedAppUsage
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 5e98236..863b717 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -27,6 +27,10 @@
import static android.view.SurfaceControlProto.HASH_CODE;
import static android.view.SurfaceControlProto.NAME;
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.Size;
import android.annotation.UnsupportedAppUsage;
import android.graphics.Bitmap;
@@ -58,10 +62,16 @@
import java.io.Closeable;
/**
- * SurfaceControl
- * @hide
+ * Handle to an on-screen Surface managed by the system compositor. The SurfaceControl is
+ * a combination of a buffer source, and metadata about how to display the buffers.
+ * By constructing a {@link Surface} from this SurfaceControl you can submit buffers to be
+ * composited. Using {@link SurfaceControl.Transaction} you can manipulate various
+ * properties of how the buffer will be displayed on-screen. SurfaceControl's are
+ * arranged into a scene-graph like hierarchy, and as such any SurfaceControl may have
+ * a parent. Geometric properties like transform, crop, and Z-ordering will be inherited
+ * from the parent, as if the child were content in the parents buffer stream.
*/
-public class SurfaceControl implements Parcelable {
+public final class SurfaceControl implements Parcelable {
private static final String TAG = "SurfaceControl";
private static native long nativeCreate(SurfaceSession session, String name,
@@ -103,6 +113,8 @@
float dtdy, float dsdy);
private static native void nativeSetColorTransform(long transactionObj, long nativeObject,
float[] matrix, float[] translation);
+ private static native void nativeSetGeometry(long transactionObj, long nativeObject,
+ Rect sourceCrop, Rect dest, long orientation);
private static native void nativeSetColor(long transactionObj, long nativeObject, float[] color);
private static native void nativeSetFlags(long transactionObj, long nativeObject,
int flags, int mask);
@@ -156,7 +168,7 @@
private static native void nativeReparentChildren(long transactionObj, long nativeObject,
IBinder handle);
private static native void nativeReparent(long transactionObj, long nativeObject,
- IBinder parentHandle);
+ long newParentNativeObject);
private static native void nativeSeverChildren(long transactionObj, long nativeObject);
private static native void nativeSetOverrideScalingMode(long transactionObj, long nativeObject,
int scalingMode);
@@ -331,8 +343,7 @@
*/
public static final int BUILT_IN_DISPLAY_ID_HDMI = 1;
- /* Display power modes * /
-
+ // Display power modes.
/**
* Display power mode off: used while blanking the screen.
* Use only with {@link SurfaceControl#setDisplayPowerMode}.
@@ -403,7 +414,6 @@
/**
* Builder class for {@link SurfaceControl} objects.
- * @hide
*/
public static class Builder {
private SurfaceSession mSession;
@@ -427,8 +437,14 @@
}
/**
- * Construct a new {@link SurfaceControl} with the set parameters.
- * @hide
+ * Begin building a SurfaceControl.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Construct a new {@link SurfaceControl} with the set parameters. The builder
+ * remains valid.
*/
public SurfaceControl build() {
if (mWidth < 0 || mHeight < 0) {
@@ -447,7 +463,6 @@
* Set a debugging-name for the SurfaceControl.
*
* @param name A name to identify the Surface in debugging.
- * @hide
*/
public Builder setName(String name) {
mName = name;
@@ -459,9 +474,9 @@
*
* @param width The buffer width in pixels.
* @param height The buffer height in pixels.
- * @hide
*/
- public Builder setBufferSize(int width, int height) {
+ public Builder setBufferSize(@IntRange(from = 0) int width,
+ @IntRange(from = 0) int height) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException(
"width and height must be positive");
@@ -474,8 +489,8 @@
/**
* Set the pixel format of the controlled surface's buffers, using constants from
* {@link android.graphics.PixelFormat}.
- * @hide
*/
+ @NonNull
public Builder setFormat(@PixelFormat.Format int format) {
mFormat = format;
return this;
@@ -490,6 +505,7 @@
* @param protectedContent Whether to require a protected sink.
* @hide
*/
+ @NonNull
public Builder setProtected(boolean protectedContent) {
if (protectedContent) {
mFlags |= PROTECTED_APP;
@@ -506,6 +522,7 @@
* not a complete prevention of readback as {@link #setProtected}.
* @hide
*/
+ @NonNull
public Builder setSecure(boolean secure) {
if (secure) {
mFlags |= SECURE;
@@ -537,8 +554,8 @@
* If the underlying buffer lacks an alpha channel, it is as if setOpaque(true)
* were set automatically.
* @param opaque Whether the Surface is OPAQUE.
- * @hide
*/
+ @NonNull
public Builder setOpaque(boolean opaque) {
if (opaque) {
mFlags |= OPAQUE;
@@ -556,9 +573,9 @@
* of the parent.
*
* @param parent The parent control.
- * @hide
*/
- public Builder setParent(SurfaceControl parent) {
+ @NonNull
+ public Builder setParent(@Nullable SurfaceControl parent) {
mParent = parent;
return this;
}
@@ -673,9 +690,6 @@
private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags,
SurfaceControl parent, int windowType, int ownerUid)
throws OutOfResourcesException, IllegalArgumentException {
- if (session == null) {
- throw new IllegalArgumentException("session must not be null");
- }
if (name == null) {
throw new IllegalArgumentException("name must not be null");
}
@@ -729,9 +743,6 @@
mCloseGuard.open("release");
}
- /**
- * @hide
- */
public void readFromParcel(Parcel in) {
if (in == null) {
throw new IllegalArgumentException("source must not be null");
@@ -748,17 +759,11 @@
assignNativeObject(object);
}
- /**
- * @hide
- */
@Override
public int describeContents() {
return 0;
}
- /**
- * @hide
- */
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mName);
@@ -791,9 +796,6 @@
proto.end(token);
}
- /**
- * @hide
- */
public static final Creator<SurfaceControl> CREATOR
= new Creator<SurfaceControl>() {
public SurfaceControl createFromParcel(Parcel in) {
@@ -823,10 +825,12 @@
}
/**
- * Release the local reference to the server-side surface.
- * Always call release() when you're done with a Surface.
- * This will make the surface invalid.
- * @hide
+ * Release the local reference to the server-side surface. The surface
+ * may continue to exist on-screen as long as its parent continues
+ * to exist. To explicitly remove a surface from the screen use
+ * {@link Transaction#reparent} with a null-parent.
+ *
+ * Always call release() when you're done with a SurfaceControl.
*/
public void release() {
if (mNativeObject != 0) {
@@ -866,7 +870,10 @@
}
/**
- * @hide
+ * Check whether this instance points to a valid layer with the system-compositor. For
+ * example this may be false if construction failed, or the layer was released.
+ *
+ * @return Whether this SurfaceControl is valid.
*/
public boolean isValid() {
return mNativeObject != 0;
@@ -962,9 +969,9 @@
/**
* @hide
*/
- public void reparent(IBinder newParentHandle) {
+ public void reparent(SurfaceControl newParent) {
synchronized(SurfaceControl.class) {
- sGlobalTransaction.reparent(this, newParentHandle);
+ sGlobalTransaction.reparent(this, newParent);
}
}
@@ -1270,9 +1277,6 @@
}
}
- /**
- * @hide
- */
@Override
public String toString() {
return "Surface(name=" + mName + ")/@0x" +
@@ -1286,6 +1290,7 @@
/**
* Describes the properties of a physical display known to surface flinger.
+ * @hide
*/
public static final class PhysicalDisplayInfo {
/**
@@ -1777,9 +1782,12 @@
}
/**
- * @hide
+ * An atomic set of changes to a set of SurfaceControl.
*/
public static class Transaction implements Closeable {
+ /**
+ * @hide
+ */
public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
Transaction.class.getClassLoader(),
nativeGetNativeTransactionFinalizer(), 512);
@@ -1789,9 +1797,12 @@
Runnable mFreeNativeResources;
/**
- * @hide
+ * Open a new transaction object. The transaction may be filed with commands to
+ * manipulate {@link SurfaceControl} instances, and then applied atomically with
+ * {@link #apply}. Eventually the user should invoke {@link #close}, when the object
+ * is no longer required. Note however that re-using a transaction after a call to apply
+ * is allowed as a convenience.
*/
- @UnsupportedAppUsage
public Transaction() {
mNativeObject = nativeCreateTransaction();
mFreeNativeResources
@@ -1801,9 +1812,7 @@
/**
* Apply the transaction, clearing it's state, and making it usable
* as a new transaction.
- * @hide
*/
- @UnsupportedAppUsage
public void apply() {
apply(false);
}
@@ -1811,7 +1820,6 @@
/**
* Close the transaction, if the transaction was not already applied this will cancel the
* transaction.
- * @hide
*/
@Override
public void close() {
@@ -1841,6 +1849,27 @@
}
/**
+ * Toggle the visibility of a given Layer and it's sub-tree.
+ *
+ * @param sc The SurfaceControl for which to set the visibility
+ * @param visible The new visibility
+ * @return This transaction object.
+ */
+ @NonNull
+ public Transaction setVisibility(@NonNull SurfaceControl sc, boolean visible) {
+ sc.checkNotReleased();
+ if (visible) {
+ return show(sc);
+ } else {
+ return hide(sc);
+ }
+ }
+
+ /**
+ * Request that a given surface and it's sub-tree be shown.
+ *
+ * @param sc The surface to show.
+ * @return This transaction.
* @hide
*/
@UnsupportedAppUsage
@@ -1851,6 +1880,10 @@
}
/**
+ * Request that a given surface and it's sub-tree be hidden.
+ *
+ * @param sc The surface to hidden.
+ * @return This transaction.
* @hide
*/
@UnsupportedAppUsage
@@ -1871,10 +1904,17 @@
}
/**
- * @hide
+ * Set the default buffer size for the SurfaceControl, if there is an
+ * {@link Surface} assosciated with the control, then
+ * this will be the default size for buffers dequeued from it.
+ * @param sc The surface to set the buffer size for.
+ * @param w The default width
+ * @param h The default height
+ * @return This Transaction
*/
- @UnsupportedAppUsage
- public Transaction setBufferSize(SurfaceControl sc, int w, int h) {
+ @NonNull
+ public Transaction setBufferSize(@NonNull SurfaceControl sc,
+ @IntRange(from = 0) int w, @IntRange(from = 0) int h) {
sc.checkNotReleased();
mResizedSurfaces.put(sc, new Point(w, h));
nativeSetSize(mNativeObject, sc.mNativeObject, w, h);
@@ -1882,10 +1922,17 @@
}
/**
- * @hide
+ * Set the Z-order for a given SurfaceControl, relative to it's siblings.
+ * If two siblings share the same Z order the ordering is undefined. Surfaces
+ * with a negative Z will be placed below the parent surface.
+ *
+ * @param sc The SurfaceControl to set the Z order on
+ * @param z The Z-order
+ * @return This Transaction.
*/
- @UnsupportedAppUsage
- public Transaction setLayer(SurfaceControl sc, int z) {
+ @NonNull
+ public Transaction setLayer(@NonNull SurfaceControl sc,
+ @IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE) int z) {
sc.checkNotReleased();
nativeSetLayer(mNativeObject, sc.mNativeObject, z);
return this;
@@ -1912,10 +1959,15 @@
}
/**
- * @hide
+ * Set the alpha for a given surface. If the alpha is non-zero the SurfaceControl
+ * will be blended with the Surfaces under it according to the specified ratio.
+ *
+ * @param sc The given SurfaceControl.
+ * @param alpha The alpha to set.
*/
- @UnsupportedAppUsage
- public Transaction setAlpha(SurfaceControl sc, float alpha) {
+ @NonNull
+ public Transaction setAlpha(@NonNull SurfaceControl sc,
+ @FloatRange(from = 0.0, to = 1.0) float alpha) {
sc.checkNotReleased();
nativeSetAlpha(mNativeObject, sc.mNativeObject, alpha);
return this;
@@ -1947,6 +1999,25 @@
}
/**
+ * Specify how the buffer assosciated with this Surface is mapped in to the
+ * parent coordinate space. The source frame will be scaled to fit the destination
+ * frame, after being rotated according to the orientation parameter.
+ *
+ * @param sc The SurfaceControl to specify the geometry of
+ * @param sourceCrop The source rectangle in buffer space. Or null for the entire buffer.
+ * @param destFrame The destination rectangle in parent space. Or null for the source frame.
+ * @param orientation The buffer rotation
+ * @return This transaction object.
+ */
+ @NonNull
+ public Transaction setGeometry(@NonNull SurfaceControl sc, @Nullable Rect sourceCrop,
+ @Nullable Rect destFrame, @Surface.Rotation int orientation) {
+ sc.checkNotReleased();
+ nativeSetGeometry(mNativeObject, sc.mNativeObject, sourceCrop, destFrame, orientation);
+ return this;
+ }
+
+ /**
* @hide
*/
@UnsupportedAppUsage
@@ -2023,20 +2094,20 @@
return this;
}
- @UnsupportedAppUsage
/**
* @hide
*/
+ @UnsupportedAppUsage
public Transaction setLayerStack(SurfaceControl sc, int layerStack) {
sc.checkNotReleased();
nativeSetLayerStack(mNativeObject, sc.mNativeObject, layerStack);
return this;
}
- @UnsupportedAppUsage
/**
* @hide
*/
+ @UnsupportedAppUsage
public Transaction deferTransactionUntil(SurfaceControl sc, IBinder handle,
long frameNumber) {
if (frameNumber < 0) {
@@ -2047,10 +2118,10 @@
return this;
}
- @UnsupportedAppUsage
/**
* @hide
*/
+ @UnsupportedAppUsage
public Transaction deferTransactionUntilSurface(SurfaceControl sc, Surface barrierSurface,
long frameNumber) {
if (frameNumber < 0) {
@@ -2071,13 +2142,25 @@
return this;
}
- /** Re-parents a specific child layer to a new parent
- * @hide
+ /**
+ * Re-parents a given layer to a new parent. Children inherit transform (position, scaling)
+ * crop, visibility, and Z-ordering from their parents, as if the children were pixels within the
+ * parent Surface.
+ *
+ * @param sc The SurfaceControl to reparent
+ * @param newParent The new parent for the given control.
+ * @return This Transaction
*/
- public Transaction reparent(SurfaceControl sc, IBinder newParentHandle) {
+ @NonNull
+ public Transaction reparent(@NonNull SurfaceControl sc,
+ @Nullable SurfaceControl newParent) {
sc.checkNotReleased();
- nativeReparent(mNativeObject, sc.mNativeObject,
- newParentHandle);
+ long otherObject = 0;
+ if (newParent != null) {
+ newParent.checkNotReleased();
+ otherObject = newParent.mNativeObject;
+ }
+ nativeReparent(mNativeObject, sc.mNativeObject, otherObject);
return this;
}
@@ -2245,9 +2328,12 @@
/**
* Merge the other transaction into this transaction, clearing the
* other transaction as if it had been applied.
- * @hide
+ *
+ * @param other The transaction to merge in to this one.
+ * @return This transaction.
*/
- public Transaction merge(Transaction other) {
+ @NonNull
+ public Transaction merge(@NonNull Transaction other) {
mResizedSurfaces.putAll(other.mResizedSurfaces);
other.mResizedSurfaces.clear();
nativeMergeTransaction(mNativeObject, other.mNativeObject);
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 61fb00d..45e6c50 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -120,10 +120,11 @@
final Rect mScreenRect = new Rect();
SurfaceSession mSurfaceSession;
- SurfaceControlWithBackground mSurfaceControl;
+ SurfaceControl mSurfaceControl;
// In the case of format changes we switch out the surface in-place
// we need to preserve the old one until the new one has drawn.
SurfaceControl mDeferredDestroySurfaceControl;
+ SurfaceControl mBackgroundControl;
final Rect mTmpRect = new Rect();
final Configuration mConfiguration = new Configuration();
@@ -487,6 +488,29 @@
}
}
+ private void updateBackgroundVisibilityInTransaction() {
+ if (mBackgroundControl == null) {
+ return;
+ }
+ if ((mSurfaceFlags & PixelFormat.OPAQUE) == 0) {
+ mBackgroundControl.show();
+ mBackgroundControl.setLayer(Integer.MIN_VALUE);
+ } else {
+ mBackgroundControl.hide();
+ }
+ }
+
+ private void releaseSurfaces() {
+ if (mSurfaceControl != null) {
+ mSurfaceControl.destroy();
+ mSurfaceControl = null;
+ }
+ if (mBackgroundControl != null) {
+ mBackgroundControl.destroy();
+ mBackgroundControl = null;
+ }
+ }
+
/** @hide */
protected void updateSurface() {
if (!mHaveFrame) {
@@ -553,14 +577,21 @@
updateOpaqueFlag();
final String name = "SurfaceView - " + viewRoot.getTitle().toString();
- mSurfaceControl = new SurfaceControlWithBackground(
- name,
- (mSurfaceFlags & SurfaceControl.OPAQUE) != 0,
- new SurfaceControl.Builder(mSurfaceSession)
- .setBufferSize(mSurfaceWidth, mSurfaceHeight)
- .setFormat(mFormat)
- .setParent(viewRoot.getSurfaceControl())
- .setFlags(mSurfaceFlags));
+ mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
+ .setName(name)
+ .setOpaque((mSurfaceFlags & SurfaceControl.OPAQUE) != 0)
+ .setBufferSize(mSurfaceWidth, mSurfaceHeight)
+ .setFormat(mFormat)
+ .setParent(viewRoot.getSurfaceControl())
+ .setFlags(mSurfaceFlags)
+ .build();
+ mBackgroundControl = new SurfaceControl.Builder(mSurfaceSession)
+ .setName("Background for -" + name)
+ .setOpaque(true)
+ .setColorLayer(true)
+ .setParent(mSurfaceControl)
+ .build();
+
} else if (mSurfaceControl == null) {
return;
}
@@ -577,11 +608,13 @@
SurfaceControl.openTransaction();
try {
mSurfaceControl.setLayer(mSubLayer);
+
if (mViewVisibility) {
mSurfaceControl.show();
} else {
mSurfaceControl.hide();
}
+ updateBackgroundVisibilityInTransaction();
// While creating the surface, we will set it's initial
// geometry. Outside of that though, we should generally
@@ -724,8 +757,7 @@
if (mSurfaceControl != null && !mSurfaceCreated) {
mSurface.release();
- mSurfaceControl.destroy();
- mSurfaceControl = null;
+ releaseSurfaces();
}
}
} catch (Exception ex) {
@@ -823,7 +855,6 @@
final ViewRootImpl viewRoot = getViewRootImpl();
applySurfaceTransforms(mSurfaceControl, position, frameNumber);
- applySurfaceTransforms(mSurfaceControl.mBackgroundControl, position, frameNumber);
applyChildSurfaceTransaction_renderWorker(mRtTransaction, viewRoot.mSurface,
frameNumber);
@@ -950,7 +981,19 @@
* @hide
*/
public void setResizeBackgroundColor(int bgColor) {
- mSurfaceControl.setBackgroundColor(bgColor);
+ if (mBackgroundControl == null) {
+ return;
+ }
+
+ final float[] colorComponents = new float[] { Color.red(bgColor) / 255.f,
+ Color.green(bgColor) / 255.f, Color.blue(bgColor) / 255.f };
+
+ SurfaceControl.openTransaction();
+ try {
+ mBackgroundControl.setColor(colorComponents);
+ } finally {
+ SurfaceControl.closeTransaction();
+ }
}
@UnsupportedAppUsage
@@ -1128,154 +1171,12 @@
};
/**
- * @hide
+ * Return a SurfaceControl which can be used for parenting Surfaces to
+ * this SurfaceView.
+ *
+ * @return The SurfaceControl for this SurfaceView.
*/
public SurfaceControl getSurfaceControl() {
return mSurfaceControl;
}
-
- class SurfaceControlWithBackground extends SurfaceControl {
- SurfaceControl mBackgroundControl;
- private boolean mOpaque = true;
- public boolean mVisible = false;
-
- public SurfaceControlWithBackground(String name, boolean opaque, SurfaceControl.Builder b)
- throws Exception {
- super(b.setName(name).build());
-
- mBackgroundControl = b.setName("Background for -" + name)
- .setFormat(OPAQUE)
- // Unset the buffer size of the background color layer.
- .setBufferSize(0, 0)
- .setColorLayer(true)
- .build();
- mOpaque = opaque;
- }
-
- @Override
- public void setAlpha(float alpha) {
- super.setAlpha(alpha);
- mBackgroundControl.setAlpha(alpha);
- }
-
- @Override
- public void setLayer(int zorder) {
- super.setLayer(zorder);
- // -3 is below all other child layers as SurfaceView never goes below -2
- mBackgroundControl.setLayer(-3);
- }
-
- @Override
- public void setPosition(float x, float y) {
- super.setPosition(x, y);
- mBackgroundControl.setPosition(x, y);
- }
-
- @Override
- public void setBufferSize(int w, int h) {
- super.setBufferSize(w, h);
- // The background surface is a color layer so we do not set a size.
- }
-
- @Override
- public void setWindowCrop(Rect crop) {
- super.setWindowCrop(crop);
- mBackgroundControl.setWindowCrop(crop);
- }
-
- @Override
- public void setWindowCrop(int width, int height) {
- super.setWindowCrop(width, height);
- mBackgroundControl.setWindowCrop(width, height);
- }
-
- @Override
- public void setLayerStack(int layerStack) {
- super.setLayerStack(layerStack);
- mBackgroundControl.setLayerStack(layerStack);
- }
-
- @Override
- public void setOpaque(boolean isOpaque) {
- super.setOpaque(isOpaque);
- mOpaque = isOpaque;
- updateBackgroundVisibility();
- }
-
- @Override
- public void setSecure(boolean isSecure) {
- super.setSecure(isSecure);
- }
-
- @Override
- public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
- super.setMatrix(dsdx, dtdx, dsdy, dtdy);
- mBackgroundControl.setMatrix(dsdx, dtdx, dsdy, dtdy);
- }
-
- @Override
- public void hide() {
- super.hide();
- mVisible = false;
- updateBackgroundVisibility();
- }
-
- @Override
- public void show() {
- super.show();
- mVisible = true;
- updateBackgroundVisibility();
- }
-
- @Override
- public void destroy() {
- super.destroy();
- mBackgroundControl.destroy();
- }
-
- @Override
- public void release() {
- super.release();
- mBackgroundControl.release();
- }
-
- @Override
- public void setTransparentRegionHint(Region region) {
- super.setTransparentRegionHint(region);
- mBackgroundControl.setTransparentRegionHint(region);
- }
-
- @Override
- public void deferTransactionUntil(IBinder handle, long frame) {
- super.deferTransactionUntil(handle, frame);
- mBackgroundControl.deferTransactionUntil(handle, frame);
- }
-
- @Override
- public void deferTransactionUntil(Surface barrier, long frame) {
- super.deferTransactionUntil(barrier, frame);
- mBackgroundControl.deferTransactionUntil(barrier, frame);
- }
-
- /** Set the color to fill the background with. */
- private void setBackgroundColor(int bgColor) {
- final float[] colorComponents = new float[] { Color.red(bgColor) / 255.f,
- Color.green(bgColor) / 255.f, Color.blue(bgColor) / 255.f };
-
- SurfaceControl.openTransaction();
- try {
- mBackgroundControl.setColor(colorComponents);
- } finally {
- SurfaceControl.closeTransaction();
- }
- }
-
- void updateBackgroundVisibility() {
- if (mOpaque && mVisible) {
- mBackgroundControl.show();
- } else {
- mBackgroundControl.hide();
- }
- }
- }
}
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 34d076f..47b206ca 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.HardwareRenderer;
+import android.graphics.Picture;
import android.graphics.Point;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
@@ -553,6 +554,10 @@
dumpProfileInfo(fd, flags);
}
+ Picture captureRenderingCommands() {
+ return null;
+ }
+
@Override
public boolean loadSystemProperties() {
boolean changed = super.loadSystemProperties();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a1aa06e..9d0c9f4 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
import static java.lang.Math.max;
@@ -5123,7 +5124,7 @@
sAcceptZeroSizeDragShadow = targetSdkVersion < Build.VERSION_CODES.P;
- sBrokenInsetsDispatch = !ViewRootImpl.USE_NEW_INSETS
+ sBrokenInsetsDispatch = ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL
|| targetSdkVersion < Build.VERSION_CODES.Q;
sCompatibilityDone = true;
@@ -8202,10 +8203,10 @@
* {@link ContentCaptureSession#notifyViewDisappeared(AutofillId)}, and
* {@link ContentCaptureSession#notifyViewTextChanged(AutofillId, CharSequence, int)}
* respectively. The structure for the a child must be created using
- * {@link ContentCaptureSession#newVirtualViewStructure(AutofillId, int)}, and the
+ * {@link ContentCaptureSession#newVirtualViewStructure(AutofillId, long)}, and the
* {@code autofillId} for a child can be obtained either through
* {@code childStructure.getAutofillId()} or
- * {@link ContentCaptureSession#newAutofillId(AutofillId, int)}.
+ * {@link ContentCaptureSession#newAutofillId(AutofillId, long)}.
*
* <p><b>Note: </b>the following methods of the {@code structure} will be ignored:
* <ul>
@@ -8226,7 +8227,15 @@
* </ul>
*/
public void onProvideContentCaptureStructure(@NonNull ViewStructure structure, int flags) {
- onProvideStructure(structure, VIEW_STRUCTURE_FOR_CONTENT_CAPTURE, flags);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+ "onProvideContentCaptureStructure() for " + getClass().getSimpleName());
+ }
+ try {
+ onProvideStructure(structure, VIEW_STRUCTURE_FOR_CONTENT_CAPTURE, flags);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
}
/** @hide */
@@ -8599,7 +8608,7 @@
if (isAttachedToWindow()) {
throw new IllegalStateException("Cannot set autofill id when view is attached");
}
- if (id != null && id.isVirtual()) {
+ if (id != null && !id.isNonVirtual()) {
throw new IllegalStateException("Cannot set autofill id assigned to virtual views");
}
if (id == null && (mPrivateFlags3 & PFLAG3_AUTOFILLID_EXPLICITLY_SET) == 0) {
@@ -9016,6 +9025,18 @@
* </ol>
*/
private void notifyAppearedOrDisappearedForContentCaptureIfNeeded(boolean appeared) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+ "notifyContentCapture(" + appeared + ") for " + getClass().getSimpleName());
+ }
+ try {
+ notifyAppearedOrDisappearedForContentCaptureIfNeededNoTrace(appeared);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ }
+
+ private void notifyAppearedOrDisappearedForContentCaptureIfNeededNoTrace(boolean appeared) {
// First check if context has client, so it saves a service lookup when it doesn't
if (!mContext.isContentCaptureSupported()) return;
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 292e933..5afc07f 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -17,17 +17,21 @@
package android.view;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.HardwareRenderer;
import android.graphics.Picture;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RenderNode;
import android.os.Debug;
import android.os.Handler;
+import android.os.Looper;
import android.os.RemoteException;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -48,16 +52,20 @@
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Function;
/**
* Various debugging/tracing tools related to {@link View} and the view hierarchy.
@@ -741,6 +749,123 @@
root.getViewRootImpl().outputDisplayList(target);
}
+ private static class PictureCallbackHandler implements AutoCloseable,
+ HardwareRenderer.PictureCapturedCallback, Runnable {
+ private final HardwareRenderer mRenderer;
+ private final Function<Picture, Boolean> mCallback;
+ private final Executor mExecutor;
+ private final ReentrantLock mLock = new ReentrantLock(false);
+ private final ArrayDeque<Picture> mQueue = new ArrayDeque<>(3);
+ private boolean mStopListening;
+ private Thread mRenderThread;
+
+ private PictureCallbackHandler(HardwareRenderer renderer,
+ Function<Picture, Boolean> callback, Executor executor) {
+ mRenderer = renderer;
+ mCallback = callback;
+ mExecutor = executor;
+ mRenderer.setPictureCaptureCallback(this);
+ }
+
+ @Override
+ public void close() {
+ mLock.lock();
+ mStopListening = true;
+ mLock.unlock();
+ mRenderer.setPictureCaptureCallback(null);
+ }
+
+ @Override
+ public void onPictureCaptured(Picture picture) {
+ mLock.lock();
+ if (mStopListening) {
+ mLock.unlock();
+ mRenderer.setPictureCaptureCallback(null);
+ return;
+ }
+ if (mRenderThread == null) {
+ mRenderThread = Thread.currentThread();
+ }
+ Picture toDestroy = null;
+ if (mQueue.size() == 3) {
+ toDestroy = mQueue.removeLast();
+ }
+ mQueue.add(picture);
+ mLock.unlock();
+ if (toDestroy == null) {
+ mExecutor.execute(this);
+ } else {
+ toDestroy.close();
+ }
+ }
+
+ @Override
+ public void run() {
+ mLock.lock();
+ final Picture picture = mQueue.poll();
+ final boolean isStopped = mStopListening;
+ mLock.unlock();
+ if (Thread.currentThread() == mRenderThread) {
+ close();
+ throw new IllegalStateException(
+ "ViewDebug#startRenderingCommandsCapture must be given an executor that "
+ + "invokes asynchronously");
+ }
+ if (isStopped) {
+ picture.close();
+ return;
+ }
+ final boolean keepReceiving = mCallback.apply(picture);
+ if (!keepReceiving) {
+ close();
+ }
+ }
+ }
+
+ /**
+ * Begins capturing the entire rendering commands for the view tree referenced by the given
+ * view. The view passed may be any View in the tree as long as it is attached. That is,
+ * {@link View#isAttachedToWindow()} must be true.
+ *
+ * Every time a frame is rendered a Picture will be passed to the given callback via the given
+ * executor. As long as the callback returns 'true' it will continue to receive new frames.
+ * The system will only invoke the callback at a rate that the callback is able to keep up with.
+ * That is, if it takes 48ms for the callback to complete and there is a 60fps animation running
+ * then the callback will only receive 33% of the frames produced.
+ *
+ * This method must be called on the same thread as the View tree.
+ *
+ * @param tree The View tree to capture the rendering commands.
+ * @param callback The callback to invoke on every frame produced. Should return true to
+ * continue receiving new frames, false to stop capturing.
+ * @param executor The executor to invoke the callback on. Recommend using a background thread
+ * to avoid stalling the UI thread. Must be an asynchronous invoke or an
+ * exception will be thrown.
+ * @return a closeable that can be used to stop capturing. May be invoked on any thread. Note
+ * that the callback may continue to receive another frame or two depending on thread timings.
+ * Returns null if the capture stream cannot be started, such as if there's no
+ * HardwareRenderer for the given view tree.
+ * @hide
+ */
+ @TestApi
+ @Nullable
+ public static AutoCloseable startRenderingCommandsCapture(View tree, Executor executor,
+ Function<Picture, Boolean> callback) {
+ final View.AttachInfo attachInfo = tree.mAttachInfo;
+ if (attachInfo == null) {
+ throw new IllegalArgumentException("Given view isn't attached");
+ }
+ if (attachInfo.mHandler.getLooper() != Looper.myLooper()) {
+ throw new IllegalStateException("Called on the wrong thread."
+ + " Must be called on the thread that owns the given View");
+ }
+ final HardwareRenderer renderer = attachInfo.mThreadedRenderer;
+ if (renderer != null) {
+ return new PictureCallbackHandler(renderer, callback, executor);
+ }
+ return null;
+ }
+
private static void capture(View root, final OutputStream clientStream, String parameter)
throws IOException {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 27d4ea4..a031b70 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -163,13 +163,16 @@
private static final boolean MT_RENDERER_AVAILABLE = true;
/**
- * If set to true, the view system will switch from using rectangles retrieved from window to
+ * If set to 2, the view system will switch from using rectangles retrieved from window to
* dispatch to the view hierarchy to using {@link InsetsController}, that derives the insets
* directly from the full configuration, enabling richer information about the insets state, as
* well as new APIs to control it frame-by-frame, and synchronize animations with it.
* <p>
- * Only switch this to true once the new insets system is productionized and the old APIs are
+ * Only set this to 2 once the new insets system is productionized and the old APIs are
* fully migrated over.
+ * <p>
+ * If set to 1, this will switch to a mode where we only use the new approach for IME, but not
+ * for the status/navigation bar.
*/
private static final String USE_NEW_INSETS_PROPERTY = "persist.wm.new_insets";
@@ -177,8 +180,26 @@
* @see #USE_NEW_INSETS_PROPERTY
* @hide
*/
- public static final boolean USE_NEW_INSETS =
- SystemProperties.getBoolean(USE_NEW_INSETS_PROPERTY, false);
+ public static final int sNewInsetsMode =
+ SystemProperties.getInt(USE_NEW_INSETS_PROPERTY, 0);
+
+ /**
+ * @see #USE_NEW_INSETS_PROPERTY
+ * @hide
+ */
+ public static final int NEW_INSETS_MODE_NONE = 0;
+
+ /**
+ * @see #USE_NEW_INSETS_PROPERTY
+ * @hide
+ */
+ public static final int NEW_INSETS_MODE_IME = 1;
+
+ /**
+ * @see #USE_NEW_INSETS_PROPERTY
+ * @hide
+ */
+ public static final int NEW_INSETS_MODE_FULL = 2;
/**
* Set this system property to true to force the view hierarchy to render
@@ -1367,7 +1388,7 @@
}
void notifyInsetsChanged() {
- if (!USE_NEW_INSETS) {
+ if (sNewInsetsMode == NEW_INSETS_MODE_NONE) {
return;
}
mApplyInsetsRequested = true;
@@ -1855,10 +1876,11 @@
}
contentInsets = ensureInsetsNonNegative(contentInsets, "content");
stableInsets = ensureInsetsNonNegative(stableInsets, "stable");
- if (USE_NEW_INSETS) {
+ if (sNewInsetsMode != NEW_INSETS_MODE_NONE) {
mLastWindowInsets = mInsetsController.calculateInsets(
mContext.getResources().getConfiguration().isScreenRound(),
- mAttachInfo.mAlwaysConsumeNavBar, displayCutout);
+ mAttachInfo.mAlwaysConsumeNavBar, displayCutout,
+ contentInsets, stableInsets);
} else {
mLastWindowInsets = new WindowInsets(contentInsets, stableInsets,
mContext.getResources().getConfiguration().isScreenRound(),
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index b3da727..e808830 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -217,7 +217,10 @@
return typeInsetMap;
}
- private static void assignCompatInsets(Insets[] typeInsetMap, Rect insets) {
+ /**
+ * @hide
+ */
+ static void assignCompatInsets(Insets[] typeInsetMap, Rect insets) {
typeInsetMap[indexOf(TOP_BAR)] = Insets.of(0, insets.top, 0, 0);
typeInsetMap[indexOf(SIDE_BARS)] = Insets.of(insets.left, 0, insets.right, insets.bottom);
}
@@ -387,7 +390,8 @@
*/
@NonNull
public WindowInsets consumeDisplayCutout() {
- return new WindowInsets(mTypeInsetsMap, mTypeMaxInsetsMap,
+ return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap,
+ mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
mIsRound, mAlwaysConsumeNavBar,
null /* displayCutout */);
}
@@ -872,7 +876,7 @@
@NonNull
public Builder setStableInsets(@NonNull Insets stableInsets) {
Preconditions.checkNotNull(stableInsets);
- assignCompatInsets(mTypeInsetsMap, stableInsets.toRect());
+ assignCompatInsets(mTypeMaxInsetsMap, stableInsets.toRect());
mStableInsetsConsumed = false;
return this;
}
diff --git a/core/java/android/view/autofill/AutofillId.java b/core/java/android/view/autofill/AutofillId.java
index 9c935af..f1c7b69 100644
--- a/core/java/android/view/autofill/AutofillId.java
+++ b/core/java/android/view/autofill/AutofillId.java
@@ -29,12 +29,14 @@
/** @hide */
public static final int NO_SESSION = 0;
- private static final int FLAG_IS_VIRTUAL = 0x1;
- private static final int FLAG_HAS_SESSION = 0x2;
+ private static final int FLAG_IS_VIRTUAL_INT = 0x1;
+ private static final int FLAG_IS_VIRTUAL_LONG = 0x2;
+ private static final int FLAG_HAS_SESSION = 0x4;
private final int mViewId;
private final int mFlags;
- private final int mVirtualId;
+ private final int mVirtualIntId;
+ private final long mVirtualLongId;
private final int mSessionId;
/** @hide */
@@ -46,40 +48,89 @@
/** @hide */
@TestApi
public AutofillId(@NonNull AutofillId parent, int virtualChildId) {
- this(FLAG_IS_VIRTUAL, parent.mViewId, virtualChildId, NO_SESSION);
+ this(FLAG_IS_VIRTUAL_INT, parent.mViewId, virtualChildId, NO_SESSION);
}
/** @hide */
public AutofillId(int parentId, int virtualChildId) {
- this(FLAG_IS_VIRTUAL, parentId, virtualChildId, NO_SESSION);
+ this(FLAG_IS_VIRTUAL_INT, parentId, virtualChildId, NO_SESSION);
}
/** @hide */
- public AutofillId(@NonNull AutofillId parent, int virtualChildId, int sessionId) {
- this(FLAG_IS_VIRTUAL | FLAG_HAS_SESSION, parent.mViewId, virtualChildId, sessionId);
+ public AutofillId(@NonNull AutofillId parent, long virtualChildId, int sessionId) {
+ this(FLAG_IS_VIRTUAL_LONG | FLAG_HAS_SESSION, parent.mViewId, virtualChildId, sessionId);
}
- private AutofillId(int flags, int parentId, int virtualChildId, int sessionId) {
+ private AutofillId(int flags, int parentId, long virtualChildId, int sessionId) {
mFlags = flags;
mViewId = parentId;
- mVirtualId = virtualChildId;
+ mVirtualIntId = ((flags & FLAG_IS_VIRTUAL_INT) != 0) ? (int) virtualChildId : View.NO_ID;
+ mVirtualLongId = ((flags & FLAG_IS_VIRTUAL_LONG) != 0) ? virtualChildId : View.NO_ID;
mSessionId = sessionId;
}
-
/** @hide */
public int getViewId() {
return mViewId;
}
- /** @hide */
- public int getVirtualChildId() {
- return mVirtualId;
+ /**
+ * Gets the virtual child id.
+ *
+ * <p>Should only be used on subsystems where such id is represented by an {@code int}
+ * (Assist and Autofill).
+ *
+ * @hide
+ */
+ public int getVirtualChildIntId() {
+ return mVirtualIntId;
}
- /** @hide */
- public boolean isVirtual() {
- return (mFlags & FLAG_IS_VIRTUAL) != 0;
+ /**
+ * Gets the virtual child id.
+ *
+ * <p>Should only be used on subsystems where such id is represented by a {@code long}
+ * (ContentCapture).
+ *
+ * @hide
+ */
+ public long getVirtualChildLongId() {
+ return mVirtualLongId;
+ }
+
+ /**
+ * Checks whether this node represents a virtual child, whose id is represented by an
+ * {@code int}.
+ *
+ * <p>Should only be used on subsystems where such id is represented by an {@code int}
+ * (Assist and Autofill).
+ *
+ * @hide
+ */
+ public boolean isVirtualInt() {
+ return (mFlags & FLAG_IS_VIRTUAL_INT) != 0;
+ }
+
+ /**
+ * Checks whether this node represents a virtual child, whose id is represented by an
+ * {@code long}.
+ *
+ * <p>Should only be used on subsystems where such id is represented by a {@code long}
+ * (ContentCapture).
+ *
+ * @hide
+ */
+ public boolean isVirtualLong() {
+ return (mFlags & FLAG_IS_VIRTUAL_LONG) != 0;
+ }
+
+ /**
+ * Checks whether this node represents a non-virtual child.
+ *
+ * @hide
+ */
+ public boolean isNonVirtual() {
+ return !isVirtualInt() && !isVirtualLong();
}
private boolean hasSession() {
@@ -100,7 +151,8 @@
final int prime = 31;
int result = 1;
result = prime * result + mViewId;
- result = prime * result + mVirtualId;
+ result = prime * result + mVirtualIntId;
+ result = prime * result + (int) (mVirtualLongId ^ (mVirtualLongId >>> 32));
result = prime * result + mSessionId;
return result;
}
@@ -112,7 +164,8 @@
if (getClass() != obj.getClass()) return false;
final AutofillId other = (AutofillId) obj;
if (mViewId != other.mViewId) return false;
- if (mVirtualId != other.mVirtualId) return false;
+ if (mVirtualIntId != other.mVirtualIntId) return false;
+ if (mVirtualLongId != other.mVirtualLongId) return false;
if (mSessionId != other.mSessionId) return false;
return true;
}
@@ -120,9 +173,12 @@
@Override
public String toString() {
final StringBuilder builder = new StringBuilder().append(mViewId);
- if (isVirtual()) {
- builder.append(':').append(mVirtualId);
+ if (isVirtualInt()) {
+ builder.append(':').append(mVirtualIntId);
+ } else if (isVirtualLong()) {
+ builder.append(':').append(mVirtualLongId);
}
+
if (hasSession()) {
builder.append('@').append(mSessionId);
}
@@ -138,12 +194,14 @@
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(mViewId);
parcel.writeInt(mFlags);
- if (isVirtual()) {
- parcel.writeInt(mVirtualId);
- }
if (hasSession()) {
parcel.writeInt(mSessionId);
}
+ if (isVirtualInt()) {
+ parcel.writeInt(mVirtualIntId);
+ } else if (isVirtualLong()) {
+ parcel.writeLong(mVirtualLongId);
+ }
}
public static final Parcelable.Creator<AutofillId> CREATOR =
@@ -152,9 +210,14 @@
public AutofillId createFromParcel(Parcel source) {
final int viewId = source.readInt();
final int flags = source.readInt();
- final int virtualId = (flags & FLAG_IS_VIRTUAL) != 0 ? source.readInt() : View.NO_ID;
final int sessionId = (flags & FLAG_HAS_SESSION) != 0 ? source.readInt() : NO_SESSION;
- return new AutofillId(flags, viewId, virtualId, sessionId);
+ if ((flags & FLAG_IS_VIRTUAL_INT) != 0) {
+ return new AutofillId(flags, viewId, source.readInt(), sessionId);
+ }
+ if ((flags & FLAG_IS_VIRTUAL_LONG) != 0) {
+ return new AutofillId(flags, viewId, source.readLong(), sessionId);
+ }
+ return new AutofillId(flags, viewId, View.NO_ID, sessionId);
}
@Override
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 888a4c5..64c34f61 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -17,6 +17,7 @@
package android.view.autofill;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
+import static android.util.DebugUtils.flagsToString;
import static android.view.autofill.Helper.sDebug;
import static android.view.autofill.Helper.sVerbose;
@@ -25,6 +26,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.content.ComponentName;
@@ -77,6 +79,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
//TODO: use java.lang.ref.Cleaner once Android supports Java 9
import sun.misc.Cleaner;
@@ -336,6 +339,25 @@
public static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
/**
+ * Displays the Augment Autofill window using the same mechanism (such as a popup-window
+ * attached to the focused view) as the standard autofill.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final int FLAG_SMART_SUGGESTION_SYSTEM = 0x1;
+
+ /** @hide */ // TODO(b/123233342): remove when not used anymore
+ public static final int FLAG_SMART_SUGGESTION_LEGACY = 0x2;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "FLAG_SMART_SUGGESTION_" }, value = {
+ FLAG_SMART_SUGGESTION_SYSTEM
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SmartSuggestionMode {}
+
+ /**
* Makes an authentication id from a request id and a dataset id.
*
* @param requestId The request id.
@@ -1686,7 +1708,7 @@
final IAutoFillManager service = mService;
final IAutoFillManagerClient serviceClient = mServiceClient;
mServiceClientCleaner = Cleaner.create(this, () -> {
- // TODO(b/111330312): call service to also remove reference to
+ // TODO(b/123100811): call service to also remove reference to
// mAugmentedAutofillServiceClient
try {
service.removeClient(serviceClient, userId);
@@ -1746,6 +1768,108 @@
}
}
+ /**
+ * Defines whether augmented autofill should be triggered for activities with such
+ * {@link android.content.ComponentName}.
+ *
+ * <p>Useful to blacklist a particular activity.
+ *
+ * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill
+ * service, and it's ignored if the caller isn't it.
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ //TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+ //in the same package as the test, and that module is compiled with SDK=test_current
+ public void setActivityAugmentedAutofillEnabled(@NonNull ComponentName activity,
+ boolean enabled) {
+ // TODO(b/123100824): implement
+ }
+
+ /**
+ * Defines whether augmented autofill should be triggered for activities of the app with such
+ * {@code packageName}.
+ *
+ * <p>Useful to blacklist any activity from a particular app.
+ *
+ * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill
+ * service, and it's ignored if the caller isn't it.
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ //TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+ //in the same package as the test, and that module is compiled with SDK=test_current
+ public void setPackageAugmentedAutofillEnabled(@NonNull String packageName, boolean enabled) {
+ // TODO(b/123100824): implement
+ }
+
+ /**
+ * Explicitly limits augmented autofill to the given packages and activities.
+ *
+ * <p>When the whitelist is set, it overrides the values passed to
+ * {@link #setActivityAugmentedAutofillEnabled(ComponentName, boolean)}
+ * and {@link #setPackageAugmentedAutofillEnabled(String, boolean)}.
+ *
+ * <p>To reset the whitelist, call it passing {@code null} to both arguments.
+ *
+ * <p>Useful when the service wants to restrict augmented autofill to a category of apps, like
+ * apps that uses addresses. For example, if the service wants to support augmented autofill on
+ * all activities of app {@code AddressApp1} and just activities {@code act1} and {@code act2}
+ * of {@code AddressApp2}, it would call:
+ * {@code setAugmentedAutofillWhitelist(Arrays.asList("AddressApp1"),
+ * Arrays.asList(new ComponentName("AddressApp2", "act1"),
+ * new ComponentName("AddressApp2", "act2")));}
+ *
+ * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill
+ * service, and it's ignored if the caller isn't it.
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ //TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+ //in the same package as the test, and that module is compiled with SDK=test_current
+ public void setAugmentedAutofillWhitelist(@Nullable List<String> packages,
+ @Nullable List<ComponentName> activities) {
+ // TODO(b/123100824): implement
+ }
+
+ /**
+ * Gets the activities where augmented autofill was disabled by
+ * {@link #setActivityAugmentedAutofillEnabled(ComponentName, boolean)}.
+ *
+ * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill
+ * service, and it's ignored if the caller isn't it.
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ @NonNull
+ public Set<ComponentName> getAugmentedAutofillDisabledActivities() {
+ return null; // TODO(b/123100824): implement
+ }
+
+ /**
+ * Gets the apps where content capture was disabled by
+ * {@link #setPackageAugmentedAutofillEnabled(String, boolean)}.
+ *
+ * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill
+ * service, and it's ignored if the caller isn't it.
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ @NonNull
+ public Set<String> getAugmentedAutofillDisabledPackages() {
+ return null; // TODO(b/123100824): implement
+ }
+
private void requestShowFillUi(int sessionId, AutofillId id, int width, int height,
Rect anchorBounds, IAutofillWindowPresenter presenter) {
final View anchor = findView(id);
@@ -1769,8 +1893,8 @@
}
if (callback != null) {
- if (id.isVirtual()) {
- callback.onAutofillEvent(anchor, id.getVirtualChildId(),
+ if (id.isVirtualInt()) {
+ callback.onAutofillEvent(anchor, id.getVirtualChildIntId(),
AutofillCallback.EVENT_INPUT_SHOWN);
} else {
callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN);
@@ -1896,7 +2020,7 @@
failedIds.add(id);
continue;
}
- if (id.isVirtual()) {
+ if (id.isVirtualInt()) {
if (virtualValues == null) {
// Most likely there will be just one view with virtual children.
virtualValues = new ArrayMap<>(1);
@@ -1907,7 +2031,7 @@
valuesByParent = new SparseArray<>(5);
virtualValues.put(view, valuesByParent);
}
- valuesByParent.put(id.getVirtualChildId(), value);
+ valuesByParent.put(id.getVirtualChildIntId(), value);
} else {
// Mark the view as to be autofilled with 'value'
if (mLastAutofilledData == null) {
@@ -2142,8 +2266,8 @@
}
if (callback != null) {
- if (id.isVirtual()) {
- callback.onAutofillEvent(anchor, id.getVirtualChildId(),
+ if (id.isVirtualInt()) {
+ callback.onAutofillEvent(anchor, id.getVirtualChildIntId(),
AutofillCallback.EVENT_INPUT_HIDDEN);
} else {
callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN);
@@ -2169,8 +2293,8 @@
}
if (callback != null) {
- if (id.isVirtual()) {
- callback.onAutofillEvent(anchor, id.getVirtualChildId(),
+ if (id.isVirtualInt()) {
+ callback.onAutofillEvent(anchor, id.getVirtualChildIntId(),
AutofillCallback.EVENT_INPUT_UNAVAILABLE);
} else {
callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
@@ -2296,6 +2420,11 @@
}
}
+ /** @hide */
+ public static String getSmartSuggestionModeToString(@SmartSuggestionMode int flags) {
+ return flagsToString(AutofillManager.class, "FLAG_SMART_SUGGESTION_", flags);
+ }
+
@GuardedBy("mLock")
private boolean isActiveLocked() {
return mState == STATE_ACTIVE;
@@ -2972,7 +3101,6 @@
@Override
public Rect getViewCoordinates(@NonNull AutofillId id) {
- // TODO(b/111330312): use handler / callback?
final AutofillManager afm = mAfm.get();
if (afm == null) return null;
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index b620ab1..a1e581e 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -352,14 +352,14 @@
* @throws IllegalArgumentException if {@code virtualIds} is empty
*/
public final void notifyViewsDisappeared(@NonNull AutofillId hostId,
- @NonNull int[] virtualIds) {
- Preconditions.checkArgument(!hostId.isVirtual(), "parent cannot be virtual");
+ @NonNull long[] virtualIds) {
+ Preconditions.checkArgument(hostId.isNonVirtual(), "parent cannot be virtual");
Preconditions.checkArgument(!ArrayUtils.isEmpty(virtualIds), "virtual ids cannot be empty");
if (!isContentCaptureEnabled()) return;
// TODO(b/123036895): use a internalNotifyViewsDisappeared that optimizes how the event is
// parcelized
- for (int id : virtualIds) {
+ for (long id : virtualIds) {
internalNotifyViewDisappeared(new AutofillId(hostId, id, getIdAsInt()));
}
}
@@ -405,9 +405,9 @@
*
* @throws IllegalArgumentException if the {@code parentId} is a virtual child id.
*/
- public @NonNull AutofillId newAutofillId(@NonNull AutofillId parentId, int virtualChildId) {
+ public @NonNull AutofillId newAutofillId(@NonNull AutofillId parentId, long virtualChildId) {
Preconditions.checkNotNull(parentId);
- Preconditions.checkArgument(!parentId.isVirtual(), "virtual ids cannot have children");
+ Preconditions.checkArgument(parentId.isNonVirtual(), "virtual ids cannot have children");
return new AutofillId(parentId, virtualChildId, getIdAsInt());
}
@@ -423,7 +423,7 @@
*/
@NonNull
public final ViewStructure newVirtualViewStructure(@NonNull AutofillId parentId,
- int virtualId) {
+ long virtualId) {
return new ViewNode.ViewStructureImpl(parentId, virtualId, getIdAsInt());
}
diff --git a/core/java/android/view/contentcapture/ViewNode.java b/core/java/android/view/contentcapture/ViewNode.java
index cbc946b..0cabafa 100644
--- a/core/java/android/view/contentcapture/ViewNode.java
+++ b/core/java/android/view/contentcapture/ViewNode.java
@@ -617,7 +617,7 @@
}
@VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk.
- public ViewStructureImpl(@NonNull AutofillId parentId, int virtualId, int sessionId) {
+ public ViewStructureImpl(@NonNull AutofillId parentId, long virtualId, int sessionId) {
mNode.mParentAutofillId = Preconditions.checkNotNull(parentId);
mNode.mAutofillId = new AutofillId(parentId, virtualId, sessionId);
}
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index f99afe6..8e7b0db 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -479,13 +479,9 @@
* If not {@code null}, this editor needs to talk to IMEs that run for the specified user, no
* matter what user ID the calling process has.
*
- * <p>Note: This field is silently ignored when:</p>
- * <ul>
- * <li>{@link android.view.inputmethod.InputMethodSystemProperty#PER_PROFILE_IME_ENABLED} is
- * {@code false}.</li>
- * <li>{@link android.view.inputmethod.InputMethodSystemProperty#MULTI_CLIENT_IME_ENABLED}
- * is {@code true}.</li>
- * </ul>
+ * <p>Note: This field will be silently ignored when
+ * {@link android.view.inputmethod.InputMethodSystemProperty#MULTI_CLIENT_IME_ENABLED} is
+ * {@code true}.</p>
*
* <p>Note also that pseudo handles such as {@link UserHandle#ALL} are not supported.</p>
*
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 1ba7d8e..15088d2 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -27,6 +27,7 @@
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
+import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -922,19 +923,6 @@
}
}
- /**
- * Returns a list of VR InputMethod currently installed.
- * @hide
- */
- @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
- public List<InputMethodInfo> getVrInputMethodList() {
- try {
- return mService.getVrInputMethodList();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
public List<InputMethodInfo> getEnabledInputMethodList() {
try {
return mService.getEnabledInputMethodList();
@@ -980,24 +968,30 @@
InputMethodPrivilegedOperationsRegistry.get(imeToken).updateStatusIcon(null, 0);
}
- /** @hide */
+ /**
+ * This hidden API is deprecated in {@link android.os.Build.VERSION_CODES#Q}. Does nothing.
+ *
+ * @param spans will be ignored.
+ *
+ * @deprecated Do not use.
+ * @hide
+ */
+ @Deprecated
@UnsupportedAppUsage
public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
- try {
- mService.registerSuggestionSpansForNotification(spans);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ Log.w(TAG, "registerSuggestionSpansForNotification() is deprecated. Does nothing.");
}
- /** @hide */
+ /**
+ * This hidden API is deprecated in {@link android.os.Build.VERSION_CODES#Q}. Does nothing.
+ *
+ * @deprecated Do not use.
+ * @hide
+ */
+ @Deprecated
@UnsupportedAppUsage
public void notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
- try {
- mService.notifySuggestionPicked(span, originalString, index);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ Log.w(TAG, "notifySuggestionPicked() is deprecated. Does nothing.");
}
/**
@@ -1888,7 +1882,15 @@
/**
* Notify the event when the user tapped or clicked the text view.
+ *
+ * @param view {@link View} which is being clicked.
+ * @see InputMethodService#onViewClicked(boolean)
+ * @deprecated The semantics of this method can never be defined well for composite {@link View}
+ * that works as a giant "Canvas", which can host its own UI hierarchy and sub focus
+ * state. {@link android.webkit.WebView} is a good example. Application / IME
+ * developers should not rely on this method.
*/
+ @Deprecated
public void viewClicked(View view) {
// Re-dispatch if there is a context mismatch.
final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
@@ -2491,11 +2493,48 @@
*/
@RequiresPermission(WRITE_SECURE_SETTINGS)
public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
- try {
- return mService.setCurrentInputMethodSubtype(subtype);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ if (Process.myUid() == Process.SYSTEM_UID) {
+ Log.w(TAG, "System process should not call setCurrentInputMethodSubtype() because "
+ + "almost always it is a bug under multi-user / multi-profile environment. "
+ + "Consider directly interacting with InputMethodManagerService "
+ + "via LocalServices.");
+ return false;
}
+ if (subtype == null) {
+ // See the JavaDoc. This is how this method has worked.
+ return false;
+ }
+ final Context fallbackContext = ActivityThread.currentApplication();
+ if (fallbackContext == null) {
+ return false;
+ }
+ if (fallbackContext.checkSelfPermission(WRITE_SECURE_SETTINGS)
+ != PackageManager.PERMISSION_GRANTED) {
+ return false;
+ }
+ final ContentResolver contentResolver = fallbackContext.getContentResolver();
+ final String imeId = Settings.Secure.getString(contentResolver,
+ Settings.Secure.DEFAULT_INPUT_METHOD);
+ if (ComponentName.unflattenFromString(imeId) == null) {
+ // Null or invalid IME ID format.
+ return false;
+ }
+ final List<InputMethodSubtype> enabledSubtypes;
+ try {
+ enabledSubtypes = mService.getEnabledInputMethodSubtypeList(imeId, true);
+ } catch (RemoteException e) {
+ return false;
+ }
+ final int numSubtypes = enabledSubtypes.size();
+ for (int i = 0; i < numSubtypes; ++i) {
+ final InputMethodSubtype enabledSubtype = enabledSubtypes.get(i);
+ if (enabledSubtype.equals(subtype)) {
+ Settings.Secure.putInt(contentResolver,
+ Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, enabledSubtype.hashCode());
+ return true;
+ }
+ }
+ return false;
}
/**
diff --git a/core/java/android/webkit/WebViewRenderer.java b/core/java/android/webkit/WebViewRenderer.java
index 5328254..fc38cd9 100644
--- a/core/java/android/webkit/WebViewRenderer.java
+++ b/core/java/android/webkit/WebViewRenderer.java
@@ -16,6 +16,8 @@
package android.webkit;
+import android.annotation.SystemApi;
+
/**
* WebViewRenderer provides an opaque handle to a {@link WebView} renderer.
*/
@@ -40,6 +42,7 @@
* This class cannot be created by applications.
* @hide
*/
+ @SystemApi
public WebViewRenderer() {
}
}
diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index 29b3b3c..3a1c457 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -150,7 +150,7 @@
}
try {
- sZygote = Process.zygoteProcess.startChildZygote(
+ sZygote = Process.ZYGOTE_PROCESS.startChildZygote(
"com.android.internal.os.WebViewZygoteInit",
"webview_zygote",
Process.WEBVIEW_ZYGOTE_UID,
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 55364ec..4a60b6a 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2901,10 +2901,6 @@
}
}
- // Notify source IME of the suggestion pick. Do this before swapping texts.
- targetSuggestionSpan.notifySelection(
- mTextView.getContext(), originalText, suggestionInfo.mSuggestionIndex);
-
// Swap text content between actual text and Suggestion span
final int suggestionStart = suggestionInfo.mSuggestionStart;
final int suggestionEnd = suggestionInfo.mSuggestionEnd;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 1085e5d..780fe8d 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -5117,7 +5117,7 @@
* @attr ref android.R.styleable#TextView_scrollHorizontally
* @see #setHorizontallyScrolling(boolean)
*/
- public final boolean isHorizontallyScrolling() {
+ public final boolean isHorizontallyScrollable() {
return mHorizontallyScrolling;
}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index b4d8322..30137e38 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -103,7 +103,9 @@
* binding to every ChooserTargetService implementation.
*/
// TODO(b/121287573): Replace with a system flag (setprop?)
- private static final boolean USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS = false;
+ private static final boolean USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS = true;
+ private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true;
+
// TODO(b/121287224): Re-evaluate this limit
private static final int SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
@@ -136,6 +138,7 @@
private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT = 2;
private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 3;
+ private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 4;
private final Handler mChooserHandler = new Handler() {
@Override
@@ -182,6 +185,9 @@
mChooserListAdapter.addServiceResults(resultInfo.originalTarget,
resultInfo.resultTargets);
}
+ break;
+
+ case SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED:
sendVoiceChoicesIfNeeded();
mChooserListAdapter.setShowServiceTargets(true);
break;
@@ -630,6 +636,7 @@
// Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
// for direct share targets. After ShareSheet is refactored we should use the
// ShareShortcutInfos directly.
+ boolean resultMessageSent = false;
for (int i = 0; i < driList.size(); i++) {
List<ChooserTarget> chooserTargets = new ArrayList<>();
for (int j = 0; j < resultList.size(); j++) {
@@ -646,6 +653,13 @@
msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null);
mChooserHandler.sendMessage(msg);
+ resultMessageSent = true;
+ }
+
+ if (resultMessageSent) {
+ final Message msg = Message.obtain();
+ msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
+ mChooserHandler.sendMessage(msg);
}
});
}
@@ -1178,13 +1192,17 @@
mTargetsNeedPruning = true;
}
}
+
if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS) {
if (DEBUG) {
Log.d(TAG, "querying direct share targets from ShortcutManager");
}
queryDirectShareTargets(this);
- } else {
- if (DEBUG) Log.d(TAG, "List built querying services");
+ }
+ if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) {
+ if (DEBUG) {
+ Log.d(TAG, "List built querying services");
+ }
queryTargetServices(this);
}
}
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index a27dbea..18c4b46 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -74,6 +74,16 @@
private static final boolean LOG_INOTIFY = false;
+ protected static final String SUPPORTED_QUERY_ARGS = joinNewline(
+ DocumentsContract.QUERY_ARG_DISPLAY_NAME,
+ DocumentsContract.QUERY_ARG_FILE_SIZE_OVER,
+ DocumentsContract.QUERY_ARG_LAST_MODIFIED_AFTER,
+ DocumentsContract.QUERY_ARG_MIME_TYPES);
+
+ private static String joinNewline(String... args) {
+ return TextUtils.join("\n", args);
+ }
+
private String[] mDefaultProjection;
@GuardedBy("mObservers")
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index c4aa1d7..a691a24 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -25,7 +25,13 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
- * Log all the things.
+ * Writes sysui_multi_event records to the system event log.
+ *
+ * Prefer the methods write(LogMaker), or count() or histogram(). Replace legacy methods with
+ * their current equivalents when the opportunity arises.
+ *
+ * This class is a lightweight dependency barrier - it is cheap and easy to construct.
+ * Logging is also cheap, so it is not normally necessary to move logging off of the UI thread.
*
* @hide
*/
@@ -52,6 +58,7 @@
public static final int VIEW_UNKNOWN = MetricsEvent.VIEW_UNKNOWN;
public static final int LOGTAG = EventLogTags.SYSUI_MULTI_ACTION;
+ /** Write an event log record, consisting of content.serialize(). */
@UnsupportedAppUsage
public void write(LogMaker content) {
if (content.getType() == MetricsEvent.TYPE_UNKNOWN) {
@@ -60,128 +67,145 @@
saveLog(content);
}
+ /** Add an integer value to the monotonically increasing counter with the given name. */
+ public void count(String name, int value) {
+ saveLog(new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
+ .setCounterName(name)
+ .setCounterValue(value));
+ }
+
+ /** Increment the bucket with the integer label on the histogram with the given name. */
+ public void histogram(String name, int bucket) {
+ // see LogHistogram in system/core/libmetricslogger/metrics_logger.cpp
+ saveLog(new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
+ .setCounterName(name)
+ .setCounterBucket(bucket)
+ .setCounterValue(1));
+ }
+
+ /* Legacy logging methods follow. These are all simple shorthands and can be replaced
+ * with an equivalent write(). */
+
+ /** Logs an OPEN event on the category.
+ * Equivalent to write(new LogMaker(category).setType(MetricsEvent.TYPE_OPEN)) */
public void visible(int category) throws IllegalArgumentException {
if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
throw new IllegalArgumentException("Must define metric category");
}
- EventLogTags.writeSysuiViewVisibility(category, 100);
saveLog(new LogMaker(category).setType(MetricsEvent.TYPE_OPEN));
}
+ /** Logs a CLOSE event on the category.
+ * Equivalent to write(new LogMaker(category).setType(MetricsEvent.TYPE_CLOSE)) */
public void hidden(int category) throws IllegalArgumentException {
if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
throw new IllegalArgumentException("Must define metric category");
}
- EventLogTags.writeSysuiViewVisibility(category, 0);
saveLog(new LogMaker(category).setType(MetricsEvent.TYPE_CLOSE));
}
- public void visibility(int category, boolean visibile)
+ /** Logs an OPEN or CLOSE event on the category, depending on visible.
+ * Equivalent to write(new LogMaker(category)
+ * .setType(visible ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE)) */
+ public void visibility(int category, boolean visible)
throws IllegalArgumentException {
- if (visibile) {
+ if (visible) {
visible(category);
} else {
hidden(category);
}
}
+ /** Logs an OPEN or CLOSE event on the category, depending on vis.
+ * Equivalent to write(new LogMaker(category)
+ .setType(vis == View.VISIBLE ?
+ MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE)) */
public void visibility(int category, int vis)
throws IllegalArgumentException {
visibility(category, vis == View.VISIBLE);
}
+ /** Logs an ACTION event on the category.
+ * Equivalent to write(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION)) */
public void action(int category) {
- EventLogTags.writeSysuiAction(category, "");
saveLog(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION));
}
+ /** Logs an ACTION event on the category.
+ * Equivalent to write(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION)
+ .setSubtype(value) */
public void action(int category, int value) {
- EventLogTags.writeSysuiAction(category, Integer.toString(value));
saveLog(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION).setSubtype(value));
}
+ /** Logs an ACTION event on the category.
+ * Equivalent to write(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION)
+ .setSubtype(value ? 1 : 0) */
public void action(int category, boolean value) {
- EventLogTags.writeSysuiAction(category, Boolean.toString(value));
saveLog(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION).setSubtype(value ? 1 : 0));
}
+ /** Logs an ACTION event on the category.
+ * Equivalent to write(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION)
+ .setPackageName(value ? 1 : 0) */
public void action(int category, String pkg) {
if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
throw new IllegalArgumentException("Must define metric category");
}
- EventLogTags.writeSysuiAction(category, pkg);
saveLog(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION).setPackageName(pkg));
}
- /** Add an integer value to the monotonically increasing counter with the given name. */
- public void count(String name, int value) {
- EventLogTags.writeSysuiCount(name, value);
- saveLog(new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
- .setCounterName(name)
- .setCounterValue(value));
- }
-
- /** Increment the bucket with the integer label on the histogram with the given name. */
- public void histogram(String name, int bucket) {
- // see LogHistogram in system/core/libmetricslogger/metrics_logger.cpp
- EventLogTags.writeSysuiHistogram(name, bucket);
- saveLog(new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
- .setCounterName(name)
- .setCounterBucket(bucket)
- .setCounterValue(1));
- }
-
- /** @deprecated use {@link #visible(int)} */
+ /** @deprecated because untestable; use {@link #visible(int)} */
@Deprecated
public static void visible(Context context, int category) throws IllegalArgumentException {
getLogger().visible(category);
}
- /** @deprecated use {@link #hidden(int)} */
+ /** @deprecated because untestable; use {@link #hidden(int)} */
@Deprecated
public static void hidden(Context context, int category) throws IllegalArgumentException {
getLogger().hidden(category);
}
- /** @deprecated use {@link #visibility(int, boolean)} */
+ /** @deprecated because untestable; use {@link #visibility(int, boolean)} */
@Deprecated
public static void visibility(Context context, int category, boolean visibile)
throws IllegalArgumentException {
getLogger().visibility(category, visibile);
}
- /** @deprecated use {@link #visibility(int, int)} */
+ /** @deprecated because untestable; use {@link #visibility(int, int)} */
@Deprecated
public static void visibility(Context context, int category, int vis)
throws IllegalArgumentException {
visibility(context, category, vis == View.VISIBLE);
}
- /** @deprecated use {@link #action(int)} */
+ /** @deprecated because untestable; use {@link #action(int)} */
@Deprecated
public static void action(Context context, int category) {
getLogger().action(category);
}
- /** @deprecated use {@link #action(int, int)} */
+ /** @deprecated because untestable; use {@link #action(int, int)} */
@Deprecated
public static void action(Context context, int category, int value) {
getLogger().action(category, value);
}
- /** @deprecated use {@link #action(int, boolean)} */
+ /** @deprecated because untestable; use {@link #action(int, boolean)} */
@Deprecated
public static void action(Context context, int category, boolean value) {
getLogger().action(category, value);
}
- /** @deprecated use {@link #write(LogMaker)} */
+ /** @deprecated because untestable; use {@link #write(LogMaker)} */
@Deprecated
public static void action(LogMaker content) {
getLogger().write(content);
}
- /** @deprecated use {@link #action(int, String)} */
+ /** @deprecated because untestable; use {@link #action(int, String)} */
@Deprecated
public static void action(Context context, int category, String pkg) {
getLogger().action(category, pkg);
@@ -189,7 +213,7 @@
/**
* Add an integer value to the monotonically increasing counter with the given name.
- * @deprecated use {@link #count(String, int)}
+ * @deprecated because untestable; use {@link #count(String, int)}
*/
@Deprecated
public static void count(Context context, String name, int value) {
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 17cc6af..534361e 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -87,6 +87,10 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.location.gnssmetrics.GnssMetrics;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.FastXmlSerializer;
@@ -187,18 +191,19 @@
private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
@VisibleForTesting
- protected KernelUidCpuTimeReader mKernelUidCpuTimeReader = new KernelUidCpuTimeReader();
+ protected KernelCpuUidUserSysTimeReader mCpuUidUserSysTimeReader =
+ new KernelCpuUidUserSysTimeReader(true);
@VisibleForTesting
protected KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
@VisibleForTesting
- protected KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader =
- new KernelUidCpuFreqTimeReader();
+ protected KernelCpuUidFreqTimeReader mCpuUidFreqTimeReader =
+ new KernelCpuUidFreqTimeReader(true);
@VisibleForTesting
- protected KernelUidCpuActiveTimeReader mKernelUidCpuActiveTimeReader =
- new KernelUidCpuActiveTimeReader();
+ protected KernelCpuUidActiveTimeReader mCpuUidActiveTimeReader =
+ new KernelCpuUidActiveTimeReader(true);
@VisibleForTesting
- protected KernelUidCpuClusterTimeReader mKernelUidCpuClusterTimeReader =
- new KernelUidCpuClusterTimeReader();
+ protected KernelCpuUidClusterTimeReader mCpuUidClusterTimeReader =
+ new KernelCpuUidClusterTimeReader(true);
@VisibleForTesting
protected KernelSingleUidTimeReader mKernelSingleUidTimeReader;
@@ -248,9 +253,9 @@
/** Last time that RPM stats were updated by updateRpmStatsLocked. */
private long mLastRpmStatsUpdateTimeMs = -RPM_STATS_UPDATE_FREQ_MS;
/**
- * Use a queue to delay removing UIDs from {@link KernelUidCpuTimeReader},
- * {@link KernelUidCpuActiveTimeReader}, {@link KernelUidCpuClusterTimeReader},
- * {@link KernelUidCpuFreqTimeReader} and from the Kernel.
+ * Use a queue to delay removing UIDs from {@link KernelCpuUidUserSysTimeReader},
+ * {@link KernelCpuUidActiveTimeReader}, {@link KernelCpuUidClusterTimeReader},
+ * {@link KernelCpuUidFreqTimeReader} and from the Kernel.
*
* Isolated and invalid UID info must be removed to conserve memory. However, STATSD and
* Batterystats both need to access UID cpu time. To resolve this race condition, only
@@ -281,22 +286,22 @@
void remove() {
if (startUid == endUid) {
- mKernelUidCpuTimeReader.removeUid(startUid);
- mKernelUidCpuFreqTimeReader.removeUid(startUid);
+ mCpuUidUserSysTimeReader.removeUid(startUid);
+ mCpuUidFreqTimeReader.removeUid(startUid);
if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) {
- mKernelUidCpuActiveTimeReader.removeUid(startUid);
- mKernelUidCpuClusterTimeReader.removeUid(startUid);
+ mCpuUidActiveTimeReader.removeUid(startUid);
+ mCpuUidClusterTimeReader.removeUid(startUid);
}
if (mKernelSingleUidTimeReader != null) {
mKernelSingleUidTimeReader.removeUid(startUid);
}
mNumUidsRemoved++;
} else if (startUid < endUid) {
- mKernelUidCpuFreqTimeReader.removeUidsInRange(startUid, endUid);
- mKernelUidCpuTimeReader.removeUidsInRange(startUid, endUid);
+ mCpuUidFreqTimeReader.removeUidsInRange(startUid, endUid);
+ mCpuUidUserSysTimeReader.removeUidsInRange(startUid, endUid);
if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) {
- mKernelUidCpuActiveTimeReader.removeUidsInRange(startUid, endUid);
- mKernelUidCpuClusterTimeReader.removeUidsInRange(startUid, endUid);
+ mCpuUidActiveTimeReader.removeUidsInRange(startUid, endUid);
+ mCpuUidClusterTimeReader.removeUidsInRange(startUid, endUid);
}
if (mKernelSingleUidTimeReader != null) {
mKernelSingleUidTimeReader.removeUidsInRange(startUid, endUid);
@@ -496,7 +501,7 @@
}
final SparseArray<long[]> allUidCpuFreqTimesMs =
- mKernelUidCpuFreqTimeReader.getAllUidCpuFreqTimeMs();
+ mCpuUidFreqTimeReader.getAllUidCpuFreqTimeMs();
// If the KernelSingleUidTimeReader has stale cpu times, then we shouldn't try to
// compute deltas since it might result in mis-attributing cpu times to wrong states.
if (mIsPerProcessStateCpuDataStale) {
@@ -553,16 +558,16 @@
return false;
}
if (mCpuFreqs == null) {
- mCpuFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile);
+ mCpuFreqs = mCpuUidFreqTimeReader.readFreqs(mPowerProfile);
}
if (mCpuFreqs != null) {
mKernelSingleUidTimeReader = new KernelSingleUidTimeReader(mCpuFreqs.length);
} else {
- mPerProcStateCpuTimesAvailable = mKernelUidCpuFreqTimeReader.allUidTimesAvailable();
+ mPerProcStateCpuTimesAvailable = mCpuUidFreqTimeReader.allUidTimesAvailable();
return false;
}
}
- mPerProcStateCpuTimesAvailable = mKernelUidCpuFreqTimeReader.allUidTimesAvailable()
+ mPerProcStateCpuTimesAvailable = mCpuUidFreqTimeReader.allUidTimesAvailable()
&& mKernelSingleUidTimeReader.singleUidCpuTimesAvailable();
return true;
}
@@ -11926,7 +11931,7 @@
}
if (mCpuFreqs == null) {
- mCpuFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile);
+ mCpuFreqs = mCpuUidFreqTimeReader.readFreqs(mPowerProfile);
}
// Calculate the wakelocks we have to distribute amongst. The system is excluded as it is
@@ -11952,12 +11957,12 @@
// When the battery is not on, we don't attribute the cpu times to any timers but we still
// need to take the snapshots.
if (!onBattery) {
- mKernelUidCpuTimeReader.readDelta(null);
- mKernelUidCpuFreqTimeReader.readDelta(null);
+ mCpuUidUserSysTimeReader.readDelta(null);
+ mCpuUidFreqTimeReader.readDelta(null);
mNumAllUidCpuTimeReads += 2;
if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) {
- mKernelUidCpuActiveTimeReader.readDelta(null);
- mKernelUidCpuClusterTimeReader.readDelta(null);
+ mCpuUidActiveTimeReader.readDelta(null);
+ mCpuUidClusterTimeReader.readDelta(null);
mNumAllUidCpuTimeReads += 2;
}
for (int cluster = mKernelCpuSpeedReaders.length - 1; cluster >= 0; --cluster) {
@@ -11967,7 +11972,7 @@
}
mUserInfoProvider.refreshUserIds();
- final SparseLongArray updatedUids = mKernelUidCpuFreqTimeReader.perClusterTimesAvailable()
+ final SparseLongArray updatedUids = mCpuUidFreqTimeReader.perClusterTimesAvailable()
? null : new SparseLongArray();
readKernelUidCpuTimesLocked(partialTimersToConsider, updatedUids, onBattery);
// updatedUids=null means /proc/uid_time_in_state provides snapshots of per-cluster cpu
@@ -12084,18 +12089,20 @@
final int numWakelocks = partialTimers == null ? 0 : partialTimers.size();
final long startTimeMs = mClocks.uptimeMillis();
- mKernelUidCpuTimeReader.readDelta((uid, userTimeUs, systemTimeUs) -> {
+ mCpuUidUserSysTimeReader.readDelta((uid, timesUs) -> {
+ long userTimeUs = timesUs[0], systemTimeUs = timesUs[1];
+
uid = mapUid(uid);
if (Process.isIsolated(uid)) {
// This could happen if the isolated uid mapping was removed before that process
// was actually killed.
- mKernelUidCpuTimeReader.removeUid(uid);
+ mCpuUidUserSysTimeReader.removeUid(uid);
Slog.d(TAG, "Got readings for an isolated uid with no mapping: " + uid);
return;
}
if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
Slog.d(TAG, "Got readings for an invalid user's uid " + uid);
- mKernelUidCpuTimeReader.removeUid(uid);
+ mCpuUidUserSysTimeReader.removeUid(uid);
return;
}
final Uid u = getUidStatsLocked(uid);
@@ -12189,21 +12196,21 @@
public void readKernelUidCpuFreqTimesLocked(@Nullable ArrayList<StopwatchTimer> partialTimers,
boolean onBattery, boolean onBatteryScreenOff) {
final boolean perClusterTimesAvailable =
- mKernelUidCpuFreqTimeReader.perClusterTimesAvailable();
+ mCpuUidFreqTimeReader.perClusterTimesAvailable();
final int numWakelocks = partialTimers == null ? 0 : partialTimers.size();
final int numClusters = mPowerProfile.getNumCpuClusters();
mWakeLockAllocationsUs = null;
final long startTimeMs = mClocks.uptimeMillis();
- mKernelUidCpuFreqTimeReader.readDelta((uid, cpuFreqTimeMs) -> {
+ mCpuUidFreqTimeReader.readDelta((uid, cpuFreqTimeMs) -> {
uid = mapUid(uid);
if (Process.isIsolated(uid)) {
- mKernelUidCpuFreqTimeReader.removeUid(uid);
+ mCpuUidFreqTimeReader.removeUid(uid);
Slog.d(TAG, "Got freq readings for an isolated uid with no mapping: " + uid);
return;
}
if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
Slog.d(TAG, "Got freq readings for an invalid user's uid " + uid);
- mKernelUidCpuFreqTimeReader.removeUid(uid);
+ mCpuUidFreqTimeReader.removeUid(uid);
return;
}
final Uid u = getUidStatsLocked(uid);
@@ -12307,16 +12314,16 @@
@VisibleForTesting
public void readKernelUidCpuActiveTimesLocked(boolean onBattery) {
final long startTimeMs = mClocks.uptimeMillis();
- mKernelUidCpuActiveTimeReader.readDelta((uid, cpuActiveTimesMs) -> {
+ mCpuUidActiveTimeReader.readDelta((uid, cpuActiveTimesMs) -> {
uid = mapUid(uid);
if (Process.isIsolated(uid)) {
- mKernelUidCpuActiveTimeReader.removeUid(uid);
+ mCpuUidActiveTimeReader.removeUid(uid);
Slog.w(TAG, "Got active times for an isolated uid with no mapping: " + uid);
return;
}
if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
Slog.w(TAG, "Got active times for an invalid user's uid " + uid);
- mKernelUidCpuActiveTimeReader.removeUid(uid);
+ mCpuUidActiveTimeReader.removeUid(uid);
return;
}
final Uid u = getUidStatsLocked(uid);
@@ -12336,16 +12343,16 @@
@VisibleForTesting
public void readKernelUidCpuClusterTimesLocked(boolean onBattery) {
final long startTimeMs = mClocks.uptimeMillis();
- mKernelUidCpuClusterTimeReader.readDelta((uid, cpuClusterTimesMs) -> {
+ mCpuUidClusterTimeReader.readDelta((uid, cpuClusterTimesMs) -> {
uid = mapUid(uid);
if (Process.isIsolated(uid)) {
- mKernelUidCpuClusterTimeReader.removeUid(uid);
+ mCpuUidClusterTimeReader.removeUid(uid);
Slog.w(TAG, "Got cluster times for an isolated uid with no mapping: " + uid);
return;
}
if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
Slog.w(TAG, "Got cluster times for an invalid user's uid " + uid);
- mKernelUidCpuClusterTimeReader.removeUid(uid);
+ mCpuUidClusterTimeReader.removeUid(uid);
return;
}
final Uid u = getUidStatsLocked(uid);
@@ -13344,7 +13351,7 @@
private static final boolean DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE = false;
private static final boolean DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME = true;
private static final long DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS = 5_000;
- private static final long DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME = 10_000;
+ private static final long DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME = 1_000;
private static final long DEFAULT_UID_REMOVE_DELAY_MS = 5L * 60L * 1000L;
private static final long DEFAULT_EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS = 600_000;
private static final long DEFAULT_BATTERY_LEVEL_COLLECTION_DELAY_MS = 300_000;
@@ -13357,7 +13364,9 @@
public boolean TRACK_CPU_TIMES_BY_PROC_STATE = DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE;
public boolean TRACK_CPU_ACTIVE_CLUSTER_TIME = DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME;
public long PROC_STATE_CPU_TIMES_READ_DELAY_MS = DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS;
- public long KERNEL_UID_READERS_THROTTLE_TIME = DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME;
+ /* Do not set default value for KERNEL_UID_READERS_THROTTLE_TIME. Need to trigger an
+ * update when startObserving. */
+ public long KERNEL_UID_READERS_THROTTLE_TIME;
public long UID_REMOVE_DELAY_MS = DEFAULT_UID_REMOVE_DELAY_MS;
public long EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS
= DEFAULT_EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS;
@@ -13464,11 +13473,11 @@
private void updateKernelUidReadersThrottleTime(long oldTimeMs, long newTimeMs) {
KERNEL_UID_READERS_THROTTLE_TIME = newTimeMs;
if (oldTimeMs != newTimeMs) {
- mKernelUidCpuTimeReader.setThrottleInterval(KERNEL_UID_READERS_THROTTLE_TIME);
- mKernelUidCpuFreqTimeReader.setThrottleInterval(KERNEL_UID_READERS_THROTTLE_TIME);
- mKernelUidCpuActiveTimeReader.setThrottleInterval(KERNEL_UID_READERS_THROTTLE_TIME);
- mKernelUidCpuClusterTimeReader
- .setThrottleInterval(KERNEL_UID_READERS_THROTTLE_TIME);
+ mCpuUidUserSysTimeReader.setThrottle(KERNEL_UID_READERS_THROTTLE_TIME);
+ mCpuUidFreqTimeReader.setThrottle(KERNEL_UID_READERS_THROTTLE_TIME);
+ mCpuUidActiveTimeReader.setThrottle(KERNEL_UID_READERS_THROTTLE_TIME);
+ mCpuUidClusterTimeReader
+ .setThrottle(KERNEL_UID_READERS_THROTTLE_TIME);
}
}
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index 051a96c..6454352 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -53,6 +53,7 @@
public static final boolean DETAILED_TRACKING_DEFAULT = true;
public static final int PERIODIC_SAMPLING_INTERVAL_DEFAULT = 100;
public static final int MAX_BINDER_CALL_STATS_COUNT_DEFAULT = 5000;
+ private static final String DEBUG_ENTRY_PREFIX = "__DEBUG_";
private static class OverflowBinder extends Binder {}
@@ -347,7 +348,7 @@
callStat.callingUid = uid;
callStat.recordedCallCount = 1;
callStat.callCount = 1;
- callStat.methodName = "__DEBUG_" + variableName;
+ callStat.methodName = DEBUG_ENTRY_PREFIX + variableName;
callStat.latencyMicros = value;
return callStat;
}
@@ -398,6 +399,10 @@
final List<ExportedCallStat> exportedCallStats = getExportedCallStats();
exportedCallStats.sort(BinderCallsStats::compareByCpuDesc);
for (ExportedCallStat e : exportedCallStats) {
+ if (e.methodName.startsWith(DEBUG_ENTRY_PREFIX)) {
+ // Do not dump debug entries.
+ continue;
+ }
sb.setLength(0);
sb.append(" ")
.append(packageMap.mapUid(e.callingUid))
diff --git a/core/java/com/android/internal/os/KernelCpuProcReader.java b/core/java/com/android/internal/os/KernelCpuProcReader.java
deleted file mode 100644
index c233ea8..0000000
--- a/core/java/com/android/internal/os/KernelCpuProcReader.java
+++ /dev/null
@@ -1,162 +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.
- */
-
-package com.android.internal.os;
-
-import android.os.StrictMode;
-import android.os.SystemClock;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.file.Files;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Arrays;
-
-/**
- * Reads cpu time proc files with throttling (adjustable interval).
- *
- * KernelCpuProcReader is implemented as singletons for built-in kernel proc files. Get___Instance()
- * method will return corresponding reader instance. In order to prevent frequent GC,
- * KernelCpuProcReader reuses a {@link ByteBuffer} to store data read from proc files.
- *
- * A KernelCpuProcReader instance keeps an error counter. When the number of read errors within that
- * instance accumulates to 5, this instance will reject all further read requests.
- *
- * Each KernelCpuProcReader instance also has a throttler. Throttle interval can be adjusted via
- * {@link #setThrottleInterval(long)} method. Default throttle interval is 3000ms. If current
- * timestamp based on {@link SystemClock#elapsedRealtime()} is less than throttle interval from
- * the last read timestamp, {@link #readBytes()} will return previous result.
- *
- * A KernelCpuProcReader instance is thread-unsafe. Caller needs to hold a lock on this object while
- * accessing its instance methods or digesting the return values.
- */
-public class KernelCpuProcReader {
- private static final String TAG = "KernelCpuProcReader";
- private static final int ERROR_THRESHOLD = 5;
- // Throttle interval in milliseconds
- private static final long DEFAULT_THROTTLE_INTERVAL = 3000L;
- private static final int MAX_BUFFER_SIZE = 1024 * 1024;
- private static final String PROC_UID_FREQ_TIME = "/proc/uid_cpupower/time_in_state";
- private static final String PROC_UID_ACTIVE_TIME = "/proc/uid_cpupower/concurrent_active_time";
- private static final String PROC_UID_CLUSTER_TIME = "/proc/uid_cpupower/concurrent_policy_time";
-
- private static final KernelCpuProcReader mFreqTimeReader = new KernelCpuProcReader(
- PROC_UID_FREQ_TIME);
- private static final KernelCpuProcReader mActiveTimeReader = new KernelCpuProcReader(
- PROC_UID_ACTIVE_TIME);
- private static final KernelCpuProcReader mClusterTimeReader = new KernelCpuProcReader(
- PROC_UID_CLUSTER_TIME);
-
- public static KernelCpuProcReader getFreqTimeReaderInstance() {
- return mFreqTimeReader;
- }
-
- public static KernelCpuProcReader getActiveTimeReaderInstance() {
- return mActiveTimeReader;
- }
-
- public static KernelCpuProcReader getClusterTimeReaderInstance() {
- return mClusterTimeReader;
- }
-
- private int mErrors;
- private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL;
- private long mLastReadTime = Long.MIN_VALUE;
- private final Path mProc;
- private byte[] mBuffer = new byte[8 * 1024];
- private int mContentSize;
-
- @VisibleForTesting
- public KernelCpuProcReader(String procFile) {
- mProc = Paths.get(procFile);
- }
-
- /**
- * Reads all bytes from the corresponding proc file.
- *
- * If elapsed time since last call to this method is less than the throttle interval, it will
- * return previous result. When IOException accumulates to 5, it will always return null. This
- * method is thread-unsafe, so is the return value. Caller needs to hold a lock on this
- * object while calling this method and digesting its return value.
- *
- * @return a {@link ByteBuffer} containing all bytes from the proc file.
- */
- public ByteBuffer readBytes() {
- if (mErrors >= ERROR_THRESHOLD) {
- return null;
- }
- if (SystemClock.elapsedRealtime() < mLastReadTime + mThrottleInterval) {
- if (mContentSize > 0) {
- return ByteBuffer.wrap(mBuffer, 0, mContentSize).asReadOnlyBuffer()
- .order(ByteOrder.nativeOrder());
- }
- return null;
- }
- mLastReadTime = SystemClock.elapsedRealtime();
- mContentSize = 0;
- final int oldMask = StrictMode.allowThreadDiskReadsMask();
- try (InputStream in = Files.newInputStream(mProc)) {
- int numBytes = 0;
- int curr;
- while ((curr = in.read(mBuffer, numBytes, mBuffer.length - numBytes)) >= 0) {
- numBytes += curr;
- if (numBytes == mBuffer.length) {
- // Hit the limit. Resize mBuffer.
- if (mBuffer.length == MAX_BUFFER_SIZE) {
- mErrors++;
- Slog.e(TAG, "Proc file is too large: " + mProc);
- return null;
- }
- mBuffer = Arrays.copyOf(mBuffer,
- Math.min(mBuffer.length << 1, MAX_BUFFER_SIZE));
- }
- }
- mContentSize = numBytes;
- return ByteBuffer.wrap(mBuffer, 0, mContentSize).asReadOnlyBuffer()
- .order(ByteOrder.nativeOrder());
- } catch (NoSuchFileException | FileNotFoundException e) {
- // Happens when the kernel does not provide this file. Not a big issue. Just log it.
- mErrors++;
- Slog.w(TAG, "File not exist: " + mProc);
- } catch (IOException e) {
- mErrors++;
- Slog.e(TAG, "Error reading: " + mProc, e);
- } finally {
- StrictMode.setThreadPolicyMask(oldMask);
- }
- return null;
- }
-
- /**
- * Sets the throttle interval. Set to 0 will disable throttling. Thread-unsafe, holding a lock
- * on this object is recommended.
- *
- * @param throttleInterval throttle interval in milliseconds
- */
- public void setThrottleInterval(long throttleInterval) {
- if (throttleInterval >= 0) {
- mThrottleInterval = throttleInterval;
- }
- }
-}
diff --git a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
index 7021b57..e6d044f 100644
--- a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
@@ -177,6 +177,9 @@
* The file contains a monotonically increasing count of time for a single boot. This class
* maintains the previous results of a call to {@link #readDelta} in order to provide a proper
* delta.
+ *
+ * The second parameter of the callback is a long[] with 2 elements, [user time in us, system
+ * time in us].
*/
public static class KernelCpuUidUserSysTimeReader extends KernelCpuUidTimeReader<long[]> {
private static final String REMOVE_UID_PROC_FILE = "/proc/uid_cputime/remove_uid_range";
diff --git a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
index ad62852..3c43a11 100644
--- a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
@@ -16,7 +16,6 @@
package com.android.internal.os;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
-import static com.android.internal.os.KernelUidCpuFreqTimeReader.UID_TIMES_PROC_FILE;
import android.annotation.NonNull;
import android.util.Slog;
@@ -34,11 +33,12 @@
@VisibleForTesting(visibility = PACKAGE)
public class KernelSingleUidTimeReader {
- private final String TAG = KernelUidCpuFreqTimeReader.class.getName();
- private final boolean DBG = false;
+ private static final String TAG = KernelSingleUidTimeReader.class.getName();
+ private static final boolean DBG = false;
- private final String PROC_FILE_DIR = "/proc/uid/";
- private final String PROC_FILE_NAME = "/time_in_state";
+ private static final String PROC_FILE_DIR = "/proc/uid/";
+ private static final String PROC_FILE_NAME = "/time_in_state";
+ private static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
@VisibleForTesting
public static final int TOTAL_READ_ERROR_COUNT = 5;
diff --git a/core/java/com/android/internal/os/KernelUidCpuActiveTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuActiveTimeReader.java
deleted file mode 100644
index bd8a67a..0000000
--- a/core/java/com/android/internal/os/KernelUidCpuActiveTimeReader.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.os;
-
-import android.annotation.Nullable;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.nio.ByteBuffer;
-import java.nio.IntBuffer;
-import java.util.function.Consumer;
-
-/**
- * Reads binary proc file /proc/uid_cpupower/concurrent_active_time and reports CPU active time to
- * BatteryStats to compute {@link PowerProfile#POWER_CPU_ACTIVE}.
- *
- * concurrent_active_time is an array of u32's in the following format:
- * [n, uid0, time0a, time0b, ..., time0n,
- * uid1, time1a, time1b, ..., time1n,
- * uid2, time2a, time2b, ..., time2n, etc.]
- * where n is the total number of cpus (num_possible_cpus)
- * ...
- * timeXn means the CPU time that a UID X spent running concurrently with n other processes.
- * The file contains a monotonically increasing count of time for a single boot. This class
- * maintains the previous results of a call to {@link #readDelta} in order to provide a
- * proper delta.
- *
- * This class uses a throttler to reject any {@link #readDelta} call within
- * {@link #mThrottleInterval}. This is different from the throttler in {@link KernelCpuProcReader},
- * which has a shorter throttle interval and returns cached result from last read when the request
- * is throttled.
- *
- * This class is NOT thread-safe and NOT designed to be accessed by more than one caller since each
- * caller has its own view of delta.
- */
-public class KernelUidCpuActiveTimeReader extends
- KernelUidCpuTimeReaderBase<KernelUidCpuActiveTimeReader.Callback> {
- private static final String TAG = KernelUidCpuActiveTimeReader.class.getSimpleName();
-
- private final KernelCpuProcReader mProcReader;
- private SparseArray<Double> mLastUidCpuActiveTimeMs = new SparseArray<>();
- private int mCores;
-
- public interface Callback extends KernelUidCpuTimeReaderBase.Callback {
- /**
- * Notifies when new data is available.
- *
- * @param uid uid int
- * @param cpuActiveTimeMs cpu active time spent by this uid in milliseconds
- */
- void onUidCpuActiveTime(int uid, long cpuActiveTimeMs);
- }
-
- public KernelUidCpuActiveTimeReader() {
- mProcReader = KernelCpuProcReader.getActiveTimeReaderInstance();
- }
-
- @VisibleForTesting
- public KernelUidCpuActiveTimeReader(KernelCpuProcReader procReader) {
- mProcReader = procReader;
- }
-
- @Override
- protected void readDeltaImpl(@Nullable Callback callback) {
- readImpl((buf) -> {
- int uid = buf.get();
- double activeTime = sumActiveTime(buf);
- if (activeTime > 0) {
- double delta = activeTime - mLastUidCpuActiveTimeMs.get(uid, 0.0);
- if (delta > 0) {
- mLastUidCpuActiveTimeMs.put(uid, activeTime);
- if (callback != null) {
- callback.onUidCpuActiveTime(uid, (long) delta);
- }
- } else if (delta < 0) {
- Slog.e(TAG, "Negative delta from active time proc: " + delta);
- }
- }
- });
- }
-
- public void readAbsolute(Callback callback) {
- readImpl((buf) -> {
- int uid = buf.get();
- double activeTime = sumActiveTime(buf);
- if (activeTime > 0) {
- callback.onUidCpuActiveTime(uid, (long) activeTime);
- }
- });
- }
-
- private double sumActiveTime(IntBuffer buffer) {
- double sum = 0;
- boolean corrupted = false;
- for (int j = 1; j <= mCores; j++) {
- int time = buffer.get();
- if (time < 0) {
- // Even if error happens, we still need to continue reading.
- // Buffer cannot be skipped.
- Slog.e(TAG, "Negative time from active time proc: " + time);
- corrupted = true;
- } else {
- sum += (double) time * 10 / j; // Unit is 10ms.
- }
- }
- return corrupted ? -1 : sum;
- }
-
- /**
- * readImpl accepts a callback to process the uid entry. readDeltaImpl needs to store the last
- * seen results while processing the buffer, while readAbsolute returns the absolute value read
- * from the buffer without storing. So readImpl contains the common logic of the two, leaving
- * the difference to a processUid function.
- *
- * @param processUid the callback function to process the uid entry in the buffer.
- */
- private void readImpl(Consumer<IntBuffer> processUid) {
- synchronized (mProcReader) {
- final ByteBuffer bytes = mProcReader.readBytes();
- if (bytes == null || bytes.remaining() <= 4) {
- // Error already logged in mProcReader.
- return;
- }
- if ((bytes.remaining() & 3) != 0) {
- Slog.wtf(TAG,
- "Cannot parse active time proc bytes to int: " + bytes.remaining());
- return;
- }
- final IntBuffer buf = bytes.asIntBuffer();
- final int cores = buf.get();
- if (mCores != 0 && cores != mCores) {
- Slog.wtf(TAG, "Cpu active time wrong # cores: " + cores);
- return;
- }
- mCores = cores;
- if (cores <= 0 || buf.remaining() % (cores + 1) != 0) {
- Slog.wtf(TAG,
- "Cpu active time format error: " + buf.remaining() + " / " + (cores
- + 1));
- return;
- }
- int numUids = buf.remaining() / (cores + 1);
- for (int i = 0; i < numUids; i++) {
- processUid.accept(buf);
- }
- if (DEBUG) {
- Slog.d(TAG, "Read uids: " + numUids);
- }
- }
- }
-
- public void removeUid(int uid) {
- mLastUidCpuActiveTimeMs.delete(uid);
- }
-
- public void removeUidsInRange(int startUid, int endUid) {
- mLastUidCpuActiveTimeMs.put(startUid, null);
- mLastUidCpuActiveTimeMs.put(endUid, null);
- final int firstIndex = mLastUidCpuActiveTimeMs.indexOfKey(startUid);
- final int lastIndex = mLastUidCpuActiveTimeMs.indexOfKey(endUid);
- mLastUidCpuActiveTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
- }
-}
diff --git a/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java
deleted file mode 100644
index 3cbfaea..0000000
--- a/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.os;
-
-import android.annotation.Nullable;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.nio.ByteBuffer;
-import java.nio.IntBuffer;
-import java.util.function.Consumer;
-
-/**
- * Reads binary proc file /proc/uid_cpupower/concurrent_policy_time and reports CPU cluster times
- * to BatteryStats to compute cluster power. See
- * {@link PowerProfile#getAveragePowerForCpuCluster(int)}.
- *
- * concurrent_policy_time is an array of u32's in the following format:
- * [n, x0, ..., xn, uid0, time0a, time0b, ..., time0n,
- * uid1, time1a, time1b, ..., time1n,
- * uid2, time2a, time2b, ..., time2n, etc.]
- * where n is the number of policies
- * xi is the number cpus on a particular policy
- * Each uidX is followed by x0 time entries corresponding to the time UID X spent on cluster0
- * running concurrently with 0, 1, 2, ..., x0 - 1 other processes, then followed by x1, ..., xn
- * time entries.
- *
- * The file contains a monotonically increasing count of time for a single boot. This class
- * maintains the previous results of a call to {@link #readDelta} in order to provide a
- * proper delta.
- *
- * This class uses a throttler to reject any {@link #readDelta} call within
- * {@link #mThrottleInterval}. This is different from the throttler in {@link KernelCpuProcReader},
- * which has a shorter throttle interval and returns cached result from last read when the request
- * is throttled.
- *
- * This class is NOT thread-safe and NOT designed to be accessed by more than one caller since each
- * caller has its own view of delta.
- */
-public class KernelUidCpuClusterTimeReader extends
- KernelUidCpuTimeReaderBase<KernelUidCpuClusterTimeReader.Callback> {
- private static final String TAG = KernelUidCpuClusterTimeReader.class.getSimpleName();
-
- private final KernelCpuProcReader mProcReader;
- private SparseArray<double[]> mLastUidPolicyTimeMs = new SparseArray<>();
-
- private int mNumClusters = -1;
- private int mNumCores;
- private int[] mNumCoresOnCluster;
-
- private double[] mCurTime; // Reuse to avoid GC.
- private long[] mDeltaTime; // Reuse to avoid GC.
- private long[] mCurTimeRounded; // Reuse to avoid GC.
-
- public interface Callback extends KernelUidCpuTimeReaderBase.Callback {
- /**
- * Notifies when new data is available.
- *
- * @param uid uid int
- * @param cpuClusterTimeMs an array of times spent by this uid on corresponding clusters.
- * The array index is the cluster index.
- */
- void onUidCpuPolicyTime(int uid, long[] cpuClusterTimeMs);
- }
-
- public KernelUidCpuClusterTimeReader() {
- mProcReader = KernelCpuProcReader.getClusterTimeReaderInstance();
- }
-
- @VisibleForTesting
- public KernelUidCpuClusterTimeReader(KernelCpuProcReader procReader) {
- mProcReader = procReader;
- }
-
- @Override
- protected void readDeltaImpl(@Nullable Callback cb) {
- readImpl((buf) -> {
- int uid = buf.get();
- double[] lastTimes = mLastUidPolicyTimeMs.get(uid);
- if (lastTimes == null) {
- lastTimes = new double[mNumClusters];
- mLastUidPolicyTimeMs.put(uid, lastTimes);
- }
- if (!sumClusterTime(buf, mCurTime)) {
- return;
- }
- boolean valid = true;
- boolean notify = false;
- for (int i = 0; i < mNumClusters; i++) {
- mDeltaTime[i] = (long) (mCurTime[i] - lastTimes[i]);
- if (mDeltaTime[i] < 0) {
- Slog.e(TAG, "Negative delta from cluster time proc: " + mDeltaTime[i]);
- valid = false;
- }
- notify |= mDeltaTime[i] > 0;
- }
- if (notify && valid) {
- System.arraycopy(mCurTime, 0, lastTimes, 0, mNumClusters);
- if (cb != null) {
- cb.onUidCpuPolicyTime(uid, mDeltaTime);
- }
- }
- });
- }
-
- public void readAbsolute(Callback callback) {
- readImpl((buf) -> {
- int uid = buf.get();
- if (sumClusterTime(buf, mCurTime)) {
- for (int i = 0; i < mNumClusters; i++) {
- mCurTimeRounded[i] = (long) mCurTime[i];
- }
- callback.onUidCpuPolicyTime(uid, mCurTimeRounded);
- }
- });
- }
-
- private boolean sumClusterTime(IntBuffer buffer, double[] clusterTime) {
- boolean valid = true;
- for (int i = 0; i < mNumClusters; i++) {
- clusterTime[i] = 0;
- for (int j = 1; j <= mNumCoresOnCluster[i]; j++) {
- int time = buffer.get();
- if (time < 0) {
- Slog.e(TAG, "Negative time from cluster time proc: " + time);
- valid = false;
- }
- clusterTime[i] += (double) time * 10 / j; // Unit is 10ms.
- }
- }
- return valid;
- }
-
- /**
- * readImpl accepts a callback to process the uid entry. readDeltaImpl needs to store the last
- * seen results while processing the buffer, while readAbsolute returns the absolute value read
- * from the buffer without storing. So readImpl contains the common logic of the two, leaving
- * the difference to a processUid function.
- *
- * @param processUid the callback function to process the uid entry in the buffer.
- */
- private void readImpl(Consumer<IntBuffer> processUid) {
- synchronized (mProcReader) {
- ByteBuffer bytes = mProcReader.readBytes();
- if (bytes == null || bytes.remaining() <= 4) {
- // Error already logged in mProcReader.
- return;
- }
- if ((bytes.remaining() & 3) != 0) {
- Slog.wtf(TAG,
- "Cannot parse cluster time proc bytes to int: " + bytes.remaining());
- return;
- }
- IntBuffer buf = bytes.asIntBuffer();
- final int numClusters = buf.get();
- if (numClusters <= 0) {
- Slog.wtf(TAG, "Cluster time format error: " + numClusters);
- return;
- }
- if (mNumClusters == -1) {
- mNumClusters = numClusters;
- }
- if (buf.remaining() < numClusters) {
- Slog.wtf(TAG, "Too few data left in the buffer: " + buf.remaining());
- return;
- }
- if (mNumCores <= 0) {
- if (!readCoreInfo(buf, numClusters)) {
- return;
- }
- } else {
- buf.position(buf.position() + numClusters);
- }
-
- if (buf.remaining() % (mNumCores + 1) != 0) {
- Slog.wtf(TAG,
- "Cluster time format error: " + buf.remaining() + " / " + (mNumCores
- + 1));
- return;
- }
- int numUids = buf.remaining() / (mNumCores + 1);
-
- for (int i = 0; i < numUids; i++) {
- processUid.accept(buf);
- }
- if (DEBUG) {
- Slog.d(TAG, "Read uids: " + numUids);
- }
- }
- }
-
- // Returns if it has read valid info.
- private boolean readCoreInfo(IntBuffer buf, int numClusters) {
- int numCores = 0;
- int[] numCoresOnCluster = new int[numClusters];
- for (int i = 0; i < numClusters; i++) {
- numCoresOnCluster[i] = buf.get();
- numCores += numCoresOnCluster[i];
- }
- if (numCores <= 0) {
- Slog.e(TAG, "Invalid # cores from cluster time proc file: " + numCores);
- return false;
- }
- mNumCores = numCores;
- mNumCoresOnCluster = numCoresOnCluster;
- mCurTime = new double[numClusters];
- mDeltaTime = new long[numClusters];
- mCurTimeRounded = new long[numClusters];
- return true;
- }
-
- public void removeUid(int uid) {
- mLastUidPolicyTimeMs.delete(uid);
- }
-
- public void removeUidsInRange(int startUid, int endUid) {
- mLastUidPolicyTimeMs.put(startUid, null);
- mLastUidPolicyTimeMs.put(endUid, null);
- final int firstIndex = mLastUidPolicyTimeMs.indexOfKey(startUid);
- final int lastIndex = mLastUidPolicyTimeMs.indexOfKey(endUid);
- mLastUidPolicyTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
- }
-}
diff --git a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
deleted file mode 100644
index 5b46d0f..0000000
--- a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
+++ /dev/null
@@ -1,296 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.os;
-
-import static com.android.internal.util.Preconditions.checkNotNull;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.StrictMode;
-import android.util.IntArray;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.IntBuffer;
-import java.util.function.Consumer;
-
-/**
- * Reads /proc/uid_time_in_state which has the format:
- *
- * uid: [freq1] [freq2] [freq3] ...
- * [uid1]: [time in freq1] [time in freq2] [time in freq3] ...
- * [uid2]: [time in freq1] [time in freq2] [time in freq3] ...
- * ...
- *
- * Binary variation reads /proc/uid_cpupower/time_in_state in the following format:
- * [n, uid0, time0a, time0b, ..., time0n,
- * uid1, time1a, time1b, ..., time1n,
- * uid2, time2a, time2b, ..., time2n, etc.]
- * where n is the total number of frequencies.
- *
- * This provides the times a UID's processes spent executing at each different cpu frequency.
- * The file contains a monotonically increasing count of time for a single boot. This class
- * maintains the previous results of a call to {@link #readDelta} in order to provide a proper
- * delta.
- *
- * This class uses a throttler to reject any {@link #readDelta} call within
- * {@link #mThrottleInterval}. This is different from the throttler in {@link KernelCpuProcReader},
- * which has a shorter throttle interval and returns cached result from last read when the request
- * is throttled.
- *
- * This class is NOT thread-safe and NOT designed to be accessed by more than one caller since each
- * caller has its own view of delta.
- */
-public class KernelUidCpuFreqTimeReader extends
- KernelUidCpuTimeReaderBase<KernelUidCpuFreqTimeReader.Callback> {
- private static final String TAG = KernelUidCpuFreqTimeReader.class.getSimpleName();
- static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
-
- public interface Callback extends KernelUidCpuTimeReaderBase.Callback {
- void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs);
- }
-
- private long[] mCpuFreqs;
- private long[] mCurTimes; // Reuse to prevent GC.
- private long[] mDeltaTimes; // Reuse to prevent GC.
- private int mCpuFreqsCount;
- private final KernelCpuProcReader mProcReader;
-
- private SparseArray<long[]> mLastUidCpuFreqTimeMs = new SparseArray<>();
-
- // We check the existence of proc file a few times (just in case it is not ready yet when we
- // start reading) and if it is not available, we simply ignore further read requests.
- private static final int TOTAL_READ_ERROR_COUNT = 5;
- private int mReadErrorCounter;
- private boolean mPerClusterTimesAvailable;
- private boolean mAllUidTimesAvailable = true;
-
- public KernelUidCpuFreqTimeReader() {
- mProcReader = KernelCpuProcReader.getFreqTimeReaderInstance();
- }
-
- @VisibleForTesting
- public KernelUidCpuFreqTimeReader(KernelCpuProcReader procReader) {
- mProcReader = procReader;
- }
-
- public boolean perClusterTimesAvailable() {
- return mPerClusterTimesAvailable;
- }
-
- public boolean allUidTimesAvailable() {
- return mAllUidTimesAvailable;
- }
-
- public SparseArray<long[]> getAllUidCpuFreqTimeMs() {
- return mLastUidCpuFreqTimeMs;
- }
-
- public long[] readFreqs(@NonNull PowerProfile powerProfile) {
- checkNotNull(powerProfile);
- if (mCpuFreqs != null) {
- // No need to read cpu freqs more than once.
- return mCpuFreqs;
- }
- if (!mAllUidTimesAvailable) {
- return null;
- }
- final int oldMask = StrictMode.allowThreadDiskReadsMask();
- try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
- return readFreqs(reader, powerProfile);
- } catch (IOException e) {
- if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
- mAllUidTimesAvailable = false;
- }
- Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
- return null;
- } finally {
- StrictMode.setThreadPolicyMask(oldMask);
- }
- }
-
- @VisibleForTesting
- public long[] readFreqs(BufferedReader reader, PowerProfile powerProfile)
- throws IOException {
- final String line = reader.readLine();
- if (line == null) {
- return null;
- }
- final String[] freqStr = line.split(" ");
- // First item would be "uid: " which needs to be ignored.
- mCpuFreqsCount = freqStr.length - 1;
- mCpuFreqs = new long[mCpuFreqsCount];
- mCurTimes = new long[mCpuFreqsCount];
- mDeltaTimes = new long[mCpuFreqsCount];
- for (int i = 0; i < mCpuFreqsCount; ++i) {
- mCpuFreqs[i] = Long.parseLong(freqStr[i + 1], 10);
- }
-
- // Check if the freqs in the proc file correspond to per-cluster freqs.
- final IntArray numClusterFreqs = extractClusterInfoFromProcFileFreqs();
- final int numClusters = powerProfile.getNumCpuClusters();
- if (numClusterFreqs.size() == numClusters) {
- mPerClusterTimesAvailable = true;
- for (int i = 0; i < numClusters; ++i) {
- if (numClusterFreqs.get(i) != powerProfile.getNumSpeedStepsInCpuCluster(i)) {
- mPerClusterTimesAvailable = false;
- break;
- }
- }
- } else {
- mPerClusterTimesAvailable = false;
- }
- Slog.i(TAG, "mPerClusterTimesAvailable=" + mPerClusterTimesAvailable);
- return mCpuFreqs;
- }
-
- @Override
- @VisibleForTesting
- public void readDeltaImpl(@Nullable Callback callback) {
- if (mCpuFreqs == null) {
- return;
- }
- readImpl((buf) -> {
- int uid = buf.get();
- long[] lastTimes = mLastUidCpuFreqTimeMs.get(uid);
- if (lastTimes == null) {
- lastTimes = new long[mCpuFreqsCount];
- mLastUidCpuFreqTimeMs.put(uid, lastTimes);
- }
- if (!getFreqTimeForUid(buf, mCurTimes)) {
- return;
- }
- boolean notify = false;
- boolean valid = true;
- for (int i = 0; i < mCpuFreqsCount; i++) {
- mDeltaTimes[i] = mCurTimes[i] - lastTimes[i];
- if (mDeltaTimes[i] < 0) {
- Slog.e(TAG, "Negative delta from freq time proc: " + mDeltaTimes[i]);
- valid = false;
- }
- notify |= mDeltaTimes[i] > 0;
- }
- if (notify && valid) {
- System.arraycopy(mCurTimes, 0, lastTimes, 0, mCpuFreqsCount);
- if (callback != null) {
- callback.onUidCpuFreqTime(uid, mDeltaTimes);
- }
- }
- });
- }
-
- public void readAbsolute(Callback callback) {
- readImpl((buf) -> {
- int uid = buf.get();
- if (getFreqTimeForUid(buf, mCurTimes)) {
- callback.onUidCpuFreqTime(uid, mCurTimes);
- }
- });
- }
-
- private boolean getFreqTimeForUid(IntBuffer buffer, long[] freqTime) {
- boolean valid = true;
- for (int i = 0; i < mCpuFreqsCount; i++) {
- freqTime[i] = (long) buffer.get() * 10; // Unit is 10ms.
- if (freqTime[i] < 0) {
- Slog.e(TAG, "Negative time from freq time proc: " + freqTime[i]);
- valid = false;
- }
- }
- return valid;
- }
-
- /**
- * readImpl accepts a callback to process the uid entry. readDeltaImpl needs to store the last
- * seen results while processing the buffer, while readAbsolute returns the absolute value read
- * from the buffer without storing. So readImpl contains the common logic of the two, leaving
- * the difference to a processUid function.
- *
- * @param processUid the callback function to process the uid entry in the buffer.
- */
- private void readImpl(Consumer<IntBuffer> processUid) {
- synchronized (mProcReader) {
- ByteBuffer bytes = mProcReader.readBytes();
- if (bytes == null || bytes.remaining() <= 4) {
- // Error already logged in mProcReader.
- return;
- }
- if ((bytes.remaining() & 3) != 0) {
- Slog.wtf(TAG, "Cannot parse freq time proc bytes to int: " + bytes.remaining());
- return;
- }
- IntBuffer buf = bytes.asIntBuffer();
- final int freqs = buf.get();
- if (freqs != mCpuFreqsCount) {
- Slog.wtf(TAG, "Cpu freqs expect " + mCpuFreqsCount + " , got " + freqs);
- return;
- }
- if (buf.remaining() % (freqs + 1) != 0) {
- Slog.wtf(TAG, "Freq time format error: " + buf.remaining() + " / " + (freqs + 1));
- return;
- }
- int numUids = buf.remaining() / (freqs + 1);
- for (int i = 0; i < numUids; i++) {
- processUid.accept(buf);
- }
- if (DEBUG) {
- Slog.d(TAG, "Read uids: #" + numUids);
- }
- }
- }
-
- public void removeUid(int uid) {
- mLastUidCpuFreqTimeMs.delete(uid);
- }
-
- public void removeUidsInRange(int startUid, int endUid) {
- mLastUidCpuFreqTimeMs.put(startUid, null);
- mLastUidCpuFreqTimeMs.put(endUid, null);
- final int firstIndex = mLastUidCpuFreqTimeMs.indexOfKey(startUid);
- final int lastIndex = mLastUidCpuFreqTimeMs.indexOfKey(endUid);
- mLastUidCpuFreqTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
- }
-
- /**
- * Extracts no. of cpu clusters and no. of freqs in each of these clusters from the freqs
- * read from the proc file.
- *
- * We need to assume that freqs in each cluster are strictly increasing.
- * For e.g. if the freqs read from proc file are: 12, 34, 15, 45, 12, 15, 52. Then it means
- * there are 3 clusters: (12, 34), (15, 45), (12, 15, 52)
- *
- * @return an IntArray filled with no. of freqs in each cluster.
- */
- private IntArray extractClusterInfoFromProcFileFreqs() {
- final IntArray numClusterFreqs = new IntArray();
- int freqsFound = 0;
- for (int i = 0; i < mCpuFreqsCount; ++i) {
- freqsFound++;
- if (i + 1 == mCpuFreqsCount || mCpuFreqs[i + 1] <= mCpuFreqs[i]) {
- numClusterFreqs.add(freqsFound);
- freqsFound = 0;
- }
- }
- return numClusterFreqs;
- }
-}
diff --git a/core/java/com/android/internal/os/KernelUidCpuTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
deleted file mode 100644
index 97b7211..0000000
--- a/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2015 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.internal.os;
-
-import android.annotation.Nullable;
-import android.os.StrictMode;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.Slog;
-import android.util.SparseLongArray;
-import android.util.TimeUtils;
-
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-
-/**
- * Reads /proc/uid_cputime/show_uid_stat which has the line format:
- *
- * uid: user_time_micro_seconds system_time_micro_seconds power_in_milli-amp-micro_seconds
- *
- * This provides the time a UID's processes spent executing in user-space and kernel-space.
- * The file contains a monotonically increasing count of time for a single boot. This class
- * maintains the previous results of a call to {@link #readDelta} in order to provide a proper
- * delta.
- */
-public class KernelUidCpuTimeReader extends
- KernelUidCpuTimeReaderBase<KernelUidCpuTimeReader.Callback> {
- private static final String TAG = KernelUidCpuTimeReader.class.getSimpleName();
- private static final String sProcFile = "/proc/uid_cputime/show_uid_stat";
- private static final String sRemoveUidProcFile = "/proc/uid_cputime/remove_uid_range";
-
- /**
- * Callback interface for processing each line of the proc file.
- */
- public interface Callback extends KernelUidCpuTimeReaderBase.Callback {
- /**
- * @param uid UID of the app
- * @param userTimeUs time spent executing in user space in microseconds
- * @param systemTimeUs time spent executing in kernel space in microseconds
- */
- void onUidCpuTime(int uid, long userTimeUs, long systemTimeUs);
- }
-
- private SparseLongArray mLastUserTimeUs = new SparseLongArray();
- private SparseLongArray mLastSystemTimeUs = new SparseLongArray();
- private long mLastTimeReadUs = 0;
-
- /**
- * Reads the proc file, calling into the callback with a delta of time for each UID.
- *
- * @param callback The callback to invoke for each line of the proc file. If null,
- * the data is consumed and subsequent calls to readDelta will provide
- * a fresh delta.
- */
- @Override
- protected void readDeltaImpl(@Nullable Callback callback) {
- final int oldMask = StrictMode.allowThreadDiskReadsMask();
- long nowUs = SystemClock.elapsedRealtime() * 1000;
- try (BufferedReader reader = new BufferedReader(new FileReader(sProcFile))) {
- TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' ');
- String line;
- while ((line = reader.readLine()) != null) {
- splitter.setString(line);
- final String uidStr = splitter.next();
- final int uid = Integer.parseInt(uidStr.substring(0, uidStr.length() - 1), 10);
- final long userTimeUs = Long.parseLong(splitter.next(), 10);
- final long systemTimeUs = Long.parseLong(splitter.next(), 10);
-
- boolean notifyCallback = false;
- long userTimeDeltaUs = userTimeUs;
- long systemTimeDeltaUs = systemTimeUs;
- // Only report if there is a callback and if this is not the first read.
- if (callback != null && mLastTimeReadUs != 0) {
- int index = mLastUserTimeUs.indexOfKey(uid);
- if (index >= 0) {
- userTimeDeltaUs -= mLastUserTimeUs.valueAt(index);
- systemTimeDeltaUs -= mLastSystemTimeUs.valueAt(index);
-
- final long timeDiffUs = nowUs - mLastTimeReadUs;
- if (userTimeDeltaUs < 0 || systemTimeDeltaUs < 0) {
- StringBuilder sb = new StringBuilder("Malformed cpu data for UID=");
- sb.append(uid).append("!\n");
- sb.append("Time between reads: ");
- TimeUtils.formatDuration(timeDiffUs / 1000, sb);
- sb.append("\n");
- sb.append("Previous times: u=");
- TimeUtils.formatDuration(mLastUserTimeUs.valueAt(index) / 1000, sb);
- sb.append(" s=");
- TimeUtils.formatDuration(mLastSystemTimeUs.valueAt(index) / 1000, sb);
-
- sb.append("\nCurrent times: u=");
- TimeUtils.formatDuration(userTimeUs / 1000, sb);
- sb.append(" s=");
- TimeUtils.formatDuration(systemTimeUs / 1000, sb);
- sb.append("\nDelta: u=");
- TimeUtils.formatDuration(userTimeDeltaUs / 1000, sb);
- sb.append(" s=");
- TimeUtils.formatDuration(systemTimeDeltaUs / 1000, sb);
- Slog.e(TAG, sb.toString());
-
- userTimeDeltaUs = 0;
- systemTimeDeltaUs = 0;
- }
- }
-
- notifyCallback = (userTimeDeltaUs != 0 || systemTimeDeltaUs != 0);
- }
- mLastUserTimeUs.put(uid, userTimeUs);
- mLastSystemTimeUs.put(uid, systemTimeUs);
- if (notifyCallback) {
- callback.onUidCpuTime(uid, userTimeDeltaUs, systemTimeDeltaUs);
- }
- }
- } catch (IOException e) {
- Slog.e(TAG, "Failed to read uid_cputime: " + e.getMessage());
- } finally {
- StrictMode.setThreadPolicyMask(oldMask);
- }
- mLastTimeReadUs = nowUs;
- }
-
- /**
- * Reads the proc file, calling into the callback with raw absolute value of time for each UID.
- * @param callback The callback to invoke for each line of the proc file.
- */
- public void readAbsolute(Callback callback) {
- final int oldMask = StrictMode.allowThreadDiskReadsMask();
- try (BufferedReader reader = new BufferedReader(new FileReader(sProcFile))) {
- TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' ');
- String line;
- while ((line = reader.readLine()) != null) {
- splitter.setString(line);
- final String uidStr = splitter.next();
- final int uid = Integer.parseInt(uidStr.substring(0, uidStr.length() - 1), 10);
- final long userTimeUs = Long.parseLong(splitter.next(), 10);
- final long systemTimeUs = Long.parseLong(splitter.next(), 10);
- callback.onUidCpuTime(uid, userTimeUs, systemTimeUs);
- }
- } catch (IOException e) {
- Slog.e(TAG, "Failed to read uid_cputime: " + e.getMessage());
- } finally {
- StrictMode.setThreadPolicyMask(oldMask);
- }
- }
-
- /**
- * Removes the UID from the kernel module and from internal accounting data. Only
- * {@link BatteryStatsImpl} and its child processes should call this, as the change on Kernel is
- * visible system wide.
- *
- * @param uid The UID to remove.
- */
- public void removeUid(int uid) {
- final int index = mLastSystemTimeUs.indexOfKey(uid);
- if (index >= 0) {
- mLastSystemTimeUs.removeAt(index);
- mLastUserTimeUs.removeAt(index);
- }
- removeUidsFromKernelModule(uid, uid);
- }
-
- /**
- * Removes UIDs in a given range from the kernel module and internal accounting data. Only
- * {@link BatteryStatsImpl} and its child processes should call this, as the change on Kernel is
- * visible system wide.
- *
- * @param startUid the first uid to remove
- * @param endUid the last uid to remove
- */
- public void removeUidsInRange(int startUid, int endUid) {
- if (endUid < startUid) {
- return;
- }
- mLastSystemTimeUs.put(startUid, 0);
- mLastUserTimeUs.put(startUid, 0);
- mLastSystemTimeUs.put(endUid, 0);
- mLastUserTimeUs.put(endUid, 0);
- final int startIndex = mLastSystemTimeUs.indexOfKey(startUid);
- final int endIndex = mLastSystemTimeUs.indexOfKey(endUid);
- mLastSystemTimeUs.removeAtRange(startIndex, endIndex - startIndex + 1);
- mLastUserTimeUs.removeAtRange(startIndex, endIndex - startIndex + 1);
- removeUidsFromKernelModule(startUid, endUid);
- }
-
- private void removeUidsFromKernelModule(int startUid, int endUid) {
- Slog.d(TAG, "Removing uids " + startUid + "-" + endUid);
- final int oldMask = StrictMode.allowThreadDiskWritesMask();
- try (FileWriter writer = new FileWriter(sRemoveUidProcFile)) {
- writer.write(startUid + "-" + endUid);
- writer.flush();
- } catch (IOException e) {
- Slog.e(TAG, "failed to remove uids " + startUid + " - " + endUid
- + " from uid_cputime module", e);
- } finally {
- StrictMode.setThreadPolicyMask(oldMask);
- }
- }
-}
diff --git a/core/java/com/android/internal/os/KernelUidCpuTimeReaderBase.java b/core/java/com/android/internal/os/KernelUidCpuTimeReaderBase.java
deleted file mode 100644
index 11e50e1..0000000
--- a/core/java/com/android/internal/os/KernelUidCpuTimeReaderBase.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.os;
-
-import android.annotation.Nullable;
-import android.os.SystemClock;
-import android.util.Slog;
-
-/**
- * The base class of all KernelUidCpuTimeReaders.
- *
- * This class is NOT designed to be thread-safe or accessed by more than one caller (due to
- * the nature of {@link #readDelta(Callback)}).
- */
-public abstract class KernelUidCpuTimeReaderBase<T extends KernelUidCpuTimeReaderBase.Callback> {
- protected static final boolean DEBUG = false;
- // Throttle interval in milliseconds
- private static final long DEFAULT_THROTTLE_INTERVAL = 10_000L;
-
- private final String TAG = this.getClass().getSimpleName();
- private long mLastTimeReadMs = Long.MIN_VALUE;
- private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL;
-
- // A generic Callback interface (used by readDelta) to be extended by subclasses.
- public interface Callback {
- }
-
- public void readDelta(@Nullable T cb) {
- if (SystemClock.elapsedRealtime() < mLastTimeReadMs + mThrottleInterval) {
- if (DEBUG) {
- Slog.d(TAG, "Throttle");
- }
- return;
- }
- readDeltaImpl(cb);
- mLastTimeReadMs = SystemClock.elapsedRealtime();
- }
-
- protected abstract void readDeltaImpl(@Nullable T cb);
-
- public void setThrottleInterval(long throttleInterval) {
- if (throttleInterval >= 0) {
- mThrottleInterval = throttleInterval;
- }
- }
-}
diff --git a/core/java/com/android/internal/os/LooperStats.java b/core/java/com/android/internal/os/LooperStats.java
index 9a7fb9f..0f0eedd 100644
--- a/core/java/com/android/internal/os/LooperStats.java
+++ b/core/java/com/android/internal/os/LooperStats.java
@@ -37,6 +37,7 @@
* @hide Only for use within the system server.
*/
public class LooperStats implements Looper.Observer {
+ public static final String DEBUG_ENTRY_PREFIX = "__DEBUG_";
private static final int SESSION_POOL_SIZE = 50;
@GuardedBy("mLock")
@@ -165,7 +166,7 @@
}
private ExportedEntry createDebugEntry(String variableName, long value) {
- final Entry entry = new Entry("__DEBUG_" + variableName);
+ final Entry entry = new Entry(DEBUG_ENTRY_PREFIX + variableName);
entry.messageCount = 1;
entry.recordedMessageCount = 1;
entry.totalLatencyMicro = value;
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 705bae4..f609f2f 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -16,13 +16,33 @@
package com.android.internal.os;
+import static android.system.OsConstants.O_CLOEXEC;
+
+import static com.android.internal.os.ZygoteConnectionConstants.MAX_ZYGOTE_ARGC;
+
+import android.net.Credentials;
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+import android.os.FactoryTest;
import android.os.IVold;
+import android.os.Process;
+import android.os.SystemProperties;
import android.os.Trace;
import android.system.ErrnoException;
import android.system.Os;
+import android.util.Log;
import dalvik.system.ZygoteHooks;
+import libcore.io.IoUtils;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
/** @hide */
public final class Zygote {
/*
@@ -94,6 +114,18 @@
/** Read-write external storage should be mounted instead of package sandbox */
public static final int MOUNT_EXTERNAL_FULL = IVold.REMOUNT_MODE_FULL;
+ /** 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 */
+ public static final boolean BLASTULA_POOL_ENABLED = false;
+
+ /**
+ * File descriptor used for communication between the signal handler and the ZygoteServer poll
+ * loop.
+ * */
+ protected static FileDescriptor sBlastulaPoolEventFD;
+
private static final ZygoteHooks VM_HOOKS = new ZygoteHooks();
/**
@@ -123,10 +155,47 @@
*/
public static final String CHILD_ZYGOTE_UID_RANGE_END = "--uid-range-end=";
+ /** Prefix prepended to socket names created by init */
+ private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_";
+
+ /**
+ * The maximum value that the sBlastulaPoolMax variable may take. This value
+ * is a mirror of BLASTULA_POOL_MAX_LIMIT found in com_android_internal_os_Zygote.cpp.
+ */
+ static final int BLASTULA_POOL_MAX_LIMIT = 10;
+
+ /**
+ * The minimum value that the sBlastulaPoolMin variable may take.
+ */
+ static final int BLASTULA_POOL_MIN_LIMIT = 1;
+
+ /**
+ * The runtime-adjustable maximum Blastula pool size.
+ */
+ static int sBlastulaPoolMax = BLASTULA_POOL_MAX_LIMIT;
+
+ /**
+ * The runtime-adjustable minimum Blastula pool size.
+ */
+ static int sBlastulaPoolMin = BLASTULA_POOL_MIN_LIMIT;
+
+ /**
+ * The runtime-adjustable value used to determine when to re-fill the
+ * blastula pool. The pool will be re-filled when
+ * (sBlastulaPoolMax - gBlastulaPoolCount) >= sBlastulaPoolRefillThreshold.
+ */
+ // TODO (chriswailes): This must be updated at the same time as sBlastulaPoolMax.
+ static int sBlastulaPoolRefillThreshold = (sBlastulaPoolMax / 2);
+
+ private static LocalServerSocket sBlastulaPoolSocket = null;
+
+ /** a prototype instance for a future List.toArray() */
+ protected static final int[][] INT_ARRAY_2D = new int[0][0];
+
private Zygote() {}
/** Called for some security initialization before any fork. */
- native static void nativeSecurityInit();
+ static native void nativeSecurityInit();
/**
* Forks a new VM instance. The current VM must have been started
@@ -170,9 +239,9 @@
// 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);
+ uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
+ fdsToIgnore, startChildZygote, instructionSet, appDataDir, packageName,
+ packagesForUid, visibleVolIds);
// Enable tracing as soon as possible for the child process.
if (pid == 0) {
Trace.setTracingEnabled(true, runtimeFlags);
@@ -184,15 +253,65 @@
return pid;
}
- native private static 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);
+ 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);
+
+ /**
+ * Specialize a Blastula instance. The current VM must have been started
+ * with the -Xzygote flag.
+ *
+ * @param uid The UNIX uid that the new process should setuid() to before spawning any threads
+ * @param gid The UNIX gid that the new process should setgid() to before spawning any threads
+ * @param gids null-ok; A list of UNIX gids that the new process should
+ * setgroups() to before spawning any threads
+ * @param runtimeFlags Bit flags that enable ART features
+ * @param rlimits null-ok An array of rlimit tuples, with the second
+ * dimension having a length of 3 and representing
+ * (resource, rlim_cur, rlim_max). These are set via the posix
+ * setrlimit(2) call.
+ * @param seInfo null-ok A string specifying SELinux information for
+ * the new process.
+ * @param niceName null-ok A string specifying the process name.
+ * @param startChildZygote If true, the new child process will itself be a
+ * new zygote process.
+ * @param instructionSet null-ok The instruction set to use.
+ * @param appDataDir null-ok The data directory of the app.
+ */
+ 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) {
+
+ nativeSpecializeBlastula(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo,
+ niceName, startChildZygote, instructionSet, appDataDir,
+ packageName, packagesForUid, visibleVolIds);
+
+ // Enable tracing as soon as possible for the child process.
+ Trace.setTracingEnabled(true, runtimeFlags);
+
+ // Note that this event ends at the end of handleChildProc.
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
+
+ /*
+ * This is called here (instead of after the fork but before the specialize) to maintain
+ * consistancy with the code paths for forkAndSpecialize.
+ *
+ * TODO (chriswailes): Look into moving this to immediately after the fork.
+ */
+ VM_HOOKS.postForkCommon();
+ }
+
+ 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);
/**
* Called to do any initialization before starting an application.
*/
- native static void nativePreApplicationInit();
+ static native void nativePreApplicationInit();
/**
* Special method to start the system server process. In addition to the
@@ -223,7 +342,8 @@
// Resets nice priority for zygote process.
resetNicePriority();
int pid = nativeForkSystemServer(
- uid, gid, gids, runtimeFlags, rlimits, permittedCapabilities, effectiveCapabilities);
+ uid, gid, gids, runtimeFlags, rlimits,
+ permittedCapabilities, effectiveCapabilities);
// Enable tracing as soon as we enter the system_server.
if (pid == 0) {
Trace.setTracingEnabled(true, runtimeFlags);
@@ -232,13 +352,13 @@
return pid;
}
- native private static int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
+ private static native int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, long permittedCapabilities, long effectiveCapabilities);
/**
* Lets children of the zygote inherit open file descriptors to this path.
*/
- native protected static void nativeAllowFileAcrossFork(String path);
+ protected static native void nativeAllowFileAcrossFork(String path);
/**
* Installs a seccomp filter that limits setresuid()/setresgid() to the passed-in range
@@ -251,7 +371,485 @@
* Zygote unmount storage space on initializing.
* This method is called once.
*/
- native protected static void nativeUnmountStorageOnInit();
+ protected static native void nativeUnmountStorageOnInit();
+
+ /**
+ * Get socket file descriptors (opened by init) from the environment and
+ * store them for access from native code later.
+ *
+ * @param isPrimary True if this is the zygote process, false if it is zygote_secondary
+ */
+ public static void getSocketFDs(boolean isPrimary) {
+ nativeGetSocketFDs(isPrimary);
+ }
+
+ protected static native void nativeGetSocketFDs(boolean isPrimary);
+
+ /**
+ * Initialize the blastula pool and fill it with the desired number of
+ * processes.
+ */
+ protected static Runnable initBlastulaPool() {
+ if (BLASTULA_POOL_ENABLED) {
+ sBlastulaPoolEventFD = getBlastulaPoolEventFD();
+
+ return fillBlastulaPool(null);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Checks to see if the current policy says that pool should be refilled, and spawns new
+ * blastulas if necessary.
+ *
+ * NOTE: This function doesn't need to be guarded with BLASTULA_POOL_ENABLED because it is
+ * only called from contexts that are only valid if the pool is enabled.
+ *
+ * @param sessionSocketRawFDs Anonymous session sockets that are currently open
+ * @return In the Zygote process this function will always return null; in blastula processes
+ * this function will return a Runnable object representing the new application that is
+ * passed up from blastulaMain.
+ */
+ protected static Runnable fillBlastulaPool(int[] sessionSocketRawFDs) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Zygote:FillBlastulaPool");
+
+ int blastulaPoolCount = getBlastulaPoolCount();
+
+ int numBlastulasToSpawn = sBlastulaPoolMax - blastulaPoolCount;
+
+ if (blastulaPoolCount < sBlastulaPoolMin
+ || numBlastulasToSpawn >= sBlastulaPoolRefillThreshold) {
+
+ // Disable some VM functionality and reset some system values
+ // before forking.
+ VM_HOOKS.preFork();
+ resetNicePriority();
+
+ while (blastulaPoolCount++ < sBlastulaPoolMax) {
+ Runnable caller = forkBlastula(sessionSocketRawFDs);
+
+ if (caller != null) {
+ return caller;
+ }
+ }
+
+ // Re-enable runtime services for the Zygote. Blastula services
+ // are re-enabled in specializeBlastula.
+ VM_HOOKS.postForkCommon();
+
+ Log.i("zygote", "Filled the blastula pool. New blastulas: " + numBlastulasToSpawn);
+ }
+
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+ return null;
+ }
+
+ /**
+ * @return Number of blastulas currently in the pool
+ */
+ private static int getBlastulaPoolCount() {
+ return nativeGetBlastulaPoolCount();
+ }
+
+ private static native int nativeGetBlastulaPoolCount();
+
+ /**
+ * @return The event FD used for communication between the signal handler and the ZygoteServer
+ * poll loop
+ */
+ private static FileDescriptor getBlastulaPoolEventFD() {
+ FileDescriptor fd = new FileDescriptor();
+ fd.setInt$(nativeGetBlastulaPoolEventFD());
+
+ return fd;
+ }
+
+ private static native int nativeGetBlastulaPoolEventFD();
+
+ /**
+ * Fork a new blastula process from the zygote
+ *
+ * @param sessionSocketRawFDs Anonymous session sockets that are currently open
+ * @return In the Zygote process this function will always return null; in blastula processes
+ * this function will return a Runnable object representing the new application that is
+ * passed up from blastulaMain.
+ */
+ private static Runnable forkBlastula(int[] sessionSocketRawFDs) {
+ FileDescriptor[] pipeFDs = null;
+
+ try {
+ pipeFDs = Os.pipe2(O_CLOEXEC);
+ } catch (ErrnoException errnoEx) {
+ throw new IllegalStateException("Unable to create blastula pipe.", errnoEx);
+ }
+
+ int pid =
+ nativeForkBlastula(pipeFDs[0].getInt$(), pipeFDs[1].getInt$(), sessionSocketRawFDs);
+
+ if (pid == 0) {
+ IoUtils.closeQuietly(pipeFDs[0]);
+ return blastulaMain(pipeFDs[1]);
+ } else {
+ // The read-end of the pipe will be closed by the native code.
+ // See removeBlastulaTableEntry();
+ IoUtils.closeQuietly(pipeFDs[1]);
+ return null;
+ }
+ }
+
+ private static native int nativeForkBlastula(int readPipeFD,
+ int writePipeFD,
+ int[] sessionSocketRawFDs);
+
+ /**
+ * This function is used by blastulas to wait for specialization requests from the system
+ * server.
+ *
+ * @param writePipe The write end of the reporting pipe used to communicate with the poll loop
+ * of the ZygoteServer.
+ * @return A runnable oject representing the new application.
+ */
+ static Runnable blastulaMain(FileDescriptor writePipe) {
+ final int pid = Process.myPid();
+
+ LocalSocket sessionSocket = null;
+ DataOutputStream blastulaOutputStream = null;
+ Credentials peerCredentials = null;
+ String[] argStrings = null;
+
+ while (true) {
+ try {
+ sessionSocket = sBlastulaPoolSocket.accept();
+
+ BufferedReader blastulaReader =
+ new BufferedReader(new InputStreamReader(sessionSocket.getInputStream()));
+ blastulaOutputStream =
+ new DataOutputStream(sessionSocket.getOutputStream());
+
+ peerCredentials = sessionSocket.getPeerCredentials();
+
+ argStrings = readArgumentList(blastulaReader);
+
+ if (argStrings != null) {
+ break;
+ } else {
+ Log.e("Blastula", "Truncated command received.");
+ IoUtils.closeQuietly(sessionSocket);
+ }
+ } catch (IOException ioEx) {
+ Log.e("Blastula", "Failed to read command: " + ioEx.getMessage());
+ IoUtils.closeQuietly(sessionSocket);
+ }
+ }
+
+ ZygoteArguments args = new ZygoteArguments(argStrings);
+
+ // TODO (chriswailes): Should this only be run for debug builds?
+ validateBlastulaCommand(args);
+
+ applyUidSecurityPolicy(args, peerCredentials);
+ applyDebuggerSystemProperty(args);
+
+ int[][] rlimits = null;
+
+ if (args.mRLimits != null) {
+ rlimits = args.mRLimits.toArray(INT_ARRAY_2D);
+ }
+
+ // This must happen before the SELinux policy for this process is
+ // changed when specializing.
+ try {
+ // Used by ZygoteProcess.zygoteSendArgsAndGetResult to fill in a
+ // Process.ProcessStartResult object.
+ blastulaOutputStream.writeInt(pid);
+ } catch (IOException ioEx) {
+ Log.e("Blastula", "Failed to write response to session socket: " + ioEx.getMessage());
+ System.exit(-1);
+ } finally {
+ IoUtils.closeQuietly(sessionSocket);
+ IoUtils.closeQuietly(sBlastulaPoolSocket);
+ }
+
+ try {
+ ByteArrayOutputStream buffer =
+ new ByteArrayOutputStream(Zygote.BLASTULA_MANAGEMENT_MESSAGE_BYTES);
+ DataOutputStream outputStream = new DataOutputStream(buffer);
+
+ // This is written as a long so that the blastula reporting pipe and blastula pool
+ // event FD handlers in ZygoteServer.runSelectLoop can be unified. These two cases
+ // should both send/receive 8 bytes.
+ outputStream.writeLong(pid);
+ outputStream.flush();
+
+ Os.write(writePipe, buffer.toByteArray(), 0, buffer.size());
+ } catch (Exception ex) {
+ Log.e("Blastula",
+ String.format("Failed to write PID (%d) to pipe (%d): %s",
+ pid, writePipe.getInt$(), ex.getMessage()));
+ System.exit(-1);
+ } finally {
+ IoUtils.closeQuietly(writePipe);
+ }
+
+ specializeBlastula(args.mUid, args.mGid, args.mGids,
+ args.mRuntimeFlags, rlimits, args.mMountExternal,
+ args.mSeInfo, args.mNiceName, args.mStartChildZygote,
+ args.mInstructionSet, args.mAppDataDir, args.mPackageName,
+ args.mPackagesForUid, args.mVisibleVolIds);
+
+ if (args.mNiceName != null) {
+ Process.setArgV0(args.mNiceName);
+ }
+
+ // End of the postFork event.
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+ return ZygoteInit.zygoteInit(args.mTargetSdkVersion,
+ args.mRemainingArgs,
+ null /* classLoader */);
+ }
+
+ private static final String BLASTULA_ERROR_PREFIX = "Invalid command to blastula: ";
+
+ /**
+ * Checks a set of zygote arguments to see if they can be handled by a blastula. Throws an
+ * exception if an invalid arugment is encountered.
+ * @param args The arguments to test
+ */
+ static void validateBlastulaCommand(ZygoteArguments args) {
+ if (args.mAbiListQuery) {
+ throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--query-abi-list");
+ } else if (args.mPidQuery) {
+ throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--get-pid");
+ } else if (args.mPreloadDefault) {
+ throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--preload-default");
+ } else if (args.mPreloadPackage != null) {
+ throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--preload-package");
+ } else if (args.mPreloadApp != null) {
+ throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--preload-app");
+ } else if (args.mStartChildZygote) {
+ throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--start-child-zygote");
+ } else if (args.mApiBlacklistExemptions != null) {
+ throw new IllegalArgumentException(
+ BLASTULA_ERROR_PREFIX + "--set-api-blacklist-exemptions");
+ } else if (args.mHiddenApiAccessLogSampleRate != -1) {
+ throw new IllegalArgumentException(
+ BLASTULA_ERROR_PREFIX + "--hidden-api-log-sampling-rate=");
+ } else if (args.mInvokeWith != null) {
+ throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--invoke-with");
+ } else if (args.mPermittedCapabilities != 0 || args.mEffectiveCapabilities != 0) {
+ throw new ZygoteSecurityException("Client may not specify capabilities: "
+ + "permitted=0x" + Long.toHexString(args.mPermittedCapabilities)
+ + ", effective=0x" + Long.toHexString(args.mEffectiveCapabilities));
+ }
+ }
+
+ /**
+ * @return Raw file descriptors for the read-end of blastula reporting pipes.
+ */
+ protected static int[] getBlastulaPipeFDs() {
+ return nativeGetBlastulaPipeFDs();
+ }
+
+ private static native int[] nativeGetBlastulaPipeFDs();
+
+ /**
+ * Remove the blastula table entry for the provided process ID.
+ *
+ * @param blastulaPID Process ID of the entry to remove
+ * @return True if the entry was removed; false if it doesn't exist
+ */
+ protected static boolean removeBlastulaTableEntry(int blastulaPID) {
+ return nativeRemoveBlastulaTableEntry(blastulaPID);
+ }
+
+ private static native boolean nativeRemoveBlastulaTableEntry(int blastulaPID);
+
+ /**
+ * uid 1000 (Process.SYSTEM_UID) may specify any uid > 1000 in normal
+ * operation. It may also specify any gid and setgroups() list it chooses.
+ * In factory test mode, it may specify any UID.
+ *
+ * @param args non-null; zygote spawner arguments
+ * @param peer non-null; peer credentials
+ * @throws ZygoteSecurityException
+ */
+ protected static void applyUidSecurityPolicy(ZygoteArguments args, Credentials peer)
+ throws ZygoteSecurityException {
+
+ if (peer.getUid() == Process.SYSTEM_UID) {
+ /* In normal operation, SYSTEM_UID can only specify a restricted
+ * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid.
+ */
+ boolean uidRestricted = FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF;
+
+ if (uidRestricted && args.mUidSpecified && (args.mUid < Process.SYSTEM_UID)) {
+ throw new ZygoteSecurityException(
+ "System UID may not launch process with UID < "
+ + Process.SYSTEM_UID);
+ }
+ }
+
+ // If not otherwise specified, uid and gid are inherited from peer
+ if (!args.mUidSpecified) {
+ args.mUid = peer.getUid();
+ args.mUidSpecified = true;
+ }
+ if (!args.mGidSpecified) {
+ args.mGid = peer.getGid();
+ args.mGidSpecified = true;
+ }
+ }
+
+ /**
+ * Applies debugger system properties to the zygote arguments.
+ *
+ * If "ro.debuggable" is "1", all apps are debuggable. Otherwise,
+ * the debugger state is specified via the "--enable-jdwp" flag
+ * in the spawn request.
+ *
+ * @param args non-null; zygote spawner args
+ */
+ protected static void applyDebuggerSystemProperty(ZygoteArguments args) {
+ if (RoSystemProperties.DEBUGGABLE) {
+ args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_JDWP;
+ }
+ }
+
+ /**
+ * Applies zygote security policy.
+ * Based on the credentials of the process issuing a zygote command:
+ * <ol>
+ * <li> uid 0 (root) may specify --invoke-with to launch Zygote with a
+ * wrapper command.
+ * <li> Any other uid may not specify any invoke-with argument.
+ * </ul>
+ *
+ * @param args non-null; zygote spawner arguments
+ * @param peer non-null; peer credentials
+ * @throws ZygoteSecurityException
+ */
+ protected static void applyInvokeWithSecurityPolicy(ZygoteArguments args, Credentials peer)
+ throws ZygoteSecurityException {
+ int peerUid = peer.getUid();
+
+ if (args.mInvokeWith != null && peerUid != 0
+ && (args.mRuntimeFlags & Zygote.DEBUG_ENABLE_JDWP) == 0) {
+ throw new ZygoteSecurityException("Peer is permitted to specify an"
+ + "explicit invoke-with wrapper command only for debuggable"
+ + "applications.");
+ }
+ }
+
+ /**
+ * Applies invoke-with system properties to the zygote arguments.
+ *
+ * @param args non-null; zygote args
+ */
+ protected static void applyInvokeWithSystemProperty(ZygoteArguments args) {
+ if (args.mInvokeWith == null && args.mNiceName != null) {
+ String property = "wrap." + args.mNiceName;
+ args.mInvokeWith = SystemProperties.get(property);
+ if (args.mInvokeWith != null && args.mInvokeWith.length() == 0) {
+ args.mInvokeWith = null;
+ }
+ }
+ }
+
+ /**
+ * Reads an argument list from the provided socket
+ * @return Argument list or null if EOF is reached
+ * @throws IOException passed straight through
+ */
+ static String[] readArgumentList(BufferedReader socketReader) throws IOException {
+
+ /**
+ * See android.os.Process.zygoteSendArgsAndGetPid()
+ * 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.
+ */
+
+ int argc;
+
+ try {
+ String argc_string = socketReader.readLine();
+
+ if (argc_string == null) {
+ // EOF reached.
+ return null;
+ }
+ argc = Integer.parseInt(argc_string);
+
+ } catch (NumberFormatException ex) {
+ Log.e("Zygote", "Invalid Zygote wire format: non-int at argc");
+ throw new IOException("Invalid wire format");
+ }
+
+ // See bug 1092107: large argc can be used for a DOS attack
+ if (argc > MAX_ZYGOTE_ARGC) {
+ throw new IOException("Max arg count exceeded");
+ }
+
+ String[] args = new String[argc];
+ for (int arg_index = 0; arg_index < argc; arg_index++) {
+ args[arg_index] = socketReader.readLine();
+ if (args[arg_index] == null) {
+ // We got an unexpected EOF.
+ throw new IOException("Truncated request");
+ }
+ }
+
+ return args;
+ }
+
+ /**
+ * Creates a managed object representing the Blastula pool 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
+ */
+ static void createBlastulaSocket(String socketName) {
+ if (BLASTULA_POOL_ENABLED && sBlastulaPoolSocket == null) {
+ sBlastulaPoolSocket = createManagedSocketFromInitSocket(socketName);
+ }
+ }
+
+ /**
+ * Creates a managed LocalServerSocket object using a file descriptor
+ * created by an init.rc script. The init scripts that specify the
+ * sockets name can be found in system/core/rootdir. The socket is bound
+ * to the file system in the /dev/sockets/ directory, and the file
+ * descriptor is shared via the ANDROID_SOCKET_<socketName> environment
+ * variable.
+ */
+ static LocalServerSocket createManagedSocketFromInitSocket(String socketName) {
+ 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("Socket unset or invalid: " + fullSocketName, ex);
+ }
+
+ try {
+ FileDescriptor fd = new FileDescriptor();
+ fd.setInt$(fileDesc);
+ return new LocalServerSocket(fd);
+ } catch (IOException ex) {
+ throw new RuntimeException(
+ "Error building socket from file descriptor: " + fileDesc, ex);
+ }
+ }
private static void callPostForkSystemServerHooks() {
// SystemServer specific post fork hooks run before child post fork hooks.
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
new file mode 100644
index 0000000..df89b26
--- /dev/null
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Handles argument parsing for args related to the zygote spawner.
+ *
+ * Current recognized args:
+ * <ul>
+ * <li> --setuid=<i>uid of child process, defaults to 0</i>
+ * <li> --setgid=<i>gid of child process, defaults to 0</i>
+ * <li> --setgroups=<i>comma-separated list of supplimentary gid's</i>
+ * <li> --capabilities=<i>a pair of comma-separated integer strings
+ * indicating Linux capabilities(2) set for child. The first string
+ * represents the <code>permitted</code> set, and the second the
+ * <code>effective</code> set. Precede each with 0 or
+ * 0x for octal or hexidecimal value. If unspecified, both default to 0.
+ * This parameter is only applied if the uid of the new process will
+ * be non-0. </i>
+ * <li> --rlimit=r,c,m<i>tuple of values for setrlimit() call.
+ * <code>r</code> is the resource, <code>c</code> and <code>m</code>
+ * are the settings for current and max value.</i>
+ * <li> --instruction-set=<i>instruction-set-string</i> which instruction set to use/emulate.
+ * <li> --nice-name=<i>nice name to appear in ps</i>
+ * <li> --package-name=<i>package name this process belongs to</i>
+ * <li> --runtime-args indicates that the remaining arg list should
+ * be handed off to com.android.internal.os.RuntimeInit, rather than
+ * processed directly.
+ * Android runtime startup (eg, Binder initialization) is also eschewed.
+ * <li> [--] <args for RuntimeInit >
+ * </ul>
+ */
+class ZygoteArguments {
+
+ /**
+ * from --setuid
+ */
+ int mUid = 0;
+ boolean mUidSpecified;
+
+ /**
+ * from --setgid
+ */
+ int mGid = 0;
+ boolean mGidSpecified;
+
+ /**
+ * from --setgroups
+ */
+ int[] mGids;
+
+ /**
+ * From --runtime-flags.
+ */
+ int mRuntimeFlags;
+
+ /**
+ * From --mount-external
+ */
+ int mMountExternal = Zygote.MOUNT_EXTERNAL_NONE;
+
+ /**
+ * from --target-sdk-version.
+ */
+ int mTargetSdkVersion;
+ boolean mTargetSdkVersionSpecified;
+
+ /**
+ * from --nice-name
+ */
+ String mNiceName;
+
+ /**
+ * from --capabilities
+ */
+ boolean mCapabilitiesSpecified;
+ long mPermittedCapabilities;
+ long mEffectiveCapabilities;
+
+ /**
+ * from --seinfo
+ */
+ boolean mSeInfoSpecified;
+ String mSeInfo;
+
+ /**
+ * from all --rlimit=r,c,m
+ */
+ ArrayList<int[]> mRLimits;
+
+ /**
+ * from --invoke-with
+ */
+ String mInvokeWith;
+
+ /** from --package-name */
+ String mPackageName;
+
+ /** from --packages-for-uid */
+ String[] mPackagesForUid;
+
+ /** from --visible-vols */
+ String[] mVisibleVolIds;
+
+ /**
+ * Any args after and including the first non-option arg (or after a '--')
+ */
+ String[] mRemainingArgs;
+
+ /**
+ * Whether the current arguments constitute an ABI list query.
+ */
+ boolean mAbiListQuery;
+
+ /**
+ * The instruction set to use, or null when not important.
+ */
+ String mInstructionSet;
+
+ /**
+ * The app data directory. May be null, e.g., for the system server. Note that this might not be
+ * reliable in the case of process-sharing apps.
+ */
+ String mAppDataDir;
+
+ /**
+ * The APK path of the package to preload, when using --preload-package.
+ */
+ String mPreloadPackage;
+
+ /**
+ * A Base64 string representing a serialize ApplicationInfo Parcel,
+ when using --preload-app.
+ */
+ String mPreloadApp;
+
+ /**
+ * The native library path of the package to preload, when using --preload-package.
+ */
+ String mPreloadPackageLibs;
+
+ /**
+ * The filename of the native library to preload, when using --preload-package.
+ */
+ String mPreloadPackageLibFileName;
+
+ /**
+ * The cache key under which to enter the preloaded package into the classloader cache, when
+ * using --preload-package.
+ */
+ String mPreloadPackageCacheKey;
+
+ /**
+ * Whether this is a request to start preloading the default resources and classes. This
+ * argument only makes sense when the zygote is in lazy preload mode (i.e, when it's started
+ * with --enable-lazy-preload).
+ */
+ boolean mPreloadDefault;
+
+ /**
+ * Whether this is a request to start a zygote process as a child of this zygote. Set with
+ * --start-child-zygote. The remaining arguments must include the CHILD_ZYGOTE_SOCKET_NAME_ARG
+ * flag to indicate the abstract socket name that should be used for communication.
+ */
+ boolean mStartChildZygote;
+
+ /**
+ * Whether the current arguments constitute a request for the zygote's PID.
+ */
+ boolean mPidQuery;
+
+ /**
+ * Exemptions from API blacklisting. These are sent to the pre-forked zygote at boot time, or
+ * when they change, via --set-api-blacklist-exemptions.
+ */
+ String[] mApiBlacklistExemptions;
+
+ /**
+ * Sampling rate for logging hidden API accesses to the event log. This is sent to the
+ * pre-forked zygote at boot time, or when it changes, via --hidden-api-log-sampling-rate.
+ */
+ int mHiddenApiAccessLogSampleRate = -1;
+
+ /**
+ * Constructs instance and parses args
+ *
+ * @param args zygote command-line args
+ */
+ ZygoteArguments(String[] args) throws IllegalArgumentException {
+ parseArgs(args);
+ }
+
+ /**
+ * Parses the commandline arguments intended for the Zygote spawner (such as "--setuid=" and
+ * "--setgid=") and creates an array containing the remaining args.
+ *
+ * Per security review bug #1112214, duplicate args are disallowed in critical cases to make
+ * injection harder.
+ */
+ private void parseArgs(String[] args)
+ throws IllegalArgumentException {
+ int curArg = 0;
+
+ boolean seenRuntimeArgs = false;
+
+ boolean expectRuntimeArgs = true;
+ for ( /* curArg */ ; curArg < args.length; curArg++) {
+ String arg = args[curArg];
+
+ if (arg.equals("--")) {
+ curArg++;
+ break;
+ } else if (arg.startsWith("--setuid=")) {
+ if (mUidSpecified) {
+ throw new IllegalArgumentException(
+ "Duplicate arg specified");
+ }
+ mUidSpecified = true;
+ mUid = Integer.parseInt(
+ arg.substring(arg.indexOf('=') + 1));
+ } else if (arg.startsWith("--setgid=")) {
+ if (mGidSpecified) {
+ throw new IllegalArgumentException(
+ "Duplicate arg specified");
+ }
+ mGidSpecified = true;
+ mGid = Integer.parseInt(
+ arg.substring(arg.indexOf('=') + 1));
+ } else if (arg.startsWith("--target-sdk-version=")) {
+ if (mTargetSdkVersionSpecified) {
+ throw new IllegalArgumentException(
+ "Duplicate target-sdk-version specified");
+ }
+ mTargetSdkVersionSpecified = true;
+ mTargetSdkVersion = Integer.parseInt(
+ arg.substring(arg.indexOf('=') + 1));
+ } else if (arg.equals("--runtime-args")) {
+ seenRuntimeArgs = true;
+ } else if (arg.startsWith("--runtime-flags=")) {
+ mRuntimeFlags = Integer.parseInt(
+ arg.substring(arg.indexOf('=') + 1));
+ } else if (arg.startsWith("--seinfo=")) {
+ if (mSeInfoSpecified) {
+ throw new IllegalArgumentException(
+ "Duplicate arg specified");
+ }
+ mSeInfoSpecified = true;
+ mSeInfo = arg.substring(arg.indexOf('=') + 1);
+ } else if (arg.startsWith("--capabilities=")) {
+ if (mCapabilitiesSpecified) {
+ throw new IllegalArgumentException(
+ "Duplicate arg specified");
+ }
+ mCapabilitiesSpecified = true;
+ String capString = arg.substring(arg.indexOf('=') + 1);
+
+ String[] capStrings = capString.split(",", 2);
+
+ if (capStrings.length == 1) {
+ mEffectiveCapabilities = Long.decode(capStrings[0]);
+ mPermittedCapabilities = mEffectiveCapabilities;
+ } else {
+ mPermittedCapabilities = Long.decode(capStrings[0]);
+ mEffectiveCapabilities = Long.decode(capStrings[1]);
+ }
+ } else if (arg.startsWith("--rlimit=")) {
+ // Duplicate --rlimit arguments are specifically allowed.
+ String[] limitStrings = arg.substring(arg.indexOf('=') + 1).split(",");
+
+ if (limitStrings.length != 3) {
+ throw new IllegalArgumentException(
+ "--rlimit= should have 3 comma-delimited ints");
+ }
+ int[] rlimitTuple = new int[limitStrings.length];
+
+ for (int i = 0; i < limitStrings.length; i++) {
+ rlimitTuple[i] = Integer.parseInt(limitStrings[i]);
+ }
+
+ if (mRLimits == null) {
+ mRLimits = new ArrayList();
+ }
+
+ mRLimits.add(rlimitTuple);
+ } else if (arg.startsWith("--setgroups=")) {
+ if (mGids != null) {
+ throw new IllegalArgumentException(
+ "Duplicate arg specified");
+ }
+
+ String[] params = arg.substring(arg.indexOf('=') + 1).split(",");
+
+ mGids = new int[params.length];
+
+ for (int i = params.length - 1; i >= 0; i--) {
+ mGids[i] = Integer.parseInt(params[i]);
+ }
+ } else if (arg.equals("--invoke-with")) {
+ if (mInvokeWith != null) {
+ throw new IllegalArgumentException(
+ "Duplicate arg specified");
+ }
+ try {
+ mInvokeWith = args[++curArg];
+ } catch (IndexOutOfBoundsException ex) {
+ throw new IllegalArgumentException(
+ "--invoke-with requires argument");
+ }
+ } else if (arg.startsWith("--nice-name=")) {
+ if (mNiceName != null) {
+ throw new IllegalArgumentException(
+ "Duplicate arg specified");
+ }
+ mNiceName = arg.substring(arg.indexOf('=') + 1);
+ } else if (arg.equals("--mount-external-default")) {
+ mMountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT;
+ } else if (arg.equals("--mount-external-read")) {
+ mMountExternal = Zygote.MOUNT_EXTERNAL_READ;
+ } else if (arg.equals("--mount-external-write")) {
+ mMountExternal = Zygote.MOUNT_EXTERNAL_WRITE;
+ } else if (arg.equals("--mount-external-full")) {
+ mMountExternal = Zygote.MOUNT_EXTERNAL_FULL;
+ } else if (arg.equals("--mount-external-installer")) {
+ mMountExternal = Zygote.MOUNT_EXTERNAL_INSTALLER;
+ } else if (arg.equals("--query-abi-list")) {
+ mAbiListQuery = true;
+ } else if (arg.equals("--get-pid")) {
+ mPidQuery = true;
+ } else if (arg.startsWith("--instruction-set=")) {
+ mInstructionSet = arg.substring(arg.indexOf('=') + 1);
+ } else if (arg.startsWith("--app-data-dir=")) {
+ mAppDataDir = arg.substring(arg.indexOf('=') + 1);
+ } else if (arg.equals("--preload-app")) {
+ mPreloadApp = args[++curArg];
+ } else if (arg.equals("--preload-package")) {
+ mPreloadPackage = args[++curArg];
+ mPreloadPackageLibs = args[++curArg];
+ mPreloadPackageLibFileName = args[++curArg];
+ mPreloadPackageCacheKey = args[++curArg];
+ } else if (arg.equals("--preload-default")) {
+ mPreloadDefault = true;
+ expectRuntimeArgs = false;
+ } else if (arg.equals("--start-child-zygote")) {
+ mStartChildZygote = true;
+ } else if (arg.equals("--set-api-blacklist-exemptions")) {
+ // consume all remaining args; this is a stand-alone command, never included
+ // with the regular fork command.
+ mApiBlacklistExemptions = Arrays.copyOfRange(args, curArg + 1, args.length);
+ curArg = args.length;
+ expectRuntimeArgs = false;
+ } else if (arg.startsWith("--hidden-api-log-sampling-rate=")) {
+ String rateStr = arg.substring(arg.indexOf('=') + 1);
+ try {
+ mHiddenApiAccessLogSampleRate = Integer.parseInt(rateStr);
+ } catch (NumberFormatException nfe) {
+ throw new IllegalArgumentException(
+ "Invalid log sampling rate: " + rateStr, nfe);
+ }
+ expectRuntimeArgs = false;
+ } else if (arg.startsWith("--package-name=")) {
+ if (mPackageName != null) {
+ throw new IllegalArgumentException("Duplicate arg specified");
+ }
+ mPackageName = arg.substring(arg.indexOf('=') + 1);
+ } else if (arg.startsWith("--packages-for-uid=")) {
+ mPackagesForUid = arg.substring(arg.indexOf('=') + 1).split(",");
+ } else if (arg.startsWith("--visible-vols=")) {
+ mVisibleVolIds = arg.substring(arg.indexOf('=') + 1).split(",");
+ } else {
+ break;
+ }
+ }
+
+ if (mAbiListQuery || mPidQuery) {
+ if (args.length - curArg > 0) {
+ throw new IllegalArgumentException("Unexpected arguments after --query-abi-list.");
+ }
+ } else if (mPreloadPackage != null) {
+ if (args.length - curArg > 0) {
+ throw new IllegalArgumentException(
+ "Unexpected arguments after --preload-package.");
+ }
+ } else if (mPreloadApp != null) {
+ if (args.length - curArg > 0) {
+ throw new IllegalArgumentException(
+ "Unexpected arguments after --preload-app.");
+ }
+ } else if (expectRuntimeArgs) {
+ if (!seenRuntimeArgs) {
+ throw new IllegalArgumentException("Unexpected argument : " + args[curArg]);
+ }
+
+ mRemainingArgs = new String[args.length - curArg];
+ System.arraycopy(args, curArg, mRemainingArgs, 0, mRemainingArgs.length);
+ }
+
+ if (mStartChildZygote) {
+ boolean seenChildSocketArg = false;
+ for (String arg : mRemainingArgs) {
+ if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) {
+ seenChildSocketArg = true;
+ break;
+ }
+ }
+ if (!seenChildSocketArg) {
+ throw new IllegalArgumentException("--start-child-zygote specified "
+ + "without " + Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG);
+ }
+ }
+ }
+}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index ced798c..78ecee1 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -24,16 +24,13 @@
import static android.system.OsConstants.STDOUT_FILENO;
import static com.android.internal.os.ZygoteConnectionConstants.CONNECTION_TIMEOUT_MILLIS;
-import static com.android.internal.os.ZygoteConnectionConstants.MAX_ZYGOTE_ARGC;
import static com.android.internal.os.ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS;
import android.content.pm.ApplicationInfo;
import android.net.Credentials;
import android.net.LocalSocket;
-import android.os.FactoryTest;
import android.os.Parcel;
import android.os.Process;
-import android.os.SystemProperties;
import android.os.Trace;
import android.system.ErrnoException;
import android.system.Os;
@@ -52,8 +49,6 @@
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Base64;
/**
@@ -62,9 +57,6 @@
class ZygoteConnection {
private static final String TAG = "Zygote";
- /** a prototype instance for a future List.toArray() */
- private static final int[][] intArray2d = new int[0][0];
-
/**
* The command socket.
*
@@ -113,7 +105,7 @@
*
* @return null-ok; file descriptor
*/
- FileDescriptor getFileDesciptor() {
+ FileDescriptor getFileDescriptor() {
return mSocket.getFileDescriptor();
}
@@ -127,11 +119,13 @@
*/
Runnable processOneCommand(ZygoteServer zygoteServer) {
String args[];
- Arguments parsedArgs = null;
+ ZygoteArguments parsedArgs = null;
FileDescriptor[] descriptors;
try {
- args = readArgumentList();
+ args = Zygote.readArgumentList(mSocketReader);
+
+ // TODO (chriswailes): Remove this and add an assert.
descriptors = mSocket.getAncillaryFileDescriptors();
} catch (IOException ex) {
throw new IllegalStateException("IOException on command socket", ex);
@@ -148,26 +142,26 @@
FileDescriptor childPipeFd = null;
FileDescriptor serverPipeFd = null;
- parsedArgs = new Arguments(args);
+ parsedArgs = new ZygoteArguments(args);
- if (parsedArgs.abiListQuery) {
+ if (parsedArgs.mAbiListQuery) {
handleAbiListQuery();
return null;
}
- if (parsedArgs.pidQuery) {
+ if (parsedArgs.mPidQuery) {
handlePidQuery();
return null;
}
- if (parsedArgs.preloadDefault) {
+ if (parsedArgs.mPreloadDefault) {
handlePreload();
return null;
}
- if (parsedArgs.preloadPackage != null) {
- handlePreloadPackage(parsedArgs.preloadPackage, parsedArgs.preloadPackageLibs,
- parsedArgs.preloadPackageLibFileName, parsedArgs.preloadPackageCacheKey);
+ if (parsedArgs.mPreloadPackage != null) {
+ handlePreloadPackage(parsedArgs.mPreloadPackage, parsedArgs.mPreloadPackageLibs,
+ parsedArgs.mPreloadPackageLibFileName, parsedArgs.mPreloadPackageCacheKey);
return null;
}
@@ -186,37 +180,37 @@
return null;
}
- if (parsedArgs.apiBlacklistExemptions != null) {
- handleApiBlacklistExemptions(parsedArgs.apiBlacklistExemptions);
+ if (parsedArgs.mApiBlacklistExemptions != null) {
+ handleApiBlacklistExemptions(parsedArgs.mApiBlacklistExemptions);
return null;
}
- if (parsedArgs.hiddenApiAccessLogSampleRate != -1) {
- handleHiddenApiAccessLogSampleRate(parsedArgs.hiddenApiAccessLogSampleRate);
+ if (parsedArgs.mHiddenApiAccessLogSampleRate != -1) {
+ handleHiddenApiAccessLogSampleRate(parsedArgs.mHiddenApiAccessLogSampleRate);
return null;
}
- if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) {
- throw new ZygoteSecurityException("Client may not specify capabilities: " +
- "permitted=0x" + Long.toHexString(parsedArgs.permittedCapabilities) +
- ", effective=0x" + Long.toHexString(parsedArgs.effectiveCapabilities));
+ if (parsedArgs.mPermittedCapabilities != 0 || parsedArgs.mEffectiveCapabilities != 0) {
+ throw new ZygoteSecurityException("Client may not specify capabilities: "
+ + "permitted=0x" + Long.toHexString(parsedArgs.mPermittedCapabilities)
+ + ", effective=0x" + Long.toHexString(parsedArgs.mEffectiveCapabilities));
}
- applyUidSecurityPolicy(parsedArgs, peer);
- applyInvokeWithSecurityPolicy(parsedArgs, peer);
+ Zygote.applyUidSecurityPolicy(parsedArgs, peer);
+ Zygote.applyInvokeWithSecurityPolicy(parsedArgs, peer);
- applyDebuggerSystemProperty(parsedArgs);
- applyInvokeWithSystemProperty(parsedArgs);
+ Zygote.applyDebuggerSystemProperty(parsedArgs);
+ Zygote.applyInvokeWithSystemProperty(parsedArgs);
int[][] rlimits = null;
- if (parsedArgs.rlimits != null) {
- rlimits = parsedArgs.rlimits.toArray(intArray2d);
+ if (parsedArgs.mRLimits != null) {
+ rlimits = parsedArgs.mRLimits.toArray(Zygote.INT_ARRAY_2D);
}
int[] fdsToIgnore = null;
- if (parsedArgs.invokeWith != null) {
+ if (parsedArgs.mInvokeWith != null) {
try {
FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC);
childPipeFd = pipeFds[1];
@@ -256,11 +250,11 @@
fd = null;
- pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
- parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
- parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.startChildZygote,
- parsedArgs.instructionSet, parsedArgs.appDataDir, parsedArgs.packageName,
- parsedArgs.packagesForUid, parsedArgs.visibleVolIds);
+ pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid, parsedArgs.mGids,
+ parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo,
+ parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
+ parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mPackageName,
+ parsedArgs.mPackagesForUid, parsedArgs.mVisibleVolIds);
try {
if (pid == 0) {
@@ -272,7 +266,7 @@
serverPipeFd = null;
return handleChildProc(parsedArgs, descriptors, childPipeFd,
- parsedArgs.startChildZygote);
+ parsedArgs.mStartChildZygote);
} else {
// In the parent. A pid < 0 indicates a failure and will be handled in
// handleParentProc.
@@ -387,541 +381,6 @@
}
/**
- * Handles argument parsing for args related to the zygote spawner.
- *
- * Current recognized args:
- * <ul>
- * <li> --setuid=<i>uid of child process, defaults to 0</i>
- * <li> --setgid=<i>gid of child process, defaults to 0</i>
- * <li> --setgroups=<i>comma-separated list of supplimentary gid's</i>
- * <li> --capabilities=<i>a pair of comma-separated integer strings
- * indicating Linux capabilities(2) set for child. The first string
- * represents the <code>permitted</code> set, and the second the
- * <code>effective</code> set. Precede each with 0 or
- * 0x for octal or hexidecimal value. If unspecified, both default to 0.
- * This parameter is only applied if the uid of the new process will
- * be non-0. </i>
- * <li> --rlimit=r,c,m<i>tuple of values for setrlimit() call.
- * <code>r</code> is the resource, <code>c</code> and <code>m</code>
- * are the settings for current and max value.</i>
- * <li> --instruction-set=<i>instruction-set-string</i> which instruction set to use/emulate.
- * <li> --nice-name=<i>nice name to appear in ps</i>
- * <li> --package-name=<i>package name this process belongs to</i>
- * <li> --runtime-args indicates that the remaining arg list should
- * be handed off to com.android.internal.os.RuntimeInit, rather than
- * processed directly.
- * Android runtime startup (eg, Binder initialization) is also eschewed.
- * <li> [--] <args for RuntimeInit >
- * </ul>
- */
- static class Arguments {
- /** from --setuid */
- int uid = 0;
- boolean uidSpecified;
-
- /** from --setgid */
- int gid = 0;
- boolean gidSpecified;
-
- /** from --setgroups */
- int[] gids;
-
- /**
- * From --runtime-flags.
- */
- int runtimeFlags;
-
- /** From --mount-external */
- int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
-
- /** from --target-sdk-version. */
- int targetSdkVersion;
- boolean targetSdkVersionSpecified;
-
- /** from --nice-name */
- String niceName;
-
- /** from --capabilities */
- boolean capabilitiesSpecified;
- long permittedCapabilities;
- long effectiveCapabilities;
-
- /** from --seinfo */
- boolean seInfoSpecified;
- String seInfo;
-
- /** from all --rlimit=r,c,m */
- ArrayList<int[]> rlimits;
-
- /** from --invoke-with */
- String invokeWith;
-
- /** from --package-name */
- String packageName;
-
- /** from --packages-for-uid */
- String[] packagesForUid;
-
- /** from --visible-vols */
- String[] visibleVolIds;
-
- /**
- * Any args after and including the first non-option arg
- * (or after a '--')
- */
- String remainingArgs[];
-
- /**
- * Whether the current arguments constitute an ABI list query.
- */
- boolean abiListQuery;
-
- /**
- * The instruction set to use, or null when not important.
- */
- String instructionSet;
-
- /**
- * The app data directory. May be null, e.g., for the system server. Note that this might
- * not be reliable in the case of process-sharing apps.
- */
- String appDataDir;
-
- /**
- * The APK path of the package to preload, when using --preload-package.
- */
- String preloadPackage;
-
- /**
- * A Base64 string representing a serialize ApplicationInfo Parcel,
- when using --preload-app.
- */
- String mPreloadApp;
-
- /**
- * The native library path of the package to preload, when using --preload-package.
- */
- String preloadPackageLibs;
-
- /**
- * The filename of the native library to preload, when using --preload-package.
- */
- String preloadPackageLibFileName;
-
- /**
- * The cache key under which to enter the preloaded package into the classloader cache,
- * when using --preload-package.
- */
- String preloadPackageCacheKey;
-
- /**
- * Whether this is a request to start preloading the default resources and classes.
- * This argument only makes sense when the zygote is in lazy preload mode (i.e, when
- * it's started with --enable-lazy-preload).
- */
- boolean preloadDefault;
-
- /**
- * Whether this is a request to start a zygote process as a child of this zygote.
- * Set with --start-child-zygote. The remaining arguments must include the
- * CHILD_ZYGOTE_SOCKET_NAME_ARG flag to indicate the abstract socket name that
- * should be used for communication.
- */
- boolean startChildZygote;
-
- /**
- * Whether the current arguments constitute a request for the zygote's PID.
- */
- boolean pidQuery;
-
- /**
- * Exemptions from API blacklisting. These are sent to the pre-forked zygote at boot time,
- * or when they change, via --set-api-blacklist-exemptions.
- */
- String[] apiBlacklistExemptions;
-
- /**
- * Sampling rate for logging hidden API accesses to the event log. This is sent to the
- * pre-forked zygote at boot time, or when it changes, via --hidden-api-log-sampling-rate.
- */
- int hiddenApiAccessLogSampleRate = -1;
-
- /**
- * Constructs instance and parses args
- * @param args zygote command-line args
- * @throws IllegalArgumentException
- */
- Arguments(String args[]) throws IllegalArgumentException {
- parseArgs(args);
- }
-
- /**
- * Parses the commandline arguments intended for the Zygote spawner
- * (such as "--setuid=" and "--setgid=") and creates an array
- * containing the remaining args.
- *
- * Per security review bug #1112214, duplicate args are disallowed in
- * critical cases to make injection harder.
- */
- private void parseArgs(String args[])
- throws IllegalArgumentException {
- int curArg = 0;
-
- boolean seenRuntimeArgs = false;
-
- boolean expectRuntimeArgs = true;
- for ( /* curArg */ ; curArg < args.length; curArg++) {
- String arg = args[curArg];
-
- if (arg.equals("--")) {
- curArg++;
- break;
- } else if (arg.startsWith("--setuid=")) {
- if (uidSpecified) {
- throw new IllegalArgumentException(
- "Duplicate arg specified");
- }
- uidSpecified = true;
- uid = Integer.parseInt(
- arg.substring(arg.indexOf('=') + 1));
- } else if (arg.startsWith("--setgid=")) {
- if (gidSpecified) {
- throw new IllegalArgumentException(
- "Duplicate arg specified");
- }
- gidSpecified = true;
- gid = Integer.parseInt(
- arg.substring(arg.indexOf('=') + 1));
- } else if (arg.startsWith("--target-sdk-version=")) {
- if (targetSdkVersionSpecified) {
- throw new IllegalArgumentException(
- "Duplicate target-sdk-version specified");
- }
- targetSdkVersionSpecified = true;
- targetSdkVersion = Integer.parseInt(
- arg.substring(arg.indexOf('=') + 1));
- } else if (arg.equals("--runtime-args")) {
- seenRuntimeArgs = true;
- } else if (arg.startsWith("--runtime-flags=")) {
- runtimeFlags = Integer.parseInt(
- arg.substring(arg.indexOf('=') + 1));
- } else if (arg.startsWith("--seinfo=")) {
- if (seInfoSpecified) {
- throw new IllegalArgumentException(
- "Duplicate arg specified");
- }
- seInfoSpecified = true;
- seInfo = arg.substring(arg.indexOf('=') + 1);
- } else if (arg.startsWith("--capabilities=")) {
- if (capabilitiesSpecified) {
- throw new IllegalArgumentException(
- "Duplicate arg specified");
- }
- capabilitiesSpecified = true;
- String capString = arg.substring(arg.indexOf('=')+1);
-
- String[] capStrings = capString.split(",", 2);
-
- if (capStrings.length == 1) {
- effectiveCapabilities = Long.decode(capStrings[0]);
- permittedCapabilities = effectiveCapabilities;
- } else {
- permittedCapabilities = Long.decode(capStrings[0]);
- effectiveCapabilities = Long.decode(capStrings[1]);
- }
- } else if (arg.startsWith("--rlimit=")) {
- // Duplicate --rlimit arguments are specifically allowed.
- String[] limitStrings
- = arg.substring(arg.indexOf('=')+1).split(",");
-
- if (limitStrings.length != 3) {
- throw new IllegalArgumentException(
- "--rlimit= should have 3 comma-delimited ints");
- }
- int[] rlimitTuple = new int[limitStrings.length];
-
- for(int i=0; i < limitStrings.length; i++) {
- rlimitTuple[i] = Integer.parseInt(limitStrings[i]);
- }
-
- if (rlimits == null) {
- rlimits = new ArrayList();
- }
-
- rlimits.add(rlimitTuple);
- } else if (arg.startsWith("--setgroups=")) {
- if (gids != null) {
- throw new IllegalArgumentException(
- "Duplicate arg specified");
- }
-
- String[] params
- = arg.substring(arg.indexOf('=') + 1).split(",");
-
- gids = new int[params.length];
-
- for (int i = params.length - 1; i >= 0 ; i--) {
- gids[i] = Integer.parseInt(params[i]);
- }
- } else if (arg.equals("--invoke-with")) {
- if (invokeWith != null) {
- throw new IllegalArgumentException(
- "Duplicate arg specified");
- }
- try {
- invokeWith = args[++curArg];
- } catch (IndexOutOfBoundsException ex) {
- throw new IllegalArgumentException(
- "--invoke-with requires argument");
- }
- } else if (arg.startsWith("--nice-name=")) {
- if (niceName != null) {
- throw new IllegalArgumentException(
- "Duplicate arg specified");
- }
- niceName = arg.substring(arg.indexOf('=') + 1);
- } else if (arg.equals("--mount-external-default")) {
- mountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT;
- } else if (arg.equals("--mount-external-read")) {
- mountExternal = Zygote.MOUNT_EXTERNAL_READ;
- } else if (arg.equals("--mount-external-write")) {
- mountExternal = Zygote.MOUNT_EXTERNAL_WRITE;
- } else if (arg.equals("--mount-external-full")) {
- mountExternal = Zygote.MOUNT_EXTERNAL_FULL;
- } else if (arg.equals("--mount-external-installer")) {
- mountExternal = Zygote.MOUNT_EXTERNAL_INSTALLER;
- } else if (arg.equals("--mount-external-legacy")) {
- mountExternal = Zygote.MOUNT_EXTERNAL_LEGACY;
- } else if (arg.equals("--query-abi-list")) {
- abiListQuery = true;
- } else if (arg.equals("--get-pid")) {
- pidQuery = true;
- } else if (arg.startsWith("--instruction-set=")) {
- instructionSet = arg.substring(arg.indexOf('=') + 1);
- } else if (arg.startsWith("--app-data-dir=")) {
- appDataDir = arg.substring(arg.indexOf('=') + 1);
- } else if (arg.equals("--preload-app")) {
- mPreloadApp = args[++curArg];
- } else if (arg.equals("--preload-package")) {
- preloadPackage = args[++curArg];
- preloadPackageLibs = args[++curArg];
- preloadPackageLibFileName = args[++curArg];
- preloadPackageCacheKey = args[++curArg];
- } else if (arg.equals("--preload-default")) {
- preloadDefault = true;
- expectRuntimeArgs = false;
- } else if (arg.equals("--start-child-zygote")) {
- startChildZygote = true;
- } else if (arg.equals("--set-api-blacklist-exemptions")) {
- // consume all remaining args; this is a stand-alone command, never included
- // with the regular fork command.
- apiBlacklistExemptions = Arrays.copyOfRange(args, curArg + 1, args.length);
- curArg = args.length;
- expectRuntimeArgs = false;
- } else if (arg.startsWith("--hidden-api-log-sampling-rate=")) {
- String rateStr = arg.substring(arg.indexOf('=') + 1);
- try {
- hiddenApiAccessLogSampleRate = Integer.parseInt(rateStr);
- } catch (NumberFormatException nfe) {
- throw new IllegalArgumentException(
- "Invalid log sampling rate: " + rateStr, nfe);
- }
- expectRuntimeArgs = false;
- } else if (arg.startsWith("--package-name=")) {
- if (packageName != null) {
- throw new IllegalArgumentException("Duplicate arg specified");
- }
- packageName = arg.substring(arg.indexOf('=') + 1);
- } else if (arg.startsWith("--packages-for-uid=")) {
- packagesForUid = arg.substring(arg.indexOf('=') + 1).split(",");
- } else if (arg.startsWith("--visible-vols=")) {
- visibleVolIds = arg.substring(arg.indexOf('=') + 1).split(",");
- } else {
- break;
- }
- }
-
- if (abiListQuery || pidQuery) {
- if (args.length - curArg > 0) {
- throw new IllegalArgumentException("Unexpected arguments after --query-abi-list.");
- }
- } else if (preloadPackage != null) {
- if (args.length - curArg > 0) {
- throw new IllegalArgumentException(
- "Unexpected arguments after --preload-package.");
- }
- } else if (mPreloadApp != null) {
- if (args.length - curArg > 0) {
- throw new IllegalArgumentException(
- "Unexpected arguments after --preload-app.");
- }
- } else if (expectRuntimeArgs) {
- if (!seenRuntimeArgs) {
- throw new IllegalArgumentException("Unexpected argument : " + args[curArg]);
- }
-
- remainingArgs = new String[args.length - curArg];
- System.arraycopy(args, curArg, remainingArgs, 0, remainingArgs.length);
- }
-
- if (startChildZygote) {
- boolean seenChildSocketArg = false;
- for (String arg : remainingArgs) {
- if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) {
- seenChildSocketArg = true;
- break;
- }
- }
- if (!seenChildSocketArg) {
- throw new IllegalArgumentException("--start-child-zygote specified " +
- "without " + Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG);
- }
- }
- }
- }
-
- /**
- * Reads an argument list from the command socket/
- * @return Argument list or null if EOF is reached
- * @throws IOException passed straight through
- */
- private String[] readArgumentList()
- throws IOException {
-
- /**
- * See android.os.Process.zygoteSendArgsAndGetPid()
- * 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.
- */
-
- int argc;
-
- try {
- String s = mSocketReader.readLine();
-
- if (s == null) {
- // EOF reached.
- return null;
- }
- argc = Integer.parseInt(s);
- } catch (NumberFormatException ex) {
- Log.e(TAG, "invalid Zygote wire format: non-int at argc");
- throw new IOException("invalid wire format");
- }
-
- // See bug 1092107: large argc can be used for a DOS attack
- if (argc > MAX_ZYGOTE_ARGC) {
- throw new IOException("max arg count exceeded");
- }
-
- String[] result = new String[argc];
- for (int i = 0; i < argc; i++) {
- result[i] = mSocketReader.readLine();
- if (result[i] == null) {
- // We got an unexpected EOF.
- throw new IOException("truncated request");
- }
- }
-
- return result;
- }
-
- /**
- * uid 1000 (Process.SYSTEM_UID) may specify any uid > 1000 in normal
- * operation. It may also specify any gid and setgroups() list it chooses.
- * In factory test mode, it may specify any UID.
- *
- * @param args non-null; zygote spawner arguments
- * @param peer non-null; peer credentials
- * @throws ZygoteSecurityException
- */
- private static void applyUidSecurityPolicy(Arguments args, Credentials peer)
- throws ZygoteSecurityException {
-
- if (peer.getUid() == Process.SYSTEM_UID) {
- /* In normal operation, SYSTEM_UID can only specify a restricted
- * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid.
- */
- boolean uidRestricted = FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF;
-
- if (uidRestricted && args.uidSpecified && (args.uid < Process.SYSTEM_UID)) {
- throw new ZygoteSecurityException(
- "System UID may not launch process with UID < "
- + Process.SYSTEM_UID);
- }
- }
-
- // If not otherwise specified, uid and gid are inherited from peer
- if (!args.uidSpecified) {
- args.uid = peer.getUid();
- args.uidSpecified = true;
- }
- if (!args.gidSpecified) {
- args.gid = peer.getGid();
- args.gidSpecified = true;
- }
- }
-
- /**
- * Applies debugger system properties to the zygote arguments.
- *
- * If "ro.debuggable" is "1", all apps are debuggable. Otherwise,
- * the debugger state is specified via the "--enable-jdwp" flag
- * in the spawn request.
- *
- * @param args non-null; zygote spawner args
- */
- public static void applyDebuggerSystemProperty(Arguments args) {
- if (RoSystemProperties.DEBUGGABLE) {
- args.runtimeFlags |= Zygote.DEBUG_ENABLE_JDWP;
- }
- }
-
- /**
- * Applies zygote security policy.
- * Based on the credentials of the process issuing a zygote command:
- * <ol>
- * <li> uid 0 (root) may specify --invoke-with to launch Zygote with a
- * wrapper command.
- * <li> Any other uid may not specify any invoke-with argument.
- * </ul>
- *
- * @param args non-null; zygote spawner arguments
- * @param peer non-null; peer credentials
- * @throws ZygoteSecurityException
- */
- private static void applyInvokeWithSecurityPolicy(Arguments args, Credentials peer)
- throws ZygoteSecurityException {
- int peerUid = peer.getUid();
-
- if (args.invokeWith != null && peerUid != 0 &&
- (args.runtimeFlags & Zygote.DEBUG_ENABLE_JDWP) == 0) {
- throw new ZygoteSecurityException("Peer is permitted to specify an"
- + "explicit invoke-with wrapper command only for debuggable"
- + "applications.");
- }
- }
-
- /**
- * Applies invoke-with system properties to the zygote arguments.
- *
- * @param args non-null; zygote args
- */
- public static void applyInvokeWithSystemProperty(Arguments args) {
- if (args.invokeWith == null && args.niceName != null) {
- String property = "wrap." + args.niceName;
- args.invokeWith = SystemProperties.get(property);
- if (args.invokeWith != null && args.invokeWith.length() == 0) {
- args.invokeWith = null;
- }
- }
- }
-
- /**
* Handles post-fork setup of child proc, closing sockets as appropriate,
* reopen stdio as appropriate, and ultimately throwing MethodAndArgsCaller
* if successful or returning if failed.
@@ -931,7 +390,7 @@
* @param pipeFd null-ok; pipe for communication back to Zygote.
* @param isZygote whether this new child process is itself a new Zygote.
*/
- private Runnable handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors,
+ private Runnable handleChildProc(ZygoteArguments parsedArgs, FileDescriptor[] descriptors,
FileDescriptor pipeFd, boolean isZygote) {
/**
* By the time we get here, the native code has closed the two actual Zygote
@@ -954,27 +413,27 @@
}
}
- if (parsedArgs.niceName != null) {
- Process.setArgV0(parsedArgs.niceName);
+ if (parsedArgs.mNiceName != null) {
+ Process.setArgV0(parsedArgs.mNiceName);
}
// End of the postFork event.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- if (parsedArgs.invokeWith != null) {
- WrapperInit.execApplication(parsedArgs.invokeWith,
- parsedArgs.niceName, parsedArgs.targetSdkVersion,
+ if (parsedArgs.mInvokeWith != null) {
+ WrapperInit.execApplication(parsedArgs.mInvokeWith,
+ parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion,
VMRuntime.getCurrentInstructionSet(),
- pipeFd, parsedArgs.remainingArgs);
+ pipeFd, parsedArgs.mRemainingArgs);
// Should not get here.
throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned");
} else {
if (!isZygote) {
- return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs,
- null /* classLoader */);
+ return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
+ parsedArgs.mRemainingArgs, null /* classLoader */);
} else {
- return ZygoteInit.childZygoteInit(parsedArgs.targetSdkVersion,
- parsedArgs.remainingArgs, null /* classLoader */);
+ return ZygoteInit.childZygoteInit(parsedArgs.mTargetSdkVersion,
+ parsedArgs.mRemainingArgs, null /* classLoader */);
}
}
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index c2c6ae6..2f00c07 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -21,7 +21,6 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
-import android.opengl.EGL14;
import android.os.Build;
import android.os.Environment;
import android.os.IInstalld;
@@ -71,16 +70,16 @@
/**
* Startup class for the zygote process.
*
- * Pre-initializes some classes, and then waits for commands on a UNIX domain
- * socket. Based on these commands, forks off child processes that inherit
- * the initial state of the VM.
+ * Pre-initializes some classes, and then waits for commands on a UNIX domain socket. Based on these
+ * commands, forks off child processes that inherit the initial state of the VM.
*
- * Please see {@link ZygoteConnection.Arguments} for documentation on the
- * client protocol.
+ * Please see {@link ZygoteArguments} for documentation on the client protocol.
*
* @hide
*/
public class ZygoteInit {
+
+ // TODO (chriswailes): Change this so it is set with Zygote or ZygoteSecondary as appropriate
private static final String TAG = "Zygote";
private static final String PROPERTY_DISABLE_OPENGL_PRELOADING = "ro.zygote.disable_gl_preload";
@@ -89,11 +88,15 @@
private static final int LOG_BOOT_PROGRESS_PRELOAD_START = 3020;
private static final int LOG_BOOT_PROGRESS_PRELOAD_END = 3030;
- /** when preloading, GC after allocating this many bytes */
+ /**
+ * when preloading, GC after allocating this many bytes
+ */
private static final int PRELOAD_GC_THRESHOLD = 50000;
private static final String ABI_LIST_ARG = "--abi-list=";
+ // TODO (chriswailes): Re-name this --zygote-socket-name= and then add a
+ // --blastula-socket-name parameter.
private static final String SOCKET_NAME_ARG = "--socket-name=";
/**
@@ -106,7 +109,9 @@
*/
private static final String PRELOADED_CLASSES = "/system/etc/preloaded-classes";
- /** Controls whether we should preload resources during zygote init. */
+ /**
+ * Controls whether we should preload resources during zygote init.
+ */
public static final boolean PRELOAD_RESOURCES = true;
private static final int UNPRIVILEGED_UID = 9999;
@@ -173,6 +178,7 @@
}
native private static void nativePreloadAppProcessHALs();
+
native private static void nativePreloadOpenGL();
private static void preloadOpenGL() {
@@ -191,8 +197,8 @@
/**
* Register AndroidKeyStoreProvider and warm up the providers that are already registered.
*
- * By doing it here we avoid that each app does it when requesting a service from the
- * provider for the first time.
+ * By doing it here we avoid that each app does it when requesting a service from the provider
+ * for the first time.
*/
private static void warmUpJcaProviders() {
long startTime = SystemClock.uptimeMillis();
@@ -218,11 +224,10 @@
}
/**
- * Performs Zygote process initialization. Loads and initializes
- * commonly used classes.
+ * Performs Zygote process initialization. Loads and initializes commonly used classes.
*
- * Most classes only cause a few hundred bytes to be allocated, but
- * a few will allocate a dozen Kbytes (in one case, 500+K).
+ * Most classes only cause a few hundred bytes to be allocated, but a few will allocate a dozen
+ * Kbytes (in one case, 500+K).
*/
private static void preloadClasses() {
final VMRuntime runtime = VMRuntime.getRuntime();
@@ -263,8 +268,8 @@
runtime.setTargetHeapUtilization(0.8f);
try {
- BufferedReader br
- = new BufferedReader(new InputStreamReader(is), 256);
+ BufferedReader br =
+ new BufferedReader(new InputStreamReader(is), 256);
int count = 0;
String line;
@@ -305,7 +310,7 @@
}
Log.i(TAG, "...preloaded " + count + " classes in "
- + (SystemClock.uptimeMillis()-startTime) + "ms.");
+ + (SystemClock.uptimeMillis() - startTime) + "ms.");
} catch (IOException e) {
Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
} finally {
@@ -331,11 +336,10 @@
}
/**
- * Load in commonly used resources, so they can be shared across
- * processes.
+ * Load in commonly used resources, so they can be shared across processes.
*
- * These tend to be a few Kbytes, but are frequently in the 20-40K
- * range, and occasionally even larger.
+ * These tend to be a few Kbytes, but are frequently in the 20-40K range, and occasionally even
+ * larger.
*/
private static void preloadResources() {
final VMRuntime runtime = VMRuntime.getRuntime();
@@ -352,7 +356,7 @@
int N = preloadDrawables(ar);
ar.recycle();
Log.i(TAG, "...preloaded " + N + " resources in "
- + (SystemClock.uptimeMillis()-startTime) + "ms.");
+ + (SystemClock.uptimeMillis() - startTime) + "ms.");
startTime = SystemClock.uptimeMillis();
ar = mResources.obtainTypedArray(
@@ -360,7 +364,7 @@
N = preloadColorStateLists(ar);
ar.recycle();
Log.i(TAG, "...preloaded " + N + " resources in "
- + (SystemClock.uptimeMillis()-startTime) + "ms.");
+ + (SystemClock.uptimeMillis() - startTime) + "ms.");
if (mResources.getBoolean(
com.android.internal.R.bool.config_freeformWindowManagement)) {
@@ -381,7 +385,7 @@
private static int preloadColorStateLists(TypedArray ar) {
int N = ar.length();
- for (int i=0; i<N; i++) {
+ for (int i = 0; i < N; i++) {
int id = ar.getResourceId(i, 0);
if (false) {
Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
@@ -390,8 +394,8 @@
if (mResources.getColorStateList(id, null) == null) {
throw new IllegalArgumentException(
"Unable to find preloaded color resource #0x"
- + Integer.toHexString(id)
- + " (" + ar.getString(i) + ")");
+ + Integer.toHexString(id)
+ + " (" + ar.getString(i) + ")");
}
}
}
@@ -401,7 +405,7 @@
private static int preloadDrawables(TypedArray ar) {
int N = ar.length();
- for (int i=0; i<N; i++) {
+ for (int i = 0; i < N; i++) {
int id = ar.getResourceId(i, 0);
if (false) {
Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
@@ -410,8 +414,8 @@
if (mResources.getDrawable(id, null) == null) {
throw new IllegalArgumentException(
"Unable to find preloaded drawable resource #0x"
- + Integer.toHexString(id)
- + " (" + ar.getString(i) + ")");
+ + Integer.toHexString(id)
+ + " (" + ar.getString(i) + ")");
}
}
}
@@ -419,9 +423,8 @@
}
/**
- * Runs several special GCs to try to clean up a few generations of
- * softly- and final-reachable objects, along with any other garbage.
- * This is only useful just before a fork().
+ * Runs several special GCs to try to clean up a few generations of softly- and final-reachable
+ * objects, along with any other garbage. This is only useful just before a fork().
*/
private static void gcAndFinalize() {
ZygoteHooks.gcAndFinalize();
@@ -430,12 +433,12 @@
/**
* Finish remaining work for the newly forked system server process.
*/
- private static Runnable handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs) {
+ private static Runnable handleSystemServerProcess(ZygoteArguments parsedArgs) {
// set umask to 0077 so new files and directories will default to owner-only permissions.
Os.umask(S_IRWXG | S_IRWXO);
- if (parsedArgs.niceName != null) {
- Process.setArgV0(parsedArgs.niceName);
+ if (parsedArgs.mNiceName != null) {
+ Process.setArgV0(parsedArgs.mNiceName);
}
final String systemServerClasspath = Os.getenv("SYSTEMSERVERCLASSPATH");
@@ -454,8 +457,8 @@
}
}
- if (parsedArgs.invokeWith != null) {
- String[] args = parsedArgs.remainingArgs;
+ if (parsedArgs.mInvokeWith != null) {
+ String[] args = parsedArgs.mRemainingArgs;
// If we have a non-null system server class path, we'll have to duplicate the
// existing arguments and append the classpath to it. ART will handle the classpath
// correctly when we exec a new process.
@@ -467,15 +470,15 @@
args = amendedArgs;
}
- WrapperInit.execApplication(parsedArgs.invokeWith,
- parsedArgs.niceName, parsedArgs.targetSdkVersion,
+ WrapperInit.execApplication(parsedArgs.mInvokeWith,
+ parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion,
VMRuntime.getCurrentInstructionSet(), null, args);
throw new IllegalStateException("Unexpected return from WrapperInit.execApplication");
} else {
ClassLoader cl = null;
if (systemServerClasspath != null) {
- cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion);
+ cl = createPathClassLoader(systemServerClasspath, parsedArgs.mTargetSdkVersion);
Thread.currentThread().setContextClassLoader(cl);
}
@@ -483,16 +486,17 @@
/*
* Pass the remaining arguments to SystemServer.
*/
- return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
+ return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
+ parsedArgs.mRemainingArgs, cl);
}
/* should never reach here */
}
/**
- * Note that preparing the profiles for system server does not require special
- * selinux permissions. From the installer perspective the system server is a regular package
- * which can capture profile information.
+ * Note that preparing the profiles for system server does not require special selinux
+ * permissions. From the installer perspective the system server is a regular package which can
+ * capture profile information.
*/
private static void prepareSystemServerProfile(String systemServerClasspath)
throws RemoteException {
@@ -544,8 +548,8 @@
}
/**
- * Performs dex-opt on the elements of {@code classPath}, if needed. We
- * choose the instruction set of the current runtime.
+ * Performs dex-opt on the elements of {@code classPath}, if needed. We choose the instruction
+ * set of the current runtime.
*/
private static void performSystemServerDexOpt(String classPath) {
final String[] classPathElements = classPath.split(":");
@@ -563,8 +567,9 @@
int dexoptNeeded;
try {
dexoptNeeded = DexFile.getDexOptNeeded(
- classPathElement, instructionSet, systemServerFilter,
- null /* classLoaderContext */, false /* newProfile */, false /* downgrade */);
+ classPathElement, instructionSet, systemServerFilter,
+ null /* classLoaderContext */, false /* newProfile */,
+ false /* downgrade */);
} catch (FileNotFoundException ignored) {
// Do not add to the classpath.
Log.w(TAG, "Missing classpath element for system server: " + classPathElement);
@@ -607,8 +612,8 @@
}
/**
- * Encodes the system server class loader context in a format that is accepted by dexopt.
- * This assumes the system server is always loaded with a {@link dalvik.system.PathClassLoader}.
+ * Encodes the system server class loader context in a format that is accepted by dexopt. This
+ * assumes the system server is always loaded with a {@link dalvik.system.PathClassLoader}.
*
* Note that ideally we would use the {@code DexoptUtils} to compute this. However we have no
* dependency here on the server so we hard code the logic again.
@@ -619,10 +624,11 @@
/**
* Encodes the class path in a format accepted by dexopt.
- * @param classPath the old class path (may be empty).
- * @param newElement the new class path elements
- * @return the class path encoding resulted from appending {@code newElement} to
- * {@code classPath}.
+ *
+ * @param classPath The old class path (may be empty).
+ * @param newElement The new class path elements
+ * @return The class path encoding resulted from appending {@code newElement} to {@code
+ * classPath}.
*/
private static String encodeSystemServerClassPath(String classPath, String newElement) {
return (classPath == null || classPath.isEmpty())
@@ -633,25 +639,25 @@
/**
* Prepare the arguments and forks for the system server process.
*
- * Returns an {@code Runnable} that provides an entrypoint into system_server code in the
- * child process, and {@code null} in the parent.
+ * @return A {@code Runnable} that provides an entrypoint into system_server code in the child
+ * process; {@code null} in the parent.
*/
private static Runnable forkSystemServer(String abiList, String socketName,
ZygoteServer zygoteServer) {
long capabilities = posixCapabilitiesAsBits(
- OsConstants.CAP_IPC_LOCK,
- OsConstants.CAP_KILL,
- OsConstants.CAP_NET_ADMIN,
- OsConstants.CAP_NET_BIND_SERVICE,
- OsConstants.CAP_NET_BROADCAST,
- OsConstants.CAP_NET_RAW,
- OsConstants.CAP_SYS_MODULE,
- OsConstants.CAP_SYS_NICE,
- OsConstants.CAP_SYS_PTRACE,
- OsConstants.CAP_SYS_TIME,
- OsConstants.CAP_SYS_TTY_CONFIG,
- OsConstants.CAP_WAKE_ALARM,
- OsConstants.CAP_BLOCK_SUSPEND
+ OsConstants.CAP_IPC_LOCK,
+ OsConstants.CAP_KILL,
+ OsConstants.CAP_NET_ADMIN,
+ OsConstants.CAP_NET_BIND_SERVICE,
+ OsConstants.CAP_NET_BROADCAST,
+ OsConstants.CAP_NET_RAW,
+ OsConstants.CAP_SYS_MODULE,
+ OsConstants.CAP_SYS_NICE,
+ OsConstants.CAP_SYS_PTRACE,
+ OsConstants.CAP_SYS_TIME,
+ OsConstants.CAP_SYS_TTY_CONFIG,
+ OsConstants.CAP_WAKE_ALARM,
+ OsConstants.CAP_BLOCK_SUSPEND
);
/* Containers run without some capabilities, so drop any caps that are not available. */
StructCapUserHeader header = new StructCapUserHeader(
@@ -666,38 +672,39 @@
/* Hardcoded command line to start the system server */
String args[] = {
- "--setuid=1000",
- "--setgid=1000",
- "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,1024,1032,1065,3001,3002,3003,3006,3007,3009,3010",
- "--capabilities=" + capabilities + "," + capabilities,
- "--nice-name=system_server",
- "--runtime-args",
- "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT,
- "com.android.server.SystemServer",
+ "--setuid=1000",
+ "--setgid=1000",
+ "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,"
+ + "1024,1032,1065,3001,3002,3003,3006,3007,3009,3010",
+ "--capabilities=" + capabilities + "," + capabilities,
+ "--nice-name=system_server",
+ "--runtime-args",
+ "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT,
+ "com.android.server.SystemServer",
};
- ZygoteConnection.Arguments parsedArgs = null;
+ ZygoteArguments parsedArgs = null;
int pid;
try {
- parsedArgs = new ZygoteConnection.Arguments(args);
- ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
- ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);
+ parsedArgs = new ZygoteArguments(args);
+ Zygote.applyDebuggerSystemProperty(parsedArgs);
+ Zygote.applyInvokeWithSystemProperty(parsedArgs);
boolean profileSystemServer = SystemProperties.getBoolean(
"dalvik.vm.profilesystemserver", false);
if (profileSystemServer) {
- parsedArgs.runtimeFlags |= Zygote.PROFILE_SYSTEM_SERVER;
+ parsedArgs.mRuntimeFlags |= Zygote.PROFILE_SYSTEM_SERVER;
}
/* Request to fork the system server process */
pid = Zygote.forkSystemServer(
- parsedArgs.uid, parsedArgs.gid,
- parsedArgs.gids,
- parsedArgs.runtimeFlags,
+ parsedArgs.mUid, parsedArgs.mGid,
+ parsedArgs.mGids,
+ parsedArgs.mRuntimeFlags,
null,
- parsedArgs.permittedCapabilities,
- parsedArgs.effectiveCapabilities);
+ parsedArgs.mPermittedCapabilities,
+ parsedArgs.mEffectiveCapabilities);
} catch (IllegalArgumentException ex) {
throw new RuntimeException(ex);
}
@@ -785,10 +792,10 @@
if (!enableLazyPreload) {
bootTimingsTraceLog.traceBegin("ZygotePreload");
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
- SystemClock.uptimeMillis());
+ SystemClock.uptimeMillis());
preload(bootTimingsTraceLog);
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
- SystemClock.uptimeMillis());
+ SystemClock.uptimeMillis());
bootTimingsTraceLog.traceEnd(); // ZygotePreload
} else {
Zygote.resetNicePriority();
@@ -844,17 +851,16 @@
/**
* Return {@code true} if this device configuration has another zygote.
*
- * We determine this by comparing the device ABI list with this zygotes
- * list. If this zygote supports all ABIs this device supports, there won't
- * be another zygote.
+ * We determine this by comparing the device ABI list with this zygotes list. If this zygote
+ * supports all ABIs this device supports, there won't be another zygote.
*/
private static boolean hasSecondZygote(String abiList) {
return !SystemProperties.get("ro.product.cpu.abilist").equals(abiList);
}
private static void waitForSecondaryZygote(String socketName) {
- String otherZygoteName = Process.ZYGOTE_SOCKET.equals(socketName) ?
- Process.SECONDARY_ZYGOTE_SOCKET : Process.ZYGOTE_SOCKET;
+ String otherZygoteName = ZygoteProcess.ZYGOTE_SOCKET_NAME.equals(socketName)
+ ? ZygoteProcess.ZYGOTE_SECONDARY_SOCKET_NAME : ZygoteProcess.ZYGOTE_SOCKET_NAME;
ZygoteProcess.waitForConnectionToZygote(otherZygoteName);
}
@@ -869,9 +875,8 @@
}
/**
- * The main function called when started through the zygote process. This
- * could be unified with main(), if the native code in nativeFinishInit()
- * were rationalized with Zygote startup.<p>
+ * The main function called when started through the zygote process. This could be unified with
+ * main(), if the native code in nativeFinishInit() were rationalized with Zygote startup.<p>
*
* Current recognized args:
* <ul>
@@ -881,7 +886,8 @@
* @param targetSdkVersion target SDK version
* @param argv arg strings
*/
- public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) {
+ public static final Runnable zygoteInit(int targetSdkVersion, String[] argv,
+ ClassLoader classLoader) {
if (RuntimeInit.DEBUG) {
Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
}
@@ -895,9 +901,9 @@
}
/**
- * The main function called when starting a child zygote process. This is used as an
- * alternative to zygoteInit(), which skips calling into initialization routines that
- * start the Binder threadpool.
+ * The main function called when starting a child zygote process. This is used as an alternative
+ * to zygoteInit(), which skips calling into initialization routines that start the Binder
+ * threadpool.
*/
static final Runnable childZygoteInit(
int targetSdkVersion, String[] argv, ClassLoader classLoader) {
diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java
index fecf9b9..c1bfde1 100644
--- a/core/java/com/android/internal/os/ZygoteServer.java
+++ b/core/java/com/android/internal/os/ZygoteServer.java
@@ -20,14 +20,14 @@
import android.net.LocalServerSocket;
import android.net.LocalSocket;
-import android.system.Os;
import android.system.ErrnoException;
+import android.system.Os;
import android.system.StructPollfd;
import android.util.Log;
-
import android.util.Slog;
-import java.io.IOException;
+
import java.io.FileDescriptor;
+import java.io.IOException;
import java.util.ArrayList;
/**
@@ -63,8 +63,7 @@
*/
private boolean mIsForkChild;
- ZygoteServer() {
- }
+ ZygoteServer() { }
void setForkChild() {
mIsForkChild = true;
@@ -197,7 +196,7 @@
if (i == 0) {
ZygoteConnection newPeer = acceptCommandPeer(abiList);
peers.add(newPeer);
- fds.add(newPeer.getFileDesciptor());
+ fds.add(newPeer.getFileDescriptor());
} else {
try {
ZygoteConnection connection = peers.get(i);
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 197e873..d61f10e 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -65,7 +65,7 @@
int dismissalSurface, int dismissalSentiment, in NotificationVisibility nv);
void onNotificationVisibilityChanged( in NotificationVisibility[] newlyVisibleKeys,
in NotificationVisibility[] noLongerVisibleKeys);
- void onNotificationExpansionChanged(in String key, in boolean userAction, in boolean expanded);
+ void onNotificationExpansionChanged(in String key, in boolean userAction, in boolean expanded, in int notificationLocation);
void onNotificationDirectReplied(String key);
void onNotificationSmartSuggestionsAdded(String key, int smartReplyCount, int smartActionCount,
boolean generatedByAsssistant);
diff --git a/core/java/com/android/internal/statusbar/NotificationVisibility.java b/core/java/com/android/internal/statusbar/NotificationVisibility.java
index a7203e7..24bb789 100644
--- a/core/java/com/android/internal/statusbar/NotificationVisibility.java
+++ b/core/java/com/android/internal/statusbar/NotificationVisibility.java
@@ -21,6 +21,8 @@
import android.os.Parcelable;
import android.util.Log;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
import java.util.ArrayDeque;
import java.util.Collection;
@@ -33,18 +35,53 @@
public int rank;
public int count;
public boolean visible = true;
+ /** The visible location of the notification, could be e.g. notification shade or HUN. */
+ public NotificationLocation location;
/*package*/ int id;
+ /**
+ * The UI location of the notification.
+ *
+ * There is a one-to-one mapping between this enum and
+ * MetricsProto.MetricsEvent.NotificationLocation.
+ */
+ public enum NotificationLocation {
+ LOCATION_UNKNOWN(MetricsEvent.LOCATION_UNKNOWN),
+ LOCATION_FIRST_HEADS_UP(MetricsEvent.LOCATION_FIRST_HEADS_UP), // visible heads-up
+ LOCATION_HIDDEN_TOP(MetricsEvent.LOCATION_HIDDEN_TOP), // hidden/scrolled away on the top
+ LOCATION_MAIN_AREA(MetricsEvent.LOCATION_MAIN_AREA), // visible in the shade
+ // in the bottom stack, and peeking
+ LOCATION_BOTTOM_STACK_PEEKING(MetricsEvent.LOCATION_BOTTOM_STACK_PEEKING),
+ // in the bottom stack, and hidden
+ LOCATION_BOTTOM_STACK_HIDDEN(MetricsEvent.LOCATION_BOTTOM_STACK_HIDDEN),
+ LOCATION_GONE(MetricsEvent.LOCATION_GONE); // the view isn't laid out at all
+
+ private final int mMetricsEventNotificationLocation;
+
+ NotificationLocation(int metricsEventNotificationLocation) {
+ mMetricsEventNotificationLocation = metricsEventNotificationLocation;
+ }
+
+ /**
+ * Returns the field from MetricsEvent.NotificationLocation that corresponds to this object.
+ */
+ public int toMetricsEventEnum() {
+ return mMetricsEventNotificationLocation;
+ }
+ }
+
private NotificationVisibility() {
id = sNexrId++;
}
- private NotificationVisibility(String key, int rank, int count, boolean visibile) {
+ private NotificationVisibility(String key, int rank, int count, boolean visible,
+ NotificationLocation location) {
this();
this.key = key;
this.rank = rank;
this.count = count;
- this.visible = visibile;
+ this.visible = visible;
+ this.location = location;
}
@Override
@@ -54,12 +91,13 @@
+ " rank=" + rank
+ " count=" + count
+ (visible?" visible":"")
+ + " location=" + location.name()
+ " )";
}
@Override
public NotificationVisibility clone() {
- return obtain(this.key, this.rank, this.count, this.visible);
+ return obtain(this.key, this.rank, this.count, this.visible, this.location);
}
@Override
@@ -89,6 +127,7 @@
out.writeInt(this.rank);
out.writeInt(this.count);
out.writeInt(this.visible ? 1 : 0);
+ out.writeString(this.location.name());
}
private void readFromParcel(Parcel in) {
@@ -96,18 +135,28 @@
this.rank = in.readInt();
this.count = in.readInt();
this.visible = in.readInt() != 0;
+ this.location = NotificationLocation.valueOf(in.readString());
}
/**
- * Return a new NotificationVisibility instance from the global pool. Allows us to
- * avoid allocating new objects in many cases.
+ * Create a new NotificationVisibility object.
*/
public static NotificationVisibility obtain(String key, int rank, int count, boolean visible) {
+ return obtain(key, rank, count, visible,
+ NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
+ }
+
+ /**
+ * Create a new NotificationVisibility object.
+ */
+ public static NotificationVisibility obtain(String key, int rank, int count, boolean visible,
+ NotificationLocation location) {
NotificationVisibility vo = obtain();
vo.key = key;
vo.rank = rank;
vo.count = count;
vo.visible = visible;
+ vo.location = location;
return vo;
}
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index 605df04..f91b837 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -327,10 +327,6 @@
}
}
- public static @NonNull <T> List<T> defeatNullable(@Nullable List<T> val) {
- return (val != null) ? val : Collections.emptyList();
- }
-
/**
* @return the first element if not empty/null, null otherwise
*/
diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java
index 1aa32cc..276cad9 100644
--- a/core/java/com/android/internal/util/Protocol.java
+++ b/core/java/com/android/internal/util/Protocol.java
@@ -53,7 +53,6 @@
public static final int BASE_WIFI_PASSPOINT_MANAGER = 0x00028000;
public static final int BASE_WIFI_PASSPOINT_SERVICE = 0x00028100;
public static final int BASE_WIFI_LOGGER = 0x00028300;
- public static final int BASE_DHCP = 0x00030000;
public static final int BASE_DATA_CONNECTION = 0x00040000;
public static final int BASE_DATA_CONNECTION_AC = 0x00041000;
public static final int BASE_DATA_CONNECTION_TRACKER = 0x00042000;
@@ -62,7 +61,6 @@
public static final int BASE_NETWORK_STATE_TRACKER = 0x00070000;
public static final int BASE_CONNECTIVITY_MANAGER = 0x00080000;
public static final int BASE_NETWORK_AGENT = 0x00081000;
- public static final int BASE_NETWORK_MONITOR = 0x00082000;
public static final int BASE_NETWORK_FACTORY = 0x00083000;
public static final int BASE_ETHERNET = 0x00084000;
public static final int BASE_LOWPAN = 0x00085000;
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 70f4ed2..0752efe 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -17,7 +17,6 @@
package com.android.internal.view;
import android.os.ResultReceiver;
-import android.text.style.SuggestionSpan;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.EditorInfo;
@@ -36,7 +35,6 @@
// TODO: Use ParceledListSlice instead
List<InputMethodInfo> getInputMethodList();
- List<InputMethodInfo> getVrInputMethodList();
// TODO: Use ParceledListSlice instead
List<InputMethodInfo> getEnabledInputMethodList();
List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in String imiId,
@@ -65,10 +63,7 @@
int displayId);
void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId);
boolean isInputMethodPickerShownForTest();
- void registerSuggestionSpansForNotification(in SuggestionSpan[] spans);
- boolean notifySuggestionPicked(in SuggestionSpan span, String originalString, int index);
InputMethodSubtype getCurrentInputMethodSubtype();
- boolean setCurrentInputMethodSubtype(in InputMethodSubtype subtype);
void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes);
// This is kept due to @UnsupportedAppUsage.
// TODO(Bug 113914148): Consider removing this.
diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java
index 7f7528d..78688ed 100644
--- a/core/java/com/android/internal/widget/EditableInputConnection.java
+++ b/core/java/com/android/internal/widget/EditableInputConnection.java
@@ -18,9 +18,7 @@
import android.os.Bundle;
import android.text.Editable;
-import android.text.Spanned;
import android.text.method.KeyListener;
-import android.text.style.SuggestionSpan;
import android.util.Log;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
@@ -173,12 +171,6 @@
if (mTextView == null) {
return super.commitText(text, newCursorPosition);
}
- if (text instanceof Spanned) {
- Spanned spanned = ((Spanned) text);
- SuggestionSpan[] spans = spanned.getSpans(0, text.length(), SuggestionSpan.class);
- mIMM.registerSuggestionSpansForNotification(spans);
- }
-
mTextView.resetErrorChangedFlag();
boolean success = super.commitText(text, newCursorPosition);
mTextView.hideErrorIfUnchanged();
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index d5dc703..8d3c482 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -34,6 +34,7 @@
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.AsyncTask;
import android.os.Handler;
@@ -176,6 +177,7 @@
private UserManager mUserManager;
private final Handler mHandler;
private final SparseLongArray mLockoutDeadlines = new SparseLongArray();
+ private Boolean mHasSecureLockScreen;
/**
* Use {@link TrustManager#isTrustUsuallyManaged(int)}.
@@ -706,6 +708,10 @@
* @param userId the user whose pattern is to be saved.
*/
public void saveLockPattern(List<LockPatternView.Cell> pattern, String savedPattern, int userId) {
+ if (!hasSecureLockScreen()) {
+ throw new UnsupportedOperationException(
+ "This operation requires the lock screen feature.");
+ }
if (pattern == null || pattern.size() < MIN_LOCK_PATTERN_SIZE) {
throw new IllegalArgumentException("pattern must not be null and at least "
+ MIN_LOCK_PATTERN_SIZE + " dots long.");
@@ -801,6 +807,10 @@
/** Update the encryption password if it is enabled **/
private void updateEncryptionPassword(final int type, final String password) {
+ if (!hasSecureLockScreen()) {
+ throw new UnsupportedOperationException(
+ "This operation requires the lock screen feature.");
+ }
if (!isDeviceEncryptionEnabled()) {
return;
}
@@ -835,6 +845,10 @@
*/
public void saveLockPassword(String password, String savedPassword, int requestedQuality,
int userHandle) {
+ if (!hasSecureLockScreen()) {
+ throw new UnsupportedOperationException(
+ "This operation requires the lock screen feature.");
+ }
if (password == null || password.length() < MIN_LOCK_PASSWORD_SIZE) {
throw new IllegalArgumentException("password must not be null and at least "
+ "of length " + MIN_LOCK_PASSWORD_SIZE);
@@ -1621,6 +1635,10 @@
*/
public boolean setLockCredentialWithToken(String credential, int type, int requestedQuality,
long tokenHandle, byte[] token, int userId) {
+ if (!hasSecureLockScreen()) {
+ throw new UnsupportedOperationException(
+ "This operation requires the lock screen feature.");
+ }
LockSettingsInternal localService = getLockSettingsInternal();
if (type != CREDENTIAL_TYPE_NONE) {
if (TextUtils.isEmpty(credential) || credential.length() < MIN_LOCK_PASSWORD_SIZE) {
@@ -1854,6 +1872,17 @@
return getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM) != 0;
}
+ /**
+ * Return true if the device supports the lock screen feature, false otherwise.
+ */
+ public boolean hasSecureLockScreen() {
+ if (mHasSecureLockScreen == null) {
+ mHasSecureLockScreen = Boolean.valueOf(mContext.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN));
+ }
+ return mHasSecureLockScreen.booleanValue();
+ }
+
public static boolean userOwnsFrpCredential(Context context, UserInfo info) {
return info != null && info.isPrimary() && info.isAdmin() && frpCredentialEnabled(context);
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 5be70ef..be12700 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -61,8 +61,11 @@
"android_database_SQLiteConnection.cpp",
"android_database_SQLiteGlobal.cpp",
"android_database_SQLiteDebug.cpp",
+ "android_graphics_Canvas.cpp",
+ "android_graphics_ColorSpace.cpp",
"android_graphics_drawable_AnimatedVectorDrawable.cpp",
"android_graphics_drawable_VectorDrawable.cpp",
+ "android_graphics_Picture.cpp",
"android_view_DisplayEventReceiver.cpp",
"android_view_DisplayListCanvas.cpp",
"android_view_TextureLayer.cpp",
@@ -117,8 +120,6 @@
"android_util_StringBlock.cpp",
"android_util_XmlBlock.cpp",
"android_util_jar_StrictJarFile.cpp",
- "android_graphics_Canvas.cpp",
- "android_graphics_Picture.cpp",
"android/graphics/AnimatedImageDrawable.cpp",
"android/graphics/Bitmap.cpp",
"android/graphics/BitmapFactory.cpp",
@@ -185,6 +186,7 @@
"android_hardware_UsbDevice.cpp",
"android_hardware_UsbDeviceConnection.cpp",
"android_hardware_UsbRequest.cpp",
+ "android_hardware_location_ActivityRecognitionHardware.cpp",
"android_util_FileObserver.cpp",
"android/opengl/poly_clip.cpp", // TODO: .arm
"android/opengl/util.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index a586dc1..18d9b5a 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -101,6 +101,7 @@
extern int register_android_hardware_UsbDevice(JNIEnv *env);
extern int register_android_hardware_UsbDeviceConnection(JNIEnv *env);
extern int register_android_hardware_UsbRequest(JNIEnv *env);
+extern int register_android_hardware_location_ActivityRecognitionHardware(JNIEnv* env);
extern int register_android_media_AudioEffectDescriptor(JNIEnv *env);
extern int register_android_media_AudioRecord(JNIEnv *env);
@@ -130,6 +131,7 @@
extern int register_android_graphics_Canvas(JNIEnv* env);
extern int register_android_graphics_CanvasProperty(JNIEnv* env);
extern int register_android_graphics_ColorFilter(JNIEnv* env);
+extern int register_android_graphics_ColorSpace(JNIEnv* env);
extern int register_android_graphics_DrawFilter(JNIEnv* env);
extern int register_android_graphics_FontFamily(JNIEnv* env);
extern int register_android_graphics_Matrix(JNIEnv* env);
@@ -1360,6 +1362,9 @@
REG_JNI(register_android_os_VintfRuntimeInfo),
REG_JNI(register_android_nio_utils),
REG_JNI(register_android_graphics_Canvas),
+ // This needs to be before register_android_graphics_Graphics, or the latter
+ // will not be able to find the jmethodID for ColorSpace.get().
+ REG_JNI(register_android_graphics_ColorSpace),
REG_JNI(register_android_graphics_Graphics),
REG_JNI(register_android_view_DisplayEventReceiver),
REG_JNI(register_android_view_RenderNode),
@@ -1456,6 +1461,7 @@
REG_JNI(register_android_hardware_UsbDevice),
REG_JNI(register_android_hardware_UsbDeviceConnection),
REG_JNI(register_android_hardware_UsbRequest),
+ REG_JNI(register_android_hardware_location_ActivityRecognitionHardware),
REG_JNI(register_android_media_AudioEffectDescriptor),
REG_JNI(register_android_media_AudioSystem),
REG_JNI(register_android_media_AudioRecord),
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 876bd4f..ad51c47 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -334,7 +334,7 @@
static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,
jint offset, jint stride, jint width, jint height,
jint configHandle, jboolean isMutable,
- jfloatArray xyzD50, jobject transferParameters) {
+ jlong colorSpacePtr) {
SkColorType colorType = GraphicsJNI::legacyBitmapConfigToColorType(configHandle);
if (NULL != jColors) {
size_t n = env->GetArrayLength(jColors);
@@ -350,17 +350,8 @@
}
SkBitmap bitmap;
- sk_sp<SkColorSpace> colorSpace;
-
- if (xyzD50 == nullptr || transferParameters == nullptr) {
- colorSpace = SkColorSpace::MakeSRGB();
- } else {
- skcms_TransferFunction p = GraphicsJNI::getNativeTransferParameters(env, transferParameters);
- skcms_Matrix3x3 xyzMatrix = GraphicsJNI::getNativeXYZMatrix(env, xyzD50);
- colorSpace = SkColorSpace::MakeRGB(p, xyzMatrix);
- }
-
- bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType, colorSpace));
+ bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType,
+ GraphicsJNI::getNativeColorSpace(colorSpacePtr)));
sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(&bitmap);
if (!nativeBitmap) {
@@ -582,17 +573,14 @@
bitmapErase(skBitmap, SkColor4f::FromColor(color), SkColorSpace::MakeSRGB());
}
-static void Bitmap_eraseLong(JNIEnv* env, jobject, jlong bitmapHandle, jobject jColorSpace,
- jfloat r, jfloat g, jfloat b, jfloat a) {
- sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(env, jColorSpace);
- if (GraphicsJNI::hasException(env)) {
- return;
- }
-
+static void Bitmap_eraseLong(JNIEnv* env, jobject, jlong bitmapHandle,
+ jlong colorSpaceHandle, jlong colorLong) {
LocalScopedBitmap bitmap(bitmapHandle);
SkBitmap skBitmap;
bitmap->getSkBitmap(&skBitmap);
- SkColor4f color = SkColor4f{r, g, b, a};
+
+ SkColor4f color = GraphicsJNI::convertColorLong(colorLong);
+ sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(colorSpaceHandle);
bitmapErase(skBitmap, color, cs);
}
@@ -1141,14 +1129,12 @@
}
static jobject Bitmap_wrapHardwareBufferBitmap(JNIEnv* env, jobject, jobject hardwareBuffer,
- jfloatArray xyzD50, jobject transferParameters) {
- skcms_TransferFunction p = GraphicsJNI::getNativeTransferParameters(env, transferParameters);
- skcms_Matrix3x3 xyzMatrix = GraphicsJNI::getNativeXYZMatrix(env, xyzD50);
- sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeRGB(p, xyzMatrix);
+ jlong colorSpacePtr) {
AHardwareBuffer* hwBuf = android_hardware_HardwareBuffer_getNativeHardwareBuffer(env,
hardwareBuffer);
sp<GraphicBuffer> buffer(AHardwareBuffer_to_GraphicBuffer(hwBuf));
- sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, colorSpace);
+ sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer,
+ GraphicsJNI::getNativeColorSpace(colorSpacePtr));
if (!bitmap.get()) {
ALOGW("failed to create hardware bitmap from hardware buffer");
return NULL;
@@ -1190,7 +1176,7 @@
///////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gBitmapMethods[] = {
- { "nativeCreate", "([IIIIIIZ[FLandroid/graphics/ColorSpace$Rgb$TransferParameters;)Landroid/graphics/Bitmap;",
+ { "nativeCreate", "([IIIIIIZJ)Landroid/graphics/Bitmap;",
(void*)Bitmap_creator },
{ "nativeCopy", "(JIZ)Landroid/graphics/Bitmap;",
(void*)Bitmap_copy },
@@ -1204,7 +1190,7 @@
{ "nativeCompress", "(JIILjava/io/OutputStream;[B)Z",
(void*)Bitmap_compress },
{ "nativeErase", "(JI)V", (void*)Bitmap_erase },
- { "nativeErase", "(JLandroid/graphics/ColorSpace;FFFF)V", (void*)Bitmap_eraseLong },
+ { "nativeErase", "(JJJ)V", (void*)Bitmap_eraseLong },
{ "nativeRowBytes", "(J)I", (void*)Bitmap_rowBytes },
{ "nativeConfig", "(J)I", (void*)Bitmap_config },
{ "nativeHasAlpha", "(J)Z", (void*)Bitmap_hasAlpha },
@@ -1236,7 +1222,7 @@
(void*)Bitmap_copyPreserveInternalConfig },
{ "nativeCreateHardwareBitmap", "(Landroid/graphics/GraphicBuffer;)Landroid/graphics/Bitmap;",
(void*) Bitmap_createHardwareBitmap },
- { "nativeWrapHardwareBufferBitmap", "(Landroid/hardware/HardwareBuffer;[FLandroid/graphics/ColorSpace$Rgb$TransferParameters;)Landroid/graphics/Bitmap;",
+ { "nativeWrapHardwareBufferBitmap", "(Landroid/hardware/HardwareBuffer;J)Landroid/graphics/Bitmap;",
(void*) Bitmap_wrapHardwareBufferBitmap },
{ "nativeCreateGraphicBufferHandle", "(J)Landroid/graphics/GraphicBuffer;",
(void*) Bitmap_createGraphicBufferHandle },
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 4b0ab5b..7d0d3d8 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -179,7 +179,7 @@
}
static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream,
- jobject padding, jobject options) {
+ jobject padding, jobject options, jlong colorSpaceHandle) {
// Set default values for the options parameters.
int sampleSize = 1;
bool onlyDecodeSize = false;
@@ -189,7 +189,7 @@
float scale = 1.0f;
bool requireUnpremultiplied = false;
jobject javaBitmap = NULL;
- sk_sp<SkColorSpace> prefColorSpace = nullptr;
+ sk_sp<SkColorSpace> prefColorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle);
// Update with options supplied by the client.
if (options != NULL) {
@@ -213,8 +213,6 @@
jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
prefColorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig);
- jobject jcolorSpace = env->GetObjectField(options, gOptions_colorSpaceFieldID);
- prefColorSpace = GraphicsJNI::getNativeColorSpace(env, jcolorSpace);
isHardware = GraphicsJNI::isHardwareConfig(env, jconfig);
isMutable = env->GetBooleanField(options, gOptions_mutableFieldID);
requireUnpremultiplied = !env->GetBooleanField(options, gOptions_premultipliedFieldID);
@@ -515,7 +513,7 @@
}
static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
- jobject padding, jobject options) {
+ jobject padding, jobject options, jlong colorSpaceHandle) {
jobject bitmap = NULL;
std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage));
@@ -524,13 +522,13 @@
std::unique_ptr<SkStreamRewindable> bufferedStream(
SkFrontBufferedStream::Make(std::move(stream), SkCodec::MinBufferedBytesNeeded()));
SkASSERT(bufferedStream.get() != NULL);
- bitmap = doDecode(env, std::move(bufferedStream), padding, options);
+ bitmap = doDecode(env, std::move(bufferedStream), padding, options, colorSpaceHandle);
}
return bitmap;
}
static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fileDescriptor,
- jobject padding, jobject bitmapFactoryOptions) {
+ jobject padding, jobject bitmapFactoryOptions, jlong colorSpaceHandle) {
NPE_CHECK_RETURN_ZERO(env, fileDescriptor);
@@ -566,7 +564,8 @@
// If there is no offset for the file descriptor, we use SkFILEStream directly.
if (::lseek(descriptor, 0, SEEK_CUR) == 0) {
assert(isSeekable(dupDescriptor));
- return doDecode(env, std::move(fileStream), padding, bitmapFactoryOptions);
+ return doDecode(env, std::move(fileStream), padding, bitmapFactoryOptions,
+ colorSpaceHandle);
}
// Use a buffered stream. Although an SkFILEStream can be rewound, this
@@ -575,24 +574,25 @@
std::unique_ptr<SkStreamRewindable> stream(SkFrontBufferedStream::Make(std::move(fileStream),
SkCodec::MinBufferedBytesNeeded()));
- return doDecode(env, std::move(stream), padding, bitmapFactoryOptions);
+ return doDecode(env, std::move(stream), padding, bitmapFactoryOptions, colorSpaceHandle);
}
static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jlong native_asset,
- jobject padding, jobject options) {
+ jobject padding, jobject options, jlong colorSpaceHandle) {
Asset* asset = reinterpret_cast<Asset*>(native_asset);
// since we know we'll be done with the asset when we return, we can
// just use a simple wrapper
- return doDecode(env, skstd::make_unique<AssetStreamAdaptor>(asset), padding, options);
+ return doDecode(env, skstd::make_unique<AssetStreamAdaptor>(asset), padding, options,
+ colorSpaceHandle);
}
static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
- jint offset, jint length, jobject options) {
+ jint offset, jint length, jobject options, jlong colorSpaceHandle) {
AutoJavaByteArray ar(env, byteArray);
return doDecode(env, skstd::make_unique<SkMemoryStream>(ar.ptr() + offset, length, false),
- nullptr, options);
+ nullptr, options, colorSpaceHandle);
}
static jboolean nativeIsSeekable(JNIEnv* env, jobject, jobject fileDescriptor) {
@@ -600,31 +600,26 @@
return isSeekable(descriptor) ? JNI_TRUE : JNI_FALSE;
}
-jobject decodeBitmap(JNIEnv* env, void* data, size_t size) {
- return doDecode(env, skstd::make_unique<SkMemoryStream>(data, size),
- nullptr, nullptr);
-}
-
///////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gMethods[] = {
{ "nativeDecodeStream",
- "(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
+ "(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;J)Landroid/graphics/Bitmap;",
(void*)nativeDecodeStream
},
{ "nativeDecodeFileDescriptor",
- "(Ljava/io/FileDescriptor;Landroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
+ "(Ljava/io/FileDescriptor;Landroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;J)Landroid/graphics/Bitmap;",
(void*)nativeDecodeFileDescriptor
},
{ "nativeDecodeAsset",
- "(JLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
+ "(JLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;J)Landroid/graphics/Bitmap;",
(void*)nativeDecodeAsset
},
{ "nativeDecodeByteArray",
- "([BIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
+ "([BIILandroid/graphics/BitmapFactory$Options;J)Landroid/graphics/Bitmap;",
(void*)nativeDecodeByteArray
},
diff --git a/core/jni/android/graphics/BitmapFactory.h b/core/jni/android/graphics/BitmapFactory.h
index 1ee49fa..e37c98d 100644
--- a/core/jni/android/graphics/BitmapFactory.h
+++ b/core/jni/android/graphics/BitmapFactory.h
@@ -28,6 +28,4 @@
jstring encodedFormatToString(JNIEnv* env, SkEncodedImageFormat format);
-jobject decodeBitmap(JNIEnv* env, void* data, size_t size);
-
#endif // _ANDROID_GRAPHICS_BITMAP_FACTORY_H_
diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp
index f831c05..b4ba749 100644
--- a/core/jni/android/graphics/BitmapRegionDecoder.cpp
+++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp
@@ -124,7 +124,7 @@
* reportSizeToVM not supported
*/
static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint inputX,
- jint inputY, jint inputWidth, jint inputHeight, jobject options) {
+ jint inputY, jint inputWidth, jint inputHeight, jobject options, jlong colorSpaceHandle) {
// Set default options.
int sampleSize = 1;
@@ -132,14 +132,12 @@
bool requireUnpremul = false;
jobject javaBitmap = NULL;
bool isHardware = false;
- sk_sp<SkColorSpace> colorSpace = nullptr;
+ sk_sp<SkColorSpace> colorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle);
// Update the default options with any options supplied by the client.
if (NULL != options) {
sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
colorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig);
- jobject jcolorSpace = env->GetObjectField(options, gOptions_colorSpaceFieldID);
- colorSpace = GraphicsJNI::getNativeColorSpace(env, jcolorSpace);
isHardware = GraphicsJNI::isHardwareConfig(env, jconfig);
requireUnpremul = !env->GetBooleanField(options, gOptions_premultipliedFieldID);
javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
@@ -255,7 +253,7 @@
static const JNINativeMethod gBitmapRegionDecoderMethods[] = {
{ "nativeDecodeRegion",
- "(JIIIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
+ "(JIIIILandroid/graphics/BitmapFactory$Options;J)Landroid/graphics/Bitmap;",
(void*)nativeDecodeRegion},
{ "nativeGetHeight", "(J)I", (void*)nativeGetHeight},
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 9e74b88..6570992 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -178,23 +178,11 @@
static jmethodID gVMRuntime_newNonMovableArray;
static jmethodID gVMRuntime_addressOf;
-static jfieldID gTransferParams_aFieldID;
-static jfieldID gTransferParams_bFieldID;
-static jfieldID gTransferParams_cFieldID;
-static jfieldID gTransferParams_dFieldID;
-static jfieldID gTransferParams_eFieldID;
-static jfieldID gTransferParams_fFieldID;
-static jfieldID gTransferParams_gFieldID;
-
static jclass gColorSpace_class;
-static jfieldID gColorSpace_IlluminantD50FieldID;
-static jmethodID gColorSpace_adaptMethodID;
static jmethodID gColorSpace_getMethodID;
static jmethodID gColorSpace_matchMethodID;
static jclass gColorSpaceRGB_class;
-static jmethodID gColorSpaceRGB_getTransferParametersMethodID;
-static jmethodID gColorSpaceRGB_getTransformMethodID;
static jmethodID gColorSpaceRGB_constructorMethodID;
static jclass gColorSpace_Named_class;
@@ -424,63 +412,6 @@
///////////////////////////////////////////////////////////////////////////////
-skcms_TransferFunction GraphicsJNI::getNativeTransferParameters(JNIEnv* env, jobject transferParams) {
- skcms_TransferFunction p;
- p.a = (float) env->GetDoubleField(transferParams, gTransferParams_aFieldID);
- p.b = (float) env->GetDoubleField(transferParams, gTransferParams_bFieldID);
- p.c = (float) env->GetDoubleField(transferParams, gTransferParams_cFieldID);
- p.d = (float) env->GetDoubleField(transferParams, gTransferParams_dFieldID);
- p.e = (float) env->GetDoubleField(transferParams, gTransferParams_eFieldID);
- p.f = (float) env->GetDoubleField(transferParams, gTransferParams_fFieldID);
- p.g = (float) env->GetDoubleField(transferParams, gTransferParams_gFieldID);
- return p;
-}
-
-skcms_Matrix3x3 GraphicsJNI::getNativeXYZMatrix(JNIEnv* env, jfloatArray xyzD50) {
- skcms_Matrix3x3 xyzMatrix;
- jfloat* array = env->GetFloatArrayElements(xyzD50, NULL);
- xyzMatrix.vals[0][0] = array[0];
- xyzMatrix.vals[1][0] = array[1];
- xyzMatrix.vals[2][0] = array[2];
- xyzMatrix.vals[0][1] = array[3];
- xyzMatrix.vals[1][1] = array[4];
- xyzMatrix.vals[2][1] = array[5];
- xyzMatrix.vals[0][2] = array[6];
- xyzMatrix.vals[1][2] = array[7];
- xyzMatrix.vals[2][2] = array[8];
- env->ReleaseFloatArrayElements(xyzD50, array, 0);
- return xyzMatrix;
-}
-
-sk_sp<SkColorSpace> GraphicsJNI::getNativeColorSpace(JNIEnv* env, jobject colorSpace) {
- if (colorSpace == nullptr) return nullptr;
- if (!env->IsInstanceOf(colorSpace, gColorSpaceRGB_class)) {
- doThrowIAE(env, "The color space must be an RGB color space");
- return nullptr;
- }
-
- jobject transferParams = env->CallObjectMethod(colorSpace,
- gColorSpaceRGB_getTransferParametersMethodID);
- if (transferParams == nullptr) {
- doThrowIAE(env, "The color space must use an ICC parametric transfer function");
- return nullptr;
- }
-
- jfloatArray illuminantD50 = (jfloatArray) env->GetStaticObjectField(gColorSpace_class,
- gColorSpace_IlluminantD50FieldID);
- jobject colorSpaceD50 = env->CallStaticObjectMethod(gColorSpace_class,
- gColorSpace_adaptMethodID, colorSpace, illuminantD50);
-
- jfloatArray xyzD50 = (jfloatArray) env->CallObjectMethod(colorSpaceD50,
- gColorSpaceRGB_getTransformMethodID);
-
- skcms_Matrix3x3 xyzMatrix = getNativeXYZMatrix(env, xyzD50);
- skcms_TransferFunction transferFunction = getNativeTransferParameters(env, transferParams);
-
- return SkColorSpace::MakeRGB(transferFunction, xyzMatrix);
-}
-
-
jobject GraphicsJNI::getColorSpace(JNIEnv* env, sk_sp<SkColorSpace>& decodeColorSpace,
SkColorType decodeColorType) {
jobject colorSpace = nullptr;
@@ -712,20 +643,7 @@
"(Ljava/lang/Class;I)Ljava/lang/Object;");
gVMRuntime_addressOf = GetMethodIDOrDie(env, gVMRuntime_class, "addressOf", "(Ljava/lang/Object;)J");
- jclass transfer_params_class = FindClassOrDie(env, "android/graphics/ColorSpace$Rgb$TransferParameters");
- gTransferParams_aFieldID = GetFieldIDOrDie(env, transfer_params_class, "a", "D");
- gTransferParams_bFieldID = GetFieldIDOrDie(env, transfer_params_class, "b", "D");
- gTransferParams_cFieldID = GetFieldIDOrDie(env, transfer_params_class, "c", "D");
- gTransferParams_dFieldID = GetFieldIDOrDie(env, transfer_params_class, "d", "D");
- gTransferParams_eFieldID = GetFieldIDOrDie(env, transfer_params_class, "e", "D");
- gTransferParams_fFieldID = GetFieldIDOrDie(env, transfer_params_class, "f", "D");
- gTransferParams_gFieldID = GetFieldIDOrDie(env, transfer_params_class, "g", "D");
-
gColorSpace_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ColorSpace"));
- gColorSpace_IlluminantD50FieldID = GetStaticFieldIDOrDie(env,
- gColorSpace_class, "ILLUMINANT_D50", "[F");
- gColorSpace_adaptMethodID = GetStaticMethodIDOrDie(env, gColorSpace_class, "adapt",
- "(Landroid/graphics/ColorSpace;[F)Landroid/graphics/ColorSpace;");
gColorSpace_getMethodID = GetStaticMethodIDOrDie(env, gColorSpace_class,
"get", "(Landroid/graphics/ColorSpace$Named;)Landroid/graphics/ColorSpace;");
gColorSpace_matchMethodID = GetStaticMethodIDOrDie(env, gColorSpace_class, "match",
@@ -735,10 +653,6 @@
FindClassOrDie(env, "android/graphics/ColorSpace$Rgb"));
gColorSpaceRGB_constructorMethodID = GetMethodIDOrDie(env, gColorSpaceRGB_class,
"<init>", "(Ljava/lang/String;[FLandroid/graphics/ColorSpace$Rgb$TransferParameters;)V");
- gColorSpaceRGB_getTransferParametersMethodID = GetMethodIDOrDie(env, gColorSpaceRGB_class,
- "getTransferParameters", "()Landroid/graphics/ColorSpace$Rgb$TransferParameters;");
- gColorSpaceRGB_getTransformMethodID = GetMethodIDOrDie(env, gColorSpaceRGB_class,
- "getTransform", "()[F");
gColorSpace_Named_class = MakeGlobalRefOrDie(env,
FindClassOrDie(env, "android/graphics/ColorSpace$Named"));
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index 699d153..dc0d022 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -100,12 +100,27 @@
int srcStride, int x, int y, int width, int height,
SkBitmap* dstBitmap);
- static skcms_TransferFunction getNativeTransferParameters(JNIEnv* env, jobject transferParams);
- static skcms_Matrix3x3 getNativeXYZMatrix(JNIEnv* env, jfloatArray xyzD50);
- static sk_sp<SkColorSpace> getNativeColorSpace(JNIEnv* env, jobject colorSpace);
+ /**
+ * Convert the native SkColorSpace retrieved from ColorSpace.Rgb.getNativeInstance().
+ *
+ * This will never throw an Exception. If the ColorSpace is one that Skia cannot
+ * use, ColorSpace.Rgb.getNativeInstance() would have thrown an Exception. It may,
+ * however, be nullptr, which may be acceptable.
+ */
+ static sk_sp<SkColorSpace> getNativeColorSpace(jlong colorSpaceHandle);
static jobject getColorSpace(JNIEnv* env, sk_sp<SkColorSpace>& decodeColorSpace,
SkColorType decodeColorType);
+
+ /**
+ * Convert from a Java @ColorLong to an SkColor4f that Skia can use directly.
+ *
+ * This ignores the encoded ColorSpace, besides checking to see if it is sRGB,
+ * which is encoded differently. The color space should be passed down separately
+ * via ColorSpace#getNativeInstance(), and converted with getNativeColorSpace(),
+ * above.
+ */
+ static SkColor4f convertColorLong(jlong color);
};
class HeapAllocator : public SkBRDAllocator {
diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp
index df735ae..5f12665 100644
--- a/core/jni/android/graphics/ImageDecoder.cpp
+++ b/core/jni/android/graphics/ImageDecoder.cpp
@@ -202,7 +202,7 @@
jint desiredWidth, jint desiredHeight, jobject jsubset,
jboolean requireMutable, jint allocator,
jboolean requireUnpremul, jboolean preferRamOverQuality,
- jboolean asAlphaMask, jobject jcolorSpace) {
+ jboolean asAlphaMask, jlong colorSpaceHandle) {
auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
SkAndroidCodec* codec = decoder->mCodec.get();
const SkISize desiredSize = SkISize::Make(desiredWidth, desiredHeight);
@@ -256,7 +256,7 @@
// This is currently the only way to know that we should decode to F16.
colorType = codec->computeOutputColorType(colorType);
}
- sk_sp<SkColorSpace> colorSpace = GraphicsJNI::getNativeColorSpace(env, jcolorSpace);
+ sk_sp<SkColorSpace> colorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle);
colorSpace = codec->computeOutputColorSpace(colorType, colorSpace);
decodeInfo = decodeInfo.makeColorType(colorType).makeColorSpace(colorSpace);
@@ -508,7 +508,7 @@
{ "nCreate", "([BIILandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteArray },
{ "nCreate", "(Ljava/io/InputStream;[BLandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateInputStream },
{ "nCreate", "(Ljava/io/FileDescriptor;Landroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateFd },
- { "nDecodeBitmap", "(JLandroid/graphics/ImageDecoder;ZIILandroid/graphics/Rect;ZIZZZLandroid/graphics/ColorSpace;)Landroid/graphics/Bitmap;",
+ { "nDecodeBitmap", "(JLandroid/graphics/ImageDecoder;ZIILandroid/graphics/Rect;ZIZZZJ)Landroid/graphics/Bitmap;",
(void*) ImageDecoder_nDecodeBitmap },
{ "nGetSampledSize","(JI)Landroid/util/Size;", (void*) ImageDecoder_nGetSampledSize },
{ "nGetPadding", "(JLandroid/graphics/Rect;)V", (void*) ImageDecoder_nGetPadding },
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index e2e3042..7679c5b 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -557,41 +557,6 @@
return result;
}
- // FIXME: Make this CriticalNative when we no longer need to use JNIEnv. b/122514935 will allow
- // passing the SkColorSpace directly from JNI.
- static void setColor(JNIEnv* env, jobject clazz, jlong paintHandle, jobject jColorSpace,
- jfloat r, jfloat g, jfloat b, jfloat a) {
- sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(env, jColorSpace);
- if (GraphicsJNI::hasException(env)) {
- return;
- }
-
- SkColor4f color = SkColor4f{r, g, b, a};
- reinterpret_cast<Paint*>(paintHandle)->setColor4f(color, cs.get());
- }
-
- // FIXME: Make this CriticalNative when we no longer need to use JNIEnv. b/122514935 will allow
- // passing the SkColorSpace directly from JNI.
- static void setShadowLayer(JNIEnv* env, jobject clazz, jlong paintHandle, jfloat radius,
- jfloat dx, jfloat dy, jobject jColorSpace,
- jfloat r, jfloat g, jfloat b, jfloat a) {
- sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(env, jColorSpace);
- if (GraphicsJNI::hasException(env)) {
- return;
- }
-
- SkColor4f color = SkColor4f{r, g, b, a};
-
- Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- if (radius <= 0) {
- paint->setLooper(nullptr);
- }
- else {
- SkScalar sigma = android::uirenderer::Blur::convertRadiusToSigma(radius);
- paint->setLooper(SkBlurDrawLooper::Make(color, cs.get(), sigma, dx, dy));
- }
- }
-
// ------------------ @FastNative ---------------------------
static jint setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) {
@@ -787,6 +752,13 @@
obj->setStyle(style);
}
+ static void setColor(jlong paintHandle, jlong colorSpaceHandle,
+ jfloat r, jfloat g, jfloat b, jfloat a) {
+ sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(colorSpaceHandle);
+ SkColor4f color = SkColor4f{r, g, b, a};
+ reinterpret_cast<Paint*>(paintHandle)->setColor4f(color, cs.get());
+ }
+
static void setAlpha(jlong paintHandle, jint a) {
reinterpret_cast<Paint*>(paintHandle)->setAlpha(a);
}
@@ -1034,6 +1006,22 @@
return SkScalarToFloat(Paint::kStdStrikeThru_Thickness * textSize);
}
+ static void setShadowLayer(jlong paintHandle, jfloat radius,
+ jfloat dx, jfloat dy, jlong colorSpaceHandle,
+ jfloat r, jfloat g, jfloat b, jfloat a) {
+ sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(colorSpaceHandle);
+ SkColor4f color = SkColor4f{r, g, b, a};
+
+ Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ if (radius <= 0) {
+ paint->setLooper(nullptr);
+ }
+ else {
+ SkScalar sigma = android::uirenderer::Blur::convertRadiusToSigma(radius);
+ paint->setLooper(SkBlurDrawLooper::Make(color, cs.get(), sigma, dx, dy));
+ }
+ }
+
static jboolean hasShadowLayer(jlong paintHandle) {
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
return paint->getLooper() && paint->getLooper()->asABlurShadow(nullptr);
@@ -1082,9 +1070,6 @@
{"nGetRunAdvance", "(J[CIIIIZI)F", (void*) PaintGlue::getRunAdvance___CIIIIZI_F},
{"nGetOffsetForAdvance", "(J[CIIIIZF)I",
(void*) PaintGlue::getOffsetForAdvance___CIIIIZF_I},
- {"nSetColor","(JLandroid/graphics/ColorSpace;FFFF)V", (void*) PaintGlue::setColor},
- {"nSetShadowLayer", "(JFFFLandroid/graphics/ColorSpace;FFFF)V",
- (void*)PaintGlue::setShadowLayer},
// --------------- @FastNative ----------------------
@@ -1114,6 +1099,7 @@
{"nSetDither","(JZ)V", (void*) PaintGlue::setDither},
{"nGetStyle","(J)I", (void*) PaintGlue::getStyle},
{"nSetStyle","(JI)V", (void*) PaintGlue::setStyle},
+ {"nSetColor","(JJFFFF)V", (void*) PaintGlue::setColor},
{"nSetAlpha","(JI)V", (void*) PaintGlue::setAlpha},
{"nGetStrokeWidth","(J)F", (void*) PaintGlue::getStrokeWidth},
{"nSetStrokeWidth","(JF)V", (void*) PaintGlue::setStrokeWidth},
@@ -1154,6 +1140,7 @@
{"nGetUnderlineThickness","(J)F", (void*) PaintGlue::getUnderlineThickness},
{"nGetStrikeThruPosition","(J)F", (void*) PaintGlue::getStrikeThruPosition},
{"nGetStrikeThruThickness","(J)F", (void*) PaintGlue::getStrikeThruThickness},
+ {"nSetShadowLayer", "(JFFFJFFFF)V", (void*)PaintGlue::setShadowLayer},
{"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer},
{"nEqualsForTextMeasurement", "(JJ)Z", (void*)PaintGlue::equalsForTextMeasurement},
};
diff --git a/core/jni/android/graphics/Picture.cpp b/core/jni/android/graphics/Picture.cpp
index fd1d87f..d29857d 100644
--- a/core/jni/android/graphics/Picture.cpp
+++ b/core/jni/android/graphics/Picture.cpp
@@ -37,6 +37,12 @@
}
}
+Picture::Picture(sk_sp<SkPicture>&& src) {
+ mPicture = std::move(src);
+ mWidth = 0;
+ mHeight = 0;
+}
+
Canvas* Picture::beginRecording(int width, int height) {
mPicture.reset(NULL);
mRecorder.reset(new SkPictureRecorder);
diff --git a/core/jni/android/graphics/Picture.h b/core/jni/android/graphics/Picture.h
index 3068631..536f651 100644
--- a/core/jni/android/graphics/Picture.h
+++ b/core/jni/android/graphics/Picture.h
@@ -37,6 +37,7 @@
class Picture {
public:
explicit Picture(const Picture* src = NULL);
+ explicit Picture(sk_sp<SkPicture>&& src);
Canvas* beginRecording(int width, int height);
diff --git a/core/jni/android_app_ActivityThread.cpp b/core/jni/android_app_ActivityThread.cpp
index d56e4c5..93f2525 100644
--- a/core/jni/android_app_ActivityThread.cpp
+++ b/core/jni/android_app_ActivityThread.cpp
@@ -24,6 +24,8 @@
#include "core_jni_helpers.h"
#include <unistd.h>
+#include <bionic_malloc.h>
+
namespace android {
static void android_app_ActivityThread_purgePendingResources(JNIEnv* env, jobject clazz) {
@@ -38,13 +40,18 @@
minikin::Layout::dumpMinikinStats(fd);
}
+static void android_app_ActivityThread_initZygoteChildHeapProfiling(JNIEnv* env, jobject clazz) {
+ android_mallopt(M_INIT_ZYGOTE_CHILD_PROFILING, nullptr, 0);
+}
static JNINativeMethod gActivityThreadMethods[] = {
// ------------ Regular JNI ------------------
{ "nPurgePendingResources", "()V",
(void*) android_app_ActivityThread_purgePendingResources },
{ "nDumpGraphicsInfo", "(Ljava/io/FileDescriptor;)V",
- (void*) android_app_ActivityThread_dumpGraphics }
+ (void*) android_app_ActivityThread_dumpGraphics },
+ { "nInitZygoteChildHeapProfiling", "()V",
+ (void*) android_app_ActivityThread_initZygoteChildHeapProfiling }
};
int register_android_app_ActivityThread(JNIEnv* env) {
diff --git a/core/jni/android_graphics_ColorSpace.cpp b/core/jni/android_graphics_ColorSpace.cpp
new file mode 100644
index 0000000..7648fd0
--- /dev/null
+++ b/core/jni/android_graphics_ColorSpace.cpp
@@ -0,0 +1,110 @@
+/*
+ * 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.
+ */
+
+#include "jni.h"
+#include "GraphicsJNI.h"
+#include "core_jni_helpers.h"
+
+#include "SkColor.h"
+#include "SkColorSpace.h"
+
+using namespace android;
+
+static skcms_Matrix3x3 getNativeXYZMatrix(JNIEnv* env, jfloatArray xyzD50) {
+ skcms_Matrix3x3 xyzMatrix;
+ jfloat* array = env->GetFloatArrayElements(xyzD50, NULL);
+ xyzMatrix.vals[0][0] = array[0];
+ xyzMatrix.vals[1][0] = array[1];
+ xyzMatrix.vals[2][0] = array[2];
+ xyzMatrix.vals[0][1] = array[3];
+ xyzMatrix.vals[1][1] = array[4];
+ xyzMatrix.vals[2][1] = array[5];
+ xyzMatrix.vals[0][2] = array[6];
+ xyzMatrix.vals[1][2] = array[7];
+ xyzMatrix.vals[2][2] = array[8];
+ env->ReleaseFloatArrayElements(xyzD50, array, 0);
+ return xyzMatrix;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static float halfToFloat(uint16_t bits) {
+ __fp16 h;
+ memcpy(&h, &bits, 2);
+ return (float)h;
+}
+
+SkColor4f GraphicsJNI::convertColorLong(jlong color) {
+ if ((color & 0x3f) == 0) {
+ // This corresponds to sRGB, which is treated differently than the rest.
+ uint8_t a = color >> 56 & 0xff;
+ uint8_t r = color >> 48 & 0xff;
+ uint8_t g = color >> 40 & 0xff;
+ uint8_t b = color >> 32 & 0xff;
+ SkColor c = SkColorSetARGB(a, r, g, b);
+ return SkColor4f::FromColor(c);
+ }
+
+ // These match the implementation of android.graphics.Color#red(long) etc.
+ float r = halfToFloat((uint16_t)(color >> 48 & 0xffff));
+ float g = halfToFloat((uint16_t)(color >> 32 & 0xffff));
+ float b = halfToFloat((uint16_t)(color >> 16 & 0xffff));
+ float a = (color >> 6 & 0x3ff) / 1023.0f;
+
+ return SkColor4f{r, g, b, a};
+}
+
+sk_sp<SkColorSpace> GraphicsJNI::getNativeColorSpace(jlong colorSpaceHandle) {
+ if (colorSpaceHandle == 0) return nullptr;
+ return sk_ref_sp(reinterpret_cast<SkColorSpace*>(colorSpaceHandle));
+}
+
+static void unref_colorSpace(SkColorSpace* cs) {
+ SkSafeUnref(cs);
+}
+
+static jlong ColorSpace_getNativeFinalizer(JNIEnv*, jobject) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&unref_colorSpace));
+}
+
+static jlong ColorSpace_creator(JNIEnv* env, jobject, jfloat a, jfloat b, jfloat c,
+ jfloat d, jfloat e, jfloat f, jfloat g, jfloatArray xyzD50) {
+ skcms_TransferFunction p;
+ p.a = a;
+ p.b = b;
+ p.c = c;
+ p.d = d;
+ p.e = e;
+ p.f = f;
+ p.g = g;
+ skcms_Matrix3x3 xyzMatrix = getNativeXYZMatrix(env, xyzD50);
+
+ return reinterpret_cast<jlong>(SkColorSpace::MakeRGB(p, xyzMatrix).release());
+}
+
+static const JNINativeMethod gColorSpaceRgbMethods[] = {
+ { "nativeGetNativeFinalizer", "()J", (void*)ColorSpace_getNativeFinalizer },
+ { "nativeCreate", "(FFFFFFF[F)J", (void*)ColorSpace_creator }
+};
+
+namespace android {
+
+int register_android_graphics_ColorSpace(JNIEnv* env) {
+ return android::RegisterMethodsOrDie(env, "android/graphics/ColorSpace$Rgb",
+ gColorSpaceRgbMethods, NELEM(gColorSpaceRgbMethods));
+}
+
+}; // namespace android
diff --git a/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
new file mode 100644
index 0000000..1c9ab94
--- /dev/null
+++ b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2014, 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 LOG_TAG "ActivityRecognitionHardware"
+
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+
+// #include <hardware/activity_recognition.h>
+// The activity recognition HAL is being deprecated. This means -
+// i) Android framework code shall not depend on activity recognition
+// being provided through the activity_recognition.h interface.
+// ii) activity recognition HAL will not be binderized as the other HALs.
+//
+
+/**
+ * Initializes the ActivityRecognitionHardware class from the native side.
+ */
+static void class_init(JNIEnv* /*env*/, jclass /*clazz*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+}
+
+/**
+ * Initializes and connect the callbacks handlers in the HAL.
+ */
+static void initialize(JNIEnv* /*env*/, jobject /*obj*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+}
+
+/**
+ * De-initializes the ActivityRecognitionHardware from the native side.
+ */
+static void release(JNIEnv* /*env*/, jobject /*obj*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+}
+
+/**
+ * Returns true if ActivityRecognition HAL is supported, false otherwise.
+ */
+static jboolean is_supported(JNIEnv* /*env*/, jclass /*clazz*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+ return JNI_FALSE;
+}
+
+/**
+ * Gets an array representing the supported activities.
+ */
+static jobjectArray get_supported_activities(JNIEnv* /*env*/, jobject /*obj*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+ return NULL;
+}
+
+/**
+ * Enables a given activity event to be actively monitored.
+ */
+static int enable_activity_event(
+ JNIEnv* /*env*/,
+ jobject /*obj*/,
+ jint /*activity_handle*/,
+ jint /*event_type*/,
+ jlong /*report_latency_ns*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+ return android::NO_INIT;
+}
+
+/**
+ * Disables a given activity event from being actively monitored.
+ */
+static int disable_activity_event(
+ JNIEnv* /*env*/,
+ jobject /*obj*/,
+ jint /*activity_handle*/,
+ jint /*event_type*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+ return android::NO_INIT;
+}
+
+/**
+ * Request flush for al batch buffers.
+ */
+static int flush(JNIEnv* /*env*/, jobject /*obj*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+ return android::NO_INIT;
+}
+
+
+static const JNINativeMethod sMethods[] = {
+ // {"name", "signature", (void*) functionPointer },
+ { "nativeClassInit", "()V", (void*) class_init },
+ { "nativeInitialize", "()V", (void*) initialize },
+ { "nativeRelease", "()V", (void*) release },
+ { "nativeIsSupported", "()Z", (void*) is_supported },
+ { "nativeGetSupportedActivities", "()[Ljava/lang/String;", (void*) get_supported_activities },
+ { "nativeEnableActivityEvent", "(IIJ)I", (void*) enable_activity_event },
+ { "nativeDisableActivityEvent", "(II)I", (void*) disable_activity_event },
+ { "nativeFlush", "()I", (void*) flush },
+};
+
+/**
+ * Registration method invoked in JNI load.
+ */
+int register_android_hardware_location_ActivityRecognitionHardware(JNIEnv* env) {
+ return jniRegisterNativeMethods(
+ env,
+ "android/hardware/location/ActivityRecognitionHardware",
+ sMethods,
+ NELEM(sMethods));
+}
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 9e32206..0d8ede7 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -488,13 +488,14 @@
static jint
android_media_AudioSystem_setDeviceConnectionState(JNIEnv *env, jobject thiz, jint device, jint state, jstring device_address, jstring device_name,
- jint codec __unused)
+ jint codec)
{
const char *c_address = env->GetStringUTFChars(device_address, NULL);
const char *c_name = env->GetStringUTFChars(device_name, NULL);
int status = check_AudioSystem_Command(AudioSystem::setDeviceConnectionState(static_cast <audio_devices_t>(device),
static_cast <audio_policy_dev_state_t>(state),
- c_address, c_name));
+ c_address, c_name,
+ static_cast <audio_format_t>(codec)));
env->ReleaseStringUTFChars(device_address, c_address);
env->ReleaseStringUTFChars(device_name, c_name);
return (jint) status;
@@ -512,12 +513,12 @@
static jint
android_media_AudioSystem_handleDeviceConfigChange(JNIEnv *env, jobject thiz, jint device, jstring device_address, jstring device_name,
- jint codec __unused)
+ jint codec)
{
const char *c_address = env->GetStringUTFChars(device_address, NULL);
const char *c_name = env->GetStringUTFChars(device_name, NULL);
int status = check_AudioSystem_Command(AudioSystem::handleDeviceConfigChange(static_cast <audio_devices_t>(device),
- c_address, c_name));
+ c_address, c_name, static_cast <audio_format_t>(codec)));
env->ReleaseStringUTFChars(device_address, c_address);
env->ReleaseStringUTFChars(device_name, c_name);
return (jint) status;
@@ -2038,10 +2039,8 @@
return (jint)AUDIO_JAVA_BAD_VALUE;
}
std::vector<audio_format_t> encodingFormats;
- //FIXME: enable when native implementaiton is merged
- //status_t status = AudioSystem::getHwOffloadEncodingFormatsSupportedForA2DP(
- // &encodingFormats);
- status_t status = NO_ERROR;
+ status_t status = AudioSystem::getHwOffloadEncodingFormatsSupportedForA2DP(
+ &encodingFormats);
if (status != NO_ERROR) {
ALOGE("%s: error %d", __FUNCTION__, status);
jStatus = nativeToJavaStatus(status);
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 9b138eb..7eddcfe 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -16,8 +16,11 @@
#define LOG_TAG "NetUtils"
+#include <vector>
+
#include "jni.h"
#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
#include "NetdClient.h"
#include <utils/misc.h>
#include <android_runtime/AndroidRuntime.h>
@@ -55,6 +58,31 @@
static const uint32_t kUDPDstPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, dest);
static const uint16_t kDhcpClientPort = 68;
+constexpr int MAXPACKETSIZE = 8 * 1024;
+// FrameworkListener limits the size of commands to 1024 bytes. TODO: fix this.
+constexpr int MAXCMDSIZE = 1024;
+
+static void throwErrnoException(JNIEnv* env, const char* functionName, int error) {
+ ScopedLocalRef<jstring> detailMessage(env, env->NewStringUTF(functionName));
+ if (detailMessage.get() == NULL) {
+ // Not really much we can do here. We're probably dead in the water,
+ // but let's try to stumble on...
+ env->ExceptionClear();
+ }
+ static jclass errnoExceptionClass =
+ MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/system/ErrnoException"));
+
+ static jmethodID errnoExceptionCtor =
+ GetMethodIDOrDie(env, errnoExceptionClass,
+ "<init>", "(Ljava/lang/String;I)V");
+
+ jobject exception = env->NewObject(errnoExceptionClass,
+ errnoExceptionCtor,
+ detailMessage.get(),
+ error);
+ env->Throw(reinterpret_cast<jthrowable>(exception));
+}
+
static void android_net_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd)
{
struct sock_filter filter_code[] = {
@@ -372,6 +400,63 @@
}
}
+static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jint netId,
+ jstring dname, jint ns_class, jint ns_type, jint flags) {
+ const jsize javaCharsCount = env->GetStringLength(dname);
+ const jsize byteCountUTF8 = env->GetStringUTFLength(dname);
+
+ // Only allow dname which could be simply formatted to UTF8.
+ // In native layer, res_mkquery would re-format the input char array to packet.
+ std::vector<char> queryname(byteCountUTF8 + 1, 0);
+
+ env->GetStringUTFRegion(dname, 0, javaCharsCount, queryname.data());
+ int fd = resNetworkQuery(netId, queryname.data(), ns_class, ns_type, flags);
+
+ if (fd < 0) {
+ throwErrnoException(env, "resNetworkQuery", -fd);
+ return nullptr;
+ }
+
+ return jniCreateFileDescriptor(env, fd);
+}
+
+static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jint netId,
+ jbyteArray msg, jint msgLen, jint flags) {
+ uint8_t data[MAXCMDSIZE];
+
+ checkLenAndCopy(env, msg, msgLen, data);
+ int fd = resNetworkSend(netId, data, msgLen, flags);
+
+ if (fd < 0) {
+ throwErrnoException(env, "resNetworkSend", -fd);
+ return nullptr;
+ }
+
+ return jniCreateFileDescriptor(env, fd);
+}
+
+static jbyteArray android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, jobject javaFd) {
+ int fd = jniGetFDFromFileDescriptor(env, javaFd);
+ int rcode;
+ std::vector<uint8_t> buf(MAXPACKETSIZE, 0);
+
+ int res = resNetworkResult(fd, &rcode, buf.data(), MAXPACKETSIZE);
+ if (res < 0) {
+ throwErrnoException(env, "resNetworkResult", -res);
+ return nullptr;
+ }
+
+ jbyteArray answer = env->NewByteArray(res);
+ if (answer == nullptr) {
+ throwErrnoException(env, "resNetworkResult", ENOMEM);
+ return nullptr;
+ } else {
+ env->SetByteArrayRegion(answer, 0, res,
+ reinterpret_cast<jbyte*>(buf.data()));
+ }
+
+ return answer;
+}
// ----------------------------------------------------------------------------
@@ -391,6 +476,9 @@
{ "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter },
{ "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachControlPacketFilter },
{ "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_setupRaSocket },
+ { "resNetworkSend", "(I[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend },
+ { "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
+ { "resNetworkResult", "(Ljava/io/FileDescriptor;)[B", (void*) android_net_utils_resNetworkResult },
};
int register_android_net_NetworkUtils(JNIEnv* env)
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 7d63ec9..7837248 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -45,6 +45,7 @@
#include <meminfo/sysmeminfo.h>
#include <memtrack/memtrack.h>
#include <memunreachable/memunreachable.h>
+#include <android-base/strings.h>
#include "android_os_Debug.h"
namespace android
@@ -231,244 +232,162 @@
return err;
}
-static void read_mapinfo(FILE *fp, stats_t* stats, bool* foundSwapPss)
-{
- char line[1024];
- int len, nameLen;
- bool skip, done = false;
-
- unsigned pss = 0, swappable_pss = 0, rss = 0;
- float sharing_proportion = 0.0;
- unsigned shared_clean = 0, shared_dirty = 0;
- unsigned private_clean = 0, private_dirty = 0;
- unsigned swapped_out = 0, swapped_out_pss = 0;
- bool is_swappable = false;
- unsigned temp;
-
- uint64_t start;
- uint64_t end = 0;
- uint64_t prevEnd = 0;
- char* name;
- int name_pos;
-
- int whichHeap = HEAP_UNKNOWN;
- int subHeap = HEAP_UNKNOWN;
- int prevHeap = HEAP_UNKNOWN;
-
- *foundSwapPss = false;
-
- if(fgets(line, sizeof(line), fp) == 0) return;
-
- while (!done) {
- prevHeap = whichHeap;
- prevEnd = end;
- whichHeap = HEAP_UNKNOWN;
- subHeap = HEAP_UNKNOWN;
- skip = false;
- is_swappable = false;
-
- len = strlen(line);
- if (len < 1) return;
- line[--len] = 0;
-
- if (sscanf(line, "%" SCNx64 "-%" SCNx64 " %*s %*x %*x:%*x %*d%n", &start, &end, &name_pos) != 2) {
- skip = true;
- } else {
- while (isspace(line[name_pos])) {
- name_pos += 1;
- }
- name = line + name_pos;
- nameLen = strlen(name);
- // Trim the end of the line if it is " (deleted)".
- const char* deleted_str = " (deleted)";
- if (nameLen > (int)strlen(deleted_str) &&
- strcmp(name+nameLen-strlen(deleted_str), deleted_str) == 0) {
- nameLen -= strlen(deleted_str);
- name[nameLen] = '\0';
- }
- if ((strstr(name, "[heap]") == name)) {
- whichHeap = HEAP_NATIVE;
- } else if (strncmp(name, "[anon:libc_malloc]", 18) == 0) {
- whichHeap = HEAP_NATIVE;
- } else if (strncmp(name, "[stack", 6) == 0) {
- whichHeap = HEAP_STACK;
- } else if (nameLen > 3 && strcmp(name+nameLen-3, ".so") == 0) {
- whichHeap = HEAP_SO;
- is_swappable = true;
- } else if (nameLen > 4 && strcmp(name+nameLen-4, ".jar") == 0) {
- whichHeap = HEAP_JAR;
- is_swappable = true;
- } else if (nameLen > 4 && strcmp(name+nameLen-4, ".apk") == 0) {
- whichHeap = HEAP_APK;
- is_swappable = true;
- } else if (nameLen > 4 && strcmp(name+nameLen-4, ".ttf") == 0) {
- whichHeap = HEAP_TTF;
- is_swappable = true;
- } else if ((nameLen > 4 && strstr(name, ".dex") != NULL) ||
- (nameLen > 5 && strcmp(name+nameLen-5, ".odex") == 0)) {
- whichHeap = HEAP_DEX;
- subHeap = HEAP_DEX_APP_DEX;
- is_swappable = true;
- } else if (nameLen > 5 && strcmp(name+nameLen-5, ".vdex") == 0) {
- whichHeap = HEAP_DEX;
- // Handle system@framework@boot* and system/framework/boot*
- if (strstr(name, "@boot") != NULL || strstr(name, "/boot") != NULL) {
- subHeap = HEAP_DEX_BOOT_VDEX;
- } else {
- subHeap = HEAP_DEX_APP_VDEX;
- }
- is_swappable = true;
- } else if (nameLen > 4 && strcmp(name+nameLen-4, ".oat") == 0) {
- whichHeap = HEAP_OAT;
- is_swappable = true;
- } else if (nameLen > 4 && strcmp(name+nameLen-4, ".art") == 0) {
- whichHeap = HEAP_ART;
- // Handle system@framework@boot* and system/framework/boot*
- if (strstr(name, "@boot") != NULL || strstr(name, "/boot") != NULL) {
- subHeap = HEAP_ART_BOOT;
- } else {
- subHeap = HEAP_ART_APP;
- }
- is_swappable = true;
- } else if (strncmp(name, "/dev/", 5) == 0) {
- whichHeap = HEAP_UNKNOWN_DEV;
- if (strncmp(name, "/dev/kgsl-3d0", 13) == 0) {
- whichHeap = HEAP_GL_DEV;
- } else if (strncmp(name, "/dev/ashmem/CursorWindow", 24) == 0) {
- whichHeap = HEAP_CURSOR;
- } else if (strncmp(name, "/dev/ashmem", 11)) {
- whichHeap = HEAP_ASHMEM;
- }
- } else if (strncmp(name, "[anon:", 6) == 0) {
- whichHeap = HEAP_UNKNOWN;
- if (strncmp(name, "[anon:dalvik-", 13) == 0) {
- whichHeap = HEAP_DALVIK_OTHER;
- if (strstr(name, "[anon:dalvik-LinearAlloc") == name) {
- subHeap = HEAP_DALVIK_OTHER_LINEARALLOC;
- } else if ((strstr(name, "[anon:dalvik-alloc space") == name) ||
- (strstr(name, "[anon:dalvik-main space") == name)) {
- // This is the regular Dalvik heap.
- whichHeap = HEAP_DALVIK;
- subHeap = HEAP_DALVIK_NORMAL;
- } else if (strstr(name, "[anon:dalvik-large object space") == name ||
- strstr(name, "[anon:dalvik-free list large object space")
- == name) {
- whichHeap = HEAP_DALVIK;
- subHeap = HEAP_DALVIK_LARGE;
- } else if (strstr(name, "[anon:dalvik-non moving space") == name) {
- whichHeap = HEAP_DALVIK;
- subHeap = HEAP_DALVIK_NON_MOVING;
- } else if (strstr(name, "[anon:dalvik-zygote space") == name) {
- whichHeap = HEAP_DALVIK;
- subHeap = HEAP_DALVIK_ZYGOTE;
- } else if (strstr(name, "[anon:dalvik-indirect ref") == name) {
- subHeap = HEAP_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE;
- } else if (strstr(name, "[anon:dalvik-jit-code-cache") == name ||
- strstr(name, "[anon:dalvik-data-code-cache") == name) {
- subHeap = HEAP_DALVIK_OTHER_CODE_CACHE;
- } else if (strstr(name, "[anon:dalvik-CompilerMetadata") == name) {
- subHeap = HEAP_DALVIK_OTHER_COMPILER_METADATA;
- } else {
- subHeap = HEAP_DALVIK_OTHER_ACCOUNTING; // Default to accounting.
- }
- }
- } else if (nameLen > 0) {
- whichHeap = HEAP_UNKNOWN_MAP;
- } else if (start == prevEnd && prevHeap == HEAP_SO) {
- // bss section of a shared library.
- whichHeap = HEAP_SO;
- }
- }
-
- //ALOGI("native=%d dalvik=%d sqlite=%d: %s\n", isNativeHeap, isDalvikHeap,
- // isSqliteHeap, line);
-
- shared_clean = 0;
- shared_dirty = 0;
- private_clean = 0;
- private_dirty = 0;
- swapped_out = 0;
- swapped_out_pss = 0;
-
- while (true) {
- if (fgets(line, 1024, fp) == 0) {
- done = true;
- break;
- }
-
- if (line[0] == 'S' && sscanf(line, "Size: %d kB", &temp) == 1) {
- /* size = temp; */
- } else if (line[0] == 'R' && sscanf(line, "Rss: %d kB", &temp) == 1) {
- rss = temp;
- } else if (line[0] == 'P' && sscanf(line, "Pss: %d kB", &temp) == 1) {
- pss = temp;
- } else if (line[0] == 'S' && sscanf(line, "Shared_Clean: %d kB", &temp) == 1) {
- shared_clean = temp;
- } else if (line[0] == 'S' && sscanf(line, "Shared_Dirty: %d kB", &temp) == 1) {
- shared_dirty = temp;
- } else if (line[0] == 'P' && sscanf(line, "Private_Clean: %d kB", &temp) == 1) {
- private_clean = temp;
- } else if (line[0] == 'P' && sscanf(line, "Private_Dirty: %d kB", &temp) == 1) {
- private_dirty = temp;
- } else if (line[0] == 'R' && sscanf(line, "Referenced: %d kB", &temp) == 1) {
- /* referenced = temp; */
- } else if (line[0] == 'S' && sscanf(line, "Swap: %d kB", &temp) == 1) {
- swapped_out = temp;
- } else if (line[0] == 'S' && sscanf(line, "SwapPss: %d kB", &temp) == 1) {
- *foundSwapPss = true;
- swapped_out_pss = temp;
- } else if (sscanf(line, "%" SCNx64 "-%" SCNx64 " %*s %*x %*x:%*x %*d", &start, &end) == 2) {
- // looks like a new mapping
- // example: "10000000-10001000 ---p 10000000 00:00 0"
- break;
- }
- }
-
- if (!skip) {
- if (is_swappable && (pss > 0)) {
- sharing_proportion = 0.0;
- if ((shared_clean > 0) || (shared_dirty > 0)) {
- sharing_proportion = (pss - private_clean
- - private_dirty)/(shared_clean+shared_dirty);
- }
- swappable_pss = (sharing_proportion*shared_clean) + private_clean;
- } else
- swappable_pss = 0;
-
- stats[whichHeap].pss += pss;
- stats[whichHeap].swappablePss += swappable_pss;
- stats[whichHeap].rss += rss;
- stats[whichHeap].privateDirty += private_dirty;
- stats[whichHeap].sharedDirty += shared_dirty;
- stats[whichHeap].privateClean += private_clean;
- stats[whichHeap].sharedClean += shared_clean;
- stats[whichHeap].swappedOut += swapped_out;
- stats[whichHeap].swappedOutPss += swapped_out_pss;
- if (whichHeap == HEAP_DALVIK || whichHeap == HEAP_DALVIK_OTHER ||
- whichHeap == HEAP_DEX || whichHeap == HEAP_ART) {
- stats[subHeap].pss += pss;
- stats[subHeap].swappablePss += swappable_pss;
- stats[subHeap].rss += rss;
- stats[subHeap].privateDirty += private_dirty;
- stats[subHeap].sharedDirty += shared_dirty;
- stats[subHeap].privateClean += private_clean;
- stats[subHeap].sharedClean += shared_clean;
- stats[subHeap].swappedOut += swapped_out;
- stats[subHeap].swappedOutPss += swapped_out_pss;
- }
- }
- }
-}
-
static void load_maps(int pid, stats_t* stats, bool* foundSwapPss)
{
*foundSwapPss = false;
+ uint64_t prev_end = 0;
+ int prev_heap = HEAP_UNKNOWN;
std::string smaps_path = base::StringPrintf("/proc/%d/smaps", pid);
- UniqueFile fp = MakeUniqueFile(smaps_path.c_str(), "re");
- if (fp == nullptr) return;
+ auto vma_scan = [&](const meminfo::Vma& vma) {
+ int which_heap = HEAP_UNKNOWN;
+ int sub_heap = HEAP_UNKNOWN;
+ bool is_swappable = false;
+ std::string name;
+ if (base::EndsWith(vma.name, " (deleted)")) {
+ name = vma.name.substr(0, vma.name.size() - strlen(" (deleted)"));
+ } else {
+ name = vma.name;
+ }
- read_mapinfo(fp.get(), stats, foundSwapPss);
+ uint32_t namesz = name.size();
+ if (base::StartsWith(name, "[heap]")) {
+ which_heap = HEAP_NATIVE;
+ } else if (base::StartsWith(name, "[anon:libc_malloc]")) {
+ which_heap = HEAP_NATIVE;
+ } else if (base::StartsWith(name, "[stack")) {
+ which_heap = HEAP_NATIVE;
+ } else if (base::EndsWith(name, ".so")) {
+ which_heap = HEAP_SO;
+ is_swappable = true;
+ } else if (base::EndsWith(name, ".jar")) {
+ which_heap = HEAP_JAR;
+ is_swappable = true;
+ } else if (base::EndsWith(name, ".apk")) {
+ which_heap = HEAP_APK;
+ is_swappable = true;
+ } else if (base::EndsWith(name, ".ttf")) {
+ which_heap = HEAP_TTF;
+ is_swappable = true;
+ } else if ((base::EndsWith(name, ".odex")) ||
+ (namesz > 4 && strstr(name.c_str(), ".dex") != nullptr)) {
+ which_heap = HEAP_DEX;
+ sub_heap = HEAP_DEX_APP_DEX;
+ is_swappable = true;
+ } else if (base::EndsWith(name, ".vdex")) {
+ which_heap = HEAP_DEX;
+ // Handle system@framework@boot and system/framework/boot
+ if ((strstr(name.c_str(), "@boot") != nullptr) ||
+ (strstr(name.c_str(), "/boot"))) {
+ sub_heap = HEAP_DEX_BOOT_VDEX;
+ } else {
+ sub_heap = HEAP_DEX_APP_VDEX;
+ }
+ is_swappable = true;
+ } else if (base::EndsWith(name, ".oat")) {
+ which_heap = HEAP_OAT;
+ is_swappable = true;
+ } else if (base::EndsWith(name, ".art")) {
+ which_heap = HEAP_ART;
+ // Handle system@framework@boot* and system/framework/boot*
+ if ((strstr(name.c_str(), "@boot") != nullptr) ||
+ (strstr(name.c_str(), "/boot"))) {
+ sub_heap = HEAP_DEX_BOOT_VDEX;
+ } else {
+ sub_heap = HEAP_DEX_APP_VDEX;
+ }
+ is_swappable = true;
+ } else if (base::StartsWith(name, "/dev/")) {
+ which_heap = HEAP_UNKNOWN_DEV;
+ if (base::StartsWith(name, "/dev/kgsl-3d0")) {
+ which_heap = HEAP_GL_DEV;
+ } else if (base::StartsWith(name, "/dev/ashmem/CursorWindow")) {
+ which_heap = HEAP_CURSOR;
+ } else if (base::StartsWith(name, "/dev/ashmem")) {
+ which_heap = HEAP_ASHMEM;
+ }
+ } else if (base::StartsWith(name, "[anon:")) {
+ which_heap = HEAP_UNKNOWN;
+ if (base::StartsWith(name, "[anon:dalvik-")) {
+ which_heap = HEAP_DALVIK_OTHER;
+ if (base::StartsWith(name, "[anon:dalvik-LinearAlloc")) {
+ sub_heap = HEAP_DALVIK_OTHER_LINEARALLOC;
+ } else if (base::StartsWith(name, "[anon:dalvik-alloc space") ||
+ base::StartsWith(name, "[anon:dalvik-main space")) {
+ // This is the regular Dalvik heap.
+ which_heap = HEAP_DALVIK;
+ sub_heap = HEAP_DALVIK_NORMAL;
+ } else if (base::StartsWith(name,
+ "[anon:dalvik-large object space") ||
+ base::StartsWith(
+ name, "[anon:dalvik-free list large object space")) {
+ which_heap = HEAP_DALVIK;
+ sub_heap = HEAP_DALVIK_LARGE;
+ } else if (base::StartsWith(name, "[anon:dalvik-non moving space")) {
+ which_heap = HEAP_DALVIK;
+ sub_heap = HEAP_DALVIK_NON_MOVING;
+ } else if (base::StartsWith(name, "[anon:dalvik-zygote space")) {
+ which_heap = HEAP_DALVIK;
+ sub_heap = HEAP_DALVIK_ZYGOTE;
+ } else if (base::StartsWith(name, "[anon:dalvik-indirect ref")) {
+ sub_heap = HEAP_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE;
+ } else if (base::StartsWith(name, "[anon:dalvik-jit-code-cache") ||
+ base::StartsWith(name, "[anon:dalvik-data-code-cache")) {
+ sub_heap = HEAP_DALVIK_OTHER_CODE_CACHE;
+ } else if (base::StartsWith(name, "[anon:dalvik-CompilerMetadata")) {
+ sub_heap = HEAP_DALVIK_OTHER_COMPILER_METADATA;
+ } else {
+ sub_heap = HEAP_DALVIK_OTHER_ACCOUNTING; // Default to accounting.
+ }
+ }
+ } else if (namesz > 0) {
+ which_heap = HEAP_UNKNOWN_MAP;
+ } else if (vma.start == prev_end && prev_heap == HEAP_SO) {
+ // bss section of a shared library
+ which_heap = HEAP_SO;
+ }
+
+ prev_end = vma.end;
+ prev_heap = which_heap;
+
+ const meminfo::MemUsage& usage = vma.usage;
+ if (usage.swap_pss > 0 && *foundSwapPss != true) {
+ *foundSwapPss = true;
+ }
+
+ uint64_t swapable_pss = 0;
+ if (is_swappable && (usage.pss > 0)) {
+ float sharing_proportion = 0.0;
+ if ((usage.shared_clean > 0) || (usage.shared_dirty > 0)) {
+ sharing_proportion = (usage.pss - usage.uss) / (usage.shared_clean + usage.shared_dirty);
+ }
+ swapable_pss = (sharing_proportion * usage.shared_clean) + usage.private_clean;
+ }
+
+ stats[which_heap].pss += usage.pss;
+ stats[which_heap].swappablePss += swapable_pss;
+ stats[which_heap].rss += usage.rss;
+ stats[which_heap].privateDirty += usage.private_dirty;
+ stats[which_heap].sharedDirty += usage.shared_dirty;
+ stats[which_heap].privateClean += usage.private_clean;
+ stats[which_heap].sharedClean += usage.shared_clean;
+ stats[which_heap].swappedOut += usage.swap;
+ stats[which_heap].swappedOutPss += usage.swap_pss;
+ if (which_heap == HEAP_DALVIK || which_heap == HEAP_DALVIK_OTHER ||
+ which_heap == HEAP_DEX || which_heap == HEAP_ART) {
+ stats[sub_heap].pss += usage.pss;
+ stats[sub_heap].swappablePss += swapable_pss;
+ stats[sub_heap].rss += usage.rss;
+ stats[sub_heap].privateDirty += usage.private_dirty;
+ stats[sub_heap].sharedDirty += usage.shared_dirty;
+ stats[sub_heap].privateClean += usage.private_clean;
+ stats[sub_heap].sharedClean += usage.shared_clean;
+ stats[sub_heap].swappedOut += usage.swap;
+ stats[sub_heap].swappedOutPss += usage.swap_pss;
+ }
+ };
+
+ meminfo::ForEachVmaFromFile(smaps_path, vma_scan);
}
static void android_os_Debug_getDirtyPagesPid(JNIEnv *env, jobject clazz,
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 897427f..0453195 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -126,7 +126,12 @@
jstring nameStr, jint w, jint h, jint format, jint flags, jlong parentObject,
jint windowType, jint ownerUid) {
ScopedUtfChars name(env, nameStr);
- sp<SurfaceComposerClient> client(android_view_SurfaceSession_getClient(env, sessionObj));
+ sp<SurfaceComposerClient> client;
+ if (sessionObj != NULL) {
+ client = android_view_SurfaceSession_getClient(env, sessionObj);
+ } else {
+ client = SurfaceComposerClient::getDefault();
+ }
SurfaceControl *parent = reinterpret_cast<SurfaceControl*>(parentObject);
sp<SurfaceControl> surface;
status_t err = client->createSurfaceChecked(
@@ -277,6 +282,21 @@
transaction->setPosition(ctrl, x, y);
}
+static void nativeSetGeometry(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
+ jobject sourceObj, jobject dstObj, jlong orientation) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+ SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+
+ Rect source, dst;
+ if (sourceObj != NULL) {
+ source = rectFromObj(env, sourceObj);
+ }
+ if (dstObj != NULL) {
+ dst = rectFromObj(env, dstObj);
+ }
+ transaction->setGeometry(ctrl, source, dst, orientation);
+}
+
static void nativeSetGeometryAppliesWithResize(JNIEnv* env, jclass clazz,
jlong transactionObj,
jlong nativeObject) {
@@ -868,13 +888,13 @@
static void nativeReparent(JNIEnv* env, jclass clazz, jlong transactionObj,
jlong nativeObject,
- jobject newParentObject) {
+ jlong newParentObject) {
auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
- sp<IBinder> parentHandle = ibinderForJavaObject(env, newParentObject);
+ auto newParent = reinterpret_cast<SurfaceControl *>(newParentObject);
{
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
- transaction->reparent(ctrl, parentHandle);
+ transaction->reparent(ctrl, newParent != NULL ? newParent->getHandle() : NULL);
}
}
@@ -1063,7 +1083,7 @@
(void*)nativeDeferTransactionUntilSurface },
{"nativeReparentChildren", "(JJLandroid/os/IBinder;)V",
(void*)nativeReparentChildren } ,
- {"nativeReparent", "(JJLandroid/os/IBinder;)V",
+ {"nativeReparent", "(JJJ)V",
(void*)nativeReparent },
{"nativeSeverChildren", "(JJ)V",
(void*)nativeSeverChildren } ,
@@ -1087,6 +1107,8 @@
{"nativeGetDisplayedContentSample",
"(Landroid/os/IBinder;JJ)Landroid/hardware/display/DisplayedContentSample;",
(void*)nativeGetDisplayedContentSample },
+ {"nativeSetGeometry", "(JJLandroid/graphics/Rect;Landroid/graphics/Rect;J)V",
+ (void*)nativeSetGeometry }
};
int register_android_view_SurfaceControl(JNIEnv* env)
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 5a8ab3c..318ec9b 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -48,6 +48,7 @@
#include <FrameInfo.h>
#include <FrameMetricsObserver.h>
#include <IContextFactory.h>
+#include <Picture.h>
#include <Properties.h>
#include <PropertyValuesAnimatorSet.h>
#include <RenderNode.h>
@@ -71,6 +72,11 @@
} gFrameMetricsObserverClassInfo;
struct {
+ jclass clazz;
+ jmethodID invokePictureCapturedCallback;
+} gHardwareRenderer;
+
+struct {
jmethodID onFrameDraw;
} gFrameDrawingCallback;
@@ -905,6 +911,27 @@
jobject mObject;
};
+static void android_view_ThreadedRenderer_setPictureCapturedCallbackJNI(JNIEnv* env,
+ jobject clazz, jlong proxyPtr, jobject pictureCallback) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ if (!pictureCallback) {
+ proxy->setPictureCapturedCallback(nullptr);
+ } else {
+ JavaVM* vm = nullptr;
+ LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM");
+ auto globalCallbackRef = std::make_shared<JGlobalRefHolder>(vm,
+ env->NewGlobalRef(pictureCallback));
+ proxy->setPictureCapturedCallback([globalCallbackRef](sk_sp<SkPicture>&& picture) {
+ JNIEnv* env = getenv(globalCallbackRef->vm());
+ Picture* wrapper = new Picture{std::move(picture)};
+ env->CallStaticVoidMethod(gHardwareRenderer.clazz,
+ gHardwareRenderer.invokePictureCapturedCallback,
+ static_cast<jlong>(reinterpret_cast<intptr_t>(wrapper)),
+ globalCallbackRef->object());
+ });
+ }
+}
+
static void android_view_ThreadedRenderer_setFrameCallback(JNIEnv* env,
jobject clazz, jlong proxyPtr, jobject frameCallback) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
@@ -1145,6 +1172,8 @@
{ "nRemoveRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_removeRenderNode},
{ "nDrawRenderNode", "(JJ)V", (void*) android_view_ThreadedRendererd_drawRenderNode},
{ "nSetContentDrawBounds", "(JIIII)V", (void*)android_view_ThreadedRenderer_setContentDrawBounds},
+ { "nSetPictureCaptureCallback", "(JLandroid/graphics/HardwareRenderer$PictureCapturedCallback;)V",
+ (void*) android_view_ThreadedRenderer_setPictureCapturedCallbackJNI },
{ "nSetFrameCallback", "(JLandroid/graphics/HardwareRenderer$FrameDrawingCallback;)V",
(void*)android_view_ThreadedRenderer_setFrameCallback},
{ "nSetFrameCompleteCallback", "(JLandroid/graphics/HardwareRenderer$FrameCompleteCallback;)V",
@@ -1198,6 +1227,13 @@
gFrameMetricsObserverClassInfo.timingDataBuffer = GetFieldIDOrDie(
env, metricsClass, "mTimingData", "[J");
+ jclass hardwareRenderer = FindClassOrDie(env,
+ "android/graphics/HardwareRenderer");
+ gHardwareRenderer.clazz = reinterpret_cast<jclass>(env->NewGlobalRef(hardwareRenderer));
+ gHardwareRenderer.invokePictureCapturedCallback = GetStaticMethodIDOrDie(env, hardwareRenderer,
+ "invokePictureCapturedCallback",
+ "(JLandroid/graphics/HardwareRenderer$PictureCapturedCallback;)V");
+
jclass frameCallbackClass = FindClassOrDie(env,
"android/graphics/HardwareRenderer$FrameDrawingCallback");
gFrameDrawingCallback.onFrameDraw = GetMethodIDOrDie(env, frameCallbackClass,
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index e81b627..2e7184b 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -29,13 +29,17 @@
#include <sys/mount.h>
#include <linux/fs.h>
+#include <array>
+#include <atomic>
#include <functional>
#include <list>
#include <optional>
#include <sstream>
#include <string>
+#include <string_view>
#include <android/fdsan.h>
+#include <arpa/inet.h>
#include <fcntl.h>
#include <grp.h>
#include <inttypes.h>
@@ -46,9 +50,11 @@
#include <stdlib.h>
#include <sys/capability.h>
#include <sys/cdefs.h>
+#include <sys/eventfd.h>
#include <sys/personality.h>
#include <sys/prctl.h>
#include <sys/resource.h>
+#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
@@ -56,10 +62,11 @@
#include <sys/wait.h>
#include <unistd.h>
-#include "android-base/logging.h"
+#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/file.h>
#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
#include <cutils/fs.h>
#include <cutils/multiuser.h>
#include <private/android_filesystem_config.h>
@@ -81,6 +88,9 @@
namespace {
+// TODO (chriswailes): Add a function to initialize native Zygote data.
+// TODO (chriswailes): Fix mixed indentation style (2 and 4 spaces).
+
using namespace std::placeholders;
using android::String8;
@@ -92,6 +102,9 @@
#define CREATE_ERROR(...) StringPrintf("%s:%d: ", __FILE__, __LINE__). \
append(StringPrintf(__VA_ARGS__))
+// This type is duplicated in fd_utils.h
+typedef const std::function<void(std::string)>& fail_fn_t;
+
static pid_t gSystemServerPid = 0;
static const char kIsolatedStorage[] = "persist.sys.isolated_storage";
@@ -103,6 +116,152 @@
static bool g_is_security_enforced = true;
+/**
+ * The maximum number of characters (not including a null terminator) that a
+ * process name may contain.
+ */
+static constexpr size_t MAX_NAME_LENGTH = 15;
+
+/**
+ * The prefix string for environmental variables storing socket FDs created by
+ * init.
+ */
+
+static constexpr std::string_view ANDROID_SOCKET_PREFIX("ANDROID_SOCKET_");
+
+/**
+ * The file descriptor for the Zygote socket opened by init.
+ */
+
+static int gZygoteSocketFD = -1;
+
+/**
+ * The file descriptor for the Blastula pool socket opened by init.
+ */
+
+static int gBlastulaPoolSocketFD = -1;
+
+/**
+ * The number of Blastulas currently in this Zygote's pool.
+ */
+static std::atomic_uint32_t gBlastulaPoolCount = 0;
+
+/**
+ * Event file descriptor used to communicate reaped blastulas to the
+ * ZygoteServer.
+ */
+static int gBlastulaPoolEventFD = -1;
+
+/**
+ * The maximum value that the gBlastulaPoolMax variable may take. This value
+ * is a mirror of Zygote.BLASTULA_POOL_MAX_LIMIT
+ */
+static constexpr int BLASTULA_POOL_MAX_LIMIT = 10;
+
+/**
+ * A helper class containing accounting information for Blastulas.
+ */
+class BlastulaTableEntry {
+ public:
+ struct EntryStorage {
+ int32_t pid;
+ int32_t read_pipe_fd;
+
+ bool operator!=(const EntryStorage& other) {
+ return pid != other.pid || read_pipe_fd != other.read_pipe_fd;
+ }
+ };
+
+ private:
+ static constexpr EntryStorage INVALID_ENTRY_VALUE = {-1, -1};
+
+ std::atomic<EntryStorage> mStorage;
+ static_assert(decltype(mStorage)::is_always_lock_free);
+
+ public:
+ constexpr BlastulaTableEntry() : mStorage(INVALID_ENTRY_VALUE) {}
+
+ /**
+ * If the provided PID matches the one stored in this entry, the entry will
+ * be invalidated and the associated file descriptor will be closed. If the
+ * PIDs don't match nothing will happen.
+ *
+ * @param pid The ID of the process who's entry we want to clear.
+ * @return True if the entry was cleared; false otherwise
+ */
+ bool ClearForPID(int32_t pid) {
+ EntryStorage storage = mStorage.load();
+
+ if (storage.pid == pid) {
+ /*
+ * There are three possible outcomes from this compare-and-exchange:
+ * 1) It succeeds, in which case we close the FD
+ * 2) It fails and the new value is INVALID_ENTRY_VALUE, in which case
+ * the entry has already been cleared.
+ * 3) It fails and the new value isn't INVALID_ENTRY_VALUE, in which
+ * case the entry has already been cleared and re-used.
+ *
+ * In all three cases the goal of the caller has been met and we can
+ * return true.
+ */
+ if (mStorage.compare_exchange_strong(storage, INVALID_ENTRY_VALUE)) {
+ close(storage.read_pipe_fd);
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @return A copy of the data stored in this entry.
+ */
+ std::optional<EntryStorage> GetValues() {
+ EntryStorage storage = mStorage.load();
+
+ if (storage != INVALID_ENTRY_VALUE) {
+ return storage;
+ } else {
+ return std::nullopt;
+ }
+ }
+
+ /**
+ * Sets the entry to the given values if it is currently invalid.
+ *
+ * @param pid The process ID for the new entry.
+ * @param read_pipe_fd The read end of the blastula control pipe for this
+ * process.
+ * @return True if the entry was set; false otherwise.
+ */
+ bool SetIfInvalid(int32_t pid, int32_t read_pipe_fd) {
+ EntryStorage new_value_storage;
+
+ new_value_storage.pid = pid;
+ new_value_storage.read_pipe_fd = read_pipe_fd;
+
+ EntryStorage expected = INVALID_ENTRY_VALUE;
+
+ return mStorage.compare_exchange_strong(expected, new_value_storage);
+ }
+};
+
+/**
+ * A table containing information about the Blastulas currently in the pool.
+ *
+ * Multiple threads may be attempting to modify the table, either from the
+ * signal handler or from the ZygoteServer poll loop. Atomic loads/stores in
+ * the BlastulaTableEntry class prevent data races during these concurrent
+ * operations.
+ */
+static std::array<BlastulaTableEntry, BLASTULA_POOL_MAX_LIMIT> gBlastulaTable;
+
+/**
+ * The list of open zygote file descriptors.
+ */
+static FileDescriptorTable* gOpenFdTable = nullptr;
+
// Must match values in com.android.internal.os.Zygote.
enum MountExternalKind {
MOUNT_EXTERNAL_NONE = 0,
@@ -119,6 +278,9 @@
DEBUG_ENABLE_JDWP = 1,
};
+// Forward declaration so we don't have to move the signal handler.
+static bool RemoveBlastulaTableEntry(pid_t blastula_pid);
+
static void RuntimeAbort(JNIEnv* env, int line, const char* msg) {
std::ostringstream oss;
oss << __FILE__ << ":" << line << ": " << msg;
@@ -129,6 +291,7 @@
static void SigChldHandler(int /*signal_number*/) {
pid_t pid;
int status;
+ int64_t blastulas_removed = 0;
// It's necessary to save and restore the errno during this function.
// Since errno is stored per thread, changing it here modifies the errno
@@ -162,6 +325,11 @@
ALOGE("Exit zygote because system server (%d) has terminated", pid);
kill(getpid(), SIGKILL);
}
+
+ // Check to see if the PID is in the blastula pool and remove it if it is.
+ if (RemoveBlastulaTableEntry(pid)) {
+ ++blastulas_removed;
+ }
}
// Note that we shouldn't consider ECHILD an error because
@@ -170,6 +338,15 @@
ALOGW("Zygote SIGCHLD error in waitpid: %s", strerror(errno));
}
+ if (blastulas_removed > 0) {
+ if (write(gBlastulaPoolEventFD, &blastulas_removed, sizeof(blastulas_removed)) == -1) {
+ // If this write fails something went terribly wrong. We will now kill
+ // the zygote and let the system bring it back up.
+ ALOGE("Zygote failed to write to blastula pool event FD: %s", strerror(errno));
+ kill(getpid(), SIGKILL);
+ }
+ }
+
errno = saved_errno;
}
@@ -194,13 +371,13 @@
struct sigaction sig_chld = {};
sig_chld.sa_handler = SigChldHandler;
- if (sigaction(SIGCHLD, &sig_chld, NULL) < 0) {
+ if (sigaction(SIGCHLD, &sig_chld, nullptr) < 0) {
ALOGW("Error setting SIGCHLD handler: %s", strerror(errno));
}
struct sigaction sig_hup = {};
sig_hup.sa_handler = SIG_IGN;
- if (sigaction(SIGHUP, &sig_hup, NULL) < 0) {
+ if (sigaction(SIGHUP, &sig_hup, nullptr) < 0) {
ALOGW("Error setting SIGHUP handler: %s", strerror(errno));
}
}
@@ -211,64 +388,57 @@
memset(&sa, 0, sizeof(sa));
sa.sa_handler = SIG_DFL;
- if (sigaction(SIGCHLD, &sa, NULL) < 0) {
+ if (sigaction(SIGCHLD, &sa, nullptr) < 0) {
ALOGW("Error unsetting SIGCHLD handler: %s", strerror(errno));
}
}
// Calls POSIX setgroups() using the int[] object as an argument.
-// A NULL argument is tolerated.
-static bool SetGids(JNIEnv* env, jintArray javaGids, std::string* error_msg) {
- if (javaGids == NULL) {
- return true;
+// A nullptr argument is tolerated.
+static void SetGids(JNIEnv* env, jintArray managed_gids, fail_fn_t fail_fn) {
+ if (managed_gids == nullptr) {
+ return;
}
- ScopedIntArrayRO gids(env, javaGids);
- if (gids.get() == NULL) {
- *error_msg = CREATE_ERROR("Getting gids int array failed");
- return false;
- }
- int rc = setgroups(gids.size(), reinterpret_cast<const gid_t*>(&gids[0]));
- if (rc == -1) {
- *error_msg = CREATE_ERROR("setgroups failed: %s, gids.size=%zu", strerror(errno), gids.size());
- return false;
+ ScopedIntArrayRO gids(env, managed_gids);
+ if (gids.get() == nullptr) {
+ fail_fn(CREATE_ERROR("Getting gids int array failed"));
}
- return true;
+ if (setgroups(gids.size(), reinterpret_cast<const gid_t*>(&gids[0])) == -1) {
+ fail_fn(CREATE_ERROR("setgroups failed: %s, gids.size=%zu", strerror(errno), gids.size()));
+ }
}
// Sets the resource limits via setrlimit(2) for the values in the
// two-dimensional array of integers that's passed in. The second dimension
-// contains a tuple of length 3: (resource, rlim_cur, rlim_max). NULL is
+// contains a tuple of length 3: (resource, rlim_cur, rlim_max). nullptr is
// treated as an empty array.
-static bool SetRLimits(JNIEnv* env, jobjectArray javaRlimits, std::string* error_msg) {
- if (javaRlimits == NULL) {
- return true;
+static void SetRLimits(JNIEnv* env, jobjectArray managed_rlimits, fail_fn_t fail_fn) {
+ if (managed_rlimits == nullptr) {
+ return;
}
rlimit rlim;
memset(&rlim, 0, sizeof(rlim));
- for (int i = 0; i < env->GetArrayLength(javaRlimits); ++i) {
- ScopedLocalRef<jobject> javaRlimitObject(env, env->GetObjectArrayElement(javaRlimits, i));
- ScopedIntArrayRO javaRlimit(env, reinterpret_cast<jintArray>(javaRlimitObject.get()));
- if (javaRlimit.size() != 3) {
- *error_msg = CREATE_ERROR("rlimits array must have a second dimension of size 3");
- return false;
+ for (int i = 0; i < env->GetArrayLength(managed_rlimits); ++i) {
+ ScopedLocalRef<jobject>
+ managed_rlimit_object(env, env->GetObjectArrayElement(managed_rlimits, i));
+ ScopedIntArrayRO rlimit_handle(env, reinterpret_cast<jintArray>(managed_rlimit_object.get()));
+
+ if (rlimit_handle.size() != 3) {
+ fail_fn(CREATE_ERROR("rlimits array must have a second dimension of size 3"));
}
- rlim.rlim_cur = javaRlimit[1];
- rlim.rlim_max = javaRlimit[2];
+ rlim.rlim_cur = rlimit_handle[1];
+ rlim.rlim_max = rlimit_handle[2];
- int rc = setrlimit(javaRlimit[0], &rlim);
- if (rc == -1) {
- *error_msg = CREATE_ERROR("setrlimit(%d, {%ld, %ld}) failed", javaRlimit[0], rlim.rlim_cur,
- rlim.rlim_max);
- return false;
+ if (setrlimit(rlimit_handle[0], &rlim) == -1) {
+ fail_fn(CREATE_ERROR("setrlimit(%d, {%ld, %ld}) failed",
+ rlimit_handle[0], rlim.rlim_cur, rlim.rlim_max));
}
}
-
- return true;
}
static void EnableDebugger() {
@@ -323,10 +493,7 @@
// Apply system or app filter based on uid.
if (uid >= AID_APP_START) {
if (is_child_zygote) {
- // set_app_zygote_seccomp_filter();
- // TODO(b/111434506) install the filter; for now, install the app filter
- // which is more restrictive.
- set_app_seccomp_filter();
+ set_app_zygote_seccomp_filter();
} else {
set_app_seccomp_filter();
}
@@ -335,32 +502,26 @@
}
}
-static bool EnableKeepCapabilities(std::string* error_msg) {
- int rc = prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
- if (rc == -1) {
- *error_msg = CREATE_ERROR("prctl(PR_SET_KEEPCAPS) failed: %s", strerror(errno));
- return false;
+static void EnableKeepCapabilities(fail_fn_t fail_fn) {
+ if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) {
+ fail_fn(CREATE_ERROR("prctl(PR_SET_KEEPCAPS) failed: %s", strerror(errno)));
}
- return true;
}
-static bool DropCapabilitiesBoundingSet(std::string* error_msg) {
- for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) {
- int rc = prctl(PR_CAPBSET_DROP, i, 0, 0, 0);
- if (rc == -1) {
+static void DropCapabilitiesBoundingSet(fail_fn_t fail_fn) {
+ for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) {;
+ if (prctl(PR_CAPBSET_DROP, i, 0, 0, 0) == -1) {
if (errno == EINVAL) {
ALOGE("prctl(PR_CAPBSET_DROP) failed with EINVAL. Please verify "
"your kernel is compiled with file capabilities support");
} else {
- *error_msg = CREATE_ERROR("prctl(PR_CAPBSET_DROP, %d) failed: %s", i, strerror(errno));
- return false;
+ fail_fn(CREATE_ERROR("prctl(PR_CAPBSET_DROP, %d) failed: %s", i, strerror(errno)));
}
}
}
- return true;
}
-static bool SetInheritable(uint64_t inheritable, std::string* error_msg) {
+static void SetInheritable(uint64_t inheritable, fail_fn_t fail_fn) {
__user_cap_header_struct capheader;
memset(&capheader, 0, sizeof(capheader));
capheader.version = _LINUX_CAPABILITY_VERSION_3;
@@ -368,23 +529,19 @@
__user_cap_data_struct capdata[2];
if (capget(&capheader, &capdata[0]) == -1) {
- *error_msg = CREATE_ERROR("capget failed: %s", strerror(errno));
- return false;
+ fail_fn(CREATE_ERROR("capget failed: %s", strerror(errno)));
}
capdata[0].inheritable = inheritable;
capdata[1].inheritable = inheritable >> 32;
if (capset(&capheader, &capdata[0]) == -1) {
- *error_msg = CREATE_ERROR("capset(inh=%" PRIx64 ") failed: %s", inheritable, strerror(errno));
- return false;
+ fail_fn(CREATE_ERROR("capset(inh=%" PRIx64 ") failed: %s", inheritable, strerror(errno)));
}
-
- return true;
}
-static bool SetCapabilities(uint64_t permitted, uint64_t effective, uint64_t inheritable,
- std::string* error_msg) {
+static void SetCapabilities(uint64_t permitted, uint64_t effective, uint64_t inheritable,
+ fail_fn_t fail_fn) {
__user_cap_header_struct capheader;
memset(&capheader, 0, sizeof(capheader));
capheader.version = _LINUX_CAPABILITY_VERSION_3;
@@ -400,27 +557,23 @@
capdata[1].inheritable = inheritable >> 32;
if (capset(&capheader, &capdata[0]) == -1) {
- *error_msg = CREATE_ERROR("capset(perm=%" PRIx64 ", eff=%" PRIx64 ", inh=%" PRIx64 ") "
- "failed: %s", permitted, effective, inheritable, strerror(errno));
- return false;
+ fail_fn(CREATE_ERROR("capset(perm=%" PRIx64 ", eff=%" PRIx64 ", inh=%" PRIx64 ") "
+ "failed: %s", permitted, effective, inheritable, strerror(errno)));
}
- return true;
}
-static bool SetSchedulerPolicy(std::string* error_msg) {
+static void SetSchedulerPolicy(fail_fn_t fail_fn) {
errno = -set_sched_policy(0, SP_DEFAULT);
if (errno != 0) {
- *error_msg = CREATE_ERROR("set_sched_policy(0, SP_DEFAULT) failed: %s", strerror(errno));
- return false;
+ fail_fn(CREATE_ERROR("set_sched_policy(0, SP_DEFAULT) failed: %s", strerror(errno)));
}
- return true;
}
static int UnmountTree(const char* path) {
size_t path_len = strlen(path);
FILE* fp = setmntent("/proc/mounts", "r");
- if (fp == NULL) {
+ if (fp == nullptr) {
ALOGE("Error opening /proc/mounts: %s", strerror(errno));
return -errno;
}
@@ -429,7 +582,7 @@
// reverse order to give us the best chance of success.
std::list<std::string> toUnmount;
mntent* mentry;
- while ((mentry = getmntent(fp)) != NULL) {
+ while ((mentry = getmntent(fp)) != nullptr) {
if (strncmp(mentry->mnt_dir, path, path_len) == 0) {
toUnmount.push_front(std::string(mentry->mnt_dir));
}
@@ -444,56 +597,55 @@
return 0;
}
-static bool createPkgSandbox(uid_t uid, const std::string& package_name, std::string* error_msg) {
+static void CreatePkgSandbox(uid_t uid, const std::string& package_name, fail_fn_t fail_fn) {
// Create /mnt/user/0/package/<package-name>
userid_t user_id = multiuser_get_user_id(uid);
std::string pkg_sandbox_dir = StringPrintf("/mnt/user/%d", user_id);
if (fs_prepare_dir(pkg_sandbox_dir.c_str(), 0751, AID_ROOT, AID_ROOT) != 0) {
- *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str());
- return false;
+ fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str()));
}
+
StringAppendF(&pkg_sandbox_dir, "/package");
if (fs_prepare_dir(pkg_sandbox_dir.c_str(), 0700, AID_ROOT, AID_ROOT) != 0) {
- *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str());
- return false;
+ fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str()));
}
+
StringAppendF(&pkg_sandbox_dir, "/%s", package_name.c_str());
if (fs_prepare_dir(pkg_sandbox_dir.c_str(), 0755, uid, uid) != 0) {
- *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str());
- return false;
+ fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str()));
}
- return true;
}
-static bool bindMount(const std::string& sourceDir, const std::string& targetDir,
- std::string* error_msg) {
- if (TEMP_FAILURE_RETRY(mount(sourceDir.c_str(), targetDir.c_str(),
- nullptr, MS_BIND | MS_REC, nullptr)) == -1) {
- *error_msg = CREATE_ERROR("Failed to mount %s to %s: %s",
- sourceDir.c_str(), targetDir.c_str(), strerror(errno));
- return false;
+static void BindMount(const std::string& sourceDir, const std::string& targetDir,
+ fail_fn_t fail_fn) {
+ if (TEMP_FAILURE_RETRY(mount(sourceDir.c_str(), targetDir.c_str(), nullptr,
+ MS_BIND | MS_REC, nullptr)) == -1) {
+ fail_fn(CREATE_ERROR("Failed to mount %s to %s: %s",
+ sourceDir.c_str(), targetDir.c_str(), strerror(errno)));
}
- if (TEMP_FAILURE_RETRY(mount(nullptr, targetDir.c_str(),
- nullptr, MS_SLAVE | MS_REC, nullptr)) == -1) {
- *error_msg = CREATE_ERROR("Failed to set MS_SLAVE for %s", targetDir.c_str());
- return false;
+
+ if (TEMP_FAILURE_RETRY(mount(nullptr, targetDir.c_str(), nullptr,
+ MS_SLAVE | MS_REC, nullptr)) == -1) {
+ fail_fn(CREATE_ERROR("Failed to set MS_SLAVE for %s", targetDir.c_str()));
}
- return true;
}
-static bool mountPkgSpecificDir(const std::string& mntSourceRoot,
- const std::string& mntTargetRoot, const std::string& packageName,
- const char* dirName, std::string* error_msg) {
+static void MountPkgSpecificDir(const std::string& mntSourceRoot,
+ const std::string& mntTargetRoot,
+ const std::string& packageName,
+ const char* dirName,
+ fail_fn_t fail_fn) {
std::string mntSourceDir = StringPrintf("%s/Android/%s/%s",
mntSourceRoot.c_str(), dirName, packageName.c_str());
std::string mntTargetDir = StringPrintf("%s/Android/%s/%s",
mntTargetRoot.c_str(), dirName, packageName.c_str());
- return bindMount(mntSourceDir, mntTargetDir, error_msg);
+
+ BindMount(mntSourceDir, mntTargetDir, fail_fn);
}
-static bool preparePkgSpecificDirs(const std::vector<std::string>& packageNames,
- const std::vector<std::string>& volumeLabels, bool mountAllObbs,
- userid_t userId, std::string* error_msg) {
+static void PreparePkgSpecificDirs(const std::vector<std::string>& packageNames,
+ const std::vector<std::string>& volumeLabels,
+ bool mountAllObbs, userid_t userId, fail_fn_t fail_fn) {
for (auto& label : volumeLabels) {
std::string mntSource = StringPrintf("/mnt/runtime/write/%s", label.c_str());
std::string mntTarget = StringPrintf("/storage/%s", label.c_str());
@@ -501,28 +653,29 @@
StringAppendF(&mntSource, "/%d", userId);
StringAppendF(&mntTarget, "/%d", userId);
}
+
for (auto& package : packageNames) {
- mountPkgSpecificDir(mntSource, mntTarget, package, "data", error_msg);
- mountPkgSpecificDir(mntSource, mntTarget, package, "media", error_msg);
+ MountPkgSpecificDir(mntSource, mntTarget, package, "data", fail_fn);
+ MountPkgSpecificDir(mntSource, mntTarget, package, "media", fail_fn);
if (!mountAllObbs) {
- mountPkgSpecificDir(mntSource, mntTarget, package, "obb", error_msg);
+ MountPkgSpecificDir(mntSource, mntTarget, package, "obb", fail_fn);
}
}
+
if (mountAllObbs) {
StringAppendF(&mntSource, "/Android/obb");
StringAppendF(&mntTarget, "/Android/obb");
- bindMount(mntSource, mntTarget, error_msg);
+ BindMount(mntSource, mntTarget, fail_fn);
}
}
- return true;
}
// Create a private mount namespace and bind mount appropriate emulated
// storage for the given user.
-static bool MountEmulatedStorage(uid_t uid, jint mount_mode,
- bool force_mount_namespace, std::string* error_msg, const std::string& package_name,
+static void MountEmulatedStorage(uid_t uid, jint mount_mode,
+ bool force_mount_namespace, const std::string& package_name,
const std::vector<std::string>& packages_for_uid,
- const std::vector<std::string>& visible_vol_ids) {
+ const std::vector<std::string>& visible_vol_ids, fail_fn_t fail_fn) {
// See storage config details at http://source.android.com/tech/storage/
String8 storageSource;
@@ -534,18 +687,17 @@
storageSource = "/mnt/runtime/write";
} else if (mount_mode == MOUNT_EXTERNAL_NONE && !force_mount_namespace) {
// Sane default of no storage visible
- return true;
+ return;
}
// Create a second private mount namespace for our process
if (unshare(CLONE_NEWNS) == -1) {
- *error_msg = CREATE_ERROR("Failed to unshare(): %s", strerror(errno));
- return false;
+ fail_fn(CREATE_ERROR("Failed to unshare(): %s", strerror(errno)));
}
// Handle force_mount_namespace with MOUNT_EXTERNAL_NONE.
if (mount_mode == MOUNT_EXTERNAL_NONE) {
- return true;
+ return;
}
if (GetBoolProperty(kIsolatedStorageSnapshot, GetBoolProperty(kIsolatedStorage, false))) {
@@ -553,66 +705,64 @@
storageSource = (mount_mode == MOUNT_EXTERNAL_FULL)
? "/mnt/runtime/full" : "/mnt/runtime/write";
if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage",
- NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) {
- *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s",
- storageSource.string(),
- strerror(errno));
- return false;
+ NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) {
+ fail_fn(CREATE_ERROR("Failed to mount %s to /storage: %s",
+ storageSource.string(),
+ strerror(errno)));
}
// Mount user-specific symlink helper into place
userid_t user_id = multiuser_get_user_id(uid);
const String8 userSource(String8::format("/mnt/user/%d", user_id));
if (fs_prepare_dir(userSource.string(), 0751, 0, 0) == -1) {
- *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", userSource.string());
- return false;
+ fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s (%s)",
+ userSource.string(), strerror(errno)));
}
- if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self",
- NULL, MS_BIND, NULL)) == -1) {
- *error_msg = CREATE_ERROR("Failed to mount %s to /storage/self: %s",
- userSource.string(),
- strerror(errno));
- return false;
+
+ if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self", nullptr, MS_BIND,
+ nullptr)) == -1) {
+ fail_fn(CREATE_ERROR("Failed to mount %s to /storage/self: %s",
+ userSource.string(),
+ strerror(errno)));
}
} else {
if (package_name.empty()) {
- return true;
+ return;
}
+
userid_t user_id = multiuser_get_user_id(uid);
- std::string pkgSandboxDir = StringPrintf("/mnt/user/%d/package/%s",
- user_id, package_name.c_str());
+ std::string pkgSandboxDir =
+ StringPrintf("/mnt/user/%d/package/%s", user_id, package_name.c_str());
struct stat sb;
bool sandboxAlreadyCreated = true;
if (TEMP_FAILURE_RETRY(lstat(pkgSandboxDir.c_str(), &sb)) == -1) {
if (errno == ENOENT) {
ALOGD("Sandbox not yet created for %s", pkgSandboxDir.c_str());
sandboxAlreadyCreated = false;
- if (!createPkgSandbox(uid, package_name, error_msg)) {
- return false;
- }
+ CreatePkgSandbox(uid, package_name, fail_fn);
} else {
- ALOGE("Failed to lstat %s", pkgSandboxDir.c_str());
- return false;
+ fail_fn(CREATE_ERROR("Failed to lstat %s: %s",
+ pkgSandboxDir.c_str(), strerror(errno)));
}
}
+
if (TEMP_FAILURE_RETRY(mount(pkgSandboxDir.c_str(), "/storage",
- nullptr, MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) {
- *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s",
- pkgSandboxDir.c_str(), strerror(errno));
- return false;
+ nullptr, MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) {
+ fail_fn(CREATE_ERROR("Failed to mount %s to /storage: %s",
+ pkgSandboxDir.c_str(), strerror(errno)));
}
+
if (access("/storage/obb_mount", F_OK) == 0) {
if (mount_mode != MOUNT_EXTERNAL_INSTALLER) {
remove("/storage/obb_mount");
}
} else {
if (mount_mode == MOUNT_EXTERNAL_INSTALLER) {
- int fd = TEMP_FAILURE_RETRY(open("/storage/obb_mount",
- O_RDWR | O_CREAT, 0660));
+ int fd =
+ TEMP_FAILURE_RETRY(open("/storage/obb_mount", O_RDWR | O_CREAT, 0660));
if (fd == -1) {
- *error_msg = CREATE_ERROR("Couldn't create /storage/obb_mount: %s",
- strerror(errno));
- return false;
+ fail_fn(CREATE_ERROR("Couldn't create /storage/obb_mount: %s",
+ strerror(errno)));
}
close(fd);
}
@@ -621,38 +771,32 @@
// pkg specific directories. Otherwise, leave as is and bind mounts will be taken
// care of by vold later.
if (sandboxAlreadyCreated) {
- if (!preparePkgSpecificDirs(packages_for_uid, visible_vol_ids,
- mount_mode == MOUNT_EXTERNAL_INSTALLER, user_id, error_msg)) {
- return false;
- }
+ PreparePkgSpecificDirs(packages_for_uid, visible_vol_ids,
+ mount_mode == MOUNT_EXTERNAL_INSTALLER, user_id, fail_fn);
}
}
} else {
- if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage",
- NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) {
- *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s",
- storageSource.string(),
- strerror(errno));
- return false;
+ if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage", nullptr,
+ MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) {
+ fail_fn(CREATE_ERROR("Failed to mount %s to /storage: %s",
+ storageSource.string(),
+ strerror(errno)));
}
// Mount user-specific symlink helper into place
userid_t user_id = multiuser_get_user_id(uid);
const String8 userSource(String8::format("/mnt/user/%d", user_id));
if (fs_prepare_dir(userSource.string(), 0751, 0, 0) == -1) {
- *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", userSource.string());
- return false;
+ fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s",
+ userSource.string()));
}
+
if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self",
- NULL, MS_BIND, NULL)) == -1) {
- *error_msg = CREATE_ERROR("Failed to mount %s to /storage/self: %s",
- userSource.string(),
- strerror(errno));
- return false;
+ nullptr, MS_BIND, nullptr)) == -1) {
+ fail_fn(CREATE_ERROR("Failed to mount %s to /storage/self: %s",
+ userSource.string(), strerror(errno)));
}
}
-
- return true;
}
static bool NeedsNoRandomizeWorkaround() {
@@ -680,55 +824,45 @@
// descriptor (if any) is closed via dup2(), replacing it with a valid
// (open) descriptor to /dev/null.
-static bool DetachDescriptors(JNIEnv* env, jintArray fdsToClose, std::string* error_msg) {
- if (!fdsToClose) {
- return true;
- }
- jsize count = env->GetArrayLength(fdsToClose);
- ScopedIntArrayRO ar(env, fdsToClose);
- if (ar.get() == NULL) {
- *error_msg = "Bad fd array";
- return false;
- }
- jsize i;
- int devnull;
- for (i = 0; i < count; i++) {
- devnull = open("/dev/null", O_RDWR);
- if (devnull < 0) {
- *error_msg = std::string("Failed to open /dev/null: ").append(strerror(errno));
- return false;
+static void DetachDescriptors(JNIEnv* env,
+ const std::vector<int>& fds_to_close,
+ fail_fn_t fail_fn) {
+
+ if (fds_to_close.size() > 0) {
+ android::base::unique_fd devnull_fd(open("/dev/null", O_RDWR));
+ if (devnull_fd == -1) {
+ fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno)));
}
- ALOGV("Switching descriptor %d to /dev/null: %s", ar[i], strerror(errno));
- if (dup2(devnull, ar[i]) < 0) {
- *error_msg = StringPrintf("Failed dup2() on descriptor %d: %s", ar[i], strerror(errno));
- return false;
+
+ for (int fd : fds_to_close) {
+ ALOGV("Switching descriptor %d to /dev/null", fd);
+ if (dup2(devnull_fd, fd) == -1) {
+ fail_fn(StringPrintf("Failed dup2() on descriptor %d: %s", fd, strerror(errno)));
+ }
}
- close(devnull);
}
- return true;
}
-void SetThreadName(const char* thread_name) {
+void SetThreadName(const std::string& thread_name) {
bool hasAt = false;
bool hasDot = false;
- const char* s = thread_name;
- while (*s) {
- if (*s == '.') {
+
+ for (const char str_el : thread_name) {
+ if (str_el == '.') {
hasDot = true;
- } else if (*s == '@') {
+ } else if (str_el == '@') {
hasAt = true;
}
- s++;
}
- const int len = s - thread_name;
- if (len < 15 || hasAt || !hasDot) {
- s = thread_name;
- } else {
- s = thread_name + len - 15;
+
+ const char* name_start_ptr = thread_name.c_str();
+ if (thread_name.length() >= MAX_NAME_LENGTH && !hasAt && hasDot) {
+ name_start_ptr += thread_name.length() - MAX_NAME_LENGTH;
}
+
// pthread_setname_np fails rather than truncating long strings.
char buf[16]; // MAX_TASK_COMM_LEN=16 is hard-coded into bionic
- strlcpy(buf, s, sizeof(buf)-1);
+ strlcpy(buf, name_start_ptr, sizeof(buf) - 1);
errno = pthread_setname_np(pthread_self(), buf);
if (errno != 0) {
ALOGW("Unable to set the name of current thread to '%s': %s", buf, strerror(errno));
@@ -737,28 +871,16 @@
android::base::SetDefaultTag(buf);
}
-// The list of open zygote file descriptors.
-static FileDescriptorTable* gOpenFdTable = NULL;
-
-static bool FillFileDescriptorVector(JNIEnv* env,
- jintArray managed_fds,
- std::vector<int>* fds,
- std::string* error_msg) {
- CHECK(fds != nullptr);
- if (managed_fds != nullptr) {
- ScopedIntArrayRO ar(env, managed_fds);
- if (ar.get() == nullptr) {
- *error_msg = "Bad fd array";
- return false;
- }
- fds->reserve(ar.size());
- for (size_t i = 0; i < ar.size(); ++i) {
- fds->push_back(ar[i]);
- }
- }
- return true;
-}
-
+/**
+ * A failure function used to report fatal errors to the managed runtime. This
+ * function is often curried with the process name information and then passed
+ * to called functions.
+ *
+ * @param env Managed runtime environment
+ * @param process_name A native representation of the process name
+ * @param managed_process_name A managed representation of the process name
+ * @param msg The error message to be reported
+ */
[[noreturn]]
static void ZygoteFailure(JNIEnv* env,
const char* process_name,
@@ -779,12 +901,25 @@
__builtin_unreachable();
}
+/**
+ * A helper method for converting managed strings to native strings. A fatal
+ * error is generated if a problem is encountered in extracting a non-null
+ * string.
+ *
+ * @param env Managed runtime environment
+ * @param process_name A native representation of the process name
+ * @param managed_process_name A managed representation of the process name
+ * @param managed_string The managed string to extract
+ *
+ * @return An empty option if the managed string is null. A optional-wrapped
+ * string otherwise.
+ */
static std::optional<std::string> ExtractJString(JNIEnv* env,
const char* process_name,
jstring managed_process_name,
jstring managed_string) {
if (managed_string == nullptr) {
- return std::optional<std::string>();
+ return std::nullopt;
} else {
ScopedUtfChars scoped_string_chars(env, managed_string);
@@ -796,15 +931,124 @@
}
}
-// Utility routine to fork a zygote.
-static pid_t ForkCommon(JNIEnv* env, bool is_system_server,
- jintArray managed_fds_to_close, jintArray managed_fds_to_ignore) {
- SetSignalHandlers();
+/**
+ * A helper method for converting managed string arrays to native vectors. A
+ * fatal error is generated if a problem is encountered in extracting a non-null array.
+ *
+ * @param env Managed runtime environment
+ * @param process_name A native representation of the process name
+ * @param managed_process_name A managed representation of the process name
+ * @param managed_array The managed integer array to extract
+ *
+ * @return An empty option if the managed array is null. A optional-wrapped
+ * vector otherwise.
+ */
+static std::optional<std::vector<int>> ExtractJIntArray(JNIEnv* env,
+ const char* process_name,
+ jstring managed_process_name,
+ jintArray managed_array) {
+ if (managed_array == nullptr) {
+ return std::nullopt;
+ } else {
+ ScopedIntArrayRO managed_array_handle(env, managed_array);
- // Block SIGCHLD prior to fork.
- sigset_t sigchld;
- sigemptyset(&sigchld);
- sigaddset(&sigchld, SIGCHLD);
+ if (managed_array_handle.get() != nullptr) {
+ std::vector<int> native_array;
+ native_array.reserve(managed_array_handle.size());
+
+ for (size_t array_index = 0; array_index < managed_array_handle.size(); ++array_index) {
+ native_array.push_back(managed_array_handle[array_index]);
+ }
+
+ return std::move(native_array);
+
+ } else {
+ ZygoteFailure(env, process_name, managed_process_name, "Failed to extract JIntArray.");
+ }
+ }
+}
+
+/**
+ * A helper method for converting managed string arrays to native vectors. A
+ * fatal error is generated if a problem is encountered in extracting a non-null array.
+ *
+ * @param env Managed runtime environment
+ * @param process_name A native representation of the process name
+ * @param managed_process_name A managed representation of the process name
+ * @param managed_array The managed string array to extract
+ *
+ * @return An empty option if the managed array is null. A optional-wrapped
+ * vector otherwise.
+ */
+static std::optional<std::vector<std::string>> ExtractJStringArray(JNIEnv* env,
+ const char* process_name,
+ jstring managed_process_name,
+ jobjectArray managed_array) {
+ if (managed_array == nullptr) {
+ return std::nullopt;
+ } else {
+ jsize element_count = env->GetArrayLength(managed_array);
+ std::vector<std::string> native_string_vector;
+ native_string_vector.reserve(element_count);
+
+ for (jsize array_index = 0; array_index < element_count; ++array_index) {
+ jstring managed_string = (jstring) env->GetObjectArrayElement(managed_array, array_index);
+ auto native_string = ExtractJString(env, process_name, managed_process_name, managed_string);
+
+ if (LIKELY(native_string.has_value())) {
+ native_string_vector.emplace_back(std::move(native_string.value()));
+ } else {
+ ZygoteFailure(env, process_name, managed_process_name,
+ "Null string found in managed string array.");
+ }
+ }
+
+ return std::move(native_string_vector);
+ }
+}
+
+/**
+ * A utility function for blocking signals.
+ *
+ * @param signum Signal number to block
+ * @param fail_fn Fatal error reporting function
+ *
+ * @see ZygoteFailure
+ */
+static void BlockSignal(int signum, fail_fn_t fail_fn) {
+ sigset_t sigs;
+ sigemptyset(&sigs);
+ sigaddset(&sigs, signum);
+
+ if (sigprocmask(SIG_BLOCK, &sigs, nullptr) == -1) {
+ fail_fn(CREATE_ERROR("Failed to block signal %s: %s", strsignal(signum), strerror(errno)));
+ }
+}
+
+
+/**
+ * A utility function for unblocking signals.
+ *
+ * @param signum Signal number to unblock
+ * @param fail_fn Fatal error reporting function
+ *
+ * @see ZygoteFailure
+ */
+static void UnblockSignal(int signum, fail_fn_t fail_fn) {
+ sigset_t sigs;
+ sigemptyset(&sigs);
+ sigaddset(&sigs, signum);
+
+ if (sigprocmask(SIG_UNBLOCK, &sigs, nullptr) == -1) {
+ fail_fn(CREATE_ERROR("Failed to un-block signal %s: %s", strsignal(signum), strerror(errno)));
+ }
+}
+
+// Utility routine to fork a process from the zygote.
+static pid_t ForkCommon(JNIEnv* env, bool is_system_server,
+ const std::vector<int>& fds_to_close,
+ const std::vector<int>& fds_to_ignore) {
+ SetSignalHandlers();
// Curry a failure function.
auto fail_fn = std::bind(ZygoteFailure, env, is_system_server ? "system_server" : "zygote",
@@ -815,9 +1059,7 @@
// This would cause failures because the FDs are not whitelisted.
//
// Note that the zygote process is single threaded at this point.
- if (sigprocmask(SIG_BLOCK, &sigchld, nullptr) == -1) {
- fail_fn(CREATE_ERROR("sigprocmask(SIG_SETMASK, { SIGCHLD }) failed: %s", strerror(errno)));
- }
+ BlockSignal(SIGCHLD, fail_fn);
// Close any logging related FDs before we start evaluating the list of
// file descriptors.
@@ -827,19 +1069,10 @@
// If this is the first fork for this zygote, create the open FD table. If
// it isn't, we just need to check whether the list of open files has changed
// (and it shouldn't in the normal case).
- std::string error_msg;
- std::vector<int> fds_to_ignore;
- if (!FillFileDescriptorVector(env, managed_fds_to_ignore, &fds_to_ignore, &error_msg)) {
- fail_fn(error_msg);
- }
-
if (gOpenFdTable == nullptr) {
- gOpenFdTable = FileDescriptorTable::Create(fds_to_ignore, &error_msg);
- if (gOpenFdTable == nullptr) {
- fail_fn(error_msg);
- }
- } else if (!gOpenFdTable->Restat(fds_to_ignore, &error_msg)) {
- fail_fn(error_msg);
+ gOpenFdTable = FileDescriptorTable::Create(fds_to_ignore, fail_fn);
+ } else {
+ gOpenFdTable->Restat(fds_to_ignore, fail_fn);
}
android_fdsan_error_level fdsan_error_level = android_fdsan_get_error_level();
@@ -851,24 +1084,18 @@
PreApplicationInit();
// Clean up any descriptors which must be closed immediately
- if (!DetachDescriptors(env, managed_fds_to_close, &error_msg)) {
- fail_fn(error_msg);
- }
+ DetachDescriptors(env, fds_to_close, fail_fn);
// Re-open all remaining open file descriptors so that they aren't shared
// with the zygote across a fork.
- if (!gOpenFdTable->ReopenOrDetach(&error_msg)) {
- fail_fn(error_msg);
- }
+ gOpenFdTable->ReopenOrDetach(fail_fn);
// Turn fdsan back on.
android_fdsan_set_error_level(fdsan_error_level);
}
// We blocked SIGCHLD prior to a fork, we unblock it here.
- if (sigprocmask(SIG_UNBLOCK, &sigchld, nullptr) == -1) {
- fail_fn(CREATE_ERROR("sigprocmask(SIG_SETMASK, { SIGCHLD }) failed: %s", strerror(errno)));
- }
+ UnblockSignal(SIGCHLD, fail_fn);
return pid;
}
@@ -883,10 +1110,9 @@
jstring managed_app_data_dir, jstring managed_package_name,
jobjectArray managed_pacakges_for_uid,
jobjectArray managed_visible_vol_ids) {
- auto fail_fn = std::bind(ZygoteFailure, env, is_system_server ? "system_server" : "zygote",
- managed_nice_name, _1);
- auto extract_fn = std::bind(ExtractJString, env, is_system_server ? "system_server" : "zygote",
- managed_nice_name, _1);
+ const char* process_name = is_system_server ? "system_server" : "zygote";
+ auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1);
+ auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
auto se_info = extract_fn(managed_se_info);
auto nice_name = extract_fn(managed_nice_name);
@@ -894,22 +1120,14 @@
auto app_data_dir = extract_fn(managed_app_data_dir);
auto package_name = extract_fn(managed_package_name);
- std::string error_msg;
-
// Keep capabilities across UID change, unless we're staying root.
if (uid != 0) {
- if (!EnableKeepCapabilities(&error_msg)) {
- fail_fn(error_msg);
- }
+ EnableKeepCapabilities(fail_fn);
}
- if (!SetInheritable(permitted_capabilities, &error_msg)) {
- fail_fn(error_msg);
- }
+ SetInheritable(permitted_capabilities, fail_fn);
- if (!DropCapabilitiesBoundingSet(&error_msg)) {
- fail_fn(error_msg);
- }
+ DropCapabilitiesBoundingSet(fail_fn);
bool use_native_bridge = !is_system_server &&
instruction_set.has_value() &&
@@ -934,56 +1152,21 @@
}
}
- std::vector<std::string> packages_for_uid;
- if (managed_pacakges_for_uid != nullptr) {
- jsize count = env->GetArrayLength(managed_pacakges_for_uid);
- for (jsize package_index = 0; package_index < count; ++package_index) {
- jstring managed_package_for_uid =
- (jstring) env->GetObjectArrayElement(managed_pacakges_for_uid, package_index);
+ std::vector<std::string> packages_for_uid =
+ ExtractJStringArray(env, process_name, managed_nice_name, managed_pacakges_for_uid).
+ value_or(std::vector<std::string>());
- auto package_for_uid = extract_fn(managed_package_for_uid);
- if (LIKELY(package_for_uid.has_value())) {
- packages_for_uid.emplace_back(std::move(package_for_uid.value()));
- } else {
- fail_fn("Null string found in managed packages_for_uid.");
- }
- }
- }
+ std::vector<std::string> visible_vol_ids =
+ ExtractJStringArray(env, process_name, managed_nice_name, managed_visible_vol_ids).
+ value_or(std::vector<std::string>());
- std::vector<std::string> visible_vol_ids;
- if (managed_visible_vol_ids != nullptr) {
- jsize count = env->GetArrayLength(managed_visible_vol_ids);
- for (jsize vol_id_index = 0; vol_id_index < count; ++vol_id_index) {
- jstring managed_visible_vol_id =
- (jstring) env->GetObjectArrayElement(managed_visible_vol_ids, vol_id_index);
-
- auto visible_vol_id = extract_fn(managed_visible_vol_id);
- if (LIKELY(visible_vol_id.has_value())) {
- visible_vol_ids.emplace_back(std::move(visible_vol_id.value()));
- } else {
- fail_fn("Null string found in managed visible_vol_ids.");
- }
- }
- }
-
- if (!MountEmulatedStorage(uid, mount_external, use_native_bridge, &error_msg,
- package_name.value(), packages_for_uid, visible_vol_ids)) {
- ALOGW("Failed to mount emulated storage: %s (%s)", error_msg.c_str(), strerror(errno));
- if (errno == ENOTCONN || errno == EROFS) {
- // When device is actively encrypting, we get ENOTCONN here
- // since FUSE was mounted before the framework restarted.
- // When encrypted device is booting, we get EROFS since
- // FUSE hasn't been created yet by init.
- // In either case, continue without external storage.
- } else {
- fail_fn(error_msg);
- }
- }
+ MountEmulatedStorage(uid, mount_external, use_native_bridge, package_name.value(),
+ packages_for_uid, visible_vol_ids, fail_fn);
// If this zygote isn't root, it won't be able to create a process group,
// since the directory is owned by root.
if (!is_system_server && getuid() == 0) {
- int rc = createProcessGroup(uid, getpid());
+ const int rc = createProcessGroup(uid, getpid());
if (rc == -EROFS) {
ALOGW("createProcessGroup failed, kernel missing CONFIG_CGROUP_CPUACCT?");
} else if (rc != 0) {
@@ -991,13 +1174,8 @@
}
}
- if (!SetGids(env, gids, &error_msg)) {
- fail_fn(error_msg);
- }
-
- if (!SetRLimits(env, rlimits, &error_msg)) {
- fail_fn(error_msg);
- }
+ SetGids(env, gids, fail_fn);
+ SetRLimits(env, rlimits, fail_fn);
if (use_native_bridge) {
// Due to the logic behind use_native_bridge we know that both app_data_dir
@@ -1056,14 +1234,9 @@
}
}
- if (!SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities,
- &error_msg)) {
- fail_fn(error_msg);
- }
+ SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities, fail_fn);
- if (!SetSchedulerPolicy(&error_msg)) {
- fail_fn(error_msg);
- }
+ SetSchedulerPolicy(fail_fn);
const char* se_info_ptr = se_info.has_value() ? se_info.value().c_str() : nullptr;
const char* nice_name_ptr = nice_name.has_value() ? nice_name.value().c_str() : nullptr;
@@ -1076,7 +1249,7 @@
// Make it easier to debug audit logs by setting the main thread's name to the
// nice name rather than "app_process".
if (nice_name.has_value()) {
- SetThreadName(nice_name.value().c_str());
+ SetThreadName(nice_name.value());
} else if (is_system_server) {
SetThreadName("system_server");
}
@@ -1089,6 +1262,7 @@
if (env->ExceptionCheck()) {
fail_fn("Error calling post fork system server hooks.");
}
+
// TODO(oth): Remove hardcoded label here (b/117874058).
static const char* kSystemServerLabel = "u:r:system_server:s0";
if (selinux_android_setcon(kSystemServerLabel) != 0) {
@@ -1192,6 +1366,74 @@
return capabilities & GetEffectiveCapabilityMask(env);
}
+
+/**
+ * Adds the given information about a newly created blastula to the Zygote's
+ * blastula table.
+ *
+ * @param blastula_pid Process ID of the newly created blastula
+ * @param read_pipe_fd File descriptor for the read end of the blastula
+ * reporting pipe. Used in the ZygoteServer poll loop to track blastula
+ * specialization.
+ */
+static void AddBlastulaTableEntry(pid_t blastula_pid, int read_pipe_fd) {
+ static int sBlastulaTableInsertIndex = 0;
+
+ int search_index = sBlastulaTableInsertIndex;
+
+ do {
+ if (gBlastulaTable[search_index].SetIfInvalid(blastula_pid, read_pipe_fd)) {
+ // Start our next search right after where we finished this one.
+ sBlastulaTableInsertIndex = (search_index + 1) % gBlastulaTable.size();
+
+ return;
+ }
+
+ search_index = (search_index + 1) % gBlastulaTable.size();
+ } while (search_index != sBlastulaTableInsertIndex);
+
+ // Much like money in the banana stand, there should always be an entry
+ // in the blastula table.
+ __builtin_unreachable();
+}
+
+/**
+ * Invalidates the entry in the BlastulaTable corresponding to the provided
+ * process ID if it is present. If an entry was removed the blastula pool
+ * count is decremented.
+ *
+ * @param blastula_pid Process ID of the blastula entry to invalidate
+ * @return True if an entry was invalidated; false otherwise
+ */
+static bool RemoveBlastulaTableEntry(pid_t blastula_pid) {
+ for (BlastulaTableEntry& entry : gBlastulaTable) {
+ if (entry.ClearForPID(blastula_pid)) {
+ --gBlastulaPoolCount;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * @return A vector of the read pipe FDs for each of the active blastulas.
+ */
+std::vector<int> MakeBlastulaPipeReadFDVector() {
+ std::vector<int> fd_vec;
+ fd_vec.reserve(gBlastulaTable.size());
+
+ for (BlastulaTableEntry& entry : gBlastulaTable) {
+ auto entry_values = entry.GetValues();
+
+ if (entry_values.has_value()) {
+ fd_vec.push_back(entry_values.value().read_pipe_fd);
+ }
+ }
+
+ return fd_vec;
+}
+
} // anonymous namespace
namespace android {
@@ -1210,12 +1452,35 @@
JNIEnv* env, jclass, jint uid, jint gid, jintArray gids,
jint runtime_flags, jobjectArray rlimits,
jint mount_external, jstring se_info, jstring nice_name,
- jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote,
+ jintArray managed_fds_to_close, jintArray managed_fds_to_ignore, jboolean is_child_zygote,
jstring instruction_set, jstring app_data_dir, jstring package_name,
jobjectArray packages_for_uid, jobjectArray visible_vol_ids) {
jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
+ if (UNLIKELY(managed_fds_to_close == nullptr)) {
+ ZygoteFailure(env, "zygote", nice_name, "Zygote received a null fds_to_close vector.");
+ }
+
+ std::vector<int> fds_to_close =
+ ExtractJIntArray(env, "zygote", nice_name, managed_fds_to_close).value();
+ std::vector<int> fds_to_ignore =
+ ExtractJIntArray(env, "zygote", nice_name, managed_fds_to_ignore)
+ .value_or(std::vector<int>());
+
+ std::vector<int> blastula_pipes = MakeBlastulaPipeReadFDVector();
+
+ 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);
+
+ if (gBlastulaPoolEventFD != -1) {
+ fds_to_close.push_back(gBlastulaPoolEventFD);
+ fds_to_ignore.push_back(gBlastulaPoolEventFD);
+ }
+
pid_t pid = ForkCommon(env, false, fds_to_close, fds_to_ignore);
+
if (pid == 0) {
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
capabilities, capabilities,
@@ -1230,9 +1495,19 @@
JNIEnv* env, jclass, uid_t uid, gid_t gid, jintArray gids,
jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities,
jlong effective_capabilities) {
+ std::vector<int> fds_to_close(MakeBlastulaPipeReadFDVector()),
+ fds_to_ignore(fds_to_close);
+
+// fds_to_close.push_back(gBlastulaPoolSocketFD);
+
+ if (gBlastulaPoolEventFD != -1) {
+ fds_to_close.push_back(gBlastulaPoolEventFD);
+ fds_to_ignore.push_back(gBlastulaPoolEventFD);
+ }
+
pid_t pid = ForkCommon(env, true,
- /* managed_fds_to_close= */ nullptr,
- /* managed_fds_to_ignore= */ nullptr);
+ fds_to_close,
+ fds_to_ignore);
if (pid == 0) {
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
permitted_capabilities, effective_capabilities,
@@ -1266,6 +1541,52 @@
return pid;
}
+/**
+ * A JNI function that forks a blastula from the Zygote while ensuring proper
+ * file descriptor hygiene.
+ *
+ * @param env Managed runtime environment
+ * @param read_pipe_fd The read FD for the blastula reporting pipe. Manually closed by blastlas
+ * in managed code.
+ * @param write_pipe_fd The write FD for the blastula reporting pipe. Manually closed by the
+ * zygote in managed code.
+ * @param managed_session_socket_fds A list of anonymous session sockets that must be ignored by
+ * the FD hygiene code and automatically "closed" in the new blastula.
+ * @return
+ */
+static jint com_android_internal_os_Zygote_nativeForkBlastula(JNIEnv* env, jclass,
+ jint read_pipe_fd, jint write_pipe_fd, jintArray managed_session_socket_fds) {
+ std::vector<int> fds_to_close(MakeBlastulaPipeReadFDVector()),
+ fds_to_ignore(fds_to_close);
+
+ std::vector<int> session_socket_fds =
+ ExtractJIntArray(env, "blastula", nullptr, managed_session_socket_fds)
+ .value_or(std::vector<int>());
+
+ // The Blastula Pool Event FD is created during the initialization of the
+ // blastula pool and should always be valid here.
+
+ fds_to_close.push_back(gZygoteSocketFD);
+ fds_to_close.push_back(gBlastulaPoolEventFD);
+ fds_to_close.insert(fds_to_close.end(), session_socket_fds.begin(), session_socket_fds.end());
+
+ fds_to_ignore.push_back(gZygoteSocketFD);
+ fds_to_ignore.push_back(gBlastulaPoolSocketFD);
+ fds_to_ignore.push_back(gBlastulaPoolEventFD);
+ fds_to_ignore.push_back(read_pipe_fd);
+ fds_to_ignore.push_back(write_pipe_fd);
+ fds_to_ignore.insert(fds_to_ignore.end(), session_socket_fds.begin(), session_socket_fds.end());
+
+ pid_t blastula_pid = ForkCommon(env, /* is_system_server= */ false, fds_to_close, fds_to_ignore);
+
+ if (blastula_pid != 0) {
+ ++gBlastulaPoolCount;
+ AddBlastulaTableEntry(blastula_pid, read_pipe_fd);
+ }
+
+ return blastula_pid;
+}
+
static void com_android_internal_os_Zygote_nativeAllowFileAcrossFork(
JNIEnv* env, jclass, jstring path) {
ScopedUtfChars path_native(env, path);
@@ -1321,14 +1642,125 @@
return;
}
- // TODO(b/111434506) install the filter
-
- /*
bool installed = install_setuidgid_seccomp_filter(uidGidMin, uidGidMax);
if (!installed) {
RuntimeAbort(env, __LINE__, "Could not install setuid/setgid seccomp filter.");
}
- */
+}
+
+/**
+ * Called from a blastula to specialize the process for a specific application.
+ *
+ * @param env Managed runtime environment
+ * @param uid User ID of the new application
+ * @param gid Group ID of the new application
+ * @param gids Extra groups that the process belongs to
+ * @param runtime_flags Flags for changing the behavior of the managed runtime
+ * @param rlimits Resource limits
+ * @param mount_external The mode (read/write/normal) that external storage will be mounted with
+ * @param se_info SELinux policy information
+ * @param nice_name New name for this process
+ * @param is_child_zygote If the process is to become a WebViewZygote
+ * @param instruction_set The instruction set expected/requested by the new application
+ * @param app_data_dir Path to the application's data directory
+ */
+static void com_android_internal_os_Zygote_nativeSpecializeBlastula(
+ JNIEnv* env, jclass, jint uid, jint gid, jintArray gids,
+ jint runtime_flags, jobjectArray rlimits,
+ jint mount_external, jstring se_info, jstring nice_name,
+ jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir,
+ jstring package_name, jobjectArray packages_for_uid, jobjectArray visible_vol_ids) {
+ jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
+
+ SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
+ capabilities, capabilities,
+ mount_external, se_info, nice_name, false,
+ is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
+ package_name, packages_for_uid, visible_vol_ids);
+}
+
+/**
+ * A helper method for fetching socket file descriptors that were opened by init from the
+ * environment.
+ *
+ * @param env Managed runtime environment
+ * @param is_primary If this process is the primary or secondary Zygote; used to compute the name
+ * of the environment variable storing the file descriptors.
+ */
+static void com_android_internal_os_Zygote_nativeGetSocketFDs(JNIEnv* env, jclass,
+ jboolean is_primary) {
+ std::string android_socket_prefix(ANDROID_SOCKET_PREFIX);
+ std::string env_var_name = android_socket_prefix + (is_primary ? "zygote" : "zygote_secondary");
+ char* env_var_val = getenv(env_var_name.c_str());
+
+ if (env_var_val != nullptr) {
+ gZygoteSocketFD = atoi(env_var_val);
+ ALOGV("Zygote:zygoteSocketFD = %d", gZygoteSocketFD);
+ } else {
+ ALOGE("Unable to fetch Zygote socket file descriptor");
+ }
+
+ env_var_name = android_socket_prefix + (is_primary ? "blastula_pool" : "blastula_pool_secondary");
+ env_var_val = getenv(env_var_name.c_str());
+
+ if (env_var_val != nullptr) {
+ gBlastulaPoolSocketFD = atoi(env_var_val);
+ ALOGV("Zygote:blastulaPoolSocketFD = %d", gBlastulaPoolSocketFD);
+ } else {
+ ALOGE("Unable to fetch Blastula pool socket file descriptor");
+ }
+}
+
+/**
+ * @param env Managed runtime environment
+ * @return A managed array of raw file descriptors for the read ends of the blastula reporting
+ * pipes.
+ */
+static jintArray com_android_internal_os_Zygote_nativeGetBlastulaPipeFDs(JNIEnv* env, jclass) {
+ std::vector<int> blastula_fds = MakeBlastulaPipeReadFDVector();
+
+ jintArray managed_blastula_fds = env->NewIntArray(blastula_fds.size());
+ env->SetIntArrayRegion(managed_blastula_fds, 0, blastula_fds.size(), blastula_fds.data());
+
+ return managed_blastula_fds;
+}
+
+/**
+ * A JNI wrapper around RemoveBlastulaTableEntry.
+ *
+ * @param env Managed runtime environment
+ * @param blastula_pid Process ID of the blastula entry to invalidate
+ * @return True if an entry was invalidated; false otherwise.
+ */
+static jboolean com_android_internal_os_Zygote_nativeRemoveBlastulaTableEntry(JNIEnv* env, jclass,
+ jint blastula_pid) {
+ return RemoveBlastulaTableEntry(blastula_pid);
+}
+
+/**
+ * Creates the blastula pool event FD if it doesn't exist and returns it. This is used by the
+ * ZygoteServer poll loop to know when to re-fill the blastula pool.
+ *
+ * @param env Managed runtime environment
+ * @return A raw event file descriptor used to communicate (from the signal handler) when the
+ * Zygote receives a SIGCHLD for a blastula
+ */
+static jint com_android_internal_os_Zygote_nativeGetBlastulaPoolEventFD(JNIEnv* env, jclass) {
+ if (gBlastulaPoolEventFD == -1) {
+ if ((gBlastulaPoolEventFD = eventfd(0, 0)) == -1) {
+ ZygoteFailure(env, "zygote", nullptr, StringPrintf("Unable to create eventfd: %s", strerror(errno)));
+ }
+ }
+
+ return gBlastulaPoolEventFD;
+}
+
+/**
+ * @param env Managed runtime environment
+ * @return The number of blastulas currently in the blastula pool
+ */
+static jint com_android_internal_os_Zygote_nativeGetBlastulaPoolCount(JNIEnv* env, jclass) {
+ return gBlastulaPoolCount;
}
static const JNINativeMethod gMethods[] = {
@@ -1346,7 +1778,22 @@
{ "nativePreApplicationInit", "()V",
(void *) com_android_internal_os_Zygote_nativePreApplicationInit },
{ "nativeInstallSeccompUidGidFilter", "(II)V",
- (void *) com_android_internal_os_Zygote_nativeInstallSeccompUidGidFilter }
+ (void *) com_android_internal_os_Zygote_nativeInstallSeccompUidGidFilter },
+ { "nativeForkBlastula", "(II[I)I",
+ (void *) com_android_internal_os_Zygote_nativeForkBlastula },
+ { "nativeSpecializeBlastula",
+ "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
+ (void *) com_android_internal_os_Zygote_nativeSpecializeBlastula },
+ { "nativeGetSocketFDs", "(Z)V",
+ (void *) com_android_internal_os_Zygote_nativeGetSocketFDs },
+ { "nativeGetBlastulaPipeFDs", "()[I",
+ (void *) com_android_internal_os_Zygote_nativeGetBlastulaPipeFDs },
+ { "nativeRemoveBlastulaTableEntry", "(I)Z",
+ (void *) com_android_internal_os_Zygote_nativeRemoveBlastulaTableEntry },
+ { "nativeGetBlastulaPoolEventFD", "()I",
+ (void *) com_android_internal_os_Zygote_nativeGetBlastulaPoolEventFD },
+ { "nativeGetBlastulaPoolCount", "()I",
+ (void *) com_android_internal_os_Zygote_nativeGetBlastulaPoolCount }
};
int register_com_android_internal_os_Zygote(JNIEnv* env) {
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 41e00b9..53dde80 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -34,9 +34,12 @@
// Static whitelist of open paths that the zygote is allowed to keep open.
static const char* kPathWhitelist[] = {
"/apex/com.android.conscrypt/javalib/conscrypt.jar",
+ "/apex/com.android.media/javalib/updatable-media.jar",
"/dev/null",
"/dev/socket/zygote",
"/dev/socket/zygote_secondary",
+ "/dev/socket/blastula_pool",
+ "/dev/socket/blastula_pool_secondary",
"/dev/socket/webview_zygote",
"/dev/socket/heapprofd",
"/sys/kernel/debug/tracing/trace_marker",
@@ -133,15 +136,14 @@
// open zygote file descriptor.
class FileDescriptorInfo {
public:
- // Create a FileDescriptorInfo for a given file descriptor. Returns
- // |NULL| if an error occurred.
- static FileDescriptorInfo* CreateFromFd(int fd, std::string* error_msg);
+ // Create a FileDescriptorInfo for a given file descriptor.
+ static FileDescriptorInfo* CreateFromFd(int fd, fail_fn_t fail_fn);
// Checks whether the file descriptor associated with this object
// refers to the same description.
- bool Restat() const;
+ bool RefersToSameFile() const;
- bool ReopenOrDetach(std::string* error_msg) const;
+ void ReopenOrDetach(fail_fn_t fail_fn) const;
const int fd;
const struct stat stat;
@@ -167,19 +169,18 @@
// address).
static bool GetSocketName(const int fd, std::string* result);
- bool DetachSocket(std::string* error_msg) const;
+ void DetachSocket(fail_fn_t fail_fn) const;
DISALLOW_COPY_AND_ASSIGN(FileDescriptorInfo);
};
// static
-FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, std::string* error_msg) {
+FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, fail_fn_t fail_fn) {
struct stat f_stat;
// This should never happen; the zygote should always have the right set
// of permissions required to stat all its open files.
if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) {
- *error_msg = android::base::StringPrintf("Unable to stat %d", fd);
- return nullptr;
+ fail_fn(android::base::StringPrintf("Unable to stat %d", fd));
}
const FileDescriptorWhitelist* whitelist = FileDescriptorWhitelist::Get();
@@ -187,15 +188,13 @@
if (S_ISSOCK(f_stat.st_mode)) {
std::string socket_name;
if (!GetSocketName(fd, &socket_name)) {
- *error_msg = "Unable to get socket name";
- return nullptr;
+ fail_fn("Unable to get socket name");
}
if (!whitelist->IsAllowed(socket_name)) {
- *error_msg = android::base::StringPrintf("Socket name not whitelisted : %s (fd=%d)",
- socket_name.c_str(),
- fd);
- return nullptr;
+ fail_fn(android::base::StringPrintf("Socket name not whitelisted : %s (fd=%d)",
+ socket_name.c_str(),
+ fd));
}
return new FileDescriptorInfo(fd);
@@ -208,26 +207,35 @@
// S_ISDIR : Not supported. (We could if we wanted to, but it's unused).
// S_ISLINK : Not supported.
// S_ISBLK : Not supported.
- // S_ISFIFO : Not supported. Note that the zygote uses pipes to communicate
- // with the child process across forks but those should have been closed
- // before we got to this point.
+ // S_ISFIFO : Not supported. Note that the Zygote and blastulas use pipes to
+ // communicate with the child processes across forks but those should have been
+ // added to the redirection exemption list.
if (!S_ISCHR(f_stat.st_mode) && !S_ISREG(f_stat.st_mode)) {
- *error_msg = android::base::StringPrintf("Unsupported st_mode %u", f_stat.st_mode);
- return nullptr;
+ std::string mode = "Unknown";
+
+ if (S_ISDIR(f_stat.st_mode)) {
+ mode = "DIR";
+ } else if (S_ISLNK(f_stat.st_mode)) {
+ mode = "LINK";
+ } else if (S_ISBLK(f_stat.st_mode)) {
+ mode = "BLOCK";
+ } else if (S_ISFIFO(f_stat.st_mode)) {
+ mode = "FIFO";
+ }
+
+ fail_fn(android::base::StringPrintf("Unsupported st_mode for FD %d: %s", fd, mode.c_str()));
}
std::string file_path;
const std::string fd_path = android::base::StringPrintf("/proc/self/fd/%d", fd);
if (!android::base::Readlink(fd_path, &file_path)) {
- *error_msg = android::base::StringPrintf("Could not read fd link %s: %s",
- fd_path.c_str(),
- strerror(errno));
- return nullptr;
+ fail_fn(android::base::StringPrintf("Could not read fd link %s: %s",
+ fd_path.c_str(),
+ strerror(errno)));
}
if (!whitelist->IsAllowed(file_path)) {
- *error_msg = std::string("Not whitelisted : ").append(file_path);
- return nullptr;
+ fail_fn(std::string("Not whitelisted : ").append(file_path));
}
// File descriptor flags : currently on FD_CLOEXEC. We can set these
@@ -235,11 +243,10 @@
// there won't be any races.
const int fd_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD));
if (fd_flags == -1) {
- *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_GETFD) (%s): %s",
- fd,
- file_path.c_str(),
- strerror(errno));
- return nullptr;
+ fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_GETFD) (%s): %s",
+ fd,
+ file_path.c_str(),
+ strerror(errno)));
}
// File status flags :
@@ -256,11 +263,10 @@
// their presence and pass them in to open().
int fs_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL));
if (fs_flags == -1) {
- *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_GETFL) (%s): %s",
- fd,
- file_path.c_str(),
- strerror(errno));
- return nullptr;
+ fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_GETFL) (%s): %s",
+ fd,
+ file_path.c_str(),
+ strerror(errno)));
}
// File offset : Ignore the offset for non seekable files.
@@ -275,7 +281,7 @@
return new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags, offset);
}
-bool FileDescriptorInfo::Restat() const {
+bool FileDescriptorInfo::RefersToSameFile() const {
struct stat f_stat;
if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) {
PLOG(ERROR) << "Unable to restat fd " << fd;
@@ -285,9 +291,9 @@
return f_stat.st_ino == stat.st_ino && f_stat.st_dev == stat.st_dev;
}
-bool FileDescriptorInfo::ReopenOrDetach(std::string* error_msg) const {
+void FileDescriptorInfo::ReopenOrDetach(fail_fn_t fail_fn) const {
if (is_sock) {
- return DetachSocket(error_msg);
+ return DetachSocket(fail_fn);
}
// NOTE: This might happen if the file was unlinked after being opened.
@@ -296,57 +302,50 @@
const int new_fd = TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags));
if (new_fd == -1) {
- *error_msg = android::base::StringPrintf("Failed open(%s, %i): %s",
- file_path.c_str(),
- open_flags,
- strerror(errno));
- return false;
+ fail_fn(android::base::StringPrintf("Failed open(%s, %i): %s",
+ file_path.c_str(),
+ open_flags,
+ strerror(errno)));
}
if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFD, fd_flags)) == -1) {
close(new_fd);
- *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_SETFD, %d) (%s): %s",
- new_fd,
- fd_flags,
- file_path.c_str(),
- strerror(errno));
- return false;
+ fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_SETFD, %d) (%s): %s",
+ new_fd,
+ fd_flags,
+ file_path.c_str(),
+ strerror(errno)));
}
if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFL, fs_flags)) == -1) {
close(new_fd);
- *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_SETFL, %d) (%s): %s",
- new_fd,
- fs_flags,
- file_path.c_str(),
- strerror(errno));
- return false;
+ fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_SETFL, %d) (%s): %s",
+ new_fd,
+ fs_flags,
+ file_path.c_str(),
+ strerror(errno)));
}
if (offset != -1 && TEMP_FAILURE_RETRY(lseek64(new_fd, offset, SEEK_SET)) == -1) {
close(new_fd);
- *error_msg = android::base::StringPrintf("Failed lseek64(%d, SEEK_SET) (%s): %s",
- new_fd,
- file_path.c_str(),
- strerror(errno));
- return false;
+ fail_fn(android::base::StringPrintf("Failed lseek64(%d, SEEK_SET) (%s): %s",
+ new_fd,
+ file_path.c_str(),
+ strerror(errno)));
}
- int dupFlags = (fd_flags & FD_CLOEXEC) ? O_CLOEXEC : 0;
- if (TEMP_FAILURE_RETRY(dup3(new_fd, fd, dupFlags)) == -1) {
+ int dup_flags = (fd_flags & FD_CLOEXEC) ? O_CLOEXEC : 0;
+ if (TEMP_FAILURE_RETRY(dup3(new_fd, fd, dup_flags)) == -1) {
close(new_fd);
- *error_msg = android::base::StringPrintf("Failed dup3(%d, %d, %d) (%s): %s",
- fd,
- new_fd,
- dupFlags,
- file_path.c_str(),
- strerror(errno));
- return false;
+ fail_fn(android::base::StringPrintf("Failed dup3(%d, %d, %d) (%s): %s",
+ fd,
+ new_fd,
+ dup_flags,
+ file_path.c_str(),
+ strerror(errno)));
}
close(new_fd);
-
- return true;
}
FileDescriptorInfo::FileDescriptorInfo(int fd) :
@@ -372,7 +371,6 @@
is_sock(false) {
}
-// static
bool FileDescriptorInfo::GetSocketName(const int fd, std::string* result) {
sockaddr_storage ss;
sockaddr* addr = reinterpret_cast<sockaddr*>(&ss);
@@ -416,86 +414,75 @@
return true;
}
-bool FileDescriptorInfo::DetachSocket(std::string* error_msg) const {
+void FileDescriptorInfo::DetachSocket(fail_fn_t fail_fn) const {
const int dev_null_fd = open("/dev/null", O_RDWR);
if (dev_null_fd < 0) {
- *error_msg = std::string("Failed to open /dev/null: ").append(strerror(errno));
- return false;
+ fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno)));
}
if (dup2(dev_null_fd, fd) == -1) {
- *error_msg = android::base::StringPrintf("Failed dup2 on socket descriptor %d: %s",
- fd,
- strerror(errno));
- return false;
+ fail_fn(android::base::StringPrintf("Failed dup2 on socket descriptor %d: %s",
+ fd,
+ strerror(errno)));
}
if (close(dev_null_fd) == -1) {
- *error_msg = android::base::StringPrintf("Failed close(%d): %s", dev_null_fd, strerror(errno));
- return false;
+ fail_fn(android::base::StringPrintf("Failed close(%d): %s", dev_null_fd, strerror(errno)));
}
-
- return true;
}
// static
FileDescriptorTable* FileDescriptorTable::Create(const std::vector<int>& fds_to_ignore,
- std::string* error_msg) {
- DIR* d = opendir(kFdPath);
- if (d == nullptr) {
- *error_msg = std::string("Unable to open directory ").append(kFdPath);
- return nullptr;
+ fail_fn_t fail_fn) {
+ DIR* proc_fd_dir = opendir(kFdPath);
+ if (proc_fd_dir == nullptr) {
+ fail_fn(std::string("Unable to open directory ").append(kFdPath));
}
- int dir_fd = dirfd(d);
- dirent* e;
+
+ int dir_fd = dirfd(proc_fd_dir);
+ dirent* dir_entry;
std::unordered_map<int, FileDescriptorInfo*> open_fd_map;
- while ((e = readdir(d)) != NULL) {
- const int fd = ParseFd(e, dir_fd);
+ while ((dir_entry = readdir(proc_fd_dir)) != nullptr) {
+ const int fd = ParseFd(dir_entry, dir_fd);
if (fd == -1) {
continue;
}
+
if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) {
LOG(INFO) << "Ignoring open file descriptor " << fd;
continue;
}
- FileDescriptorInfo* info = FileDescriptorInfo::CreateFromFd(fd, error_msg);
- if (info == NULL) {
- if (closedir(d) == -1) {
- PLOG(ERROR) << "Unable to close directory";
- }
- return NULL;
- }
- open_fd_map[fd] = info;
+ open_fd_map[fd] = FileDescriptorInfo::CreateFromFd(fd, fail_fn);
}
- if (closedir(d) == -1) {
- *error_msg = "Unable to close directory";
- return nullptr;
+ if (closedir(proc_fd_dir) == -1) {
+ fail_fn("Unable to close directory");
}
+
return new FileDescriptorTable(open_fd_map);
}
-bool FileDescriptorTable::Restat(const std::vector<int>& fds_to_ignore, std::string* error_msg) {
+void FileDescriptorTable::Restat(const std::vector<int>& fds_to_ignore, fail_fn_t fail_fn) {
std::set<int> open_fds;
// First get the list of open descriptors.
- DIR* d = opendir(kFdPath);
- if (d == NULL) {
- *error_msg = android::base::StringPrintf("Unable to open directory %s: %s",
- kFdPath,
- strerror(errno));
- return false;
+ DIR* proc_fd_dir = opendir(kFdPath);
+ if (proc_fd_dir == nullptr) {
+ fail_fn(android::base::StringPrintf("Unable to open directory %s: %s",
+ kFdPath,
+ strerror(errno)));
}
- int dir_fd = dirfd(d);
- dirent* e;
- while ((e = readdir(d)) != NULL) {
- const int fd = ParseFd(e, dir_fd);
+ int dir_fd = dirfd(proc_fd_dir);
+ dirent* dir_entry;
+ while ((dir_entry = readdir(proc_fd_dir)) != nullptr) {
+ const int fd = ParseFd(dir_entry, dir_fd);
if (fd == -1) {
continue;
}
+
if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) {
LOG(INFO) << "Ignoring open file descriptor " << fd;
continue;
@@ -504,27 +491,24 @@
open_fds.insert(fd);
}
- if (closedir(d) == -1) {
- *error_msg = android::base::StringPrintf("Unable to close directory: %s", strerror(errno));
- return false;
+ if (closedir(proc_fd_dir) == -1) {
+ fail_fn(android::base::StringPrintf("Unable to close directory: %s", strerror(errno)));
}
- return RestatInternal(open_fds, error_msg);
+ RestatInternal(open_fds, fail_fn);
}
-// Reopens all file descriptors that are contained in the table. Returns true
-// if all descriptors were successfully re-opened or detached, and false if an
-// error occurred.
-bool FileDescriptorTable::ReopenOrDetach(std::string* error_msg) {
+// Reopens all file descriptors that are contained in the table.
+void FileDescriptorTable::ReopenOrDetach(fail_fn_t fail_fn) {
std::unordered_map<int, FileDescriptorInfo*>::const_iterator it;
for (it = open_fd_map_.begin(); it != open_fd_map_.end(); ++it) {
const FileDescriptorInfo* info = it->second;
- if (info == NULL || !info->ReopenOrDetach(error_msg)) {
- return false;
+ if (info == nullptr) {
+ return;
+ } else {
+ info->ReopenOrDetach(fail_fn);
}
}
-
- return true;
}
FileDescriptorTable::FileDescriptorTable(
@@ -532,9 +516,7 @@
: open_fd_map_(map) {
}
-bool FileDescriptorTable::RestatInternal(std::set<int>& open_fds, std::string* error_msg) {
- bool error = false;
-
+void FileDescriptorTable::RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn) {
// Iterate through the list of file descriptors we've already recorded
// and check whether :
//
@@ -557,28 +539,18 @@
} else {
// The entry from the file descriptor table is still open. Restat
// it and check whether it refers to the same file.
- const bool same_file = it->second->Restat();
- if (!same_file) {
+ if (!it->second->RefersToSameFile()) {
// The file descriptor refers to a different description. We must
// update our entry in the table.
delete it->second;
- it->second = FileDescriptorInfo::CreateFromFd(*element, error_msg);
- if (it->second == NULL) {
- // The descriptor no longer no longer refers to a whitelisted file.
- // We flag an error and remove it from the list of files we're
- // tracking.
- error = true;
- it = open_fd_map_.erase(it);
- } else {
- // Successfully restatted the file, move on to the next open FD.
- ++it;
- }
+ it->second = FileDescriptorInfo::CreateFromFd(*element, fail_fn);
} else {
// It's the same file. Nothing to do here. Move on to the next open
// FD.
- ++it;
}
+ ++it;
+
// Finally, remove the FD from the set of open_fds. We do this last because
// |element| will not remain valid after a call to erase.
open_fds.erase(element);
@@ -597,25 +569,15 @@
std::set<int>::const_iterator it;
for (it = open_fds.begin(); it != open_fds.end(); ++it) {
const int fd = (*it);
- FileDescriptorInfo* info = FileDescriptorInfo::CreateFromFd(fd, error_msg);
- if (info == NULL) {
- // A newly opened file is not on the whitelist. Flag an error and
- // continue.
- error = true;
- } else {
- // Track the newly opened file.
- open_fd_map_[fd] = info;
- }
+ open_fd_map_[fd] = FileDescriptorInfo::CreateFromFd(fd, fail_fn);
}
}
-
- return !error;
}
// static
-int FileDescriptorTable::ParseFd(dirent* e, int dir_fd) {
+int FileDescriptorTable::ParseFd(dirent* dir_entry, int dir_fd) {
char* end;
- const int fd = strtol(e->d_name, &end, 10);
+ const int fd = strtol(dir_entry->d_name, &end, 10);
if ((*end) != '\0') {
return -1;
}
diff --git a/core/jni/fd_utils.h b/core/jni/fd_utils.h
index 09022a2..2caf157 100644
--- a/core/jni/fd_utils.h
+++ b/core/jni/fd_utils.h
@@ -30,6 +30,9 @@
class FileDescriptorInfo;
+// This type is duplicated in com_android_internal_os_Zygote.cpp
+typedef const std::function<void(std::string)>& fail_fn_t;
+
// Whitelist of open paths that the zygote is allowed to keep open.
//
// In addition to the paths listed in kPathWhitelist in file_utils.cpp, and
@@ -76,19 +79,19 @@
// /proc/self/fd for the list of open file descriptors and collects
// information about them. Returns NULL if an error occurs.
static FileDescriptorTable* Create(const std::vector<int>& fds_to_ignore,
- std::string* error_msg);
+ fail_fn_t fail_fn);
- bool Restat(const std::vector<int>& fds_to_ignore, std::string* error_msg);
+ void Restat(const std::vector<int>& fds_to_ignore, fail_fn_t fail_fn);
// Reopens all file descriptors that are contained in the table. Returns true
// if all descriptors were successfully re-opened or detached, and false if an
// error occurred.
- bool ReopenOrDetach(std::string* error_msg);
+ void ReopenOrDetach(fail_fn_t fail_fn);
private:
explicit FileDescriptorTable(const std::unordered_map<int, FileDescriptorInfo*>& map);
- bool RestatInternal(std::set<int>& open_fds, std::string* error_msg);
+ void RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn);
static int ParseFd(dirent* e, int dir_fd);
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index f68c760..6cdba33 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2179,4 +2179,8 @@
// OPEN: Settings > Network & internet > Click Mobile network to land on a page with a list of
// SIM/eSIM subscriptions.
MOBILE_NETWORK_LIST = 1627;
+
+ // OPEN: Settings > Display > Adaptive sleep
+ // OS: Q
+ SETTINGS_ADAPTIVE_SLEEP = 1628;
}
diff --git a/core/proto/android/debug/enums.proto b/core/proto/android/debug/enums.proto
new file mode 100644
index 0000000..6747bb7
--- /dev/null
+++ b/core/proto/android/debug/enums.proto
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+package android.debug;
+
+option java_outer_classname = "AdbProtoEnums";
+option java_multiple_files = true;
+
+/**
+ * adb connection state used to track adb connection changes in AdbDebuggingManager.java.
+ */
+enum AdbConnectionStateEnum {
+ UNKNOWN = 0;
+
+ /**
+ * The adb connection is waiting for approval from the user.
+ */
+ AWAITING_USER_APPROVAL = 1;
+
+ /**
+ * The user allowed the adb connection from the system.
+ */
+ USER_ALLOWED = 2;
+
+ /**
+ * The user denied the adb connection from the system.
+ */
+ USER_DENIED = 3;
+
+ /**
+ * The adb connection was automatically allowed without user interaction due to the system
+ * being previously allowed by the user with the 'always allow' option selected, and the adb
+ * grant has not yet expired.
+ */
+ AUTOMATICALLY_ALLOWED = 4;
+
+ /**
+ * An empty or invalid base64 encoded key was provided to the framework; the connection was
+ * automatically denied.
+ */
+ DENIED_INVALID_KEY = 5;
+
+ /**
+ * vold decrypt has not yet occurred; the connection was automatically denied.
+ */
+ DENIED_VOLD_DECRYPT = 6;
+
+ /**
+ * The adb session has been disconnected.
+ */
+ DISCONNECTED = 7;
+}
+
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index 7f3ea7a..e68f9db 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -221,6 +221,8 @@
optional bool use_heartbeats = 23;
message TimeController {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Whether or not TimeController should skip setting wakeup alarms for jobs that aren't
// ready now.
optional bool skip_not_ready_jobs = 1;
@@ -228,6 +230,8 @@
optional TimeController time_controller = 25;
message QuotaController {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
// How much time each app will have to run jobs within their standby bucket window.
optional int64 allowed_time_per_period_ms = 1;
// How much time the package should have before transitioning from out-of-quota to in-quota.
@@ -251,6 +255,21 @@
optional int64 rare_window_size_ms = 6;
// The maximum amount of time an app can have its jobs running within a 24 hour window.
optional int64 max_execution_time_ms = 7;
+ // The maximum number of jobs an app can run within this particular standby bucket's
+ // window size.
+ optional int32 max_job_count_active = 8;
+ // The maximum number of jobs an app can run within this particular standby bucket's
+ // window size.
+ optional int32 max_job_count_working = 9;
+ // The maximum number of jobs an app can run within this particular standby bucket's
+ // window size.
+ optional int32 max_job_count_frequent = 10;
+ // The maximum number of jobs an app can run within this particular standby bucket's
+ // window size.
+ optional int32 max_job_count_rare = 11;
+ // The maximum number of jobs that should be allowed to run in the past
+ // {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS}.
+ optional int32 max_job_count_per_allowed_time = 12;
}
optional QuotaController quota_controller = 24;
diff --git a/core/proto/android/server/usagestatsservice.proto b/core/proto/android/server/usagestatsservice.proto
index 528c1a4..050ec7a 100644
--- a/core/proto/android/server/usagestatsservice.proto
+++ b/core/proto/android/server/usagestatsservice.proto
@@ -88,6 +88,11 @@
// If class field is an Activity, instance_id is a unique id of the
// Activity object.
optional int32 instance_id = 14;
+ // task_root_package_index contains the index + 1 of the task root package name in the string
+ // pool
+ optional int32 task_root_package_index = 15;
+ // task_root_class_index contains the index + 1 of the task root class name in the string pool
+ optional int32 task_root_class_index = 16;
}
// The following fields contain supplemental data used to build IntervalStats, such as a string
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ea0c8e2..96b8dc2 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1748,6 +1748,10 @@
<permission android:name="android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED"
android:protectionLevel="signature" />
+ <!-- @hide Allows the device to be reset, clearing all data and enables Test Harness Mode. -->
+ <permission android:name="android.permission.ENABLE_TEST_HARNESS_MODE"
+ android:protectionLevel="signature" />
+
<!-- ================================== -->
<!-- Permissions for accessing accounts -->
<!-- ================================== -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5995640..1a21306 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -381,7 +381,7 @@
<!-- Configuration of Ethernet interfaces in the following format:
- <interface name|mac address>;[Network Capabilities];[IP config]
+ <interface name|mac address>;[Network Capabilities];[IP config];[Override Transport]
Where
[Network Capabilities] Optional. A comma seprated list of network capabilities.
Values must be from NetworkCapabilities#NET_CAPABILITIES_* constants.
@@ -389,11 +389,16 @@
use the following format to specify static IP configuration:
ip=<ip-address/mask> gateway=<ip-address> dns=<comma-sep-ip-addresses>
domains=<comma-sep-domains>
+ [Override Transport] Optional. An override network transport type to allow
+ the propagation of an interface type on the other end of a local Ethernet
+ interface. Value must be from NetworkCapabilities#TRANSPORT_* constants. If
+ left out, this will default to TRANSPORT_ETHERNET.
-->
<string-array translatable="false" name="config_ethernet_interfaces">
<!--
<item>eth1;12,13,14,15;ip=192.168.0.10/24 gateway=192.168.0.1 dns=4.4.4.4,8.8.8.8</item>
<item>eth2;;ip=192.168.0.11/24</item>
+ <item>eth3;12,13,14,15;ip=192.168.0.12/24;1</item>
-->
</string-array>
@@ -525,6 +530,9 @@
<!-- Boolean indicating whether the wifi chipset has dual frequency band support -->
<bool translatable="false" name="config_wifi_dual_band_support">false</bool>
+ <!-- Maximum number of concurrent WiFi interfaces in AP mode -->
+ <integer translatable="false" name="config_wifi_max_ap_interfaces">1</integer>
+
<!-- Boolean indicating whether the wifi chipset requires the softap band be -->
<!-- converted from 5GHz to ANY due to hardware restrictions -->
<bool translatable="false" name="config_wifi_convert_apband_5ghz_to_any">false</bool>
@@ -705,6 +713,9 @@
Software implementation will be used if config_hardware_auto_brightness_available is not set -->
<bool name="config_automatic_brightness_available">false</bool>
+ <!-- Flag indicating whether we should enable the adaptive sleep.-->
+ <bool name="config_adaptive_sleep_available">false</bool>
+
<!-- Flag indicating whether we should enable smart battery. -->
<bool name="config_smart_battery_available">false</bool>
@@ -931,6 +942,10 @@
in hardware. -->
<bool name="config_setColorTransformAccelerated">false</bool>
+ <!-- Boolean indicating whether the HWC setColorTransform function can be performed efficiently
+ in hardware for individual layers. -->
+ <bool name="config_setColorTransformAcceleratedPerLayer">false</bool>
+
<!-- Control whether Night display is available. This should only be enabled on devices
that have a HWC implementation that can apply the matrix passed to setColorTransform
without impacting power, performance, and app compatibility (e.g. protected content). -->
@@ -1743,6 +1758,19 @@
config_enableGeofenceOverlay is false. -->
<string name="config_geofenceProviderPackageName" translatable="false">@null</string>
+ <!-- Whether to enable Hardware Activity-Recognition overlay which allows Hardware
+ Activity-Recognition to be replaced by an app at run-time. When disabled, only the
+ config_activityRecognitionHardwarePackageName package will be searched for
+ its implementation, otherwise packages whose signature matches the
+ signatures of config_locationProviderPackageNames will be searched, and
+ the service with the highest version number will be picked. Anyone who
+ wants to disable the overlay mechanism can set it to false.
+ -->
+ <bool name="config_enableActivityRecognitionHardwareOverlay" translatable="false">true</bool>
+ <!-- Package name providing Hardware Activity-Recognition API support. Used only when
+ config_enableActivityRecognitionHardwareOverlay is false. -->
+ <string name="config_activityRecognitionHardwarePackageName" translatable="false">@null</string>
+
<!-- Package name(s) containing location provider support.
These packages can contain services implementing location providers,
such as the Geocode Provider, Network Location Provider, and
@@ -3728,4 +3756,7 @@
<!-- Enable Zram writeback feature to allow unused pages in zram be written to flash. -->
<bool name="config_zramWriteback">false</bool>
+
+ <!-- Whether cbrs is supported on the device or not -->
+ <bool translatable="false" name="config_cbrs_supported">false</bool>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 0878562..f2b4b9c 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3353,9 +3353,9 @@
<string name="wifi_available_action_all_networks">All networks</string>
<!-- Notification title for a connection to a app suggested wireless network.-->
- <string name="wifi_suggestion_title">Connected to Wi\u2011Fi network proposed by <xliff:g id="name" example="App123">%s</xliff:g></string>
+ <string name="wifi_suggestion_title">A Wi\u2011Fi network proposed by <xliff:g id="name" example="App123">%s</xliff:g> is available</string>
<!-- Notification content for a connection to a app suggested wireless network.-->
- <string name="wifi_suggestion_content">Do you want to let <xliff:g id="name" example="App123">%s</xliff:g> propose networks for you?</string>
+ <string name="wifi_suggestion_content">Do you want to connect to networks proposed by <xliff:g id="name" example="App123">%s</xliff:g>?</string>
<!-- Notification action for allowing app specified in the notification body.-->
<string name="wifi_suggestion_action_allow_app">Yes</string>
<!-- Notification action for disallowing app specified in the notification body.-->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 53c33a3..83982c7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1845,6 +1845,7 @@
<java-symbol type="array" name="config_defaultNotificationVibePattern" />
<java-symbol type="array" name="config_notificationFallbackVibePattern" />
<java-symbol type="bool" name="config_useAttentionLight" />
+ <java-symbol type="bool" name="config_adaptive_sleep_available" />
<java-symbol type="bool" name="config_animateScreenLights" />
<java-symbol type="bool" name="config_automatic_brightness_available" />
<java-symbol type="bool" name="config_smart_battery_available" />
@@ -1853,6 +1854,7 @@
<java-symbol type="bool" name="config_enableNightMode" />
<java-symbol type="bool" name="config_tintNotificationActionButtons" />
<java-symbol type="bool" name="config_dozeAfterScreenOffByDefault" />
+ <java-symbol type="bool" name="config_enableActivityRecognitionHardwareOverlay" />
<java-symbol type="bool" name="config_enableFusedLocationOverlay" />
<java-symbol type="bool" name="config_enableHardwareFlpOverlay" />
<java-symbol type="bool" name="config_enableGeocoderOverlay" />
@@ -1877,6 +1879,7 @@
<java-symbol type="bool" name="config_supportLongPressPowerWhenNonInteractive" />
<java-symbol type="bool" name="config_wifi_background_scan_support" />
<java-symbol type="bool" name="config_wifi_dual_band_support" />
+ <java-symbol type="integer" name="config_wifi_max_ap_interfaces" />
<java-symbol type="bool" name="config_wifi_convert_apband_5ghz_to_any" />
<java-symbol type="bool" name="config_wifi_local_only_hotspot_5ghz" />
<java-symbol type="bool" name="config_wifi_connected_mac_randomization_supported" />
@@ -2021,6 +2024,7 @@
<java-symbol type="string" name="car_mode_disable_notification_title" />
<java-symbol type="string" name="chooser_wallpaper" />
<java-symbol type="string" name="config_datause_iface" />
+ <java-symbol type="string" name="config_activityRecognitionHardwarePackageName" />
<java-symbol type="string" name="config_fusedLocationProviderPackageName" />
<java-symbol type="string" name="config_hardwareFlpPackageName" />
<java-symbol type="string" name="config_geocoderProviderPackageName" />
@@ -3031,6 +3035,7 @@
<java-symbol type="drawable" name="ic_doc_generic" />
<java-symbol type="bool" name="config_setColorTransformAccelerated" />
+ <java-symbol type="bool" name="config_setColorTransformAcceleratedPerLayer" />
<java-symbol type="bool" name="config_displayWhiteBalanceAvailable" />
<java-symbol type="bool" name="config_nightDisplayAvailable" />
<java-symbol type="bool" name="config_allowDisablingAssistDisclosure" />
@@ -3540,4 +3545,7 @@
<java-symbol type="bool" name="config_silenceSensorAvailable" />
<java-symbol type="bool" name="config_zramWriteback" />
+
+ <!-- For CBRS -->
+ <java-symbol type="bool" name="config_cbrs_supported" />
</resources>
diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
index 8d42c74..5731daa 100644
--- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
+++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
@@ -20,12 +20,23 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static android.app.admin.PasswordMetrics.complexityLevelToMinQuality;
+import static android.app.admin.PasswordMetrics.getActualRequiredQuality;
+import static android.app.admin.PasswordMetrics.getMinimumMetrics;
+import static android.app.admin.PasswordMetrics.getTargetQualityMetrics;
+import static android.app.admin.PasswordMetrics.sanitizeComplexityLevel;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
-import android.app.admin.PasswordMetrics.PasswordComplexityBucket;
import android.os.Parcel;
import androidx.test.filters.SmallTest;
@@ -109,7 +120,7 @@
assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX,
PasswordMetrics.computeForPassword("1").quality);
// contains a long sequence so isn't complex
- assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC,
+ assertEquals(PASSWORD_QUALITY_NUMERIC,
PasswordMetrics.computeForPassword("1234").quality);
assertEquals(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
PasswordMetrics.computeForPassword("").quality);
@@ -145,7 +156,7 @@
new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 5));
assertNotEquals(new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 4),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 4));
+ new PasswordMetrics(PASSWORD_QUALITY_COMPLEX, 4));
metrics0 = PasswordMetrics.computeForPassword("1234abcd,./");
metrics1 = PasswordMetrics.computeForPassword("1234abcd,./");
@@ -176,9 +187,9 @@
@Test
public void testConstructQuality() {
PasswordMetrics expected = new PasswordMetrics();
- expected.quality = DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+ expected.quality = PASSWORD_QUALITY_COMPLEX;
- PasswordMetrics actual = new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX);
+ PasswordMetrics actual = new PasswordMetrics(PASSWORD_QUALITY_COMPLEX);
assertEquals(expected, actual);
}
@@ -256,42 +267,178 @@
}
@Test
- public void testComplexityLevelToBucket_none() {
- PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket(
- PASSWORD_COMPLEXITY_NONE).getMetrics();
+ public void testSanitizeComplexityLevel_none() {
+ assertEquals(PASSWORD_COMPLEXITY_NONE, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_NONE));
- for (PasswordMetrics metrics : bucket) {
- assertEquals(PASSWORD_COMPLEXITY_NONE, metrics.determineComplexity());
- }
}
@Test
- public void testComplexityLevelToBucket_low() {
- PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket(
- PASSWORD_COMPLEXITY_LOW).getMetrics();
-
- for (PasswordMetrics metrics : bucket) {
- assertEquals(PASSWORD_COMPLEXITY_LOW, metrics.determineComplexity());
- }
+ public void testSanitizeComplexityLevel_low() {
+ assertEquals(PASSWORD_COMPLEXITY_LOW, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_LOW));
}
@Test
- public void testComplexityLevelToBucket_medium() {
- PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket(
- PASSWORD_COMPLEXITY_MEDIUM).getMetrics();
-
- for (PasswordMetrics metrics : bucket) {
- assertEquals(PASSWORD_COMPLEXITY_MEDIUM, metrics.determineComplexity());
- }
+ public void testSanitizeComplexityLevel_medium() {
+ assertEquals(
+ PASSWORD_COMPLEXITY_MEDIUM, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_MEDIUM));
}
@Test
- public void testComplexityLevelToBucket_high() {
- PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket(
- PASSWORD_COMPLEXITY_HIGH).getMetrics();
+ public void testSanitizeComplexityLevel_high() {
+ assertEquals(PASSWORD_COMPLEXITY_HIGH, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_HIGH));
+ }
- for (PasswordMetrics metrics : bucket) {
- assertEquals(PASSWORD_COMPLEXITY_HIGH, metrics.determineComplexity());
- }
+ @Test
+ public void testSanitizeComplexityLevel_invalid() {
+ assertEquals(PASSWORD_COMPLEXITY_NONE, sanitizeComplexityLevel(-1));
+ }
+
+ @Test
+ public void testComplexityLevelToMinQuality_none() {
+ assertEquals(PASSWORD_QUALITY_UNSPECIFIED,
+ complexityLevelToMinQuality(PASSWORD_COMPLEXITY_NONE));
+ }
+
+ @Test
+ public void testComplexityLevelToMinQuality_low() {
+ assertEquals(PASSWORD_QUALITY_SOMETHING,
+ complexityLevelToMinQuality(PASSWORD_COMPLEXITY_LOW));
+ }
+
+ @Test
+ public void testComplexityLevelToMinQuality_medium() {
+ assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX,
+ complexityLevelToMinQuality(PASSWORD_COMPLEXITY_MEDIUM));
+ }
+
+ @Test
+ public void testComplexityLevelToMinQuality_high() {
+ assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX,
+ complexityLevelToMinQuality(PASSWORD_COMPLEXITY_HIGH));
+ }
+
+ @Test
+ public void testComplexityLevelToMinQuality_invalid() {
+ assertEquals(PASSWORD_QUALITY_UNSPECIFIED, complexityLevelToMinQuality(-1));
+ }
+
+ @Test
+ public void testGetTargetQualityMetrics_noneComplexityReturnsDefaultMetrics() {
+ PasswordMetrics metrics =
+ getTargetQualityMetrics(PASSWORD_COMPLEXITY_NONE, PASSWORD_QUALITY_ALPHANUMERIC);
+
+ assertTrue(metrics.isDefault());
+ }
+
+ @Test
+ public void testGetTargetQualityMetrics_qualityNotAllowedReturnsMinQualityMetrics() {
+ PasswordMetrics metrics =
+ getTargetQualityMetrics(PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_NUMERIC);
+
+ assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality);
+ assertEquals(/* expected= */ 4, metrics.length);
+ }
+
+ @Test
+ public void testGetTargetQualityMetrics_highComplexityNumericComplex() {
+ PasswordMetrics metrics = getTargetQualityMetrics(
+ PASSWORD_COMPLEXITY_HIGH, PASSWORD_QUALITY_NUMERIC_COMPLEX);
+
+ assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality);
+ assertEquals(/* expected= */ 8, metrics.length);
+ }
+
+ @Test
+ public void testGetTargetQualityMetrics_mediumComplexityAlphabetic() {
+ PasswordMetrics metrics = getTargetQualityMetrics(
+ PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_ALPHABETIC);
+
+ assertEquals(PASSWORD_QUALITY_ALPHABETIC, metrics.quality);
+ assertEquals(/* expected= */ 4, metrics.length);
+ }
+
+ @Test
+ public void testGetTargetQualityMetrics_lowComplexityAlphanumeric() {
+ PasswordMetrics metrics = getTargetQualityMetrics(
+ PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_ALPHANUMERIC);
+
+ assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, metrics.quality);
+ assertEquals(/* expected= */ 4, metrics.length);
+ }
+
+ @Test
+ public void testGetActualRequiredQuality_nonComplex() {
+ int actual = getActualRequiredQuality(
+ PASSWORD_QUALITY_NUMERIC_COMPLEX,
+ /* requiresNumeric= */ false,
+ /* requiresLettersOrSymbols= */ false);
+
+ assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, actual);
+ }
+
+ @Test
+ public void testGetActualRequiredQuality_complexRequiresNone() {
+ int actual = getActualRequiredQuality(
+ PASSWORD_QUALITY_COMPLEX,
+ /* requiresNumeric= */ false,
+ /* requiresLettersOrSymbols= */ false);
+
+ assertEquals(PASSWORD_QUALITY_UNSPECIFIED, actual);
+ }
+
+ @Test
+ public void testGetActualRequiredQuality_complexRequiresNumeric() {
+ int actual = getActualRequiredQuality(
+ PASSWORD_QUALITY_COMPLEX,
+ /* requiresNumeric= */ true,
+ /* requiresLettersOrSymbols= */ false);
+
+ assertEquals(PASSWORD_QUALITY_NUMERIC, actual);
+ }
+
+ @Test
+ public void testGetActualRequiredQuality_complexRequiresLetters() {
+ int actual = getActualRequiredQuality(
+ PASSWORD_QUALITY_COMPLEX,
+ /* requiresNumeric= */ false,
+ /* requiresLettersOrSymbols= */ true);
+
+ assertEquals(PASSWORD_QUALITY_ALPHABETIC, actual);
+ }
+
+ @Test
+ public void testGetActualRequiredQuality_complexRequiresNumericAndLetters() {
+ int actual = getActualRequiredQuality(
+ PASSWORD_QUALITY_COMPLEX,
+ /* requiresNumeric= */ true,
+ /* requiresLettersOrSymbols= */ true);
+
+ assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, actual);
+ }
+
+ @Test
+ public void testGetMinimumMetrics_userInputStricter() {
+ PasswordMetrics metrics = getMinimumMetrics(
+ PASSWORD_COMPLEXITY_HIGH,
+ PASSWORD_QUALITY_ALPHANUMERIC,
+ PASSWORD_QUALITY_NUMERIC,
+ /* requiresNumeric= */ false,
+ /* requiresLettersOrSymbols= */ false);
+
+ assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, metrics.quality);
+ assertEquals(/* expected= */ 6, metrics.length);
+ }
+
+ @Test
+ public void testGetMinimumMetrics_actualRequiredQualityStricter() {
+ PasswordMetrics metrics = getMinimumMetrics(
+ PASSWORD_COMPLEXITY_HIGH,
+ PASSWORD_QUALITY_UNSPECIFIED,
+ PASSWORD_QUALITY_NUMERIC,
+ /* requiresNumeric= */ false,
+ /* requiresLettersOrSymbols= */ false);
+
+ assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality);
+ assertEquals(/* expected= */ 8, metrics.length);
}
}
diff --git a/core/tests/coretests/src/android/os/WorkSourceTest.java b/core/tests/coretests/src/android/os/WorkSourceTest.java
index 425ab89..e94d60c 100644
--- a/core/tests/coretests/src/android/os/WorkSourceTest.java
+++ b/core/tests/coretests/src/android/os/WorkSourceTest.java
@@ -341,12 +341,37 @@
}
public void testGetAttributionId() {
- WorkSource ws1 = new WorkSource();
- WorkChain wc = ws1.createWorkChain();
- wc.addNode(100, "tag");
- assertEquals(100, wc.getAttributionUid());
- wc.addNode(200, "tag2");
- assertEquals(100, wc.getAttributionUid());
+ WorkSource ws = new WorkSource();
+ WorkChain wc1 = ws.createWorkChain();
+ wc1.addNode(100, "tag");
+ assertEquals(100, wc1.getAttributionUid());
+ assertEquals(100, ws.getAttributionUid());
+ wc1.addNode(200, "tag2");
+ assertEquals(100, wc1.getAttributionUid());
+ assertEquals(100, ws.getAttributionUid());
+ WorkChain wc2 = ws.createWorkChain();
+ wc2.addNode(300, "tag3");
+ assertEquals(300, wc2.getAttributionUid());
+ assertEquals(100, ws.getAttributionUid());
+ }
+
+ public void testGetAttributionIdWithoutWorkChain() {
+ WorkSource ws1 = new WorkSource(100);
+ ws1.add(200);
+ WorkSource ws2 = new WorkSource();
+ ws2.add(100);
+ ws2.add(200);
+ assertEquals(100, ws1.getAttributionUid());
+ assertEquals(100, ws2.getAttributionUid());
+ }
+
+ public void testGetAttributionWhenEmpty() {
+ WorkSource ws = new WorkSource();
+ assertEquals(-1, ws.getAttributionUid());
+ WorkChain wc = ws.createWorkChain();
+ assertEquals(-1, ws.getAttributionUid());
+ assertEquals(-1, wc.getAttributionUid());
+ assertNull(wc.getAttributionTag());
}
public void testGetAttributionTag() {
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 0fb1c7c..1431741 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -100,6 +100,7 @@
Settings.Global.ACTIVITY_MANAGER_CONSTANTS,
Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED,
Settings.Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED,
+ Settings.Global.ADB_ALLOWED_CONNECTION_TIME,
Settings.Global.ADB_ENABLED,
Settings.Global.ADD_USERS_WHEN_LOCKED,
Settings.Global.AIRPLANE_MODE_ON,
@@ -120,6 +121,7 @@
Settings.Global.APP_IDLE_CONSTANTS,
Settings.Global.APP_OPS_CONSTANTS,
Settings.Global.APP_STANDBY_ENABLED,
+ Settings.Global.APP_TIME_LIMIT_USAGE_SOURCE,
Settings.Global.ASSISTED_GPS_ENABLED,
Settings.Global.AUDIO_SAFE_VOLUME_STATE,
Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
@@ -145,6 +147,7 @@
Settings.Global.BLOCKED_SLICES,
Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT,
Settings.Global.BLOCKING_HELPER_STREAK_LIMIT,
+ Settings.Global.BLUETOOTH_BTSNOOP_DEFAULT_MODE,
Settings.Global.BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX,
Settings.Global.BLUETOOTH_A2DP_SRC_PRIORITY_PREFIX,
Settings.Global.BLUETOOTH_A2DP_SUPPORTS_OPTIONAL_CODECS_PREFIX,
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 7f00ad9..7cd3c44 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -101,6 +101,7 @@
@Test
public void testChangeInsets() {
mController.changeInsets(Insets.of(0, 30, 40, 0));
+ mController.applyChangeInsets(new InsetsState());
assertEquals(Insets.of(0, 30, 40, 0), mController.getCurrentInsets());
ArgumentCaptor<SurfaceParams> captor = ArgumentCaptor.forClass(SurfaceParams.class);
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index 9857b7a..2db2f5f 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -60,7 +60,7 @@
mState.getSource(TYPE_IME).setVisible(true);
SparseIntArray typeSideMap = new SparseIntArray();
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
- DisplayCutout.NO_CUTOUT, typeSideMap);
+ DisplayCutout.NO_CUTOUT, null, null, typeSideMap);
assertEquals(Insets.of(0, 100, 0, 100), insets.getSystemWindowInsets());
assertEquals(Insets.of(0, 100, 0, 100), insets.getInsets(Type.all()));
assertEquals(INSET_SIDE_TOP, typeSideMap.get(TYPE_TOP_BAR));
@@ -76,7 +76,7 @@
mState.getSource(TYPE_IME).setFrame(new Rect(0, 100, 100, 300));
mState.getSource(TYPE_IME).setVisible(true);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
- DisplayCutout.NO_CUTOUT, null);
+ DisplayCutout.NO_CUTOUT, null, null, null);
assertEquals(100, insets.getStableInsetBottom());
assertEquals(Insets.of(0, 0, 0, 100), insets.getMaxInsets(Type.all()));
assertEquals(Insets.of(0, 0, 0, 200), insets.getSystemWindowInsets());
@@ -92,7 +92,7 @@
mState.getSource(TYPE_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300));
mState.getSource(TYPE_NAVIGATION_BAR).setVisible(true);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
- DisplayCutout.NO_CUTOUT, null);
+ DisplayCutout.NO_CUTOUT, null, null, null);
assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.topBar()));
assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(Type.sideBars()));
@@ -106,7 +106,7 @@
mState.getSource(TYPE_IME).setVisible(true);
mState.removeSource(TYPE_IME);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
- DisplayCutout.NO_CUTOUT, null);
+ DisplayCutout.NO_CUTOUT, null, null, null);
assertEquals(0, insets.getSystemWindowInsetBottom());
}
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index 15a96a1..d57fa8f 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -18,7 +18,7 @@
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.sideBars;
-
+import static android.view.WindowInsets.Type.topBar;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -54,6 +54,7 @@
assertTrue(new WindowInsets((Rect) null).isConsumed());
}
+ // TODO: Move this to CTS once API made public
@Test
public void typeMap() {
Builder b = new WindowInsets.Builder();
@@ -62,4 +63,14 @@
WindowInsets insets = b.build();
assertEquals(300, insets.getSystemWindowInsets().bottom);
}
+
+ // TODO: Move this to CTS once API made public
+ @Test
+ public void compatInsets() {
+ Builder b = new WindowInsets.Builder();
+ b.setSystemWindowInsets(Insets.of(0, 50, 30, 10));
+ WindowInsets insets = b.build();
+ assertEquals(Insets.of(0, 50, 0, 0), insets.getInsets(topBar()));
+ assertEquals(Insets.of(0, 0, 30, 10), insets.getInsets(sideBars()));
+ }
}
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 0dd7685..683d16b 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -88,32 +88,32 @@
public void setOnKeyEventResult(boolean handled, int sequence) {}
- public float getMagnificationScale() {
+ public float getMagnificationScale(int displayId) {
return 0.0f;
}
- public float getMagnificationCenterX() {
+ public float getMagnificationCenterX(int displayId) {
return 0.0f;
}
- public float getMagnificationCenterY() {
+ public float getMagnificationCenterY(int displayId) {
return 0.0f;
}
- public Region getMagnificationRegion() {
+ public Region getMagnificationRegion(int displayId) {
return null;
}
- public boolean resetMagnification(boolean animate) {
+ public boolean resetMagnification(int displayId, boolean animate) {
return false;
}
- public boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
- boolean animate) {
+ public boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX,
+ float centerY, boolean animate) {
return false;
}
- public void setMagnificationCallbackEnabled(boolean enabled) {}
+ public void setMagnificationCallbackEnabled(int displayId, boolean enabled) {}
public boolean setSoftKeyboardShowMode(int showMode) {
return false;
diff --git a/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java b/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java
index 33bc593..2f17b32 100644
--- a/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java
+++ b/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java
@@ -34,26 +34,57 @@
public void testNonVirtual() {
final AutofillId id = new AutofillId(42);
assertThat(id.getViewId()).isEqualTo(42);
- assertThat(id.isVirtual()).isFalse();
- assertThat(id.getVirtualChildId()).isEqualTo(View.NO_ID);
+ assertThat(id.isNonVirtual()).isTrue();
+ assertThat(id.isVirtualInt()).isFalse();
+ assertThat(id.isVirtualLong()).isFalse();
+ assertThat(id.getVirtualChildIntId()).isEqualTo(View.NO_ID);
+ assertThat(id.getVirtualChildLongId()).isEqualTo(View.NO_ID);
final AutofillId clone = cloneThroughParcel(id);
assertThat(clone.getViewId()).isEqualTo(42);
- assertThat(clone.isVirtual()).isFalse();
- assertThat(clone.getVirtualChildId()).isEqualTo(View.NO_ID);
+ assertThat(clone.isNonVirtual()).isTrue();
+ assertThat(clone.isVirtualInt()).isFalse();
+ assertThat(clone.isVirtualLong()).isFalse();
+ assertThat(clone.getVirtualChildIntId()).isEqualTo(View.NO_ID);
+ assertThat(clone.getVirtualChildLongId()).isEqualTo(View.NO_ID);
}
@Test
- public void testVirtual() {
+ public void testVirtual_int() {
final AutofillId id = new AutofillId(42, 108);
assertThat(id.getViewId()).isEqualTo(42);
- assertThat(id.isVirtual()).isTrue();
- assertThat(id.getVirtualChildId()).isEqualTo(108);
+ assertThat(id.isVirtualInt()).isTrue();
+ assertThat(id.isVirtualLong()).isFalse();
+ assertThat(id.isNonVirtual()).isFalse();
+ assertThat(id.getVirtualChildIntId()).isEqualTo(108);
+ assertThat(id.getVirtualChildLongId()).isEqualTo(View.NO_ID);
final AutofillId clone = cloneThroughParcel(id);
assertThat(clone.getViewId()).isEqualTo(42);
- assertThat(clone.isVirtual()).isTrue();
- assertThat(clone.getVirtualChildId()).isEqualTo(108);
+ assertThat(clone.isVirtualLong()).isFalse();
+ assertThat(clone.isVirtualInt()).isTrue();
+ assertThat(clone.isNonVirtual()).isFalse();
+ assertThat(clone.getVirtualChildIntId()).isEqualTo(108);
+ assertThat(clone.getVirtualChildLongId()).isEqualTo(View.NO_ID);
+ }
+
+ @Test
+ public void testVirtual_long() {
+ final AutofillId id = new AutofillId(new AutofillId(42), 4815162342L, 108);
+ assertThat(id.getViewId()).isEqualTo(42);
+ assertThat(id.isVirtualLong()).isTrue();
+ assertThat(id.isVirtualInt()).isFalse();
+ assertThat(id.isNonVirtual()).isFalse();
+ assertThat(id.getVirtualChildIntId()).isEqualTo(View.NO_ID);
+ assertThat(id.getVirtualChildLongId()).isEqualTo(4815162342L);
+
+ final AutofillId clone = cloneThroughParcel(id);
+ assertThat(clone.getViewId()).isEqualTo(42);
+ assertThat(clone.isVirtualLong()).isTrue();
+ assertThat(clone.isVirtualInt()).isFalse();
+ assertThat(clone.isNonVirtual()).isFalse();
+ assertThat(clone.getVirtualChildIntId()).isEqualTo(View.NO_ID);
+ assertThat(clone.getVirtualChildLongId()).isEqualTo(4815162342L);
}
@Test
@@ -62,27 +93,33 @@
final AutofillId id = new AutofillId(new AutofillId(42), 108);
assertThat(id.getViewId()).isEqualTo(42);
- assertThat(id.isVirtual()).isTrue();
- assertThat(id.getVirtualChildId()).isEqualTo(108);
+ assertThat(id.isVirtualInt()).isTrue();
+ assertThat(id.getVirtualChildIntId()).isEqualTo(108);
final AutofillId clone = cloneThroughParcel(id);
assertThat(clone.getViewId()).isEqualTo(42);
- assertThat(clone.isVirtual()).isTrue();
- assertThat(clone.getVirtualChildId()).isEqualTo(108);
+ assertThat(clone.isVirtualInt()).isTrue();
+ assertThat(clone.getVirtualChildIntId()).isEqualTo(108);
}
@Test
public void testVirtual_withSession() {
- final AutofillId id = new AutofillId(new AutofillId(42), 108, 666);
+ final AutofillId id = new AutofillId(new AutofillId(42), 108L, 666);
assertThat(id.getViewId()).isEqualTo(42);
- assertThat(id.isVirtual()).isTrue();
- assertThat(id.getVirtualChildId()).isEqualTo(108);
+ assertThat(id.isVirtualLong()).isTrue();
+ assertThat(id.isVirtualInt()).isFalse();
+ assertThat(id.isNonVirtual()).isFalse();
+ assertThat(id.getVirtualChildLongId()).isEqualTo(108L);
+ assertThat(id.getVirtualChildIntId()).isEqualTo(View.NO_ID);
assertThat(id.getSessionId()).isEqualTo(666);
final AutofillId clone = cloneThroughParcel(id);
assertThat(clone.getViewId()).isEqualTo(42);
- assertThat(clone.isVirtual()).isTrue();
- assertThat(clone.getVirtualChildId()).isEqualTo(108);
+ assertThat(clone.isVirtualLong()).isTrue();
+ assertThat(clone.isVirtualInt()).isFalse();
+ assertThat(clone.isNonVirtual()).isFalse();
+ assertThat(clone.getVirtualChildLongId()).isEqualTo(108L);
+ assertThat(clone.getVirtualChildIntId()).isEqualTo(View.NO_ID);
assertThat(clone.getSessionId()).isEqualTo(666);
}
@@ -118,13 +155,14 @@
assertThat(virtualIdDifferentParent).isNotEqualTo(virtualIdDifferentChild);
assertThat(virtualIdDifferentChild).isNotEqualTo(virtualIdDifferentParent);
- final AutofillId virtualIdDifferentSession = new AutofillId(new AutofillId(42), 1, 108);
+ final AutofillId virtualIdDifferentSession = new AutofillId(new AutofillId(42), 1L, 108);
assertThat(virtualIdDifferentSession).isNotEqualTo(virtualId);
assertThat(virtualId).isNotEqualTo(virtualIdDifferentSession);
assertThat(virtualIdDifferentSession).isNotEqualTo(realId);
assertThat(realId).isNotEqualTo(virtualIdDifferentSession);
- final AutofillId sameVirtualIdDifferentSession = new AutofillId(new AutofillId(42), 1, 108);
+ final AutofillId sameVirtualIdDifferentSession =
+ new AutofillId(new AutofillId(42), 1L, 108);
assertThat(sameVirtualIdDifferentSession).isEqualTo(virtualIdDifferentSession);
assertThat(virtualIdDifferentSession).isEqualTo(sameVirtualIdDifferentSession);
assertThat(sameVirtualIdDifferentSession.hashCode())
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
index ff97aa1..c2eb18b 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
@@ -48,17 +48,18 @@
@Test
public void testNewAutofillId_invalid() {
- assertThrows(NullPointerException.class, () -> mSession1.newAutofillId(null, 42));
+ assertThrows(NullPointerException.class, () -> mSession1.newAutofillId(null, 42L));
assertThrows(IllegalArgumentException.class,
- () -> mSession1.newAutofillId(new AutofillId(42, 42), 42));
+ () -> mSession1.newAutofillId(new AutofillId(42, 42), 42L));
}
@Test
public void testNewAutofillId_valid() {
final AutofillId parentId = new AutofillId(42);
- final AutofillId childId = mSession1.newAutofillId(parentId, 108);
+ final AutofillId childId = mSession1.newAutofillId(parentId, 108L);
assertThat(childId.getViewId()).isEqualTo(42);
- assertThat(childId.getVirtualChildId()).isEqualTo(108);
+ assertThat(childId.getVirtualChildLongId()).isEqualTo(108L);
+ assertThat(childId.getVirtualChildIntId()).isEqualTo(View.NO_ID);
assertThat(childId.getSessionId()).isEqualTo(mSession1.getIdAsInt());
}
@@ -66,8 +67,8 @@
public void testNewAutofillId_differentSessions() {
assertThat(mSession1.getIdAsInt()).isNotSameAs(mSession2.getIdAsInt()); //sanity check
final AutofillId parentId = new AutofillId(42);
- final AutofillId childId1 = mSession1.newAutofillId(parentId, 108);
- final AutofillId childId2 = mSession2.newAutofillId(parentId, 108);
+ final AutofillId childId1 = mSession1.newAutofillId(parentId, 108L);
+ final AutofillId childId2 = mSession2.newAutofillId(parentId, 108L);
assertThat(childId1).isNotEqualTo(childId2);
assertThat(childId2).isNotEqualTo(childId1);
}
@@ -91,9 +92,9 @@
@Test
public void testNewVirtualViewStructure() {
final AutofillId parentId = new AutofillId(42);
- final ViewStructure structure = mSession1.newVirtualViewStructure(parentId, 108);
+ final ViewStructure structure = mSession1.newVirtualViewStructure(parentId, 108L);
assertThat(structure).isNotNull();
- final AutofillId childId = mSession1.newAutofillId(parentId, 108);
+ final AutofillId childId = mSession1.newAutofillId(parentId, 108L);
assertThat(structure.getAutofillId()).isEqualTo(childId);
}
@@ -101,16 +102,16 @@
public void testNotifyViewsDisappeared_invalid() {
// Null parent
assertThrows(NullPointerException.class,
- () -> mSession1.notifyViewsDisappeared(null, new int[] {42}));
+ () -> mSession1.notifyViewsDisappeared(null, new long[] {42}));
// Null child
assertThrows(IllegalArgumentException.class,
() -> mSession1.notifyViewsDisappeared(new AutofillId(42), null));
// Empty child
assertThrows(IllegalArgumentException.class,
- () -> mSession1.notifyViewsDisappeared(new AutofillId(42), new int[] {}));
+ () -> mSession1.notifyViewsDisappeared(new AutofillId(42), new long[] {}));
// Virtual parent
assertThrows(IllegalArgumentException.class,
- () -> mSession1.notifyViewsDisappeared(new AutofillId(42, 108), new int[] {666}));
+ () -> mSession1.notifyViewsDisappeared(new AutofillId(42, 108), new long[] {666}));
}
// Cannot use @Spy because we need to pass the session id on constructor
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
index 0179ead..9cac7e7 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
@@ -43,6 +43,10 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
import com.android.internal.util.ArrayUtils;
import org.junit.Before;
@@ -76,13 +80,13 @@
@RunWith(AndroidJUnit4.class)
public class BatteryStatsCpuTimesTest {
@Mock
- KernelUidCpuTimeReader mKernelUidCpuTimeReader;
+ KernelCpuUidUserSysTimeReader mCpuUidUserSysTimeReader;
@Mock
- KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader;
+ KernelCpuUidFreqTimeReader mCpuUidFreqTimeReader;
@Mock
- KernelUidCpuActiveTimeReader mKernelUidCpuActiveTimeReader;
+ KernelCpuUidActiveTimeReader mCpuUidActiveTimeReader;
@Mock
- KernelUidCpuClusterTimeReader mKernelUidCpuClusterTimeReader;
+ KernelCpuUidClusterTimeReader mCpuUidClusterTimeReader;
@Mock
BatteryStatsImpl.UserInfoProvider mUserInfoProvider;
@Mock
@@ -98,10 +102,10 @@
mClocks = new MockClocks();
mBatteryStatsImpl = new MockBatteryStatsImpl(mClocks)
- .setKernelUidCpuTimeReader(mKernelUidCpuTimeReader)
- .setKernelUidCpuFreqTimeReader(mKernelUidCpuFreqTimeReader)
- .setKernelUidCpuActiveTimeReader(mKernelUidCpuActiveTimeReader)
- .setKernelUidCpuClusterTimeReader(mKernelUidCpuClusterTimeReader)
+ .setKernelCpuUidUserSysTimeReader(mCpuUidUserSysTimeReader)
+ .setKernelCpuUidFreqTimeReader(mCpuUidFreqTimeReader)
+ .setKernelCpuUidActiveTimeReader(mCpuUidActiveTimeReader)
+ .setKernelCpuUidClusterTimeReader(mCpuUidClusterTimeReader)
.setUserInfoProvider(mUserInfoProvider);
}
@@ -113,21 +117,21 @@
final int numClusters = 3;
initKernelCpuSpeedReaders(numClusters);
final long[] freqs = {1, 12, 123, 12, 1234};
- when(mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile)).thenReturn(freqs);
+ when(mCpuUidFreqTimeReader.readFreqs(mPowerProfile)).thenReturn(freqs);
// RUN
mBatteryStatsImpl.updateCpuTimeLocked(false, false);
// VERIFY
assertArrayEquals("Unexpected cpu freqs", freqs, mBatteryStatsImpl.getCpuFreqs());
- verify(mKernelUidCpuTimeReader).readDelta(null);
- verify(mKernelUidCpuFreqTimeReader).readDelta(null);
+ verify(mCpuUidUserSysTimeReader).readDelta(null);
+ verify(mCpuUidFreqTimeReader).readDelta(null);
for (int i = 0; i < numClusters; ++i) {
verify(mKernelCpuSpeedReaders[i]).readDelta();
}
// Prepare for next test
- Mockito.reset(mUserInfoProvider, mKernelUidCpuFreqTimeReader, mKernelUidCpuTimeReader);
+ Mockito.reset(mUserInfoProvider, mCpuUidFreqTimeReader, mCpuUidUserSysTimeReader);
for (int i = 0; i < numClusters; ++i) {
Mockito.reset(mKernelCpuSpeedReaders[i]);
}
@@ -140,17 +144,18 @@
// VERIFY
verify(mUserInfoProvider).refreshUserIds();
- verify(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+ verify(mCpuUidUserSysTimeReader).readDelta(
+ any(KernelCpuUidUserSysTimeReader.Callback.class));
// perClusterTimesAvailable is called twice, once in updateCpuTimeLocked() and the other
// in readKernelUidCpuFreqTimesLocked.
- verify(mKernelUidCpuFreqTimeReader, times(2)).perClusterTimesAvailable();
- verify(mKernelUidCpuFreqTimeReader).readDelta(
- any(KernelUidCpuFreqTimeReader.Callback.class));
- verify(mKernelUidCpuActiveTimeReader).readDelta(
- any(KernelUidCpuActiveTimeReader.Callback.class));
- verify(mKernelUidCpuClusterTimeReader).readDelta(
- any(KernelUidCpuClusterTimeReader.Callback.class));
- verifyNoMoreInteractions(mKernelUidCpuFreqTimeReader);
+ verify(mCpuUidFreqTimeReader, times(2)).perClusterTimesAvailable();
+ verify(mCpuUidFreqTimeReader).readDelta(
+ any(KernelCpuUidFreqTimeReader.Callback.class));
+ verify(mCpuUidActiveTimeReader).readDelta(
+ any(KernelCpuUidActiveTimeReader.Callback.class));
+ verify(mCpuUidClusterTimeReader).readDelta(
+ any(KernelCpuUidClusterTimeReader.Callback.class));
+ verifyNoMoreInteractions(mCpuUidFreqTimeReader);
for (int i = 0; i < numClusters; ++i) {
verify(mKernelCpuSpeedReaders[i]).readDelta();
}
@@ -253,13 +258,14 @@
{12, 34}, {34897394, 3123983}, {79775429834l, 8430434903489l}
};
doAnswer(invocation -> {
- final KernelUidCpuTimeReader.Callback callback =
- (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidUserSysTimeReader.Callback<long[]> callback =
+ (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuTime(testUids[i], uidTimesUs[i][0], uidTimesUs[i][1]);
+ callback.onUidCpuTime(testUids[i], uidTimesUs[i]);
}
return null;
- }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+ }).when(mCpuUidUserSysTimeReader).readDelta(
+ any(KernelCpuUidUserSysTimeReader.Callback.class));
// RUN
final SparseLongArray updatedUids = new SparseLongArray();
@@ -287,13 +293,14 @@
{9379, 3332409833484l}, {493247, 723234}, {3247819, 123348}
};
doAnswer(invocation -> {
- final KernelUidCpuTimeReader.Callback callback =
- (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidUserSysTimeReader.Callback<long[]> callback =
+ (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuTime(testUids[i], deltasUs[i][0], deltasUs[i][1]);
+ callback.onUidCpuTime(testUids[i], deltasUs[i]);
}
return null;
- }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+ }).when(mCpuUidUserSysTimeReader).readDelta(
+ any(KernelCpuUidUserSysTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, null, true);
@@ -326,13 +333,14 @@
{12, 34}, {34897394, 3123983}, {79775429834l, 8430434903489l}
};
doAnswer(invocation -> {
- final KernelUidCpuTimeReader.Callback callback =
- (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidUserSysTimeReader.Callback<long[]> callback =
+ (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuTime(testUids[i], uidTimesUs[i][0], uidTimesUs[i][1]);
+ callback.onUidCpuTime(testUids[i], uidTimesUs[i]);
}
return null;
- }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+ }).when(mCpuUidUserSysTimeReader).readDelta(
+ any(KernelCpuUidUserSysTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, null, true);
@@ -350,7 +358,7 @@
assertEquals("Unexpected system cpu time for uid=" + testUids[i],
uidTimesUs[i][1], u.getSystemCpuTimeUs(STATS_SINCE_CHARGED));
}
- verify(mKernelUidCpuTimeReader).removeUid(isolatedUid);
+ verify(mCpuUidUserSysTimeReader).removeUid(isolatedUid);
// Add an isolated uid mapping and repeat the test.
@@ -361,13 +369,14 @@
{9379, 3332409833484l}, {493247, 723234}, {3247819, 123348}
};
doAnswer(invocation -> {
- final KernelUidCpuTimeReader.Callback callback =
- (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidUserSysTimeReader.Callback<long[]> callback =
+ (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuTime(testUids[i], deltasUs[i][0], deltasUs[i][1]);
+ callback.onUidCpuTime(testUids[i], deltasUs[i]);
}
return null;
- }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+ }).when(mCpuUidUserSysTimeReader).readDelta(
+ any(KernelCpuUidUserSysTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, null, true);
@@ -414,15 +423,16 @@
{12, 34}, {34897394, 3123983}, {79775429834l, 8430434903489l}
};
doAnswer(invocation -> {
- final KernelUidCpuTimeReader.Callback callback =
- (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidUserSysTimeReader.Callback<long[]> callback =
+ (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuTime(testUids[i], uidTimesUs[i][0], uidTimesUs[i][1]);
+ callback.onUidCpuTime(testUids[i], uidTimesUs[i]);
}
// And one for the invalid uid
- callback.onUidCpuTime(invalidUid, 3879, 239);
+ callback.onUidCpuTime(invalidUid, new long[]{3879, 239});
return null;
- }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+ }).when(mCpuUidUserSysTimeReader).readDelta(
+ any(KernelCpuUidUserSysTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, null, true);
@@ -438,7 +448,7 @@
}
assertNull("There shouldn't be an entry for invalid uid=" + invalidUid,
mBatteryStatsImpl.getUidStats().get(invalidUid));
- verify(mKernelUidCpuTimeReader).removeUid(invalidUid);
+ verify(mCpuUidUserSysTimeReader).removeUid(invalidUid);
}
@Test
@@ -462,13 +472,14 @@
{12, 34}, {3394, 3123}, {7977, 80434}
};
doAnswer(invocation -> {
- final KernelUidCpuTimeReader.Callback callback =
- (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidUserSysTimeReader.Callback<long[]> callback =
+ (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuTime(testUids[i], uidTimesUs[i][0], uidTimesUs[i][1]);
+ callback.onUidCpuTime(testUids[i], uidTimesUs[i]);
}
return null;
- }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+ }).when(mCpuUidUserSysTimeReader).readDelta(
+ any(KernelCpuUidUserSysTimeReader.Callback.class));
// RUN
final SparseLongArray updatedUids = new SparseLongArray();
@@ -541,14 +552,14 @@
{8, 25, 3, 0, 42}
};
doAnswer(invocation -> {
- final KernelUidCpuFreqTimeReader.Callback callback =
- (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback callback =
+ (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]);
+ callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
return null;
- }).when(mKernelUidCpuFreqTimeReader).readDelta(
- any(KernelUidCpuFreqTimeReader.Callback.class));
+ }).when(mCpuUidFreqTimeReader).readDelta(
+ any(KernelCpuUidFreqTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, false);
@@ -574,14 +585,14 @@
{43, 3345, 2143, 123, 4554}
};
doAnswer(invocation -> {
- final KernelUidCpuFreqTimeReader.Callback callback =
- (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback callback =
+ (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuFreqTime(testUids[i], deltasMs[i]);
+ callback.onUidCpuTime(testUids[i], deltasMs[i]);
}
return null;
- }).when(mKernelUidCpuFreqTimeReader).readDelta(
- any(KernelUidCpuFreqTimeReader.Callback.class));
+ }).when(mCpuUidFreqTimeReader).readDelta(
+ any(KernelCpuUidFreqTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, true);
@@ -624,15 +635,15 @@
{8, 25, 3, 0, 42}
};
doAnswer(invocation -> {
- final KernelUidCpuFreqTimeReader.Callback callback =
- (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback callback =
+ (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]);
+ callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
return null;
- }).when(mKernelUidCpuFreqTimeReader).readDelta(
- any(KernelUidCpuFreqTimeReader.Callback.class));
- when(mKernelUidCpuFreqTimeReader.perClusterTimesAvailable()).thenReturn(true);
+ }).when(mCpuUidFreqTimeReader).readDelta(
+ any(KernelCpuUidFreqTimeReader.Callback.class));
+ when(mCpuUidFreqTimeReader.perClusterTimesAvailable()).thenReturn(true);
// RUN
mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, false);
@@ -668,14 +679,14 @@
{43, 3345, 2143, 123, 4554}
};
doAnswer(invocation -> {
- final KernelUidCpuFreqTimeReader.Callback callback =
- (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback callback =
+ (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuFreqTime(testUids[i], deltasMs[i]);
+ callback.onUidCpuTime(testUids[i], deltasMs[i]);
}
return null;
- }).when(mKernelUidCpuFreqTimeReader).readDelta(
- any(KernelUidCpuFreqTimeReader.Callback.class));
+ }).when(mCpuUidFreqTimeReader).readDelta(
+ any(KernelCpuUidFreqTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, true);
@@ -734,15 +745,15 @@
{8, 25, 3, 0, 42}
};
doAnswer(invocation -> {
- final KernelUidCpuFreqTimeReader.Callback callback =
- (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback callback =
+ (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]);
+ callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
return null;
- }).when(mKernelUidCpuFreqTimeReader).readDelta(
- any(KernelUidCpuFreqTimeReader.Callback.class));
- when(mKernelUidCpuFreqTimeReader.perClusterTimesAvailable()).thenReturn(true);
+ }).when(mCpuUidFreqTimeReader).readDelta(
+ any(KernelCpuUidFreqTimeReader.Callback.class));
+ when(mCpuUidFreqTimeReader.perClusterTimesAvailable()).thenReturn(true);
// RUN
mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(partialTimers, true, false);
@@ -824,14 +835,14 @@
{8, 25, 3, 0, 42}
};
doAnswer(invocation -> {
- final KernelUidCpuFreqTimeReader.Callback callback =
- (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback callback =
+ (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]);
+ callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
return null;
- }).when(mKernelUidCpuFreqTimeReader).readDelta(
- any(KernelUidCpuFreqTimeReader.Callback.class));
+ }).when(mCpuUidFreqTimeReader).readDelta(
+ any(KernelCpuUidFreqTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, false);
@@ -857,14 +868,14 @@
{43, 3345, 2143, 123, 4554, 9374983794839l, 979875}
};
doAnswer(invocation -> {
- final KernelUidCpuFreqTimeReader.Callback callback =
- (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback callback =
+ (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuFreqTime(testUids[i], deltasMs[i]);
+ callback.onUidCpuTime(testUids[i], deltasMs[i]);
}
return null;
- }).when(mKernelUidCpuFreqTimeReader).readDelta(
- any(KernelUidCpuFreqTimeReader.Callback.class));
+ }).when(mCpuUidFreqTimeReader).readDelta(
+ any(KernelCpuUidFreqTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, true);
@@ -901,14 +912,14 @@
{8, 25, 3, 0, 42}
};
doAnswer(invocation -> {
- final KernelUidCpuFreqTimeReader.Callback callback =
- (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback callback =
+ (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]);
+ callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
return null;
- }).when(mKernelUidCpuFreqTimeReader).readDelta(
- any(KernelUidCpuFreqTimeReader.Callback.class));
+ }).when(mCpuUidFreqTimeReader).readDelta(
+ any(KernelCpuUidFreqTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, false);
@@ -927,7 +938,7 @@
assertNull("Unexpected screen-off cpu times for uid=" + testUids[i],
u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED));
}
- verify(mKernelUidCpuFreqTimeReader).removeUid(isolatedUid);
+ verify(mCpuUidFreqTimeReader).removeUid(isolatedUid);
// Add an isolated uid mapping and repeat the test.
@@ -941,14 +952,14 @@
{43, 3345, 2143, 123, 4554}
};
doAnswer(invocation -> {
- final KernelUidCpuFreqTimeReader.Callback callback =
- (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback callback =
+ (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuFreqTime(testUids[i], deltasMs[i]);
+ callback.onUidCpuTime(testUids[i], deltasMs[i]);
}
return null;
- }).when(mKernelUidCpuFreqTimeReader).readDelta(
- any(KernelUidCpuFreqTimeReader.Callback.class));
+ }).when(mCpuUidFreqTimeReader).readDelta(
+ any(KernelCpuUidFreqTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, false);
@@ -996,16 +1007,16 @@
{8, 25, 3, 0, 42}
};
doAnswer(invocation -> {
- final KernelUidCpuFreqTimeReader.Callback callback =
- (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback callback =
+ (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]);
+ callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
// And one for the invalid uid
- callback.onUidCpuFreqTime(invalidUid, new long[]{12, 839, 32, 34, 21});
+ callback.onUidCpuTime(invalidUid, new long[]{12, 839, 32, 34, 21});
return null;
- }).when(mKernelUidCpuFreqTimeReader).readDelta(
- any(KernelUidCpuFreqTimeReader.Callback.class));
+ }).when(mCpuUidFreqTimeReader).readDelta(
+ any(KernelCpuUidFreqTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, false);
@@ -1022,7 +1033,7 @@
}
assertNull("There shouldn't be an entry for invalid uid=" + invalidUid,
mBatteryStatsImpl.getUidStats().get(invalidUid));
- verify(mKernelUidCpuFreqTimeReader).removeUid(invalidUid);
+ verify(mCpuUidFreqTimeReader).removeUid(invalidUid);
}
@Test
@@ -1039,14 +1050,14 @@
});
final long[] uidTimesMs = {8000, 25000, 3000, 0, 42000};
doAnswer(invocation -> {
- final KernelUidCpuActiveTimeReader.Callback callback =
- (KernelUidCpuActiveTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidActiveTimeReader.Callback callback =
+ (KernelCpuUidActiveTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuActiveTime(testUids[i], uidTimesMs[i]);
+ callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
return null;
- }).when(mKernelUidCpuActiveTimeReader).readDelta(
- any(KernelUidCpuActiveTimeReader.Callback.class));
+ }).when(mCpuUidActiveTimeReader).readDelta(
+ any(KernelCpuUidActiveTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuActiveTimesLocked(true);
@@ -1065,14 +1076,14 @@
updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
final long[] deltasMs = {43000, 3345000, 2143000, 123000, 4554000};
doAnswer(invocation -> {
- final KernelUidCpuActiveTimeReader.Callback callback =
- (KernelUidCpuActiveTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidActiveTimeReader.Callback callback =
+ (KernelCpuUidActiveTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuActiveTime(testUids[i], deltasMs[i]);
+ callback.onUidCpuTime(testUids[i], deltasMs[i]);
}
return null;
- }).when(mKernelUidCpuActiveTimeReader).readDelta(
- any(KernelUidCpuActiveTimeReader.Callback.class));
+ }).when(mCpuUidActiveTimeReader).readDelta(
+ any(KernelCpuUidActiveTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuActiveTimesLocked(true);
@@ -1103,16 +1114,16 @@
});
final long[] uidTimesMs = {8000, 25000, 3000, 0, 42000};
doAnswer(invocation -> {
- final KernelUidCpuActiveTimeReader.Callback callback =
- (KernelUidCpuActiveTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidActiveTimeReader.Callback callback =
+ (KernelCpuUidActiveTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuActiveTime(testUids[i], uidTimesMs[i]);
+ callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
// And one for the invalid uid
- callback.onUidCpuActiveTime(invalidUid, 1200L);
+ callback.onUidCpuTime(invalidUid, 1200L);
return null;
- }).when(mKernelUidCpuActiveTimeReader).readDelta(
- any(KernelUidCpuActiveTimeReader.Callback.class));
+ }).when(mCpuUidActiveTimeReader).readDelta(
+ any(KernelCpuUidActiveTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuActiveTimesLocked(true);
@@ -1126,7 +1137,7 @@
}
assertNull("There shouldn't be an entry for invalid uid=" + invalidUid,
mBatteryStatsImpl.getUidStats().get(invalidUid));
- verify(mKernelUidCpuActiveTimeReader).removeUid(invalidUid);
+ verify(mCpuUidActiveTimeReader).removeUid(invalidUid);
}
@Test
@@ -1147,14 +1158,14 @@
{8000, 0}
};
doAnswer(invocation -> {
- final KernelUidCpuClusterTimeReader.Callback callback =
- (KernelUidCpuClusterTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidClusterTimeReader.Callback callback =
+ (KernelCpuUidClusterTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuPolicyTime(testUids[i], uidTimesMs[i]);
+ callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
return null;
- }).when(mKernelUidCpuClusterTimeReader).readDelta(
- any(KernelUidCpuClusterTimeReader.Callback.class));
+ }).when(mCpuUidClusterTimeReader).readDelta(
+ any(KernelCpuUidClusterTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuClusterTimesLocked(true);
@@ -1177,14 +1188,14 @@
{43000, 3345000}
};
doAnswer(invocation -> {
- final KernelUidCpuClusterTimeReader.Callback callback =
- (KernelUidCpuClusterTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidClusterTimeReader.Callback callback =
+ (KernelCpuUidClusterTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuPolicyTime(testUids[i], deltasMs[i]);
+ callback.onUidCpuTime(testUids[i], deltasMs[i]);
}
return null;
- }).when(mKernelUidCpuClusterTimeReader).readDelta(
- any(KernelUidCpuClusterTimeReader.Callback.class));
+ }).when(mCpuUidClusterTimeReader).readDelta(
+ any(KernelCpuUidClusterTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuClusterTimesLocked(true);
@@ -1193,7 +1204,8 @@
for (int i = 0; i < testUids.length; ++i) {
final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
assertNotNull("No entry for uid=" + testUids[i], u);
- assertArrayEquals("Unexpected cpu cluster time for uid=" + testUids[i], sum(uidTimesMs[i], deltasMs[i]),
+ assertArrayEquals("Unexpected cpu cluster time for uid=" + testUids[i],
+ sum(uidTimesMs[i], deltasMs[i]),
u.getCpuClusterTimes());
}
}
@@ -1219,16 +1231,16 @@
{8000, 0}
};
doAnswer(invocation -> {
- final KernelUidCpuClusterTimeReader.Callback callback =
- (KernelUidCpuClusterTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidClusterTimeReader.Callback callback =
+ (KernelCpuUidClusterTimeReader.Callback) invocation.getArguments()[0];
for (int i = 0; i < testUids.length; ++i) {
- callback.onUidCpuPolicyTime(testUids[i], uidTimesMs[i]);
+ callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
// And one for the invalid uid
- callback.onUidCpuPolicyTime(invalidUid, new long[] {400, 1000});
+ callback.onUidCpuTime(invalidUid, new long[]{400, 1000});
return null;
- }).when(mKernelUidCpuClusterTimeReader).readDelta(
- any(KernelUidCpuClusterTimeReader.Callback.class));
+ }).when(mCpuUidClusterTimeReader).readDelta(
+ any(KernelCpuUidClusterTimeReader.Callback.class));
// RUN
mBatteryStatsImpl.readKernelUidCpuClusterTimesLocked(true);
@@ -1242,7 +1254,7 @@
}
assertNull("There shouldn't be an entry for invalid uid=" + invalidUid,
mBatteryStatsImpl.getUidStats().get(invalidUid));
- verify(mKernelUidCpuClusterTimeReader).removeUid(invalidUid);
+ verify(mCpuUidClusterTimeReader).removeUid(invalidUid);
}
@Test
@@ -1268,25 +1280,25 @@
mClocks.realtime = mClocks.uptime = 400_000;
mBatteryStatsImpl.clearPendingRemovedUids();
assertEquals(1, mBatteryStatsImpl.getPendingRemovedUids().size());
- verify(mKernelUidCpuActiveTimeReader).removeUid(1);
- verify(mKernelUidCpuActiveTimeReader).removeUidsInRange(5, 10);
- verify(mKernelUidCpuClusterTimeReader).removeUid(1);
- verify(mKernelUidCpuClusterTimeReader).removeUidsInRange(5, 10);
- verify(mKernelUidCpuFreqTimeReader).removeUid(1);
- verify(mKernelUidCpuFreqTimeReader).removeUidsInRange(5, 10);
- verify(mKernelUidCpuTimeReader).removeUid(1);
- verify(mKernelUidCpuTimeReader).removeUidsInRange(5, 10);
+ verify(mCpuUidActiveTimeReader).removeUid(1);
+ verify(mCpuUidActiveTimeReader).removeUidsInRange(5, 10);
+ verify(mCpuUidClusterTimeReader).removeUid(1);
+ verify(mCpuUidClusterTimeReader).removeUidsInRange(5, 10);
+ verify(mCpuUidFreqTimeReader).removeUid(1);
+ verify(mCpuUidFreqTimeReader).removeUidsInRange(5, 10);
+ verify(mCpuUidUserSysTimeReader).removeUid(1);
+ verify(mCpuUidUserSysTimeReader).removeUidsInRange(5, 10);
mClocks.realtime = mClocks.uptime = 800_000;
mBatteryStatsImpl.clearPendingRemovedUids();
assertEquals(0, mBatteryStatsImpl.getPendingRemovedUids().size());
- verify(mKernelUidCpuActiveTimeReader).removeUid(100);
- verify(mKernelUidCpuClusterTimeReader).removeUid(100);
- verify(mKernelUidCpuFreqTimeReader).removeUid(100);
- verify(mKernelUidCpuTimeReader).removeUid(100);
+ verify(mCpuUidActiveTimeReader).removeUid(100);
+ verify(mCpuUidClusterTimeReader).removeUid(100);
+ verify(mCpuUidFreqTimeReader).removeUid(100);
+ verify(mCpuUidUserSysTimeReader).removeUid(100);
- verifyNoMoreInteractions(mKernelUidCpuActiveTimeReader, mKernelUidCpuClusterTimeReader,
- mKernelUidCpuFreqTimeReader, mKernelUidCpuTimeReader);
+ verifyNoMoreInteractions(mCpuUidActiveTimeReader, mCpuUidClusterTimeReader,
+ mCpuUidFreqTimeReader, mCpuUidUserSysTimeReader);
}
private void updateTimeBasesLocked(boolean unplugged, int screenState,
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
index dc93675..0771829 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
@@ -39,6 +39,7 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
import com.android.internal.util.ArrayUtils;
import org.junit.Before;
@@ -54,7 +55,7 @@
@RunWith(AndroidJUnit4.class)
public class BatteryStatsImplTest {
@Mock
- private KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader;
+ private KernelCpuUidFreqTimeReader mKernelUidCpuFreqTimeReader;
@Mock
private KernelSingleUidTimeReader mKernelSingleUidTimeReader;
@@ -67,7 +68,7 @@
when(mKernelUidCpuFreqTimeReader.allUidTimesAvailable()).thenReturn(true);
when(mKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true);
mBatteryStatsImpl = new MockBatteryStatsImpl()
- .setKernelUidCpuFreqTimeReader(mKernelUidCpuFreqTimeReader)
+ .setKernelCpuUidFreqTimeReader(mKernelUidCpuFreqTimeReader)
.setKernelSingleUidTimeReader(mKernelSingleUidTimeReader);
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index d69e1d1..a6329298 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -38,7 +38,6 @@
BatteryStatsTimerTest.class,
BatteryStatsUidTest.class,
BatteryStatsUserLifecycleTests.class,
- KernelCpuProcReaderTest.class,
KernelCpuProcStringReaderTest.class,
KernelCpuUidActiveTimeReaderTest.class,
KernelCpuUidClusterTimeReaderTest.class,
@@ -46,9 +45,6 @@
KernelCpuUidUserSysTimeReaderTest.class,
KernelMemoryBandwidthStatsTest.class,
KernelSingleUidTimeReaderTest.class,
- KernelUidCpuFreqTimeReaderTest.class,
- KernelUidCpuActiveTimeReaderTest.class,
- KernelUidCpuClusterTimeReaderTest.class,
KernelWakelockReaderTest.class,
LongSamplingCounterTest.class,
LongSamplingCounterArrayTest.class,
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java
deleted file mode 100644
index a25a7489..0000000
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java
+++ /dev/null
@@ -1,199 +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.
- */
-
-package com.android.internal.os;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.os.FileUtils;
-import android.os.SystemClock;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.nio.file.Files;
-import java.util.Arrays;
-import java.util.Random;
-
-/**
- * Test class for {@link KernelCpuProcReader}.
- *
- * $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuProcReaderTest
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class KernelCpuProcReaderTest {
-
- private File mRoot;
- private File mTestDir;
- private File mTestFile;
- private Random mRand = new Random();
-
- private KernelCpuProcReader mKernelCpuProcReader;
-
- private Context getContext() {
- return InstrumentationRegistry.getContext();
- }
-
- @Before
- public void setUp() {
- mTestDir = getContext().getDir("test", Context.MODE_PRIVATE);
- mRoot = getContext().getFilesDir();
- mTestFile = new File(mTestDir, "test.file");
- mKernelCpuProcReader = new KernelCpuProcReader(mTestFile.getAbsolutePath());
- }
-
- @After
- public void tearDown() throws Exception {
- FileUtils.deleteContents(mTestDir);
- FileUtils.deleteContents(mRoot);
- }
-
-
- /**
- * Tests that reading will return null if the file does not exist.
- */
- @Test
- public void testReadInvalidFile() throws Exception {
- assertEquals(null, mKernelCpuProcReader.readBytes());
- }
-
- /**
- * Tests that reading will always return null after 5 failures.
- */
- @Test
- public void testReadErrorsLimit() throws Exception {
- mKernelCpuProcReader.setThrottleInterval(0);
- for (int i = 0; i < 3; i++) {
- assertNull(mKernelCpuProcReader.readBytes());
- SystemClock.sleep(50);
- }
-
- final byte[] data = new byte[1024];
- mRand.nextBytes(data);
- try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
- os.write(data);
- }
- assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
-
- assertTrue(mTestFile.delete());
- for (int i = 0; i < 3; i++) {
- assertNull(mKernelCpuProcReader.readBytes());
- SystemClock.sleep(50);
- }
- try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
- os.write(data);
- }
- assertNull(mKernelCpuProcReader.readBytes());
- }
-
- /**
- * Tests reading functionality.
- */
- @Test
- public void testSimpleRead() throws Exception {
- final byte[] data = new byte[1024];
- mRand.nextBytes(data);
- try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
- os.write(data);
- }
- assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
- }
-
- /**
- * Tests multiple reading functionality.
- */
- @Test
- public void testMultipleRead() throws Exception {
- mKernelCpuProcReader.setThrottleInterval(0);
- for (int i = 0; i < 100; i++) {
- final byte[] data = new byte[mRand.nextInt(102400) + 4];
- mRand.nextBytes(data);
- try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
- os.write(data);
- }
- assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
- assertTrue(mTestFile.delete());
- }
- }
-
- /**
- * Tests reading with resizing.
- */
- @Test
- public void testReadWithResize() throws Exception {
- final byte[] data = new byte[128001];
- mRand.nextBytes(data);
- try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
- os.write(data);
- }
- assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
- }
-
- /**
- * Tests that reading a file over the limit (1MB) will return null.
- */
- @Test
- public void testReadOverLimit() throws Exception {
- final byte[] data = new byte[1228800];
- mRand.nextBytes(data);
- try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
- os.write(data);
- }
- assertNull(mKernelCpuProcReader.readBytes());
- }
-
- /**
- * Tests throttling. Deleting underlying file should not affect cache.
- */
- @Test
- public void testThrottle() throws Exception {
- mKernelCpuProcReader.setThrottleInterval(3000);
- final byte[] data = new byte[20001];
- mRand.nextBytes(data);
- try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
- os.write(data);
- }
- assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
- assertTrue(mTestFile.delete());
- for (int i = 0; i < 5; i++) {
- assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
- SystemClock.sleep(10);
- }
- SystemClock.sleep(5000);
- assertNull(mKernelCpuProcReader.readBytes());
- }
-
- private byte[] toArray(ByteBuffer buffer) {
- assertNotNull(buffer);
- byte[] arr = new byte[buffer.remaining()];
- buffer.get(arr);
- return arr;
- }
-}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java
index 7a31605..cbd2ba4 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java
@@ -299,9 +299,10 @@
assertTrue(mTestFile.delete());
try (BufferedWriter w = Files.newBufferedWriter(mTestFile.toPath())) {
w.write(data1);
- modify.countDown();
} catch (Throwable e) {
errs.add(e);
+ } finally {
+ modify.countDown();
}
}, 600, TimeUnit.MILLISECONDS);
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuActiveTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuActiveTimeReaderTest.java
deleted file mode 100644
index 12f6c18..0000000
--- a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuActiveTimeReaderTest.java
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.os;
-
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.Random;
-
-/**
- * Test class for {@link KernelUidCpuActiveTimeReader}.
- *
- * To run it:
- * bit FrameworksCoreTests:com.android.internal.os.KernelUidCpuActiveTimeReaderTest
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class KernelUidCpuActiveTimeReaderTest {
- @Mock
- private KernelCpuProcReader mProcReader;
- @Mock
- private KernelUidCpuActiveTimeReader.Callback mCallback;
- private KernelUidCpuActiveTimeReader mReader;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mReader = new KernelUidCpuActiveTimeReader(mProcReader);
- mReader.setThrottleInterval(0);
- }
-
- @Test
- public void testReadDelta() {
- final int cores = 8;
- final int[] uids = {1, 22, 333, 4444, 5555};
-
- final long[][] times = increaseTime(new long[uids.length][cores]);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times));
- mReader.readDelta(mCallback);
- for (int i = 0; i < uids.length; i++) {
- verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(times[i]));
- }
- verifyNoMoreInteractions(mCallback);
-
- // Verify that a second call will only return deltas.
- Mockito.reset(mCallback);
- final long[][] times1 = increaseTime(times);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times1));
- mReader.readDelta(mCallback);
- for (int i = 0; i < uids.length; i++) {
- verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times1[i], times[i])));
- }
- verifyNoMoreInteractions(mCallback);
-
- // Verify that there won't be a callback if the proc file values didn't change.
- Mockito.reset(mCallback);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times1));
- mReader.readDelta(mCallback);
- verifyNoMoreInteractions(mCallback);
-
- // Verify that calling with a null callback doesn't result in any crashes
- Mockito.reset(mCallback);
- final long[][] times2 = increaseTime(times1);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times2));
- mReader.readDelta(null);
-
- // Verify that the readDelta call will only return deltas when
- // the previous call had null callback.
- Mockito.reset(mCallback);
- final long[][] times3 = increaseTime(times2);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times3));
- mReader.readDelta(mCallback);
- for (int i = 0; i < uids.length; ++i) {
- verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times3[i], times2[i])));
- }
- verifyNoMoreInteractions(mCallback);
- }
-
- @Test
- public void testReadAbsolute() {
- final int cores = 8;
- final int[] uids = {1, 22, 333, 4444, 5555};
-
- final long[][] times = increaseTime(new long[uids.length][cores]);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times));
- mReader.readAbsolute(mCallback);
- for (int i = 0; i < uids.length; i++) {
- verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(times[i]));
- }
- verifyNoMoreInteractions(mCallback);
-
- // Verify that a second call still returns absolute values
- Mockito.reset(mCallback);
- final long[][] times1 = increaseTime(times);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times1));
- mReader.readAbsolute(mCallback);
- for (int i = 0; i < uids.length; i++) {
- verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(times1[i]));
- }
- verifyNoMoreInteractions(mCallback);
- }
-
- @Test
- public void testReadDelta_malformedData() {
- final int cores = 8;
- final int[] uids = {1, 22, 333, 4444, 5555};
- final long[][] times = increaseTime(new long[uids.length][cores]);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times));
- mReader.readDelta(mCallback);
- for (int i = 0; i < uids.length; i++) {
- verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(times[i]));
- }
- verifyNoMoreInteractions(mCallback);
-
- // Verify that there is no callback if subsequent call is in wrong format.
- Mockito.reset(mCallback);
- final long[][] times1 = increaseTime(times);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times1).putInt(0, 5));
- mReader.readDelta(mCallback);
- verifyNoMoreInteractions(mCallback);
-
- // Verify that the internal state was not modified if the given core count does not match
- // the following # of entries.
- Mockito.reset(mCallback);
- final long[][] times2 = increaseTime(times);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times2));
- mReader.readDelta(mCallback);
- for (int i = 0; i < uids.length; i++) {
- verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times2[i], times[i])));
- }
- verifyNoMoreInteractions(mCallback);
-
- // Verify that there is no callback if any value in the proc file is -ve.
- Mockito.reset(mCallback);
- final long[][] times3 = increaseTime(times2);
- times3[uids.length - 1][cores - 1] *= -1;
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times3));
- mReader.readDelta(mCallback);
- for (int i = 0; i < uids.length - 1; ++i) {
- verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times3[i], times2[i])));
- }
- verifyNoMoreInteractions(mCallback);
-
- // Verify that the internal state was not modified when the proc file had -ve value.
- Mockito.reset(mCallback);
- for (int i = 0; i < cores; i++) {
- times3[uids.length - 1][i] = times2[uids.length - 1][i] + uids[uids.length - 1] * 2520;
- }
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times3));
- mReader.readDelta(mCallback);
- verify(mCallback).onUidCpuActiveTime(uids[uids.length - 1],
- getTotal(subtract(times3[uids.length - 1], times2[uids.length - 1])));
- verifyNoMoreInteractions(mCallback);
-
- // Verify that there is no callback if the values in the proc file are decreased.
- Mockito.reset(mCallback);
- final long[][] times4 = increaseTime(times3);
- System.arraycopy(times3[uids.length - 1], 0, times4[uids.length - 1], 0, cores);
- times4[uids.length - 1][cores - 1] -= 100;
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times4));
- mReader.readDelta(mCallback);
- for (int i = 0; i < uids.length - 1; ++i) {
- verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times4[i], times3[i])));
- }
- verifyNoMoreInteractions(mCallback);
-
- // Verify that the internal state was not modified when the proc file had decreased values.
- Mockito.reset(mCallback);
- for (int i = 0; i < cores; i++) {
- times4[uids.length - 1][i] = times3[uids.length - 1][i] + uids[uids.length - 1] * 2520;
- }
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times4));
- mReader.readDelta(mCallback);
- verify(mCallback).onUidCpuActiveTime(uids[uids.length - 1],
- getTotal(subtract(times4[uids.length - 1], times3[uids.length - 1])));
- verifyNoMoreInteractions(mCallback);
- }
-
- private long[] subtract(long[] a1, long[] a2) {
- long[] val = new long[a1.length];
- for (int i = 0; i < val.length; ++i) {
- val[i] = a1[i] - a2[i];
- }
- return val;
- }
-
- /**
- * Unit of original and return value is 10ms. What's special about 2520? 2520 is LCM of 1, 2, 3,
- * ..., 10. So that when wedivide shared cpu time by concurrent thread count, we always get a
- * nice integer, avoiding rounding errors.
- */
- private long[][] increaseTime(long[][] original) {
- long[][] newTime = new long[original.length][original[0].length];
- Random rand = new Random();
- for (int i = 0; i < original.length; i++) {
- for (int j = 0; j < original[0].length; j++) {
- newTime[i][j] = original[i][j] + rand.nextInt(1000) * 2520 + 2520;
- }
- }
- return newTime;
- }
-
- // Unit of times is 10ms
- private long getTotal(long[] times) {
- long sum = 0;
- for (int i = 0; i < times.length; i++) {
- sum += times[i] * 10 / (i + 1);
- }
- return sum;
- }
-
- /**
- * Format uids and times (in 10ms) into the following format:
- * [n, uid0, time0a, time0b, ..., time0n,
- * uid1, time1a, time1b, ..., time1n,
- * uid2, time2a, time2b, ..., time2n, etc.]
- * where n is the total number of cpus (num_possible_cpus)
- */
- private ByteBuffer getUidTimesBytes(int[] uids, long[][] times) {
- int size = (1 + uids.length * (times[0].length + 1)) * 4;
- ByteBuffer buf = ByteBuffer.allocate(size);
- buf.order(ByteOrder.nativeOrder());
- buf.putInt(times[0].length);
- for (int i = 0; i < uids.length; i++) {
- buf.putInt(uids[i]);
- for (int j = 0; j < times[i].length; j++) {
- buf.putInt((int) times[i][j]);
- }
- }
- buf.flip();
- return buf.order(ByteOrder.nativeOrder());
- }
-}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuClusterTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuClusterTimeReaderTest.java
deleted file mode 100644
index 532f337..0000000
--- a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuClusterTimeReaderTest.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.os;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.Mockito.when;
-
-import android.util.SparseArray;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.Arrays;
-import java.util.Random;
-
-/**
- * Test class for {@link KernelUidCpuClusterTimeReader}.
- *
- * To run it:
- * bit FrameworksCoreTests:com.android.internal.os.KernelUidCpuClusterTimeReaderTest
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class KernelUidCpuClusterTimeReaderTest {
- @Mock
- private KernelCpuProcReader mProcReader;
- private KernelUidCpuClusterTimeReader mReader;
- private VerifiableCallback mCallback;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mReader = new KernelUidCpuClusterTimeReader(mProcReader);
- mCallback = new VerifiableCallback();
- mReader.setThrottleInterval(0);
- }
-
- @Test
- public void testReadDelta() throws Exception {
- VerifiableCallback cb = new VerifiableCallback();
- final int cores = 6;
- final int[] clusters = {2, 4};
- final int[] uids = {1, 22, 333, 4444, 5555};
-
- // Verify initial call
- final long[][] times = increaseTime(new long[uids.length][cores]);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times));
- mReader.readDelta(cb);
- for (int i = 0; i < uids.length; i++) {
- cb.verify(uids[i], getTotal(clusters, times[i]));
- }
- cb.verifyNoMoreInteractions();
-
- // Verify that a second call will only return deltas.
- cb.clear();
- Mockito.reset(mProcReader);
- final long[][] times1 = increaseTime(times);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1));
- mReader.readDelta(cb);
- for (int i = 0; i < uids.length; i++) {
- cb.verify(uids[i], getTotal(clusters, subtract(times1[i], times[i])));
- }
- cb.verifyNoMoreInteractions();
-
- // Verify that there won't be a callback if the proc file values didn't change.
- cb.clear();
- Mockito.reset(mProcReader);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1));
- mReader.readDelta(cb);
- cb.verifyNoMoreInteractions();
-
- // Verify that calling with a null callback doesn't result in any crashes
- Mockito.reset(mProcReader);
- final long[][] times2 = increaseTime(times1);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times2));
- mReader.readDelta(null);
-
- // Verify that the readDelta call will only return deltas when
- // the previous call had null callback.
- cb.clear();
- Mockito.reset(mProcReader);
- final long[][] times3 = increaseTime(times2);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times3));
- mReader.readDelta(cb);
- for (int i = 0; i < uids.length; i++) {
- cb.verify(uids[i], getTotal(clusters, subtract(times3[i], times2[i])));
- }
- cb.verifyNoMoreInteractions();
-
- }
-
- @Test
- public void testReadAbsolute() throws Exception {
- VerifiableCallback cb = new VerifiableCallback();
- final int cores = 6;
- final int[] clusters = {2, 4};
- final int[] uids = {1, 22, 333, 4444, 5555};
-
- // Verify return absolute value
- final long[][] times = increaseTime(new long[uids.length][cores]);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times));
- mReader.readAbsolute(cb);
- for (int i = 0; i < uids.length; i++) {
- cb.verify(uids[i], getTotal(clusters, times[i]));
- }
- cb.verifyNoMoreInteractions();
-
- // Verify that a second call should return the same absolute value
- cb.clear();
- Mockito.reset(mProcReader);
- final long[][] times1 = increaseTime(times);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1));
- mReader.readAbsolute(cb);
- for (int i = 0; i < uids.length; i++) {
- cb.verify(uids[i], getTotal(clusters, times1[i]));
- }
- cb.verifyNoMoreInteractions();
- }
-
- @Test
- public void testReadDelta_malformedData() throws Exception {
- final int cores = 6;
- final int[] clusters = {2, 4};
- final int[] uids = {1, 22, 333, 4444, 5555};
-
- // Verify initial call
- final long[][] times = increaseTime(new long[uids.length][cores]);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times));
- mReader.readDelta(mCallback);
- for (int i = 0; i < uids.length; i++) {
- mCallback.verify(uids[i], getTotal(clusters, times[i]));
- }
- mCallback.verifyNoMoreInteractions();
-
- // Verify that there is no callback if a call has wrong format
- mCallback.clear();
- Mockito.reset(mProcReader);
- final long[][] temp = increaseTime(times);
- final long[][] times1 = new long[uids.length][];
- for (int i = 0; i < temp.length; i++) {
- times1[i] = Arrays.copyOfRange(temp[i], 0, 4);
- }
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1));
- mReader.readDelta(mCallback);
- mCallback.verifyNoMoreInteractions();
-
- // Verify that the internal state was not modified if the given core count does not match
- // the following # of entries.
- mCallback.clear();
- Mockito.reset(mProcReader);
- final long[][] times2 = increaseTime(times);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times2));
- mReader.readDelta(mCallback);
- for (int i = 0; i < uids.length; i++) {
- mCallback.verify(uids[i], getTotal(clusters, subtract(times2[i], times[i])));
- }
- mCallback.verifyNoMoreInteractions();
-
- // Verify that there is no callback if any value in the proc file is -ve.
- mCallback.clear();
- Mockito.reset(mProcReader);
- final long[][] times3 = increaseTime(times2);
- times3[uids.length - 1][cores - 1] *= -1;
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times3));
- mReader.readDelta(mCallback);
- for (int i = 0; i < uids.length - 1; i++) {
- mCallback.verify(uids[i], getTotal(clusters, subtract(times3[i], times2[i])));
- }
- mCallback.verifyNoMoreInteractions();
-
- // Verify that the internal state was not modified when the proc file had -ve value.
- mCallback.clear();
- Mockito.reset(mProcReader);
- for (int i = 0; i < cores; i++) {
- times3[uids.length - 1][i] = times2[uids.length - 1][i] + uids[uids.length - 1] * 2520;
- }
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times3));
- mReader.readDelta(mCallback);
- mCallback.verify(uids[uids.length - 1],
- getTotal(clusters, subtract(times3[uids.length - 1], times2[uids.length - 1])));
-
- // Verify that there is no callback if the values in the proc file are decreased.
- mCallback.clear();
- Mockito.reset(mProcReader);
- final long[][] times4 = increaseTime(times3);
- System.arraycopy(times3[uids.length - 1], 0, times4[uids.length - 1], 0, cores);
- times4[uids.length - 1][cores - 1] -= 100;
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times4));
- mReader.readDelta(mCallback);
- for (int i = 0; i < uids.length - 1; i++) {
- mCallback.verify(uids[i], getTotal(clusters, subtract(times4[i], times3[i])));
- }
- mCallback.verifyNoMoreInteractions();
-
- // Verify that the internal state was not modified when the proc file had decreased values.
- mCallback.clear();
- Mockito.reset(mProcReader);
- for (int i = 0; i < cores; i++) {
- times4[uids.length - 1][i] = times3[uids.length - 1][i] + uids[uids.length - 1] * 2520;
- }
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times4));
- mReader.readDelta(mCallback);
- mCallback.verify(uids[uids.length - 1],
- getTotal(clusters, subtract(times3[uids.length - 1], times2[uids.length - 1])));
- mCallback.verifyNoMoreInteractions();
- }
-
-
- private long[] subtract(long[] a1, long[] a2) {
- long[] val = new long[a1.length];
- for (int i = 0; i < val.length; ++i) {
- val[i] = a1[i] - a2[i];
- }
- return val;
- }
-
- /**
- * Unit is 10ms. What's special about 2520? 2520 is LCM of 1, 2, 3, ..., 10. So that when we
- * divide shared cpu time by concurrent thread count, we always get a nice integer, avoiding
- * rounding errors.
- */
- private long[][] increaseTime(long[][] original) {
- long[][] newTime = new long[original.length][original[0].length];
- Random rand = new Random();
- for (int i = 0; i < original.length; i++) {
- for (int j = 0; j < original[0].length; j++) {
- newTime[i][j] = original[i][j] + rand.nextInt(1000) * 2520 + 2520;
- }
- }
- return newTime;
- }
-
- // Format an array of cluster times according to the algorithm in KernelUidCpuClusterTimeReader
- private long[] getTotal(int[] cluster, long[] times) {
- int core = 0;
- long[] sumTimes = new long[cluster.length];
- for (int i = 0; i < cluster.length; i++) {
- double sum = 0;
- for (int j = 0; j < cluster[i]; j++) {
- sum += (double) times[core++] * 10 / (j + 1);
- }
- sumTimes[i] = (long) sum;
- }
- return sumTimes;
- }
-
- private class VerifiableCallback implements KernelUidCpuClusterTimeReader.Callback {
-
- SparseArray<long[]> mData = new SparseArray<>();
- int count = 0;
-
- public void verify(int uid, long[] cpuClusterTimeMs) {
- long[] array = mData.get(uid);
- assertNotNull(array);
- assertArrayEquals(cpuClusterTimeMs, array);
- count++;
- }
-
- public void clear() {
- mData.clear();
- count = 0;
- }
-
- @Override
- public void onUidCpuPolicyTime(int uid, long[] cpuClusterTimeMs) {
- long[] array = new long[cpuClusterTimeMs.length];
- System.arraycopy(cpuClusterTimeMs, 0, array, 0, array.length);
- mData.put(uid, array);
- }
-
- public void verifyNoMoreInteractions() {
- assertEquals(mData.size(), count);
- }
- }
-
- /**
- * Format uids and times (in 10ms) into the following format:
- * [n, x0, ..., xn, uid0, time0a, time0b, ..., time0n,
- * uid1, time1a, time1b, ..., time1n,
- * uid2, time2a, time2b, ..., time2n, etc.]
- * where n is the number of policies
- * xi is the number cpus on a particular policy
- */
- private ByteBuffer getUidTimesBytes(int[] uids, int[] clusters, long[][] times) {
- int size = (1 + clusters.length + uids.length * (times[0].length + 1)) * 4;
- ByteBuffer buf = ByteBuffer.allocate(size);
- buf.order(ByteOrder.nativeOrder());
- buf.putInt(clusters.length);
- for (int i = 0; i < clusters.length; i++) {
- buf.putInt(clusters[i]);
- }
- for (int i = 0; i < uids.length; i++) {
- buf.putInt(uids[i]);
- for (int j = 0; j < times[i].length; j++) {
- buf.putInt((int) (times[i][j]));
- }
- }
- buf.flip();
- return buf.order(ByteOrder.nativeOrder());
- }
-}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java
deleted file mode 100644
index 6d2980b..0000000
--- a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java
+++ /dev/null
@@ -1,331 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.os;
-
-import static org.junit.Assert.assertArrayEquals;
-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.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.util.SparseArray;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.io.BufferedReader;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.Arrays;
-
-/**
- * Test class for {@link KernelUidCpuFreqTimeReader}.
- *
- * To run the tests, use
- *
- * runtest -c com.android.internal.os.KernelUidCpuFreqTimeReaderTest frameworks-core
- *
- * or the following steps:
- *
- * Build: m FrameworksCoreTests
- * Install: adb install -r \
- * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreTests/FrameworksCoreTests.apk
- * Run: adb shell am instrument -e class com.android.internal.os.KernelUidCpuFreqTimeReaderTest -w \
- * com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner
- *
- * or
- *
- * bit FrameworksCoreTests:com.android.internal.os.KernelUidCpuFreqTimeReaderTest
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class KernelUidCpuFreqTimeReaderTest {
- @Mock
- private BufferedReader mBufferedReader;
- @Mock
- private KernelUidCpuFreqTimeReader.Callback mCallback;
- @Mock
- private PowerProfile mPowerProfile;
- @Mock
- private KernelCpuProcReader mProcReader;
-
- private KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mKernelUidCpuFreqTimeReader = new KernelUidCpuFreqTimeReader(mProcReader);
- mKernelUidCpuFreqTimeReader.setThrottleInterval(0);
- }
-
- @Test
- public void testReadFreqs_perClusterTimesNotAvailable() throws Exception {
- final long[][] freqs = {
- {1, 12, 123, 1234},
- {1, 12, 123, 23, 123, 1234, 12345, 123456},
- {1, 12, 123, 23, 123, 1234, 12345, 123456, 12, 123, 12345},
- {1, 12, 123, 23, 2345, 234567}
- };
- final int[] numClusters = {2, 2, 3, 1};
- final int[][] numFreqs = {{3, 6}, {4, 5}, {3, 5, 4}, {3}};
- for (int i = 0; i < freqs.length; ++i) {
- setCpuClusterFreqs(numClusters[i], numFreqs[i]);
- when(mBufferedReader.readLine()).thenReturn(getFreqsLine(freqs[i]));
- long[] actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs(
- mBufferedReader, mPowerProfile);
- assertArrayEquals(freqs[i], actualFreqs);
- verifyZeroInteractions(mCallback);
- final String errMsg = String.format("Freqs=%s, nClusters=%d, nFreqs=%s",
- Arrays.toString(freqs[i]), numClusters[i], Arrays.toString(numFreqs[i]));
- assertFalse(errMsg, mKernelUidCpuFreqTimeReader.perClusterTimesAvailable());
-
- // Verify that a second call won't read the proc file again
- Mockito.reset(mBufferedReader);
- actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile);
- assertArrayEquals(freqs[i], actualFreqs);
- assertFalse(errMsg, mKernelUidCpuFreqTimeReader.perClusterTimesAvailable());
-
- // Prepare for next iteration
- Mockito.reset(mBufferedReader, mPowerProfile);
- }
- }
-
- @Test
- public void testReadFreqs_perClusterTimesAvailable() throws Exception {
- final long[][] freqs = {
- {1, 12, 123, 1234},
- {1, 12, 123, 23, 123, 1234, 12345, 123456},
- {1, 12, 123, 23, 123, 1234, 12345, 123456, 12, 123, 12345, 1234567}
- };
- final int[] numClusters = {1, 2, 3};
- final int[][] numFreqs = {{4}, {3, 5}, {3, 5, 4}};
- for (int i = 0; i < freqs.length; ++i) {
- setCpuClusterFreqs(numClusters[i], numFreqs[i]);
- when(mBufferedReader.readLine()).thenReturn(getFreqsLine(freqs[i]));
- long[] actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs(
- mBufferedReader, mPowerProfile);
- assertArrayEquals(freqs[i], actualFreqs);
- verifyZeroInteractions(mCallback);
- final String errMsg = String.format("Freqs=%s, nClusters=%d, nFreqs=%s",
- Arrays.toString(freqs[i]), numClusters[i], Arrays.toString(numFreqs[i]));
- assertTrue(errMsg, mKernelUidCpuFreqTimeReader.perClusterTimesAvailable());
-
- // Verify that a second call won't read the proc file again
- Mockito.reset(mBufferedReader);
- actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile);
- assertArrayEquals(freqs[i], actualFreqs);
- assertTrue(errMsg, mKernelUidCpuFreqTimeReader.perClusterTimesAvailable());
-
- // Prepare for next iteration
- Mockito.reset(mBufferedReader, mPowerProfile);
- }
- }
-
- @Test
- public void testReadDelta_Binary() throws Exception {
- VerifiableCallback cb = new VerifiableCallback();
- final long[] freqs = {110, 123, 145, 167, 289, 997};
- final int[] uids = {1, 22, 333, 444, 555};
- final long[][] times = new long[uids.length][freqs.length];
- for (int i = 0; i < uids.length; ++i) {
- for (int j = 0; j < freqs.length; ++j) {
- times[i][j] = uids[i] * freqs[j] * 10;
- }
- }
- when(mBufferedReader.readLine()).thenReturn(getFreqsLine(freqs));
- long[] actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mBufferedReader, mPowerProfile);
-
- assertArrayEquals(freqs, actualFreqs);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times));
- mKernelUidCpuFreqTimeReader.readDeltaImpl(cb);
- for (int i = 0; i < uids.length; ++i) {
- cb.verify(uids[i], times[i]);
- }
- cb.verifyNoMoreInteractions();
-
- // Verify that a second call will only return deltas.
- cb.clear();
- Mockito.reset(mProcReader);
- final long[][] newTimes1 = new long[uids.length][freqs.length];
- for (int i = 0; i < uids.length; ++i) {
- for (int j = 0; j < freqs.length; ++j) {
- newTimes1[i][j] = times[i][j] + (uids[i] + freqs[j]) * 50;
- }
- }
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes1));
- mKernelUidCpuFreqTimeReader.readDeltaImpl(cb);
- for (int i = 0; i < uids.length; ++i) {
- cb.verify(uids[i], subtract(newTimes1[i], times[i]));
- }
- cb.verifyNoMoreInteractions();
-
- // Verify that there won't be a callback if the proc file values didn't change.
- cb.clear();
- Mockito.reset(mProcReader);
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes1));
- mKernelUidCpuFreqTimeReader.readDeltaImpl(cb);
- cb.verifyNoMoreInteractions();
-
- // Verify that calling with a null callback doesn't result in any crashes
- cb.clear();
- Mockito.reset(mProcReader);
- final long[][] newTimes2 = new long[uids.length][freqs.length];
- for (int i = 0; i < uids.length; ++i) {
- for (int j = 0; j < freqs.length; ++j) {
- newTimes2[i][j] = newTimes1[i][j] + (uids[i] * freqs[j]) * 30;
- }
- }
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes2));
- mKernelUidCpuFreqTimeReader.readDeltaImpl(null);
- cb.verifyNoMoreInteractions();
-
- // Verify that the readDelta call will only return deltas when
- // the previous call had null callback.
- cb.clear();
- Mockito.reset(mProcReader);
- final long[][] newTimes3 = new long[uids.length][freqs.length];
- for (int i = 0; i < uids.length; ++i) {
- for (int j = 0; j < freqs.length; ++j) {
- newTimes3[i][j] = newTimes2[i][j] + (uids[i] + freqs[j]) * 40;
- }
- }
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes3));
- mKernelUidCpuFreqTimeReader.readDeltaImpl(cb);
- for (int i = 0; i < uids.length; ++i) {
- cb.verify(uids[i], subtract(newTimes3[i], newTimes2[i]));
- }
- cb.verifyNoMoreInteractions();
- }
-
- @Test
- public void testReadAbsolute() throws Exception {
- VerifiableCallback cb = new VerifiableCallback();
- final long[] freqs = {110, 123, 145, 167, 289, 997};
- final int[] uids = {1, 22, 333, 444, 555};
- final long[][] times = new long[uids.length][freqs.length];
- for (int i = 0; i < uids.length; ++i) {
- for (int j = 0; j < freqs.length; ++j) {
- times[i][j] = uids[i] * freqs[j] * 10;
- }
- }
- when(mBufferedReader.readLine()).thenReturn(getFreqsLine(freqs));
- long[] actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mBufferedReader, mPowerProfile);
-
- assertArrayEquals(freqs, actualFreqs);
- // Verify that the absolute values are returned
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times));
- mKernelUidCpuFreqTimeReader.readAbsolute(cb);
- for (int i = 0; i < uids.length; ++i) {
- cb.verify(uids[i], times[i]);
- }
- cb.verifyNoMoreInteractions();
-
- // Verify that a second call should still return absolute values
- cb.clear();
- Mockito.reset(mProcReader);
- final long[][] newTimes1 = new long[uids.length][freqs.length];
- for (int i = 0; i < uids.length; ++i) {
- for (int j = 0; j < freqs.length; ++j) {
- newTimes1[i][j] = times[i][j] + (uids[i] + freqs[j]) * 50;
- }
- }
- when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes1));
- mKernelUidCpuFreqTimeReader.readAbsolute(cb);
- for (int i = 0; i < uids.length; ++i) {
- cb.verify(uids[i], newTimes1[i]);
- }
- cb.verifyNoMoreInteractions();
- }
-
- private long[] subtract(long[] a1, long[] a2) {
- long[] val = new long[a1.length];
- for (int i = 0; i < val.length; ++i) {
- val[i] = a1[i] - a2[i];
- }
- return val;
- }
-
- private String getFreqsLine(long[] freqs) {
- final StringBuilder sb = new StringBuilder();
- sb.append("uid:");
- for (int i = 0; i < freqs.length; ++i) {
- sb.append(" " + freqs[i]);
- }
- return sb.toString();
- }
-
- private ByteBuffer getUidTimesBytes(int[] uids, long[][] times) {
- int size = (1 + uids.length + uids.length * times[0].length) * 4;
- ByteBuffer buf = ByteBuffer.allocate(size);
- buf.order(ByteOrder.nativeOrder());
- buf.putInt(times[0].length);
- for (int i = 0; i < uids.length; i++) {
- buf.putInt(uids[i]);
- for (int j = 0; j < times[i].length; j++) {
- buf.putInt((int) (times[i][j] / 10));
- }
- }
- buf.flip();
- return buf.asReadOnlyBuffer().order(ByteOrder.nativeOrder());
- }
-
- private void setCpuClusterFreqs(int numClusters, int... clusterFreqs) {
- assertEquals(numClusters, clusterFreqs.length);
- when(mPowerProfile.getNumCpuClusters()).thenReturn(numClusters);
- for (int i = 0; i < numClusters; ++i) {
- when(mPowerProfile.getNumSpeedStepsInCpuCluster(i)).thenReturn(clusterFreqs[i]);
- }
- }
-
- private class VerifiableCallback implements KernelUidCpuFreqTimeReader.Callback {
-
- SparseArray<long[]> mData = new SparseArray<>();
- int count = 0;
-
- public void verify(int uid, long[] cpuFreqTimeMs) {
- long[] array = mData.get(uid);
- assertNotNull(array);
- assertArrayEquals(cpuFreqTimeMs, array);
- count++;
- }
-
- public void clear() {
- mData.clear();
- count = 0;
- }
-
- @Override
- public void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs) {
- long[] array = new long[cpuFreqTimeMs.length];
- System.arraycopy(cpuFreqTimeMs, 0, array, 0, array.length);
- mData.put(uid, array);
- }
-
- public void verifyNoMoreInteractions() {
- assertEquals(mData.size(), count);
- }
- }
-}
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index c18445e..bc0e0a4 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -21,6 +21,10 @@
import android.util.SparseIntArray;
import com.android.internal.location.gnssmetrics.GnssMetrics;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
import java.util.ArrayList;
import java.util.Queue;
@@ -43,13 +47,14 @@
mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
setExternalStatsSyncLocked(new DummyExternalStatsSync());
- for (int i=0; i< GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) {
- mGpsSignalQualityTimer[i] = new StopwatchTimer(clocks, null, -1000-i, null,
- mOnBatteryTimeBase);
+ for (int i = 0; i < GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) {
+ mGpsSignalQualityTimer[i] = new StopwatchTimer(clocks, null, -1000 - i, null,
+ mOnBatteryTimeBase);
}
// A no-op handler.
- mHandler = new Handler(Looper.getMainLooper()) {};
+ mHandler = new Handler(Looper.getMainLooper()) {
+ };
}
MockBatteryStatsImpl() {
@@ -95,23 +100,26 @@
return this;
}
- public MockBatteryStatsImpl setKernelUidCpuFreqTimeReader(KernelUidCpuFreqTimeReader reader) {
- mKernelUidCpuFreqTimeReader = reader;
+ public MockBatteryStatsImpl setKernelCpuUidFreqTimeReader(KernelCpuUidFreqTimeReader reader) {
+ mCpuUidFreqTimeReader = reader;
return this;
}
- public MockBatteryStatsImpl setKernelUidCpuActiveTimeReader(KernelUidCpuActiveTimeReader reader) {
- mKernelUidCpuActiveTimeReader = reader;
+ public MockBatteryStatsImpl setKernelCpuUidActiveTimeReader(
+ KernelCpuUidActiveTimeReader reader) {
+ mCpuUidActiveTimeReader = reader;
return this;
}
- public MockBatteryStatsImpl setKernelUidCpuClusterTimeReader(KernelUidCpuClusterTimeReader reader) {
- mKernelUidCpuClusterTimeReader = reader;
+ public MockBatteryStatsImpl setKernelCpuUidClusterTimeReader(
+ KernelCpuUidClusterTimeReader reader) {
+ mCpuUidClusterTimeReader = reader;
return this;
}
- public MockBatteryStatsImpl setKernelUidCpuTimeReader(KernelUidCpuTimeReader reader) {
- mKernelUidCpuTimeReader = reader;
+ public MockBatteryStatsImpl setKernelCpuUidUserSysTimeReader(
+ KernelCpuUidUserSysTimeReader reader) {
+ mCpuUidUserSysTimeReader = reader;
return this;
}
diff --git a/core/tests/coretests/src/com/android/internal/statusbar/NotificationVisibilityTest.java b/core/tests/coretests/src/com/android/internal/statusbar/NotificationVisibilityTest.java
new file mode 100644
index 0000000..b740ecc
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/statusbar/NotificationVisibilityTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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 com.android.internal.statusbar;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class NotificationVisibilityTest {
+
+ @Test
+ public void testNotificationLocation_sameValuesAsMetricsProto() throws Exception {
+ for (NotificationVisibility.NotificationLocation location :
+ NotificationVisibility.NotificationLocation.values()) {
+ Field locationField = MetricsEvent.class.getField(location.name());
+ int metricsValue = locationField.getInt(null);
+ assertThat(metricsValue, is(location.toMetricsEventEnum()));
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java b/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java
new file mode 100644
index 0000000..1a81c2c
--- /dev/null
+++ b/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java
@@ -0,0 +1,48 @@
+/*
+ * 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 com.android.server.wm.test.filters;
+
+import android.os.Bundle;
+
+import com.android.test.filters.SelectTest;
+
+/**
+ * JUnit test filter that select Window Manager Service related tests from FrameworksCoreTests.
+ *
+ * <p>Use this filter when running FrameworksCoreTests as
+ * <pre>
+ * adb shell am instrument -w \
+ * -e filter com.android.server.wm.test.filters.CoreTestsFilter \
+ * -e selectTest_verbose true \
+ * com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
+ */
+public final class CoreTestsFilter extends SelectTest {
+
+ private static final String[] SELECTED_CORE_TESTS = {
+ "android.app.servertransaction.", // all tests under the package.
+ "android.view.DisplayCutoutTest",
+ "android.view.InsetsControllerTest",
+ "android.view.InsetsSourceTest",
+ "android.view.InsetsSourceConsumerTest",
+ "android.view.InsetsStateTest",
+ };
+
+ public CoreTestsFilter(Bundle testArgs) {
+ super(addSelectTest(testArgs, SELECTED_CORE_TESTS));
+ }
+}
diff --git a/core/tests/hdmitests/Android.mk b/core/tests/hdmitests/Android.mk
index 2ca31a6..f155feb 100644
--- a/core/tests/hdmitests/Android.mk
+++ b/core/tests/hdmitests/Android.mk
@@ -20,7 +20,7 @@
# Include all test java files
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules frameworks-base-testutils
+LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules frameworks-base-testutils truth-prebuilt
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_PACKAGE_NAME := HdmiCecTests
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiUtilsTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiUtilsTest.java
new file mode 100644
index 0000000..fdc6b84
--- /dev/null
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiUtilsTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.hardware.hdmi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+/**
+ * Tests for {@link HdmiUtils}.
+ */
+@RunWith(JUnit4.class)
+@SmallTest
+public class HdmiUtilsTest {
+ @Test
+ public void testInvalidAddress() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0, -1))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN);
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0xFFFF, 0xFFFF))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN);
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0xFFFFF, 0))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN);
+ }
+
+ @Test
+ public void testSameAddress() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x1000, 0x1000))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_SAME);
+ }
+
+ @Test
+ public void testDirectlyAbove() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x1000, 0x1200))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE);
+ }
+
+ @Test
+ public void testDirectlyAbove_rootDevice() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x0000, 0x2000))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE);
+ }
+
+ @Test
+ public void testDirectlyAbove_leafDevice() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x1240, 0x1245))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE);
+ }
+
+ @Test
+ public void testAbove() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x1000, 0x1210))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_ABOVE);
+ }
+
+ @Test
+ public void testAbove_rootDevice() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x0000, 0x1200))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_ABOVE);
+ }
+
+ @Test
+ public void testDirectlyBelow() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x2250, 0x2200))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW);
+ }
+
+ @Test
+ public void testDirectlyBelow_rootDevice() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x5000, 0x0000))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW);
+ }
+
+ @Test
+ public void testDirectlyBelow_leafDevice() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x3249, 0x3240))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW);
+ }
+
+ @Test
+ public void testBelow() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x5143, 0x5100))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_BELOW);
+ }
+
+ @Test
+ public void testBelow_rootDevice() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x3420, 0x0000))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_BELOW);
+ }
+
+ @Test
+ public void testSibling() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x4000, 0x5000))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_SIBLING);
+ }
+
+ @Test
+ public void testSibling_leafDevice() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x798A, 0x798F))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_SIBLING);
+ }
+
+ @Test
+ public void testDifferentBranch() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x798A, 0x7970))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIFFERENT_BRANCH);
+ }
+
+ @Test
+ public void isValidPysicalAddress_true() {
+ assertThat(HdmiUtils.isValidPhysicalAddress(0)).isTrue();
+ assertThat(HdmiUtils.isValidPhysicalAddress(0xFFFE)).isTrue();
+ assertThat(HdmiUtils.isValidPhysicalAddress(0x1200)).isTrue();
+ }
+
+ @Test
+ public void isValidPysicalAddress_outOfRange() {
+ assertThat(HdmiUtils.isValidPhysicalAddress(-1)).isFalse();
+ assertThat(HdmiUtils.isValidPhysicalAddress(0xFFFF)).isFalse();
+ assertThat(HdmiUtils.isValidPhysicalAddress(0x10000)).isFalse();
+ }
+
+ @Test
+ public void isValidPysicalAddress_nonTrailingZeros() {
+ assertThat(HdmiUtils.isValidPhysicalAddress(0x0001)).isFalse();
+ assertThat(HdmiUtils.isValidPhysicalAddress(0x0213)).isFalse();
+ }
+}
diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
index 03cf3eb..9913531 100644
--- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -67,6 +68,7 @@
// TODO(b/63758238): stop spying the class under test
mLockPatternUtils = spy(new LockPatternUtils(context));
when(mLockPatternUtils.getLockSettings()).thenReturn(ils);
+ doReturn(true).when(mLockPatternUtils).hasSecureLockScreen();
final UserInfo userInfo = Mockito.mock(UserInfo.class);
when(userInfo.isDemo()).thenReturn(isDemoUser);
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 597d14a..904c3fb 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -180,6 +180,7 @@
<permission name="android.permission.WRITE_APN_SETTINGS"/>
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
<permission name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"/>
+ <permission name="android.permission.READ_PRECISE_PHONE_STATE"/>
<permission name="com.android.voicemail.permission.READ_VOICEMAIL"/>
<permission name="com.android.voicemail.permission.WRITE_VOICEMAIL"/>
</privapp-permissions>
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 30f0bfa..bfbdbc5 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -21,7 +21,6 @@
import android.annotation.ColorLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.Size;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.annotation.WorkerThread;
@@ -748,22 +747,10 @@
throw new IllegalArgumentException("usage flags must contain USAGE_GPU_SAMPLED_IMAGE.");
}
int format = hardwareBuffer.getFormat();
- ColorSpace.Rgb rgb = null;
- if (colorSpace != null) {
- if (!(colorSpace instanceof ColorSpace.Rgb)) {
- throw new IllegalArgumentException("colorSpace must be an RGB color space");
- }
- rgb = (ColorSpace.Rgb) colorSpace;
- } else {
- rgb = (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.SRGB);
+ if (colorSpace == null) {
+ colorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
}
- ColorSpace.Rgb.TransferParameters parameters = rgb.getTransferParameters();
- if (parameters == null) {
- throw new IllegalArgumentException("colorSpace must use an ICC "
- + "parametric transfer function");
- }
- ColorSpace.Rgb d50 = (ColorSpace.Rgb) ColorSpace.adapt(rgb, ColorSpace.ILLUMINANT_D50);
- return nativeWrapHardwareBufferBitmap(hardwareBuffer, d50.getTransform(), parameters);
+ return nativeWrapHardwareBufferBitmap(hardwareBuffer, colorSpace.getNativeInstance());
}
/**
@@ -1103,26 +1090,18 @@
throw new IllegalArgumentException("can't create bitmap without a color space");
}
- Bitmap bm;
- // nullptr color spaces have a particular meaning in native and are interpreted as sRGB
- // (we also avoid the unnecessary extra work of the else branch)
- if (config != Config.ARGB_8888 || colorSpace == ColorSpace.get(ColorSpace.Named.SRGB)) {
- bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true, null, null);
- } else {
- if (!(colorSpace instanceof ColorSpace.Rgb)) {
- throw new IllegalArgumentException("colorSpace must be an RGB color space");
+ if (config != Config.ARGB_8888) {
+ if (config == Config.RGBA_F16) {
+ // FIXME: This should be LINEAR_EXTENDED_SRGB, but that would fail a CTS test. See
+ // b/120960866. SRGB matches the old (incorrect) behavior.
+ //colorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB);
+ colorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
+ } else {
+ colorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
}
- ColorSpace.Rgb rgb = (ColorSpace.Rgb) colorSpace;
- ColorSpace.Rgb.TransferParameters parameters = rgb.getTransferParameters();
- if (parameters == null) {
- throw new IllegalArgumentException("colorSpace must use an ICC "
- + "parametric transfer function");
- }
-
- ColorSpace.Rgb d50 = (ColorSpace.Rgb) ColorSpace.adapt(rgb, ColorSpace.ILLUMINANT_D50);
- bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true,
- d50.getTransform(), parameters);
}
+ Bitmap bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true,
+ colorSpace.getNativeInstance());
if (display != null) {
bm.mDensity = display.densityDpi;
@@ -1200,8 +1179,9 @@
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("width and height must be > 0");
}
+ ColorSpace sRGB = ColorSpace.get(ColorSpace.Named.SRGB);
Bitmap bm = nativeCreate(colors, offset, stride, width, height,
- config.nativeInt, false, null, null);
+ config.nativeInt, false, sRGB.getNativeInstance());
if (display != null) {
bm.mDensity = display.densityDpi;
}
@@ -1798,11 +1778,7 @@
}
ColorSpace cs = Color.colorSpace(c);
- float r = Color.red(c);
- float g = Color.green(c);
- float b = Color.blue(c);
- float a = Color.alpha(c);
- nativeErase(mNativePtr, cs, r, g, b, a);
+ nativeErase(mNativePtr, cs.getNativeInstance(), c);
}
/**
@@ -2133,8 +2109,7 @@
private static native Bitmap nativeCreate(int[] colors, int offset,
int stride, int width, int height,
int nativeConfig, boolean mutable,
- @Nullable @Size(9) float[] xyzD50,
- @Nullable ColorSpace.Rgb.TransferParameters p);
+ long nativeColorSpace);
private static native Bitmap nativeCopy(long nativeSrcBitmap, int nativeConfig,
boolean isMutable);
private static native Bitmap nativeCopyAshmem(long nativeSrcBitmap);
@@ -2149,8 +2124,7 @@
int quality, OutputStream stream,
byte[] tempStorage);
private static native void nativeErase(long nativeBitmap, int color);
- private static native void nativeErase(long nativeBitmap, ColorSpace cs,
- float r, float g, float b, float a);
+ private static native void nativeErase(long nativeBitmap, long colorSpacePtr, long color);
private static native int nativeRowBytes(long nativeBitmap);
private static native int nativeConfig(long nativeBitmap);
@@ -2194,8 +2168,7 @@
private static native Bitmap nativeCopyPreserveInternalConfig(long nativeBitmap);
private static native Bitmap nativeCreateHardwareBitmap(GraphicBuffer buffer);
private static native Bitmap nativeWrapHardwareBufferBitmap(HardwareBuffer buffer,
- @Size(9) float[] xyzD50,
- ColorSpace.Rgb.TransferParameters p);
+ long nativeColorSpace);
private static native GraphicBuffer nativeCreateGraphicBufferHandle(long nativeBitmap);
private static native boolean nativeGetColorSpace(long nativePtr, float[] xyz, float[] params);
private static native boolean nativeIsSRGB(long nativePtr);
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index 022fbdc..7aff041 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -460,6 +460,21 @@
}
}
}
+
+ /**
+ * Helper for passing SkColorSpace pointer to native.
+ *
+ * @throws IllegalArgumentException if the ColorSpace is not Rgb or does
+ * not have TransferParameters.
+ */
+ static long nativeColorSpace(Options opts) {
+ if (opts == null || opts.inPreferredColorSpace == null) {
+ return 0;
+ }
+
+ return opts.inPreferredColorSpace.getNativeInstance();
+ }
+
}
/**
@@ -633,7 +648,8 @@
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
try {
- bm = nativeDecodeByteArray(data, offset, length, opts);
+ bm = nativeDecodeByteArray(data, offset, length, opts,
+ Options.nativeColorSpace(opts));
if (bm == null && opts != null && opts.inBitmap != null) {
throw new IllegalArgumentException("Problem decoding into existing bitmap");
@@ -728,7 +744,7 @@
try {
if (is instanceof AssetManager.AssetInputStream) {
final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
- bm = nativeDecodeAsset(asset, outPadding, opts);
+ bm = nativeDecodeAsset(asset, outPadding, opts, Options.nativeColorSpace(opts));
} else {
bm = decodeStreamInternal(is, outPadding, opts);
}
@@ -755,7 +771,8 @@
byte [] tempStorage = null;
if (opts != null) tempStorage = opts.inTempStorage;
if (tempStorage == null) tempStorage = new byte[DECODE_BUFFER_SIZE];
- return nativeDecodeStream(is, tempStorage, outPadding, opts);
+ return nativeDecodeStream(is, tempStorage, outPadding, opts,
+ Options.nativeColorSpace(opts));
}
/**
@@ -798,7 +815,8 @@
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeFileDescriptor");
try {
if (nativeIsSeekable(fd)) {
- bm = nativeDecodeFileDescriptor(fd, outPadding, opts);
+ bm = nativeDecodeFileDescriptor(fd, outPadding, opts,
+ Options.nativeColorSpace(opts));
} else {
FileInputStream fis = new FileInputStream(fd);
try {
@@ -835,14 +853,15 @@
@UnsupportedAppUsage
private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage,
- Rect padding, Options opts);
+ Rect padding, Options opts, long colorSpaceHandle);
@UnsupportedAppUsage
private static native Bitmap nativeDecodeFileDescriptor(FileDescriptor fd,
- Rect padding, Options opts);
+ Rect padding, Options opts, long colorSpaceHandle);
@UnsupportedAppUsage
- private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts);
+ private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts,
+ long colorSpaceHandle);
@UnsupportedAppUsage
private static native Bitmap nativeDecodeByteArray(byte[] data, int offset,
- int length, Options opts);
+ int length, Options opts, long colorSpaceHandle);
private static native boolean nativeIsSeekable(FileDescriptor fd);
}
diff --git a/graphics/java/android/graphics/BitmapRegionDecoder.java b/graphics/java/android/graphics/BitmapRegionDecoder.java
index 43282d3..1410423 100644
--- a/graphics/java/android/graphics/BitmapRegionDecoder.java
+++ b/graphics/java/android/graphics/BitmapRegionDecoder.java
@@ -195,7 +195,8 @@
|| rect.top >= getHeight())
throw new IllegalArgumentException("rectangle is outside the image");
return nativeDecodeRegion(mNativeBitmapRegionDecoder, rect.left, rect.top,
- rect.right - rect.left, rect.bottom - rect.top, options);
+ rect.right - rect.left, rect.bottom - rect.top, options,
+ BitmapFactory.Options.nativeColorSpace(options));
}
}
@@ -265,7 +266,7 @@
private static native Bitmap nativeDecodeRegion(long lbm,
int start_x, int start_y, int width, int height,
- BitmapFactory.Options options);
+ BitmapFactory.Options options, long colorSpaceHandle);
private static native int nativeGetWidth(long lbm);
private static native int nativeGetHeight(long lbm);
private static native void nativeClean(long lbm);
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 9fa70a5..4755d45 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -25,6 +25,8 @@
import android.annotation.SuppressAutoDoc;
import android.util.Pair;
+import libcore.util.NativeAllocationRegistry;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -199,6 +201,9 @@
private static final float[] NTSC_1953_PRIMARIES = { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f };
private static final float[] ILLUMINANT_D50_XYZ = { 0.964212f, 1.0f, 0.825188f };
+ private static final Rgb.TransferParameters SRGB_TRANSFER_PARAMETERS =
+ new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4);
+
// See static initialization block next to #get(Named)
private static final ColorSpace[] sNamedColorSpaces = new ColorSpace[Named.values().length];
@@ -1341,6 +1346,26 @@
}
/**
+ * Helper method for creating native SkColorSpace.
+ *
+ * This essentially calls adapt on a ColorSpace that has not been fully
+ * created. It also does not fully create the adapted ColorSpace, but
+ * just returns the transform.
+ */
+ @NonNull @Size(9)
+ private static float[] adaptToIlluminantD50(
+ @NonNull @Size(2) float[] origWhitePoint,
+ @NonNull @Size(9) float[] origTransform) {
+ float[] desired = ILLUMINANT_D50;
+ if (compare(origWhitePoint, desired)) return origTransform;
+
+ float[] xyz = xyYToXyz(desired);
+ float[] adaptationTransform = chromaticAdaptation(Adaptation.BRADFORD.mTransform,
+ xyYToXyz(origWhitePoint), xyz);
+ return mul3x3(adaptationTransform, origTransform);
+ }
+
+ /**
* <p>Returns an instance of {@link ColorSpace} whose ID matches the
* specified ID.</p>
*
@@ -1431,7 +1456,7 @@
"sRGB IEC61966-2.1",
SRGB_PRIMARIES,
ILLUMINANT_D65,
- new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4),
+ SRGB_TRANSFER_PARAMETERS,
Named.SRGB.ordinal()
);
sNamedColorSpaces[Named.LINEAR_SRGB.ordinal()] = new ColorSpace.Rgb(
@@ -1446,9 +1471,11 @@
"scRGB-nl IEC 61966-2-2:2003",
SRGB_PRIMARIES,
ILLUMINANT_D65,
+ null,
x -> absRcpResponse(x, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4),
x -> absResponse(x, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4),
-0.799f, 2.399f,
+ null, // FIXME: Use SRGB_TRANSFER_PARAMETERS
Named.EXTENDED_SRGB.ordinal()
);
sNamedColorSpaces[Named.LINEAR_EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb(
@@ -1485,7 +1512,7 @@
"Display P3",
new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
ILLUMINANT_D65,
- new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4),
+ SRGB_TRANSFER_PARAMETERS,
Named.DISPLAY_P3.ordinal()
);
sNamedColorSpaces[Named.NTSC_1953.ordinal()] = new ColorSpace.Rgb(
@@ -1967,6 +1994,15 @@
}
/**
+ * Retrieve the native SkColorSpace object for passing to native.
+ *
+ * Only valid on ColorSpace.Rgb.
+ */
+ long getNativeInstance() {
+ throw new IllegalArgumentException("colorSpace must be an RGB color space");
+ }
+
+ /**
* {@usesMathJax}
*
* <p>An RGB color space is an additive color space using the
@@ -2269,7 +2305,22 @@
private final boolean mIsWideGamut;
private final boolean mIsSrgb;
- @Nullable private TransferParameters mTransferParameters;
+ @Nullable private final TransferParameters mTransferParameters;
+ private final long mNativePtr;
+
+ @Override
+ long getNativeInstance() {
+ if (mNativePtr == 0) {
+ // If this object has TransferParameters, it must have a native object.
+ throw new IllegalArgumentException("ColorSpace must use an ICC "
+ + "parametric transfer function! used " + this);
+ }
+ return mNativePtr;
+ }
+
+ private static native long nativeGetNativeFinalizer();
+ private static native long nativeCreate(float a, float b, float c, float d,
+ float e, float f, float g, float[] xyz);
/**
* <p>Creates a new RGB color space using a 3x3 column-major transform matrix.
@@ -2298,8 +2349,8 @@
@NonNull @Size(9) float[] toXYZ,
@NonNull DoubleUnaryOperator oetf,
@NonNull DoubleUnaryOperator eotf) {
- this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ),
- oetf, eotf, 0.0f, 1.0f, MIN_ID);
+ this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), null,
+ oetf, eotf, 0.0f, 1.0f, null, MIN_ID);
}
/**
@@ -2349,7 +2400,7 @@
@NonNull DoubleUnaryOperator eotf,
float min,
float max) {
- this(name, primaries, whitePoint, oetf, eotf, min, max, MIN_ID);
+ this(name, primaries, whitePoint, null, oetf, eotf, min, max, null, MIN_ID);
}
/**
@@ -2459,7 +2510,7 @@
@NonNull @Size(min = 2, max = 3) float[] whitePoint,
@NonNull TransferParameters function,
@IntRange(from = MIN_ID, to = MAX_ID) int id) {
- this(name, primaries, whitePoint,
+ this(name, primaries, whitePoint, null,
function.e == 0.0 && function.f == 0.0 ?
x -> rcpResponse(x, function.a, function.b,
function.c, function.d, function.g) :
@@ -2470,8 +2521,7 @@
function.c, function.d, function.g) :
x -> response(x, function.a, function.b, function.c,
function.d, function.e, function.f, function.g),
- 0.0f, 1.0f, id);
- mTransferParameters = function;
+ 0.0f, 1.0f, function, id);
}
/**
@@ -2586,13 +2636,12 @@
float min,
float max,
@IntRange(from = MIN_ID, to = MAX_ID) int id) {
- this(name, primaries, whitePoint,
+ this(name, primaries, whitePoint, null,
gamma == 1.0 ? DoubleUnaryOperator.identity() :
x -> Math.pow(x < 0.0 ? 0.0 : x, 1 / gamma),
gamma == 1.0 ? DoubleUnaryOperator.identity() :
x -> Math.pow(x < 0.0 ? 0.0 : x, gamma),
- min, max, id);
- mTransferParameters = new TransferParameters(1.0, 0.0, 0.0, 0.0, gamma);
+ min, max, new TransferParameters(1.0, 0.0, 0.0, 0.0, gamma), id);
}
/**
@@ -2615,10 +2664,13 @@
* @param name Name of the color space, cannot be null, its length must be >= 1
* @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
* @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
+ * @param transform Computed transform matrix that converts from RGB to XYZ, or
+ * {@code null} to compute it from {@code primaries} and {@code whitePoint}.
* @param oetf Opto-electronic transfer function, cannot be null
* @param eotf Electro-optical transfer function, cannot be null
* @param min The minimum valid value in this color space's RGB range
* @param max The maximum valid value in this color space's RGB range
+ * @param transferParameters Parameters for the transfer functions
* @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID}
*
* @throws IllegalArgumentException If any of the following conditions is met:
@@ -2637,10 +2689,12 @@
@NonNull @Size(min = 1) String name,
@NonNull @Size(min = 6, max = 9) float[] primaries,
@NonNull @Size(min = 2, max = 3) float[] whitePoint,
+ @Nullable @Size(9) float[] transform,
@NonNull DoubleUnaryOperator oetf,
@NonNull DoubleUnaryOperator eotf,
float min,
float max,
+ @Nullable TransferParameters transferParameters,
@IntRange(from = MIN_ID, to = MAX_ID) int id) {
super(name, Model.RGB, id);
@@ -2668,7 +2722,15 @@
mWhitePoint = xyWhitePoint(whitePoint);
mPrimaries = xyPrimaries(primaries);
- mTransform = computeXYZMatrix(mPrimaries, mWhitePoint);
+ if (transform == null) {
+ mTransform = computeXYZMatrix(mPrimaries, mWhitePoint);
+ } else {
+ if (transform.length != 9) {
+ throw new IllegalArgumentException("Transform must have 9 entries! Has "
+ + transform.length);
+ }
+ mTransform = transform;
+ }
mInverseTransform = inverse3x3(mTransform);
mOetf = oetf;
@@ -2681,10 +2743,39 @@
mClampedOetf = oetf.andThen(clamp);
mClampedEotf = clamp.andThen(eotf);
+ mTransferParameters = transferParameters;
+
// A color space is wide-gamut if its area is >90% of NTSC 1953 and
// if it entirely contains the Color space definition in xyY
mIsWideGamut = isWideGamut(mPrimaries, min, max);
mIsSrgb = isSrgb(mPrimaries, mWhitePoint, oetf, eotf, min, max, id);
+
+ if (mTransferParameters != null) {
+ if (mWhitePoint == null || mTransform == null) {
+ throw new IllegalStateException(
+ "ColorSpace (" + this + ") cannot create native object! mWhitePoint: "
+ + mWhitePoint + " mTransform: " + mTransform);
+ }
+
+ // This mimics the old code that was in native.
+ float[] nativeTransform = adaptToIlluminantD50(mWhitePoint, mTransform);
+ mNativePtr = nativeCreate((float) mTransferParameters.a,
+ (float) mTransferParameters.b,
+ (float) mTransferParameters.c,
+ (float) mTransferParameters.d,
+ (float) mTransferParameters.e,
+ (float) mTransferParameters.f,
+ (float) mTransferParameters.g,
+ nativeTransform);
+ NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativePtr);
+ } else {
+ mNativePtr = 0;
+ }
+ }
+
+ private static class NoImagePreloadHolder {
+ public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+ ColorSpace.Rgb.class.getClassLoader(), nativeGetNativeFinalizer(), 0);
}
/**
@@ -2695,27 +2786,9 @@
private Rgb(Rgb colorSpace,
@NonNull @Size(9) float[] transform,
@NonNull @Size(min = 2, max = 3) float[] whitePoint) {
- super(colorSpace.getName(), Model.RGB, -1);
-
- mWhitePoint = xyWhitePoint(whitePoint);
- mPrimaries = colorSpace.mPrimaries;
-
- mTransform = transform;
- mInverseTransform = inverse3x3(transform);
-
- mMin = colorSpace.mMin;
- mMax = colorSpace.mMax;
-
- mOetf = colorSpace.mOetf;
- mEotf = colorSpace.mEotf;
-
- mClampedOetf = colorSpace.mClampedOetf;
- mClampedEotf = colorSpace.mClampedEotf;
-
- mIsWideGamut = colorSpace.mIsWideGamut;
- mIsSrgb = colorSpace.mIsSrgb;
-
- mTransferParameters = colorSpace.mTransferParameters;
+ this(colorSpace.getName(), colorSpace.mPrimaries, whitePoint, transform,
+ colorSpace.mOetf, colorSpace.mEotf, colorSpace.mMin, colorSpace.mMax,
+ colorSpace.mTransferParameters, MIN_ID);
}
/**
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index e402055..c4ddd50 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -20,6 +20,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.app.Activity;
import android.app.ActivityManager;
import android.os.IBinder;
@@ -667,6 +668,17 @@
nSetContentDrawBounds(mNativeProxy, left, top, right, bottom);
}
+ /** @hide */
+ public void setPictureCaptureCallback(@Nullable PictureCapturedCallback callback) {
+ nSetPictureCaptureCallback(mNativeProxy, callback);
+ }
+
+ /** called by native */
+ static void invokePictureCapturedCallback(long picturePtr, PictureCapturedCallback callback) {
+ Picture picture = new Picture(picturePtr);
+ callback.onPictureCaptured(picture);
+ }
+
/**
* Interface used to receive callbacks when a frame is being drawn.
*
@@ -695,6 +707,17 @@
void onFrameComplete(long frameNr);
}
+ /**
+ * Interface for listening to picture captures
+ * @hide
+ */
+ @TestApi
+ public interface PictureCapturedCallback {
+ /** @hide */
+ @TestApi
+ void onPictureCaptured(Picture picture);
+ }
+
private static void validateAlpha(float alpha, String argumentName) {
if (!(alpha >= 0.0f && alpha <= 1.0f)) {
throw new IllegalArgumentException(argumentName + " must be a valid alpha, "
@@ -998,6 +1021,9 @@
private static native void nSetContentDrawBounds(long nativeProxy, int left,
int top, int right, int bottom);
+ private static native void nSetPictureCaptureCallback(long nativeProxy,
+ PictureCapturedCallback callback);
+
private static native void nSetFrameCallback(long nativeProxy, FrameDrawingCallback callback);
private static native void nSetFrameCompleteCallback(long nativeProxy,
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index e3b165c..466a5fc 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -1628,17 +1628,6 @@
if (mPostProcessor != null && mUnpremultipliedRequired) {
throw new IllegalStateException("Cannot draw to unpremultiplied pixels!");
}
-
- if (mDesiredColorSpace != null) {
- if (!(mDesiredColorSpace instanceof ColorSpace.Rgb)) {
- throw new IllegalArgumentException("The target color space must use the "
- + "RGB color model - provided: " + mDesiredColorSpace);
- }
- if (((ColorSpace.Rgb) mDesiredColorSpace).getTransferParameters() == null) {
- throw new IllegalArgumentException("The target color space must use an "
- + "ICC parametric transfer function - provided: " + mDesiredColorSpace);
- }
- }
}
private static void checkSubset(int width, int height, Rect r) {
@@ -1655,10 +1644,12 @@
@NonNull
private Bitmap decodeBitmapInternal() throws IOException {
checkState();
+ long colorSpacePtr = mDesiredColorSpace == null ? 0 :
+ mDesiredColorSpace.getNativeInstance();
return nDecodeBitmap(mNativePtr, this, mPostProcessor != null,
mDesiredWidth, mDesiredHeight, mCropRect,
mMutable, mAllocator, mUnpremultipliedRequired,
- mConserveMemory, mDecodeAsAlphaMask, mDesiredColorSpace);
+ mConserveMemory, mDecodeAsAlphaMask, colorSpacePtr);
}
private void callHeaderDecoded(@Nullable OnHeaderDecodedListener listener,
@@ -1946,7 +1937,7 @@
@Nullable Rect cropRect, boolean mutable,
int allocator, boolean unpremulRequired,
boolean conserveMemory, boolean decodeAsAlphaMask,
- @Nullable ColorSpace desiredColorSpace)
+ long desiredColorSpace)
throws IOException;
private static native Size nGetSampledSize(long nativePtr,
int sampleSize);
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 54e1abc..7eee6f4 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1017,7 +1017,7 @@
float b = Color.blue(color);
float a = Color.alpha(color);
- nSetColor(mNativePaint, cs, r, g, b, a);
+ nSetColor(mNativePaint, cs.getNativeInstance(), r, g, b, a);
mColor = color;
}
@@ -1455,7 +1455,7 @@
float g = Color.green(shadowColor);
float b = Color.blue(shadowColor);
float a = Color.alpha(shadowColor);
- nSetShadowLayer(mNativePaint, radius, dx, dy, cs, r, g, b, a);
+ nSetShadowLayer(mNativePaint, radius, dx, dy, cs.getNativeInstance(), r, g, b, a);
mShadowLayerRadius = radius;
mShadowLayerDx = dx;
@@ -3003,11 +3003,6 @@
int contextStart, int contextEnd, boolean isRtl, int offset);
private static native int nGetOffsetForAdvance(long paintPtr, char[] text, int start, int end,
int contextStart, int contextEnd, boolean isRtl, float advance);
- private static native void nSetColor(long paintPtr, ColorSpace cs,
- float r, float g, float b, float a);
- private static native void nSetShadowLayer(long paintPtr,
- float radius, float dx, float dy, ColorSpace cs,
- float r, float g, float b, float a);
// ---------------- @FastNative ------------------------
@@ -3063,7 +3058,8 @@
int mMinikinLocaleListId);
@CriticalNative
private static native void nSetShadowLayer(long paintPtr,
- float radius, float dx, float dy, @ColorInt int color);
+ float radius, float dx, float dy, long colorSpaceHandle,
+ float r, float g, float b, float a);
@CriticalNative
private static native boolean nHasShadowLayer(long paintPtr);
@CriticalNative
@@ -3111,6 +3107,9 @@
@CriticalNative
private static native void nSetFilterBitmap(long paintPtr, boolean filter);
@CriticalNative
+ private static native void nSetColor(long paintPtr, long colorSpaceHandle,
+ float r, float g, float b, float a);
+ @CriticalNative
private static native void nSetStrikeThruText(long paintPtr, boolean strikeThruText);
@CriticalNative
private static native boolean nIsElegantTextHeight(long paintPtr);
diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java
index f6d801b..8d12cbf 100644
--- a/graphics/java/android/graphics/Picture.java
+++ b/graphics/java/android/graphics/Picture.java
@@ -16,6 +16,7 @@
package android.graphics;
+import android.annotation.NonNull;
import android.annotation.UnsupportedAppUsage;
import java.io.InputStream;
@@ -34,7 +35,8 @@
*/
public class Picture {
private PictureCanvas mRecordingCanvas;
- @UnsupportedAppUsage
+ // TODO: Figure out if this was a false-positive
+ @UnsupportedAppUsage(maxTargetSdk = 28)
private long mNativePicture;
private boolean mRequiresHwAcceleration;
@@ -56,23 +58,43 @@
this(nativeConstructor(src != null ? src.mNativePicture : 0));
}
- private Picture(long nativePicture) {
+ /** @hide */
+ public Picture(long nativePicture) {
if (nativePicture == 0) {
- throw new RuntimeException();
+ throw new IllegalArgumentException();
}
mNativePicture = nativePicture;
}
+ /**
+ * Immediately releases the backing data of the Picture. This object will no longer
+ * be usable after calling this, and any further calls on the Picture will throw an
+ * IllegalStateException.
+ * // TODO: Support?
+ * @hide
+ */
+ public void close() {
+ if (mNativePicture != 0) {
+ nativeDestructor(mNativePicture);
+ mNativePicture = 0;
+ }
+ }
+
@Override
protected void finalize() throws Throwable {
try {
- nativeDestructor(mNativePicture);
- mNativePicture = 0;
+ close();
} finally {
super.finalize();
}
}
+ private void verifyValid() {
+ if (mNativePicture == 0) {
+ throw new IllegalStateException("Picture is destroyed");
+ }
+ }
+
/**
* To record a picture, call beginRecording() and then draw into the Canvas
* that is returned. Nothing we appear on screen, but all of the draw
@@ -81,7 +103,9 @@
* that was returned must no longer be used, and nothing should be drawn
* into it.
*/
+ @NonNull
public Canvas beginRecording(int width, int height) {
+ verifyValid();
if (mRecordingCanvas != null) {
throw new IllegalStateException("Picture already recording, must call #endRecording()");
}
@@ -98,6 +122,7 @@
* or {@link Canvas#drawPicture(Picture)} is called.
*/
public void endRecording() {
+ verifyValid();
if (mRecordingCanvas != null) {
mRequiresHwAcceleration = mRecordingCanvas.mHoldsHwBitmap;
mRecordingCanvas = null;
@@ -110,7 +135,8 @@
* does not reflect (per se) the content of the picture.
*/
public int getWidth() {
- return nativeGetWidth(mNativePicture);
+ verifyValid();
+ return nativeGetWidth(mNativePicture);
}
/**
@@ -118,7 +144,8 @@
* does not reflect (per se) the content of the picture.
*/
public int getHeight() {
- return nativeGetHeight(mNativePicture);
+ verifyValid();
+ return nativeGetHeight(mNativePicture);
}
/**
@@ -133,6 +160,7 @@
* false otherwise.
*/
public boolean requiresHardwareAcceleration() {
+ verifyValid();
return mRequiresHwAcceleration;
}
@@ -149,7 +177,8 @@
*
* @param canvas The picture is drawn to this canvas
*/
- public void draw(Canvas canvas) {
+ public void draw(@NonNull Canvas canvas) {
+ verifyValid();
if (mRecordingCanvas != null) {
endRecording();
}
@@ -172,7 +201,7 @@
* raw or compressed pixels.
*/
@Deprecated
- public static Picture createFromStream(InputStream stream) {
+ public static Picture createFromStream(@NonNull InputStream stream) {
return new Picture(nativeCreateFromStream(stream, new byte[WORKING_STREAM_STORAGE]));
}
@@ -188,10 +217,11 @@
* Bitmap from which you can persist it as raw or compressed pixels.
*/
@Deprecated
- public void writeToStream(OutputStream stream) {
+ public void writeToStream(@NonNull OutputStream stream) {
+ verifyValid();
// do explicit check before calling the native method
if (stream == null) {
- throw new NullPointerException();
+ throw new IllegalArgumentException("stream cannot be null");
}
if (!nativeWriteToStream(mNativePicture, stream, new byte[WORKING_STREAM_STORAGE])) {
throw new RuntimeException();
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index 3b1d44b..09b18b7 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -184,7 +184,7 @@
*
* @param name The name of the RenderNode, used for debugging purpose. May be null.
*/
- public RenderNode(String name) {
+ public RenderNode(@Nullable String name) {
this(name, null);
}
diff --git a/graphics/proto/Android.bp b/graphics/proto/Android.bp
new file mode 100644
index 0000000..1d06348
--- /dev/null
+++ b/graphics/proto/Android.bp
@@ -0,0 +1,11 @@
+java_library_static {
+ name: "game-driver-protos",
+ host_supported: true,
+ proto: {
+ type: "lite",
+ },
+ srcs: ["game_driver.proto"],
+ no_framework_libs: true,
+ jarjar_rules: "jarjar-rules.txt",
+ sdk_version: "28",
+}
diff --git a/graphics/proto/game_driver.proto b/graphics/proto/game_driver.proto
new file mode 100644
index 0000000..fd7ffcc
--- /dev/null
+++ b/graphics/proto/game_driver.proto
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package android.gamedriver;
+
+option java_package = "android.gamedriver";
+option java_outer_classname = "GameDriverProto";
+
+message Blacklist {
+ optional int64 version_code = 1;
+ repeated string package_names = 2;
+}
+
+message Blacklists {
+ repeated Blacklist blacklists = 1;
+}
diff --git a/graphics/proto/jarjar-rules.txt b/graphics/proto/jarjar-rules.txt
new file mode 100644
index 0000000..4e40637
--- /dev/null
+++ b/graphics/proto/jarjar-rules.txt
@@ -0,0 +1 @@
+rule com.google.protobuf.** com.android.framework.protobuf.@1
diff --git a/jarjar_rules_hidl.txt b/jarjar_rules_hidl.txt
new file mode 100644
index 0000000..4b2331d
--- /dev/null
+++ b/jarjar_rules_hidl.txt
@@ -0,0 +1 @@
+rule android.hidl.** android.internal.hidl.@1
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index dd62bbb..7265692 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -551,6 +551,19 @@
SkPaint paint = inPaint;
paint.setAlpha(mProperties.getRootAlpha() * 255);
+ if (canvas->getGrContext() == nullptr) {
+ // Recording to picture, don't use the SkSurface which won't work off of renderthread.
+ Bitmap& bitmap = getBitmapUpdateIfDirty();
+ SkBitmap skiaBitmap;
+ bitmap.getSkBitmap(&skiaBitmap);
+
+ int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth());
+ int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight());
+ canvas->drawBitmapRect(skiaBitmap, SkRect::MakeWH(scaledWidth, scaledHeight), bounds,
+ &paint, SkCanvas::kFast_SrcRectConstraint);
+ return;
+ }
+
SkRect src;
sk_sp<SkSurface> vdSurface = mCache.getSurface(&src);
if (vdSurface) {
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index 240efb4..60c8057 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -74,7 +74,13 @@
void GLFunctorDrawable::onDraw(SkCanvas* canvas) {
if (canvas->getGrContext() == nullptr) {
- SkDEBUGF(("Attempting to draw GLFunctor into an unsupported surface"));
+ // We're dumping a picture, render a light-blue rectangle instead
+ // TODO: Draw the WebView text on top? Seemingly complicated as SkPaint doesn't
+ // seem to have a default typeface that works. We only ever use drawGlyphs, which
+ // requires going through minikin & hwui's canvas which we don't have here.
+ SkPaint paint;
+ paint.setColor(0xFF81D4FA);
+ canvas->drawRect(mBounds, paint);
return;
}
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index df82243..47c9094 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -111,7 +111,7 @@
const Rect& layerDamage = layers.entries()[i].damage;
- SkCanvas* layerCanvas = tryCapture(layerNode->getLayerSurface());
+ SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
int saveCount = layerCanvas->save();
SkASSERT(saveCount == 1);
@@ -139,8 +139,6 @@
layerCanvas->restoreToCount(saveCount);
mLightCenter = savedLightCenter;
- endCapture(layerNode->getLayerSurface());
-
// cache the current context so that we can defer flushing it until
// either all the layers have been rendered or the context changes
GrContext* currentContext = layerNode->getLayerSurface()->getCanvas()->getGrContext();
@@ -244,6 +242,7 @@
}
virtual void onProcess(const sp<Task<bool>>& task) override {
+ ATRACE_NAME("SavePictureTask");
SavePictureTask* t = static_cast<SavePictureTask*>(task.get());
if (0 == access(t->filename.c_str(), F_OK)) {
@@ -265,46 +264,56 @@
SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface) {
if (CC_UNLIKELY(Properties::skpCaptureEnabled)) {
- bool recordingPicture = mCaptureSequence > 0;
char prop[PROPERTY_VALUE_MAX] = {'\0'};
- if (!recordingPicture) {
+ if (mCaptureSequence <= 0) {
property_get(PROPERTY_CAPTURE_SKP_FILENAME, prop, "0");
- recordingPicture = prop[0] != '0' &&
- mCapturedFile != prop; // ensure we capture only once per filename
- if (recordingPicture) {
+ if (prop[0] != '0' && mCapturedFile != prop) {
mCapturedFile = prop;
mCaptureSequence = property_get_int32(PROPERTY_CAPTURE_SKP_FRAMES, 1);
}
}
- if (recordingPicture) {
+ if (mCaptureSequence > 0 || mPictureCapturedCallback) {
mRecorder.reset(new SkPictureRecorder());
- return mRecorder->beginRecording(surface->width(), surface->height(), nullptr,
- SkPictureRecorder::kPlaybackDrawPicture_RecordFlag);
+ SkCanvas* pictureCanvas = mRecorder->beginRecording(surface->width(), surface->height(), nullptr,
+ SkPictureRecorder::kPlaybackDrawPicture_RecordFlag);
+ mNwayCanvas = std::make_unique<SkNWayCanvas>(surface->width(), surface->height());
+ mNwayCanvas->addCanvas(surface->getCanvas());
+ mNwayCanvas->addCanvas(pictureCanvas);
+ return mNwayCanvas.get();
}
}
return surface->getCanvas();
}
void SkiaPipeline::endCapture(SkSurface* surface) {
+ mNwayCanvas.reset();
if (CC_UNLIKELY(mRecorder.get())) {
+ ATRACE_CALL();
sk_sp<SkPicture> picture = mRecorder->finishRecordingAsPicture();
- surface->getCanvas()->drawPicture(picture);
if (picture->approximateOpCount() > 0) {
- auto data = picture->serialize();
+ if (mCaptureSequence > 0) {
+ ATRACE_BEGIN("picture->serialize");
+ auto data = picture->serialize();
+ ATRACE_END();
- // offload saving to file in a different thread
- if (!mSavePictureProcessor.get()) {
- TaskManager* taskManager = getTaskManager();
- mSavePictureProcessor = new SavePictureProcessor(
- taskManager->canRunTasks() ? taskManager : nullptr);
+ // offload saving to file in a different thread
+ if (!mSavePictureProcessor.get()) {
+ TaskManager* taskManager = getTaskManager();
+ mSavePictureProcessor = new SavePictureProcessor(
+ taskManager->canRunTasks() ? taskManager : nullptr);
+ }
+ if (1 == mCaptureSequence) {
+ mSavePictureProcessor->savePicture(data, mCapturedFile);
+ } else {
+ mSavePictureProcessor->savePicture(
+ data,
+ mCapturedFile + "_" + std::to_string(mCaptureSequence));
+ }
+ mCaptureSequence--;
}
- if (1 == mCaptureSequence) {
- mSavePictureProcessor->savePicture(data, mCapturedFile);
- } else {
- mSavePictureProcessor->savePicture(
- data, mCapturedFile + "_" + std::to_string(mCaptureSequence));
+ if (mPictureCapturedCallback) {
+ std::invoke(mPictureCapturedCallback, std::move(picture));
}
- mCaptureSequence--;
}
mRecorder.reset();
}
@@ -314,6 +323,11 @@
const std::vector<sp<RenderNode>>& nodes, bool opaque,
const Rect& contentDrawBounds, sk_sp<SkSurface> surface,
const SkMatrix& preTransform) {
+ bool previousSkpEnabled = Properties::skpCaptureEnabled;
+ if (mPictureCapturedCallback) {
+ Properties::skpCaptureEnabled = true;
+ }
+
renderVectorDrawableCache();
// draw all layers up front
@@ -334,6 +348,8 @@
ATRACE_NAME("flush commands");
surface->getCanvas()->flush();
+
+ Properties::skpCaptureEnabled = previousSkpEnabled;
}
namespace {
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index cf6f5b2..e9957df 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -105,6 +105,11 @@
mLightCenter = lightGeometry.center;
}
+ void setPictureCapturedCallback(
+ const std::function<void(sk_sp<SkPicture>&&)>& callback) override {
+ mPictureCapturedCallback = callback;
+ }
+
protected:
void dumpResourceCacheUsage() const;
void setSurfaceColorProperties(renderthread::ColorMode colorMode);
@@ -163,6 +168,8 @@
* parallel tryCapture calls (not really needed).
*/
std::unique_ptr<SkPictureRecorder> mRecorder;
+ std::unique_ptr<SkNWayCanvas> mNwayCanvas;
+ std::function<void(sk_sp<SkPicture>&&)> mPictureCapturedCallback;
static float mLightRadius;
static uint8_t mAmbientShadowAlpha;
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 9e7abf4..db97763 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -184,6 +184,10 @@
mFrameCompleteCallbacks.push_back(std::move(func));
}
+ void setPictureCapturedCallback(const std::function<void(sk_sp<SkPicture>&&)>& callback) {
+ mRenderPipeline->setPictureCapturedCallback(callback);
+ }
+
void setForceDark(bool enable) {
mUseForceDark = enable;
}
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index d4dd629..2cfc8df3 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -59,15 +59,15 @@
virtual MakeCurrentResult makeCurrent() = 0;
virtual Frame getFrame() = 0;
virtual bool draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
- const LightGeometry& lightGeometry,
- LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds,
- bool opaque, const LightInfo& lightInfo,
+ const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
+ const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
const std::vector<sp<RenderNode>>& renderNodes,
FrameInfoVisualizer* profiler) = 0;
virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
FrameInfo* currentFrameInfo, bool* requireSwap) = 0;
virtual DeferredLayerUpdater* createTextureLayer() = 0;
- virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior, ColorMode colorMode) = 0;
+ virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior,
+ ColorMode colorMode) = 0;
virtual void onStop() = 0;
virtual bool isSurfaceReady() = 0;
virtual bool isContextReady() = 0;
@@ -85,6 +85,8 @@
virtual SkColorType getSurfaceColorType() const = 0;
virtual sk_sp<SkColorSpace> getSurfaceColorSpace() = 0;
virtual GrSurfaceOrigin getSurfaceOrigin() = 0;
+ virtual void setPictureCapturedCallback(
+ const std::function<void(sk_sp<SkPicture>&&)>& callback) = 0;
virtual ~IRenderPipeline() {}
};
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index aa6af23..ab59af7 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -21,6 +21,7 @@
#include "Properties.h"
#include "Readback.h"
#include "Rect.h"
+#include "WebViewFunctorManager.h"
#include "pipeline/skia/SkiaOpenGLPipeline.h"
#include "pipeline/skia/VectorDrawableAtlas.h"
#include "renderstate/RenderState.h"
@@ -30,7 +31,6 @@
#include "renderthread/RenderThread.h"
#include "utils/Macros.h"
#include "utils/TimeUtils.h"
-#include "WebViewFunctorManager.h"
#include <ui/GraphicBuffer.h>
@@ -147,9 +147,7 @@
void RenderProxy::destroyFunctor(int functor) {
ATRACE_CALL();
RenderThread& thread = RenderThread::getInstance();
- thread.queue().post([=]() {
- WebViewFunctorManager::instance().destroyFunctor(functor);
- });
+ thread.queue().post([=]() { WebViewFunctorManager::instance().destroyFunctor(functor); });
}
DeferredLayerUpdater* RenderProxy::createTextureLayer() {
@@ -164,9 +162,9 @@
bool RenderProxy::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap& bitmap) {
auto& thread = RenderThread::getInstance();
- return thread.queue().runSync(
- [&]() -> bool { return thread.readback().copyLayerInto(layer, &bitmap)
- == CopyResult::Success; });
+ return thread.queue().runSync([&]() -> bool {
+ return thread.readback().copyLayerInto(layer, &bitmap) == CopyResult::Success;
+ });
}
void RenderProxy::pushLayerUpdate(DeferredLayerUpdater* layer) {
@@ -204,9 +202,8 @@
}
int RenderProxy::maxTextureSize() {
- static int maxTextureSize = RenderThread::getInstance().queue().runSync([]() {
- return DeviceInfo::get()->maxTextureSize();
- });
+ static int maxTextureSize = RenderThread::getInstance().queue().runSync(
+ []() { return DeviceInfo::get()->maxTextureSize(); });
return maxTextureSize;
}
@@ -281,6 +278,12 @@
mDrawFrameTask.setContentDrawBounds(left, top, right, bottom);
}
+void RenderProxy::setPictureCapturedCallback(
+ const std::function<void(sk_sp<SkPicture>&&)>& callback) {
+ mRenderThread.queue().post(
+ [ this, cb = callback ]() { mContext->setPictureCapturedCallback(cb); });
+}
+
void RenderProxy::setFrameCallback(std::function<void(int64_t)>&& callback) {
mDrawFrameTask.setFrameCallback(std::move(callback));
}
@@ -302,9 +305,7 @@
}
void RenderProxy::setForceDark(bool enable) {
- mRenderThread.queue().post([this, enable]() {
- mContext->setForceDark(enable);
- });
+ mRenderThread.queue().post([this, enable]() { mContext->setForceDark(enable); });
}
int RenderProxy::copySurfaceInto(sp<Surface>& surface, int left, int top, int right, int bottom,
@@ -348,9 +349,8 @@
// TODO: fix everything that hits this. We should never be triggering a readback ourselves.
return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap);
} else {
- return thread.queue().runSync([&]() -> int {
- return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap);
- });
+ return thread.queue().runSync(
+ [&]() -> int { return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap); });
}
}
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 9dc9181..6e1bfd7 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -114,6 +114,8 @@
ANDROID_API void removeRenderNode(RenderNode* node);
ANDROID_API void drawRenderNode(RenderNode* node);
ANDROID_API void setContentDrawBounds(int left, int top, int right, int bottom);
+ ANDROID_API void setPictureCapturedCallback(
+ const std::function<void(sk_sp<SkPicture>&&)>& callback);
ANDROID_API void setFrameCallback(std::function<void(int64_t)>&& callback);
ANDROID_API void setFrameCompleteCallback(std::function<void(int64_t)>&& callback);
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index 154bd56..3d0afb09 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -16,6 +16,8 @@
package android.location;
+import android.Manifest;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.os.Build;
@@ -161,6 +163,7 @@
private WorkSource mWorkSource = null;
@UnsupportedAppUsage
private boolean mHideFromAppOps = false; // True if this request shouldn't be counted by AppOps
+ private boolean mLocationSettingsIgnored = false;
@UnsupportedAppUsage
private String mProvider = LocationManager.FUSED_PROVIDER;
@@ -261,6 +264,7 @@
mWorkSource = src.mWorkSource;
mHideFromAppOps = src.mHideFromAppOps;
mLowPowerMode = src.mLowPowerMode;
+ mLocationSettingsIgnored = src.mLocationSettingsIgnored;
}
/**
@@ -375,6 +379,32 @@
}
/**
+ * Requests that user location settings be ignored in order to satisfy this request. This API
+ * is only for use in extremely rare scenarios where it is appropriate to ignore user location
+ * settings, such as a user initiated emergency (dialing 911 for instance).
+ *
+ * @param locationSettingsIgnored Whether to ignore location settings
+ * @return the same object, so that setters can be chained
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @SystemApi
+ public LocationRequest setLocationSettingsIgnored(boolean locationSettingsIgnored) {
+ mLocationSettingsIgnored = locationSettingsIgnored;
+ return this;
+ }
+
+ /**
+ * Returns true if location settings will be ignored in order to satisfy this request.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean isLocationSettingsIgnored() {
+ return mLocationSettingsIgnored;
+ }
+
+ /**
* Explicitly set the fastest interval for location updates, in
* milliseconds.
*
diff --git a/location/lib/java/com/android/location/provider/ActivityChangedEvent.java b/location/lib/java/com/android/location/provider/ActivityChangedEvent.java
new file mode 100644
index 0000000..843dd67
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityChangedEvent.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2014 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.location.provider;
+
+import android.annotation.NonNull;
+
+import java.security.InvalidParameterException;
+import java.util.List;
+
+/**
+ * A class representing an event for Activity changes.
+ * @hide
+ */
+public class ActivityChangedEvent {
+ private final List<ActivityRecognitionEvent> mActivityRecognitionEvents;
+
+ public ActivityChangedEvent(List<ActivityRecognitionEvent> activityRecognitionEvents) {
+ if (activityRecognitionEvents == null) {
+ throw new InvalidParameterException(
+ "Parameter 'activityRecognitionEvents' must not be null.");
+ }
+
+ mActivityRecognitionEvents = activityRecognitionEvents;
+ }
+
+ @NonNull
+ public Iterable<ActivityRecognitionEvent> getActivityRecognitionEvents() {
+ return mActivityRecognitionEvents;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("[ ActivityChangedEvent:");
+
+ for (ActivityRecognitionEvent event : mActivityRecognitionEvents) {
+ builder.append("\n ");
+ builder.append(event.toString());
+ }
+ builder.append("\n]");
+
+ return builder.toString();
+ }
+}
diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionEvent.java b/location/lib/java/com/android/location/provider/ActivityRecognitionEvent.java
new file mode 100644
index 0000000..e54dea4
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityRecognitionEvent.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2014 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.location.provider;
+
+/**
+ * A class that represents an Activity Recognition Event.
+ * @hide
+ */
+public class ActivityRecognitionEvent {
+ private final String mActivity;
+ private final int mEventType;
+ private final long mTimestampNs;
+
+ public ActivityRecognitionEvent(String activity, int eventType, long timestampNs) {
+ mActivity = activity;
+ mEventType = eventType;
+ mTimestampNs = timestampNs;
+ }
+
+ public String getActivity() {
+ return mActivity;
+ }
+
+ public int getEventType() {
+ return mEventType;
+ }
+
+ public long getTimestampNs() {
+ return mTimestampNs;
+ }
+
+ @Override
+ public String toString() {
+ String eventString;
+ switch (mEventType) {
+ case ActivityRecognitionProvider.EVENT_TYPE_ENTER:
+ eventString = "Enter";
+ break;
+ case ActivityRecognitionProvider.EVENT_TYPE_EXIT:
+ eventString = "Exit";
+ break;
+ case ActivityRecognitionProvider.EVENT_TYPE_FLUSH_COMPLETE:
+ eventString = "FlushComplete";
+ break;
+ default:
+ eventString = "<Invalid>";
+ break;
+ }
+
+ return String.format(
+ "Activity='%s', EventType=%s(%s), TimestampNs=%s",
+ mActivity,
+ eventString,
+ mEventType,
+ mTimestampNs);
+ }
+}
diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionProvider.java b/location/lib/java/com/android/location/provider/ActivityRecognitionProvider.java
new file mode 100644
index 0000000..0eff7d3
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityRecognitionProvider.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2014 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.location.provider;
+
+import com.android.internal.util.Preconditions;
+
+import android.hardware.location.IActivityRecognitionHardware;
+import android.hardware.location.IActivityRecognitionHardwareSink;
+import android.os.RemoteException;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+
+/**
+ * A class that exposes {@link IActivityRecognitionHardware} functionality to unbundled services.
+ * @hide
+ */
+public final class ActivityRecognitionProvider {
+ private final IActivityRecognitionHardware mService;
+ private final HashSet<Sink> mSinkSet = new HashSet<>();
+
+ // the following constants must remain in sync with activity_recognition.h
+
+ public static final String ACTIVITY_IN_VEHICLE = "android.activity_recognition.in_vehicle";
+ public static final String ACTIVITY_ON_BICYCLE = "android.activity_recognition.on_bicycle";
+ public static final String ACTIVITY_WALKING = "android.activity_recognition.walking";
+ public static final String ACTIVITY_RUNNING = "android.activity_recognition.running";
+ public static final String ACTIVITY_STILL = "android.activity_recognition.still";
+ public static final String ACTIVITY_TILTING = "android.activity_recognition.tilting";
+
+ // NOTE: when adding an additional EVENT_TYPE_, EVENT_TYPE_COUNT needs to be updated in
+ // android.hardware.location.ActivityRecognitionHardware
+ public static final int EVENT_TYPE_FLUSH_COMPLETE = 0;
+ public static final int EVENT_TYPE_ENTER = 1;
+ public static final int EVENT_TYPE_EXIT = 2;
+
+ // end constants activity_recognition.h
+
+ /**
+ * Used to receive Activity-Recognition events.
+ */
+ public interface Sink {
+ void onActivityChanged(ActivityChangedEvent event);
+ }
+
+ public ActivityRecognitionProvider(IActivityRecognitionHardware service)
+ throws RemoteException {
+ Preconditions.checkNotNull(service);
+ mService = service;
+ mService.registerSink(new SinkTransport());
+ }
+
+ public String[] getSupportedActivities() throws RemoteException {
+ return mService.getSupportedActivities();
+ }
+
+ public boolean isActivitySupported(String activity) throws RemoteException {
+ return mService.isActivitySupported(activity);
+ }
+
+ public void registerSink(Sink sink) {
+ Preconditions.checkNotNull(sink);
+ synchronized (mSinkSet) {
+ mSinkSet.add(sink);
+ }
+ }
+
+ // TODO: if this functionality is exposed to 3rd party developers, handle unregistration (here
+ // and in the service) of all sinks while failing to disable all events
+ public void unregisterSink(Sink sink) {
+ Preconditions.checkNotNull(sink);
+ synchronized (mSinkSet) {
+ mSinkSet.remove(sink);
+ }
+ }
+
+ public boolean enableActivityEvent(String activity, int eventType, long reportLatencyNs)
+ throws RemoteException {
+ return mService.enableActivityEvent(activity, eventType, reportLatencyNs);
+ }
+
+ public boolean disableActivityEvent(String activity, int eventType) throws RemoteException {
+ return mService.disableActivityEvent(activity, eventType);
+ }
+
+ public boolean flush() throws RemoteException {
+ return mService.flush();
+ }
+
+ private final class SinkTransport extends IActivityRecognitionHardwareSink.Stub {
+ @Override
+ public void onActivityChanged(android.hardware.location.ActivityChangedEvent event) {
+ Collection<Sink> sinks;
+ synchronized (mSinkSet) {
+ if (mSinkSet.isEmpty()) {
+ return;
+ }
+ sinks = new ArrayList<>(mSinkSet);
+ }
+
+ // translate the event from platform internal and GmsCore types
+ ArrayList<ActivityRecognitionEvent> gmsEvents = new ArrayList<>();
+ for (android.hardware.location.ActivityRecognitionEvent reportingEvent
+ : event.getActivityRecognitionEvents()) {
+ ActivityRecognitionEvent gmsEvent = new ActivityRecognitionEvent(
+ reportingEvent.getActivity(),
+ reportingEvent.getEventType(),
+ reportingEvent.getTimestampNs());
+ gmsEvents.add(gmsEvent);
+ }
+ ActivityChangedEvent gmsEvent = new ActivityChangedEvent(gmsEvents);
+
+ for (Sink sink : sinks) {
+ sink.onActivityChanged(gmsEvent);
+ }
+ }
+ }
+}
diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionProviderClient.java b/location/lib/java/com/android/location/provider/ActivityRecognitionProviderClient.java
new file mode 100644
index 0000000..326d901
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityRecognitionProviderClient.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2015 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.location.provider;
+
+import android.annotation.NonNull;
+import android.hardware.location.IActivityRecognitionHardware;
+import android.hardware.location.IActivityRecognitionHardwareClient;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A client class for interaction with an Activity-Recognition provider.
+ * @hide
+ */
+public abstract class ActivityRecognitionProviderClient {
+ private static final String TAG = "ArProviderClient";
+
+ protected ActivityRecognitionProviderClient() {}
+
+ private IActivityRecognitionHardwareClient.Stub mClient =
+ new IActivityRecognitionHardwareClient.Stub() {
+ @Override
+ public void onAvailabilityChanged(
+ boolean isSupported,
+ IActivityRecognitionHardware instance) {
+ int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID) {
+ Log.d(TAG, "Ignoring calls from non-system server. Uid: " + callingUid);
+ return;
+ }
+ ActivityRecognitionProvider provider;
+ try {
+ provider = isSupported ? new ActivityRecognitionProvider(instance) : null;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error creating Hardware Activity-Recognition Provider.", e);
+ return;
+ }
+ onProviderChanged(isSupported, provider);
+ }
+ };
+
+ /**
+ * Gets the binder needed to interact with proxy provider in the platform.
+ */
+ @NonNull
+ public IBinder getBinder() {
+ return mClient;
+ }
+
+ /**
+ * Called when a change in the availability of {@link ActivityRecognitionProvider} is detected.
+ *
+ * @param isSupported whether the platform supports the provider natively
+ * @param instance the available provider's instance
+ */
+ public abstract void onProviderChanged(
+ boolean isSupported,
+ ActivityRecognitionProvider instance);
+}
diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionProviderWatcher.java b/location/lib/java/com/android/location/provider/ActivityRecognitionProviderWatcher.java
new file mode 100644
index 0000000..42f77b4
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityRecognitionProviderWatcher.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2014 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.location.provider;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.location.IActivityRecognitionHardware;
+import android.hardware.location.IActivityRecognitionHardwareWatcher;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A watcher class for Activity-Recognition instances.
+ *
+ * @deprecated use {@link ActivityRecognitionProviderClient} instead.
+ * @hide
+ */
+@Deprecated
+public class ActivityRecognitionProviderWatcher {
+ private static final String TAG = "ActivityRecognitionProviderWatcher";
+
+ private static ActivityRecognitionProviderWatcher sWatcher;
+ private static final Object sWatcherLock = new Object();
+
+ private ActivityRecognitionProvider mActivityRecognitionProvider;
+
+ private ActivityRecognitionProviderWatcher() {}
+
+ public static ActivityRecognitionProviderWatcher getInstance() {
+ synchronized (sWatcherLock) {
+ if (sWatcher == null) {
+ sWatcher = new ActivityRecognitionProviderWatcher();
+ }
+ return sWatcher;
+ }
+ }
+
+ private IActivityRecognitionHardwareWatcher.Stub mWatcherStub =
+ new IActivityRecognitionHardwareWatcher.Stub() {
+ @Override
+ public void onInstanceChanged(IActivityRecognitionHardware instance) {
+ int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID) {
+ Log.d(TAG, "Ignoring calls from non-system server. Uid: " + callingUid);
+ return;
+ }
+
+ try {
+ mActivityRecognitionProvider = new ActivityRecognitionProvider(instance);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error creating Hardware Activity-Recognition", e);
+ }
+ }
+ };
+
+ /**
+ * Gets the binder needed to interact with proxy provider in the platform.
+ */
+ @NonNull
+ public IBinder getBinder() {
+ return mWatcherStub;
+ }
+
+ /**
+ * Gets an object that supports the functionality of {@link ActivityRecognitionProvider}.
+ *
+ * @return Non-null value if the functionality is supported by the platform, false otherwise.
+ */
+ @Nullable
+ public ActivityRecognitionProvider getActivityRecognitionProvider() {
+ return mActivityRecognitionProvider;
+ }
+}
diff --git a/lowpan/tests/Android.mk b/lowpan/tests/Android.mk
index 67727a7..832ed2f 100644
--- a/lowpan/tests/Android.mk
+++ b/lowpan/tests/Android.mk
@@ -45,7 +45,7 @@
LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude)
LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-test \
+ androidx.test.rules \
guava \
mockito-target-minus-junit4 \
frameworks-base-testutils \
diff --git a/lowpan/tests/AndroidManifest.xml b/lowpan/tests/AndroidManifest.xml
index a216214..4225613 100644
--- a/lowpan/tests/AndroidManifest.xml
+++ b/lowpan/tests/AndroidManifest.xml
@@ -30,7 +30,7 @@
</activity>
</application>
- <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="android.net.lowpan.test"
android:label="Frameworks LoWPAN API Tests">
</instrumentation>
diff --git a/lowpan/tests/AndroidTest.xml b/lowpan/tests/AndroidTest.xml
index 72ad050..978cc02 100644
--- a/lowpan/tests/AndroidTest.xml
+++ b/lowpan/tests/AndroidTest.xml
@@ -22,6 +22,6 @@
<option name="test-tag" value="FrameworksLowpanApiTests" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.net.lowpan.test" />
- <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
</test>
</configuration>
diff --git a/lowpan/tests/README.md b/lowpan/tests/README.md
index d0eed95..cb5772e 100644
--- a/lowpan/tests/README.md
+++ b/lowpan/tests/README.md
@@ -37,7 +37,7 @@
If you manually build and push the test APK to the device you can run tests using
```
-adb shell am instrument -w 'android.net.wifi.test/android.support.test.runner.AndroidJUnitRunner'
+adb shell am instrument -w 'android.net.wifi.test/androidx.test.runner.AndroidJUnitRunner'
```
## Adding Tests
diff --git a/lowpan/tests/runtests.sh b/lowpan/tests/runtests.sh
index 040f4f0..8267a79 100755
--- a/lowpan/tests/runtests.sh
+++ b/lowpan/tests/runtests.sh
@@ -21,4 +21,4 @@
adb install -r -g "$OUT/data/app/FrameworksLowpanApiTests/FrameworksLowpanApiTests.apk"
-adb shell am instrument -w "$@" 'android.net.lowpan.test/android.support.test.runner.AndroidJUnitRunner'
+adb shell am instrument -w "$@" 'android.net.lowpan.test/androidx.test.runner.AndroidJUnitRunner'
diff --git a/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java b/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java
index a495d3d..86f9d0e 100644
--- a/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java
+++ b/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java
@@ -23,15 +23,18 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.test.TestLooper;
-import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
-import java.util.Map;
+
+import androidx.test.runner.AndroidJUnit4;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Map;
+
/** Unit tests for android.net.lowpan.LowpanInterface. */
@RunWith(AndroidJUnit4.class)
@SmallTest
diff --git a/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java b/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java
index 3dd7504..998e8a5 100644
--- a/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java
+++ b/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java
@@ -26,8 +26,10 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.test.TestLooper;
-import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/media/Android.bp b/media/Android.bp
index 8ebc91a..0eb86ac 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -16,8 +16,7 @@
}
java_library {
- // TODO: include media2.jar in the media apex and add it to the bootclasspath.
- name: "media2",
+ name: "updatable-media",
srcs: [
":media2-srcs",
@@ -28,6 +27,8 @@
"mediaplayer2-protos",
],
+ installable: true,
+
// Make sure that the implementaion only relies on SDK or system APIs.
sdk_version: "system_current",
}
@@ -35,19 +36,19 @@
filegroup {
name: "media2-srcs",
srcs: [
- "java/android/media/CloseGuard.java",
- "java/android/media/DataSourceCallback.java",
- "java/android/media/DataSourceDesc.java",
- "java/android/media/UriDataSourceDesc.java",
- "java/android/media/FileDataSourceDesc.java",
- "java/android/media/CallbackDataSourceDesc.java",
- "java/android/media/VideoSize.java",
- "java/android/media/Media2Utils.java",
- "java/android/media/MediaPlayer2Utils.java",
- "java/android/media/MediaPlayer2.java",
- "java/android/media/Media2HTTPService.java",
- "java/android/media/Media2HTTPConnection.java",
- "java/android/media/RoutingDelegate.java",
- "java/android/media/BufferingParams.java",
+ "apex/java/android/media/CloseGuard.java",
+ "apex/java/android/media/DataSourceCallback.java",
+ "apex/java/android/media/DataSourceDesc.java",
+ "apex/java/android/media/UriDataSourceDesc.java",
+ "apex/java/android/media/FileDataSourceDesc.java",
+ "apex/java/android/media/CallbackDataSourceDesc.java",
+ "apex/java/android/media/VideoSize.java",
+ "apex/java/android/media/Media2Utils.java",
+ "apex/java/android/media/MediaPlayer2Utils.java",
+ "apex/java/android/media/MediaPlayer2.java",
+ "apex/java/android/media/Media2HTTPService.java",
+ "apex/java/android/media/Media2HTTPConnection.java",
+ "apex/java/android/media/RoutingDelegate.java",
+ "apex/java/android/media/BufferingParams.java",
],
}
diff --git a/media/java/android/media/BufferingParams.java b/media/apex/java/android/media/BufferingParams.java
similarity index 100%
rename from media/java/android/media/BufferingParams.java
rename to media/apex/java/android/media/BufferingParams.java
diff --git a/media/java/android/media/CallbackDataSourceDesc.java b/media/apex/java/android/media/CallbackDataSourceDesc.java
similarity index 100%
rename from media/java/android/media/CallbackDataSourceDesc.java
rename to media/apex/java/android/media/CallbackDataSourceDesc.java
diff --git a/media/java/android/media/CloseGuard.java b/media/apex/java/android/media/CloseGuard.java
similarity index 100%
rename from media/java/android/media/CloseGuard.java
rename to media/apex/java/android/media/CloseGuard.java
diff --git a/media/java/android/media/DataSourceCallback.java b/media/apex/java/android/media/DataSourceCallback.java
similarity index 100%
rename from media/java/android/media/DataSourceCallback.java
rename to media/apex/java/android/media/DataSourceCallback.java
diff --git a/media/java/android/media/DataSourceDesc.java b/media/apex/java/android/media/DataSourceDesc.java
similarity index 100%
rename from media/java/android/media/DataSourceDesc.java
rename to media/apex/java/android/media/DataSourceDesc.java
diff --git a/media/java/android/media/FileDataSourceDesc.java b/media/apex/java/android/media/FileDataSourceDesc.java
similarity index 100%
rename from media/java/android/media/FileDataSourceDesc.java
rename to media/apex/java/android/media/FileDataSourceDesc.java
diff --git a/media/java/android/media/Media2HTTPConnection.java b/media/apex/java/android/media/Media2HTTPConnection.java
similarity index 100%
rename from media/java/android/media/Media2HTTPConnection.java
rename to media/apex/java/android/media/Media2HTTPConnection.java
diff --git a/media/java/android/media/Media2HTTPService.java b/media/apex/java/android/media/Media2HTTPService.java
similarity index 100%
rename from media/java/android/media/Media2HTTPService.java
rename to media/apex/java/android/media/Media2HTTPService.java
diff --git a/media/java/android/media/Media2Utils.java b/media/apex/java/android/media/Media2Utils.java
similarity index 100%
rename from media/java/android/media/Media2Utils.java
rename to media/apex/java/android/media/Media2Utils.java
diff --git a/media/java/android/media/MediaPlayer2.java b/media/apex/java/android/media/MediaPlayer2.java
similarity index 100%
rename from media/java/android/media/MediaPlayer2.java
rename to media/apex/java/android/media/MediaPlayer2.java
diff --git a/media/java/android/media/MediaPlayer2Utils.java b/media/apex/java/android/media/MediaPlayer2Utils.java
similarity index 100%
rename from media/java/android/media/MediaPlayer2Utils.java
rename to media/apex/java/android/media/MediaPlayer2Utils.java
diff --git a/media/java/android/media/RoutingDelegate.java b/media/apex/java/android/media/RoutingDelegate.java
similarity index 100%
rename from media/java/android/media/RoutingDelegate.java
rename to media/apex/java/android/media/RoutingDelegate.java
diff --git a/media/java/android/media/UriDataSourceDesc.java b/media/apex/java/android/media/UriDataSourceDesc.java
similarity index 100%
rename from media/java/android/media/UriDataSourceDesc.java
rename to media/apex/java/android/media/UriDataSourceDesc.java
diff --git a/media/java/android/media/VideoSize.java b/media/apex/java/android/media/VideoSize.java
similarity index 100%
rename from media/java/android/media/VideoSize.java
rename to media/apex/java/android/media/VideoSize.java
diff --git a/media/java/android/media/AudioRecordingConfiguration.java b/media/java/android/media/AudioRecordingConfiguration.java
index 52771e4..1d763ce 100644
--- a/media/java/android/media/AudioRecordingConfiguration.java
+++ b/media/java/android/media/AudioRecordingConfiguration.java
@@ -356,11 +356,11 @@
dest.writeInt(mDeviceSource);
dest.writeInt(mClientEffects.length);
for (int i = 0; i < mClientEffects.length; i++) {
- mClientEffects[i].writeToParcel(dest, 0);
+ mClientEffects[i].writeToParcel(dest);
}
dest.writeInt(mDeviceEffects.length);
for (int i = 0; i < mDeviceEffects.length; i++) {
- mDeviceEffects[i].writeToParcel(dest, 0);
+ mDeviceEffects[i].writeToParcel(dest);
}
}
@@ -375,13 +375,13 @@
mClientPortId = in.readInt();
mClientSilenced = in.readBoolean();
mDeviceSource = in.readInt();
- mClientEffects = AudioEffect.Descriptor.CREATOR.newArray(in.readInt());
+ mClientEffects = new AudioEffect.Descriptor[in.readInt()];
for (int i = 0; i < mClientEffects.length; i++) {
- mClientEffects[i] = AudioEffect.Descriptor.CREATOR.createFromParcel(in);
+ mClientEffects[i] = new AudioEffect.Descriptor(in);
}
- mDeviceEffects = AudioEffect.Descriptor.CREATOR.newArray(in.readInt());
- for (int i = 0; i < mClientEffects.length; i++) {
- mDeviceEffects[i] = AudioEffect.Descriptor.CREATOR.createFromParcel(in);
+ mDeviceEffects = new AudioEffect.Descriptor[in.readInt()];
+ for (int i = 0; i < mDeviceEffects.length; i++) {
+ mDeviceEffects[i] = new AudioEffect.Descriptor(in);
}
}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 2848b89..af016d5 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1100,7 +1100,8 @@
(1 << STREAM_RING) |
(1 << STREAM_NOTIFICATION) |
(1 << STREAM_SYSTEM) |
- (1 << STREAM_VOICE_CALL);
+ (1 << STREAM_VOICE_CALL) |
+ (1 << STREAM_BLUETOOTH_SCO);
/**
* Event posted by AudioTrack and AudioRecord JNI (JNIDeviceCallback) when routing changes.
diff --git a/media/java/android/media/IRemoteVolumeController.aidl b/media/java/android/media/IRemoteVolumeController.aidl
index e4a4a42..74c05c4 100644
--- a/media/java/android/media/IRemoteVolumeController.aidl
+++ b/media/java/android/media/IRemoteVolumeController.aidl
@@ -9,6 +9,7 @@
*
* 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.
@@ -16,7 +17,7 @@
package android.media;
-import android.media.session.ISessionController;
+import android.media.session.MediaSession;
/**
* AIDL for the MediaSessionService to report interesting events on remote playback
@@ -25,8 +26,8 @@
* @hide
*/
oneway interface IRemoteVolumeController {
- void remoteVolumeChanged(ISessionController session, int flags);
+ void remoteVolumeChanged(in MediaSession.Token sessionToken, int flags);
// sets the default session to use with the slider, replaces remoteSliderVisibility
// on IVolumeController
- void updateRemoteController(ISessionController session);
+ void updateRemoteController(in MediaSession.Token sessionToken);
}
diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java
index 7c5335b..887b447 100644
--- a/media/java/android/media/MediaController2.java
+++ b/media/java/android/media/MediaController2.java
@@ -45,7 +45,7 @@
/**
* Allows an app to interact with an active {@link MediaSession2} or a
- * MediaSession2Service which would provide {@link MediaSession2}. Media buttons and other
+ * {@link MediaSession2Service} which would provide {@link MediaSession2}. Media buttons and other
* commands can be sent to the session.
* <p>
* This API is not generally intended for third party application developers.
@@ -53,7 +53,6 @@
* <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
* for consistent behavior across all devices.
*/
-// TODO: use @link for MediaSession2Service
public class MediaController2 implements AutoCloseable {
static final String TAG = "MediaController2";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -71,6 +70,8 @@
private final Object mLock = new Object();
//@GuardedBy("mLock")
+ private boolean mClosed;
+ //@GuardedBy("mLock")
private int mNextSeqNumber;
//@GuardedBy("mLock")
private Session2Link mSessionBinder;
@@ -141,7 +142,14 @@
@Override
public void close() {
synchronized (mLock) {
+ if (mClosed) {
+ // Already closed. Ignore rest of clean up code.
+ // Note: unbindService() throws IllegalArgumentException when it's called twice.
+ return;
+ }
+ mClosed = true;
if (mServiceConnection != null) {
+ // Note: This should be called even when the bindService() has returned false.
mContext.unbindService(mServiceConnection);
}
if (mSessionBinder != null) {
@@ -152,6 +160,7 @@
// No-op
}
}
+ mConnectedToken = null;
mPendingCommands.clear();
mRequestedCommandSeqNumbers.clear();
mCallbackExecutor.execute(() -> {
@@ -162,6 +171,22 @@
}
/**
+ * Returns {@link Session2Token} of the connected session.
+ * If it is not connected yet, it returns {@code null}.
+ * <p>
+ * This may differ with the {@link Session2Token} from the constructor. For example, if the
+ * controller is created with the token for {@link MediaSession2Service}, this would return
+ * token for the {@link MediaSession2} in the service.
+ *
+ * @return Session2Token of the connected session, or {@code null} if not connected
+ */
+ public Session2Token getConnectedSessionToken() {
+ synchronized (mLock) {
+ return mConnectedToken;
+ }
+ }
+
+ /**
* Returns whether the session's playback is active.
*
* @return {@code true} if playback active. {@code false} otherwise.
@@ -299,7 +324,7 @@
MediaController2.this, command, args);
if (resultReceiver != null) {
if (result == null) {
- throw new RuntimeException("onSessionCommand shouldn't return null");
+ resultReceiver.send(Session2Command.RESULT_INFO_SKIPPED, null);
} else {
resultReceiver.send(result.getResultCode(), result.getResultData());
}
@@ -416,8 +441,8 @@
* @param controller the controller for this event
* @param command the session command
* @param args optional arguments
- * @return the result for the session command. A runtime exception will be thrown if null
- * is returned.
+ * @return the result for the session command. If {@code null}, RESULT_INFO_SKIPPED
+ * will be sent to the session.
*/
@Nullable
public Session2Command.Result onSessionCommand(@NonNull MediaController2 controller,
diff --git a/media/java/android/media/MediaDescription.java b/media/java/android/media/MediaDescription.java
index e6aea99..31079e5 100644
--- a/media/java/android/media/MediaDescription.java
+++ b/media/java/android/media/MediaDescription.java
@@ -7,6 +7,7 @@
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
/**
* A simple set of metadata for a media item suitable for display. This can be
@@ -122,9 +123,9 @@
private MediaDescription(Parcel in) {
mMediaId = in.readString();
- mTitle = in.readCharSequence();
- mSubtitle = in.readCharSequence();
- mDescription = in.readCharSequence();
+ mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
mIcon = in.readParcelable(null);
mIconUri = in.readParcelable(null);
mExtras = in.readBundle();
@@ -210,9 +211,9 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mMediaId);
- dest.writeCharSequence(mTitle);
- dest.writeCharSequence(mSubtitle);
- dest.writeCharSequence(mDescription);
+ TextUtils.writeToParcel(mTitle, dest, 0);
+ TextUtils.writeToParcel(mSubtitle, dest, 0);
+ TextUtils.writeToParcel(mDescription, dest, 0);
dest.writeParcelable(mIcon, flags);
dest.writeParcelable(mIconUri, flags);
dest.writeBundle(mExtras);
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index bfc10da5..2d2c4a8 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -176,7 +176,8 @@
* @param uuid The UUID of the crypto scheme.
*/
public static final boolean isCryptoSchemeSupported(@NonNull UUID uuid) {
- return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), null);
+ return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), null,
+ SECURITY_LEVEL_UNKNOWN);
}
/**
@@ -189,7 +190,25 @@
*/
public static final boolean isCryptoSchemeSupported(
@NonNull UUID uuid, @NonNull String mimeType) {
- return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), mimeType);
+ return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid),
+ mimeType, SECURITY_LEVEL_UNKNOWN);
+ }
+
+ /**
+ * Query if the given scheme identified by its UUID is supported on
+ * this device, and whether the DRM plugin is able to handle the
+ * media container format specified by mimeType at the requested
+ * security level.
+ *
+ * @param uuid The UUID of the crypto scheme.
+ * @param mimeType The MIME type of the media container, e.g. "video/mp4"
+ * or "video/webm"
+ * @param securityLevel the security level requested
+ */
+ public static final boolean isCryptoSchemeSupported(
+ @NonNull UUID uuid, @NonNull String mimeType, @SecurityLevel int securityLevel) {
+ return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), mimeType,
+ securityLevel);
}
private static final byte[] getByteArrayFromUUID(@NonNull UUID uuid) {
@@ -206,7 +225,7 @@
}
private static final native boolean isCryptoSchemeSupportedNative(
- @NonNull byte[] uuid, @Nullable String mimeType);
+ @NonNull byte[] uuid, @Nullable String mimeType, @SecurityLevel int securityLevel);
private EventHandler createHandler() {
Looper looper;
diff --git a/media/java/android/media/MediaMetadata.java b/media/java/android/media/MediaMetadata.java
index 2721ad1..a3d75a3 100644
--- a/media/java/android/media/MediaMetadata.java
+++ b/media/java/android/media/MediaMetadata.java
@@ -34,8 +34,8 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Set;
import java.util.Objects;
+import java.util.Set;
/**
* Contains metadata about an item, such as the title, artist, etc.
@@ -422,7 +422,7 @@
}
private MediaMetadata(Parcel in) {
- mBundle = Bundle.setDefusable(in.readBundle(), true);
+ mBundle = in.readBundle();
}
/**
diff --git a/media/java/android/media/MediaParceledListSlice.aidl b/media/java/android/media/MediaParceledListSlice.aidl
new file mode 100644
index 0000000..5c0e5bc
--- /dev/null
+++ b/media/java/android/media/MediaParceledListSlice.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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.media;
+
+/** @hide */
+parcelable MediaParceledListSlice;
diff --git a/media/java/android/media/MediaParceledListSlice.java b/media/java/android/media/MediaParceledListSlice.java
new file mode 100644
index 0000000..16a37d9
--- /dev/null
+++ b/media/java/android/media/MediaParceledListSlice.java
@@ -0,0 +1,200 @@
+/*
+ * 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.media;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Transfer a large list of objects across an IPC. Splits into multiple transactions if needed.
+ * Note: Only use classes declared final in order to avoid subclasses overriding reading/writing
+ * parcel logic.
+ *
+ * TODO: Add test for sending large data
+ * @param <T> A Parcelable class which will be sent over the binder calls.
+ * @hide
+ */
+public class MediaParceledListSlice<T extends Parcelable> implements Parcelable {
+ private static final String TAG = "MediaParceledListSlice";
+ private static final boolean DEBUG = false;
+
+ private static final int MAX_IPC_SIZE = 64 * 1024; // IBinder.MAX_IPC_SIZE
+
+ final List<T> mList;
+
+ public MediaParceledListSlice(List<T> list) {
+ if (list == null) {
+ throw new IllegalArgumentException("list shouldn't be null");
+ }
+ mList = list;
+ }
+
+ MediaParceledListSlice(Parcel p) {
+ final int itemCount = p.readInt();
+ mList = new ArrayList<>(itemCount);
+ if (DEBUG) {
+ Log.d(TAG, "Retrieving " + itemCount + " items");
+ }
+ if (itemCount <= 0) {
+ return;
+ }
+
+ int i = 0;
+ while (i < itemCount) {
+ if (p.readInt() == 0) {
+ break;
+ }
+
+ final T parcelable = p.readParcelable(null);
+ mList.add(parcelable);
+
+ if (DEBUG) {
+ Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size() - 1));
+ }
+ i++;
+ }
+ if (i >= itemCount) {
+ return;
+ }
+ final IBinder retriever = p.readStrongBinder();
+ while (i < itemCount) {
+ if (DEBUG) {
+ Log.d(TAG, "Reading more @" + i + " of " + itemCount + ": retriever=" + retriever);
+ }
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInt(i);
+ try {
+ retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failure retrieving array; only received " + i + " of " + itemCount, e);
+ return;
+ }
+ while (i < itemCount && reply.readInt() != 0) {
+ final T parcelable = reply.readParcelable(null);
+ mList.add(parcelable);
+
+ if (DEBUG) {
+ Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size() - 1));
+ }
+ i++;
+ }
+ reply.recycle();
+ data.recycle();
+ }
+ }
+
+ public List<T> getList() {
+ return mList;
+ }
+
+ /**
+ * Write this to another Parcel. Note that this discards the internal Parcel
+ * and should not be used anymore. This is so we can pass this to a Binder
+ * where we won't have a chance to call recycle on this.
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ final int itemCount = mList.size();
+ dest.writeInt(itemCount);
+ if (DEBUG) {
+ Log.d(TAG, "Writing " + itemCount + " items");
+ }
+ if (itemCount > 0) {
+ int i = 0;
+ while (i < itemCount && dest.dataSize() < MAX_IPC_SIZE) {
+ dest.writeInt(1);
+
+ final T parcelable = mList.get(i);
+ dest.writeParcelable(parcelable, flags);
+
+ if (DEBUG) {
+ Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i));
+ }
+ i++;
+ }
+ if (i < itemCount) {
+ dest.writeInt(0);
+ Binder retriever = new Binder() {
+ @Override
+ protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ if (code != FIRST_CALL_TRANSACTION) {
+ return super.onTransact(code, data, reply, flags);
+ }
+ int i = data.readInt();
+ if (DEBUG) {
+ Log.d(TAG, "Writing more @" + i + " of " + itemCount);
+ }
+ while (i < itemCount && reply.dataSize() < MAX_IPC_SIZE) {
+ reply.writeInt(1);
+
+ final T parcelable = mList.get(i);
+ reply.writeParcelable(parcelable, flags);
+
+ if (DEBUG) {
+ Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i));
+ }
+ i++;
+ }
+ if (i < itemCount) {
+ if (DEBUG) {
+ Log.d(TAG, "Breaking @" + i + " of " + itemCount);
+ }
+ reply.writeInt(0);
+ }
+ return true;
+ }
+ };
+ if (DEBUG) {
+ Log.d(TAG, "Breaking @" + i + " of " + itemCount + ": retriever=" + retriever);
+ }
+ dest.writeStrongBinder(retriever);
+ }
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ int contents = 0;
+ final List<T> list = getList();
+ for (int i = 0; i < list.size(); i++) {
+ contents |= list.get(i).describeContents();
+ }
+ return contents;
+ }
+
+ public static final Parcelable.Creator<MediaParceledListSlice> CREATOR =
+ new Parcelable.Creator<MediaParceledListSlice>() {
+ @Override
+ public MediaParceledListSlice createFromParcel(Parcel in) {
+ return new MediaParceledListSlice(in);
+ }
+
+ @Override
+ public MediaParceledListSlice[] newArray(int size) {
+ return new MediaParceledListSlice[size];
+ }
+ };
+}
diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java
index 3adac72..fdd07fd 100644
--- a/media/java/android/media/MediaSession2.java
+++ b/media/java/android/media/MediaSession2.java
@@ -90,6 +90,8 @@
private boolean mClosed;
//@GuardedBy("mLock")
private boolean mPlaybackActive;
+ //@GuardedBy("mLock")
+ private ForegroundServiceEventCallback mForegroundServiceEventCallback;
MediaSession2(@NonNull Context context, @NonNull String id, PendingIntent sessionActivity,
@NonNull Executor callbackExecutor, @NonNull SessionCallback callback) {
@@ -119,6 +121,7 @@
public void close() {
try {
List<ControllerInfo> controllerInfos;
+ ForegroundServiceEventCallback callback;
synchronized (mLock) {
if (mClosed) {
return;
@@ -126,11 +129,15 @@
mClosed = true;
controllerInfos = getConnectedControllers();
mConnectedControllers.clear();
- mCallback.onSessionClosed(this);
+ callback = mForegroundServiceEventCallback;
+ mForegroundServiceEventCallback = null;
}
synchronized (MediaSession2.class) {
SESSION_ID_LIST.remove(mSessionId);
}
+ if (callback != null) {
+ callback.onSessionClosed(this);
+ }
for (ControllerInfo info : controllerInfos) {
info.notifyDisconnected();
}
@@ -224,11 +231,16 @@
* @param playbackActive {@code true} if the playback active, {@code false} otherwise.
**/
public void setPlaybackActive(boolean playbackActive) {
+ final ForegroundServiceEventCallback serviceCallback;
synchronized (mLock) {
if (mPlaybackActive == playbackActive) {
return;
}
mPlaybackActive = playbackActive;
+ serviceCallback = mForegroundServiceEventCallback;
+ }
+ if (serviceCallback != null) {
+ serviceCallback.onPlaybackActiveChanged(this, playbackActive);
}
List<ControllerInfo> controllerInfos = getConnectedControllers();
for (ControllerInfo controller : controllerInfos) {
@@ -257,6 +269,18 @@
return mCallback;
}
+ void setForegroundServiceEventCallback(ForegroundServiceEventCallback callback) {
+ synchronized (mLock) {
+ if (mForegroundServiceEventCallback == callback) {
+ return;
+ }
+ if (mForegroundServiceEventCallback != null && callback != null) {
+ throw new IllegalStateException("A session cannot be added to multiple services");
+ }
+ mForegroundServiceEventCallback = callback;
+ }
+ }
+
// Called by Session2Link.onConnect and MediaSession2Service.MediaSession2ServiceStub.connect
void onConnect(final Controller2Link controller, int callingPid, int callingUid, int seq,
Bundle connectionRequest) {
@@ -373,7 +397,7 @@
MediaSession2.this, controllerInfo, command, args);
if (resultReceiver != null) {
if (result == null) {
- throw new RuntimeException("onSessionCommand shouldn't return null");
+ resultReceiver.send(Session2Command.RESULT_INFO_SKIPPED, null);
} else {
resultReceiver.send(result.getResultCode(), result.getResultData());
}
@@ -695,8 +719,6 @@
* This API is not generally intended for third party application developers.
*/
public abstract static class SessionCallback {
- ForegroundServiceEventCallback mForegroundServiceEventCallback;
-
/**
* Called when a controller is created for this session. Return allowed commands for
* controller. By default it returns {@code null}.
@@ -731,8 +753,8 @@
* @param controller controller information
* @param command the session command
* @param args optional arguments
- * @return the result for the session command. A runtime exception will be thrown if null
- * is returned.
+ * @return the result for the session command. If {@code null}, RESULT_INFO_SKIPPED
+ * will be sent to the session.
*/
@Nullable
public Session2Command.Result onSessionCommand(@NonNull MediaSession2 session,
@@ -753,19 +775,10 @@
public void onCommandResult(@NonNull MediaSession2 session,
@NonNull ControllerInfo controller, @NonNull Object token,
@NonNull Session2Command command, @NonNull Session2Command.Result result) {}
+ }
- final void onSessionClosed(MediaSession2 session) {
- if (mForegroundServiceEventCallback != null) {
- mForegroundServiceEventCallback.onSessionClosed(session);
- }
- }
-
- void setForegroundServiceEventCallback(ForegroundServiceEventCallback callback) {
- mForegroundServiceEventCallback = callback;
- }
-
- abstract static class ForegroundServiceEventCallback {
- public void onSessionClosed(MediaSession2 session) {}
- }
+ abstract static class ForegroundServiceEventCallback {
+ public void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) {}
+ public void onSessionClosed(MediaSession2 session) {}
}
}
diff --git a/media/java/android/media/MediaSession2Service.java b/media/java/android/media/MediaSession2Service.java
index 8fb00fe..5bb746a 100644
--- a/media/java/android/media/MediaSession2Service.java
+++ b/media/java/android/media/MediaSession2Service.java
@@ -19,7 +19,10 @@
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.NotificationManager;
import android.app.Service;
+import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
@@ -28,8 +31,6 @@
import android.util.ArrayMap;
import android.util.Log;
-import com.android.internal.annotations.GuardedBy;
-
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
@@ -42,11 +43,7 @@
* Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
* <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
* for consistent behavior across all devices.
- * @hide
*/
-// TODO: Unhide
-// TODO: Add onUpdateNotification(), and calls it to get Notification for startForegroundService()
-// when a session's player state becomes playing.
public abstract class MediaSession2Service extends Service {
/**
* The {@link Intent} that must be declared as handled by the service.
@@ -56,10 +53,29 @@
private static final String TAG = "MediaSession2Service";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private final Object mLock = new Object();
- @GuardedBy("mLock")
- private Map<String, MediaSession2> mSessions = new ArrayMap<>();
+ private final MediaSession2.ForegroundServiceEventCallback mForegroundServiceEventCallback =
+ new MediaSession2.ForegroundServiceEventCallback() {
+ @Override
+ public void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) {
+ MediaSession2Service.this.onPlaybackActiveChanged(session, playbackActive);
+ }
+ @Override
+ public void onSessionClosed(MediaSession2 session) {
+ removeSession(session);
+ }
+ };
+
+ private final Object mLock = new Object();
+ //@GuardedBy("mLock")
+ private NotificationManager mNotificationManager;
+ //@GuardedBy("mLock")
+ private Intent mStartSelfIntent;
+ //@GuardedBy("mLock")
+ private Map<String, MediaSession2> mSessions = new ArrayMap<>();
+ //@GuardedBy("mLock")
+ private Map<MediaSession2, MediaNotification> mNotifications = new ArrayMap<>();
+ //@GuardedBy("mLock")
private MediaSession2ServiceStub mStub;
/**
@@ -72,7 +88,12 @@
@Override
public void onCreate() {
super.onCreate();
- mStub = new MediaSession2ServiceStub(this);
+ synchronized (mLock) {
+ mStub = new MediaSession2ServiceStub(this);
+ mStartSelfIntent = new Intent(this, this.getClass());
+ mNotificationManager =
+ (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ }
}
@CallSuper
@@ -80,18 +101,13 @@
@Nullable
public IBinder onBind(@NonNull Intent intent) {
if (SERVICE_INTERFACE.equals(intent.getAction())) {
- return mStub;
+ synchronized (mLock) {
+ return mStub;
+ }
}
return null;
}
- @CallSuper
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- // TODO: Dispatch media key events to the primary session.
- return START_STICKY;
- }
-
/**
* Called by the system to notify that it is no longer used and is being removed. Do not call
* this method directly.
@@ -104,10 +120,12 @@
public void onDestroy() {
super.onDestroy();
synchronized (mLock) {
- for (MediaSession2 session : mSessions.values()) {
- session.getCallback().setForegroundServiceEventCallback(null);
+ List<MediaSession2> sessions = getSessions();
+ for (MediaSession2 session : sessions) {
+ removeSession(session);
}
mSessions.clear();
+ mNotifications.clear();
}
mStub.close();
}
@@ -144,6 +162,24 @@
public abstract MediaSession2 onGetPrimarySession();
/**
+ * Called when notification UI needs update. Override this method to show or cancel your own
+ * notification UI.
+ * <p>
+ * This would be called on {@link MediaSession2}'s callback executor when playback state is
+ * changed.
+ * <p>
+ * With the notification returned here, the service becomes foreground service when the playback
+ * is started. Apps must request the permission
+ * {@link android.Manifest.permission#FOREGROUND_SERVICE} in order to use this API. It becomes
+ * background service after the playback is stopped.
+ *
+ * @param session a session that needs notification update.
+ * @return a {@link MediaNotification}. Can be {@code null}.
+ */
+ @Nullable
+ public abstract MediaNotification onUpdateNotification(@NonNull MediaSession2 session);
+
+ /**
* Adds a session to this service.
* <p>
* Added session will be removed automatically when it's closed, or removed when
@@ -161,21 +197,15 @@
}
synchronized (mLock) {
MediaSession2 previousSession = mSessions.get(session.getSessionId());
- if (previousSession != session) {
- if (previousSession != null) {
+ if (previousSession != null) {
+ if (previousSession != session) {
Log.w(TAG, "Session ID should be unique, ID=" + session.getSessionId()
+ ", previous=" + previousSession + ", session=" + session);
}
return;
}
mSessions.put(session.getSessionId(), session);
- session.getCallback().setForegroundServiceEventCallback(
- new MediaSession2.SessionCallback.ForegroundServiceEventCallback() {
- @Override
- public void onSessionClosed(MediaSession2 session) {
- removeSession(session);
- }
- });
+ session.setForegroundServiceEventCallback(mForegroundServiceEventCallback);
}
}
@@ -189,8 +219,21 @@
if (session == null) {
throw new IllegalArgumentException("session shouldn't be null");
}
+ MediaNotification notification;
synchronized (mLock) {
+ if (mSessions.get(session.getSessionId()) != session) {
+ // Session isn't added or removed already.
+ return;
+ }
mSessions.remove(session.getSessionId());
+ notification = mNotifications.remove(session);
+ }
+ session.setForegroundServiceEventCallback(null);
+ if (notification != null) {
+ mNotificationManager.cancel(notification.getNotificationId());
+ }
+ if (getSessions().isEmpty()) {
+ stopForeground(false);
}
}
@@ -207,6 +250,78 @@
return list;
}
+ /**
+ * Called by registered {@link MediaSession2.ForegroundServiceEventCallback}
+ *
+ * @param session session with change
+ * @param playbackActive {@code true} if playback is active.
+ */
+ void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) {
+ MediaNotification mediaNotification = onUpdateNotification(session);
+ if (mediaNotification == null) {
+ // The service implementation doesn't want to use the automatic start/stopForeground
+ // feature.
+ return;
+ }
+ synchronized (mLock) {
+ mNotifications.put(session, mediaNotification);
+ }
+ int id = mediaNotification.getNotificationId();
+ Notification notification = mediaNotification.getNotification();
+ if (!playbackActive) {
+ mNotificationManager.notify(id, notification);
+ return;
+ }
+ // playbackActive == true
+ startForegroundService(mStartSelfIntent);
+ startForeground(id, notification);
+ }
+
+ /**
+ * Returned by {@link #onUpdateNotification(MediaSession2)} for making session service
+ * foreground service to keep playback running in the background. It's highly recommended to
+ * show media style notification here.
+ */
+ public static class MediaNotification {
+ private final int mNotificationId;
+ private final Notification mNotification;
+
+ /**
+ * Default constructor
+ *
+ * @param notificationId notification id to be used for
+ * {@link NotificationManager#notify(int, Notification)}.
+ * @param notification a notification to make session service run in the foreground. Media
+ * style notification is recommended here.
+ */
+ public MediaNotification(int notificationId, @NonNull Notification notification) {
+ if (notification == null) {
+ throw new IllegalArgumentException("notification shouldn't be null");
+ }
+ mNotificationId = notificationId;
+ mNotification = notification;
+ }
+
+ /**
+ * Gets the id of the notification.
+ *
+ * @return the notification id
+ */
+ public int getNotificationId() {
+ return mNotificationId;
+ }
+
+ /**
+ * Gets the notification.
+ *
+ * @return the notification
+ */
+ @NonNull
+ public Notification getNotification() {
+ return mNotification;
+ }
+ }
+
private static final class MediaSession2ServiceStub extends IMediaSession2Service.Stub
implements AutoCloseable {
final WeakReference<MediaSession2Service> mService;
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index 3b51c82..325420b 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -21,18 +21,14 @@
import android.content.ComponentName;
import android.content.Intent;
import android.graphics.Bitmap;
+import android.media.session.MediaSession;
import android.media.session.MediaSessionLegacyHelper;
import android.media.session.PlaybackState;
-import android.media.session.MediaSession;
import android.os.Bundle;
-import android.os.Handler;
import android.os.Looper;
-import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
-import java.lang.IllegalArgumentException;
-
/**
* RemoteControlClient enables exposing information meant to be consumed by remote controls
* capable of displaying metadata, artwork and media transport control buttons.
@@ -682,7 +678,7 @@
// USE_SESSIONS
if (mSession != null) {
- int pbState = PlaybackState.getStateFromRccState(state);
+ int pbState = getStateFromRccState(state);
long position = hasPosition ? mPlaybackPositionMs
: PlaybackState.PLAYBACK_POSITION_UNKNOWN;
@@ -718,8 +714,7 @@
// USE_SESSIONS
if (mSession != null) {
PlaybackState.Builder bob = new PlaybackState.Builder(mSessionPlaybackState);
- bob.setActions(
- PlaybackState.getActionsFromRccControlFlags(transportControlFlags));
+ bob.setActions(getActionsFromRccControlFlags(transportControlFlags));
mSessionPlaybackState = bob.build();
mSession.setPlaybackState(mSessionPlaybackState);
}
@@ -1001,16 +996,19 @@
* Period for playback position drift checks, 15s when playing at 1x or slower.
*/
private final static long POSITION_REFRESH_PERIOD_PLAYING_MS = 15000;
+
/**
* Minimum period for playback position drift checks, never more often when every 2s, when
* fast forwarding or rewinding.
*/
private final static long POSITION_REFRESH_PERIOD_MIN_MS = 2000;
+
/**
* The value above which the difference between client-reported playback position and
* estimated position is considered a drift.
*/
private final static long POSITION_DRIFT_MAX_MS = 500;
+
/**
* Compute the period at which the estimated playback position should be compared against the
* actual playback position. Is a funciton of playback speed.
@@ -1025,4 +1023,151 @@
POSITION_REFRESH_PERIOD_MIN_MS);
}
}
+
+ /**
+ * Get the {@link PlaybackState} state for the given
+ * {@link RemoteControlClient} state.
+ *
+ * @param rccState The state used by {@link RemoteControlClient}.
+ * @return The equivalent state used by {@link PlaybackState}.
+ */
+ private static int getStateFromRccState(int rccState) {
+ switch (rccState) {
+ case PLAYSTATE_BUFFERING:
+ return PlaybackState.STATE_BUFFERING;
+ case PLAYSTATE_ERROR:
+ return PlaybackState.STATE_ERROR;
+ case PLAYSTATE_FAST_FORWARDING:
+ return PlaybackState.STATE_FAST_FORWARDING;
+ case PLAYSTATE_NONE:
+ return PlaybackState.STATE_NONE;
+ case PLAYSTATE_PAUSED:
+ return PlaybackState.STATE_PAUSED;
+ case PLAYSTATE_PLAYING:
+ return PlaybackState.STATE_PLAYING;
+ case PLAYSTATE_REWINDING:
+ return PlaybackState.STATE_REWINDING;
+ case PLAYSTATE_SKIPPING_BACKWARDS:
+ return PlaybackState.STATE_SKIPPING_TO_PREVIOUS;
+ case PLAYSTATE_SKIPPING_FORWARDS:
+ return PlaybackState.STATE_SKIPPING_TO_NEXT;
+ case PLAYSTATE_STOPPED:
+ return PlaybackState.STATE_STOPPED;
+ default:
+ return -1;
+ }
+ }
+
+ /**
+ * Get the {@link RemoteControlClient} state for the given
+ * {@link PlaybackState} state.
+ *
+ * @param state The state used by {@link PlaybackState}.
+ * @return The equivalent state used by {@link RemoteControlClient}.
+ */
+ static int getRccStateFromState(int state) {
+ switch (state) {
+ case PlaybackState.STATE_BUFFERING:
+ return PLAYSTATE_BUFFERING;
+ case PlaybackState.STATE_ERROR:
+ return PLAYSTATE_ERROR;
+ case PlaybackState.STATE_FAST_FORWARDING:
+ return PLAYSTATE_FAST_FORWARDING;
+ case PlaybackState.STATE_NONE:
+ return PLAYSTATE_NONE;
+ case PlaybackState.STATE_PAUSED:
+ return PLAYSTATE_PAUSED;
+ case PlaybackState.STATE_PLAYING:
+ return PLAYSTATE_PLAYING;
+ case PlaybackState.STATE_REWINDING:
+ return PLAYSTATE_REWINDING;
+ case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
+ return PLAYSTATE_SKIPPING_BACKWARDS;
+ case PlaybackState.STATE_SKIPPING_TO_NEXT:
+ return PLAYSTATE_SKIPPING_FORWARDS;
+ case PlaybackState.STATE_STOPPED:
+ return PLAYSTATE_STOPPED;
+ default:
+ return -1;
+ }
+ }
+
+ private static long getActionsFromRccControlFlags(int rccFlags) {
+ long actions = 0;
+ long flag = 1;
+ while (flag <= rccFlags) {
+ if ((flag & rccFlags) != 0) {
+ actions |= getActionForRccFlag((int) flag);
+ }
+ flag = flag << 1;
+ }
+ return actions;
+ }
+
+ static int getRccControlFlagsFromActions(long actions) {
+ int rccFlags = 0;
+ long action = 1;
+ while (action <= actions && action < Integer.MAX_VALUE) {
+ if ((action & actions) != 0) {
+ rccFlags |= getRccFlagForAction(action);
+ }
+ action = action << 1;
+ }
+ return rccFlags;
+ }
+
+ private static long getActionForRccFlag(int flag) {
+ switch (flag) {
+ case FLAG_KEY_MEDIA_PREVIOUS:
+ return PlaybackState.ACTION_SKIP_TO_PREVIOUS;
+ case FLAG_KEY_MEDIA_REWIND:
+ return PlaybackState.ACTION_REWIND;
+ case FLAG_KEY_MEDIA_PLAY:
+ return PlaybackState.ACTION_PLAY;
+ case FLAG_KEY_MEDIA_PLAY_PAUSE:
+ return PlaybackState.ACTION_PLAY_PAUSE;
+ case FLAG_KEY_MEDIA_PAUSE:
+ return PlaybackState.ACTION_PAUSE;
+ case FLAG_KEY_MEDIA_STOP:
+ return PlaybackState.ACTION_STOP;
+ case FLAG_KEY_MEDIA_FAST_FORWARD:
+ return PlaybackState.ACTION_FAST_FORWARD;
+ case FLAG_KEY_MEDIA_NEXT:
+ return PlaybackState.ACTION_SKIP_TO_NEXT;
+ case FLAG_KEY_MEDIA_POSITION_UPDATE:
+ return PlaybackState.ACTION_SEEK_TO;
+ case FLAG_KEY_MEDIA_RATING:
+ return PlaybackState.ACTION_SET_RATING;
+ }
+ return 0;
+ }
+
+ private static int getRccFlagForAction(long action) {
+ // We only care about the lower set of actions that can map to rcc
+ // flags.
+ int testAction = action < Integer.MAX_VALUE ? (int) action : 0;
+ switch (testAction) {
+ case (int) PlaybackState.ACTION_SKIP_TO_PREVIOUS:
+ return FLAG_KEY_MEDIA_PREVIOUS;
+ case (int) PlaybackState.ACTION_REWIND:
+ return FLAG_KEY_MEDIA_REWIND;
+ case (int) PlaybackState.ACTION_PLAY:
+ return FLAG_KEY_MEDIA_PLAY;
+ case (int) PlaybackState.ACTION_PLAY_PAUSE:
+ return FLAG_KEY_MEDIA_PLAY_PAUSE;
+ case (int) PlaybackState.ACTION_PAUSE:
+ return FLAG_KEY_MEDIA_PAUSE;
+ case (int) PlaybackState.ACTION_STOP:
+ return FLAG_KEY_MEDIA_STOP;
+ case (int) PlaybackState.ACTION_FAST_FORWARD:
+ return FLAG_KEY_MEDIA_FAST_FORWARD;
+ case (int) PlaybackState.ACTION_SKIP_TO_NEXT:
+ return FLAG_KEY_MEDIA_NEXT;
+ case (int) PlaybackState.ACTION_SEEK_TO:
+ return FLAG_KEY_MEDIA_POSITION_UPDATE;
+ case (int) PlaybackState.ACTION_SET_RATING:
+ return FLAG_KEY_MEDIA_RATING;
+ }
+ return 0;
+ }
}
diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java
index 5e9eed7..f70963a 100644
--- a/media/java/android/media/RemoteController.java
+++ b/media/java/android/media/RemoteController.java
@@ -632,8 +632,8 @@
l = this.mOnClientUpdateListener;
}
if (l != null) {
- int playstate = state == null ? RemoteControlClient.PLAYSTATE_NONE : PlaybackState
- .getRccStateFromState(state.getState());
+ int playstate = state == null ? RemoteControlClient.PLAYSTATE_NONE
+ : RemoteControlClient.getRccStateFromState(state.getState());
if (state == null || state.getPosition() == PlaybackState.PLAYBACK_POSITION_UNKNOWN) {
l.onClientPlaybackStateUpdate(playstate);
} else {
@@ -642,7 +642,7 @@
}
if (state != null) {
l.onClientTransportControlUpdate(
- PlaybackState.getRccControlFlagsFromActions(state.getActions()));
+ RemoteControlClient.getRccControlFlagsFromActions(state.getActions()));
}
}
}
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index 42597aa..73d3d88 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -138,7 +138,7 @@
mAudioAttributes = attributes;
// The audio attributes have to be set before the media player is prepared.
// Re-initialize it.
- setUri(mUri);
+ setUri(mUri, mVolumeShaperConfig);
}
/**
@@ -415,6 +415,7 @@
mLocalPlayer.reset();
mLocalPlayer.release();
mLocalPlayer = null;
+ mVolumeShaper = null;
synchronized (sActiveRingtones) {
sActiveRingtones.remove(this);
}
diff --git a/media/java/android/media/Session2Token.java b/media/java/android/media/Session2Token.java
index d8f74c5..238cc2b 100644
--- a/media/java/android/media/Session2Token.java
+++ b/media/java/android/media/Session2Token.java
@@ -35,7 +35,7 @@
import java.util.Objects;
/**
- * Represents an ongoing {@link MediaSession2} or a MediaSession2Service.
+ * Represents an ongoing {@link MediaSession2} or a {@link MediaSession2Service}.
* If it's representing a session service, it may not be ongoing.
* <p>
* This API is not generally intended for third party application developers.
@@ -48,14 +48,6 @@
* <p>
* It can be also obtained by {@link android.media.session.MediaSessionManager}.
*/
-// New version of MediaSession2.Token for following reasons
-// - Stop implementing Parcelable for updatable support
-// - Represent session and library service (formerly browser service) in one class.
-// Previously MediaSession2.Token was for session and ComponentName was for service.
-// This helps controller apps to keep target of dispatching media key events in uniform way.
-// For details about the reason, see following. (Android O+)
-// android.media.session.MediaSessionManager.Callback#onAddressedPlayerChanged
-// TODO: use @link for MediaSession2Service
public final class Session2Token implements Parcelable {
private static final String TAG = "Session2Token";
@@ -85,12 +77,13 @@
public static final int TYPE_SESSION = 0;
/**
- * Type for MediaSession2Service.
+ * Type for {@link MediaSession2Service}.
*/
public static final int TYPE_SESSION_SERVICE = 1;
private final int mUid;
- private final @TokenType int mType;
+ @TokenType
+ private final int mType;
private final String mPackageName;
private final String mServiceName;
private final Session2Link mSessionLink;
@@ -206,14 +199,6 @@
}
/**
- * @hide
- * @return component name of the session. Can be {@code null} for {@link #TYPE_SESSION}.
- */
- public ComponentName getComponentName() {
- return mComponentName;
- }
-
- /**
* @return type of the token
* @see #TYPE_SESSION
* @see #TYPE_SESSION_SERVICE
@@ -222,10 +207,7 @@
return mType;
}
- /**
- * @hide
- */
- public Session2Link getSessionLink() {
+ Session2Link getSessionLink() {
return mSessionLink;
}
diff --git a/media/java/android/media/VolumeProvider.java b/media/java/android/media/VolumeProvider.java
index 1c017c5..0297406 100644
--- a/media/java/android/media/VolumeProvider.java
+++ b/media/java/android/media/VolumeProvider.java
@@ -16,6 +16,7 @@
package android.media;
import android.annotation.IntDef;
+import android.annotation.SystemApi;
import android.media.session.MediaSession;
import java.lang.annotation.Retention;
@@ -147,6 +148,7 @@
* Sets a callback to receive volume changes.
* @hide
*/
+ @SystemApi
public void setCallback(Callback callback) {
mCallback = callback;
}
@@ -155,6 +157,7 @@
* Listens for changes to the volume.
* @hide
*/
+ @SystemApi
public static abstract class Callback {
public abstract void onVolumeChanged(VolumeProvider volumeProvider);
}
diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java
index 52e9ae1..5b4bbce 100644
--- a/media/java/android/media/audiofx/AudioEffect.java
+++ b/media/java/android/media/audiofx/AudioEffect.java
@@ -26,7 +26,6 @@
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
-import android.os.Parcelable;
import android.util.Log;
import java.lang.ref.WeakReference;
@@ -229,7 +228,7 @@
* The method {@link #queryEffects()} returns an array of Descriptors to facilitate effects
* enumeration.
*/
- public static final class Descriptor implements Parcelable {
+ public static class Descriptor {
public Descriptor() {
}
@@ -294,7 +293,9 @@
this.implementor = implementor;
}
- private Descriptor(Parcel in) {
+ /** @hide */
+ @TestApi
+ public Descriptor(Parcel in) {
type = UUID.fromString(in.readString());
uuid = UUID.fromString(in.readString());
connectMode = in.readString();
@@ -302,33 +303,14 @@
implementor = in.readString();
}
- public static final Parcelable.Creator<Descriptor> CREATOR =
- new Parcelable.Creator<Descriptor>() {
- /**
- * Rebuilds a Descriptor previously stored with writeToParcel().
- * @param p Parcel object to read the Descriptor from
- * @return a new Descriptor created from the data in the parcel
- */
- public Descriptor createFromParcel(Parcel p) {
- return new Descriptor(p);
- }
- public Descriptor[] newArray(int size) {
- return new Descriptor[size];
- }
- };
-
@Override
public int hashCode() {
return Objects.hash(type, uuid, connectMode, name, implementor);
}
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
+ /** @hide */
+ @TestApi
+ public void writeToParcel(Parcel dest) {
dest.writeString(type.toString());
dest.writeString(uuid.toString());
dest.writeString(connectMode);
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java
index 2bccd88..b1b14c6 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/java/android/media/browse/MediaBrowser.java
@@ -23,8 +23,8 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
-import android.content.pm.ParceledListSlice;
import android.media.MediaDescription;
+import android.media.MediaParceledListSlice;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.os.Binder;
@@ -653,7 +653,7 @@
}
private final void onLoadChildren(final IMediaBrowserServiceCallbacks callback,
- final String parentId, final ParceledListSlice list, final Bundle options) {
+ final String parentId, final MediaParceledListSlice list, final Bundle options) {
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -1107,12 +1107,12 @@
}
@Override
- public void onLoadChildren(String parentId, ParceledListSlice list) {
+ public void onLoadChildren(String parentId, MediaParceledListSlice list) {
onLoadChildrenWithOptions(parentId, list, null);
}
@Override
- public void onLoadChildrenWithOptions(String parentId, ParceledListSlice list,
+ public void onLoadChildrenWithOptions(String parentId, MediaParceledListSlice list,
final Bundle options) {
MediaBrowser mediaBrowser = mMediaBrowser.get();
if (mediaBrowser != null) {
diff --git a/media/java/android/media/session/ControllerCallbackLink.java b/media/java/android/media/session/ControllerCallbackLink.java
index a143c9b..adc14a5 100644
--- a/media/java/android/media/session/ControllerCallbackLink.java
+++ b/media/java/android/media/session/ControllerCallbackLink.java
@@ -16,35 +16,42 @@
package android.media.session;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.content.Context;
+import android.content.pm.PackageManager;
import android.media.MediaMetadata;
+import android.media.MediaParceledListSlice;
import android.media.session.MediaController.PlaybackInfo;
import android.media.session.MediaSession.QueueItem;
+import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.Process;
import android.os.RemoteException;
import java.util.List;
/**
* Handles incoming commands to {@link MediaController.Callback}.
- * <p>
- * This API is not generally intended for third party application developers.
+ * @hide
*/
+@SystemApi
public final class ControllerCallbackLink implements Parcelable {
+ final Context mContext;
final CallbackStub mCallbackStub;
final ISessionControllerCallback mIControllerCallback;
/**
* Constructor for stub (Callee)
- * @hide
*/
- @SystemApi
- public ControllerCallbackLink(@NonNull CallbackStub callbackStub) {
+ public ControllerCallbackLink(@NonNull Context context, @NonNull CallbackStub callbackStub) {
+ mContext = context;
mCallbackStub = callbackStub;
mIControllerCallback = new CallbackStubProxy();
}
@@ -52,14 +59,16 @@
/**
* Constructor for interface (Caller)
*/
- ControllerCallbackLink(Parcel in) {
+ public ControllerCallbackLink(IBinder binder) {
+ mContext = null;
mCallbackStub = null;
- mIControllerCallback = ISessionControllerCallback.Stub.asInterface(in.readStrongBinder());
+ mIControllerCallback = ISessionControllerCallback.Stub.asInterface(binder);
}
/**
* Notify controller that the connected session is destroyed.
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifySessionDestroyed() {
try {
mIControllerCallback.notifySessionDestroyed();
@@ -74,6 +83,7 @@
* @param event the name of the event
* @param extras the extras included with the event
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyEvent(@NonNull String event, @Nullable Bundle extras) {
try {
mIControllerCallback.notifyEvent(event, extras);
@@ -87,6 +97,7 @@
*
* @param state the new playback state
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyPlaybackStateChanged(@Nullable PlaybackState state) {
try {
mIControllerCallback.notifyPlaybackStateChanged(state);
@@ -100,6 +111,7 @@
*
* @param metadata the new metadata
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyMetadataChanged(@Nullable MediaMetadata metadata) {
try {
mIControllerCallback.notifyMetadataChanged(metadata);
@@ -113,9 +125,11 @@
*
* @param queue the new queue
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyQueueChanged(@Nullable List<QueueItem> queue) {
try {
- mIControllerCallback.notifyQueueChanged(queue);
+ mIControllerCallback.notifyQueueChanged(queue == null ? null :
+ new MediaParceledListSlice(queue));
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -126,6 +140,7 @@
*
* @param title the new queue title
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyQueueTitleChanged(@Nullable CharSequence title) {
try {
mIControllerCallback.notifyQueueTitleChanged(title);
@@ -139,6 +154,7 @@
*
* @param extras the new extras
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyExtrasChanged(@Nullable Bundle extras) {
try {
mIControllerCallback.notifyExtrasChanged(extras);
@@ -152,6 +168,7 @@
*
* @param info the new playback info
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyVolumeInfoChanged(@NonNull PlaybackInfo info) {
try {
mIControllerCallback.notifyVolumeInfoChanged(info);
@@ -180,7 +197,7 @@
new Parcelable.Creator<ControllerCallbackLink>() {
@Override
public ControllerCallbackLink createFromParcel(Parcel in) {
- return new ControllerCallbackLink(in);
+ return new ControllerCallbackLink(in.readStrongBinder());
}
@Override
@@ -191,9 +208,7 @@
/**
* Class for Stub implementation
- * @hide
*/
- @SystemApi
public abstract static class CallbackStub {
/** Stub method for ISessionControllerCallback.notifySessionDestroyed */
public void onSessionDestroyed() {
@@ -241,22 +256,46 @@
@Override
public void notifyPlaybackStateChanged(PlaybackState state) {
- mCallbackStub.onPlaybackStateChanged(state);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onPlaybackStateChanged(state);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void notifyMetadataChanged(MediaMetadata metadata) {
- mCallbackStub.onMetadataChanged(metadata);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onMetadataChanged(metadata);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
- public void notifyQueueChanged(List<QueueItem> queue) {
- mCallbackStub.onQueueChanged(queue);
+ public void notifyQueueChanged(MediaParceledListSlice queue) {
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onQueueChanged(queue == null ? null : queue.getList());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void notifyQueueTitleChanged(CharSequence title) {
- mCallbackStub.onQueueTitleChanged(title);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onQueueTitleChanged(title);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
@@ -266,7 +305,25 @@
@Override
public void notifyVolumeInfoChanged(PlaybackInfo info) {
- mCallbackStub.onVolumeInfoChanged(info);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onVolumeInfoChanged(info);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private void ensureMediaControlPermission() {
+ // Check if it's system server or has MEDIA_CONTENT_CONTROL.
+ // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
+ // check here.
+ if (getCallingUid() == Process.SYSTEM_UID || mContext.checkCallingPermission(
+ android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+ == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ throw new SecurityException("Must hold the MEDIA_CONTENT_CONTROL permission.");
}
}
}
diff --git a/media/java/android/media/session/ControllerLink.aidl b/media/java/android/media/session/ControllerLink.aidl
new file mode 100644
index 0000000..532df59
--- /dev/null
+++ b/media/java/android/media/session/ControllerLink.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.media.session;
+
+parcelable ControllerLink;
diff --git a/media/java/android/media/session/ControllerLink.java b/media/java/android/media/session/ControllerLink.java
new file mode 100644
index 0000000..937df20
--- /dev/null
+++ b/media/java/android/media/session/ControllerLink.java
@@ -0,0 +1,986 @@
+/*
+ * 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.media.session;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.PendingIntent;
+import android.media.MediaMetadata;
+import android.media.MediaParceledListSlice;
+import android.media.Rating;
+import android.media.session.MediaController.PlaybackInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.view.KeyEvent;
+
+import java.util.List;
+
+/**
+ * Handles incoming commands from {@link MediaController}.
+ * @hide
+ */
+@SystemApi
+public final class ControllerLink implements Parcelable {
+ public static final Parcelable.Creator<ControllerLink> CREATOR =
+ new Parcelable.Creator<ControllerLink>() {
+ @Override
+ public ControllerLink createFromParcel(Parcel in) {
+ return new ControllerLink(in.readStrongBinder());
+ }
+
+ @Override
+ public ControllerLink[] newArray(int size) {
+ return new ControllerLink[size];
+ }
+ };
+
+ final ControllerStub mControllerStub;
+ final ISessionController mISessionController;
+
+ /**
+ * Constructor for stub (Callee)
+ */
+ public ControllerLink(@NonNull ControllerStub controllerStub) {
+ mControllerStub = controllerStub;
+ mISessionController = new StubProxy();
+ }
+
+ /**
+ * Constructor for interface (Caller)
+ */
+ public ControllerLink(IBinder binder) {
+ mControllerStub = null;
+ mISessionController = ISessionController.Stub.asInterface(binder);
+ }
+
+ /**
+ * Tell system that a controller sends a command.
+ *
+ * @param packageName the package name of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param command the name of the command
+ * @param args the arguments included with the command
+ * @param cb the result receiver for getting the result of the command
+ */
+ void sendCommand(@NonNull String packageName, @NonNull ControllerCallbackLink caller,
+ @NonNull String command, @Nullable Bundle args, @Nullable ResultReceiver cb) {
+ try {
+ mISessionController.sendCommand(packageName, caller, command, args, cb);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that a controller sends a media button event.
+ *
+ * @param packageName the package name of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param asSystemService whether this event should be considered as from system service
+ * @param mediaButton the media button key event
+ */
+ boolean sendMediaButton(@NonNull String packageName,
+ @NonNull ControllerCallbackLink caller, boolean asSystemService,
+ @NonNull KeyEvent mediaButton) {
+ try {
+ return mISessionController.sendMediaButton(packageName, caller, asSystemService,
+ mediaButton);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Registers a controller callback link to the system.
+ *
+ * @param packageName the package name of the controller
+ * @param cb the controller callback link to register
+ */
+ void registerCallback(@NonNull String packageName, @NonNull ControllerCallbackLink cb) {
+ try {
+ mISessionController.registerCallback(packageName, cb);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Unregisters a controller callback link from the system.
+ *
+ * @param cb the controller callback link to register
+ */
+ void unregisterCallback(@NonNull ControllerCallbackLink cb) {
+ try {
+ mISessionController.unregisterCallback(cb);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Gets the package name of the connected session.
+ */
+ @NonNull
+ String getPackageName() {
+ try {
+ return mISessionController.getPackageName();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Gets the tag of the connected session.
+ */
+ @NonNull
+ String getTag() {
+ try {
+ return mISessionController.getTag();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Gets the {@link PendingIntent} for launching UI of the connected session.
+ */
+ @Nullable
+ PendingIntent getLaunchPendingIntent() {
+ try {
+ return mISessionController.getLaunchPendingIntent();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Gets the flags of the connected session.
+ */
+ long getFlags() {
+ try {
+ return mISessionController.getFlags();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Gets the volume attributes of the connected session.
+ */
+ @NonNull
+ PlaybackInfo getVolumeAttributes() {
+ try {
+ return mISessionController.getVolumeAttributes();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that a controller requests adjusting the volume.
+ *
+ * @param packageName the package name of the controller
+ * @param opPackageName the op package name of this request
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param asSystemService whether this event should be considered as from system service
+ * @param direction the direction to adjust the volume in
+ * @param flags the flags with this volume change request
+ */
+ void adjustVolume(@NonNull String packageName, @NonNull String opPackageName,
+ @NonNull ControllerCallbackLink caller, boolean asSystemService, int direction,
+ int flags) {
+ try {
+ mISessionController.adjustVolume(packageName, opPackageName, caller, asSystemService,
+ direction, flags);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that a controller requests setting the volume.
+ *
+ * @param packageName the package name of the controller
+ * @param opPackageName the op package name of this request
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param flags the flags with this volume change request
+ */
+ void setVolumeTo(@NonNull String packageName, @NonNull String opPackageName,
+ @NonNull ControllerCallbackLink caller, int value, int flags) {
+ try {
+ mISessionController.setVolumeTo(packageName, opPackageName, caller, value, flags);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that a controller requests preparing media.
+ *
+ * @param packageName the package name of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ */
+ void prepare(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+ try {
+ mISessionController.prepare(packageName, caller);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that a controller requests preparing media from given media ID.
+ *
+ * @param packageName the package name of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param mediaId the ID of the media
+ * @param extras the extras included with this request.
+ */
+ void prepareFromMediaId(@NonNull String packageName,
+ @NonNull ControllerCallbackLink caller, @NonNull String mediaId,
+ @Nullable Bundle extras) {
+ try {
+ mISessionController.prepareFromMediaId(packageName, caller, mediaId, extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that a controller requests preparing media from given search query.
+ *
+ * @param packageName the package name of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param query the search query
+ * @param extras the extras included with this request.
+ */
+ void prepareFromSearch(@NonNull String packageName,
+ @NonNull ControllerCallbackLink caller, @NonNull String query,
+ @Nullable Bundle extras) {
+ try {
+ mISessionController.prepareFromSearch(packageName, caller, query, extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that a controller requests preparing media from given uri.
+ *
+ * @param packageName the package name of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param uri the uri of the media
+ * @param extras the extras included with this request.
+ */
+ void prepareFromUri(@NonNull String packageName, @NonNull ControllerCallbackLink caller,
+ @NonNull Uri uri, @Nullable Bundle extras) {
+ try {
+ mISessionController.prepareFromUri(packageName, caller, uri, extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that a controller requests playing media.
+ *
+ * @param packageName the package name of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ */
+ void play(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+ try {
+ mISessionController.play(packageName, caller);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that a controller requests playing media from given media ID.
+ *
+ * @param packageName the package name of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param mediaId the ID of the media
+ * @param extras the extras included with this request.
+ */
+ void playFromMediaId(@NonNull String packageName, @NonNull ControllerCallbackLink caller,
+ @NonNull String mediaId, @Nullable Bundle extras) {
+ try {
+ mISessionController.playFromMediaId(packageName, caller, mediaId, extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that a controller requests playing media from given search query.
+ *
+ * @param packageName the package name of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param query the search query
+ * @param extras the extras included with this request.
+ */
+ void playFromSearch(@NonNull String packageName, @NonNull ControllerCallbackLink caller,
+ @NonNull String query, @Nullable Bundle extras) {
+ try {
+ mISessionController.playFromSearch(packageName, caller, query, extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that a controller requests playing media from given uri.
+ *
+ * @param packageName the package name of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param uri the uri of the media
+ * @param extras the extras included with this request.
+ */
+ void playFromUri(@NonNull String packageName, @NonNull ControllerCallbackLink caller,
+ @NonNull Uri uri, @Nullable Bundle extras) {
+ try {
+ mISessionController.playFromUri(packageName, caller, uri, extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that a controller requests skipping to the queue item with given ID.
+ *
+ * @param packageName the package name of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param id the queue id of the item
+ */
+ void skipToQueueItem(@NonNull String packageName, @NonNull ControllerCallbackLink caller,
+ long id) {
+ try {
+ mISessionController.skipToQueueItem(packageName, caller, id);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that a controller requests pausing media.
+ *
+ * @param packageName the package name of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ */
+ void pause(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+ try {
+ mISessionController.pause(packageName, caller);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that a controller requests stopping media.
+ *
+ * @param packageName the package name of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ */
+ void stop(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+ try {
+ mISessionController.stop(packageName, caller);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that a controller requests skipping to the next queue item.
+ *
+ * @param packageName the package name of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ */
+ void next(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+ try {
+ mISessionController.next(packageName, caller);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that a controller requests skipping to the previous queue item.
+ *
+ * @param packageName the package name of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ */
+ void previous(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+ try {
+ mISessionController.previous(packageName, caller);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that a controller requests fast-forwarding.
+ *
+ * @param packageName the package name of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ */
+ void fastForward(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+ try {
+ mISessionController.fastForward(packageName, caller);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that a controller requests rewinding.
+ *
+ * @param packageName the package name of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ */
+ void rewind(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+ try {
+ mISessionController.rewind(packageName, caller);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that a controller requests seeking to the specific position.
+ *
+ * @param packageName the package name of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param pos the position to move to, in milliseconds
+ */
+ void seekTo(@NonNull String packageName, @NonNull ControllerCallbackLink caller,
+ long pos) {
+ try {
+ mISessionController.seekTo(packageName, caller, pos);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that a controller requests rating of the current media.
+ *
+ * @param packageName the package name of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param rating the rating of the current media
+ */
+ void rate(@NonNull String packageName, @NonNull ControllerCallbackLink caller,
+ @NonNull Rating rating) {
+ try {
+ mISessionController.rate(packageName, caller, rating);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that a controller sends a custom action.
+ *
+ * @param packageName the package name of the controller
+ * @param caller the {@link ControllerCallbackLink} of the controller
+ * @param action the name of the action
+ * @param args the arguments included with this action
+ */
+ void sendCustomAction(@NonNull String packageName,
+ @NonNull ControllerCallbackLink caller, @NonNull String action, @Nullable Bundle args) {
+ try {
+ mISessionController.sendCustomAction(packageName, caller, action, args);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Gets the current metadata of the connected session.
+ */
+ @Nullable
+ public MediaMetadata getMetadata() {
+ try {
+ return mISessionController.getMetadata();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Gets the current playback state of the connected session.
+ */
+ @Nullable
+ public PlaybackState getPlaybackState() {
+ try {
+ return mISessionController.getPlaybackState();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Gets the current queue of the connected session.
+ */
+ @Nullable
+ public List<MediaSession.QueueItem> getQueue() {
+ try {
+ MediaParceledListSlice queue = mISessionController.getQueue();
+ return queue == null ? null : queue.getList();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Gets the current queue title of the connected session.
+ */
+ @Nullable
+ public CharSequence getQueueTitle() {
+ try {
+ return mISessionController.getQueueTitle();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Gets the current extras of the connected session.
+ */
+ @Nullable
+ public Bundle getExtras() {
+ try {
+ return mISessionController.getExtras();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Gets the current rating type of the connected session.
+ */
+ public int getRatingType() {
+ try {
+ return mISessionController.getRatingType();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** Gets the binder */
+ @NonNull
+ public IBinder getBinder() {
+ return mISessionController.asBinder();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(mISessionController.asBinder());
+ }
+
+ /**
+ * Class for Stub implementation
+ */
+ public abstract static class ControllerStub {
+ /** Stub method for ISessionController.sendCommand */
+ public void sendCommand(@NonNull String packageName, @NonNull ControllerCallbackLink caller,
+ @NonNull String command, @Nullable Bundle args, @Nullable ResultReceiver cb) {
+ }
+
+ /** Stub method for ISessionController.sendMediaButton */
+ public boolean sendMediaButton(@NonNull String packageName,
+ @NonNull ControllerCallbackLink caller, boolean asSystemService,
+ @NonNull KeyEvent mediaButton) {
+ return false;
+ }
+
+ /** Stub method for ISessionController.registerCallback */
+ public void registerCallback(@NonNull String packageName,
+ @NonNull ControllerCallbackLink cb) {
+ }
+
+ /** Stub method for ISessionController.unregisterCallback */
+ public void unregisterCallback(@NonNull ControllerCallbackLink cb) {
+ }
+
+ /** Stub method for ISessionController.getPackageName */
+ @NonNull
+ public String getPackageName() {
+ return null;
+ }
+
+ /** Stub method for ISessionController.getTag */
+ @NonNull
+ public String getTag() {
+ return null;
+ }
+
+ /** Stub method for ISessionController.getLaunchPendingIntent */
+ @Nullable
+ public PendingIntent getLaunchPendingIntent() {
+ return null;
+ }
+
+ /** Stub method for ISessionController.getFlags */
+ public long getFlags() {
+ return 0;
+ }
+
+ /** Stub method for ISessionController.getVolumeAttributes */
+ @NonNull
+ public PlaybackInfo getVolumeAttributes() {
+ return null;
+ }
+
+ /** Stub method for ISessionController.adjustVolume */
+ public void adjustVolume(@NonNull String packageName, @NonNull String opPackageName,
+ @NonNull ControllerCallbackLink caller, boolean asSystemService, int direction,
+ int flags) {
+ }
+
+ /** Stub method for ISessionController.setVolumeTo */
+ public void setVolumeTo(@NonNull String packageName, @NonNull String opPackageName,
+ @NonNull ControllerCallbackLink caller, int value, int flags) {
+ }
+
+ /** Stub method for ISessionController.prepare */
+ public void prepare(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+ }
+
+ /** Stub method for ISessionController.prepareFromMediaId */
+ public void prepareFromMediaId(@NonNull String packageName,
+ @NonNull ControllerCallbackLink caller, @NonNull String mediaId,
+ @Nullable Bundle extras) {
+ }
+
+ /** Stub method for ISessionController.prepareFromSearch */
+ public void prepareFromSearch(@NonNull String packageName,
+ @NonNull ControllerCallbackLink caller, @NonNull String query,
+ @Nullable Bundle extras) {
+ }
+
+ /** Stub method for ISessionController.prepareFromUri */
+ public void prepareFromUri(@NonNull String packageName,
+ @NonNull ControllerCallbackLink caller, @NonNull Uri uri, @Nullable Bundle extras) {
+ }
+
+ /** Stub method for ISessionController.play */
+ public void play(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+ }
+
+ /** Stub method for ISessionController.playFromMediaId */
+ public void playFromMediaId(@NonNull String packageName,
+ @NonNull ControllerCallbackLink caller, @NonNull String mediaId,
+ @Nullable Bundle extras) {
+ }
+
+ /** Stub method for ISessionController.playFromSearch */
+ public void playFromSearch(@NonNull String packageName,
+ @NonNull ControllerCallbackLink caller, @NonNull String query,
+ @Nullable Bundle extras) {
+ }
+
+ /** Stub method for ISessionController.playFromUri */
+ public void playFromUri(@NonNull String packageName, @NonNull ControllerCallbackLink caller,
+ @NonNull Uri uri, @Nullable Bundle extras) {
+ }
+
+ /** Stub method for ISessionController.skipToQueueItem */
+ public void skipToQueueItem(@NonNull String packageName,
+ @NonNull ControllerCallbackLink caller, long id) {
+ }
+
+ /** Stub method for ISessionController.pause */
+ public void pause(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+ }
+
+ /** Stub method for ISessionController.stop */
+ public void stop(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+ }
+
+ /** Stub method for ISessionController.next */
+ public void next(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+ }
+
+ /** Stub method for ISessionController.previous */
+ public void previous(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+ }
+
+ /** Stub method for ISessionController.fastForward */
+ public void fastForward(@NonNull String packageName,
+ @NonNull ControllerCallbackLink caller) {
+ }
+
+ /** Stub method for ISessionController.rewind */
+ public void rewind(@NonNull String packageName, @NonNull ControllerCallbackLink caller) {
+ }
+
+ /** Stub method for ISessionController.seekTo */
+ public void seekTo(@NonNull String packageName, @NonNull ControllerCallbackLink caller,
+ long pos) {
+ }
+
+ /** Stub method for ISessionController.rate */
+ public void rate(@NonNull String packageName, @NonNull ControllerCallbackLink caller,
+ @NonNull Rating rating) {
+ }
+
+ /** Stub method for ISessionController.sendCustomAction */
+ public void sendCustomAction(@NonNull String packageName,
+ @NonNull ControllerCallbackLink caller, @NonNull String action,
+ @Nullable Bundle args) {
+ }
+
+ /** Stub method for ISessionController.getMetadata */
+ @Nullable
+ public MediaMetadata getMetadata() {
+ return null;
+ }
+
+ /** Stub method for ISessionController.getPlaybackState */
+ @Nullable
+ public PlaybackState getPlaybackState() {
+ return null;
+ }
+
+ /** Stub method for ISessionController.getQueue */
+ @Nullable
+ public List<MediaSession.QueueItem> getQueue() {
+ return null;
+ }
+
+ /** Stub method for ISessionController.getQueueTitle */
+ @Nullable
+ public CharSequence getQueueTitle() {
+ return null;
+ }
+
+ /** Stub method for ISessionController.getExtras */
+ @Nullable
+ public Bundle getExtras() {
+ return null;
+ }
+
+ /** Stub method for ISessionController.getRatingType */
+ public int getRatingType() {
+ return Rating.RATING_NONE;
+ }
+ }
+
+ private class StubProxy extends ISessionController.Stub {
+ @Override
+ public void sendCommand(String packageName, ControllerCallbackLink caller,
+ String command, Bundle args, ResultReceiver cb) {
+ mControllerStub.sendCommand(packageName, caller, command, args, cb);
+ }
+
+ @Override
+ public boolean sendMediaButton(String packageName, ControllerCallbackLink caller,
+ boolean asSystemService, KeyEvent mediaButton) {
+ return mControllerStub.sendMediaButton(packageName, caller, asSystemService,
+ mediaButton);
+ }
+
+ @Override
+ public void registerCallback(String packageName, ControllerCallbackLink cb) {
+ mControllerStub.registerCallback(packageName, cb);
+ }
+
+ @Override
+ public void unregisterCallback(ControllerCallbackLink cb) {
+ mControllerStub.unregisterCallback(cb);
+ }
+
+ @Override
+ public String getPackageName() {
+ return mControllerStub.getPackageName();
+ }
+
+ @Override
+ public String getTag() {
+ return mControllerStub.getTag();
+ }
+
+ @Override
+ public PendingIntent getLaunchPendingIntent() {
+ return mControllerStub.getLaunchPendingIntent();
+ }
+
+ @Override
+ public long getFlags() {
+ return mControllerStub.getFlags();
+ }
+
+ @Override
+ public PlaybackInfo getVolumeAttributes() {
+ return mControllerStub.getVolumeAttributes();
+ }
+
+ @Override
+ public void adjustVolume(String packageName, String opPackageName,
+ ControllerCallbackLink caller, boolean asSystemService, int direction,
+ int flags) {
+ mControllerStub.adjustVolume(packageName, opPackageName, caller, asSystemService,
+ direction, flags);
+ }
+
+ @Override
+ public void setVolumeTo(String packageName, String opPackageName,
+ ControllerCallbackLink caller, int value, int flags) {
+ mControllerStub.setVolumeTo(packageName, opPackageName, caller, value, flags);
+ }
+
+ @Override
+ public void prepare(String packageName, ControllerCallbackLink caller) {
+ mControllerStub.prepare(packageName, caller);
+ }
+
+ @Override
+ public void prepareFromMediaId(String packageName, ControllerCallbackLink caller,
+ String mediaId, Bundle extras) {
+ mControllerStub.prepareFromMediaId(packageName, caller, mediaId, extras);
+ }
+
+ @Override
+ public void prepareFromSearch(String packageName, ControllerCallbackLink caller,
+ String query, Bundle extras) {
+ mControllerStub.prepareFromSearch(packageName, caller, query, extras);
+ }
+
+ @Override
+ public void prepareFromUri(String packageName, ControllerCallbackLink caller,
+ Uri uri, Bundle extras) {
+ mControllerStub.prepareFromUri(packageName, caller, uri, extras);
+ }
+
+ @Override
+ public void play(String packageName, ControllerCallbackLink caller) {
+ mControllerStub.play(packageName, caller);
+ }
+
+ @Override
+ public void playFromMediaId(String packageName, ControllerCallbackLink caller,
+ String mediaId, Bundle extras) {
+ mControllerStub.playFromMediaId(packageName, caller, mediaId, extras);
+ }
+
+ @Override
+ public void playFromSearch(String packageName, ControllerCallbackLink caller,
+ String query, Bundle extras) {
+ mControllerStub.playFromSearch(packageName, caller, query, extras);
+ }
+
+ @Override
+ public void playFromUri(String packageName, ControllerCallbackLink caller,
+ Uri uri, Bundle extras) {
+ mControllerStub.playFromUri(packageName, caller, uri, extras);
+ }
+
+ @Override
+ public void skipToQueueItem(String packageName, ControllerCallbackLink caller, long id) {
+ mControllerStub.skipToQueueItem(packageName, caller, id);
+ }
+
+ @Override
+ public void pause(String packageName, ControllerCallbackLink caller) {
+ mControllerStub.pause(packageName, caller);
+ }
+
+ @Override
+ public void stop(String packageName, ControllerCallbackLink caller) {
+ mControllerStub.stop(packageName, caller);
+ }
+
+ @Override
+ public void next(String packageName, ControllerCallbackLink caller) {
+ mControllerStub.next(packageName, caller);
+ }
+
+ @Override
+ public void previous(String packageName, ControllerCallbackLink caller) {
+ mControllerStub.previous(packageName, caller);
+ }
+
+ @Override
+ public void fastForward(String packageName, ControllerCallbackLink caller) {
+ mControllerStub.fastForward(packageName, caller);
+ }
+
+ @Override
+ public void rewind(String packageName, ControllerCallbackLink caller) {
+ mControllerStub.rewind(packageName, caller);
+ }
+
+ @Override
+ public void seekTo(String packageName, ControllerCallbackLink caller, long pos) {
+ mControllerStub.seekTo(packageName, caller, pos);
+ }
+
+ @Override
+ public void rate(String packageName, ControllerCallbackLink caller, Rating rating) {
+ mControllerStub.rate(packageName, caller, rating);
+ }
+
+ @Override
+ public void sendCustomAction(String packageName, ControllerCallbackLink caller,
+ String action, Bundle args) {
+ mControllerStub.sendCustomAction(packageName, caller, action, args);
+ }
+
+ @Override
+ public MediaMetadata getMetadata() {
+ return mControllerStub.getMetadata();
+ }
+
+ @Override
+ public PlaybackState getPlaybackState() {
+ return mControllerStub.getPlaybackState();
+ }
+
+ @Override
+ public MediaParceledListSlice getQueue() {
+ List<MediaSession.QueueItem> queue = mControllerStub.getQueue();
+ return queue == null ? null : new MediaParceledListSlice(queue);
+ }
+
+ @Override
+ public CharSequence getQueueTitle() {
+ return mControllerStub.getQueueTitle();
+ }
+
+ @Override
+ public Bundle getExtras() {
+ return mControllerStub.getExtras();
+ }
+
+ @Override
+ public int getRatingType() {
+ return mControllerStub.getRatingType();
+ }
+ }
+}
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index 1524ad9..9b1ad7b 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -18,7 +18,8 @@
import android.app.PendingIntent;
import android.media.AudioAttributes;
import android.media.MediaMetadata;
-import android.media.session.ISessionController;
+import android.media.MediaParceledListSlice;
+import android.media.session.ControllerLink;
import android.media.session.PlaybackState;
import android.media.session.MediaSession;
import android.os.Bundle;
@@ -30,18 +31,17 @@
*/
interface ISession {
void sendEvent(String event, in Bundle data);
- ISessionController getController();
+ ControllerLink getController();
void setFlags(int flags);
void setActive(boolean active);
void setMediaButtonReceiver(in PendingIntent mbr);
void setLaunchPendingIntent(in PendingIntent pi);
- void destroy();
+ void destroySession();
// These commands are for the TransportPerformer
void setMetadata(in MediaMetadata metadata, long duration, String metadataDescription);
void setPlaybackState(in PlaybackState state);
- // TODO(b/122432476): Replace List with MediaParceledListSlice
- void setQueue(in List<MediaSession.QueueItem> queue);
+ void setQueue(in MediaParceledListSlice queue);
void setQueueTitle(CharSequence title);
void setExtras(in Bundle extras);
void setRatingType(int type);
diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl
index 2ba09fd..a3439a1 100644
--- a/media/java/android/media/session/ISessionController.aidl
+++ b/media/java/android/media/session/ISessionController.aidl
@@ -18,6 +18,7 @@
import android.app.PendingIntent;
import android.content.Intent;
import android.media.MediaMetadata;
+import android.media.MediaParceledListSlice;
import android.media.Rating;
import android.media.session.ControllerCallbackLink;
import android.media.session.MediaController;
@@ -39,9 +40,8 @@
String command, in Bundle args, in ResultReceiver cb);
boolean sendMediaButton(String packageName, in ControllerCallbackLink caller,
boolean asSystemService, in KeyEvent mediaButton);
- void registerCallbackListener(String packageName, in ControllerCallbackLink cb);
- void unregisterCallbackListener(in ControllerCallbackLink cb);
- boolean isTransportControlEnabled();
+ void registerCallback(String packageName, in ControllerCallbackLink cb);
+ void unregisterCallback(in ControllerCallbackLink cb);
String getPackageName();
String getTag();
PendingIntent getLaunchPendingIntent();
@@ -81,7 +81,7 @@
String action, in Bundle args);
MediaMetadata getMetadata();
PlaybackState getPlaybackState();
- List<MediaSession.QueueItem> getQueue();
+ MediaParceledListSlice getQueue();
CharSequence getQueueTitle();
Bundle getExtras();
int getRatingType();
diff --git a/media/java/android/media/session/ISessionControllerCallback.aidl b/media/java/android/media/session/ISessionControllerCallback.aidl
index 5c02e7c..56ae852 100644
--- a/media/java/android/media/session/ISessionControllerCallback.aidl
+++ b/media/java/android/media/session/ISessionControllerCallback.aidl
@@ -16,8 +16,8 @@
package android.media.session;
import android.media.MediaMetadata;
+import android.media.MediaParceledListSlice;
import android.media.session.MediaController;
-import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.os.Bundle;
@@ -31,7 +31,7 @@
// These callbacks are for the TransportController
void notifyPlaybackStateChanged(in PlaybackState state);
void notifyMetadataChanged(in MediaMetadata metadata);
- void notifyQueueChanged(in List<MediaSession.QueueItem> queue);
+ void notifyQueueChanged(in MediaParceledListSlice queue);
void notifyQueueTitleChanged(CharSequence title);
void notifyExtrasChanged(in Bundle extras);
void notifyVolumeInfoChanged(in MediaController.PlaybackInfo info);
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index 46516e0..ed16250 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -18,13 +18,14 @@
import android.content.ComponentName;
import android.media.IRemoteVolumeController;
import android.media.Session2Token;
+import android.media.session.ControllerLink;
import android.media.session.IActiveSessionsListener;
import android.media.session.ICallback;
import android.media.session.IOnMediaKeyListener;
import android.media.session.IOnVolumeKeyLongPressListener;
-import android.media.session.ISession;
import android.media.session.ISession2TokensListener;
import android.media.session.SessionCallbackLink;
+import android.media.session.SessionLink;
import android.os.Bundle;
import android.view.KeyEvent;
@@ -33,9 +34,10 @@
* @hide
*/
interface ISessionManager {
- ISession createSession(String packageName, in SessionCallbackLink cb, String tag, int userId);
+ SessionLink createSession(String packageName, in SessionCallbackLink sessionCb, String tag,
+ int userId);
void notifySession2Created(in Session2Token sessionToken);
- List<IBinder> getSessions(in ComponentName compName, int userId);
+ List<ControllerLink> getSessions(in ComponentName compName, int userId);
List<Session2Token> getSession2Tokens(int userId);
void dispatchMediaKeyEvent(String packageName, boolean asSystemService, in KeyEvent keyEvent,
boolean needWakeLock);
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index a1b8170..057c9cb 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -34,7 +34,6 @@
import android.os.Message;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.RemoteException;
import android.os.ResultReceiver;
import android.text.TextUtils;
import android.util.Log;
@@ -68,12 +67,11 @@
private static final int MSG_UPDATE_EXTRAS = 7;
private static final int MSG_DESTROYED = 8;
- private final ISessionController mSessionBinder;
+ private final ControllerLink mSessionBinder;
private final MediaSession.Token mToken;
private final Context mContext;
- private final ControllerCallbackLink mCbStub =
- new ControllerCallbackLink(new CallbackStub(this));
+ private final ControllerCallbackLink mCbStub;
private final ArrayList<MessageHandler> mCallbacks = new ArrayList<MessageHandler>();
private final Object mLock = new Object();
@@ -84,12 +82,11 @@
private final TransportControls mTransportControls;
/**
- * Call for creating a MediaController directly from a binder. Should only
+ * Call for creating a MediaController directly from a controller link. Should only
* be used by framework code.
- *
* @hide
*/
- public MediaController(Context context, ISessionController sessionBinder) {
+ public MediaController(Context context, ControllerLink sessionBinder) {
if (sessionBinder == null) {
throw new IllegalArgumentException("Session token cannot be null");
}
@@ -100,6 +97,17 @@
mTransportControls = new TransportControls();
mToken = new MediaSession.Token(sessionBinder);
mContext = context;
+ mCbStub = new ControllerCallbackLink(context, new CallbackStub(this));
+ }
+
+ /**
+ * Call for creating a MediaController directly from a binder. Should only
+ * be used by framework code.
+ * @hide
+ * TODO: remove this constructor
+ */
+ public MediaController(Context context, ISessionController sessionBinder) {
+ this(context, new ControllerLink(sessionBinder.asBinder()));
}
/**
@@ -109,7 +117,7 @@
* @param token The token for the session.
*/
public MediaController(@NonNull Context context, @NonNull MediaSession.Token token) {
- this(context, token.getBinder());
+ this(context, token.getControllerLink());
}
/**
@@ -158,7 +166,7 @@
try {
return mSessionBinder.sendMediaButton(mContext.getPackageName(), mCbStub,
asSystemService, keyEvent);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
// System is dead. =(
}
return false;
@@ -195,7 +203,7 @@
mSessionBinder.adjustVolume(mContext.getPackageName(),
mContext.getOpPackageName(), mCbStub, true, direction,
AudioManager.FLAG_SHOW_UI);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling adjustVolumeBy", e);
}
}
@@ -209,7 +217,7 @@
// AppOpsManager usages.
mSessionBinder.adjustVolume(mContext.getPackageName(),
mContext.getOpPackageName(), mCbStub, true, 0, flags);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling adjustVolumeBy", e);
}
}
@@ -224,7 +232,7 @@
public @Nullable PlaybackState getPlaybackState() {
try {
return mSessionBinder.getPlaybackState();
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling getPlaybackState.", e);
return null;
}
@@ -238,7 +246,7 @@
public @Nullable MediaMetadata getMetadata() {
try {
return mSessionBinder.getMetadata();
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling getMetadata.", e);
return null;
}
@@ -253,7 +261,7 @@
public @Nullable List<MediaSession.QueueItem> getQueue() {
try {
return mSessionBinder.getQueue();
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling getQueue.", e);
}
return null;
@@ -265,7 +273,7 @@
public @Nullable CharSequence getQueueTitle() {
try {
return mSessionBinder.getQueueTitle();
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling getQueueTitle", e);
}
return null;
@@ -277,7 +285,7 @@
public @Nullable Bundle getExtras() {
try {
return mSessionBinder.getExtras();
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling getExtras", e);
}
return null;
@@ -300,7 +308,7 @@
public int getRatingType() {
try {
return mSessionBinder.getRatingType();
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling getRatingType.", e);
return Rating.RATING_NONE;
}
@@ -314,7 +322,7 @@
public @MediaSession.SessionFlags long getFlags() {
try {
return mSessionBinder.getFlags();
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling getFlags.", e);
}
return 0;
@@ -328,7 +336,7 @@
public @Nullable PlaybackInfo getPlaybackInfo() {
try {
return mSessionBinder.getVolumeAttributes();
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling getAudioInfo.", e);
}
return null;
@@ -343,7 +351,7 @@
public @Nullable PendingIntent getSessionActivity() {
try {
return mSessionBinder.getLaunchPendingIntent();
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling getPendingIntent.", e);
}
return null;
@@ -376,7 +384,7 @@
// AppOpsManager usages.
mSessionBinder.setVolumeTo(mContext.getPackageName(), mContext.getOpPackageName(),
mCbStub, value, flags);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling setVolumeTo.", e);
}
}
@@ -401,7 +409,7 @@
// AppOpsManager usages.
mSessionBinder.adjustVolume(mContext.getPackageName(), mContext.getOpPackageName(),
mCbStub, false, direction, flags);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling adjustVolumeBy.", e);
}
}
@@ -467,7 +475,7 @@
}
try {
mSessionBinder.sendCommand(mContext.getPackageName(), mCbStub, command, args, cb);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.d(TAG, "Dead object in sendCommand.", e);
}
}
@@ -481,7 +489,7 @@
if (mPackageName == null) {
try {
mPackageName = mSessionBinder.getPackageName();
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.d(TAG, "Dead object in getPackageName.", e);
}
}
@@ -498,7 +506,7 @@
if (mTag == null) {
try {
mTag = mSessionBinder.getTag();
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.d(TAG, "Dead object in getTag.", e);
}
}
@@ -508,7 +516,7 @@
/*
* @hide
*/
- ISessionController getSessionBinder() {
+ ControllerLink getSessionBinder() {
return mSessionBinder;
}
@@ -518,7 +526,7 @@
@UnsupportedAppUsage
public boolean controlsSameSession(MediaController other) {
if (other == null) return false;
- return mSessionBinder.asBinder() == other.getSessionBinder().asBinder();
+ return mSessionBinder.getBinder() == other.getSessionBinder().getBinder();
}
private void addCallbackLocked(Callback cb, Handler handler) {
@@ -532,9 +540,9 @@
if (!mCbRegistered) {
try {
- mSessionBinder.registerCallbackListener(mContext.getPackageName(), mCbStub);
+ mSessionBinder.registerCallback(mContext.getPackageName(), mCbStub);
mCbRegistered = true;
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.e(TAG, "Dead object in registerCallback", e);
}
}
@@ -552,8 +560,8 @@
}
if (mCbRegistered && mCallbacks.size() == 0) {
try {
- mSessionBinder.unregisterCallbackListener(mCbStub);
- } catch (RemoteException e) {
+ mSessionBinder.unregisterCallback(mCbStub);
+ } catch (RuntimeException e) {
Log.e(TAG, "Dead object in removeCallbackLocked");
}
mCbRegistered = false;
@@ -680,7 +688,7 @@
public void prepare() {
try {
mSessionBinder.prepare(mContext.getPackageName(), mCbStub);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling prepare.", e);
}
}
@@ -705,7 +713,7 @@
try {
mSessionBinder.prepareFromMediaId(mContext.getPackageName(), mCbStub, mediaId,
extras);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling prepare(" + mediaId + ").", e);
}
}
@@ -732,7 +740,7 @@
try {
mSessionBinder.prepareFromSearch(mContext.getPackageName(), mCbStub, query,
extras);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling prepare(" + query + ").", e);
}
}
@@ -756,7 +764,7 @@
}
try {
mSessionBinder.prepareFromUri(mContext.getPackageName(), mCbStub, uri, extras);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling prepare(" + uri + ").", e);
}
}
@@ -767,7 +775,7 @@
public void play() {
try {
mSessionBinder.play(mContext.getPackageName(), mCbStub);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling play.", e);
}
}
@@ -787,7 +795,7 @@
try {
mSessionBinder.playFromMediaId(mContext.getPackageName(), mCbStub, mediaId,
extras);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling play(" + mediaId + ").", e);
}
}
@@ -809,7 +817,7 @@
}
try {
mSessionBinder.playFromSearch(mContext.getPackageName(), mCbStub, query, extras);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling play(" + query + ").", e);
}
}
@@ -828,7 +836,7 @@
}
try {
mSessionBinder.playFromUri(mContext.getPackageName(), mCbStub, uri, extras);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling play(" + uri + ").", e);
}
}
@@ -840,7 +848,7 @@
public void skipToQueueItem(long id) {
try {
mSessionBinder.skipToQueueItem(mContext.getPackageName(), mCbStub, id);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling skipToItem(" + id + ").", e);
}
}
@@ -852,7 +860,7 @@
public void pause() {
try {
mSessionBinder.pause(mContext.getPackageName(), mCbStub);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling pause.", e);
}
}
@@ -864,7 +872,7 @@
public void stop() {
try {
mSessionBinder.stop(mContext.getPackageName(), mCbStub);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling stop.", e);
}
}
@@ -877,7 +885,7 @@
public void seekTo(long pos) {
try {
mSessionBinder.seekTo(mContext.getPackageName(), mCbStub, pos);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling seekTo.", e);
}
}
@@ -889,7 +897,7 @@
public void fastForward() {
try {
mSessionBinder.fastForward(mContext.getPackageName(), mCbStub);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling fastForward.", e);
}
}
@@ -900,7 +908,7 @@
public void skipToNext() {
try {
mSessionBinder.next(mContext.getPackageName(), mCbStub);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling next.", e);
}
}
@@ -912,7 +920,7 @@
public void rewind() {
try {
mSessionBinder.rewind(mContext.getPackageName(), mCbStub);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling rewind.", e);
}
}
@@ -923,7 +931,7 @@
public void skipToPrevious() {
try {
mSessionBinder.previous(mContext.getPackageName(), mCbStub);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling previous.", e);
}
}
@@ -938,7 +946,7 @@
public void setRating(Rating rating) {
try {
mSessionBinder.rate(mContext.getPackageName(), mCbStub, rating);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.wtf(TAG, "Error calling rate.", e);
}
}
@@ -973,7 +981,7 @@
}
try {
mSessionBinder.sendCustomAction(mContext.getPackageName(), mCbStub, action, args);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.d(TAG, "Dead object in sendCustomAction.", e);
}
}
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index e07cf15..1a185e9 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
import android.app.PendingIntent;
@@ -33,25 +34,15 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.RemoteException;
import android.os.ResultReceiver;
-import android.os.UserHandle;
import android.service.media.MediaBrowserService;
import android.text.TextUtils;
-import android.util.Log;
-import android.util.Pair;
-import android.view.KeyEvent;
-import android.view.ViewConfiguration;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
import java.util.List;
-import java.util.Objects;
/**
* Allows interaction with media controllers, volume keys, media buttons, and
@@ -76,7 +67,7 @@
* MediaSession objects are thread safe.
*/
public final class MediaSession {
- private static final String TAG = "MediaSession";
+ static final String TAG = "MediaSession";
/**
* Set this flag on the session to indicate that it can handle media button
@@ -123,21 +114,11 @@
FLAG_EXCLUSIVE_GLOBAL_PRIORITY })
public @interface SessionFlags { }
- private final Object mLock = new Object();
- private final int mMaxBitmapSize;
-
- private final MediaSession.Token mSessionToken;
- private final MediaController mController;
- private final ISession mBinder;
- private final SessionCallbackLink mCbStub;
+ private final MediaSessionEngine mImpl;
// Do not change the name of mCallback. Support lib accesses this by using reflection.
@UnsupportedAppUsage
- private CallbackMessageHandler mCallback;
- private VolumeProvider mVolumeProvider;
- private PlaybackState mPlaybackState;
-
- private boolean mActive = false;
+ private Object mCallback;
/**
* Creates a new session. The session will automatically be registered with
@@ -149,37 +130,22 @@
* @param tag A short name for debugging purposes.
*/
public MediaSession(@NonNull Context context, @NonNull String tag) {
- this(context, tag, UserHandle.myUserId());
- }
-
- /**
- * Creates a new session as the specified user. To create a session as a
- * user other than your own you must hold the
- * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}
- * permission.
- *
- * @param context The context to use to create the session.
- * @param tag A short name for debugging purposes.
- * @param userId The user id to create the session as.
- * @hide
- */
- public MediaSession(@NonNull Context context, @NonNull String tag, int userId) {
if (context == null) {
throw new IllegalArgumentException("context cannot be null.");
}
if (TextUtils.isEmpty(tag)) {
throw new IllegalArgumentException("tag cannot be null or empty");
}
- mMaxBitmapSize = context.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize);
- mCbStub = new SessionCallbackLink(new CallbackStub(this));
MediaSessionManager manager = (MediaSessionManager) context
.getSystemService(Context.MEDIA_SESSION_SERVICE);
try {
- mBinder = manager.createSession(mCbStub, tag, userId);
- mSessionToken = new Token(mBinder.getController());
- mController = new MediaController(context, mSessionToken);
- } catch (RemoteException e) {
+ MediaSessionEngine.CallbackStub cbStub = new MediaSessionEngine.CallbackStub();
+ SessionCallbackLink cbLink = new SessionCallbackLink(context, cbStub);
+ SessionLink sessionLink = manager.createSession(cbLink, tag);
+ mImpl = new MediaSessionEngine(context, sessionLink, cbLink, cbStub,
+ context.getResources().getDimensionPixelSize(
+ android.R.dimen.config_mediaMetadataBitmapMaxSize));
+ } catch (RuntimeException e) {
throw new RuntimeException("Remote error creating session.", e);
}
}
@@ -194,7 +160,8 @@
* @param callback The callback object
*/
public void setCallback(@Nullable Callback callback) {
- setCallback(callback, null);
+ mCallback = callback == null ? null : new Object();
+ mImpl.setCallback(callback);
}
/**
@@ -207,24 +174,8 @@
* @param handler The handler that events should be posted on.
*/
public void setCallback(@Nullable Callback callback, @Nullable Handler handler) {
- synchronized (mLock) {
- if (mCallback != null) {
- // We're updating the callback, clear the session from the old one.
- mCallback.mCallback.mSession = null;
- mCallback.removeCallbacksAndMessages(null);
- }
- if (callback == null) {
- mCallback = null;
- return;
- }
- if (handler == null) {
- handler = new Handler();
- }
- callback.mSession = this;
- CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(),
- callback);
- mCallback = msgHandler;
- }
+ mCallback = callback == null ? null : new Object();
+ mImpl.setCallback(callback, handler);
}
/**
@@ -235,11 +186,7 @@
* @param pi The intent to launch to show UI for this Session.
*/
public void setSessionActivity(@Nullable PendingIntent pi) {
- try {
- mBinder.setLaunchPendingIntent(pi);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Failure in setLaunchPendingIntent.", e);
- }
+ mImpl.setSessionActivity(pi);
}
/**
@@ -251,11 +198,7 @@
* @param mbr The {@link PendingIntent} to send the media button event to.
*/
public void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
- try {
- mBinder.setMediaButtonReceiver(mbr);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e);
- }
+ mImpl.setMediaButtonReceiver(mbr);
}
/**
@@ -264,11 +207,7 @@
* @param flags The flags to set for this session.
*/
public void setFlags(@SessionFlags int flags) {
- try {
- mBinder.setFlags(flags);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Failure in setFlags.", e);
- }
+ mImpl.setFlags(flags);
}
/**
@@ -283,14 +222,7 @@
* @param attributes The {@link AudioAttributes} for this session's audio.
*/
public void setPlaybackToLocal(AudioAttributes attributes) {
- if (attributes == null) {
- throw new IllegalArgumentException("Attributes cannot be null for local playback.");
- }
- try {
- mBinder.setPlaybackToLocal(attributes);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Failure in setPlaybackToLocal.", e);
- }
+ mImpl.setPlaybackToLocal(attributes);
}
/**
@@ -305,26 +237,7 @@
* not be null.
*/
public void setPlaybackToRemote(@NonNull VolumeProvider volumeProvider) {
- if (volumeProvider == null) {
- throw new IllegalArgumentException("volumeProvider may not be null!");
- }
- synchronized (mLock) {
- mVolumeProvider = volumeProvider;
- }
- volumeProvider.setCallback(new VolumeProvider.Callback() {
- @Override
- public void onVolumeChanged(VolumeProvider volumeProvider) {
- notifyRemoteVolumeChanged(volumeProvider);
- }
- });
-
- try {
- mBinder.setPlaybackToRemote(volumeProvider.getVolumeControl(),
- volumeProvider.getMaxVolume());
- mBinder.setCurrentVolume(volumeProvider.getCurrentVolume());
- } catch (RemoteException e) {
- Log.wtf(TAG, "Failure in setPlaybackToRemote.", e);
- }
+ mImpl.setPlaybackToRemote(volumeProvider);
}
/**
@@ -336,15 +249,7 @@
* @param active Whether this session is active or not.
*/
public void setActive(boolean active) {
- if (mActive == active) {
- return;
- }
- try {
- mBinder.setActive(active);
- mActive = active;
- } catch (RemoteException e) {
- Log.wtf(TAG, "Failure in setActive.", e);
- }
+ mImpl.setActive(active);
}
/**
@@ -353,7 +258,7 @@
* @return True if the session is active, false otherwise.
*/
public boolean isActive() {
- return mActive;
+ return mImpl.isActive();
}
/**
@@ -365,14 +270,7 @@
* @param extras Any extras included with the event
*/
public void sendSessionEvent(@NonNull String event, @Nullable Bundle extras) {
- if (TextUtils.isEmpty(event)) {
- throw new IllegalArgumentException("event cannot be null or empty");
- }
- try {
- mBinder.sendEvent(event, extras);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error sending event", e);
- }
+ mImpl.sendSessionEvent(event, extras);
}
/**
@@ -381,11 +279,7 @@
* but it must be released if your activity or service is being destroyed.
*/
public void release() {
- try {
- mBinder.destroy();
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error releasing session: ", e);
- }
+ mImpl.close();
}
/**
@@ -397,7 +291,7 @@
* session
*/
public @NonNull Token getSessionToken() {
- return mSessionToken;
+ return mImpl.getSessionToken();
}
/**
@@ -407,7 +301,7 @@
* @return A controller for this session.
*/
public @NonNull MediaController getController() {
- return mController;
+ return mImpl.getController();
}
/**
@@ -416,12 +310,7 @@
* @param state The current state of playback
*/
public void setPlaybackState(@Nullable PlaybackState state) {
- mPlaybackState = state;
- try {
- mBinder.setPlaybackState(state);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Dead object in setPlaybackState.", e);
- }
+ mImpl.setPlaybackState(state);
}
/**
@@ -433,24 +322,7 @@
* @see android.media.MediaMetadata.Builder#putBitmap
*/
public void setMetadata(@Nullable MediaMetadata metadata) {
- long duration = -1;
- int fields = 0;
- MediaDescription description = null;
- if (metadata != null) {
- metadata = (new MediaMetadata.Builder(metadata, mMaxBitmapSize)).build();
- if (metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
- duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
- }
- fields = metadata.size();
- description = metadata.getDescription();
- }
- String metadataDescription = "size=" + fields + ", description=" + description;
-
- try {
- mBinder.setMetadata(metadata, duration, metadataDescription);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Dead object in setPlaybackState.", e);
- }
+ mImpl.setMetadata(metadata);
}
/**
@@ -465,11 +337,7 @@
* @param queue A list of items in the play queue.
*/
public void setQueue(@Nullable List<QueueItem> queue) {
- try {
- mBinder.setQueue(queue);
- } catch (RemoteException e) {
- Log.wtf("Dead object in setQueue.", e);
- }
+ mImpl.setQueue(queue);
}
/**
@@ -480,11 +348,7 @@
* @param title The title of the play queue.
*/
public void setQueueTitle(@Nullable CharSequence title) {
- try {
- mBinder.setQueueTitle(title);
- } catch (RemoteException e) {
- Log.wtf("Dead object in setQueueTitle.", e);
- }
+ mImpl.setQueueTitle(title);
}
/**
@@ -501,11 +365,7 @@
* </ul>
*/
public void setRatingType(@Rating.Style int type) {
- try {
- mBinder.setRatingType(type);
- } catch (RemoteException e) {
- Log.e(TAG, "Error in setRatingType.", e);
- }
+ mImpl.setRatingType(type);
}
/**
@@ -516,11 +376,7 @@
* @param extras The extras associated with the {@link MediaSession}.
*/
public void setExtras(@Nullable Bundle extras) {
- try {
- mBinder.setExtras(extras);
- } catch (RemoteException e) {
- Log.wtf("Dead object in setExtras.", e);
- }
+ mImpl.setExtras(extras);
}
/**
@@ -532,11 +388,7 @@
* @see MediaSessionManager#isTrustedForMediaControl(RemoteUserInfo)
*/
public final @NonNull RemoteUserInfo getCurrentControllerInfo() {
- if (mCallback == null || mCallback.mCurrentControllerInfo == null) {
- throw new IllegalStateException(
- "This should be called inside of MediaSession.Callback methods");
- }
- return mCallback.mCurrentControllerInfo;
+ return mImpl.getCurrentControllerInfo();
}
/**
@@ -546,17 +398,7 @@
* @hide
*/
public void notifyRemoteVolumeChanged(VolumeProvider provider) {
- synchronized (mLock) {
- if (provider == null || provider != mVolumeProvider) {
- Log.w(TAG, "Received update from stale volume provider");
- return;
- }
- }
- try {
- mBinder.setCurrentVolume(provider.getCurrentVolume());
- } catch (RemoteException e) {
- Log.e(TAG, "Error in notifyVolumeChanged", e);
- }
+ mImpl.notifyRemoteVolumeChanged(provider);
}
/**
@@ -568,119 +410,7 @@
*/
@UnsupportedAppUsage
public String getCallingPackage() {
- if (mCallback != null && mCallback.mCurrentControllerInfo != null) {
- return mCallback.mCurrentControllerInfo.getPackageName();
- }
- return null;
- }
-
- private void dispatchPrepare(RemoteUserInfo caller) {
- postToCallback(caller, CallbackMessageHandler.MSG_PREPARE, null, null);
- }
-
- private void dispatchPrepareFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) {
- postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras);
- }
-
- private void dispatchPrepareFromSearch(RemoteUserInfo caller, String query, Bundle extras) {
- postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_SEARCH, query, extras);
- }
-
- private void dispatchPrepareFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) {
- postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_URI, uri, extras);
- }
-
- private void dispatchPlay(RemoteUserInfo caller) {
- postToCallback(caller, CallbackMessageHandler.MSG_PLAY, null, null);
- }
-
- private void dispatchPlayFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) {
- postToCallback(caller, CallbackMessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras);
- }
-
- private void dispatchPlayFromSearch(RemoteUserInfo caller, String query, Bundle extras) {
- postToCallback(caller, CallbackMessageHandler.MSG_PLAY_SEARCH, query, extras);
- }
-
- private void dispatchPlayFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) {
- postToCallback(caller, CallbackMessageHandler.MSG_PLAY_URI, uri, extras);
- }
-
- private void dispatchSkipToItem(RemoteUserInfo caller, long id) {
- postToCallback(caller, CallbackMessageHandler.MSG_SKIP_TO_ITEM, id, null);
- }
-
- private void dispatchPause(RemoteUserInfo caller) {
- postToCallback(caller, CallbackMessageHandler.MSG_PAUSE, null, null);
- }
-
- private void dispatchStop(RemoteUserInfo caller) {
- postToCallback(caller, CallbackMessageHandler.MSG_STOP, null, null);
- }
-
- private void dispatchNext(RemoteUserInfo caller) {
- postToCallback(caller, CallbackMessageHandler.MSG_NEXT, null, null);
- }
-
- private void dispatchPrevious(RemoteUserInfo caller) {
- postToCallback(caller, CallbackMessageHandler.MSG_PREVIOUS, null, null);
- }
-
- private void dispatchFastForward(RemoteUserInfo caller) {
- postToCallback(caller, CallbackMessageHandler.MSG_FAST_FORWARD, null, null);
- }
-
- private void dispatchRewind(RemoteUserInfo caller) {
- postToCallback(caller, CallbackMessageHandler.MSG_REWIND, null, null);
- }
-
- private void dispatchSeekTo(RemoteUserInfo caller, long pos) {
- postToCallback(caller, CallbackMessageHandler.MSG_SEEK_TO, pos, null);
- }
-
- private void dispatchRate(RemoteUserInfo caller, Rating rating) {
- postToCallback(caller, CallbackMessageHandler.MSG_RATE, rating, null);
- }
-
- private void dispatchCustomAction(RemoteUserInfo caller, String action, Bundle args) {
- postToCallback(caller, CallbackMessageHandler.MSG_CUSTOM_ACTION, action, args);
- }
-
- private void dispatchMediaButton(RemoteUserInfo caller, Intent mediaButtonIntent) {
- postToCallback(caller, CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent, null);
- }
-
- private void dispatchMediaButtonDelayed(RemoteUserInfo info, Intent mediaButtonIntent,
- long delay) {
- postToCallbackDelayed(info, CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT,
- mediaButtonIntent, null, delay);
- }
-
- private void dispatchAdjustVolume(RemoteUserInfo caller, int direction) {
- postToCallback(caller, CallbackMessageHandler.MSG_ADJUST_VOLUME, direction, null);
- }
-
- private void dispatchSetVolumeTo(RemoteUserInfo caller, int volume) {
- postToCallback(caller, CallbackMessageHandler.MSG_SET_VOLUME, volume, null);
- }
-
- private void dispatchCommand(RemoteUserInfo caller, String command, Bundle args,
- ResultReceiver resultCb) {
- Command cmd = new Command(command, args, resultCb);
- postToCallback(caller, CallbackMessageHandler.MSG_COMMAND, cmd, null);
- }
-
- private void postToCallback(RemoteUserInfo caller, int what, Object obj, Bundle data) {
- postToCallbackDelayed(caller, what, obj, data, 0);
- }
-
- private void postToCallbackDelayed(RemoteUserInfo caller, int what, Object obj, Bundle data,
- long delay) {
- synchronized (mLock) {
- if (mCallback != null) {
- mCallback.post(caller, what, obj, data, delay);
- }
- }
+ return mImpl.getCallingPackage();
}
/**
@@ -689,17 +419,7 @@
* @hide
*/
public static boolean isActiveState(int state) {
- switch (state) {
- case PlaybackState.STATE_FAST_FORWARDING:
- case PlaybackState.STATE_REWINDING:
- case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
- case PlaybackState.STATE_SKIPPING_TO_NEXT:
- case PlaybackState.STATE_BUFFERING:
- case PlaybackState.STATE_CONNECTING:
- case PlaybackState.STATE_PLAYING:
- return true;
- }
- return false;
+ return MediaSessionEngine.isActiveState(state);
}
/**
@@ -709,13 +429,13 @@
*/
public static final class Token implements Parcelable {
- private ISessionController mBinder;
+ private ControllerLink mControllerLink;
/**
* @hide
*/
- public Token(ISessionController binder) {
- mBinder = binder;
+ public Token(ControllerLink controllerLink) {
+ mControllerLink = controllerLink;
}
@Override
@@ -725,14 +445,15 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeStrongBinder(mBinder.asBinder());
+ dest.writeParcelable(mControllerLink, flags);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
- result = prime * result + ((mBinder == null) ? 0 : mBinder.asBinder().hashCode());
+ result = prime * result + ((mControllerLink == null)
+ ? 0 : mControllerLink.getBinder().hashCode());
return result;
}
@@ -745,23 +466,31 @@
if (getClass() != obj.getClass())
return false;
Token other = (Token) obj;
- if (mBinder == null) {
- if (other.mBinder != null)
+ if (mControllerLink == null) {
+ if (other.mControllerLink != null) {
return false;
- } else if (!mBinder.asBinder().equals(other.mBinder.asBinder()))
+ }
+ } else if (!mControllerLink.getBinder().equals(other.mControllerLink.getBinder())) {
return false;
+ }
return true;
}
- ISessionController getBinder() {
- return mBinder;
+ /**
+ * Gets the controller link in this token.
+ * @hide
+ */
+ @SystemApi
+ public ControllerLink getControllerLink() {
+ return mControllerLink;
}
- public static final Parcelable.Creator<Token> CREATOR
- = new Parcelable.Creator<Token>() {
+ public static final Parcelable.Creator<Token> CREATOR =
+ new Parcelable.Creator<Token>() {
@Override
public Token createFromParcel(Parcel in) {
- return new Token(ISessionController.Stub.asInterface(in.readStrongBinder()));
+ ControllerLink link = in.readParcelable(null);
+ return new Token(link);
}
@Override
@@ -777,9 +506,7 @@
*/
public abstract static class Callback {
- private MediaSession mSession;
- private CallbackMessageHandler mHandler;
- private boolean mMediaPlayPauseKeyPending;
+ MediaSessionEngine.MediaButtonEventDelegate mMediaButtonEventDelegate;
public Callback() {
}
@@ -811,110 +538,12 @@
* @return True if the event was handled, false otherwise.
*/
public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
- if (mSession != null && mHandler != null
- && Intent.ACTION_MEDIA_BUTTON.equals(mediaButtonIntent.getAction())) {
- KeyEvent ke = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
- if (ke != null && ke.getAction() == KeyEvent.ACTION_DOWN) {
- PlaybackState state = mSession.mPlaybackState;
- long validActions = state == null ? 0 : state.getActions();
- switch (ke.getKeyCode()) {
- case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
- case KeyEvent.KEYCODE_HEADSETHOOK:
- if (ke.getRepeatCount() > 0) {
- // Consider long-press as a single tap.
- handleMediaPlayPauseKeySingleTapIfPending();
- } else if (mMediaPlayPauseKeyPending) {
- // Consider double tap as the next.
- mHandler.removeMessages(CallbackMessageHandler
- .MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
- mMediaPlayPauseKeyPending = false;
- if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
- onSkipToNext();
- }
- } else {
- mMediaPlayPauseKeyPending = true;
- mSession.dispatchMediaButtonDelayed(
- mSession.getCurrentControllerInfo(),
- mediaButtonIntent, ViewConfiguration.getDoubleTapTimeout());
- }
- return true;
- default:
- // If another key is pressed within double tap timeout, consider the
- // pending play/pause as a single tap to handle media keys in order.
- handleMediaPlayPauseKeySingleTapIfPending();
- break;
- }
-
- switch (ke.getKeyCode()) {
- case KeyEvent.KEYCODE_MEDIA_PLAY:
- if ((validActions & PlaybackState.ACTION_PLAY) != 0) {
- onPlay();
- return true;
- }
- break;
- case KeyEvent.KEYCODE_MEDIA_PAUSE:
- if ((validActions & PlaybackState.ACTION_PAUSE) != 0) {
- onPause();
- return true;
- }
- break;
- case KeyEvent.KEYCODE_MEDIA_NEXT:
- if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
- onSkipToNext();
- return true;
- }
- break;
- case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
- if ((validActions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) {
- onSkipToPrevious();
- return true;
- }
- break;
- case KeyEvent.KEYCODE_MEDIA_STOP:
- if ((validActions & PlaybackState.ACTION_STOP) != 0) {
- onStop();
- return true;
- }
- break;
- case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
- if ((validActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
- onFastForward();
- return true;
- }
- break;
- case KeyEvent.KEYCODE_MEDIA_REWIND:
- if ((validActions & PlaybackState.ACTION_REWIND) != 0) {
- onRewind();
- return true;
- }
- break;
- }
- }
+ if (mMediaButtonEventDelegate != null) {
+ return mMediaButtonEventDelegate.onMediaButtonIntent(mediaButtonIntent);
}
return false;
}
- private void handleMediaPlayPauseKeySingleTapIfPending() {
- if (!mMediaPlayPauseKeyPending) {
- return;
- }
- mMediaPlayPauseKeyPending = false;
- mHandler.removeMessages(CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
- PlaybackState state = mSession.mPlaybackState;
- long validActions = state == null ? 0 : state.getActions();
- boolean isPlaying = state != null
- && state.getState() == PlaybackState.STATE_PLAYING;
- boolean canPlay = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
- | PlaybackState.ACTION_PLAY)) != 0;
- boolean canPause = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
- | PlaybackState.ACTION_PAUSE)) != 0;
- if (isPlaying && canPause) {
- onPause();
- } else if (!isPlaying && canPlay) {
- onPlay();
- }
- }
-
/**
* Override to handle requests to prepare playback. During the preparation, a session should
* not hold audio focus in order to allow other sessions play seamlessly. The state of
@@ -1057,251 +686,14 @@
*/
public void onCustomAction(@NonNull String action, @Nullable Bundle extras) {
}
- }
- /**
- * @hide
- */
- public static final class CallbackStub extends SessionCallbackLink.CallbackStub {
- private WeakReference<MediaSession> mMediaSession;
-
- public CallbackStub(MediaSession session) {
- mMediaSession = new WeakReference<>(session);
- }
-
- private static RemoteUserInfo createRemoteUserInfo(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- return new RemoteUserInfo(packageName, pid, uid,
- caller != null ? caller.getBinder() : null);
- }
-
- @Override
- public void onCommand(String packageName, int pid, int uid,
- ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchCommand(createRemoteUserInfo(packageName, pid, uid, caller),
- command, args, cb);
- }
- }
-
- @Override
- public void onMediaButton(String packageName, int pid, int uid, Intent mediaButtonIntent,
- int sequenceNumber, ResultReceiver cb) {
- MediaSession session = mMediaSession.get();
- try {
- if (session != null) {
- session.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid, null),
- mediaButtonIntent);
- }
- } finally {
- if (cb != null) {
- cb.send(sequenceNumber, null);
- }
- }
- }
-
- @Override
- public void onMediaButtonFromController(String packageName, int pid, int uid,
- ControllerCallbackLink caller, Intent mediaButtonIntent) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid, caller),
- mediaButtonIntent);
- }
- }
-
- @Override
- public void onPrepare(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPrepare(createRemoteUserInfo(packageName, pid, uid, caller));
- }
- }
-
- @Override
- public void onPrepareFromMediaId(String packageName, int pid, int uid,
- ControllerCallbackLink caller, String mediaId,
- Bundle extras) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPrepareFromMediaId(
- createRemoteUserInfo(packageName, pid, uid, caller), mediaId, extras);
- }
- }
-
- @Override
- public void onPrepareFromSearch(String packageName, int pid, int uid,
- ControllerCallbackLink caller, String query,
- Bundle extras) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPrepareFromSearch(
- createRemoteUserInfo(packageName, pid, uid, caller), query, extras);
- }
- }
-
- @Override
- public void onPrepareFromUri(String packageName, int pid, int uid,
- ControllerCallbackLink caller, Uri uri, Bundle extras) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPrepareFromUri(createRemoteUserInfo(packageName, pid, uid, caller),
- uri, extras);
- }
- }
-
- @Override
- public void onPlay(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPlay(createRemoteUserInfo(packageName, pid, uid, caller));
- }
- }
-
- @Override
- public void onPlayFromMediaId(String packageName, int pid, int uid,
- ControllerCallbackLink caller, String mediaId,
- Bundle extras) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPlayFromMediaId(createRemoteUserInfo(packageName, pid, uid, caller),
- mediaId, extras);
- }
- }
-
- @Override
- public void onPlayFromSearch(String packageName, int pid, int uid,
- ControllerCallbackLink caller, String query,
- Bundle extras) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPlayFromSearch(createRemoteUserInfo(packageName, pid, uid, caller),
- query, extras);
- }
- }
-
- @Override
- public void onPlayFromUri(String packageName, int pid, int uid,
- ControllerCallbackLink caller, Uri uri, Bundle extras) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPlayFromUri(createRemoteUserInfo(packageName, pid, uid, caller),
- uri, extras);
- }
- }
-
- @Override
- public void onSkipToTrack(String packageName, int pid, int uid,
- ControllerCallbackLink caller, long id) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchSkipToItem(createRemoteUserInfo(packageName, pid, uid, caller), id);
- }
- }
-
- @Override
- public void onPause(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPause(createRemoteUserInfo(packageName, pid, uid, caller));
- }
- }
-
- @Override
- public void onStop(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchStop(createRemoteUserInfo(packageName, pid, uid, caller));
- }
- }
-
- @Override
- public void onNext(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchNext(createRemoteUserInfo(packageName, pid, uid, caller));
- }
- }
-
- @Override
- public void onPrevious(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPrevious(createRemoteUserInfo(packageName, pid, uid, caller));
- }
- }
-
- @Override
- public void onFastForward(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchFastForward(createRemoteUserInfo(packageName, pid, uid, caller));
- }
- }
-
- @Override
- public void onRewind(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchRewind(createRemoteUserInfo(packageName, pid, uid, caller));
- }
- }
-
- @Override
- public void onSeekTo(String packageName, int pid, int uid,
- ControllerCallbackLink caller, long pos) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchSeekTo(createRemoteUserInfo(packageName, pid, uid, caller), pos);
- }
- }
-
- @Override
- public void onRate(String packageName, int pid, int uid, ControllerCallbackLink caller,
- Rating rating) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchRate(createRemoteUserInfo(packageName, pid, uid, caller), rating);
- }
- }
-
- @Override
- public void onCustomAction(String packageName, int pid, int uid,
- ControllerCallbackLink caller, String action, Bundle args) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchCustomAction(createRemoteUserInfo(packageName, pid, uid, caller),
- action, args);
- }
- }
-
- @Override
- public void onAdjustVolume(String packageName, int pid, int uid,
- ControllerCallbackLink caller, int direction) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchAdjustVolume(createRemoteUserInfo(packageName, pid, uid, caller),
- direction);
- }
- }
-
- @Override
- public void onSetVolumeTo(String packageName, int pid, int uid,
- ControllerCallbackLink caller, int value) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchSetVolumeTo(createRemoteUserInfo(packageName, pid, uid, caller),
- value);
- }
+ /**
+ * @hide
+ */
+ @SystemApi
+ public void onSetMediaButtonEventDelegate(
+ @NonNull MediaSessionEngine.MediaButtonEventDelegate delegate) {
+ mMediaButtonEventDelegate = delegate;
}
}
@@ -1315,7 +707,7 @@
*/
public static final int UNKNOWN_ID = -1;
- private final MediaDescription mDescription;
+ private final MediaSessionEngine.QueueItem mImpl;
@UnsupportedAppUsage
private final long mId;
@@ -1327,39 +719,32 @@
* play queue and cannot be {@link #UNKNOWN_ID}.
*/
public QueueItem(MediaDescription description, long id) {
- if (description == null) {
- throw new IllegalArgumentException("Description cannot be null.");
- }
- if (id == UNKNOWN_ID) {
- throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID");
- }
- mDescription = description;
+ mImpl = new MediaSessionEngine.QueueItem(description, id);
mId = id;
}
private QueueItem(Parcel in) {
- mDescription = MediaDescription.CREATOR.createFromParcel(in);
- mId = in.readLong();
+ mImpl = new MediaSessionEngine.QueueItem(in);
+ mId = mImpl.getQueueId();
}
/**
* Get the description for this item.
*/
public MediaDescription getDescription() {
- return mDescription;
+ return mImpl.getDescription();
}
/**
* Get the queue id for this item.
*/
public long getQueueId() {
- return mId;
+ return mImpl.getQueueId();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
- mDescription.writeToParcel(dest, flags);
- dest.writeLong(mId);
+ mImpl.writeToParcel(dest, flags);
}
@Override
@@ -1383,9 +768,7 @@
@Override
public String toString() {
- return "MediaSession.QueueItem {" +
- "Description=" + mDescription +
- ", Id=" + mId + " }";
+ return mImpl.toString();
}
@Override
@@ -1398,167 +781,7 @@
return false;
}
- final QueueItem item = (QueueItem) o;
- if (mId != item.mId) {
- return false;
- }
-
- if (!Objects.equals(mDescription, item.mDescription)) {
- return false;
- }
-
- return true;
- }
- }
-
- private static final class Command {
- public final String command;
- public final Bundle extras;
- public final ResultReceiver stub;
-
- public Command(String command, Bundle extras, ResultReceiver stub) {
- this.command = command;
- this.extras = extras;
- this.stub = stub;
- }
- }
-
- private class CallbackMessageHandler extends Handler {
- private static final int MSG_COMMAND = 1;
- private static final int MSG_MEDIA_BUTTON = 2;
- private static final int MSG_PREPARE = 3;
- private static final int MSG_PREPARE_MEDIA_ID = 4;
- private static final int MSG_PREPARE_SEARCH = 5;
- private static final int MSG_PREPARE_URI = 6;
- private static final int MSG_PLAY = 7;
- private static final int MSG_PLAY_MEDIA_ID = 8;
- private static final int MSG_PLAY_SEARCH = 9;
- private static final int MSG_PLAY_URI = 10;
- private static final int MSG_SKIP_TO_ITEM = 11;
- private static final int MSG_PAUSE = 12;
- private static final int MSG_STOP = 13;
- private static final int MSG_NEXT = 14;
- private static final int MSG_PREVIOUS = 15;
- private static final int MSG_FAST_FORWARD = 16;
- private static final int MSG_REWIND = 17;
- private static final int MSG_SEEK_TO = 18;
- private static final int MSG_RATE = 19;
- private static final int MSG_CUSTOM_ACTION = 20;
- private static final int MSG_ADJUST_VOLUME = 21;
- private static final int MSG_SET_VOLUME = 22;
- private static final int MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT = 23;
-
- private MediaSession.Callback mCallback;
- private RemoteUserInfo mCurrentControllerInfo;
-
- public CallbackMessageHandler(Looper looper, MediaSession.Callback callback) {
- super(looper);
- mCallback = callback;
- mCallback.mHandler = this;
- }
-
- public void post(RemoteUserInfo caller, int what, Object obj, Bundle data, long delayMs) {
- Pair<RemoteUserInfo, Object> objWithCaller = Pair.create(caller, obj);
- Message msg = obtainMessage(what, objWithCaller);
- msg.setAsynchronous(true);
- msg.setData(data);
- if (delayMs > 0) {
- sendMessageDelayed(msg, delayMs);
- } else {
- sendMessage(msg);
- }
- }
-
- @Override
- public void handleMessage(Message msg) {
- mCurrentControllerInfo = ((Pair<RemoteUserInfo, Object>) msg.obj).first;
-
- VolumeProvider vp;
- Object obj = ((Pair<RemoteUserInfo, Object>) msg.obj).second;
-
- switch (msg.what) {
- case MSG_COMMAND:
- Command cmd = (Command) obj;
- mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
- break;
- case MSG_MEDIA_BUTTON:
- mCallback.onMediaButtonEvent((Intent) obj);
- break;
- case MSG_PREPARE:
- mCallback.onPrepare();
- break;
- case MSG_PREPARE_MEDIA_ID:
- mCallback.onPrepareFromMediaId((String) obj, msg.getData());
- break;
- case MSG_PREPARE_SEARCH:
- mCallback.onPrepareFromSearch((String) obj, msg.getData());
- break;
- case MSG_PREPARE_URI:
- mCallback.onPrepareFromUri((Uri) obj, msg.getData());
- break;
- case MSG_PLAY:
- mCallback.onPlay();
- break;
- case MSG_PLAY_MEDIA_ID:
- mCallback.onPlayFromMediaId((String) obj, msg.getData());
- break;
- case MSG_PLAY_SEARCH:
- mCallback.onPlayFromSearch((String) obj, msg.getData());
- break;
- case MSG_PLAY_URI:
- mCallback.onPlayFromUri((Uri) obj, msg.getData());
- break;
- case MSG_SKIP_TO_ITEM:
- mCallback.onSkipToQueueItem((Long) obj);
- break;
- case MSG_PAUSE:
- mCallback.onPause();
- break;
- case MSG_STOP:
- mCallback.onStop();
- break;
- case MSG_NEXT:
- mCallback.onSkipToNext();
- break;
- case MSG_PREVIOUS:
- mCallback.onSkipToPrevious();
- break;
- case MSG_FAST_FORWARD:
- mCallback.onFastForward();
- break;
- case MSG_REWIND:
- mCallback.onRewind();
- break;
- case MSG_SEEK_TO:
- mCallback.onSeekTo((Long) obj);
- break;
- case MSG_RATE:
- mCallback.onSetRating((Rating) obj);
- break;
- case MSG_CUSTOM_ACTION:
- mCallback.onCustomAction((String) obj, msg.getData());
- break;
- case MSG_ADJUST_VOLUME:
- synchronized (mLock) {
- vp = mVolumeProvider;
- }
- if (vp != null) {
- vp.onAdjustVolume((int) obj);
- }
- break;
- case MSG_SET_VOLUME:
- synchronized (mLock) {
- vp = mVolumeProvider;
- }
- if (vp != null) {
- vp.onSetVolumeTo((int) obj);
- }
- break;
- case MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT:
- mCallback.handleMediaPlayPauseKeySingleTapIfPending();
- break;
- }
- mCurrentControllerInfo = null;
+ return mImpl.equals(((QueueItem) o).mImpl);
}
}
}
diff --git a/media/java/android/media/session/MediaSessionEngine.java b/media/java/android/media/session/MediaSessionEngine.java
new file mode 100644
index 0000000..f159a95
--- /dev/null
+++ b/media/java/android/media/session/MediaSessionEngine.java
@@ -0,0 +1,1479 @@
+/*
+ * 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.media.session;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.UnsupportedAppUsage;
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioAttributes;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.Rating;
+import android.media.VolumeProvider;
+import android.media.session.MediaSessionManager.RemoteUserInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.ResultReceiver;
+import android.service.media.MediaBrowserService;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+import android.view.KeyEvent;
+import android.view.ViewConfiguration;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class MediaSessionEngine implements AutoCloseable {
+ private static final String TAG = MediaSession.TAG;
+
+ private final Object mLock = new Object();
+ private final int mMaxBitmapSize;
+
+ private final MediaSession.Token mSessionToken;
+ private final MediaController mController;
+ private final SessionLink mSessionLink;
+ private final SessionCallbackLink mCbLink;
+
+ // Do not change the name of mCallbackWrapper. Support lib accesses this by using reflection.
+ @UnsupportedAppUsage
+ private CallbackMessageHandler mCallbackHandler;
+ private VolumeProvider mVolumeProvider;
+ private PlaybackState mPlaybackState;
+
+ private boolean mActive = false;
+
+ /**
+ * Creates a new session. The session will automatically be registered with
+ * the system but will not be published until {@link #setActive(boolean)
+ * setActive(true)} is called. You must call {@link #close()} when
+ * finished with the session.
+ *
+ * @param context The context to use to create the session.
+ * @param sessionLink A session link for the binder of MediaSessionRecord
+ * @param cbStub A callback link that handles incoming command to {@link MediaSession.Callback}.
+ */
+ public MediaSessionEngine(@NonNull Context context, @NonNull SessionLink sessionLink,
+ @NonNull SessionCallbackLink cbLink, @NonNull CallbackStub cbStub, int maxBitmapSize) {
+ mSessionLink = sessionLink;
+ mCbLink = cbLink;
+ mMaxBitmapSize = maxBitmapSize;
+
+ cbStub.setSessionImpl(this);
+ mSessionToken = new MediaSession.Token(mSessionLink.getController());
+ mController = new MediaController(context, mSessionToken);
+ }
+
+ /**
+ * Set the callback to receive updates for the MediaSession. This includes
+ * media button events and transport controls. The caller's thread will be
+ * used to post updates.
+ * <p>
+ * Set the callback to null to stop receiving updates.
+ *
+ * @param callback The callback object
+ */
+ public void setCallback(@Nullable MediaSession.Callback callback) {
+ setCallback(callback, new Handler());
+ }
+
+ /**
+ * Set the callback to receive updates for the MediaSession. This includes
+ * media button events and transport controls.
+ * <p>
+ * Set the callback to null to stop receiving updates.
+ *
+ * @param callback The callback to receive updates on.
+ * @param handler The handler that events should be posted on.
+ */
+ public void setCallback(@Nullable MediaSession.Callback callback, @NonNull Handler handler) {
+ setCallbackInternal(callback == null ? null : new CallbackWrapper(callback), handler);
+ }
+
+ private void setCallbackInternal(CallbackWrapper callback, Handler handler) {
+ synchronized (mLock) {
+ if (mCallbackHandler != null) {
+ // We're updating the callback, clear the session from the old one.
+ mCallbackHandler.mCallbackWrapper.mSessionImpl = null;
+ mCallbackHandler.removeCallbacksAndMessages(null);
+ }
+ if (callback == null) {
+ mCallbackHandler = null;
+ return;
+ }
+ callback.mSessionImpl = this;
+ CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(),
+ callback);
+ mCallbackHandler = msgHandler;
+ }
+ }
+
+ /**
+ * Set an intent for launching UI for this Session. This can be used as a
+ * quick link to an ongoing media screen. The intent should be for an
+ * activity that may be started using {@link Activity#startActivity(Intent)}.
+ *
+ * @param pi The intent to launch to show UI for this Session.
+ */
+ public void setSessionActivity(@Nullable PendingIntent pi) {
+ try {
+ mSessionLink.setLaunchPendingIntent(pi);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Failure in setLaunchPendingIntent.", e);
+ }
+ }
+
+ /**
+ * Set a pending intent for your media button receiver to allow restarting
+ * playback after the session has been stopped. If your app is started in
+ * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via
+ * the pending intent.
+ *
+ * @param mbr The {@link PendingIntent} to send the media button event to.
+ */
+ public void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
+ try {
+ mSessionLink.setMediaButtonReceiver(mbr);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e);
+ }
+ }
+
+ /**
+ * Set any flags for the session.
+ *
+ * @param flags The flags to set for this session.
+ */
+ public void setFlags(@MediaSession.SessionFlags int flags) {
+ try {
+ mSessionLink.setFlags(flags);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Failure in setFlags.", e);
+ }
+ }
+
+ /**
+ * Set the attributes for this session's audio. This will affect the
+ * system's volume handling for this session. If
+ * {@link #setPlaybackToRemote} was previously called it will stop receiving
+ * volume commands and the system will begin sending volume changes to the
+ * appropriate stream.
+ * <p>
+ * By default sessions use attributes for media.
+ *
+ * @param attributes The {@link AudioAttributes} for this session's audio.
+ */
+ public void setPlaybackToLocal(AudioAttributes attributes) {
+ if (attributes == null) {
+ throw new IllegalArgumentException("Attributes cannot be null for local playback.");
+ }
+ try {
+ mSessionLink.setPlaybackToLocal(attributes);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Failure in setPlaybackToLocal.", e);
+ }
+ }
+
+ /**
+ * Configure this session to use remote volume handling. This must be called
+ * to receive volume button events, otherwise the system will adjust the
+ * appropriate stream volume for this session. If
+ * {@link #setPlaybackToLocal} was previously called the system will stop
+ * handling volume changes for this session and pass them to the volume
+ * provider instead.
+ *
+ * @param volumeProvider The provider that will handle volume changes. May
+ * not be null.
+ */
+ public void setPlaybackToRemote(@NonNull VolumeProvider volumeProvider) {
+ if (volumeProvider == null) {
+ throw new IllegalArgumentException("volumeProvider may not be null!");
+ }
+ synchronized (mLock) {
+ mVolumeProvider = volumeProvider;
+ }
+ volumeProvider.setCallback(new VolumeProvider.Callback() {
+ @Override
+ public void onVolumeChanged(VolumeProvider volumeProvider) {
+ notifyRemoteVolumeChanged(volumeProvider);
+ }
+ });
+
+ try {
+ mSessionLink.setPlaybackToRemote(volumeProvider.getVolumeControl(),
+ volumeProvider.getMaxVolume());
+ mSessionLink.setCurrentVolume(volumeProvider.getCurrentVolume());
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Failure in setPlaybackToRemote.", e);
+ }
+ }
+
+ /**
+ * Set if this session is currently active and ready to receive commands. If
+ * set to false your session's controller may not be discoverable. You must
+ * set the session to active before it can start receiving media button
+ * events or transport commands.
+ *
+ * @param active Whether this session is active or not.
+ */
+ public void setActive(boolean active) {
+ if (mActive == active) {
+ return;
+ }
+ try {
+ mSessionLink.setActive(active);
+ mActive = active;
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Failure in setActive.", e);
+ }
+ }
+
+ /**
+ * Get the current active state of this session.
+ *
+ * @return True if the session is active, false otherwise.
+ */
+ public boolean isActive() {
+ return mActive;
+ }
+
+ /**
+ * Send a proprietary event to all MediaControllers listening to this
+ * Session. It's up to the Controller/Session owner to determine the meaning
+ * of any events.
+ *
+ * @param event The name of the event to send
+ * @param extras Any extras included with the event
+ */
+ public void sendSessionEvent(@NonNull String event, @Nullable Bundle extras) {
+ if (TextUtils.isEmpty(event)) {
+ throw new IllegalArgumentException("event cannot be null or empty");
+ }
+ try {
+ mSessionLink.sendEvent(event, extras);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Error sending event", e);
+ }
+ }
+
+ /**
+ * This must be called when an app has finished performing playback. If
+ * playback is expected to start again shortly the session can be left open,
+ * but it must be released if your activity or service is being destroyed.
+ */
+ public void close() {
+ try {
+ mSessionLink.destroySession();
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Error releasing session: ", e);
+ }
+ }
+
+ /**
+ * Retrieve a token object that can be used by apps to create a
+ * {@link MediaController} for interacting with this session. The owner of
+ * the session is responsible for deciding how to distribute these tokens.
+ *
+ * @return A token that can be used to create a MediaController for this
+ * session
+ */
+ public @NonNull MediaSession.Token getSessionToken() {
+ return mSessionToken;
+ }
+
+ /**
+ * Get a controller for this session. This is a convenience method to avoid
+ * having to cache your own controller in process.
+ *
+ * @return A controller for this session.
+ */
+ public @NonNull MediaController getController() {
+ return mController;
+ }
+
+ /**
+ * Update the current playback state.
+ *
+ * @param state The current state of playback
+ */
+ public void setPlaybackState(@Nullable PlaybackState state) {
+ mPlaybackState = state;
+ try {
+ mSessionLink.setPlaybackState(state);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Dead object in setPlaybackState.", e);
+ }
+ }
+
+ /**
+ * Update the current metadata. New metadata can be created using
+ * {@link android.media.MediaMetadata.Builder}. This operation may take time proportional to
+ * the size of the bitmap to replace large bitmaps with a scaled down copy.
+ *
+ * @param metadata The new metadata
+ * @see android.media.MediaMetadata.Builder#putBitmap
+ */
+ public void setMetadata(@Nullable MediaMetadata metadata) {
+ long duration = -1;
+ int fields = 0;
+ MediaDescription description = null;
+ if (metadata != null) {
+ metadata = (new MediaMetadata.Builder(metadata, mMaxBitmapSize)).build();
+ if (metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
+ duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
+ }
+ fields = metadata.size();
+ description = metadata.getDescription();
+ }
+ String metadataDescription = "size=" + fields + ", description=" + description;
+
+ try {
+ mSessionLink.setMetadata(metadata, duration, metadataDescription);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Dead object in setPlaybackState.", e);
+ }
+ }
+
+ /**
+ * Update the list of items in the play queue. It is an ordered list and
+ * should contain the current item, and previous or upcoming items if they
+ * exist. Specify null if there is no current play queue.
+ * <p>
+ * The queue should be of reasonable size. If the play queue is unbounded
+ * within your app, it is better to send a reasonable amount in a sliding
+ * window instead.
+ *
+ * @param queue A list of items in the play queue.
+ */
+ public void setQueue(@Nullable List<MediaSession.QueueItem> queue) {
+ try {
+ mSessionLink.setQueue(queue);
+ } catch (RuntimeException e) {
+ Log.wtf("Dead object in setQueue.", e);
+ }
+ }
+
+ /**
+ * Set the title of the play queue. The UI should display this title along
+ * with the play queue itself.
+ * e.g. "Play Queue", "Now Playing", or an album name.
+ *
+ * @param title The title of the play queue.
+ */
+ public void setQueueTitle(@Nullable CharSequence title) {
+ try {
+ mSessionLink.setQueueTitle(title);
+ } catch (RuntimeException e) {
+ Log.wtf("Dead object in setQueueTitle.", e);
+ }
+ }
+
+ /**
+ * Set the style of rating used by this session. Apps trying to set the
+ * rating should use this style. Must be one of the following:
+ * <ul>
+ * <li>{@link Rating#RATING_NONE}</li>
+ * <li>{@link Rating#RATING_3_STARS}</li>
+ * <li>{@link Rating#RATING_4_STARS}</li>
+ * <li>{@link Rating#RATING_5_STARS}</li>
+ * <li>{@link Rating#RATING_HEART}</li>
+ * <li>{@link Rating#RATING_PERCENTAGE}</li>
+ * <li>{@link Rating#RATING_THUMB_UP_DOWN}</li>
+ * </ul>
+ */
+ public void setRatingType(@Rating.Style int type) {
+ try {
+ mSessionLink.setRatingType(type);
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Error in setRatingType.", e);
+ }
+ }
+
+ /**
+ * Set some extras that can be associated with the {@link MediaSession}. No assumptions should
+ * be made as to how a {@link MediaController} will handle these extras.
+ * Keys should be fully qualified (e.g. com.example.MY_EXTRA) to avoid conflicts.
+ *
+ * @param extras The extras associated with the {@link MediaSession}.
+ */
+ public void setExtras(@Nullable Bundle extras) {
+ try {
+ mSessionLink.setExtras(extras);
+ } catch (RuntimeException e) {
+ Log.wtf("Dead object in setExtras.", e);
+ }
+ }
+
+ /**
+ * Gets the controller information who sent the current request.
+ * <p>
+ * Note: This is only valid while in a request callback, such as
+ * {@link MediaSession.Callback#onPlay}.
+ *
+ * @throws IllegalStateException If this method is called outside of
+ * {@link MediaSession.Callback} methods.
+ * @see MediaSessionManager#isTrustedForMediaControl(RemoteUserInfo)
+ */
+ public @NonNull RemoteUserInfo getCurrentControllerInfo() {
+ if (mCallbackHandler == null || mCallbackHandler.mCurrentControllerInfo == null) {
+ throw new IllegalStateException(
+ "This should be called inside of MediaSession.Callback methods");
+ }
+ return mCallbackHandler.mCurrentControllerInfo;
+ }
+
+ /**
+ * Notify the system that the remote volume changed.
+ *
+ * @param provider The provider that is handling volume changes.
+ * @hide
+ */
+ public void notifyRemoteVolumeChanged(VolumeProvider provider) {
+ synchronized (mLock) {
+ if (provider == null || provider != mVolumeProvider) {
+ Log.w(TAG, "Received update from stale volume provider");
+ return;
+ }
+ }
+ try {
+ mSessionLink.setCurrentVolume(provider.getCurrentVolume());
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Error in notifyVolumeChanged", e);
+ }
+ }
+
+ /**
+ * Returns the name of the package that sent the last media button, transport control, or
+ * command from controllers and the system. This is only valid while in a request callback, such
+ * as {@link MediaSession.Callback#onPlay}.
+ */
+ public String getCallingPackage() {
+ if (mCallbackHandler != null && mCallbackHandler.mCurrentControllerInfo != null) {
+ return mCallbackHandler.mCurrentControllerInfo.getPackageName();
+ }
+ return null;
+ }
+
+ private void dispatchPrepare(RemoteUserInfo caller) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PREPARE, null, null);
+ }
+
+ private void dispatchPrepareFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras);
+ }
+
+ private void dispatchPrepareFromSearch(RemoteUserInfo caller, String query, Bundle extras) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_SEARCH, query, extras);
+ }
+
+ private void dispatchPrepareFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_URI, uri, extras);
+ }
+
+ private void dispatchPlay(RemoteUserInfo caller) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PLAY, null, null);
+ }
+
+ private void dispatchPlayFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras);
+ }
+
+ private void dispatchPlayFromSearch(RemoteUserInfo caller, String query, Bundle extras) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PLAY_SEARCH, query, extras);
+ }
+
+ private void dispatchPlayFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PLAY_URI, uri, extras);
+ }
+
+ private void dispatchSkipToItem(RemoteUserInfo caller, long id) {
+ postToCallback(caller, CallbackMessageHandler.MSG_SKIP_TO_ITEM, id, null);
+ }
+
+ private void dispatchPause(RemoteUserInfo caller) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PAUSE, null, null);
+ }
+
+ private void dispatchStop(RemoteUserInfo caller) {
+ postToCallback(caller, CallbackMessageHandler.MSG_STOP, null, null);
+ }
+
+ private void dispatchNext(RemoteUserInfo caller) {
+ postToCallback(caller, CallbackMessageHandler.MSG_NEXT, null, null);
+ }
+
+ private void dispatchPrevious(RemoteUserInfo caller) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PREVIOUS, null, null);
+ }
+
+ private void dispatchFastForward(RemoteUserInfo caller) {
+ postToCallback(caller, CallbackMessageHandler.MSG_FAST_FORWARD, null, null);
+ }
+
+ private void dispatchRewind(RemoteUserInfo caller) {
+ postToCallback(caller, CallbackMessageHandler.MSG_REWIND, null, null);
+ }
+
+ private void dispatchSeekTo(RemoteUserInfo caller, long pos) {
+ postToCallback(caller, CallbackMessageHandler.MSG_SEEK_TO, pos, null);
+ }
+
+ private void dispatchRate(RemoteUserInfo caller, Rating rating) {
+ postToCallback(caller, CallbackMessageHandler.MSG_RATE, rating, null);
+ }
+
+ private void dispatchCustomAction(RemoteUserInfo caller, String action, Bundle args) {
+ postToCallback(caller, CallbackMessageHandler.MSG_CUSTOM_ACTION, action, args);
+ }
+
+ private void dispatchMediaButton(RemoteUserInfo caller, Intent mediaButtonIntent) {
+ postToCallback(caller, CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent, null);
+ }
+
+ private void dispatchMediaButtonDelayed(RemoteUserInfo info, Intent mediaButtonIntent,
+ long delay) {
+ postToCallbackDelayed(info, CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT,
+ mediaButtonIntent, null, delay);
+ }
+
+ private void dispatchAdjustVolume(RemoteUserInfo caller, int direction) {
+ postToCallback(caller, CallbackMessageHandler.MSG_ADJUST_VOLUME, direction, null);
+ }
+
+ private void dispatchSetVolumeTo(RemoteUserInfo caller, int volume) {
+ postToCallback(caller, CallbackMessageHandler.MSG_SET_VOLUME, volume, null);
+ }
+
+ private void dispatchCommand(RemoteUserInfo caller, String command, Bundle args,
+ ResultReceiver resultCb) {
+ Command cmd = new Command(command, args, resultCb);
+ postToCallback(caller, CallbackMessageHandler.MSG_COMMAND, cmd, null);
+ }
+
+ private void postToCallback(RemoteUserInfo caller, int what, Object obj, Bundle data) {
+ postToCallbackDelayed(caller, what, obj, data, 0);
+ }
+
+ private void postToCallbackDelayed(RemoteUserInfo caller, int what, Object obj, Bundle data,
+ long delay) {
+ synchronized (mLock) {
+ if (mCallbackHandler != null) {
+ mCallbackHandler.post(caller, what, obj, data, delay);
+ }
+ }
+ }
+
+ /**
+ * Return true if this is considered an active playback state.
+ */
+ public static boolean isActiveState(int state) {
+ switch (state) {
+ case PlaybackState.STATE_FAST_FORWARDING:
+ case PlaybackState.STATE_REWINDING:
+ case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
+ case PlaybackState.STATE_SKIPPING_TO_NEXT:
+ case PlaybackState.STATE_BUFFERING:
+ case PlaybackState.STATE_CONNECTING:
+ case PlaybackState.STATE_PLAYING:
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Interface for handling MediaButtoneEvent
+ */
+ public interface MediaButtonEventDelegate {
+ /**
+ * Called when a media button is pressed and this session has the
+ * highest priority or a controller sends a media button event to the
+ * session.
+ *
+ * @param mediaButtonIntent an intent containing the KeyEvent as an extra
+ * @return True if the event was handled, false otherwise.
+ */
+ boolean onMediaButtonIntent(Intent mediaButtonIntent);
+ }
+
+ /**
+ * Receives media buttons, transport controls, and commands from controllers
+ * and the system. A callback may be set using {@link #setCallback}.
+ * @hide
+ */
+ public static class CallbackWrapper implements MediaButtonEventDelegate {
+
+ private final MediaSession.Callback mCallback;
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ MediaSessionEngine mSessionImpl;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ CallbackMessageHandler mHandler;
+ private boolean mMediaPlayPauseKeyPending;
+
+ public CallbackWrapper(MediaSession.Callback callback) {
+ mCallback = callback;
+ if (mCallback != null) {
+ mCallback.onSetMediaButtonEventDelegate(this);
+ }
+ }
+
+ /**
+ * Called when a controller has sent a command to this session.
+ * The owner of the session may handle custom commands but is not
+ * required to.
+ *
+ * @param command The command name.
+ * @param args Optional parameters for the command, may be null.
+ * @param cb A result receiver to which a result may be sent by the command, may be null.
+ */
+ public void onCommand(@NonNull String command, @Nullable Bundle args,
+ @Nullable ResultReceiver cb) {
+ if (mCallback != null) {
+ mCallback.onCommand(command, args, cb);
+ }
+ }
+
+ /**
+ * Called when a media button is pressed and this session has the
+ * highest priority or a controller sends a media button event to the
+ * session. The default behavior will call the relevant method if the
+ * action for it was set.
+ * <p>
+ * The intent will be of type {@link Intent#ACTION_MEDIA_BUTTON} with a
+ * KeyEvent in {@link Intent#EXTRA_KEY_EVENT}
+ *
+ * @param mediaButtonIntent an intent containing the KeyEvent as an
+ * extra
+ * @return True if the event was handled, false otherwise.
+ */
+ public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
+ return mCallback == null ? false : mCallback.onMediaButtonEvent(mediaButtonIntent);
+ }
+
+ private void handleMediaPlayPauseKeySingleTapIfPending() {
+ if (!mMediaPlayPauseKeyPending) {
+ return;
+ }
+ mMediaPlayPauseKeyPending = false;
+ mHandler.removeMessages(CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
+ PlaybackState state = mSessionImpl.mPlaybackState;
+ long validActions = state == null ? 0 : state.getActions();
+ boolean isPlaying = state != null
+ && state.getState() == PlaybackState.STATE_PLAYING;
+ boolean canPlay = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
+ | PlaybackState.ACTION_PLAY)) != 0;
+ boolean canPause = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
+ | PlaybackState.ACTION_PAUSE)) != 0;
+ if (isPlaying && canPause) {
+ onPause();
+ } else if (!isPlaying && canPlay) {
+ onPlay();
+ }
+ }
+
+ /**
+ * Override to handle requests to prepare playback. During the preparation, a session should
+ * not hold audio focus in order to allow other sessions play seamlessly. The state of
+ * playback should be updated to {@link PlaybackState#STATE_PAUSED} after the preparation is
+ * done.
+ */
+ public void onPrepare() {
+ if (mCallback != null) {
+ mCallback.onPrepare();
+ }
+ }
+
+ /**
+ * Override to handle requests to prepare for playing a specific mediaId that was provided
+ * by your app's {@link MediaBrowserService}. During the preparation, a session should not
+ * hold audio focus in order to allow other sessions play seamlessly. The state of playback
+ * should be updated to {@link PlaybackState#STATE_PAUSED} after the preparation is done.
+ * The playback of the prepared content should start in the implementation of
+ * {@link #onPlay}. Override {@link #onPlayFromMediaId} to handle requests for starting
+ * playback without preparation.
+ */
+ public void onPrepareFromMediaId(String mediaId, Bundle extras) {
+ if (mCallback != null) {
+ mCallback.onPrepareFromMediaId(mediaId, extras);
+ }
+ }
+
+ /**
+ * Override to handle requests to prepare playback from a search query. An empty query
+ * indicates that the app may prepare any music. The implementation should attempt to make a
+ * smart choice about what to play. During the preparation, a session should not hold audio
+ * focus in order to allow other sessions play seamlessly. The state of playback should be
+ * updated to {@link PlaybackState#STATE_PAUSED} after the preparation is done. The playback
+ * of the prepared content should start in the implementation of {@link #onPlay}. Override
+ * {@link #onPlayFromSearch} to handle requests for starting playback without preparation.
+ */
+ public void onPrepareFromSearch(String query, Bundle extras) {
+ if (mCallback != null) {
+ mCallback.onPrepareFromSearch(query, extras);
+ }
+ }
+
+ /**
+ * Override to handle requests to prepare a specific media item represented by a URI.
+ * During the preparation, a session should not hold audio focus in order to allow
+ * other sessions play seamlessly. The state of playback should be updated to
+ * {@link PlaybackState#STATE_PAUSED} after the preparation is done.
+ * The playback of the prepared content should start in the implementation of
+ * {@link #onPlay}. Override {@link #onPlayFromUri} to handle requests
+ * for starting playback without preparation.
+ */
+ public void onPrepareFromUri(Uri uri, Bundle extras) {
+ if (mCallback != null) {
+ mCallback.onPrepareFromUri(uri, extras);
+ }
+ }
+
+ /**
+ * Override to handle requests to begin playback.
+ */
+ public void onPlay() {
+ if (mCallback != null) {
+ mCallback.onPlay();
+ }
+ }
+
+ /**
+ * Override to handle requests to begin playback from a search query. An
+ * empty query indicates that the app may play any music. The
+ * implementation should attempt to make a smart choice about what to
+ * play.
+ */
+ public void onPlayFromSearch(String query, Bundle extras) {
+ if (mCallback != null) {
+ mCallback.onPlayFromSearch(query, extras);
+ }
+ }
+
+ /**
+ * Override to handle requests to play a specific mediaId that was
+ * provided by your app's {@link MediaBrowserService}.
+ */
+ public void onPlayFromMediaId(String mediaId, Bundle extras) {
+ if (mCallback != null) {
+ mCallback.onPlayFromMediaId(mediaId, extras);
+ }
+ }
+
+ /**
+ * Override to handle requests to play a specific media item represented by a URI.
+ */
+ public void onPlayFromUri(Uri uri, Bundle extras) {
+ if (mCallback != null) {
+ mCallback.onPlayFromUri(uri, extras);
+ }
+ }
+
+ /**
+ * Override to handle requests to play an item with a given id from the
+ * play queue.
+ */
+ public void onSkipToQueueItem(long id) {
+ if (mCallback != null) {
+ mCallback.onSkipToQueueItem(id);
+ }
+ }
+
+ /**
+ * Override to handle requests to pause playback.
+ */
+ public void onPause() {
+ if (mCallback != null) {
+ mCallback.onPause();
+ }
+ }
+
+ /**
+ * Override to handle requests to skip to the next media item.
+ */
+ public void onSkipToNext() {
+ if (mCallback != null) {
+ mCallback.onSkipToNext();
+ }
+ }
+
+ /**
+ * Override to handle requests to skip to the previous media item.
+ */
+ public void onSkipToPrevious() {
+ if (mCallback != null) {
+ mCallback.onSkipToPrevious();
+ }
+ }
+
+ /**
+ * Override to handle requests to fast forward.
+ */
+ public void onFastForward() {
+ if (mCallback != null) {
+ mCallback.onFastForward();
+ }
+ }
+
+ /**
+ * Override to handle requests to rewind.
+ */
+ public void onRewind() {
+ if (mCallback != null) {
+ mCallback.onRewind();
+ }
+ }
+
+ /**
+ * Override to handle requests to stop playback.
+ */
+ public void onStop() {
+ if (mCallback != null) {
+ mCallback.onStop();
+ }
+ }
+
+ /**
+ * Override to handle requests to seek to a specific position in ms.
+ *
+ * @param pos New position to move to, in milliseconds.
+ */
+ public void onSeekTo(long pos) {
+ if (mCallback != null) {
+ mCallback.onSeekTo(pos);
+ }
+ }
+
+ /**
+ * Override to handle the item being rated.
+ *
+ * @param rating
+ */
+ public void onSetRating(@NonNull Rating rating) {
+ if (mCallback != null) {
+ mCallback.onSetRating(rating);
+ }
+ }
+
+ /**
+ * Called when a {@link MediaController} wants a {@link PlaybackState.CustomAction} to be
+ * performed.
+ *
+ * @param action The action that was originally sent in the
+ * {@link PlaybackState.CustomAction}.
+ * @param extras Optional extras specified by the {@link MediaController}.
+ */
+ public void onCustomAction(@NonNull String action, @Nullable Bundle extras) {
+ if (mCallback != null) {
+ mCallback.onCustomAction(action, extras);
+ }
+ }
+
+ @Override
+ public boolean onMediaButtonIntent(Intent mediaButtonIntent) {
+ if (mSessionImpl != null && mHandler != null
+ && Intent.ACTION_MEDIA_BUTTON.equals(mediaButtonIntent.getAction())) {
+ KeyEvent ke = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+ if (ke != null && ke.getAction() == KeyEvent.ACTION_DOWN) {
+ PlaybackState state = mSessionImpl.mPlaybackState;
+ long validActions = state == null ? 0 : state.getActions();
+ switch (ke.getKeyCode()) {
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ if (ke.getRepeatCount() > 0) {
+ // Consider long-press as a single tap.
+ handleMediaPlayPauseKeySingleTapIfPending();
+ } else if (mMediaPlayPauseKeyPending) {
+ // Consider double tap as the next.
+ mHandler.removeMessages(CallbackMessageHandler
+ .MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
+ mMediaPlayPauseKeyPending = false;
+ if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
+ onSkipToNext();
+ }
+ } else {
+ mMediaPlayPauseKeyPending = true;
+ mSessionImpl.dispatchMediaButtonDelayed(
+ mSessionImpl.getCurrentControllerInfo(),
+ mediaButtonIntent, ViewConfiguration.getDoubleTapTimeout());
+ }
+ return true;
+ default:
+ // If another key is pressed within double tap timeout, consider the
+ // pending play/pause as a single tap to handle media keys in order.
+ handleMediaPlayPauseKeySingleTapIfPending();
+ break;
+ }
+
+ switch (ke.getKeyCode()) {
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ if ((validActions & PlaybackState.ACTION_PLAY) != 0) {
+ onPlay();
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ if ((validActions & PlaybackState.ACTION_PAUSE) != 0) {
+ onPause();
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
+ onSkipToNext();
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ if ((validActions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) {
+ onSkipToPrevious();
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ if ((validActions & PlaybackState.ACTION_STOP) != 0) {
+ onStop();
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ if ((validActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
+ onFastForward();
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ if ((validActions & PlaybackState.ACTION_REWIND) != 0) {
+ onRewind();
+ return true;
+ }
+ break;
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public static final class CallbackStub extends SessionCallbackLink.CallbackStub {
+ private WeakReference<MediaSessionEngine> mSessionImpl;
+
+ private static RemoteUserInfo createRemoteUserInfo(String packageName, int pid, int uid) {
+ return new RemoteUserInfo(packageName, pid, uid);
+ }
+
+ public CallbackStub() {
+ }
+
+ @Override
+ public void onCommand(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchCommand(createRemoteUserInfo(packageName, pid, uid),
+ command, args, cb);
+ }
+ }
+
+ @Override
+ public void onMediaButton(String packageName, int pid, int uid, Intent mediaButtonIntent,
+ int sequenceNumber, ResultReceiver cb) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ try {
+ if (sessionImpl != null) {
+ sessionImpl.dispatchMediaButton(
+ createRemoteUserInfo(packageName, pid, uid), mediaButtonIntent);
+ }
+ } finally {
+ if (cb != null) {
+ cb.send(sequenceNumber, null);
+ }
+ }
+ }
+
+ @Override
+ public void onMediaButtonFromController(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, Intent mediaButtonIntent) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid),
+ mediaButtonIntent);
+ }
+ }
+
+ @Override
+ public void onPrepare(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPrepare(createRemoteUserInfo(packageName, pid, uid));
+ }
+ }
+
+ @Override
+ public void onPrepareFromMediaId(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String mediaId,
+ Bundle extras) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPrepareFromMediaId(
+ createRemoteUserInfo(packageName, pid, uid), mediaId, extras);
+ }
+ }
+
+ @Override
+ public void onPrepareFromSearch(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String query,
+ Bundle extras) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPrepareFromSearch(
+ createRemoteUserInfo(packageName, pid, uid), query, extras);
+ }
+ }
+
+ @Override
+ public void onPrepareFromUri(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, Uri uri, Bundle extras) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPrepareFromUri(
+ createRemoteUserInfo(packageName, pid, uid), uri, extras);
+ }
+ }
+
+ @Override
+ public void onPlay(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPlay(createRemoteUserInfo(packageName, pid, uid));
+ }
+ }
+
+ @Override
+ public void onPlayFromMediaId(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String mediaId,
+ Bundle extras) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPlayFromMediaId(
+ createRemoteUserInfo(packageName, pid, uid), mediaId, extras);
+ }
+ }
+
+ @Override
+ public void onPlayFromSearch(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String query,
+ Bundle extras) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPlayFromSearch(
+ createRemoteUserInfo(packageName, pid, uid), query, extras);
+ }
+ }
+
+ @Override
+ public void onPlayFromUri(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, Uri uri, Bundle extras) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPlayFromUri(
+ createRemoteUserInfo(packageName, pid, uid), uri, extras);
+ }
+ }
+
+ @Override
+ public void onSkipToTrack(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, long id) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchSkipToItem(
+ createRemoteUserInfo(packageName, pid, uid), id);
+ }
+ }
+
+ @Override
+ public void onPause(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPause(createRemoteUserInfo(packageName, pid, uid));
+ }
+ }
+
+ @Override
+ public void onStop(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchStop(createRemoteUserInfo(packageName, pid, uid));
+ }
+ }
+
+ @Override
+ public void onNext(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchNext(createRemoteUserInfo(packageName, pid, uid));
+ }
+ }
+
+ @Override
+ public void onPrevious(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPrevious(createRemoteUserInfo(packageName, pid, uid));
+ }
+ }
+
+ @Override
+ public void onFastForward(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchFastForward(
+ createRemoteUserInfo(packageName, pid, uid));
+ }
+ }
+
+ @Override
+ public void onRewind(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchRewind(createRemoteUserInfo(packageName, pid, uid));
+ }
+ }
+
+ @Override
+ public void onSeekTo(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, long pos) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchSeekTo(
+ createRemoteUserInfo(packageName, pid, uid), pos);
+ }
+ }
+
+ @Override
+ public void onRate(String packageName, int pid, int uid, ControllerCallbackLink caller,
+ Rating rating) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchRate(
+ createRemoteUserInfo(packageName, pid, uid), rating);
+ }
+ }
+
+ @Override
+ public void onCustomAction(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String action, Bundle args) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchCustomAction(
+ createRemoteUserInfo(packageName, pid, uid), action, args);
+ }
+ }
+
+ @Override
+ public void onAdjustVolume(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, int direction) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchAdjustVolume(
+ createRemoteUserInfo(packageName, pid, uid), direction);
+ }
+ }
+
+ @Override
+ public void onSetVolumeTo(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, int value) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchSetVolumeTo(
+ createRemoteUserInfo(packageName, pid, uid), value);
+ }
+ }
+
+ void setSessionImpl(MediaSessionEngine sessionImpl) {
+ mSessionImpl = new WeakReference<>(sessionImpl);
+ }
+ }
+
+ /**
+ * A single item that is part of the play queue. It contains a description
+ * of the item and its id in the queue.
+ */
+ public static final class QueueItem {
+ /**
+ * This id is reserved. No items can be explicitly assigned this id.
+ */
+ public static final int UNKNOWN_ID = -1;
+
+ private final MediaDescription mDescription;
+ private final long mId;
+
+ /**
+ * Create a new {@link MediaSession.QueueItem}.
+ *
+ * @param description The {@link MediaDescription} for this item.
+ * @param id An identifier for this item. It must be unique within the
+ * play queue and cannot be {@link #UNKNOWN_ID}.
+ */
+ public QueueItem(MediaDescription description, long id) {
+ if (description == null) {
+ throw new IllegalArgumentException("Description cannot be null.");
+ }
+ if (id == UNKNOWN_ID) {
+ throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID");
+ }
+ mDescription = description;
+ mId = id;
+ }
+
+ public QueueItem(Parcel in) {
+ mDescription = MediaDescription.CREATOR.createFromParcel(in);
+ mId = in.readLong();
+ }
+
+ /**
+ * Get the description for this item.
+ */
+ public MediaDescription getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Get the queue id for this item.
+ */
+ public long getQueueId() {
+ return mId;
+ }
+
+ /**
+ * Flatten this object in to a Parcel.
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ mDescription.writeToParcel(dest, flags);
+ dest.writeLong(mId);
+ }
+
+ @Override
+ public String toString() {
+ return "MediaSession.QueueItem {" + "Description=" + mDescription + ", Id=" + mId
+ + " }";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null) {
+ return false;
+ }
+
+ if (!(o instanceof QueueItem)) {
+ return false;
+ }
+
+ final QueueItem item = (QueueItem) o;
+ if (mId != item.mId) {
+ return false;
+ }
+
+ if (!Objects.equals(mDescription, item.mDescription)) {
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ private static final class Command {
+ public final String command;
+ public final Bundle extras;
+ public final ResultReceiver stub;
+
+ Command(String command, Bundle extras, ResultReceiver stub) {
+ this.command = command;
+ this.extras = extras;
+ this.stub = stub;
+ }
+ }
+
+ private class CallbackMessageHandler extends Handler {
+ private static final int MSG_COMMAND = 1;
+ private static final int MSG_MEDIA_BUTTON = 2;
+ private static final int MSG_PREPARE = 3;
+ private static final int MSG_PREPARE_MEDIA_ID = 4;
+ private static final int MSG_PREPARE_SEARCH = 5;
+ private static final int MSG_PREPARE_URI = 6;
+ private static final int MSG_PLAY = 7;
+ private static final int MSG_PLAY_MEDIA_ID = 8;
+ private static final int MSG_PLAY_SEARCH = 9;
+ private static final int MSG_PLAY_URI = 10;
+ private static final int MSG_SKIP_TO_ITEM = 11;
+ private static final int MSG_PAUSE = 12;
+ private static final int MSG_STOP = 13;
+ private static final int MSG_NEXT = 14;
+ private static final int MSG_PREVIOUS = 15;
+ private static final int MSG_FAST_FORWARD = 16;
+ private static final int MSG_REWIND = 17;
+ private static final int MSG_SEEK_TO = 18;
+ private static final int MSG_RATE = 19;
+ private static final int MSG_CUSTOM_ACTION = 20;
+ private static final int MSG_ADJUST_VOLUME = 21;
+ private static final int MSG_SET_VOLUME = 22;
+ private static final int MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT = 23;
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ CallbackWrapper mCallbackWrapper;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ RemoteUserInfo mCurrentControllerInfo;
+
+ CallbackMessageHandler(Looper looper, CallbackWrapper callbackWrapper) {
+ super(looper);
+ mCallbackWrapper = callbackWrapper;
+ mCallbackWrapper.mHandler = this;
+ }
+
+ void post(RemoteUserInfo caller, int what, Object obj, Bundle data, long delayMs) {
+ Pair<RemoteUserInfo, Object> objWithCaller = Pair.create(caller, obj);
+ Message msg = obtainMessage(what, objWithCaller);
+ msg.setAsynchronous(true);
+ msg.setData(data);
+ if (delayMs > 0) {
+ sendMessageDelayed(msg, delayMs);
+ } else {
+ sendMessage(msg);
+ }
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ mCurrentControllerInfo = ((Pair<RemoteUserInfo, Object>) msg.obj).first;
+
+ VolumeProvider vp;
+ Object obj = ((Pair<RemoteUserInfo, Object>) msg.obj).second;
+
+ switch (msg.what) {
+ case MSG_COMMAND:
+ Command cmd = (Command) obj;
+ mCallbackWrapper.onCommand(cmd.command, cmd.extras, cmd.stub);
+ break;
+ case MSG_MEDIA_BUTTON:
+ mCallbackWrapper.onMediaButtonEvent((Intent) obj);
+ break;
+ case MSG_PREPARE:
+ mCallbackWrapper.onPrepare();
+ break;
+ case MSG_PREPARE_MEDIA_ID:
+ mCallbackWrapper.onPrepareFromMediaId((String) obj, msg.getData());
+ break;
+ case MSG_PREPARE_SEARCH:
+ mCallbackWrapper.onPrepareFromSearch((String) obj, msg.getData());
+ break;
+ case MSG_PREPARE_URI:
+ mCallbackWrapper.onPrepareFromUri((Uri) obj, msg.getData());
+ break;
+ case MSG_PLAY:
+ mCallbackWrapper.onPlay();
+ break;
+ case MSG_PLAY_MEDIA_ID:
+ mCallbackWrapper.onPlayFromMediaId((String) obj, msg.getData());
+ break;
+ case MSG_PLAY_SEARCH:
+ mCallbackWrapper.onPlayFromSearch((String) obj, msg.getData());
+ break;
+ case MSG_PLAY_URI:
+ mCallbackWrapper.onPlayFromUri((Uri) obj, msg.getData());
+ break;
+ case MSG_SKIP_TO_ITEM:
+ mCallbackWrapper.onSkipToQueueItem((Long) obj);
+ break;
+ case MSG_PAUSE:
+ mCallbackWrapper.onPause();
+ break;
+ case MSG_STOP:
+ mCallbackWrapper.onStop();
+ break;
+ case MSG_NEXT:
+ mCallbackWrapper.onSkipToNext();
+ break;
+ case MSG_PREVIOUS:
+ mCallbackWrapper.onSkipToPrevious();
+ break;
+ case MSG_FAST_FORWARD:
+ mCallbackWrapper.onFastForward();
+ break;
+ case MSG_REWIND:
+ mCallbackWrapper.onRewind();
+ break;
+ case MSG_SEEK_TO:
+ mCallbackWrapper.onSeekTo((Long) obj);
+ break;
+ case MSG_RATE:
+ mCallbackWrapper.onSetRating((Rating) obj);
+ break;
+ case MSG_CUSTOM_ACTION:
+ mCallbackWrapper.onCustomAction((String) obj, msg.getData());
+ break;
+ case MSG_ADJUST_VOLUME:
+ synchronized (mLock) {
+ vp = mVolumeProvider;
+ }
+ if (vp != null) {
+ vp.onAdjustVolume((int) obj);
+ }
+ break;
+ case MSG_SET_VOLUME:
+ synchronized (mLock) {
+ vp = mVolumeProvider;
+ }
+ if (vp != null) {
+ vp.onSetVolumeTo((int) obj);
+ }
+ break;
+ case MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT:
+ mCallbackWrapper.handleMediaPlayPauseKeySingleTapIfPending();
+ break;
+ }
+ mCurrentControllerInfo = null;
+ }
+ }
+}
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 4596c22..c64c452 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -28,7 +28,6 @@
import android.media.IRemoteVolumeController;
import android.media.MediaSession2;
import android.media.Session2Token;
-import android.media.browse.MediaBrowser;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
@@ -37,6 +36,7 @@
import android.os.UserHandle;
import android.service.media.MediaBrowserService;
import android.service.notification.NotificationListenerService;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.view.KeyEvent;
@@ -104,9 +104,14 @@
* @return The binder object from the system
* @hide
*/
- public @NonNull ISession createSession(@NonNull SessionCallbackLink cbStub,
- @NonNull String tag, int userId) throws RemoteException {
- return mService.createSession(mContext.getPackageName(), cbStub, tag, userId);
+ @NonNull
+ public SessionLink createSession(@NonNull SessionCallbackLink cbStub, @NonNull String tag) {
+ try {
+ return mService.createSession(mContext.getPackageName(), cbStub, tag,
+ UserHandle.myUserId());
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
}
/**
@@ -117,7 +122,6 @@
* {@link MediaSession2.Builder} instead.
*
* @param token newly created session2 token
- * @hide
*/
public void notifySession2Created(@NonNull Session2Token token) {
if (token == null) {
@@ -171,11 +175,10 @@
@Nullable ComponentName notificationListener, int userId) {
ArrayList<MediaController> controllers = new ArrayList<MediaController>();
try {
- List<IBinder> binders = mService.getSessions(notificationListener, userId);
+ List<ControllerLink> binders = mService.getSessions(notificationListener, userId);
int size = binders.size();
for (int i = 0; i < size; i++) {
- MediaController controller = new MediaController(mContext, ISessionController.Stub
- .asInterface(binders.get(i)));
+ MediaController controller = new MediaController(mContext, binders.get(i));
controllers.add(controller);
}
} catch (RemoteException e) {
@@ -192,9 +195,7 @@
* reject your uses of {@link MediaSession2}.
*
* @return A list of {@link Session2Token}.
- * @hide
*/
- // TODO: unhide
@NonNull
public List<Session2Token> getSession2Tokens() {
return getSession2Tokens(UserHandle.myUserId());
@@ -205,13 +206,12 @@
* given user.
* <p>
* If you want to get tokens for another user, you must hold the
- * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission.
+ * android.Manifest.permission#INTERACT_ACROSS_USERS_FULL permission.
*
* @param userId The user id to fetch sessions for.
* @return A list of {@link Session2Token}
* @hide
*/
- // TODO: unhide
@NonNull
public List<Session2Token> getSession2Tokens(int userId) {
try {
@@ -338,13 +338,25 @@
* for consistent behavior across all devices.
*
* @param listener The listener to add
- * @param handler The handler to call listener on. If {@code null}, calling thread's looper will
- * be used.
- * @hide
*/
- // TODO(jaewan): Unhide
public void addOnSession2TokensChangedListener(
- @NonNull OnSession2TokensChangedListener listener, @Nullable Handler handler) {
+ @NonNull OnSession2TokensChangedListener listener) {
+ addOnSession2TokensChangedListener(UserHandle.myUserId(), listener, new Handler());
+ }
+
+ /**
+ * Adds a listener to be notified when the {@link #getSession2Tokens()} changes.
+ * <p>
+ * This API is not generally intended for third party application developers.
+ * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
+ * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
+ * for consistent behavior across all devices.
+ *
+ * @param listener The listener to add
+ * @param handler The handler to call listener on.
+ */
+ public void addOnSession2TokensChangedListener(
+ @NonNull OnSession2TokensChangedListener listener, @NonNull Handler handler) {
addOnSession2TokensChangedListener(UserHandle.myUserId(), listener, handler);
}
@@ -388,9 +400,7 @@
* Removes the {@link OnSession2TokensChangedListener} to stop receiving session token updates.
*
* @param listener The listener to remove.
- * @hide
*/
- // TODO(jaewan): Unhide
public void removeOnSession2TokensChangedListener(
@NonNull OnSession2TokensChangedListener listener) {
if (listener == null) {
@@ -684,8 +694,6 @@
* Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
* <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
* for consistent behavior across all devices.
- *
- * @hide
*/
public interface OnSession2TokensChangedListener {
/**
@@ -789,7 +797,6 @@
private final String mPackageName;
private final int mPid;
private final int mUid;
- private final IBinder mCallerBinder;
/**
* Create a new remote user information.
@@ -799,22 +806,9 @@
* @param uid The uid of the remote user
*/
public RemoteUserInfo(@NonNull String packageName, int pid, int uid) {
- this(packageName, pid, uid, null);
- }
-
- /**
- * Create a new remote user information.
- *
- * @param packageName The package name of the remote user
- * @param pid The pid of the remote user
- * @param uid The uid of the remote user
- * @param callerBinder The binder of the remote user. Can be {@code null}.
- */
- public RemoteUserInfo(String packageName, int pid, int uid, IBinder callerBinder) {
mPackageName = packageName;
mPid = pid;
mUid = uid;
- mCallerBinder = callerBinder;
}
/**
@@ -839,13 +833,8 @@
}
/**
- * Returns equality of two RemoteUserInfo. Two RemoteUserInfos are the same only if they're
- * sent to the same controller (either {@link MediaController} or
- * {@link MediaBrowser}. If it's not nor one of them is triggered by the key presses, they
- * would be considered as different one.
- * <p>
- * If you only want to compare the caller's package, compare them with the
- * {@link #getPackageName()}, {@link #getPid()}, and/or {@link #getUid()} directly.
+ * Returns equality of two RemoteUserInfo. Two RemoteUserInfo objects are equal
+ * if and only if they have the same package name, same pid, and same uid.
*
* @param obj the reference object with which to compare.
* @return {@code true} if equals, {@code false} otherwise
@@ -859,8 +848,9 @@
return true;
}
RemoteUserInfo otherUserInfo = (RemoteUserInfo) obj;
- return (mCallerBinder == null || otherUserInfo.mCallerBinder == null) ? false
- : mCallerBinder.equals(otherUserInfo.mCallerBinder);
+ return TextUtils.equals(mPackageName, otherUserInfo.mPackageName)
+ && mPid == otherUserInfo.mPid
+ && mUid == otherUserInfo.mUid;
}
@Override
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 17d16b8..0d0ec4c 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -19,17 +19,16 @@
import android.annotation.IntDef;
import android.annotation.LongDef;
import android.annotation.Nullable;
-import android.media.RemoteControlClient;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.text.TextUtils;
-import java.util.ArrayList;
-import java.util.List;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
/**
* Playback state for a {@link MediaSession}. This includes a state like
@@ -318,7 +317,7 @@
mActions = in.readLong();
mCustomActions = in.createTypedArrayList(CustomAction.CREATOR);
mActiveItemId = in.readLong();
- mErrorMessage = in.readCharSequence();
+ mErrorMessage = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
mExtras = in.readBundle();
}
@@ -353,7 +352,7 @@
dest.writeLong(mActions);
dest.writeTypedList(mCustomActions);
dest.writeLong(mActiveItemId);
- dest.writeCharSequence(mErrorMessage);
+ TextUtils.writeToParcel(mErrorMessage, dest, 0);
dest.writeBundle(mExtras);
}
@@ -481,161 +480,6 @@
return mExtras;
}
- /**
- * Get the {@link PlaybackState} state for the given
- * {@link RemoteControlClient} state.
- *
- * @param rccState The state used by {@link RemoteControlClient}.
- * @return The equivalent state used by {@link PlaybackState}.
- * @hide
- */
- public static int getStateFromRccState(int rccState) {
- switch (rccState) {
- case RemoteControlClient.PLAYSTATE_BUFFERING:
- return STATE_BUFFERING;
- case RemoteControlClient.PLAYSTATE_ERROR:
- return STATE_ERROR;
- case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
- return STATE_FAST_FORWARDING;
- case RemoteControlClient.PLAYSTATE_NONE:
- return STATE_NONE;
- case RemoteControlClient.PLAYSTATE_PAUSED:
- return STATE_PAUSED;
- case RemoteControlClient.PLAYSTATE_PLAYING:
- return STATE_PLAYING;
- case RemoteControlClient.PLAYSTATE_REWINDING:
- return STATE_REWINDING;
- case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
- return STATE_SKIPPING_TO_PREVIOUS;
- case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
- return STATE_SKIPPING_TO_NEXT;
- case RemoteControlClient.PLAYSTATE_STOPPED:
- return STATE_STOPPED;
- default:
- return -1;
- }
- }
-
- /**
- * Get the {@link RemoteControlClient} state for the given
- * {@link PlaybackState} state.
- *
- * @param state The state used by {@link PlaybackState}.
- * @return The equivalent state used by {@link RemoteControlClient}.
- * @hide
- */
- public static int getRccStateFromState(int state) {
- switch (state) {
- case STATE_BUFFERING:
- return RemoteControlClient.PLAYSTATE_BUFFERING;
- case STATE_ERROR:
- return RemoteControlClient.PLAYSTATE_ERROR;
- case STATE_FAST_FORWARDING:
- return RemoteControlClient.PLAYSTATE_FAST_FORWARDING;
- case STATE_NONE:
- return RemoteControlClient.PLAYSTATE_NONE;
- case STATE_PAUSED:
- return RemoteControlClient.PLAYSTATE_PAUSED;
- case STATE_PLAYING:
- return RemoteControlClient.PLAYSTATE_PLAYING;
- case STATE_REWINDING:
- return RemoteControlClient.PLAYSTATE_REWINDING;
- case STATE_SKIPPING_TO_PREVIOUS:
- return RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS;
- case STATE_SKIPPING_TO_NEXT:
- return RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS;
- case STATE_STOPPED:
- return RemoteControlClient.PLAYSTATE_STOPPED;
- default:
- return -1;
- }
- }
-
- /**
- * @hide
- */
- public static long getActionsFromRccControlFlags(int rccFlags) {
- long actions = 0;
- long flag = 1;
- while (flag <= rccFlags) {
- if ((flag & rccFlags) != 0) {
- actions |= getActionForRccFlag((int) flag);
- }
- flag = flag << 1;
- }
- return actions;
- }
-
- /**
- * @hide
- */
- public static int getRccControlFlagsFromActions(long actions) {
- int rccFlags = 0;
- long action = 1;
- while (action <= actions && action < Integer.MAX_VALUE) {
- if ((action & actions) != 0) {
- rccFlags |= getRccFlagForAction(action);
- }
- action = action << 1;
- }
- return rccFlags;
- }
-
- private static long getActionForRccFlag(int flag) {
- switch (flag) {
- case RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS:
- return ACTION_SKIP_TO_PREVIOUS;
- case RemoteControlClient.FLAG_KEY_MEDIA_REWIND:
- return ACTION_REWIND;
- case RemoteControlClient.FLAG_KEY_MEDIA_PLAY:
- return ACTION_PLAY;
- case RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE:
- return ACTION_PLAY_PAUSE;
- case RemoteControlClient.FLAG_KEY_MEDIA_PAUSE:
- return ACTION_PAUSE;
- case RemoteControlClient.FLAG_KEY_MEDIA_STOP:
- return ACTION_STOP;
- case RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD:
- return ACTION_FAST_FORWARD;
- case RemoteControlClient.FLAG_KEY_MEDIA_NEXT:
- return ACTION_SKIP_TO_NEXT;
- case RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE:
- return ACTION_SEEK_TO;
- case RemoteControlClient.FLAG_KEY_MEDIA_RATING:
- return ACTION_SET_RATING;
- }
- return 0;
- }
-
- private static int getRccFlagForAction(long action) {
- // We only care about the lower set of actions that can map to rcc
- // flags.
- int testAction = action < Integer.MAX_VALUE ? (int) action : 0;
- switch (testAction) {
- case (int) ACTION_SKIP_TO_PREVIOUS:
- return RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS;
- case (int) ACTION_REWIND:
- return RemoteControlClient.FLAG_KEY_MEDIA_REWIND;
- case (int) ACTION_PLAY:
- return RemoteControlClient.FLAG_KEY_MEDIA_PLAY;
- case (int) ACTION_PLAY_PAUSE:
- return RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE;
- case (int) ACTION_PAUSE:
- return RemoteControlClient.FLAG_KEY_MEDIA_PAUSE;
- case (int) ACTION_STOP:
- return RemoteControlClient.FLAG_KEY_MEDIA_STOP;
- case (int) ACTION_FAST_FORWARD:
- return RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD;
- case (int) ACTION_SKIP_TO_NEXT:
- return RemoteControlClient.FLAG_KEY_MEDIA_NEXT;
- case (int) ACTION_SEEK_TO:
- return RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE;
- case (int) ACTION_SET_RATING:
- return RemoteControlClient.FLAG_KEY_MEDIA_RATING;
- }
- return 0;
- }
-
public static final Parcelable.Creator<PlaybackState> CREATOR =
new Parcelable.Creator<PlaybackState>() {
@Override
diff --git a/media/java/android/media/session/SessionCallbackLink.java b/media/java/android/media/session/SessionCallbackLink.java
index 7547bff..3bcb65c 100644
--- a/media/java/android/media/session/SessionCallbackLink.java
+++ b/media/java/android/media/session/SessionCallbackLink.java
@@ -16,31 +16,40 @@
package android.media.session;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.media.Rating;
import android.net.Uri;
+import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
/**
* Handles incoming commands to {@link MediaSession.Callback}.
- * <p>
- * This API is not generally intended for third party application developers.
+ * @hide
*/
+@SystemApi
public final class SessionCallbackLink implements Parcelable {
+ final Context mContext;
final CallbackStub mCallbackStub;
final ISessionCallback mISessionCallback;
/**
* Constructor for stub (Callee)
*/
- SessionCallbackLink(@NonNull CallbackStub callbackStub) {
+ SessionCallbackLink(@NonNull Context context, @NonNull CallbackStub callbackStub) {
+ mContext = context;
mCallbackStub = callbackStub;
mISessionCallback = new CallbackStubProxy();
}
@@ -48,9 +57,10 @@
/**
* Constructor for interface (Caller)
*/
- SessionCallbackLink(Parcel in) {
+ public SessionCallbackLink(IBinder binder) {
+ mContext = null;
mCallbackStub = null;
- mISessionCallback = ISessionCallback.Stub.asInterface(in.readStrongBinder());
+ mISessionCallback = ISessionCallback.Stub.asInterface(binder);
}
/**
@@ -64,6 +74,7 @@
* @param args the arguments included with the command
* @param cb the result receiver for getting the result of the command
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyCommand(@NonNull String packageName, int pid, int uid,
@NonNull ControllerCallbackLink caller, @NonNull String command,
@Nullable Bundle args, @Nullable ResultReceiver cb) {
@@ -84,6 +95,7 @@
* @param sequenceNumber the sequence number of this call
* @param cb the result receiver for getting the result of the command
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyMediaButton(@NonNull String packageName, int pid, int uid,
@NonNull Intent mediaButtonIntent, int sequenceNumber,
@Nullable ResultReceiver cb) {
@@ -104,6 +116,7 @@
* @param caller the {@link ControllerCallbackLink} of the controller
* @param mediaButtonIntent the media button intent
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyMediaButtonFromController(@NonNull String packageName, int pid, int uid,
@NonNull ControllerCallbackLink caller, @NonNull Intent mediaButtonIntent) {
try {
@@ -122,6 +135,7 @@
* @param uid the uid of the controller
* @param caller the {@link ControllerCallbackLink} of the controller
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyPrepare(@NonNull String packageName, int pid, int uid,
@NonNull ControllerCallbackLink caller) {
try {
@@ -141,6 +155,7 @@
* @param mediaId the ID of the media
* @param extras the extras included with this request.
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyPrepareFromMediaId(@NonNull String packageName, int pid, int uid,
@NonNull ControllerCallbackLink caller, @NonNull String mediaId,
@Nullable Bundle extras) {
@@ -162,6 +177,7 @@
* @param query the search query
* @param extras the extras included with this request.
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyPrepareFromSearch(@NonNull String packageName, int pid, int uid,
@NonNull ControllerCallbackLink caller, @NonNull String query,
@Nullable Bundle extras) {
@@ -182,6 +198,7 @@
* @param uri the uri of the media
* @param extras the extras included with this request.
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyPrepareFromUri(@NonNull String packageName, int pid, int uid,
@NonNull ControllerCallbackLink caller, @NonNull Uri uri, @Nullable Bundle extras) {
try {
@@ -199,6 +216,7 @@
* @param uid the uid of the controller
* @param caller the {@link ControllerCallbackLink} of the controller
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyPlay(@NonNull String packageName, int pid, int uid,
@NonNull ControllerCallbackLink caller) {
try {
@@ -218,6 +236,7 @@
* @param mediaId the ID of the media
* @param extras the extras included with this request.
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyPlayFromMediaId(@NonNull String packageName, int pid, int uid,
@NonNull ControllerCallbackLink caller, @NonNull String mediaId,
@Nullable Bundle extras) {
@@ -238,6 +257,7 @@
* @param query the search query
* @param extras the extras included with this request.
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyPlayFromSearch(@NonNull String packageName, int pid, int uid,
@NonNull ControllerCallbackLink caller, @NonNull String query,
@Nullable Bundle extras) {
@@ -258,6 +278,7 @@
* @param uri the uri of the media
* @param extras the extras included with this request.
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyPlayFromUri(@NonNull String packageName, int pid, int uid,
@NonNull ControllerCallbackLink caller, @NonNull Uri uri, @Nullable Bundle extras) {
try {
@@ -276,6 +297,7 @@
* @param caller the {@link ControllerCallbackLink} of the controller
* @param id the queue id of the item
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifySkipToTrack(@NonNull String packageName, int pid, int uid,
@NonNull ControllerCallbackLink caller, long id) {
try {
@@ -293,6 +315,7 @@
* @param uid the uid of the controller
* @param caller the {@link ControllerCallbackLink} of the controller
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyPause(@NonNull String packageName, int pid, int uid,
@NonNull ControllerCallbackLink caller) {
try {
@@ -310,6 +333,7 @@
* @param uid the uid of the controller
* @param caller the {@link ControllerCallbackLink} of the controller
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyStop(@NonNull String packageName, int pid, int uid,
@NonNull ControllerCallbackLink caller) {
try {
@@ -327,6 +351,7 @@
* @param uid the uid of the controller
* @param caller the {@link ControllerCallbackLink} of the controller
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyNext(@NonNull String packageName, int pid, int uid,
@NonNull ControllerCallbackLink caller) {
try {
@@ -344,6 +369,7 @@
* @param uid the uid of the controller
* @param caller the {@link ControllerCallbackLink} of the controller
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyPrevious(@NonNull String packageName, int pid, int uid,
@NonNull ControllerCallbackLink caller) {
try {
@@ -361,6 +387,7 @@
* @param uid the uid of the controller
* @param caller the {@link ControllerCallbackLink} of the controller
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyFastForward(@NonNull String packageName, int pid, int uid,
@NonNull ControllerCallbackLink caller) {
try {
@@ -378,6 +405,7 @@
* @param uid the uid of the controller
* @param caller the {@link ControllerCallbackLink} of the controller
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyRewind(@NonNull String packageName, int pid, int uid,
@NonNull ControllerCallbackLink caller) {
try {
@@ -396,6 +424,7 @@
* @param caller the {@link ControllerCallbackLink} of the controller
* @param pos the position to move to, in milliseconds
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifySeekTo(@NonNull String packageName, int pid, int uid,
@NonNull ControllerCallbackLink caller, long pos) {
try {
@@ -414,6 +443,7 @@
* @param caller the {@link ControllerCallbackLink} of the controller
* @param rating the rating of the current media
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyRate(@NonNull String packageName, int pid, int uid,
@NonNull ControllerCallbackLink caller, @NonNull Rating rating) {
try {
@@ -433,6 +463,7 @@
* @param action the name of the action
* @param args the arguments included with this action
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyCustomAction(@NonNull String packageName, int pid, int uid,
@NonNull ControllerCallbackLink caller, @NonNull String action, @Nullable Bundle args) {
try {
@@ -451,6 +482,7 @@
* @param caller the {@link ControllerCallbackLink} of the controller
* @param direction the direction of the volume change.
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifyAdjustVolume(@NonNull String packageName, int pid, int uid,
@NonNull ControllerCallbackLink caller, int direction) {
try {
@@ -469,6 +501,7 @@
* @param caller the {@link ControllerCallbackLink} of the controller
* @param value the volume value to set
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
public void notifySetVolumeTo(@NonNull String packageName, int pid, int uid,
@NonNull ControllerCallbackLink caller, int value) {
try {
@@ -498,7 +531,7 @@
new Parcelable.Creator<SessionCallbackLink>() {
@Override
public SessionCallbackLink createFromParcel(Parcel in) {
- return new SessionCallbackLink(in);
+ return new SessionCallbackLink(in.readStrongBinder());
}
@Override
@@ -636,141 +669,290 @@
@Override
public void notifyCommand(String packageName, int pid, int uid,
ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) {
- mCallbackStub.onCommand(packageName, pid, uid, caller, command, args, cb);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onCommand(packageName, pid, uid, caller, command, args, cb);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void notifyMediaButton(String packageName, int pid, int uid,
Intent mediaButtonIntent, int sequenceNumber, ResultReceiver cb) {
- mCallbackStub.onMediaButton(packageName, pid, uid, mediaButtonIntent, sequenceNumber,
- cb);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onMediaButton(packageName, pid, uid, mediaButtonIntent,
+ sequenceNumber, cb);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void notifyMediaButtonFromController(String packageName, int pid, int uid,
ControllerCallbackLink caller, Intent mediaButtonIntent) {
- mCallbackStub.onMediaButtonFromController(packageName, pid, uid, caller,
- mediaButtonIntent);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onMediaButtonFromController(packageName, pid, uid, caller,
+ mediaButtonIntent);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void notifyPrepare(String packageName, int pid, int uid,
ControllerCallbackLink caller) {
- mCallbackStub.onPrepare(packageName, pid, uid, caller);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onPrepare(packageName, pid, uid, caller);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void notifyPrepareFromMediaId(String packageName, int pid, int uid,
ControllerCallbackLink caller, String mediaId, Bundle extras) {
- mCallbackStub.onPrepareFromMediaId(packageName, pid, uid, caller, mediaId, extras);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onPrepareFromMediaId(packageName, pid, uid, caller, mediaId, extras);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void notifyPrepareFromSearch(String packageName, int pid, int uid,
ControllerCallbackLink caller, String query, Bundle extras) {
- mCallbackStub.onPrepareFromSearch(packageName, pid, uid, caller, query, extras);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onPrepareFromSearch(packageName, pid, uid, caller, query, extras);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void notifyPrepareFromUri(String packageName, int pid, int uid,
ControllerCallbackLink caller, Uri uri, Bundle extras) {
- mCallbackStub.onPrepareFromUri(packageName, pid, uid, caller, uri, extras);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onPrepareFromUri(packageName, pid, uid, caller, uri, extras);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void notifyPlay(String packageName, int pid, int uid,
ControllerCallbackLink caller) {
- mCallbackStub.onPlay(packageName, pid, uid, caller);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onPlay(packageName, pid, uid, caller);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void notifyPlayFromMediaId(String packageName, int pid, int uid,
ControllerCallbackLink caller, String mediaId, Bundle extras) {
- mCallbackStub.onPlayFromMediaId(packageName, pid, uid, caller, mediaId, extras);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onPlayFromMediaId(packageName, pid, uid, caller, mediaId, extras);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void notifyPlayFromSearch(String packageName, int pid, int uid,
ControllerCallbackLink caller, String query, Bundle extras) {
- mCallbackStub.onPlayFromSearch(packageName, pid, uid, caller, query, extras);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onPlayFromSearch(packageName, pid, uid, caller, query, extras);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void notifyPlayFromUri(String packageName, int pid, int uid,
ControllerCallbackLink caller, Uri uri, Bundle extras) {
- mCallbackStub.onPlayFromUri(packageName, pid, uid, caller, uri, extras);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onPlayFromUri(packageName, pid, uid, caller, uri, extras);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void notifySkipToTrack(String packageName, int pid, int uid,
ControllerCallbackLink caller, long id) {
- mCallbackStub.onSkipToTrack(packageName, pid, uid, caller, id);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onSkipToTrack(packageName, pid, uid, caller, id);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void notifyPause(String packageName, int pid, int uid,
ControllerCallbackLink caller) {
- mCallbackStub.onPause(packageName, pid, uid, caller);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onPause(packageName, pid, uid, caller);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void notifyStop(String packageName, int pid, int uid,
ControllerCallbackLink caller) {
- mCallbackStub.onStop(packageName, pid, uid, caller);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onStop(packageName, pid, uid, caller);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void notifyNext(String packageName, int pid, int uid,
ControllerCallbackLink caller) {
- mCallbackStub.onNext(packageName, pid, uid, caller);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onNext(packageName, pid, uid, caller);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void notifyPrevious(String packageName, int pid, int uid,
ControllerCallbackLink caller) {
- mCallbackStub.onPrevious(packageName, pid, uid, caller);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onPrevious(packageName, pid, uid, caller);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void notifyFastForward(String packageName, int pid, int uid,
ControllerCallbackLink caller) {
- mCallbackStub.onFastForward(packageName, pid, uid, caller);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onFastForward(packageName, pid, uid, caller);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void notifyRewind(String packageName, int pid, int uid,
ControllerCallbackLink caller) {
- mCallbackStub.onRewind(packageName, pid, uid, caller);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onRewind(packageName, pid, uid, caller);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void notifySeekTo(String packageName, int pid, int uid,
ControllerCallbackLink caller, long pos) {
- mCallbackStub.onSeekTo(packageName, pid, uid, caller, pos);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onSeekTo(packageName, pid, uid, caller, pos);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void notifyRate(String packageName, int pid, int uid, ControllerCallbackLink caller,
Rating rating) {
- mCallbackStub.onRate(packageName, pid, uid, caller, rating);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onRate(packageName, pid, uid, caller, rating);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
- @Override
public void notifyCustomAction(String packageName, int pid, int uid,
ControllerCallbackLink caller, String action, Bundle args) {
- mCallbackStub.onCustomAction(packageName, pid, uid, caller, action, args);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onCustomAction(packageName, pid, uid, caller, action, args);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void notifyAdjustVolume(String packageName, int pid, int uid,
ControllerCallbackLink caller, int direction) {
- mCallbackStub.onAdjustVolume(packageName, pid, uid, caller, direction);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onAdjustVolume(packageName, pid, uid, caller, direction);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void notifySetVolumeTo(String packageName, int pid, int uid,
ControllerCallbackLink caller, int value) {
- mCallbackStub.onSetVolumeTo(packageName, pid, uid, caller, value);
+ ensureMediaControlPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCallbackStub.onSetVolumeTo(packageName, pid, uid, caller, value);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private void ensureMediaControlPermission() {
+ // Check if it's system server or has MEDIA_CONTENT_CONTROL.
+ // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
+ // check here.
+ if (getCallingUid() == Process.SYSTEM_UID || mContext.checkCallingPermission(
+ android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+ == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ throw new SecurityException("Must hold the MEDIA_CONTENT_CONTROL permission.");
}
}
}
diff --git a/media/java/android/media/session/SessionLink.aidl b/media/java/android/media/session/SessionLink.aidl
new file mode 100644
index 0000000..c3be23e
--- /dev/null
+++ b/media/java/android/media/session/SessionLink.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.media.session;
+
+parcelable SessionLink;
diff --git a/media/java/android/media/session/SessionLink.java b/media/java/android/media/session/SessionLink.java
new file mode 100644
index 0000000..0da0a5a
--- /dev/null
+++ b/media/java/android/media/session/SessionLink.java
@@ -0,0 +1,454 @@
+/*
+ * 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.media.session;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.PendingIntent;
+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;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import java.util.List;
+
+/**
+ * Handles incoming commands from {@link MediaSession}.
+ * @hide
+ */
+@SystemApi
+public final class SessionLink implements Parcelable {
+ public static final Parcelable.Creator<SessionLink> CREATOR =
+ new Parcelable.Creator<SessionLink>() {
+ @Override
+ public SessionLink createFromParcel(Parcel in) {
+ return new SessionLink(in.readStrongBinder());
+ }
+
+ @Override
+ public SessionLink[] newArray(int size) {
+ return new SessionLink[size];
+ }
+ };
+
+ final SessionStub mSessionStub;
+ final ISession mISession;
+
+ /**
+ * Constructor for stub (Callee)
+ */
+ public SessionLink(@NonNull SessionStub sessionStub) {
+ mSessionStub = sessionStub;
+ mISession = new StubProxy();
+ }
+
+ /**
+ * Constructor for interface (Caller)
+ */
+ public SessionLink(IBinder binder) {
+ mSessionStub = null;
+ mISession = ISession.Stub.asInterface(binder);
+ }
+
+ /**
+ * Tell system that the session sends an event to all the connected controllers.
+ *
+ * @param event the name of the event
+ * @param extras the extras included with the event
+ */
+ void sendEvent(@NonNull String event, @Nullable Bundle extras) {
+ try {
+ mISession.sendEvent(event, extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Gets the controller link from the system.
+ */
+ @NonNull
+ ControllerLink getController() {
+ try {
+ return mISession.getController();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that the session sets the flags.
+ *
+ * @param flags the new session flags
+ */
+ void setFlags(int flags) {
+ try {
+ mISession.setFlags(flags);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that the session is (in)active.
+ *
+ * @param active the new activeness state
+ */
+ void setActive(boolean active) {
+ try {
+ mISession.setActive(active);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that the session sets the media button receiver.
+ *
+ * @param mbr the pending intent for media button receiver
+ */
+ void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
+ try {
+ mISession.setMediaButtonReceiver(mbr);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that the session sets the pending intent for launching UI.
+ *
+ * @param pi the pending intent for launching UI
+ */
+ void setLaunchPendingIntent(@Nullable PendingIntent pi) {
+ try {
+ mISession.setLaunchPendingIntent(pi);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that the session is destroyed.
+ */
+ void destroySession() {
+ try {
+ mISession.destroySession();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that the session sets the new metadata.
+ *
+ * @param metadata the new metadata
+ * @param duration the duration of the media in milliseconds
+ * @param metadataDescription the description of the metadata
+ */
+ void setMetadata(@Nullable MediaMetadata metadata, long duration,
+ @Nullable String metadataDescription) {
+ try {
+ mISession.setMetadata(metadata, duration, metadataDescription);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that the session sets the new playback state.
+ *
+ * @param state the new playback state
+ */
+ void setPlaybackState(@Nullable PlaybackState state) {
+ try {
+ mISession.setPlaybackState(state);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that the session sets the new queue.
+ *
+ * @param queue the new queue
+ */
+ void setQueue(@Nullable List<QueueItem> queue) {
+ try {
+ mISession.setQueue(queue == null ? null : new MediaParceledListSlice(queue));
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that the session sets the new queue title.
+ *
+ * @param title the new queue title
+ */
+ void setQueueTitle(@Nullable CharSequence title) {
+ try {
+ mISession.setQueueTitle(title);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that the session sets the new extras.
+ *
+ * @param extras the new extras
+ */
+ void setExtras(@Nullable Bundle extras) {
+ try {
+ mISession.setExtras(extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that the session sets the new rating type of the current media.
+ *
+ * @param type the rating type.
+ */
+ void setRatingType(@Rating.Style int type) {
+ try {
+ mISession.setRatingType(type);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that the session represents a local playback.
+ *
+ * @param attributes the audio attributes of the local playback.
+ */
+ void setPlaybackToLocal(@NonNull AudioAttributes attributes) {
+ try {
+ mISession.setPlaybackToLocal(attributes);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that the session represents a remote playback.
+ *
+ * @param control the volume control type
+ * @param max the max volume
+ */
+ void setPlaybackToRemote(@VolumeProvider.ControlType int control, int max) {
+ try {
+ mISession.setPlaybackToRemote(control, max);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tell system that the session sets the new current volume.
+ *
+ * @param currentVolume the new current volume
+ */
+ void setCurrentVolume(int currentVolume) {
+ try {
+ mISession.setCurrentVolume(currentVolume);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** Gets the binder */
+ @NonNull
+ public IBinder getBinder() {
+ return mISession.asBinder();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(mISession.asBinder());
+ }
+
+ /**
+ * Class for Stub implementation
+ */
+ public abstract static class SessionStub {
+ /** Stub method for ISession.sendEvent */
+ public void sendEvent(@NonNull String event, @Nullable Bundle data) {
+ }
+
+ /** Stub method for ISession.getController */
+ @NonNull
+ public ControllerLink getController() {
+ return null;
+ }
+
+ /** Stub method for ISession.setFlags */
+ public void setFlags(int flags) {
+ }
+
+ /** Stub method for ISession.setActive */
+ public void setActive(boolean active) {
+ }
+
+ /** Stub method for ISession.setMediaButtonReceiver */
+ public void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
+ }
+
+ /** Stub method for ISession.setLaunchPendingIntent */
+ public void setLaunchPendingIntent(@Nullable PendingIntent pi) {
+ }
+
+ /** Stub method for ISession.destroySession */
+ public void destroySession() {
+ }
+
+ /** Stub method for ISession.setMetadata */
+ public void setMetadata(@Nullable MediaMetadata metadata, long duration,
+ @Nullable String metadataDescription) {
+ }
+
+ /** Stub method for ISession.setPlaybackState */
+ public void setPlaybackState(@Nullable PlaybackState state) {
+ }
+
+ /** Stub method for ISession.setQueue */
+ public void setQueue(@Nullable List<QueueItem> queue) {
+ }
+
+ /** Stub method for ISession.setQueueTitle */
+ public void setQueueTitle(@Nullable CharSequence title) {
+ }
+
+ /** Stub method for ISession.setExtras */
+ public void setExtras(@Nullable Bundle extras) {
+ }
+
+ /** Stub method for ISession.setRatingType */
+ public void setRatingType(int type) {
+ }
+
+ /** Stub method for ISession.setPlaybackToLocal */
+ public void setPlaybackToLocal(@NonNull AudioAttributes attributes) {
+ }
+
+ /** Stub method for ISession.setPlaybackToRemote */
+ public void setPlaybackToRemote(int control, int max) {
+ }
+
+ /** Stub method for ISession.setCurrentVolume */
+ public void setCurrentVolume(int currentVolume) {
+ }
+ }
+
+ private class StubProxy extends ISession.Stub {
+ @Override
+ public void sendEvent(String event, Bundle data) {
+ mSessionStub.sendEvent(event, data);
+ }
+
+ @Override
+ public ControllerLink getController() {
+ return mSessionStub.getController();
+ }
+
+ @Override
+ public void setFlags(int flags) {
+ mSessionStub.setFlags(flags);
+ }
+
+ @Override
+ public void setActive(boolean active) {
+ mSessionStub.setActive(active);
+ }
+
+ @Override
+ public void setMediaButtonReceiver(PendingIntent mbr) {
+ mSessionStub.setMediaButtonReceiver(mbr);
+ }
+
+ @Override
+ public void setLaunchPendingIntent(PendingIntent pi) {
+ mSessionStub.setLaunchPendingIntent(pi);
+ }
+
+ @Override
+ public void destroySession() {
+ mSessionStub.destroySession();
+ }
+
+ @Override
+ public void setMetadata(MediaMetadata metadata, long duration, String metadataDescription) {
+ mSessionStub.setMetadata(metadata, duration, metadataDescription);
+ }
+
+ @Override
+ public void setPlaybackState(PlaybackState state) {
+ mSessionStub.setPlaybackState(state);
+ }
+
+ @Override
+ public void setQueue(MediaParceledListSlice queue) {
+ mSessionStub.setQueue(queue == null ? null : queue.getList());
+ }
+
+ @Override
+ public void setQueueTitle(CharSequence title) {
+ mSessionStub.setQueueTitle(title);
+ }
+
+ @Override
+ public void setExtras(Bundle extras) {
+ mSessionStub.setExtras(extras);
+ }
+
+ @Override
+ public void setRatingType(int type) {
+ mSessionStub.setRatingType(type);
+ }
+
+ @Override
+ public void setPlaybackToLocal(AudioAttributes attributes) {
+ mSessionStub.setPlaybackToLocal(attributes);
+ }
+
+ @Override
+ public void setPlaybackToRemote(int control, int max) {
+ mSessionStub.setPlaybackToRemote(control, max);
+ }
+
+ @Override
+ public void setCurrentVolume(int currentVolume) {
+ mSessionStub.setCurrentVolume(currentVolume);
+ }
+ }
+}
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 5cb8fb8..30907a5 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -33,7 +33,10 @@
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiUtils;
+import android.hardware.hdmi.HdmiUtils.HdmiAddressRelativePosition;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
@@ -49,7 +52,6 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Retention;
@@ -145,6 +147,8 @@
// Attributes specific to HDMI
private final HdmiDeviceInfo mHdmiDeviceInfo;
private final boolean mIsConnectedToHdmiSwitch;
+ @HdmiAddressRelativePosition
+ private final int mHdmiConnectionRelativePosition;
private final String mParentId;
private final Bundle mExtras;
@@ -260,7 +264,9 @@
private TvInputInfo(ResolveInfo service, String id, int type, boolean isHardwareInput,
CharSequence label, int labelResId, Icon icon, Icon iconStandby, Icon iconDisconnected,
String setupActivity, boolean canRecord, int tunerCount, HdmiDeviceInfo hdmiDeviceInfo,
- boolean isConnectedToHdmiSwitch, String parentId, Bundle extras) {
+ boolean isConnectedToHdmiSwitch,
+ @HdmiAddressRelativePosition int hdmiConnectionRelativePosition, String parentId,
+ Bundle extras) {
mService = service;
mId = id;
mType = type;
@@ -275,6 +281,7 @@
mTunerCount = tunerCount;
mHdmiDeviceInfo = hdmiDeviceInfo;
mIsConnectedToHdmiSwitch = isConnectedToHdmiSwitch;
+ mHdmiConnectionRelativePosition = hdmiConnectionRelativePosition;
mParentId = parentId;
mExtras = extras;
}
@@ -419,6 +426,7 @@
/**
* Returns {@code true}, if a CEC device for this TV input is connected to an HDMI switch, i.e.,
* the device isn't directly connected to a HDMI port.
+ * TODO(b/110094868): add @Deprecated for Q
* @hide
*/
@SystemApi
@@ -427,6 +435,16 @@
}
/**
+ * Returns the relative position of this HDMI input.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ @HdmiAddressRelativePosition
+ public int getHdmiConnectionRelativePosition() {
+ return mHdmiConnectionRelativePosition;
+ }
+
+ /**
* Checks if this TV input is marked hidden by the user in the settings.
*
* @param context Supplies a {@link Context} used to check if this TV input is hidden.
@@ -555,6 +573,7 @@
&& mTunerCount == obj.mTunerCount
&& Objects.equals(mHdmiDeviceInfo, obj.mHdmiDeviceInfo)
&& mIsConnectedToHdmiSwitch == obj.mIsConnectedToHdmiSwitch
+ && mHdmiConnectionRelativePosition == obj.mHdmiConnectionRelativePosition
&& TextUtils.equals(mParentId, obj.mParentId)
&& Objects.equals(mExtras, obj.mExtras);
}
@@ -589,6 +608,7 @@
dest.writeInt(mTunerCount);
dest.writeParcelable(mHdmiDeviceInfo, flags);
dest.writeByte(mIsConnectedToHdmiSwitch ? (byte) 1 : 0);
+ dest.writeInt(mHdmiConnectionRelativePosition);
dest.writeString(mParentId);
dest.writeBundle(mExtras);
}
@@ -630,6 +650,7 @@
mTunerCount = in.readInt();
mHdmiDeviceInfo = in.readParcelable(null);
mIsConnectedToHdmiSwitch = in.readByte() == 1;
+ mHdmiConnectionRelativePosition = in.readInt();
mParentId = in.readString();
mExtras = in.readBundle();
}
@@ -883,12 +904,17 @@
int type;
boolean isHardwareInput = false;
boolean isConnectedToHdmiSwitch = false;
+ @HdmiAddressRelativePosition
+ int hdmiConnectionRelativePosition = HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN;
if (mHdmiDeviceInfo != null) {
id = generateInputId(componentName, mHdmiDeviceInfo);
type = TYPE_HDMI;
isHardwareInput = true;
- isConnectedToHdmiSwitch = (mHdmiDeviceInfo.getPhysicalAddress() & 0x0FFF) != 0;
+ hdmiConnectionRelativePosition = getRelativePosition(mContext, mHdmiDeviceInfo);
+ isConnectedToHdmiSwitch =
+ hdmiConnectionRelativePosition
+ != HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW;
} else if (mTvInputHardwareInfo != null) {
id = generateInputId(componentName, mTvInputHardwareInfo);
type = sHardwareTypeToTvInputType.get(mTvInputHardwareInfo.getType(), TYPE_TUNER);
@@ -901,7 +927,8 @@
return new TvInputInfo(mResolveInfo, id, type, isHardwareInput, mLabel, mLabelResId,
mIcon, mIconStandby, mIconDisconnected, mSetupActivity,
mCanRecord == null ? false : mCanRecord, mTunerCount == null ? 0 : mTunerCount,
- mHdmiDeviceInfo, isConnectedToHdmiSwitch, mParentId, mExtras);
+ mHdmiDeviceInfo, isConnectedToHdmiSwitch, hdmiConnectionRelativePosition,
+ mParentId, mExtras);
}
private static String generateInputId(ComponentName name) {
@@ -923,6 +950,16 @@
+ tvInputHardwareInfo.getDeviceId();
}
+ private static int getRelativePosition(Context context, HdmiDeviceInfo info) {
+ HdmiControlManager hcm =
+ (HdmiControlManager) context.getSystemService(Context.HDMI_CONTROL_SERVICE);
+ if (hcm == null) {
+ return HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN;
+ }
+ return HdmiUtils.getHdmiAddressRelativePosition(
+ info.getPhysicalAddress(), hcm.getPhysicalAddress());
+ }
+
private void parseServiceMetadata(int inputType) {
ServiceInfo si = mResolveInfo.serviceInfo;
PackageManager pm = mContext.getPackageManager();
diff --git a/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl b/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
index deeab1a..8dc480d 100644
--- a/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
+++ b/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
@@ -2,8 +2,8 @@
package android.service.media;
-import android.content.pm.ParceledListSlice;
import android.graphics.Bitmap;
+import android.media.MediaParceledListSlice;
import android.media.session.MediaSession;
import android.os.Bundle;
@@ -22,6 +22,7 @@
*/
void onConnect(String root, in MediaSession.Token session, in Bundle extras);
void onConnectFailed();
- void onLoadChildren(String mediaId, in ParceledListSlice list);
- void onLoadChildrenWithOptions(String mediaId, in ParceledListSlice list, in Bundle options);
+ void onLoadChildren(String mediaId, in MediaParceledListSlice list);
+ void onLoadChildrenWithOptions(String mediaId, in MediaParceledListSlice list,
+ in Bundle options);
}
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index 5a60ac1..2fbc699 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -25,21 +25,18 @@
import android.app.Service;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.content.pm.ParceledListSlice;
+import android.media.MediaParceledListSlice;
import android.media.browse.MediaBrowser;
import android.media.browse.MediaBrowserUtils;
import android.media.session.MediaSession;
+import android.media.session.MediaSessionManager;
+import android.media.session.MediaSessionManager.RemoteUserInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
-import android.media.session.MediaSessionManager;
-import android.media.session.MediaSessionManager.RemoteUserInfo;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
-import android.service.media.IMediaBrowserService;
-import android.service.media.IMediaBrowserServiceCallbacks;
-import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
@@ -544,8 +541,7 @@
throw new IllegalStateException("This should be called inside of onGetRoot or"
+ " onLoadChildren or onLoadItem methods");
}
- return new RemoteUserInfo(mCurConnection.pkg, mCurConnection.pid, mCurConnection.uid,
- mCurConnection.callbacks.asBinder());
+ return new RemoteUserInfo(mCurConnection.pkg, mCurConnection.pid, mCurConnection.uid);
}
/**
@@ -672,8 +668,8 @@
*/
private void performLoadChildren(final String parentId, final ConnectionRecord connection,
final Bundle options) {
- final Result<List<MediaBrowser.MediaItem>> result
- = new Result<List<MediaBrowser.MediaItem>>(parentId) {
+ final Result<List<MediaBrowser.MediaItem>> result =
+ new Result<List<MediaBrowser.MediaItem>>(parentId) {
@Override
void onResultSent(List<MediaBrowser.MediaItem> list, @ResultFlags int flag) {
if (mConnections.get(connection.callbacks.asBinder()) != connection) {
@@ -686,9 +682,9 @@
List<MediaBrowser.MediaItem> filteredList =
(flag & RESULT_FLAG_OPTION_NOT_HANDLED) != 0
- ? applyOptions(list, options) : list;
- final ParceledListSlice<MediaBrowser.MediaItem> pls =
- filteredList == null ? null : new ParceledListSlice<>(filteredList);
+ ? applyOptions(list, options) : list;
+ final MediaParceledListSlice<MediaBrowser.MediaItem> pls =
+ filteredList == null ? null : new MediaParceledListSlice<>(filteredList);
try {
connection.callbacks.onLoadChildrenWithOptions(parentId, pls, options);
} catch (RemoteException ex) {
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 0198470..48dbf55 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -84,7 +84,7 @@
}
cc_library_shared {
- name: "libmediaplayer2_jni",
+ name: "libmedia2_jni",
srcs: [
"android_media_DataSourceCallback.cpp",
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
deleted file mode 100644
index 1d7d6de..0000000
--- a/media/jni/Android.mk
+++ /dev/null
@@ -1,30 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_SRC_FILES := \
- Media2Jni.cpp \
-
-# TODO: Move libmedia2_jni from system to media apex. Currently, libraries defined in
-# Android.mk is not visible in apex build.
-LOCAL_MODULE:= libmedia2_jni
-LOCAL_SHARED_LIBRARIES := libdl liblog
-
-sanitizer_runtime_libraries := $(call normalize-path-list,$(addsuffix .so,\
- $(ADDRESS_SANITIZER_RUNTIME_LIBRARY) \
- $(UBSAN_RUNTIME_LIBRARY) \
- $(TSAN_RUNTIME_LIBRARY)))
-
-# $(info Sanitizer: $(sanitizer_runtime_libraries))
-
-ndk_libraries := $(call normalize-path-list,$(addprefix lib,$(addsuffix .so,\
- $(NDK_PREBUILT_SHARED_LIBRARIES))))
-
-# $(info NDK: $(ndk_libraries))
-
-LOCAL_CFLAGS += -DLINKED_LIBRARIES='"$(sanitizer_runtime_libraries):$(ndk_libraries)"'
-
-sanitizer_runtime_libraries :=
-ndk_libraries :=
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/media/jni/Media2Jni.cpp b/media/jni/Media2Jni.cpp
deleted file mode 100644
index 6c0a65c..0000000
--- a/media/jni/Media2Jni.cpp
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
-**
-** 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.
-*/
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "MediaPlayer2-JNI"
-#include "utils/Log.h"
-
-#include "jni.h"
-#include <android/dlext.h>
-#include <dirent.h>
-#include <dlfcn.h>
-#include <errno.h>
-#include <string.h>
-
-extern "C" {
- // Copied from GraphicsEnv.cpp
- // TODO(b/37049319) Get this from a header once one exists
- android_namespace_t* android_create_namespace(const char* name,
- const char* ld_library_path,
- const char* default_library_path,
- uint64_t type,
- const char* permitted_when_isolated_path,
- android_namespace_t* parent);
- bool android_link_namespaces(android_namespace_t* from,
- android_namespace_t* to,
- const char* shared_libs_sonames);
- enum {
- ANDROID_NAMESPACE_TYPE_ISOLATED = 1,
- };
-
-} // extern "C"
-
-static const char kApexLibPath[] = "/apex/com.android.media/lib"
-#ifdef __LP64__
- "64"
-#endif
- "/";
-static const char kMediaPlayer2LibPath[] = "/apex/com.android.media/lib"
-#ifdef __LP64__
- "64"
-#endif
- "/libmediaplayer2_jni.so";
-
-typedef jint (*Media2JniOnLoad)(JavaVM*, void*);
-
-JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
-{
- android_namespace_t *media2Ns = android_create_namespace("media2",
- nullptr, // ld_library_path
- kApexLibPath,
- ANDROID_NAMESPACE_TYPE_ISOLATED,
- nullptr, // permitted_when_isolated_path
- nullptr); // parent
- if (!android_link_namespaces(media2Ns, nullptr, LINKED_LIBRARIES)) {
- ALOGE("Failed to link namespace. Failed to load extractor plug-ins in apex.");
- return -1;
- }
- const android_dlextinfo dlextinfo = {
- .flags = ANDROID_DLEXT_USE_NAMESPACE,
- .library_namespace = media2Ns,
- };
- // load libmediaplayer2_jni and call JNI_OnLoad.
- void *libHandle = android_dlopen_ext(kMediaPlayer2LibPath, RTLD_NOW | RTLD_LOCAL, &dlextinfo);
- if (libHandle == NULL) {
- ALOGW("couldn't dlopen(%s) %s", kMediaPlayer2LibPath, strerror(errno));
- return -1;
- }
- Media2JniOnLoad media2JniOnLoad = (Media2JniOnLoad) dlsym(libHandle, "JNI_OnLoad");
- if (!media2JniOnLoad) {
- ALOGW("%s does not contain JNI_OnLoad()", kMediaPlayer2LibPath);
- dlclose(libHandle);
- return -1;
- }
- return media2JniOnLoad(vm, reserved);
-}
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 42c5b05..81fce8a 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -542,14 +542,15 @@
// static
-bool JDrm::IsCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType) {
+bool JDrm::IsCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType,
+ DrmPlugin::SecurityLevel securityLevel) {
sp<IDrm> drm = MakeDrm();
if (drm == NULL) {
return false;
}
- return drm->isCryptoSchemeSupported(uuid, mimeType);
+ return drm->isCryptoSchemeSupported(uuid, mimeType, securityLevel);
}
status_t JDrm::initCheck() const {
@@ -930,8 +931,30 @@
setDrm(env, thiz, drm);
}
+DrmPlugin::SecurityLevel jintToSecurityLevel(jint jlevel) {
+ DrmPlugin::SecurityLevel level;
+
+ if (jlevel == gSecurityLevels.kSecurityLevelMax) {
+ level = DrmPlugin::kSecurityLevelMax;
+ } else if (jlevel == gSecurityLevels.kSecurityLevelSwSecureCrypto) {
+ level = DrmPlugin::kSecurityLevelSwSecureCrypto;
+ } else if (jlevel == gSecurityLevels.kSecurityLevelSwSecureDecode) {
+ level = DrmPlugin::kSecurityLevelSwSecureDecode;
+ } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureCrypto) {
+ level = DrmPlugin::kSecurityLevelHwSecureCrypto;
+ } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureDecode) {
+ level = DrmPlugin::kSecurityLevelHwSecureDecode;
+ } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureAll) {
+ level = DrmPlugin::kSecurityLevelHwSecureAll;
+ } else {
+ level = DrmPlugin::kSecurityLevelUnknown;
+ }
+ return level;
+}
+
static jboolean android_media_MediaDrm_isCryptoSchemeSupportedNative(
- JNIEnv *env, jobject /* thiz */, jbyteArray uuidObj, jstring jmimeType) {
+ JNIEnv *env, jobject /* thiz */, jbyteArray uuidObj, jstring jmimeType,
+ jint jSecurityLevel) {
if (uuidObj == NULL) {
jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
@@ -952,8 +975,9 @@
if (jmimeType != NULL) {
mimeType = JStringToString8(env, jmimeType);
}
+ DrmPlugin::SecurityLevel securityLevel = jintToSecurityLevel(jSecurityLevel);
- return JDrm::IsCryptoSchemeSupported(uuid.array(), mimeType);
+ return JDrm::IsCryptoSchemeSupported(uuid.array(), mimeType, securityLevel);
}
static jbyteArray android_media_MediaDrm_openSession(
@@ -965,21 +989,8 @@
}
Vector<uint8_t> sessionId;
- DrmPlugin::SecurityLevel level;
-
- if (jlevel == gSecurityLevels.kSecurityLevelMax) {
- level = DrmPlugin::kSecurityLevelMax;
- } else if (jlevel == gSecurityLevels.kSecurityLevelSwSecureCrypto) {
- level = DrmPlugin::kSecurityLevelSwSecureCrypto;
- } else if (jlevel == gSecurityLevels.kSecurityLevelSwSecureDecode) {
- level = DrmPlugin::kSecurityLevelSwSecureDecode;
- } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureCrypto) {
- level = DrmPlugin::kSecurityLevelHwSecureCrypto;
- } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureDecode) {
- level = DrmPlugin::kSecurityLevelHwSecureDecode;
- } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureAll) {
- level = DrmPlugin::kSecurityLevelHwSecureAll;
- } else {
+ DrmPlugin::SecurityLevel level = jintToSecurityLevel(jlevel);
+ if (level == DrmPlugin::kSecurityLevelUnknown) {
jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid security level");
return NULL;
}
@@ -1903,7 +1914,7 @@
{ "native_setup", "(Ljava/lang/Object;[BLjava/lang/String;)V",
(void *)android_media_MediaDrm_native_setup },
- { "isCryptoSchemeSupportedNative", "([BLjava/lang/String;)Z",
+ { "isCryptoSchemeSupportedNative", "([BLjava/lang/String;I)Z",
(void *)android_media_MediaDrm_isCryptoSchemeSupportedNative },
{ "openSession", "(I)[B",
diff --git a/media/jni/android_media_MediaDrm.h b/media/jni/android_media_MediaDrm.h
index b9356f3..9338861 100644
--- a/media/jni/android_media_MediaDrm.h
+++ b/media/jni/android_media_MediaDrm.h
@@ -37,7 +37,9 @@
};
struct JDrm : public BnDrmClient {
- static bool IsCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType);
+ static bool IsCryptoSchemeSupported(const uint8_t uuid[16],
+ const String8 &mimeType,
+ DrmPlugin::SecurityLevel level);
JDrm(JNIEnv *env, jobject thiz, const uint8_t uuid[16], const String8 &appPackageName);
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 73d4c45..7c1af4a 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -56,6 +56,7 @@
shared_libs: [
"liblog",
+ "libhidlbase",
"libcutils",
"libandroidfw",
"libinput",
@@ -70,6 +71,8 @@
"libnetd_client",
"libhwui",
"libxml2",
+ "android.hardware.configstore@1.0",
+ "android.hardware.configstore-utils",
],
static_libs: [
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 8be8eda..8b45af0 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -172,6 +172,7 @@
ASensorEventQueue_hasEvents;
ASensorEventQueue_registerSensor; # introduced=26
ASensorEventQueue_setEventRate;
+ ASensorEventQueue_requestAdditionalInfoEvents; # introduced=29
ASensorManager_configureDirectReport; # introduced=26
ASensorManager_createEventQueue;
ASensorManager_createHardwareBufferDirectChannel; # introduced=26
@@ -185,6 +186,7 @@
ASensorManager_getSensorList;
ASensor_getFifoMaxEventCount; # introduced=21
ASensor_getFifoReservedEventCount; # introduced=21
+ ASensor_getHandle; # introduced=29
ASensor_getHighestDirectReportRateLevel; # introduced=26
ASensor_getMinDelay;
ASensor_getName;
@@ -207,7 +209,7 @@
AStorageManager_unmountObb;
ASurfaceControl_create; # introduced=29
ASurfaceControl_createFromWindow; # introduced=29
- ASurfaceControl_destroy; # introduced=29
+ ASurfaceControl_release; # introduced=29
ASurfaceTexture_acquireANativeWindow; # introduced=28
ASurfaceTexture_attachToGLContext; # introduced=28
ASurfaceTexture_detachFromGLContext; # introduced=28
@@ -216,13 +218,24 @@
ASurfaceTexture_getTransformMatrix; # introduced=28
ASurfaceTexture_release; # introduced=28
ASurfaceTexture_updateTexImage; # introduced=28
+ ASurfaceTransactionStats_getAcquireTime; # introduced=29
+ ASurfaceTransactionStats_getASurfaceControls; # introduced=29
+ ASurfaceTransactionStats_getLatchTime; # introduced=29
+ ASurfaceTransactionStats_getPresentFenceFd; # introduced=29
+ ASurfaceTransactionStats_getPreviousReleaseFenceFd; # introduced=29
+ ASurfaceTransactionStats_releaseASurfaceControls; # introduced=29
ASurfaceTransaction_apply; # introduced=29
ASurfaceTransaction_create; # introduced=29
ASurfaceTransaction_delete; # introduced=29
+ ASurfaceTransaction_reparent; # introduced=29
ASurfaceTransaction_setBuffer; # introduced=29
+ ASurfaceTransaction_setBufferAlpha; # introduced=29
ASurfaceTransaction_setBufferTransparency; # introduced=29
ASurfaceTransaction_setDamageRegion; # introduced=29
+ ASurfaceTransaction_setDesiredPresentTime; # introduced=29
ASurfaceTransaction_setGeometry; # introduced=29
+ ASurfaceTransaction_setHdrMetadata_cta861_3; # introduced=29
+ ASurfaceTransaction_setHdrMetadata_smpte2086; # introduced=29
ASurfaceTransaction_setOnComplete; # introduced=29
ASurfaceTransaction_setVisibility; # introduced=29
ASurfaceTransaction_setZOrder; # introduced=29
diff --git a/native/android/sensor.cpp b/native/android/sensor.cpp
index c3b2e25..63082fd 100644
--- a/native/android/sensor.cpp
+++ b/native/android/sensor.cpp
@@ -115,6 +115,7 @@
if (queue != 0) {
ALooper_addFd(looper, queue->getFd(), ident, ALOOPER_EVENT_INPUT, callback, data);
queue->looper = looper;
+ queue->requestAdditionalInfo = false;
queue->incStrong(manager);
}
return static_cast<ASensorEventQueue*>(queue.get());
@@ -274,11 +275,19 @@
return android::BAD_VALUE;
}
- ssize_t actual = static_cast<SensorEventQueue*>(queue)->read(events, count);
+ SensorEventQueue* sensorQueue = static_cast<SensorEventQueue*>(queue);
+ ssize_t actual = sensorQueue->read(events, count);
if (actual > 0) {
- static_cast<SensorEventQueue*>(queue)->sendAck(events, actual);
+ sensorQueue->sendAck(events, actual);
}
- return actual;
+
+ return sensorQueue->filterEvents(events, actual);
+}
+
+int ASensorEventQueue_requestAdditionalInfoEvents(ASensorEventQueue* queue, bool enable) {
+ RETURN_IF_QUEUE_IS_NULL(android::BAD_VALUE);
+ queue->requestAdditionalInfo = enable;
+ return android::OK;
}
/*****************************************************************************/
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index ead5b0b..f0100a9 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -14,14 +14,26 @@
* limitations under the License.
*/
+#include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
#include <android/native_window.h>
#include <android/surface_control.h>
+#include <configstore/Utils.h>
+
+#include <gui/HdrMetadata.h>
+#include <gui/ISurfaceComposer.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
#include <gui/SurfaceControl.h>
+#include <ui/HdrCapabilities.h>
+
+#include <utils/Timers.h>
+
+using namespace android::hardware::configstore;
+using namespace android::hardware::configstore::V1_0;
using namespace android;
+using android::hardware::configstore::V1_0::ISurfaceFlingerConfigs;
using Transaction = SurfaceComposerClient::Transaction;
@@ -95,10 +107,9 @@
return reinterpret_cast<ASurfaceControl*>(surfaceControl.get());
}
-void ASurfaceControl_destroy(ASurfaceControl* aSurfaceControl) {
+void ASurfaceControl_release(ASurfaceControl* aSurfaceControl) {
sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
- Transaction().reparent(surfaceControl, nullptr).apply();
SurfaceControl_release(surfaceControl.get());
}
@@ -120,6 +131,86 @@
transaction->apply();
}
+typedef struct ASurfaceControlStats {
+ int64_t acquireTime;
+ sp<Fence> previousReleaseFence;
+} ASurfaceControlStats;
+
+struct ASurfaceTransactionStats {
+ std::unordered_map<ASurfaceControl*, ASurfaceControlStats> aSurfaceControlStats;
+ int64_t latchTime;
+ sp<Fence> presentFence;
+};
+
+int64_t ASurfaceTransactionStats_getLatchTime(ASurfaceTransactionStats* aSurfaceTransactionStats) {
+ CHECK_NOT_NULL(aSurfaceTransactionStats);
+ return aSurfaceTransactionStats->latchTime;
+}
+
+int ASurfaceTransactionStats_getPresentFenceFd(ASurfaceTransactionStats* aSurfaceTransactionStats) {
+ CHECK_NOT_NULL(aSurfaceTransactionStats);
+ auto& presentFence = aSurfaceTransactionStats->presentFence;
+ return (presentFence) ? presentFence->dup() : -1;
+}
+
+void ASurfaceTransactionStats_getASurfaceControls(ASurfaceTransactionStats* aSurfaceTransactionStats,
+ ASurfaceControl*** outASurfaceControls,
+ size_t* outASurfaceControlsSize) {
+ CHECK_NOT_NULL(aSurfaceTransactionStats);
+ CHECK_NOT_NULL(outASurfaceControls);
+ CHECK_NOT_NULL(outASurfaceControlsSize);
+
+ size_t size = aSurfaceTransactionStats->aSurfaceControlStats.size();
+
+ SurfaceControl** surfaceControls = new SurfaceControl*[size];
+ ASurfaceControl** aSurfaceControls = reinterpret_cast<ASurfaceControl**>(surfaceControls);
+
+ size_t i = 0;
+ for (auto& [aSurfaceControl, aSurfaceControlStats] : aSurfaceTransactionStats->aSurfaceControlStats) {
+ aSurfaceControls[i] = aSurfaceControl;
+ i++;
+ }
+
+ *outASurfaceControls = aSurfaceControls;
+ *outASurfaceControlsSize = size;
+}
+
+int64_t ASurfaceTransactionStats_getAcquireTime(ASurfaceTransactionStats* aSurfaceTransactionStats,
+ ASurfaceControl* aSurfaceControl) {
+ CHECK_NOT_NULL(aSurfaceTransactionStats);
+ CHECK_NOT_NULL(aSurfaceControl);
+
+ const auto& aSurfaceControlStats =
+ aSurfaceTransactionStats->aSurfaceControlStats.find(aSurfaceControl);
+ LOG_ALWAYS_FATAL_IF(
+ aSurfaceControlStats == aSurfaceTransactionStats->aSurfaceControlStats.end(),
+ "ASurfaceControl not found");
+
+ return aSurfaceControlStats->second.acquireTime;
+}
+
+int ASurfaceTransactionStats_getPreviousReleaseFenceFd(
+ ASurfaceTransactionStats* aSurfaceTransactionStats, ASurfaceControl* aSurfaceControl) {
+ CHECK_NOT_NULL(aSurfaceTransactionStats);
+ CHECK_NOT_NULL(aSurfaceControl);
+
+ const auto& aSurfaceControlStats =
+ aSurfaceTransactionStats->aSurfaceControlStats.find(aSurfaceControl);
+ LOG_ALWAYS_FATAL_IF(
+ aSurfaceControlStats == aSurfaceTransactionStats->aSurfaceControlStats.end(),
+ "ASurfaceControl not found");
+
+ auto& previousReleaseFence = aSurfaceControlStats->second.previousReleaseFence;
+ return (previousReleaseFence) ? previousReleaseFence->dup() : -1;
+}
+
+void ASurfaceTransactionStats_releaseASurfaceControls(ASurfaceControl** aSurfaceControls) {
+ CHECK_NOT_NULL(aSurfaceControls);
+
+ SurfaceControl** surfaceControls = reinterpret_cast<SurfaceControl**>(aSurfaceControls);
+ delete[] surfaceControls;
+}
+
void ASurfaceTransaction_setOnComplete(ASurfaceTransaction* aSurfaceTransaction, void* context,
ASurfaceTransaction_OnComplete func) {
CHECK_NOT_NULL(aSurfaceTransaction);
@@ -127,9 +218,23 @@
CHECK_NOT_NULL(func);
TransactionCompletedCallbackTakesContext callback = [func](void* callback_context,
- const TransactionStats& stats) {
- int fence = (stats.presentFence) ? stats.presentFence->dup() : -1;
- (*func)(callback_context, fence);
+ nsecs_t latchTime,
+ const sp<Fence>& presentFence,
+ const std::vector<SurfaceControlStats>& surfaceControlStats) {
+ ASurfaceTransactionStats aSurfaceTransactionStats;
+
+ aSurfaceTransactionStats.latchTime = latchTime;
+ aSurfaceTransactionStats.presentFence = presentFence;
+
+ auto& aSurfaceControlStats = aSurfaceTransactionStats.aSurfaceControlStats;
+
+ for (const auto& [surfaceControl, acquireTime, previousReleaseFence] : surfaceControlStats) {
+ ASurfaceControl* aSurfaceControl = reinterpret_cast<ASurfaceControl*>(surfaceControl.get());
+ aSurfaceControlStats[aSurfaceControl].acquireTime = acquireTime;
+ aSurfaceControlStats[aSurfaceControl].previousReleaseFence = previousReleaseFence;
+ }
+
+ (*func)(callback_context, &aSurfaceTransactionStats);
};
Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
@@ -137,7 +242,23 @@
transaction->addTransactionCompletedCallback(callback, context);
}
-void ASurfaceTransaction_setVisibility(ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl,
+void ASurfaceTransaction_reparent(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
+ ASurfaceControl* newParentASurfaceControl) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+ CHECK_NOT_NULL(aSurfaceControl);
+
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+ sp<SurfaceControl> newParentSurfaceControl = ASurfaceControl_to_SurfaceControl(
+ newParentASurfaceControl);
+ sp<IBinder> newParentHandle = (newParentSurfaceControl)? newParentSurfaceControl->getHandle() : nullptr;
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ transaction->reparent(surfaceControl, newParentHandle);
+}
+
+void ASurfaceTransaction_setVisibility(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
int8_t visibility) {
CHECK_NOT_NULL(aSurfaceTransaction);
CHECK_NOT_NULL(aSurfaceControl);
@@ -157,7 +278,8 @@
}
}
-void ASurfaceTransaction_setZOrder(ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl,
+void ASurfaceTransaction_setZOrder(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
int32_t z_order) {
CHECK_NOT_NULL(aSurfaceTransaction);
CHECK_NOT_NULL(aSurfaceControl);
@@ -168,8 +290,9 @@
transaction->setLayer(surfaceControl, z_order);
}
-void ASurfaceTransaction_setBuffer(ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl,
- AHardwareBuffer* buffer, int fence_fd) {
+void ASurfaceTransaction_setBuffer(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
+ AHardwareBuffer* buffer, int acquire_fence_fd) {
CHECK_NOT_NULL(aSurfaceTransaction);
CHECK_NOT_NULL(aSurfaceControl);
@@ -179,8 +302,8 @@
sp<GraphicBuffer> graphic_buffer(reinterpret_cast<GraphicBuffer*>(buffer));
transaction->setBuffer(surfaceControl, graphic_buffer);
- if (fence_fd != -1) {
- sp<Fence> fence = new Fence(fence_fd);
+ if (acquire_fence_fd != -1) {
+ sp<Fence> fence = new Fence(acquire_fence_fd);
transaction->setAcquireFence(surfaceControl, fence);
}
}
@@ -215,7 +338,8 @@
transaction->setFlags(surfaceControl, flags, layer_state_t::eLayerOpaque);
}
-void ASurfaceTransaction_setDamageRegion(ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl,
+void ASurfaceTransaction_setDamageRegion(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
const ARect rects[], uint32_t count) {
CHECK_NOT_NULL(aSurfaceTransaction);
CHECK_NOT_NULL(aSurfaceControl);
@@ -230,3 +354,80 @@
transaction->setSurfaceDamageRegion(surfaceControl, region);
}
+
+void ASurfaceTransaction_setDesiredPresentTime(ASurfaceTransaction* aSurfaceTransaction,
+ int64_t desiredPresentTime) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ transaction->setDesiredPresentTime(static_cast<nsecs_t>(desiredPresentTime));
+}
+
+void ASurfaceTransaction_setBufferAlpha(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
+ float alpha) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+ CHECK_NOT_NULL(aSurfaceControl);
+
+ LOG_ALWAYS_FATAL_IF(alpha < 0.0 || alpha > 1.0, "invalid alpha");
+
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ transaction->setAlpha(surfaceControl, alpha);
+}
+
+void ASurfaceTransaction_setHdrMetadata_smpte2086(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
+ struct AHdrMetadata_smpte2086* metadata) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+ CHECK_NOT_NULL(aSurfaceControl);
+
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ HdrMetadata hdrMetadata;
+
+ if (metadata) {
+ hdrMetadata.smpte2086.displayPrimaryRed.x = metadata->displayPrimaryRed.x;
+ hdrMetadata.smpte2086.displayPrimaryRed.y = metadata->displayPrimaryRed.y;
+ hdrMetadata.smpte2086.displayPrimaryGreen.x = metadata->displayPrimaryGreen.x;
+ hdrMetadata.smpte2086.displayPrimaryGreen.y = metadata->displayPrimaryGreen.y;
+ hdrMetadata.smpte2086.displayPrimaryBlue.x = metadata->displayPrimaryBlue.x;
+ hdrMetadata.smpte2086.displayPrimaryBlue.y = metadata->displayPrimaryBlue.y;
+ hdrMetadata.smpte2086.whitePoint.x = metadata->whitePoint.x;
+ hdrMetadata.smpte2086.whitePoint.y = metadata->whitePoint.y;
+ hdrMetadata.smpte2086.minLuminance = metadata->minLuminance;
+ hdrMetadata.smpte2086.maxLuminance = metadata->maxLuminance;
+
+ hdrMetadata.validTypes |= HdrMetadata::SMPTE2086;
+ } else {
+ hdrMetadata.validTypes &= ~HdrMetadata::SMPTE2086;
+ }
+
+ transaction->setHdrMetadata(surfaceControl, hdrMetadata);
+}
+
+void ASurfaceTransaction_setHdrMetadata_cta861_3(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
+ struct AHdrMetadata_cta861_3* metadata) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+ CHECK_NOT_NULL(aSurfaceControl);
+
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ HdrMetadata hdrMetadata;
+
+ if (metadata) {
+ hdrMetadata.cta8613.maxContentLightLevel = metadata->maxContentLightLevel;
+ hdrMetadata.cta8613.maxFrameAverageLightLevel = metadata->maxFrameAverageLightLevel;
+
+ hdrMetadata.validTypes |= HdrMetadata::CTA861_3;
+ } else {
+ hdrMetadata.validTypes &= ~HdrMetadata::CTA861_3;
+ }
+
+ transaction->setHdrMetadata(surfaceControl, hdrMetadata);
+}
diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
index 499d493..f346b00 100644
--- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java
+++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
@@ -344,7 +344,6 @@
if (entry != null) {
entry.setSeen();
mAgingHelper.onNotificationSeen(entry);
- mSmartActionsHelper.onNotificationSeen(entry);
}
}
} catch (Throwable e) {
diff --git a/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java b/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java
index 39a1676..406b44d 100644
--- a/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java
+++ b/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java
@@ -20,8 +20,11 @@
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
+import android.provider.DeviceConfig;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.KeyValueListParser;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -29,6 +32,7 @@
* Observes the settings for {@link Assistant}.
*/
final class AssistantSettings extends ContentObserver {
+ private static final String LOG_TAG = "AssistantSettings";
public static Factory FACTORY = AssistantSettings::createAndRegister;
private static final boolean DEFAULT_GENERATE_REPLIES = true;
private static final boolean DEFAULT_GENERATE_ACTIONS = true;
@@ -39,19 +43,33 @@
private static final Uri DISMISS_TO_VIEW_RATIO_LIMIT_URI =
Settings.Global.getUriFor(
Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT);
- private static final Uri SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI =
- Settings.Global.getUriFor(
- Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS);
private static final Uri NOTIFICATION_NEW_INTERRUPTION_MODEL_URI =
Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL);
- private static final String KEY_GENERATE_REPLIES = "generate_replies";
- private static final String KEY_GENERATE_ACTIONS = "generate_actions";
+ /**
+ * Flag determining whether the Notification Assistant should generate replies for
+ * notifications.
+ * <p>
+ * This flag belongs to the namespace: {@link DeviceConfig#NAMESPACE_NOTIFICATION_ASSISTANT}.
+ */
+ @VisibleForTesting
+ static final String KEY_GENERATE_REPLIES = "notification_assistant_generate_replies";
+
+ /**
+ * Flag determining whether the Notification Assistant should generate contextual actions in
+ * notifications.
+ * <p>
+ * This flag belongs to the namespace: {@link DeviceConfig#NAMESPACE_NOTIFICATION_ASSISTANT}.
+ */
+ @VisibleForTesting
+ static final String KEY_GENERATE_ACTIONS = "notification_assistant_generate_actions";
private final KeyValueListParser mParser = new KeyValueListParser(',');
private final ContentResolver mResolver;
private final int mUserId;
+ private final Handler mHandler;
+
@VisibleForTesting
protected final Runnable mOnUpdateRunnable;
@@ -65,6 +83,7 @@
private AssistantSettings(Handler handler, ContentResolver resolver, int userId,
Runnable onUpdateRunnable) {
super(handler);
+ mHandler = handler;
mResolver = resolver;
mUserId = userId;
mOnUpdateRunnable = onUpdateRunnable;
@@ -75,6 +94,7 @@
AssistantSettings assistantSettings =
new AssistantSettings(handler, resolver, userId, onUpdateRunnable);
assistantSettings.register();
+ assistantSettings.registerDeviceConfigs();
return assistantSettings;
}
@@ -91,13 +111,62 @@
mResolver.registerContentObserver(
DISMISS_TO_VIEW_RATIO_LIMIT_URI, false, this, mUserId);
mResolver.registerContentObserver(STREAK_LIMIT_URI, false, this, mUserId);
- mResolver.registerContentObserver(
- SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI, false, this, mUserId);
// Update all uris on creation.
update(null);
}
+ private void registerDeviceConfigs() {
+ DeviceConfig.addOnPropertyChangedListener(
+ DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+ this::postToHandler,
+ this::onDeviceConfigPropertyChanged);
+
+ // Update the fields in this class from the current state of the device config.
+ updateFromDeviceConfigFlags();
+ }
+
+ private void postToHandler(Runnable r) {
+ this.mHandler.post(r);
+ }
+
+ @VisibleForTesting
+ void onDeviceConfigPropertyChanged(String namespace, String name, String value) {
+ if (!DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT.equals(namespace)) {
+ Log.e(LOG_TAG, "Received update from DeviceConfig for unrelated namespace: "
+ + namespace + " " + name + "=" + value);
+ return;
+ }
+
+ updateFromDeviceConfigFlags();
+ }
+
+ private void updateFromDeviceConfigFlags() {
+ String generateRepliesFlag = DeviceConfig.getProperty(
+ DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+ KEY_GENERATE_REPLIES);
+ if (TextUtils.isEmpty(generateRepliesFlag)) {
+ mGenerateReplies = DEFAULT_GENERATE_REPLIES;
+ } else {
+ // parseBoolean returns false for everything that isn't 'true' so there's no need to
+ // sanitise the flag string here.
+ mGenerateReplies = Boolean.parseBoolean(generateRepliesFlag);
+ }
+
+ String generateActionsFlag = DeviceConfig.getProperty(
+ DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+ KEY_GENERATE_ACTIONS);
+ if (TextUtils.isEmpty(generateActionsFlag)) {
+ mGenerateActions = DEFAULT_GENERATE_ACTIONS;
+ } else {
+ // parseBoolean returns false for everything that isn't 'true' so there's no need to
+ // sanitise the flag string here.
+ mGenerateActions = Boolean.parseBoolean(generateActionsFlag);
+ }
+
+ mOnUpdateRunnable.run();
+ }
+
@Override
public void onChange(boolean selfChange, Uri uri) {
update(uri);
@@ -114,15 +183,6 @@
mResolver, Settings.Global.BLOCKING_HELPER_STREAK_LIMIT,
ChannelImpressions.DEFAULT_STREAK_LIMIT);
}
- if (uri == null || SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI.equals(uri)) {
- mParser.setString(
- Settings.Global.getString(mResolver,
- Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
- mGenerateReplies =
- mParser.getBoolean(KEY_GENERATE_REPLIES, DEFAULT_GENERATE_REPLIES);
- mGenerateActions =
- mParser.getBoolean(KEY_GENERATE_ACTIONS, DEFAULT_GENERATE_ACTIONS);
- }
if (uri == null || NOTIFICATION_NEW_INTERRUPTION_MODEL_URI.equals(uri)) {
int mNewInterruptionModelInt = Settings.Secure.getInt(
mResolver, Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL,
diff --git a/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java b/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java
index acf1180..ce2c409 100644
--- a/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java
+++ b/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java
@@ -236,10 +236,6 @@
return mSeen;
}
- public boolean isExpanded() {
- return mExpanded;
- }
-
public boolean isShowActionEventLogged() {
return mIsShowActionEventLogged;
}
diff --git a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
index 95df5f2..0d528e7 100644
--- a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
+++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
@@ -152,18 +152,26 @@
return replies;
}
- void onNotificationSeen(@NonNull NotificationEntry entry) {
- if (entry.isExpanded()) {
- maybeSendActionShownEvent(entry);
- }
- }
-
void onNotificationExpansionChanged(@NonNull NotificationEntry entry, boolean isUserAction,
boolean isExpanded) {
- // Notification can be expanded in the background, and thus the isUserAction check.
- if (isUserAction && isExpanded) {
- maybeSendActionShownEvent(entry);
+ if (!isExpanded) {
+ return;
}
+ String resultId = mNotificationKeyToResultIdCache.get(entry.getSbn().getKey());
+ if (resultId == null) {
+ return;
+ }
+ // Only report if this is the first time the user sees these suggestions.
+ if (entry.isShowActionEventLogged()) {
+ return;
+ }
+ entry.setShowActionEventLogged();
+ TextClassifierEvent textClassifierEvent =
+ createTextClassifierEventBuilder(TextClassifierEvent.TYPE_ACTIONS_SHOWN,
+ resultId)
+ .build();
+ // TODO: If possible, report which replies / actions are actually seen by user.
+ mTextClassifier.onTextClassifierEvent(textClassifierEvent);
}
void onNotificationDirectReplied(@NonNull String key) {
@@ -234,26 +242,6 @@
.setResultId(resultId);
}
- private void maybeSendActionShownEvent(@NonNull NotificationEntry entry) {
- if (mTextClassifier == null) {
- return;
- }
- String resultId = mNotificationKeyToResultIdCache.get(entry.getSbn().getKey());
- if (resultId == null) {
- return;
- }
- // Only report if this is the first time the user sees these suggestions.
- if (entry.isShowActionEventLogged()) {
- return;
- }
- entry.setShowActionEventLogged();
- TextClassifierEvent textClassifierEvent =
- createTextClassifierEventBuilder(TextClassifierEvent.TYPE_ACTIONS_SHOWN, resultId)
- .build();
- // TODO: If possible, report which replies / actions are actually seen by user.
- mTextClassifier.onTextClassifierEvent(textClassifierEvent);
- }
-
/**
* Returns whether a notification is eligible for action adjustments.
*
diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java
index fd23f2b..51b723d 100644
--- a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java
+++ b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java
@@ -26,6 +26,7 @@
import android.content.ContentResolver;
import android.os.Handler;
import android.os.Looper;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
@@ -62,9 +63,6 @@
Settings.Global.putFloat(mResolver,
Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT, 0.8f);
Settings.Global.putInt(mResolver, Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, 2);
- Settings.Global.putString(mResolver,
- Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
- "generate_replies=true,generate_actions=true");
Settings.Secure.putInt(mResolver, Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL, 1);
mAssistantSettings = AssistantSettings.createForTesting(
@@ -73,56 +71,78 @@
@Test
public void testGenerateRepliesDisabled() {
- Settings.Global.putString(mResolver,
- Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
- "generate_replies=false");
-
- // Notify for the settings values we updated.
- mAssistantSettings.onChange(false,
- Settings.Global.getUriFor(
- Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
-
+ mAssistantSettings.onDeviceConfigPropertyChanged(
+ DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+ AssistantSettings.KEY_GENERATE_REPLIES,
+ "false");
assertFalse(mAssistantSettings.mGenerateReplies);
}
@Test
public void testGenerateRepliesEnabled() {
- Settings.Global.putString(mResolver,
- Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_replies=true");
+ mAssistantSettings.onDeviceConfigPropertyChanged(
+ DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+ AssistantSettings.KEY_GENERATE_REPLIES,
+ "true");
- // Notify for the settings values we updated.
- mAssistantSettings.onChange(false,
- Settings.Global.getUriFor(
- Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
+ assertTrue(mAssistantSettings.mGenerateReplies);
+ }
+ @Test
+ public void testGenerateRepliesEmptyFlag() {
+ mAssistantSettings.onDeviceConfigPropertyChanged(
+ DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+ AssistantSettings.KEY_GENERATE_REPLIES,
+ "false");
+
+ assertFalse(mAssistantSettings.mGenerateReplies);
+
+ mAssistantSettings.onDeviceConfigPropertyChanged(
+ DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+ AssistantSettings.KEY_GENERATE_REPLIES,
+ "");
+
+ // Go back to the default value.
assertTrue(mAssistantSettings.mGenerateReplies);
}
@Test
public void testGenerateActionsDisabled() {
- Settings.Global.putString(mResolver,
- Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_actions=false");
+ mAssistantSettings.onDeviceConfigPropertyChanged(
+ DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+ AssistantSettings.KEY_GENERATE_ACTIONS,
+ "false");
- // Notify for the settings values we updated.
- mAssistantSettings.onChange(false,
- Settings.Global.getUriFor(
- Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
-
- assertTrue(mAssistantSettings.mGenerateReplies);
+ assertFalse(mAssistantSettings.mGenerateActions);
}
@Test
public void testGenerateActionsEnabled() {
- Settings.Global.putString(mResolver,
- Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_actions=true");
+ mAssistantSettings.onDeviceConfigPropertyChanged(
+ DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+ AssistantSettings.KEY_GENERATE_ACTIONS,
+ "true");
- // Notify for the settings values we updated.
- mAssistantSettings.onChange(false,
- Settings.Global.getUriFor(
- Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
+ assertTrue(mAssistantSettings.mGenerateActions);
+ }
- assertTrue(mAssistantSettings.mGenerateReplies);
+ @Test
+ public void testGenerateActionsEmptyFlag() {
+ mAssistantSettings.onDeviceConfigPropertyChanged(
+ DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+ AssistantSettings.KEY_GENERATE_ACTIONS,
+ "false");
+
+ assertFalse(mAssistantSettings.mGenerateActions);
+
+ mAssistantSettings.onDeviceConfigPropertyChanged(
+ DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+ AssistantSettings.KEY_GENERATE_ACTIONS,
+ "");
+
+ // Go back to the default value.
+ assertTrue(mAssistantSettings.mGenerateActions);
}
@Test
diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
index 7b7ce3d..707349b 100644
--- a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
+++ b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
@@ -273,24 +273,22 @@
final String message = "Where are you?";
Notification notification = mNotificationBuilder.setContentText(message).build();
when(mNotificationEntry.getNotification()).thenReturn(notification);
- when(mNotificationEntry.isExpanded()).thenReturn(false);
mSmartActionsHelper.suggestReplies(mNotificationEntry);
- mSmartActionsHelper.onNotificationSeen(mNotificationEntry);
+ mSmartActionsHelper.onNotificationExpansionChanged(mNotificationEntry, false, false);
verify(mTextClassifier, never()).onTextClassifierEvent(
Mockito.any(TextClassifierEvent.class));
}
@Test
- public void testOnNotificationsSeen_expanded() {
+ public void testOnNotifications_expanded() {
final String message = "Where are you?";
Notification notification = mNotificationBuilder.setContentText(message).build();
when(mNotificationEntry.getNotification()).thenReturn(notification);
- when(mNotificationEntry.isExpanded()).thenReturn(true);
mSmartActionsHelper.suggestReplies(mNotificationEntry);
- mSmartActionsHelper.onNotificationSeen(mNotificationEntry);
+ mSmartActionsHelper.onNotificationExpansionChanged(mNotificationEntry, false, true);
ArgumentCaptor<TextClassifierEvent> argumentCaptor =
ArgumentCaptor.forClass(TextClassifierEvent.class);
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 8d04702..da3416b 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -76,7 +76,7 @@
private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
- Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES,
+ Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES, Root.COLUMN_QUERY_ARGS
};
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
@@ -444,6 +444,7 @@
row.add(Root.COLUMN_FLAGS, root.flags);
row.add(Root.COLUMN_TITLE, root.title);
row.add(Root.COLUMN_DOCUMENT_ID, root.docId);
+ row.add(Root.COLUMN_QUERY_ARGS, SUPPORTED_QUERY_ARGS);
long availableBytes = -1;
if (root.reportAvailableBytes) {
diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp
index 2f7d599..a2da0a0 100644
--- a/packages/NetworkStack/Android.bp
+++ b/packages/NetworkStack/Android.bp
@@ -24,7 +24,7 @@
":services-networkstack-shared-srcs",
],
static_libs: [
- "dhcp-packet-lib",
+ "services-netlink-lib",
]
}
diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml
index 7f8bb93..5ab833b 100644
--- a/packages/NetworkStack/AndroidManifest.xml
+++ b/packages/NetworkStack/AndroidManifest.xml
@@ -28,6 +28,7 @@
<!-- Launch captive portal app as specific user -->
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.NETWORK_STACK" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:label="NetworkStack"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/NetworkStack/src/android/net/apf/ApfFilter.java b/packages/NetworkStack/src/android/net/apf/ApfFilter.java
new file mode 100644
index 0000000..50c4dfc
--- /dev/null
+++ b/packages/NetworkStack/src/android/net/apf/ApfFilter.java
@@ -0,0 +1,1591 @@
+/*
+ * Copyright (C) 2016 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.apf;
+
+import static android.net.util.SocketUtils.makePacketSocketAddress;
+import static android.system.OsConstants.AF_PACKET;
+import static android.system.OsConstants.ARPHRD_ETHER;
+import static android.system.OsConstants.ETH_P_ARP;
+import static android.system.OsConstants.ETH_P_IP;
+import static android.system.OsConstants.ETH_P_IPV6;
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_RAW;
+
+import static com.android.internal.util.BitUtils.bytesToBEInt;
+import static com.android.internal.util.BitUtils.getUint16;
+import static com.android.internal.util.BitUtils.getUint32;
+import static com.android.internal.util.BitUtils.getUint8;
+import static com.android.internal.util.BitUtils.uint32;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
+
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.NetworkUtils;
+import android.net.apf.ApfGenerator.IllegalInstructionException;
+import android.net.apf.ApfGenerator.Register;
+import android.net.ip.IpClient.IpClientCallbacksWrapper;
+import android.net.metrics.ApfProgramEvent;
+import android.net.metrics.ApfStats;
+import android.net.metrics.IpConnectivityLog;
+import android.net.metrics.RaEvent;
+import android.net.util.InterfaceParams;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.HexDump;
+import com.android.internal.util.IndentingPrintWriter;
+
+import libcore.io.IoBridge;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * For networks that support packet filtering via APF programs, {@code ApfFilter}
+ * listens for IPv6 ICMPv6 router advertisements (RAs) and generates APF programs to
+ * filter out redundant duplicate ones.
+ *
+ * Threading model:
+ * A collection of RAs we've received is kept in mRas. Generating APF programs uses mRas to
+ * know what RAs to filter for, thus generating APF programs is dependent on mRas.
+ * mRas can be accessed by multiple threads:
+ * - ReceiveThread, which listens for RAs and adds them to mRas, and generates APF programs.
+ * - callers of:
+ * - setMulticastFilter(), which can cause an APF program to be generated.
+ * - dump(), which dumps mRas among other things.
+ * - shutdown(), which clears mRas.
+ * So access to mRas is synchronized.
+ *
+ * @hide
+ */
+public class ApfFilter {
+
+ // Helper class for specifying functional filter parameters.
+ public static class ApfConfiguration {
+ public ApfCapabilities apfCapabilities;
+ public boolean multicastFilter;
+ public boolean ieee802_3Filter;
+ public int[] ethTypeBlackList;
+ }
+
+ // Enums describing the outcome of receiving an RA packet.
+ private static enum ProcessRaResult {
+ MATCH, // Received RA matched a known RA
+ DROPPED, // Received RA ignored due to MAX_RAS
+ PARSE_ERROR, // Received RA could not be parsed
+ ZERO_LIFETIME, // Received RA had 0 lifetime
+ UPDATE_NEW_RA, // APF program updated for new RA
+ UPDATE_EXPIRY // APF program updated for expiry
+ }
+
+ /**
+ * APF packet counters.
+ *
+ * Packet counters are 32bit big-endian values, and allocated near the end of the APF data
+ * buffer, using negative byte offsets, where -4 is equivalent to maximumApfProgramSize - 4,
+ * the last writable 32bit word.
+ */
+ @VisibleForTesting
+ public static enum Counter {
+ RESERVED_OOB, // Points to offset 0 from the end of the buffer (out-of-bounds)
+ TOTAL_PACKETS,
+ PASSED_ARP,
+ PASSED_DHCP,
+ PASSED_IPV4,
+ PASSED_IPV6_NON_ICMP,
+ PASSED_IPV4_UNICAST,
+ PASSED_IPV6_ICMP,
+ PASSED_IPV6_UNICAST_NON_ICMP,
+ PASSED_ARP_NON_IPV4,
+ PASSED_ARP_UNKNOWN,
+ PASSED_ARP_UNICAST_REPLY,
+ PASSED_NON_IP_UNICAST,
+ DROPPED_ETH_BROADCAST,
+ DROPPED_RA,
+ DROPPED_GARP_REPLY,
+ DROPPED_ARP_OTHER_HOST,
+ DROPPED_IPV4_L2_BROADCAST,
+ DROPPED_IPV4_BROADCAST_ADDR,
+ DROPPED_IPV4_BROADCAST_NET,
+ DROPPED_IPV4_MULTICAST,
+ DROPPED_IPV6_ROUTER_SOLICITATION,
+ DROPPED_IPV6_MULTICAST_NA,
+ DROPPED_IPV6_MULTICAST,
+ DROPPED_IPV6_MULTICAST_PING,
+ DROPPED_IPV6_NON_ICMP_MULTICAST,
+ DROPPED_802_3_FRAME,
+ DROPPED_ETHERTYPE_BLACKLISTED,
+ DROPPED_ARP_REPLY_SPA_NO_HOST;
+
+ // Returns the negative byte offset from the end of the APF data segment for
+ // a given counter.
+ public int offset() {
+ return - this.ordinal() * 4; // Currently, all counters are 32bit long.
+ }
+
+ // Returns the total size of the data segment in bytes.
+ public static int totalSize() {
+ return (Counter.class.getEnumConstants().length - 1) * 4;
+ }
+ }
+
+ /**
+ * When APFv4 is supported, loads R1 with the offset of the specified counter.
+ */
+ private void maybeSetupCounter(ApfGenerator gen, Counter c) {
+ if (mApfCapabilities.hasDataAccess()) {
+ gen.addLoadImmediate(Register.R1, c.offset());
+ }
+ }
+
+ // When APFv4 is supported, these point to the trampolines generated by emitEpilogue().
+ // Otherwise, they're just aliases for PASS_LABEL and DROP_LABEL.
+ private final String mCountAndPassLabel;
+ private final String mCountAndDropLabel;
+
+ // Thread to listen for RAs.
+ @VisibleForTesting
+ class ReceiveThread extends Thread {
+ private final byte[] mPacket = new byte[1514];
+ private final FileDescriptor mSocket;
+ private final long mStart = SystemClock.elapsedRealtime();
+
+ private int mReceivedRas = 0;
+ private int mMatchingRas = 0;
+ private int mDroppedRas = 0;
+ private int mParseErrors = 0;
+ private int mZeroLifetimeRas = 0;
+ private int mProgramUpdates = 0;
+
+ private volatile boolean mStopped;
+
+ public ReceiveThread(FileDescriptor socket) {
+ mSocket = socket;
+ }
+
+ public void halt() {
+ mStopped = true;
+ try {
+ // Interrupts the read() call the thread is blocked in.
+ IoBridge.closeAndSignalBlockedThreads(mSocket);
+ } catch (IOException ignored) {}
+ }
+
+ @Override
+ public void run() {
+ log("begin monitoring");
+ while (!mStopped) {
+ try {
+ int length = Os.read(mSocket, mPacket, 0, mPacket.length);
+ updateStats(processRa(mPacket, length));
+ } catch (IOException|ErrnoException e) {
+ if (!mStopped) {
+ Log.e(TAG, "Read error", e);
+ }
+ }
+ }
+ logStats();
+ }
+
+ private void updateStats(ProcessRaResult result) {
+ mReceivedRas++;
+ switch(result) {
+ case MATCH:
+ mMatchingRas++;
+ return;
+ case DROPPED:
+ mDroppedRas++;
+ return;
+ case PARSE_ERROR:
+ mParseErrors++;
+ return;
+ case ZERO_LIFETIME:
+ mZeroLifetimeRas++;
+ return;
+ case UPDATE_EXPIRY:
+ mMatchingRas++;
+ mProgramUpdates++;
+ return;
+ case UPDATE_NEW_RA:
+ mProgramUpdates++;
+ return;
+ }
+ }
+
+ private void logStats() {
+ final long nowMs = SystemClock.elapsedRealtime();
+ synchronized (this) {
+ final ApfStats stats = new ApfStats.Builder()
+ .setReceivedRas(mReceivedRas)
+ .setMatchingRas(mMatchingRas)
+ .setDroppedRas(mDroppedRas)
+ .setParseErrors(mParseErrors)
+ .setZeroLifetimeRas(mZeroLifetimeRas)
+ .setProgramUpdates(mProgramUpdates)
+ .setDurationMs(nowMs - mStart)
+ .setMaxProgramSize(mApfCapabilities.maximumApfProgramSize)
+ .setProgramUpdatesAll(mNumProgramUpdates)
+ .setProgramUpdatesAllowingMulticast(mNumProgramUpdatesAllowingMulticast)
+ .build();
+ mMetricsLog.log(stats);
+ logApfProgramEventLocked(nowMs / DateUtils.SECOND_IN_MILLIS);
+ }
+ }
+ }
+
+ private static final String TAG = "ApfFilter";
+ private static final boolean DBG = true;
+ private static final boolean VDBG = false;
+
+ private static final int ETH_HEADER_LEN = 14;
+ private static final int ETH_DEST_ADDR_OFFSET = 0;
+ private static final int ETH_ETHERTYPE_OFFSET = 12;
+ private static final int ETH_TYPE_MIN = 0x0600;
+ private static final int ETH_TYPE_MAX = 0xFFFF;
+ private static final byte[] ETH_BROADCAST_MAC_ADDRESS =
+ {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
+ // TODO: Make these offsets relative to end of link-layer header; don't include ETH_HEADER_LEN.
+ private static final int IPV4_FRAGMENT_OFFSET_OFFSET = ETH_HEADER_LEN + 6;
+ // Endianness is not an issue for this constant because the APF interpreter always operates in
+ // network byte order.
+ private static final int IPV4_FRAGMENT_OFFSET_MASK = 0x1fff;
+ private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9;
+ private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16;
+ private static final int IPV4_ANY_HOST_ADDRESS = 0;
+ private static final int IPV4_BROADCAST_ADDRESS = -1; // 255.255.255.255
+
+ // Traffic class and Flow label are not byte aligned. Luckily we
+ // don't care about either value so we'll consider bytes 1-3 of the
+ // IPv6 header as don't care.
+ private static final int IPV6_FLOW_LABEL_OFFSET = ETH_HEADER_LEN + 1;
+ private static final int IPV6_FLOW_LABEL_LEN = 3;
+ private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6;
+ private static final int IPV6_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 8;
+ private static final int IPV6_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 24;
+ private static final int IPV6_HEADER_LEN = 40;
+ // The IPv6 all nodes address ff02::1
+ private static final byte[] IPV6_ALL_NODES_ADDRESS =
+ { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
+
+ private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN;
+
+ // NOTE: this must be added to the IPv4 header length in IPV4_HEADER_SIZE_MEMORY_SLOT
+ private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 2;
+ private static final int UDP_HEADER_LEN = 8;
+
+ private static final int DHCP_CLIENT_PORT = 68;
+ // NOTE: this must be added to the IPv4 header length in IPV4_HEADER_SIZE_MEMORY_SLOT
+ private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 28;
+
+ private static final int ARP_HEADER_OFFSET = ETH_HEADER_LEN;
+ private static final byte[] ARP_IPV4_HEADER = {
+ 0, 1, // Hardware type: Ethernet (1)
+ 8, 0, // Protocol type: IP (0x0800)
+ 6, // Hardware size: 6
+ 4, // Protocol size: 4
+ };
+ private static final int ARP_OPCODE_OFFSET = ARP_HEADER_OFFSET + 6;
+ // Opcode: ARP request (0x0001), ARP reply (0x0002)
+ private static final short ARP_OPCODE_REQUEST = 1;
+ private static final short ARP_OPCODE_REPLY = 2;
+ private static final int ARP_SOURCE_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 14;
+ private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 24;
+ // Do not log ApfProgramEvents whose actual lifetimes was less than this.
+ private static final int APF_PROGRAM_EVENT_LIFETIME_THRESHOLD = 2;
+ // Limit on the Black List size to cap on program usage for this
+ // TODO: Select a proper max length
+ private static final int APF_MAX_ETH_TYPE_BLACK_LIST_LEN = 20;
+
+ private final ApfCapabilities mApfCapabilities;
+ private final IpClientCallbacksWrapper mIpClientCallback;
+ private final InterfaceParams mInterfaceParams;
+ private final IpConnectivityLog mMetricsLog;
+
+ @VisibleForTesting
+ byte[] mHardwareAddress;
+ @VisibleForTesting
+ ReceiveThread mReceiveThread;
+ @GuardedBy("this")
+ private long mUniqueCounter;
+ @GuardedBy("this")
+ private boolean mMulticastFilter;
+ @GuardedBy("this")
+ private boolean mInDozeMode;
+ private final boolean mDrop802_3Frames;
+ private final int[] mEthTypeBlackList;
+
+ // Detects doze mode state transitions.
+ private final BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)) {
+ PowerManager powerManager =
+ (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ final boolean deviceIdle = powerManager.isDeviceIdleMode();
+ setDozeMode(deviceIdle);
+ }
+ }
+ };
+ private final Context mContext;
+
+ // Our IPv4 address, if we have just one, otherwise null.
+ @GuardedBy("this")
+ private byte[] mIPv4Address;
+ // The subnet prefix length of our IPv4 network. Only valid if mIPv4Address is not null.
+ @GuardedBy("this")
+ private int mIPv4PrefixLength;
+
+ @VisibleForTesting
+ ApfFilter(Context context, ApfConfiguration config, InterfaceParams ifParams,
+ IpClientCallbacksWrapper ipClientCallback, IpConnectivityLog log) {
+ mApfCapabilities = config.apfCapabilities;
+ mIpClientCallback = ipClientCallback;
+ mInterfaceParams = ifParams;
+ mMulticastFilter = config.multicastFilter;
+ mDrop802_3Frames = config.ieee802_3Filter;
+ mContext = context;
+
+ if (mApfCapabilities.hasDataAccess()) {
+ mCountAndPassLabel = "countAndPass";
+ mCountAndDropLabel = "countAndDrop";
+ } else {
+ // APFv4 unsupported: turn jumps to the counter trampolines to immediately PASS or DROP,
+ // preserving the original pre-APFv4 behavior.
+ mCountAndPassLabel = ApfGenerator.PASS_LABEL;
+ mCountAndDropLabel = ApfGenerator.DROP_LABEL;
+ }
+
+ // Now fill the black list from the passed array
+ mEthTypeBlackList = filterEthTypeBlackList(config.ethTypeBlackList);
+
+ mMetricsLog = log;
+
+ // TODO: ApfFilter should not generate programs until IpClient sends provisioning success.
+ maybeStartFilter();
+
+ // Listen for doze-mode transition changes to enable/disable the IPv6 multicast filter.
+ mContext.registerReceiver(mDeviceIdleReceiver,
+ new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED));
+ }
+
+ public synchronized void setDataSnapshot(byte[] data) {
+ mDataSnapshot = data;
+ }
+
+ private void log(String s) {
+ Log.d(TAG, "(" + mInterfaceParams.name + "): " + s);
+ }
+
+ @GuardedBy("this")
+ private long getUniqueNumberLocked() {
+ return mUniqueCounter++;
+ }
+
+ @GuardedBy("this")
+ private static int[] filterEthTypeBlackList(int[] ethTypeBlackList) {
+ ArrayList<Integer> bl = new ArrayList<Integer>();
+
+ for (int p : ethTypeBlackList) {
+ // Check if the protocol is a valid ether type
+ if ((p < ETH_TYPE_MIN) || (p > ETH_TYPE_MAX)) {
+ continue;
+ }
+
+ // Check if the protocol is not repeated in the passed array
+ if (bl.contains(p)) {
+ continue;
+ }
+
+ // Check if list reach its max size
+ if (bl.size() == APF_MAX_ETH_TYPE_BLACK_LIST_LEN) {
+ Log.w(TAG, "Passed EthType Black List size too large (" + bl.size() +
+ ") using top " + APF_MAX_ETH_TYPE_BLACK_LIST_LEN + " protocols");
+ break;
+ }
+
+ // Now add the protocol to the list
+ bl.add(p);
+ }
+
+ return bl.stream().mapToInt(Integer::intValue).toArray();
+ }
+
+ /**
+ * Attempt to start listening for RAs and, if RAs are received, generating and installing
+ * filters to ignore useless RAs.
+ */
+ @VisibleForTesting
+ void maybeStartFilter() {
+ FileDescriptor socket;
+ try {
+ mHardwareAddress = mInterfaceParams.macAddr.toByteArray();
+ synchronized(this) {
+ // Clear the APF memory to reset all counters upon connecting to the first AP
+ // in an SSID. This is limited to APFv4 devices because this large write triggers
+ // a crash on some older devices (b/78905546).
+ if (mApfCapabilities.hasDataAccess()) {
+ byte[] zeroes = new byte[mApfCapabilities.maximumApfProgramSize];
+ mIpClientCallback.installPacketFilter(zeroes);
+ }
+
+ // Install basic filters
+ installNewProgramLocked();
+ }
+ socket = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IPV6);
+ SocketAddress addr = makePacketSocketAddress(
+ (short) ETH_P_IPV6, mInterfaceParams.index);
+ Os.bind(socket, addr);
+ NetworkUtils.attachRaFilter(socket, mApfCapabilities.apfPacketFormat);
+ } catch(SocketException|ErrnoException e) {
+ Log.e(TAG, "Error starting filter", e);
+ return;
+ }
+ mReceiveThread = new ReceiveThread(socket);
+ mReceiveThread.start();
+ }
+
+ // Returns seconds since device boot.
+ @VisibleForTesting
+ protected long currentTimeSeconds() {
+ return SystemClock.elapsedRealtime() / DateUtils.SECOND_IN_MILLIS;
+ }
+
+ public static class InvalidRaException extends Exception {
+ public InvalidRaException(String m) {
+ super(m);
+ }
+ }
+
+ // A class to hold information about an RA.
+ @VisibleForTesting
+ class Ra {
+ // From RFC4861:
+ private static final int ICMP6_RA_HEADER_LEN = 16;
+ private static final int ICMP6_RA_CHECKSUM_OFFSET =
+ ETH_HEADER_LEN + IPV6_HEADER_LEN + 2;
+ private static final int ICMP6_RA_CHECKSUM_LEN = 2;
+ private static final int ICMP6_RA_OPTION_OFFSET =
+ ETH_HEADER_LEN + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN;
+ private static final int ICMP6_RA_ROUTER_LIFETIME_OFFSET =
+ ETH_HEADER_LEN + IPV6_HEADER_LEN + 6;
+ private static final int ICMP6_RA_ROUTER_LIFETIME_LEN = 2;
+ // Prefix information option.
+ private static final int ICMP6_PREFIX_OPTION_TYPE = 3;
+ private static final int ICMP6_PREFIX_OPTION_LEN = 32;
+ private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET = 4;
+ private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN = 4;
+ private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET = 8;
+ private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN = 4;
+
+ // From RFC6106: Recursive DNS Server option
+ private static final int ICMP6_RDNSS_OPTION_TYPE = 25;
+ // From RFC6106: DNS Search List option
+ private static final int ICMP6_DNSSL_OPTION_TYPE = 31;
+
+ // From RFC4191: Route Information option
+ private static final int ICMP6_ROUTE_INFO_OPTION_TYPE = 24;
+ // Above three options all have the same format:
+ private static final int ICMP6_4_BYTE_LIFETIME_OFFSET = 4;
+ private static final int ICMP6_4_BYTE_LIFETIME_LEN = 4;
+
+ // Note: mPacket's position() cannot be assumed to be reset.
+ private final ByteBuffer mPacket;
+ // List of binary ranges that include the whole packet except the lifetimes.
+ // Pairs consist of offset and length.
+ private final ArrayList<Pair<Integer, Integer>> mNonLifetimes =
+ new ArrayList<Pair<Integer, Integer>>();
+ // Minimum lifetime in packet
+ long mMinLifetime;
+ // When the packet was last captured, in seconds since Unix Epoch
+ long mLastSeen;
+
+ // For debugging only. Offsets into the packet where PIOs are.
+ private final ArrayList<Integer> mPrefixOptionOffsets = new ArrayList<>();
+
+ // For debugging only. Offsets into the packet where RDNSS options are.
+ private final ArrayList<Integer> mRdnssOptionOffsets = new ArrayList<>();
+
+ // For debugging only. How many times this RA was seen.
+ int seenCount = 0;
+
+ // For debugging only. Returns the hex representation of the last matching packet.
+ String getLastMatchingPacket() {
+ return HexDump.toHexString(mPacket.array(), 0, mPacket.capacity(),
+ false /* lowercase */);
+ }
+
+ // For debugging only. Returns the string representation of the IPv6 address starting at
+ // position pos in the packet.
+ private String IPv6AddresstoString(int pos) {
+ try {
+ byte[] array = mPacket.array();
+ // Can't just call copyOfRange() and see if it throws, because if it reads past the
+ // end it pads with zeros instead of throwing.
+ if (pos < 0 || pos + 16 > array.length || pos + 16 < pos) {
+ return "???";
+ }
+ byte[] addressBytes = Arrays.copyOfRange(array, pos, pos + 16);
+ InetAddress address = (Inet6Address) InetAddress.getByAddress(addressBytes);
+ return address.getHostAddress();
+ } catch (UnsupportedOperationException e) {
+ // array() failed. Cannot happen, mPacket is array-backed and read-write.
+ return "???";
+ } catch (ClassCastException|UnknownHostException e) {
+ // Cannot happen.
+ return "???";
+ }
+ }
+
+ // Can't be static because it's in a non-static inner class.
+ // TODO: Make this static once RA is its own class.
+ private void prefixOptionToString(StringBuffer sb, int offset) {
+ String prefix = IPv6AddresstoString(offset + 16);
+ int length = getUint8(mPacket, offset + 2);
+ long valid = getUint32(mPacket, offset + 4);
+ long preferred = getUint32(mPacket, offset + 8);
+ sb.append(String.format("%s/%d %ds/%ds ", prefix, length, valid, preferred));
+ }
+
+ private void rdnssOptionToString(StringBuffer sb, int offset) {
+ int optLen = getUint8(mPacket, offset + 1) * 8;
+ if (optLen < 24) return; // Malformed or empty.
+ long lifetime = getUint32(mPacket, offset + 4);
+ int numServers = (optLen - 8) / 16;
+ sb.append("DNS ").append(lifetime).append("s");
+ for (int server = 0; server < numServers; server++) {
+ sb.append(" ").append(IPv6AddresstoString(offset + 8 + 16 * server));
+ }
+ }
+
+ public String toString() {
+ try {
+ StringBuffer sb = new StringBuffer();
+ sb.append(String.format("RA %s -> %s %ds ",
+ IPv6AddresstoString(IPV6_SRC_ADDR_OFFSET),
+ IPv6AddresstoString(IPV6_DEST_ADDR_OFFSET),
+ getUint16(mPacket, ICMP6_RA_ROUTER_LIFETIME_OFFSET)));
+ for (int i: mPrefixOptionOffsets) {
+ prefixOptionToString(sb, i);
+ }
+ for (int i: mRdnssOptionOffsets) {
+ rdnssOptionToString(sb, i);
+ }
+ return sb.toString();
+ } catch (BufferUnderflowException|IndexOutOfBoundsException e) {
+ return "<Malformed RA>";
+ }
+ }
+
+ /**
+ * Add a binary range of the packet that does not include a lifetime to mNonLifetimes.
+ * Assumes mPacket.position() is as far as we've parsed the packet.
+ * @param lastNonLifetimeStart offset within packet of where the last binary range of
+ * data not including a lifetime.
+ * @param lifetimeOffset offset from mPacket.position() to the next lifetime data.
+ * @param lifetimeLength length of the next lifetime data.
+ * @return offset within packet of where the next binary range of data not including
+ * a lifetime. This can be passed into the next invocation of this function
+ * via {@code lastNonLifetimeStart}.
+ */
+ private int addNonLifetime(int lastNonLifetimeStart, int lifetimeOffset,
+ int lifetimeLength) {
+ lifetimeOffset += mPacket.position();
+ mNonLifetimes.add(new Pair<Integer, Integer>(lastNonLifetimeStart,
+ lifetimeOffset - lastNonLifetimeStart));
+ return lifetimeOffset + lifetimeLength;
+ }
+
+ private int addNonLifetimeU32(int lastNonLifetimeStart) {
+ return addNonLifetime(lastNonLifetimeStart,
+ ICMP6_4_BYTE_LIFETIME_OFFSET, ICMP6_4_BYTE_LIFETIME_LEN);
+ }
+
+ // Note that this parses RA and may throw InvalidRaException (from
+ // Buffer.position(int) or due to an invalid-length option) or IndexOutOfBoundsException
+ // (from ByteBuffer.get(int) ) if parsing encounters something non-compliant with
+ // specifications.
+ Ra(byte[] packet, int length) throws InvalidRaException {
+ if (length < ICMP6_RA_OPTION_OFFSET) {
+ throw new InvalidRaException("Not an ICMP6 router advertisement");
+ }
+
+ mPacket = ByteBuffer.wrap(Arrays.copyOf(packet, length));
+ mLastSeen = currentTimeSeconds();
+
+ // Sanity check packet in case a packet arrives before we attach RA filter
+ // to our packet socket. b/29586253
+ if (getUint16(mPacket, ETH_ETHERTYPE_OFFSET) != ETH_P_IPV6 ||
+ getUint8(mPacket, IPV6_NEXT_HEADER_OFFSET) != IPPROTO_ICMPV6 ||
+ getUint8(mPacket, ICMP6_TYPE_OFFSET) != ICMPV6_ROUTER_ADVERTISEMENT) {
+ throw new InvalidRaException("Not an ICMP6 router advertisement");
+ }
+
+
+ RaEvent.Builder builder = new RaEvent.Builder();
+
+ // Ignore the flow label and low 4 bits of traffic class.
+ int lastNonLifetimeStart = addNonLifetime(0,
+ IPV6_FLOW_LABEL_OFFSET,
+ IPV6_FLOW_LABEL_LEN);
+
+ // Ignore the checksum.
+ lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart,
+ ICMP6_RA_CHECKSUM_OFFSET,
+ ICMP6_RA_CHECKSUM_LEN);
+
+ // Parse router lifetime
+ lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart,
+ ICMP6_RA_ROUTER_LIFETIME_OFFSET,
+ ICMP6_RA_ROUTER_LIFETIME_LEN);
+ builder.updateRouterLifetime(getUint16(mPacket, ICMP6_RA_ROUTER_LIFETIME_OFFSET));
+
+ // Ensures that the RA is not truncated.
+ mPacket.position(ICMP6_RA_OPTION_OFFSET);
+ while (mPacket.hasRemaining()) {
+ final int position = mPacket.position();
+ final int optionType = getUint8(mPacket, position);
+ final int optionLength = getUint8(mPacket, position + 1) * 8;
+ long lifetime;
+ switch (optionType) {
+ case ICMP6_PREFIX_OPTION_TYPE:
+ // Parse valid lifetime
+ lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart,
+ ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET,
+ ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN);
+ lifetime = getUint32(mPacket,
+ position + ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET);
+ builder.updatePrefixValidLifetime(lifetime);
+ // Parse preferred lifetime
+ lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart,
+ ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET,
+ ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN);
+ lifetime = getUint32(mPacket,
+ position + ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET);
+ builder.updatePrefixPreferredLifetime(lifetime);
+ mPrefixOptionOffsets.add(position);
+ break;
+ // These three options have the same lifetime offset and size, and
+ // are processed with the same specialized addNonLifetimeU32:
+ case ICMP6_RDNSS_OPTION_TYPE:
+ mRdnssOptionOffsets.add(position);
+ lastNonLifetimeStart = addNonLifetimeU32(lastNonLifetimeStart);
+ lifetime = getUint32(mPacket, position + ICMP6_4_BYTE_LIFETIME_OFFSET);
+ builder.updateRdnssLifetime(lifetime);
+ break;
+ case ICMP6_ROUTE_INFO_OPTION_TYPE:
+ lastNonLifetimeStart = addNonLifetimeU32(lastNonLifetimeStart);
+ lifetime = getUint32(mPacket, position + ICMP6_4_BYTE_LIFETIME_OFFSET);
+ builder.updateRouteInfoLifetime(lifetime);
+ break;
+ case ICMP6_DNSSL_OPTION_TYPE:
+ lastNonLifetimeStart = addNonLifetimeU32(lastNonLifetimeStart);
+ lifetime = getUint32(mPacket, position + ICMP6_4_BYTE_LIFETIME_OFFSET);
+ builder.updateDnsslLifetime(lifetime);
+ break;
+ default:
+ // RFC4861 section 4.2 dictates we ignore unknown options for fowards
+ // compatibility.
+ break;
+ }
+ if (optionLength <= 0) {
+ throw new InvalidRaException(String.format(
+ "Invalid option length opt=%d len=%d", optionType, optionLength));
+ }
+ mPacket.position(position + optionLength);
+ }
+ // Mark non-lifetime bytes since last lifetime.
+ addNonLifetime(lastNonLifetimeStart, 0, 0);
+ mMinLifetime = minLifetime(packet, length);
+ mMetricsLog.log(builder.build());
+ }
+
+ // Ignoring lifetimes (which may change) does {@code packet} match this RA?
+ boolean matches(byte[] packet, int length) {
+ if (length != mPacket.capacity()) return false;
+ byte[] referencePacket = mPacket.array();
+ for (Pair<Integer, Integer> nonLifetime : mNonLifetimes) {
+ for (int i = nonLifetime.first; i < (nonLifetime.first + nonLifetime.second); i++) {
+ if (packet[i] != referencePacket[i]) return false;
+ }
+ }
+ return true;
+ }
+
+ // What is the minimum of all lifetimes within {@code packet} in seconds?
+ // Precondition: matches(packet, length) already returned true.
+ long minLifetime(byte[] packet, int length) {
+ long minLifetime = Long.MAX_VALUE;
+ // Wrap packet in ByteBuffer so we can read big-endian values easily
+ ByteBuffer byteBuffer = ByteBuffer.wrap(packet);
+ for (int i = 0; (i + 1) < mNonLifetimes.size(); i++) {
+ int offset = mNonLifetimes.get(i).first + mNonLifetimes.get(i).second;
+
+ // The flow label is in mNonLifetimes, but it's not a lifetime.
+ if (offset == IPV6_FLOW_LABEL_OFFSET) {
+ continue;
+ }
+
+ // The checksum is in mNonLifetimes, but it's not a lifetime.
+ if (offset == ICMP6_RA_CHECKSUM_OFFSET) {
+ continue;
+ }
+
+ final int lifetimeLength = mNonLifetimes.get(i+1).first - offset;
+ final long optionLifetime;
+ switch (lifetimeLength) {
+ case 2:
+ optionLifetime = getUint16(byteBuffer, offset);
+ break;
+ case 4:
+ optionLifetime = getUint32(byteBuffer, offset);
+ break;
+ default:
+ throw new IllegalStateException("bogus lifetime size " + lifetimeLength);
+ }
+ minLifetime = Math.min(minLifetime, optionLifetime);
+ }
+ return minLifetime;
+ }
+
+ // How many seconds does this RA's have to live, taking into account the fact
+ // that we might have seen it a while ago.
+ long currentLifetime() {
+ return mMinLifetime - (currentTimeSeconds() - mLastSeen);
+ }
+
+ boolean isExpired() {
+ // TODO: We may want to handle 0 lifetime RAs differently, if they are common. We'll
+ // have to calculte the filter lifetime specially as a fraction of 0 is still 0.
+ return currentLifetime() <= 0;
+ }
+
+ // Append a filter for this RA to {@code gen}. Jump to DROP_LABEL if it should be dropped.
+ // Jump to the next filter if packet doesn't match this RA.
+ @GuardedBy("ApfFilter.this")
+ long generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException {
+ String nextFilterLabel = "Ra" + getUniqueNumberLocked();
+ // Skip if packet is not the right size
+ gen.addLoadFromMemory(Register.R0, gen.PACKET_SIZE_MEMORY_SLOT);
+ gen.addJumpIfR0NotEquals(mPacket.capacity(), nextFilterLabel);
+ int filterLifetime = (int)(currentLifetime() / FRACTION_OF_LIFETIME_TO_FILTER);
+ // Skip filter if expired
+ gen.addLoadFromMemory(Register.R0, gen.FILTER_AGE_MEMORY_SLOT);
+ gen.addJumpIfR0GreaterThan(filterLifetime, nextFilterLabel);
+ for (int i = 0; i < mNonLifetimes.size(); i++) {
+ // Generate code to match the packet bytes
+ Pair<Integer, Integer> nonLifetime = mNonLifetimes.get(i);
+ // Don't generate JNEBS instruction for 0 bytes as it always fails the
+ // ASSERT_FORWARD_IN_PROGRAM(pc + cmp_imm - 1) check where cmp_imm is
+ // the number of bytes to compare. nonLifetime is zero between the
+ // valid and preferred lifetimes in the prefix option.
+ if (nonLifetime.second != 0) {
+ gen.addLoadImmediate(Register.R0, nonLifetime.first);
+ gen.addJumpIfBytesNotEqual(Register.R0,
+ Arrays.copyOfRange(mPacket.array(), nonLifetime.first,
+ nonLifetime.first + nonLifetime.second),
+ nextFilterLabel);
+ }
+ // Generate code to test the lifetimes haven't gone down too far
+ if ((i + 1) < mNonLifetimes.size()) {
+ Pair<Integer, Integer> nextNonLifetime = mNonLifetimes.get(i + 1);
+ int offset = nonLifetime.first + nonLifetime.second;
+
+ // Skip the Flow label.
+ if (offset == IPV6_FLOW_LABEL_OFFSET) {
+ continue;
+ }
+ // Skip the checksum.
+ if (offset == ICMP6_RA_CHECKSUM_OFFSET) {
+ continue;
+ }
+ int length = nextNonLifetime.first - offset;
+ switch (length) {
+ case 4: gen.addLoad32(Register.R0, offset); break;
+ case 2: gen.addLoad16(Register.R0, offset); break;
+ default: throw new IllegalStateException("bogus lifetime size " + length);
+ }
+ gen.addJumpIfR0LessThan(filterLifetime, nextFilterLabel);
+ }
+ }
+ maybeSetupCounter(gen, Counter.DROPPED_RA);
+ gen.addJump(mCountAndDropLabel);
+ gen.defineLabel(nextFilterLabel);
+ return filterLifetime;
+ }
+ }
+
+ // Maximum number of RAs to filter for.
+ private static final int MAX_RAS = 10;
+
+ @GuardedBy("this")
+ private ArrayList<Ra> mRas = new ArrayList<Ra>();
+
+ // There is always some marginal benefit to updating the installed APF program when an RA is
+ // seen because we can extend the program's lifetime slightly, but there is some cost to
+ // updating the program, so don't bother unless the program is going to expire soon. This
+ // constant defines "soon" in seconds.
+ private static final long MAX_PROGRAM_LIFETIME_WORTH_REFRESHING = 30;
+ // We don't want to filter an RA for it's whole lifetime as it'll be expired by the time we ever
+ // see a refresh. Using half the lifetime might be a good idea except for the fact that
+ // packets may be dropped, so let's use 6.
+ private static final int FRACTION_OF_LIFETIME_TO_FILTER = 6;
+
+ // When did we last install a filter program? In seconds since Unix Epoch.
+ @GuardedBy("this")
+ private long mLastTimeInstalledProgram;
+ // How long should the last installed filter program live for? In seconds.
+ @GuardedBy("this")
+ private long mLastInstalledProgramMinLifetime;
+ @GuardedBy("this")
+ private ApfProgramEvent.Builder mLastInstallEvent;
+
+ // For debugging only. The last program installed.
+ @GuardedBy("this")
+ private byte[] mLastInstalledProgram;
+
+ /**
+ * For debugging only. Contains the latest APF buffer snapshot captured from the firmware.
+ *
+ * A typical size for this buffer is 4KB. It is present only if the WiFi HAL supports
+ * IWifiStaIface#readApfPacketFilterData(), and the APF interpreter advertised support for
+ * the opcodes to access the data buffer (LDDW and STDW).
+ */
+ @GuardedBy("this") @Nullable
+ private byte[] mDataSnapshot;
+
+ // How many times the program was updated since we started.
+ @GuardedBy("this")
+ private int mNumProgramUpdates = 0;
+ // How many times the program was updated since we started for allowing multicast traffic.
+ @GuardedBy("this")
+ private int mNumProgramUpdatesAllowingMulticast = 0;
+
+ /**
+ * Generate filter code to process ARP packets. Execution of this code ends in either the
+ * DROP_LABEL or PASS_LABEL and does not fall off the end.
+ * Preconditions:
+ * - Packet being filtered is ARP
+ */
+ @GuardedBy("this")
+ private void generateArpFilterLocked(ApfGenerator gen) throws IllegalInstructionException {
+ // Here's a basic summary of what the ARP filter program does:
+ //
+ // if not ARP IPv4
+ // pass
+ // if not ARP IPv4 reply or request
+ // pass
+ // if ARP reply source ip is 0.0.0.0
+ // drop
+ // if unicast ARP reply
+ // pass
+ // if interface has no IPv4 address
+ // if target ip is 0.0.0.0
+ // drop
+ // else
+ // if target ip is not the interface ip
+ // drop
+ // pass
+
+ final String checkTargetIPv4 = "checkTargetIPv4";
+
+ // Pass if not ARP IPv4.
+ gen.addLoadImmediate(Register.R0, ARP_HEADER_OFFSET);
+ maybeSetupCounter(gen, Counter.PASSED_ARP_NON_IPV4);
+ gen.addJumpIfBytesNotEqual(Register.R0, ARP_IPV4_HEADER, mCountAndPassLabel);
+
+ // Pass if unknown ARP opcode.
+ gen.addLoad16(Register.R0, ARP_OPCODE_OFFSET);
+ gen.addJumpIfR0Equals(ARP_OPCODE_REQUEST, checkTargetIPv4); // Skip to unicast check
+ maybeSetupCounter(gen, Counter.PASSED_ARP_UNKNOWN);
+ gen.addJumpIfR0NotEquals(ARP_OPCODE_REPLY, mCountAndPassLabel);
+
+ // Drop if ARP reply source IP is 0.0.0.0
+ gen.addLoad32(Register.R0, ARP_SOURCE_IP_ADDRESS_OFFSET);
+ maybeSetupCounter(gen, Counter.DROPPED_ARP_REPLY_SPA_NO_HOST);
+ gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, mCountAndDropLabel);
+
+ // Pass if unicast reply.
+ gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
+ maybeSetupCounter(gen, Counter.PASSED_ARP_UNICAST_REPLY);
+ gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, mCountAndPassLabel);
+
+ // Either a unicast request, a unicast reply, or a broadcast reply.
+ gen.defineLabel(checkTargetIPv4);
+ if (mIPv4Address == null) {
+ // When there is no IPv4 address, drop GARP replies (b/29404209).
+ gen.addLoad32(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET);
+ maybeSetupCounter(gen, Counter.DROPPED_GARP_REPLY);
+ gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, mCountAndDropLabel);
+ } else {
+ // When there is an IPv4 address, drop unicast/broadcast requests
+ // and broadcast replies with a different target IPv4 address.
+ gen.addLoadImmediate(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET);
+ maybeSetupCounter(gen, Counter.DROPPED_ARP_OTHER_HOST);
+ gen.addJumpIfBytesNotEqual(Register.R0, mIPv4Address, mCountAndDropLabel);
+ }
+
+ maybeSetupCounter(gen, Counter.PASSED_ARP);
+ gen.addJump(mCountAndPassLabel);
+ }
+
+ /**
+ * Generate filter code to process IPv4 packets. Execution of this code ends in either the
+ * DROP_LABEL or PASS_LABEL and does not fall off the end.
+ * Preconditions:
+ * - Packet being filtered is IPv4
+ */
+ @GuardedBy("this")
+ private void generateIPv4FilterLocked(ApfGenerator gen) throws IllegalInstructionException {
+ // Here's a basic summary of what the IPv4 filter program does:
+ //
+ // if filtering multicast (i.e. multicast lock not held):
+ // if it's DHCP destined to our MAC:
+ // pass
+ // if it's L2 broadcast:
+ // drop
+ // if it's IPv4 multicast:
+ // drop
+ // if it's IPv4 broadcast:
+ // drop
+ // pass
+
+ if (mMulticastFilter) {
+ final String skipDhcpv4Filter = "skip_dhcp_v4_filter";
+
+ // Pass DHCP addressed to us.
+ // Check it's UDP.
+ gen.addLoad8(Register.R0, IPV4_PROTOCOL_OFFSET);
+ gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipDhcpv4Filter);
+ // Check it's not a fragment. This matches the BPF filter installed by the DHCP client.
+ gen.addLoad16(Register.R0, IPV4_FRAGMENT_OFFSET_OFFSET);
+ gen.addJumpIfR0AnyBitsSet(IPV4_FRAGMENT_OFFSET_MASK, skipDhcpv4Filter);
+ // Check it's addressed to DHCP client port.
+ gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
+ gen.addLoad16Indexed(Register.R0, UDP_DESTINATION_PORT_OFFSET);
+ gen.addJumpIfR0NotEquals(DHCP_CLIENT_PORT, skipDhcpv4Filter);
+ // Check it's DHCP to our MAC address.
+ gen.addLoadImmediate(Register.R0, DHCP_CLIENT_MAC_OFFSET);
+ // NOTE: Relies on R1 containing IPv4 header offset.
+ gen.addAddR1();
+ gen.addJumpIfBytesNotEqual(Register.R0, mHardwareAddress, skipDhcpv4Filter);
+ maybeSetupCounter(gen, Counter.PASSED_DHCP);
+ gen.addJump(mCountAndPassLabel);
+
+ // Drop all multicasts/broadcasts.
+ gen.defineLabel(skipDhcpv4Filter);
+
+ // If IPv4 destination address is in multicast range, drop.
+ gen.addLoad8(Register.R0, IPV4_DEST_ADDR_OFFSET);
+ gen.addAnd(0xf0);
+ maybeSetupCounter(gen, Counter.DROPPED_IPV4_MULTICAST);
+ gen.addJumpIfR0Equals(0xe0, mCountAndDropLabel);
+
+ // If IPv4 broadcast packet, drop regardless of L2 (b/30231088).
+ maybeSetupCounter(gen, Counter.DROPPED_IPV4_BROADCAST_ADDR);
+ gen.addLoad32(Register.R0, IPV4_DEST_ADDR_OFFSET);
+ gen.addJumpIfR0Equals(IPV4_BROADCAST_ADDRESS, mCountAndDropLabel);
+ if (mIPv4Address != null && mIPv4PrefixLength < 31) {
+ maybeSetupCounter(gen, Counter.DROPPED_IPV4_BROADCAST_NET);
+ int broadcastAddr = ipv4BroadcastAddress(mIPv4Address, mIPv4PrefixLength);
+ gen.addJumpIfR0Equals(broadcastAddr, mCountAndDropLabel);
+ }
+
+ // If L2 broadcast packet, drop.
+ // TODO: can we invert this condition to fall through to the common pass case below?
+ maybeSetupCounter(gen, Counter.PASSED_IPV4_UNICAST);
+ gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
+ gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, mCountAndPassLabel);
+ maybeSetupCounter(gen, Counter.DROPPED_IPV4_L2_BROADCAST);
+ gen.addJump(mCountAndDropLabel);
+ }
+
+ // Otherwise, pass
+ maybeSetupCounter(gen, Counter.PASSED_IPV4);
+ gen.addJump(mCountAndPassLabel);
+ }
+
+
+ /**
+ * Generate filter code to process IPv6 packets. Execution of this code ends in either the
+ * DROP_LABEL or PASS_LABEL, or falls off the end for ICMPv6 packets.
+ * Preconditions:
+ * - Packet being filtered is IPv6
+ */
+ @GuardedBy("this")
+ private void generateIPv6FilterLocked(ApfGenerator gen) throws IllegalInstructionException {
+ // Here's a basic summary of what the IPv6 filter program does:
+ //
+ // if we're dropping multicast
+ // if it's not IPCMv6 or it's ICMPv6 but we're in doze mode:
+ // if it's multicast:
+ // drop
+ // pass
+ // if it's ICMPv6 RS to any:
+ // drop
+ // if it's ICMPv6 NA to ff02::1:
+ // drop
+
+ gen.addLoad8(Register.R0, IPV6_NEXT_HEADER_OFFSET);
+
+ // Drop multicast if the multicast filter is enabled.
+ if (mMulticastFilter) {
+ final String skipIPv6MulticastFilterLabel = "skipIPv6MulticastFilter";
+ final String dropAllIPv6MulticastsLabel = "dropAllIPv6Multicast";
+
+ // While in doze mode, drop ICMPv6 multicast pings, let the others pass.
+ // While awake, let all ICMPv6 multicasts through.
+ if (mInDozeMode) {
+ // Not ICMPv6? -> Proceed to multicast filtering
+ gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6, dropAllIPv6MulticastsLabel);
+
+ // ICMPv6 but not ECHO? -> Skip the multicast filter.
+ // (ICMPv6 ECHO requests will go through the multicast filter below).
+ gen.addLoad8(Register.R0, ICMP6_TYPE_OFFSET);
+ gen.addJumpIfR0NotEquals(ICMPV6_ECHO_REQUEST_TYPE, skipIPv6MulticastFilterLabel);
+ } else {
+ gen.addJumpIfR0Equals(IPPROTO_ICMPV6, skipIPv6MulticastFilterLabel);
+ }
+
+ // Drop all other packets sent to ff00::/8 (multicast prefix).
+ gen.defineLabel(dropAllIPv6MulticastsLabel);
+ maybeSetupCounter(gen, Counter.DROPPED_IPV6_NON_ICMP_MULTICAST);
+ gen.addLoad8(Register.R0, IPV6_DEST_ADDR_OFFSET);
+ gen.addJumpIfR0Equals(0xff, mCountAndDropLabel);
+ // Not multicast. Pass.
+ maybeSetupCounter(gen, Counter.PASSED_IPV6_UNICAST_NON_ICMP);
+ gen.addJump(mCountAndPassLabel);
+ gen.defineLabel(skipIPv6MulticastFilterLabel);
+ } else {
+ // If not ICMPv6, pass.
+ maybeSetupCounter(gen, Counter.PASSED_IPV6_NON_ICMP);
+ gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6, mCountAndPassLabel);
+ }
+
+ // If we got this far, the packet is ICMPv6. Drop some specific types.
+
+ // Add unsolicited multicast neighbor announcements filter
+ String skipUnsolicitedMulticastNALabel = "skipUnsolicitedMulticastNA";
+ gen.addLoad8(Register.R0, ICMP6_TYPE_OFFSET);
+ // Drop all router solicitations (b/32833400)
+ maybeSetupCounter(gen, Counter.DROPPED_IPV6_ROUTER_SOLICITATION);
+ gen.addJumpIfR0Equals(ICMPV6_ROUTER_SOLICITATION, mCountAndDropLabel);
+ // If not neighbor announcements, skip filter.
+ gen.addJumpIfR0NotEquals(ICMPV6_NEIGHBOR_ADVERTISEMENT, skipUnsolicitedMulticastNALabel);
+ // If to ff02::1, drop.
+ // TODO: Drop only if they don't contain the address of on-link neighbours.
+ gen.addLoadImmediate(Register.R0, IPV6_DEST_ADDR_OFFSET);
+ gen.addJumpIfBytesNotEqual(Register.R0, IPV6_ALL_NODES_ADDRESS,
+ skipUnsolicitedMulticastNALabel);
+ maybeSetupCounter(gen, Counter.DROPPED_IPV6_MULTICAST_NA);
+ gen.addJump(mCountAndDropLabel);
+ gen.defineLabel(skipUnsolicitedMulticastNALabel);
+ }
+
+ /**
+ * Begin generating an APF program to:
+ * <ul>
+ * <li>Drop/Pass 802.3 frames (based on policy)
+ * <li>Drop packets with EtherType within the Black List
+ * <li>Drop ARP requests not for us, if mIPv4Address is set,
+ * <li>Drop IPv4 broadcast packets, except DHCP destined to our MAC,
+ * <li>Drop IPv4 multicast packets, if mMulticastFilter,
+ * <li>Pass all other IPv4 packets,
+ * <li>Drop all broadcast non-IP non-ARP packets.
+ * <li>Pass all non-ICMPv6 IPv6 packets,
+ * <li>Pass all non-IPv4 and non-IPv6 packets,
+ * <li>Drop IPv6 ICMPv6 NAs to ff02::1.
+ * <li>Drop IPv6 ICMPv6 RSs.
+ * <li>Let execution continue off the end of the program for IPv6 ICMPv6 packets. This allows
+ * insertion of RA filters here, or if there aren't any, just passes the packets.
+ * </ul>
+ */
+ @GuardedBy("this")
+ private ApfGenerator emitPrologueLocked() throws IllegalInstructionException {
+ // This is guaranteed to succeed because of the check in maybeCreate.
+ ApfGenerator gen = new ApfGenerator(mApfCapabilities.apfVersionSupported);
+
+ if (mApfCapabilities.hasDataAccess()) {
+ // Increment TOTAL_PACKETS
+ maybeSetupCounter(gen, Counter.TOTAL_PACKETS);
+ gen.addLoadData(Register.R0, 0); // load counter
+ gen.addAdd(1);
+ gen.addStoreData(Register.R0, 0); // write-back counter
+ }
+
+ // Here's a basic summary of what the initial program does:
+ //
+ // if it's a 802.3 Frame (ethtype < 0x0600):
+ // drop or pass based on configurations
+ // if it has a ether-type that belongs to the black list
+ // drop
+ // if it's ARP:
+ // insert ARP filter to drop or pass these appropriately
+ // if it's IPv4:
+ // insert IPv4 filter to drop or pass these appropriately
+ // if it's not IPv6:
+ // if it's broadcast:
+ // drop
+ // pass
+ // insert IPv6 filter to drop, pass, or fall off the end for ICMPv6 packets
+
+ gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET);
+
+ if (mDrop802_3Frames) {
+ // drop 802.3 frames (ethtype < 0x0600)
+ maybeSetupCounter(gen, Counter.DROPPED_802_3_FRAME);
+ gen.addJumpIfR0LessThan(ETH_TYPE_MIN, mCountAndDropLabel);
+ }
+
+ // Handle ether-type black list
+ maybeSetupCounter(gen, Counter.DROPPED_ETHERTYPE_BLACKLISTED);
+ for (int p : mEthTypeBlackList) {
+ gen.addJumpIfR0Equals(p, mCountAndDropLabel);
+ }
+
+ // Add ARP filters:
+ String skipArpFiltersLabel = "skipArpFilters";
+ gen.addJumpIfR0NotEquals(ETH_P_ARP, skipArpFiltersLabel);
+ generateArpFilterLocked(gen);
+ gen.defineLabel(skipArpFiltersLabel);
+
+ // Add IPv4 filters:
+ String skipIPv4FiltersLabel = "skipIPv4Filters";
+ // NOTE: Relies on R0 containing ethertype. This is safe because if we got here, we did not
+ // execute the ARP filter, since that filter does not fall through, but either drops or
+ // passes.
+ gen.addJumpIfR0NotEquals(ETH_P_IP, skipIPv4FiltersLabel);
+ generateIPv4FilterLocked(gen);
+ gen.defineLabel(skipIPv4FiltersLabel);
+
+ // Check for IPv6:
+ // NOTE: Relies on R0 containing ethertype. This is safe because if we got here, we did not
+ // execute the ARP or IPv4 filters, since those filters do not fall through, but either
+ // drop or pass.
+ String ipv6FilterLabel = "IPv6Filters";
+ gen.addJumpIfR0Equals(ETH_P_IPV6, ipv6FilterLabel);
+
+ // Drop non-IP non-ARP broadcasts, pass the rest
+ gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
+ maybeSetupCounter(gen, Counter.PASSED_NON_IP_UNICAST);
+ gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, mCountAndPassLabel);
+ maybeSetupCounter(gen, Counter.DROPPED_ETH_BROADCAST);
+ gen.addJump(mCountAndDropLabel);
+
+ // Add IPv6 filters:
+ gen.defineLabel(ipv6FilterLabel);
+ generateIPv6FilterLocked(gen);
+ return gen;
+ }
+
+ /**
+ * Append packet counting epilogue to the APF program.
+ *
+ * Currently, the epilogue consists of two trampolines which count passed and dropped packets
+ * before jumping to the actual PASS and DROP labels.
+ */
+ @GuardedBy("this")
+ private void emitEpilogue(ApfGenerator gen) throws IllegalInstructionException {
+ // If APFv4 is unsupported, no epilogue is necessary: if execution reached this far, it
+ // will just fall-through to the PASS label.
+ if (!mApfCapabilities.hasDataAccess()) return;
+
+ // Execution will reach the bottom of the program if none of the filters match,
+ // which will pass the packet to the application processor.
+ maybeSetupCounter(gen, Counter.PASSED_IPV6_ICMP);
+
+ // Append the count & pass trampoline, which increments the counter at the data address
+ // pointed to by R1, then jumps to the pass label. This saves a few bytes over inserting
+ // the entire sequence inline for every counter.
+ gen.defineLabel(mCountAndPassLabel);
+ gen.addLoadData(Register.R0, 0); // R0 = *(R1 + 0)
+ gen.addAdd(1); // R0++
+ gen.addStoreData(Register.R0, 0); // *(R1 + 0) = R0
+ gen.addJump(gen.PASS_LABEL);
+
+ // Same as above for the count & drop trampoline.
+ gen.defineLabel(mCountAndDropLabel);
+ gen.addLoadData(Register.R0, 0); // R0 = *(R1 + 0)
+ gen.addAdd(1); // R0++
+ gen.addStoreData(Register.R0, 0); // *(R1 + 0) = R0
+ gen.addJump(gen.DROP_LABEL);
+ }
+
+ /**
+ * Generate and install a new filter program.
+ */
+ @GuardedBy("this")
+ @VisibleForTesting
+ void installNewProgramLocked() {
+ purgeExpiredRasLocked();
+ ArrayList<Ra> rasToFilter = new ArrayList<>();
+ final byte[] program;
+ long programMinLifetime = Long.MAX_VALUE;
+ long maximumApfProgramSize = mApfCapabilities.maximumApfProgramSize;
+ if (mApfCapabilities.hasDataAccess()) {
+ // Reserve space for the counters.
+ maximumApfProgramSize -= Counter.totalSize();
+ }
+
+ try {
+ // Step 1: Determine how many RA filters we can fit in the program.
+ ApfGenerator gen = emitPrologueLocked();
+
+ // The epilogue normally goes after the RA filters, but add it early to include its
+ // length when estimating the total.
+ emitEpilogue(gen);
+
+ // Can't fit the program even without any RA filters?
+ if (gen.programLengthOverEstimate() > maximumApfProgramSize) {
+ Log.e(TAG, "Program exceeds maximum size " + maximumApfProgramSize);
+ return;
+ }
+
+ for (Ra ra : mRas) {
+ ra.generateFilterLocked(gen);
+ // Stop if we get too big.
+ if (gen.programLengthOverEstimate() > maximumApfProgramSize) break;
+ rasToFilter.add(ra);
+ }
+
+ // Step 2: Actually generate the program
+ gen = emitPrologueLocked();
+ for (Ra ra : rasToFilter) {
+ programMinLifetime = Math.min(programMinLifetime, ra.generateFilterLocked(gen));
+ }
+ emitEpilogue(gen);
+ program = gen.generate();
+ } catch (IllegalInstructionException|IllegalStateException e) {
+ Log.e(TAG, "Failed to generate APF program.", e);
+ return;
+ }
+ final long now = currentTimeSeconds();
+ mLastTimeInstalledProgram = now;
+ mLastInstalledProgramMinLifetime = programMinLifetime;
+ mLastInstalledProgram = program;
+ mNumProgramUpdates++;
+
+ if (VDBG) {
+ hexDump("Installing filter: ", program, program.length);
+ }
+ mIpClientCallback.installPacketFilter(program);
+ logApfProgramEventLocked(now);
+ mLastInstallEvent = new ApfProgramEvent.Builder()
+ .setLifetime(programMinLifetime)
+ .setFilteredRas(rasToFilter.size())
+ .setCurrentRas(mRas.size())
+ .setProgramLength(program.length)
+ .setFlags(mIPv4Address != null, mMulticastFilter);
+ }
+
+ @GuardedBy("this")
+ private void logApfProgramEventLocked(long now) {
+ if (mLastInstallEvent == null) {
+ return;
+ }
+ ApfProgramEvent.Builder ev = mLastInstallEvent;
+ mLastInstallEvent = null;
+ final long actualLifetime = now - mLastTimeInstalledProgram;
+ ev.setActualLifetime(actualLifetime);
+ if (actualLifetime < APF_PROGRAM_EVENT_LIFETIME_THRESHOLD) {
+ return;
+ }
+ mMetricsLog.log(ev.build());
+ }
+
+ /**
+ * Returns {@code true} if a new program should be installed because the current one dies soon.
+ */
+ private boolean shouldInstallnewProgram() {
+ long expiry = mLastTimeInstalledProgram + mLastInstalledProgramMinLifetime;
+ return expiry < currentTimeSeconds() + MAX_PROGRAM_LIFETIME_WORTH_REFRESHING;
+ }
+
+ private void hexDump(String msg, byte[] packet, int length) {
+ log(msg + HexDump.toHexString(packet, 0, length, false /* lowercase */));
+ }
+
+ @GuardedBy("this")
+ private void purgeExpiredRasLocked() {
+ for (int i = 0; i < mRas.size();) {
+ if (mRas.get(i).isExpired()) {
+ log("Expiring " + mRas.get(i));
+ mRas.remove(i);
+ } else {
+ i++;
+ }
+ }
+ }
+
+ /**
+ * Process an RA packet, updating the list of known RAs and installing a new APF program
+ * if the current APF program should be updated.
+ * @return a ProcessRaResult enum describing what action was performed.
+ */
+ @VisibleForTesting
+ synchronized ProcessRaResult processRa(byte[] packet, int length) {
+ if (VDBG) hexDump("Read packet = ", packet, length);
+
+ // Have we seen this RA before?
+ for (int i = 0; i < mRas.size(); i++) {
+ Ra ra = mRas.get(i);
+ if (ra.matches(packet, length)) {
+ if (VDBG) log("matched RA " + ra);
+ // Update lifetimes.
+ ra.mLastSeen = currentTimeSeconds();
+ ra.mMinLifetime = ra.minLifetime(packet, length);
+ ra.seenCount++;
+
+ // Keep mRas in LRU order so as to prioritize generating filters for recently seen
+ // RAs. LRU prioritizes this because RA filters are generated in order from mRas
+ // until the filter program exceeds the maximum filter program size allowed by the
+ // chipset, so RAs appearing earlier in mRas are more likely to make it into the
+ // filter program.
+ // TODO: consider sorting the RAs in order of increasing expiry time as well.
+ // Swap to front of array.
+ mRas.add(0, mRas.remove(i));
+
+ // If the current program doesn't expire for a while, don't update.
+ if (shouldInstallnewProgram()) {
+ installNewProgramLocked();
+ return ProcessRaResult.UPDATE_EXPIRY;
+ }
+ return ProcessRaResult.MATCH;
+ }
+ }
+ purgeExpiredRasLocked();
+ // TODO: figure out how to proceed when we've received more then MAX_RAS RAs.
+ if (mRas.size() >= MAX_RAS) {
+ return ProcessRaResult.DROPPED;
+ }
+ final Ra ra;
+ try {
+ ra = new Ra(packet, length);
+ } catch (Exception e) {
+ Log.e(TAG, "Error parsing RA", e);
+ return ProcessRaResult.PARSE_ERROR;
+ }
+ // Ignore 0 lifetime RAs.
+ if (ra.isExpired()) {
+ return ProcessRaResult.ZERO_LIFETIME;
+ }
+ log("Adding " + ra);
+ mRas.add(ra);
+ installNewProgramLocked();
+ return ProcessRaResult.UPDATE_NEW_RA;
+ }
+
+ /**
+ * Create an {@link ApfFilter} if {@code apfCapabilities} indicates support for packet
+ * filtering using APF programs.
+ */
+ public static ApfFilter maybeCreate(Context context, ApfConfiguration config,
+ InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback) {
+ if (context == null || config == null || ifParams == null) return null;
+ ApfCapabilities apfCapabilities = config.apfCapabilities;
+ if (apfCapabilities == null) return null;
+ if (apfCapabilities.apfVersionSupported == 0) return null;
+ if (apfCapabilities.maximumApfProgramSize < 512) {
+ Log.e(TAG, "Unacceptably small APF limit: " + apfCapabilities.maximumApfProgramSize);
+ return null;
+ }
+ // For now only support generating programs for Ethernet frames. If this restriction is
+ // lifted:
+ // 1. the program generator will need its offsets adjusted.
+ // 2. the packet filter attached to our packet socket will need its offset adjusted.
+ if (apfCapabilities.apfPacketFormat != ARPHRD_ETHER) return null;
+ if (!ApfGenerator.supportsVersion(apfCapabilities.apfVersionSupported)) {
+ Log.e(TAG, "Unsupported APF version: " + apfCapabilities.apfVersionSupported);
+ return null;
+ }
+
+ return new ApfFilter(context, config, ifParams, ipClientCallback, new IpConnectivityLog());
+ }
+
+ public synchronized void shutdown() {
+ if (mReceiveThread != null) {
+ log("shutting down");
+ mReceiveThread.halt(); // Also closes socket.
+ mReceiveThread = null;
+ }
+ mRas.clear();
+ mContext.unregisterReceiver(mDeviceIdleReceiver);
+ }
+
+ public synchronized void setMulticastFilter(boolean isEnabled) {
+ if (mMulticastFilter == isEnabled) return;
+ mMulticastFilter = isEnabled;
+ if (!isEnabled) {
+ mNumProgramUpdatesAllowingMulticast++;
+ }
+ installNewProgramLocked();
+ }
+
+ @VisibleForTesting
+ public synchronized void setDozeMode(boolean isEnabled) {
+ if (mInDozeMode == isEnabled) return;
+ mInDozeMode = isEnabled;
+ installNewProgramLocked();
+ }
+
+ /** Find the single IPv4 LinkAddress if there is one, otherwise return null. */
+ private static LinkAddress findIPv4LinkAddress(LinkProperties lp) {
+ LinkAddress ipv4Address = null;
+ for (LinkAddress address : lp.getLinkAddresses()) {
+ if (!(address.getAddress() instanceof Inet4Address)) {
+ continue;
+ }
+ if (ipv4Address != null && !ipv4Address.isSameAddressAs(address)) {
+ // More than one IPv4 address, abort.
+ return null;
+ }
+ ipv4Address = address;
+ }
+ return ipv4Address;
+ }
+
+ public synchronized void setLinkProperties(LinkProperties lp) {
+ // NOTE: Do not keep a copy of LinkProperties as it would further duplicate state.
+ final LinkAddress ipv4Address = findIPv4LinkAddress(lp);
+ final byte[] addr = (ipv4Address != null) ? ipv4Address.getAddress().getAddress() : null;
+ final int prefix = (ipv4Address != null) ? ipv4Address.getPrefixLength() : 0;
+ if ((prefix == mIPv4PrefixLength) && Arrays.equals(addr, mIPv4Address)) {
+ return;
+ }
+ mIPv4Address = addr;
+ mIPv4PrefixLength = prefix;
+ installNewProgramLocked();
+ }
+
+ static public long counterValue(byte[] data, Counter counter)
+ throws ArrayIndexOutOfBoundsException {
+ // Follow the same wrap-around addressing scheme of the interpreter.
+ int offset = counter.offset();
+ if (offset < 0) {
+ offset = data.length + offset;
+ }
+
+ // Decode 32bit big-endian integer into a long so we can count up beyond 2^31.
+ long value = 0;
+ for (int i = 0; i < 4; i++) {
+ value = value << 8 | (data[offset] & 0xFF);
+ offset++;
+ }
+ return value;
+ }
+
+ public synchronized void dump(IndentingPrintWriter pw) {
+ pw.println("Capabilities: " + mApfCapabilities);
+ pw.println("Receive thread: " + (mReceiveThread != null ? "RUNNING" : "STOPPED"));
+ pw.println("Multicast: " + (mMulticastFilter ? "DROP" : "ALLOW"));
+ try {
+ pw.println("IPv4 address: " + InetAddress.getByAddress(mIPv4Address).getHostAddress());
+ } catch (UnknownHostException|NullPointerException e) {}
+
+ if (mLastTimeInstalledProgram == 0) {
+ pw.println("No program installed.");
+ return;
+ }
+ pw.println("Program updates: " + mNumProgramUpdates);
+ pw.println(String.format(
+ "Last program length %d, installed %ds ago, lifetime %ds",
+ mLastInstalledProgram.length, currentTimeSeconds() - mLastTimeInstalledProgram,
+ mLastInstalledProgramMinLifetime));
+
+ pw.println("RA filters:");
+ pw.increaseIndent();
+ for (Ra ra: mRas) {
+ pw.println(ra);
+ pw.increaseIndent();
+ pw.println(String.format(
+ "Seen: %d, last %ds ago", ra.seenCount, currentTimeSeconds() - ra.mLastSeen));
+ if (DBG) {
+ pw.println("Last match:");
+ pw.increaseIndent();
+ pw.println(ra.getLastMatchingPacket());
+ pw.decreaseIndent();
+ }
+ pw.decreaseIndent();
+ }
+ pw.decreaseIndent();
+
+ if (DBG) {
+ pw.println("Last program:");
+ pw.increaseIndent();
+ pw.println(HexDump.toHexString(mLastInstalledProgram, false /* lowercase */));
+ pw.decreaseIndent();
+ }
+
+ pw.println("APF packet counters: ");
+ pw.increaseIndent();
+ if (!mApfCapabilities.hasDataAccess()) {
+ pw.println("APF counters not supported");
+ } else if (mDataSnapshot == null) {
+ pw.println("No last snapshot.");
+ } else {
+ try {
+ Counter[] counters = Counter.class.getEnumConstants();
+ for (Counter c : Arrays.asList(counters).subList(1, counters.length)) {
+ long value = counterValue(mDataSnapshot, c);
+ // Only print non-zero counters
+ if (value != 0) {
+ pw.println(c.toString() + ": " + value);
+ }
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ pw.println("Uh-oh: " + e);
+ }
+ if (VDBG) {
+ pw.println("Raw data dump: ");
+ pw.println(HexDump.dumpHexString(mDataSnapshot));
+ }
+ }
+ pw.decreaseIndent();
+ }
+
+ // TODO: move to android.net.NetworkUtils
+ @VisibleForTesting
+ public static int ipv4BroadcastAddress(byte[] addrBytes, int prefixLength) {
+ return bytesToBEInt(addrBytes) | (int) (uint32(-1) >>> prefixLength);
+ }
+}
diff --git a/services/net/java/android/net/apf/ApfGenerator.java b/packages/NetworkStack/src/android/net/apf/ApfGenerator.java
similarity index 100%
rename from services/net/java/android/net/apf/ApfGenerator.java
rename to packages/NetworkStack/src/android/net/apf/ApfGenerator.java
diff --git a/services/net/java/android/net/dhcp/DhcpAckPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpAckPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpAckPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpAckPacket.java
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java
new file mode 100644
index 0000000..04ac9a3
--- /dev/null
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java
@@ -0,0 +1,1052 @@
+/*
+ * Copyright (C) 2015 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.dhcp;
+
+import static android.net.dhcp.DhcpPacket.DHCP_BROADCAST_ADDRESS;
+import static android.net.dhcp.DhcpPacket.DHCP_DNS_SERVER;
+import static android.net.dhcp.DhcpPacket.DHCP_DOMAIN_NAME;
+import static android.net.dhcp.DhcpPacket.DHCP_LEASE_TIME;
+import static android.net.dhcp.DhcpPacket.DHCP_MTU;
+import static android.net.dhcp.DhcpPacket.DHCP_REBINDING_TIME;
+import static android.net.dhcp.DhcpPacket.DHCP_RENEWAL_TIME;
+import static android.net.dhcp.DhcpPacket.DHCP_ROUTER;
+import static android.net.dhcp.DhcpPacket.DHCP_SUBNET_MASK;
+import static android.net.dhcp.DhcpPacket.DHCP_VENDOR_INFO;
+import static android.net.dhcp.DhcpPacket.INADDR_ANY;
+import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST;
+import static android.net.util.SocketUtils.makePacketSocketAddress;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_PACKET;
+import static android.system.OsConstants.ETH_P_IP;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOCK_RAW;
+import static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_BROADCAST;
+import static android.system.OsConstants.SO_RCVBUF;
+import static android.system.OsConstants.SO_REUSEADDR;
+
+import android.content.Context;
+import android.net.DhcpResults;
+import android.net.NetworkUtils;
+import android.net.TrafficStats;
+import android.net.ip.IpClient;
+import android.net.metrics.DhcpClientEvent;
+import android.net.metrics.DhcpErrorEvent;
+import android.net.metrics.IpConnectivityLog;
+import android.net.util.InterfaceParams;
+import android.net.util.SocketUtils;
+import android.os.Message;
+import android.os.SystemClock;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.util.HexDump;
+import com.android.internal.util.MessageUtils;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.internal.util.WakeupMessage;
+
+import libcore.io.IoBridge;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Random;
+
+/**
+ * A DHCPv4 client.
+ *
+ * Written to behave similarly to the DhcpStateMachine + dhcpcd 5.5.6 combination used in Android
+ * 5.1 and below, as configured on Nexus 6. The interface is the same as DhcpStateMachine.
+ *
+ * TODO:
+ *
+ * - Exponential backoff when receiving NAKs (not specified by the RFC, but current behaviour).
+ * - Support persisting lease state and support INIT-REBOOT. Android 5.1 does this, but it does not
+ * do so correctly: instead of requesting the lease last obtained on a particular network (e.g., a
+ * given SSID), it requests the last-leased IP address on the same interface, causing a delay if
+ * the server NAKs or a timeout if it doesn't.
+ *
+ * Known differences from current behaviour:
+ *
+ * - Does not request the "static routes" option.
+ * - Does not support BOOTP servers. DHCP has been around since 1993, should be everywhere now.
+ * - Requests the "broadcast" option, but does nothing with it.
+ * - Rejects invalid subnet masks such as 255.255.255.1 (current code treats that as 255.255.255.0).
+ *
+ * @hide
+ */
+public class DhcpClient extends StateMachine {
+
+ private static final String TAG = "DhcpClient";
+ private static final boolean DBG = true;
+ private static final boolean STATE_DBG = false;
+ private static final boolean MSG_DBG = false;
+ private static final boolean PACKET_DBG = false;
+
+ // Timers and timeouts.
+ private static final int SECONDS = 1000;
+ private static final int FIRST_TIMEOUT_MS = 2 * SECONDS;
+ private static final int MAX_TIMEOUT_MS = 128 * SECONDS;
+
+ // This is not strictly needed, since the client is asynchronous and implements exponential
+ // backoff. It's maintained for backwards compatibility with the previous DHCP code, which was
+ // a blocking operation with a 30-second timeout. We pick 36 seconds so we can send packets at
+ // t=0, t=2, t=6, t=14, t=30, allowing for 10% jitter.
+ private static final int DHCP_TIMEOUT_MS = 36 * SECONDS;
+
+ // DhcpClient uses IpClient's handler.
+ private static final int PUBLIC_BASE = IpClient.DHCPCLIENT_CMD_BASE;
+
+ /* Commands from controller to start/stop DHCP */
+ public static final int CMD_START_DHCP = PUBLIC_BASE + 1;
+ public static final int CMD_STOP_DHCP = PUBLIC_BASE + 2;
+
+ /* Notification from DHCP state machine prior to DHCP discovery/renewal */
+ public static final int CMD_PRE_DHCP_ACTION = PUBLIC_BASE + 3;
+ /* Notification from DHCP state machine post DHCP discovery/renewal. Indicates
+ * success/failure */
+ public static final int CMD_POST_DHCP_ACTION = PUBLIC_BASE + 4;
+ /* Notification from DHCP state machine before quitting */
+ public static final int CMD_ON_QUIT = PUBLIC_BASE + 5;
+
+ /* Command from controller to indicate DHCP discovery/renewal can continue
+ * after pre DHCP action is complete */
+ public static final int CMD_PRE_DHCP_ACTION_COMPLETE = PUBLIC_BASE + 6;
+
+ /* Command and event notification to/from IpManager requesting the setting
+ * (or clearing) of an IPv4 LinkAddress.
+ */
+ public static final int CMD_CLEAR_LINKADDRESS = PUBLIC_BASE + 7;
+ public static final int CMD_CONFIGURE_LINKADDRESS = PUBLIC_BASE + 8;
+ public static final int EVENT_LINKADDRESS_CONFIGURED = PUBLIC_BASE + 9;
+
+ /* Message.arg1 arguments to CMD_POST_DHCP_ACTION notification */
+ public static final int DHCP_SUCCESS = 1;
+ public static final int DHCP_FAILURE = 2;
+
+ // Internal messages.
+ private static final int PRIVATE_BASE = IpClient.DHCPCLIENT_CMD_BASE + 100;
+ private static final int CMD_KICK = PRIVATE_BASE + 1;
+ private static final int CMD_RECEIVED_PACKET = PRIVATE_BASE + 2;
+ private static final int CMD_TIMEOUT = PRIVATE_BASE + 3;
+ private static final int CMD_RENEW_DHCP = PRIVATE_BASE + 4;
+ private static final int CMD_REBIND_DHCP = PRIVATE_BASE + 5;
+ private static final int CMD_EXPIRE_DHCP = PRIVATE_BASE + 6;
+
+ // For message logging.
+ private static final Class[] sMessageClasses = { DhcpClient.class };
+ private static final SparseArray<String> sMessageNames =
+ MessageUtils.findMessageNames(sMessageClasses);
+
+ // DHCP parameters that we request.
+ /* package */ static final byte[] REQUESTED_PARAMS = new byte[] {
+ DHCP_SUBNET_MASK,
+ DHCP_ROUTER,
+ DHCP_DNS_SERVER,
+ DHCP_DOMAIN_NAME,
+ DHCP_MTU,
+ DHCP_BROADCAST_ADDRESS, // TODO: currently ignored.
+ DHCP_LEASE_TIME,
+ DHCP_RENEWAL_TIME,
+ DHCP_REBINDING_TIME,
+ DHCP_VENDOR_INFO,
+ };
+
+ // DHCP flag that means "yes, we support unicast."
+ private static final boolean DO_UNICAST = false;
+
+ // System services / libraries we use.
+ private final Context mContext;
+ private final Random mRandom;
+ private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
+
+ // Sockets.
+ // - We use a packet socket to receive, because servers send us packets bound for IP addresses
+ // which we have not yet configured, and the kernel protocol stack drops these.
+ // - We use a UDP socket to send, so the kernel handles ARP and routing for us (DHCP servers can
+ // be off-link as well as on-link).
+ private FileDescriptor mPacketSock;
+ private FileDescriptor mUdpSock;
+ private ReceiveThread mReceiveThread;
+
+ // State variables.
+ private final StateMachine mController;
+ private final WakeupMessage mKickAlarm;
+ private final WakeupMessage mTimeoutAlarm;
+ private final WakeupMessage mRenewAlarm;
+ private final WakeupMessage mRebindAlarm;
+ private final WakeupMessage mExpiryAlarm;
+ private final String mIfaceName;
+
+ private boolean mRegisteredForPreDhcpNotification;
+ private InterfaceParams mIface;
+ // TODO: MacAddress-ify more of this class hierarchy.
+ private byte[] mHwAddr;
+ private SocketAddress mInterfaceBroadcastAddr;
+ private int mTransactionId;
+ private long mTransactionStartMillis;
+ private DhcpResults mDhcpLease;
+ private long mDhcpLeaseExpiry;
+ private DhcpResults mOffer;
+
+ // Milliseconds SystemClock timestamps used to record transition times to DhcpBoundState.
+ private long mLastInitEnterTime;
+ private long mLastBoundExitTime;
+
+ // States.
+ private State mStoppedState = new StoppedState();
+ private State mDhcpState = new DhcpState();
+ private State mDhcpInitState = new DhcpInitState();
+ private State mDhcpSelectingState = new DhcpSelectingState();
+ private State mDhcpRequestingState = new DhcpRequestingState();
+ private State mDhcpHaveLeaseState = new DhcpHaveLeaseState();
+ private State mConfiguringInterfaceState = new ConfiguringInterfaceState();
+ private State mDhcpBoundState = new DhcpBoundState();
+ private State mDhcpRenewingState = new DhcpRenewingState();
+ private State mDhcpRebindingState = new DhcpRebindingState();
+ private State mDhcpInitRebootState = new DhcpInitRebootState();
+ private State mDhcpRebootingState = new DhcpRebootingState();
+ private State mWaitBeforeStartState = new WaitBeforeStartState(mDhcpInitState);
+ private State mWaitBeforeRenewalState = new WaitBeforeRenewalState(mDhcpRenewingState);
+
+ private WakeupMessage makeWakeupMessage(String cmdName, int cmd) {
+ cmdName = DhcpClient.class.getSimpleName() + "." + mIfaceName + "." + cmdName;
+ return new WakeupMessage(mContext, getHandler(), cmdName, cmd);
+ }
+
+ // TODO: Take an InterfaceParams instance instead of an interface name String.
+ private DhcpClient(Context context, StateMachine controller, String iface) {
+ super(TAG, controller.getHandler());
+
+ mContext = context;
+ mController = controller;
+ mIfaceName = iface;
+
+ addState(mStoppedState);
+ addState(mDhcpState);
+ addState(mDhcpInitState, mDhcpState);
+ addState(mWaitBeforeStartState, mDhcpState);
+ addState(mDhcpSelectingState, mDhcpState);
+ addState(mDhcpRequestingState, mDhcpState);
+ addState(mDhcpHaveLeaseState, mDhcpState);
+ addState(mConfiguringInterfaceState, mDhcpHaveLeaseState);
+ addState(mDhcpBoundState, mDhcpHaveLeaseState);
+ addState(mWaitBeforeRenewalState, mDhcpHaveLeaseState);
+ addState(mDhcpRenewingState, mDhcpHaveLeaseState);
+ addState(mDhcpRebindingState, mDhcpHaveLeaseState);
+ addState(mDhcpInitRebootState, mDhcpState);
+ addState(mDhcpRebootingState, mDhcpState);
+
+ setInitialState(mStoppedState);
+
+ mRandom = new Random();
+
+ // Used to schedule packet retransmissions.
+ mKickAlarm = makeWakeupMessage("KICK", CMD_KICK);
+ // Used to time out PacketRetransmittingStates.
+ mTimeoutAlarm = makeWakeupMessage("TIMEOUT", CMD_TIMEOUT);
+ // Used to schedule DHCP reacquisition.
+ mRenewAlarm = makeWakeupMessage("RENEW", CMD_RENEW_DHCP);
+ mRebindAlarm = makeWakeupMessage("REBIND", CMD_REBIND_DHCP);
+ mExpiryAlarm = makeWakeupMessage("EXPIRY", CMD_EXPIRE_DHCP);
+ }
+
+ public void registerForPreDhcpNotification() {
+ mRegisteredForPreDhcpNotification = true;
+ }
+
+ public static DhcpClient makeDhcpClient(
+ Context context, StateMachine controller, InterfaceParams ifParams) {
+ DhcpClient client = new DhcpClient(context, controller, ifParams.name);
+ client.mIface = ifParams;
+ client.start();
+ return client;
+ }
+
+ private boolean initInterface() {
+ if (mIface == null) mIface = InterfaceParams.getByName(mIfaceName);
+ if (mIface == null) {
+ Log.e(TAG, "Can't determine InterfaceParams for " + mIfaceName);
+ return false;
+ }
+
+ mHwAddr = mIface.macAddr.toByteArray();
+ mInterfaceBroadcastAddr = makePacketSocketAddress(mIface.index, DhcpPacket.ETHER_BROADCAST);
+ return true;
+ }
+
+ private void startNewTransaction() {
+ mTransactionId = mRandom.nextInt();
+ mTransactionStartMillis = SystemClock.elapsedRealtime();
+ }
+
+ private boolean initSockets() {
+ return initPacketSocket() && initUdpSocket();
+ }
+
+ private boolean initPacketSocket() {
+ try {
+ mPacketSock = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IP);
+ SocketAddress addr = makePacketSocketAddress((short) ETH_P_IP, mIface.index);
+ Os.bind(mPacketSock, addr);
+ NetworkUtils.attachDhcpFilter(mPacketSock);
+ } catch(SocketException|ErrnoException e) {
+ Log.e(TAG, "Error creating packet socket", e);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean initUdpSocket() {
+ final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_DHCP);
+ try {
+ mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ SocketUtils.bindSocketToInterface(mUdpSock, mIfaceName);
+ Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1);
+ Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1);
+ Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_RCVBUF, 0);
+ Os.bind(mUdpSock, Inet4Address.ANY, DhcpPacket.DHCP_CLIENT);
+ } catch(SocketException|ErrnoException e) {
+ Log.e(TAG, "Error creating UDP socket", e);
+ return false;
+ } finally {
+ TrafficStats.setThreadStatsTag(oldTag);
+ }
+ return true;
+ }
+
+ private boolean connectUdpSock(Inet4Address to) {
+ try {
+ Os.connect(mUdpSock, to, DhcpPacket.DHCP_SERVER);
+ return true;
+ } catch (SocketException|ErrnoException e) {
+ Log.e(TAG, "Error connecting UDP socket", e);
+ return false;
+ }
+ }
+
+ private static void closeQuietly(FileDescriptor fd) {
+ try {
+ IoBridge.closeAndSignalBlockedThreads(fd);
+ } catch (IOException ignored) {}
+ }
+
+ private void closeSockets() {
+ closeQuietly(mUdpSock);
+ closeQuietly(mPacketSock);
+ }
+
+ class ReceiveThread extends Thread {
+
+ private final byte[] mPacket = new byte[DhcpPacket.MAX_LENGTH];
+ private volatile boolean mStopped = false;
+
+ public void halt() {
+ mStopped = true;
+ closeSockets(); // Interrupts the read() call the thread is blocked in.
+ }
+
+ @Override
+ public void run() {
+ if (DBG) Log.d(TAG, "Receive thread started");
+ while (!mStopped) {
+ int length = 0; // Or compiler can't tell it's initialized if a parse error occurs.
+ try {
+ length = Os.read(mPacketSock, mPacket, 0, mPacket.length);
+ DhcpPacket packet = null;
+ packet = DhcpPacket.decodeFullPacket(mPacket, length, DhcpPacket.ENCAP_L2);
+ if (DBG) Log.d(TAG, "Received packet: " + packet);
+ sendMessage(CMD_RECEIVED_PACKET, packet);
+ } catch (IOException|ErrnoException e) {
+ if (!mStopped) {
+ Log.e(TAG, "Read error", e);
+ logError(DhcpErrorEvent.RECEIVE_ERROR);
+ }
+ } catch (DhcpPacket.ParseException e) {
+ Log.e(TAG, "Can't parse packet: " + e.getMessage());
+ if (PACKET_DBG) {
+ Log.d(TAG, HexDump.dumpHexString(mPacket, 0, length));
+ }
+ if (e.errorCode == DhcpErrorEvent.DHCP_NO_COOKIE) {
+ int snetTagId = 0x534e4554;
+ String bugId = "31850211";
+ int uid = -1;
+ String data = DhcpPacket.ParseException.class.getName();
+ EventLog.writeEvent(snetTagId, bugId, uid, data);
+ }
+ logError(e.errorCode);
+ }
+ }
+ if (DBG) Log.d(TAG, "Receive thread stopped");
+ }
+ }
+
+ private short getSecs() {
+ return (short) ((SystemClock.elapsedRealtime() - mTransactionStartMillis) / 1000);
+ }
+
+ private boolean transmitPacket(ByteBuffer buf, String description, int encap, Inet4Address to) {
+ try {
+ if (encap == DhcpPacket.ENCAP_L2) {
+ if (DBG) Log.d(TAG, "Broadcasting " + description);
+ Os.sendto(mPacketSock, buf.array(), 0, buf.limit(), 0, mInterfaceBroadcastAddr);
+ } else if (encap == DhcpPacket.ENCAP_BOOTP && to.equals(INADDR_BROADCAST)) {
+ if (DBG) Log.d(TAG, "Broadcasting " + description);
+ // We only send L3-encapped broadcasts in DhcpRebindingState,
+ // where we have an IP address and an unconnected UDP socket.
+ //
+ // N.B.: We only need this codepath because DhcpRequestPacket
+ // hardcodes the source IP address to 0.0.0.0. We could reuse
+ // the packet socket if this ever changes.
+ Os.sendto(mUdpSock, buf, 0, to, DhcpPacket.DHCP_SERVER);
+ } else {
+ // It's safe to call getpeername here, because we only send unicast packets if we
+ // have an IP address, and we connect the UDP socket in DhcpBoundState#enter.
+ if (DBG) Log.d(TAG, String.format("Unicasting %s to %s",
+ description, Os.getpeername(mUdpSock)));
+ Os.write(mUdpSock, buf);
+ }
+ } catch(ErrnoException|IOException e) {
+ Log.e(TAG, "Can't send packet: ", e);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean sendDiscoverPacket() {
+ ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
+ DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr,
+ DO_UNICAST, REQUESTED_PARAMS);
+ return transmitPacket(packet, "DHCPDISCOVER", DhcpPacket.ENCAP_L2, INADDR_BROADCAST);
+ }
+
+ private boolean sendRequestPacket(
+ Inet4Address clientAddress, Inet4Address requestedAddress,
+ Inet4Address serverAddress, Inet4Address to) {
+ // TODO: should we use the transaction ID from the server?
+ final int encap = INADDR_ANY.equals(clientAddress)
+ ? DhcpPacket.ENCAP_L2 : DhcpPacket.ENCAP_BOOTP;
+
+ ByteBuffer packet = DhcpPacket.buildRequestPacket(
+ encap, mTransactionId, getSecs(), clientAddress,
+ DO_UNICAST, mHwAddr, requestedAddress,
+ serverAddress, REQUESTED_PARAMS, null);
+ String serverStr = (serverAddress != null) ? serverAddress.getHostAddress() : null;
+ String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() +
+ " request=" + requestedAddress.getHostAddress() +
+ " serverid=" + serverStr;
+ return transmitPacket(packet, description, encap, to);
+ }
+
+ private void scheduleLeaseTimers() {
+ if (mDhcpLeaseExpiry == 0) {
+ Log.d(TAG, "Infinite lease, no timer scheduling needed");
+ return;
+ }
+
+ final long now = SystemClock.elapsedRealtime();
+
+ // TODO: consider getting the renew and rebind timers from T1 and T2.
+ // See also:
+ // https://tools.ietf.org/html/rfc2131#section-4.4.5
+ // https://tools.ietf.org/html/rfc1533#section-9.9
+ // https://tools.ietf.org/html/rfc1533#section-9.10
+ final long remainingDelay = mDhcpLeaseExpiry - now;
+ final long renewDelay = remainingDelay / 2;
+ final long rebindDelay = remainingDelay * 7 / 8;
+ mRenewAlarm.schedule(now + renewDelay);
+ mRebindAlarm.schedule(now + rebindDelay);
+ mExpiryAlarm.schedule(now + remainingDelay);
+ Log.d(TAG, "Scheduling renewal in " + (renewDelay / 1000) + "s");
+ Log.d(TAG, "Scheduling rebind in " + (rebindDelay / 1000) + "s");
+ Log.d(TAG, "Scheduling expiry in " + (remainingDelay / 1000) + "s");
+ }
+
+ private void notifySuccess() {
+ mController.sendMessage(
+ CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, new DhcpResults(mDhcpLease));
+ }
+
+ private void notifyFailure() {
+ mController.sendMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0, null);
+ }
+
+ private void acceptDhcpResults(DhcpResults results, String msg) {
+ mDhcpLease = results;
+ mOffer = null;
+ Log.d(TAG, msg + " lease: " + mDhcpLease);
+ notifySuccess();
+ }
+
+ private void clearDhcpState() {
+ mDhcpLease = null;
+ mDhcpLeaseExpiry = 0;
+ mOffer = null;
+ }
+
+ /**
+ * Quit the DhcpStateMachine.
+ *
+ * @hide
+ */
+ public void doQuit() {
+ Log.d(TAG, "doQuit");
+ quit();
+ }
+
+ @Override
+ protected void onQuitting() {
+ Log.d(TAG, "onQuitting");
+ mController.sendMessage(CMD_ON_QUIT);
+ }
+
+ abstract class LoggingState extends State {
+ private long mEnterTimeMs;
+
+ @Override
+ public void enter() {
+ if (STATE_DBG) Log.d(TAG, "Entering state " + getName());
+ mEnterTimeMs = SystemClock.elapsedRealtime();
+ }
+
+ @Override
+ public void exit() {
+ long durationMs = SystemClock.elapsedRealtime() - mEnterTimeMs;
+ logState(getName(), (int) durationMs);
+ }
+
+ private String messageName(int what) {
+ return sMessageNames.get(what, Integer.toString(what));
+ }
+
+ private String messageToString(Message message) {
+ long now = SystemClock.uptimeMillis();
+ return new StringBuilder(" ")
+ .append(message.getWhen() - now)
+ .append(messageName(message.what))
+ .append(" ").append(message.arg1)
+ .append(" ").append(message.arg2)
+ .append(" ").append(message.obj)
+ .toString();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (MSG_DBG) {
+ Log.d(TAG, getName() + messageToString(message));
+ }
+ return NOT_HANDLED;
+ }
+
+ @Override
+ public String getName() {
+ // All DhcpClient's states are inner classes with a well defined name.
+ // Use getSimpleName() and avoid super's getName() creating new String instances.
+ return getClass().getSimpleName();
+ }
+ }
+
+ // Sends CMD_PRE_DHCP_ACTION to the controller, waits for the controller to respond with
+ // CMD_PRE_DHCP_ACTION_COMPLETE, and then transitions to mOtherState.
+ abstract class WaitBeforeOtherState extends LoggingState {
+ protected State mOtherState;
+
+ @Override
+ public void enter() {
+ super.enter();
+ mController.sendMessage(CMD_PRE_DHCP_ACTION);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ super.processMessage(message);
+ switch (message.what) {
+ case CMD_PRE_DHCP_ACTION_COMPLETE:
+ transitionTo(mOtherState);
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ class StoppedState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case CMD_START_DHCP:
+ if (mRegisteredForPreDhcpNotification) {
+ transitionTo(mWaitBeforeStartState);
+ } else {
+ transitionTo(mDhcpInitState);
+ }
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ class WaitBeforeStartState extends WaitBeforeOtherState {
+ public WaitBeforeStartState(State otherState) {
+ super();
+ mOtherState = otherState;
+ }
+ }
+
+ class WaitBeforeRenewalState extends WaitBeforeOtherState {
+ public WaitBeforeRenewalState(State otherState) {
+ super();
+ mOtherState = otherState;
+ }
+ }
+
+ class DhcpState extends State {
+ @Override
+ public void enter() {
+ clearDhcpState();
+ if (initInterface() && initSockets()) {
+ mReceiveThread = new ReceiveThread();
+ mReceiveThread.start();
+ } else {
+ notifyFailure();
+ transitionTo(mStoppedState);
+ }
+ }
+
+ @Override
+ public void exit() {
+ if (mReceiveThread != null) {
+ mReceiveThread.halt(); // Also closes sockets.
+ mReceiveThread = null;
+ }
+ clearDhcpState();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ super.processMessage(message);
+ switch (message.what) {
+ case CMD_STOP_DHCP:
+ transitionTo(mStoppedState);
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ public boolean isValidPacket(DhcpPacket packet) {
+ // TODO: check checksum.
+ int xid = packet.getTransactionId();
+ if (xid != mTransactionId) {
+ Log.d(TAG, "Unexpected transaction ID " + xid + ", expected " + mTransactionId);
+ return false;
+ }
+ if (!Arrays.equals(packet.getClientMac(), mHwAddr)) {
+ Log.d(TAG, "MAC addr mismatch: got " +
+ HexDump.toHexString(packet.getClientMac()) + ", expected " +
+ HexDump.toHexString(packet.getClientMac()));
+ return false;
+ }
+ return true;
+ }
+
+ public void setDhcpLeaseExpiry(DhcpPacket packet) {
+ long leaseTimeMillis = packet.getLeaseTimeMillis();
+ mDhcpLeaseExpiry =
+ (leaseTimeMillis > 0) ? SystemClock.elapsedRealtime() + leaseTimeMillis : 0;
+ }
+
+ /**
+ * Retransmits packets using jittered exponential backoff with an optional timeout. Packet
+ * transmission is triggered by CMD_KICK, which is sent by an AlarmManager alarm. If a subclass
+ * sets mTimeout to a positive value, then timeout() is called by an AlarmManager alarm mTimeout
+ * milliseconds after entering the state. Kicks and timeouts are cancelled when leaving the
+ * state.
+ *
+ * Concrete subclasses must implement sendPacket, which is called when the alarm fires and a
+ * packet needs to be transmitted, and receivePacket, which is triggered by CMD_RECEIVED_PACKET
+ * sent by the receive thread. They may also set mTimeout and implement timeout.
+ */
+ abstract class PacketRetransmittingState extends LoggingState {
+
+ private int mTimer;
+ protected int mTimeout = 0;
+
+ @Override
+ public void enter() {
+ super.enter();
+ initTimer();
+ maybeInitTimeout();
+ sendMessage(CMD_KICK);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ super.processMessage(message);
+ switch (message.what) {
+ case CMD_KICK:
+ sendPacket();
+ scheduleKick();
+ return HANDLED;
+ case CMD_RECEIVED_PACKET:
+ receivePacket((DhcpPacket) message.obj);
+ return HANDLED;
+ case CMD_TIMEOUT:
+ timeout();
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+
+ @Override
+ public void exit() {
+ super.exit();
+ mKickAlarm.cancel();
+ mTimeoutAlarm.cancel();
+ }
+
+ abstract protected boolean sendPacket();
+ abstract protected void receivePacket(DhcpPacket packet);
+ protected void timeout() {}
+
+ protected void initTimer() {
+ mTimer = FIRST_TIMEOUT_MS;
+ }
+
+ protected int jitterTimer(int baseTimer) {
+ int maxJitter = baseTimer / 10;
+ int jitter = mRandom.nextInt(2 * maxJitter) - maxJitter;
+ return baseTimer + jitter;
+ }
+
+ protected void scheduleKick() {
+ long now = SystemClock.elapsedRealtime();
+ long timeout = jitterTimer(mTimer);
+ long alarmTime = now + timeout;
+ mKickAlarm.schedule(alarmTime);
+ mTimer *= 2;
+ if (mTimer > MAX_TIMEOUT_MS) {
+ mTimer = MAX_TIMEOUT_MS;
+ }
+ }
+
+ protected void maybeInitTimeout() {
+ if (mTimeout > 0) {
+ long alarmTime = SystemClock.elapsedRealtime() + mTimeout;
+ mTimeoutAlarm.schedule(alarmTime);
+ }
+ }
+ }
+
+ class DhcpInitState extends PacketRetransmittingState {
+ public DhcpInitState() {
+ super();
+ }
+
+ @Override
+ public void enter() {
+ super.enter();
+ startNewTransaction();
+ mLastInitEnterTime = SystemClock.elapsedRealtime();
+ }
+
+ protected boolean sendPacket() {
+ return sendDiscoverPacket();
+ }
+
+ protected void receivePacket(DhcpPacket packet) {
+ if (!isValidPacket(packet)) return;
+ if (!(packet instanceof DhcpOfferPacket)) return;
+ mOffer = packet.toDhcpResults();
+ if (mOffer != null) {
+ Log.d(TAG, "Got pending lease: " + mOffer);
+ transitionTo(mDhcpRequestingState);
+ }
+ }
+ }
+
+ // Not implemented. We request the first offer we receive.
+ class DhcpSelectingState extends LoggingState {
+ }
+
+ class DhcpRequestingState extends PacketRetransmittingState {
+ public DhcpRequestingState() {
+ mTimeout = DHCP_TIMEOUT_MS / 2;
+ }
+
+ protected boolean sendPacket() {
+ return sendRequestPacket(
+ INADDR_ANY, // ciaddr
+ (Inet4Address) mOffer.ipAddress.getAddress(), // DHCP_REQUESTED_IP
+ (Inet4Address) mOffer.serverAddress, // DHCP_SERVER_IDENTIFIER
+ INADDR_BROADCAST); // packet destination address
+ }
+
+ protected void receivePacket(DhcpPacket packet) {
+ if (!isValidPacket(packet)) return;
+ if ((packet instanceof DhcpAckPacket)) {
+ DhcpResults results = packet.toDhcpResults();
+ if (results != null) {
+ setDhcpLeaseExpiry(packet);
+ acceptDhcpResults(results, "Confirmed");
+ transitionTo(mConfiguringInterfaceState);
+ }
+ } else if (packet instanceof DhcpNakPacket) {
+ // TODO: Wait a while before returning into INIT state.
+ Log.d(TAG, "Received NAK, returning to INIT");
+ mOffer = null;
+ transitionTo(mDhcpInitState);
+ }
+ }
+
+ @Override
+ protected void timeout() {
+ // After sending REQUESTs unsuccessfully for a while, go back to init.
+ transitionTo(mDhcpInitState);
+ }
+ }
+
+ class DhcpHaveLeaseState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case CMD_EXPIRE_DHCP:
+ Log.d(TAG, "Lease expired!");
+ notifyFailure();
+ transitionTo(mDhcpInitState);
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+
+ @Override
+ public void exit() {
+ // Clear any extant alarms.
+ mRenewAlarm.cancel();
+ mRebindAlarm.cancel();
+ mExpiryAlarm.cancel();
+ clearDhcpState();
+ // Tell IpManager to clear the IPv4 address. There is no need to
+ // wait for confirmation since any subsequent packets are sent from
+ // INADDR_ANY anyway (DISCOVER, REQUEST).
+ mController.sendMessage(CMD_CLEAR_LINKADDRESS);
+ }
+ }
+
+ class ConfiguringInterfaceState extends LoggingState {
+ @Override
+ public void enter() {
+ super.enter();
+ mController.sendMessage(CMD_CONFIGURE_LINKADDRESS, mDhcpLease.ipAddress);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ super.processMessage(message);
+ switch (message.what) {
+ case EVENT_LINKADDRESS_CONFIGURED:
+ transitionTo(mDhcpBoundState);
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ class DhcpBoundState extends LoggingState {
+ @Override
+ public void enter() {
+ super.enter();
+ if (mDhcpLease.serverAddress != null && !connectUdpSock(mDhcpLease.serverAddress)) {
+ // There's likely no point in going into DhcpInitState here, we'll probably
+ // just repeat the transaction, get the same IP address as before, and fail.
+ //
+ // NOTE: It is observed that connectUdpSock() basically never fails, due to
+ // SO_BINDTODEVICE. Examining the local socket address shows it will happily
+ // return an IPv4 address from another interface, or even return "0.0.0.0".
+ //
+ // TODO: Consider deleting this check, following testing on several kernels.
+ notifyFailure();
+ transitionTo(mStoppedState);
+ }
+
+ scheduleLeaseTimers();
+ logTimeToBoundState();
+ }
+
+ @Override
+ public void exit() {
+ super.exit();
+ mLastBoundExitTime = SystemClock.elapsedRealtime();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ super.processMessage(message);
+ switch (message.what) {
+ case CMD_RENEW_DHCP:
+ if (mRegisteredForPreDhcpNotification) {
+ transitionTo(mWaitBeforeRenewalState);
+ } else {
+ transitionTo(mDhcpRenewingState);
+ }
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+
+ private void logTimeToBoundState() {
+ long now = SystemClock.elapsedRealtime();
+ if (mLastBoundExitTime > mLastInitEnterTime) {
+ logState(DhcpClientEvent.RENEWING_BOUND, (int)(now - mLastBoundExitTime));
+ } else {
+ logState(DhcpClientEvent.INITIAL_BOUND, (int)(now - mLastInitEnterTime));
+ }
+ }
+ }
+
+ abstract class DhcpReacquiringState extends PacketRetransmittingState {
+ protected String mLeaseMsg;
+
+ @Override
+ public void enter() {
+ super.enter();
+ startNewTransaction();
+ }
+
+ abstract protected Inet4Address packetDestination();
+
+ protected boolean sendPacket() {
+ return sendRequestPacket(
+ (Inet4Address) mDhcpLease.ipAddress.getAddress(), // ciaddr
+ INADDR_ANY, // DHCP_REQUESTED_IP
+ null, // DHCP_SERVER_IDENTIFIER
+ packetDestination()); // packet destination address
+ }
+
+ protected void receivePacket(DhcpPacket packet) {
+ if (!isValidPacket(packet)) return;
+ if ((packet instanceof DhcpAckPacket)) {
+ final DhcpResults results = packet.toDhcpResults();
+ if (results != null) {
+ if (!mDhcpLease.ipAddress.equals(results.ipAddress)) {
+ Log.d(TAG, "Renewed lease not for our current IP address!");
+ notifyFailure();
+ transitionTo(mDhcpInitState);
+ }
+ setDhcpLeaseExpiry(packet);
+ // Updating our notion of DhcpResults here only causes the
+ // DNS servers and routes to be updated in LinkProperties
+ // in IpManager and by any overridden relevant handlers of
+ // the registered IpManager.Callback. IP address changes
+ // are not supported here.
+ acceptDhcpResults(results, mLeaseMsg);
+ transitionTo(mDhcpBoundState);
+ }
+ } else if (packet instanceof DhcpNakPacket) {
+ Log.d(TAG, "Received NAK, returning to INIT");
+ notifyFailure();
+ transitionTo(mDhcpInitState);
+ }
+ }
+ }
+
+ class DhcpRenewingState extends DhcpReacquiringState {
+ public DhcpRenewingState() {
+ mLeaseMsg = "Renewed";
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (super.processMessage(message) == HANDLED) {
+ return HANDLED;
+ }
+
+ switch (message.what) {
+ case CMD_REBIND_DHCP:
+ transitionTo(mDhcpRebindingState);
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+
+ @Override
+ protected Inet4Address packetDestination() {
+ // Not specifying a SERVER_IDENTIFIER option is a violation of RFC 2131, but...
+ // http://b/25343517 . Try to make things work anyway by using broadcast renews.
+ return (mDhcpLease.serverAddress != null) ?
+ mDhcpLease.serverAddress : INADDR_BROADCAST;
+ }
+ }
+
+ class DhcpRebindingState extends DhcpReacquiringState {
+ public DhcpRebindingState() {
+ mLeaseMsg = "Rebound";
+ }
+
+ @Override
+ public void enter() {
+ super.enter();
+
+ // We need to broadcast and possibly reconnect the socket to a
+ // completely different server.
+ closeQuietly(mUdpSock);
+ if (!initUdpSocket()) {
+ Log.e(TAG, "Failed to recreate UDP socket");
+ transitionTo(mDhcpInitState);
+ }
+ }
+
+ @Override
+ protected Inet4Address packetDestination() {
+ return INADDR_BROADCAST;
+ }
+ }
+
+ class DhcpInitRebootState extends LoggingState {
+ }
+
+ class DhcpRebootingState extends LoggingState {
+ }
+
+ private void logError(int errorCode) {
+ mMetricsLog.log(mIfaceName, new DhcpErrorEvent(errorCode));
+ }
+
+ private void logState(String name, int durationMs) {
+ final DhcpClientEvent event = new DhcpClientEvent.Builder()
+ .setMsg(name)
+ .setDurationMs(durationMs)
+ .build();
+ mMetricsLog.log(mIfaceName, event);
+ }
+}
diff --git a/services/net/java/android/net/dhcp/DhcpDeclinePacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpDeclinePacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpDeclinePacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpDeclinePacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpDiscoverPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpDiscoverPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpDiscoverPacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpInformPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpInformPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpInformPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpInformPacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpNakPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpNakPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpNakPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpNakPacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpOfferPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpOfferPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpOfferPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpOfferPacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpReleasePacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpReleasePacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpReleasePacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpReleasePacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpRequestPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpRequestPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpRequestPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpRequestPacket.java
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java b/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java
index 14e2936..7b112df 100644
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java
@@ -29,7 +29,6 @@
import static android.system.OsConstants.IPPROTO_UDP;
import static android.system.OsConstants.SOCK_DGRAM;
import static android.system.OsConstants.SOL_SOCKET;
-import static android.system.OsConstants.SO_BINDTODEVICE;
import static android.system.OsConstants.SO_BROADCAST;
import static android.system.OsConstants.SO_REUSEADDR;
@@ -45,6 +44,7 @@
import android.net.NetworkUtils;
import android.net.TrafficStats;
import android.net.util.SharedLog;
+import android.net.util.SocketUtils;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@@ -629,14 +629,10 @@
final int oldTag = TrafficStats.getAndSetThreadStatsTag(TAG_SYSTEM_DHCP_SERVER);
try {
mSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ SocketUtils.bindSocketToInterface(mSocket, mIfName);
Os.setsockoptInt(mSocket, SOL_SOCKET, SO_REUSEADDR, 1);
- // SO_BINDTODEVICE actually takes a string. This works because the first member
- // of struct ifreq is a NULL-terminated interface name.
- // TODO: add a setsockoptString()
- Os.setsockoptIfreq(mSocket, SOL_SOCKET, SO_BINDTODEVICE, mIfName);
Os.setsockoptInt(mSocket, SOL_SOCKET, SO_BROADCAST, 1);
Os.bind(mSocket, Inet4Address.ANY, DHCP_SERVER);
- NetworkUtils.protectFromVpn(mSocket);
return mSocket;
} catch (IOException | ErrnoException e) {
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java b/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java
index f38888a..868f3be 100644
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java
@@ -30,10 +30,10 @@
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.NetworkUtils;
-
-import com.google.android.collect.Sets;
+import android.util.ArraySet;
import java.net.Inet4Address;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -208,7 +208,7 @@
* but it must always be set explicitly before building the {@link DhcpServingParams}.
*/
public Builder setDefaultRouters(@NonNull Inet4Address... defaultRouters) {
- return setDefaultRouters(Sets.newArraySet(defaultRouters));
+ return setDefaultRouters(new ArraySet<>(Arrays.asList(defaultRouters)));
}
/**
@@ -238,7 +238,7 @@
* building the {@link DhcpServingParams}.
*/
public Builder setDnsServers(@NonNull Inet4Address... dnsServers) {
- return setDnsServers(Sets.newArraySet(dnsServers));
+ return setDnsServers(new ArraySet<>(Arrays.asList(dnsServers)));
}
/**
@@ -268,7 +268,7 @@
* and do not need to be set here.
*/
public Builder setExcludedAddrs(@NonNull Inet4Address... excludedAddrs) {
- return setExcludedAddrs(Sets.newArraySet(excludedAddrs));
+ return setExcludedAddrs(new ArraySet<>(Arrays.asList(excludedAddrs)));
}
/**
diff --git a/packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java b/packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java
new file mode 100644
index 0000000..385dd52
--- /dev/null
+++ b/packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2016 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.ip;
+
+import static android.net.util.SocketUtils.makePacketSocketAddress;
+import static android.system.OsConstants.AF_PACKET;
+import static android.system.OsConstants.ARPHRD_ETHER;
+import static android.system.OsConstants.ETH_P_ALL;
+import static android.system.OsConstants.SOCK_RAW;
+
+import android.net.NetworkUtils;
+import android.net.util.ConnectivityPacketSummary;
+import android.net.util.InterfaceParams;
+import android.net.util.PacketReader;
+import android.os.Handler;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
+
+import libcore.util.HexEncoding;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+
+/**
+ * Critical connectivity packet tracking daemon.
+ *
+ * Tracks ARP, DHCPv4, and IPv6 RS/RA/NS/NA packets.
+ *
+ * This class's constructor, start() and stop() methods must only be called
+ * from the same thread on which the passed in |log| is accessed.
+ *
+ * Log lines include a hexdump of the packet, which can be decoded via:
+ *
+ * echo -n H3XSTR1NG | sed -e 's/\([0-9A-F][0-9A-F]\)/\1 /g' -e 's/^/000000 /'
+ * | text2pcap - -
+ * | tcpdump -n -vv -e -r -
+ *
+ * @hide
+ */
+public class ConnectivityPacketTracker {
+ private static final String TAG = ConnectivityPacketTracker.class.getSimpleName();
+ private static final boolean DBG = false;
+ private static final String MARK_START = "--- START ---";
+ private static final String MARK_STOP = "--- STOP ---";
+ private static final String MARK_NAMED_START = "--- START (%s) ---";
+ private static final String MARK_NAMED_STOP = "--- STOP (%s) ---";
+
+ private final String mTag;
+ private final LocalLog mLog;
+ private final PacketReader mPacketListener;
+ private boolean mRunning;
+ private String mDisplayName;
+
+ public ConnectivityPacketTracker(Handler h, InterfaceParams ifParams, LocalLog log) {
+ if (ifParams == null) throw new IllegalArgumentException("null InterfaceParams");
+
+ mTag = TAG + "." + ifParams.name;
+ mLog = log;
+ mPacketListener = new PacketListener(h, ifParams);
+ }
+
+ public void start(String displayName) {
+ mRunning = true;
+ mDisplayName = displayName;
+ mPacketListener.start();
+ }
+
+ public void stop() {
+ mPacketListener.stop();
+ mRunning = false;
+ mDisplayName = null;
+ }
+
+ private final class PacketListener extends PacketReader {
+ private final InterfaceParams mInterface;
+
+ PacketListener(Handler h, InterfaceParams ifParams) {
+ super(h, ifParams.defaultMtu);
+ mInterface = ifParams;
+ }
+
+ @Override
+ protected FileDescriptor createFd() {
+ FileDescriptor s = null;
+ try {
+ s = Os.socket(AF_PACKET, SOCK_RAW, 0);
+ NetworkUtils.attachControlPacketFilter(s, ARPHRD_ETHER);
+ Os.bind(s, makePacketSocketAddress((short) ETH_P_ALL, mInterface.index));
+ } catch (ErrnoException | IOException e) {
+ logError("Failed to create packet tracking socket: ", e);
+ closeFd(s);
+ return null;
+ }
+ return s;
+ }
+
+ @Override
+ protected void handlePacket(byte[] recvbuf, int length) {
+ final String summary = ConnectivityPacketSummary.summarize(
+ mInterface.macAddr, recvbuf, length);
+ if (summary == null) return;
+
+ if (DBG) Log.d(mTag, summary);
+ addLogEntry(summary +
+ "\n[" + new String(HexEncoding.encode(recvbuf, 0, length)) + "]");
+ }
+
+ @Override
+ protected void onStart() {
+ final String msg = TextUtils.isEmpty(mDisplayName)
+ ? MARK_START
+ : String.format(MARK_NAMED_START, mDisplayName);
+ mLog.log(msg);
+ }
+
+ @Override
+ protected void onStop() {
+ String msg = TextUtils.isEmpty(mDisplayName)
+ ? MARK_STOP
+ : String.format(MARK_NAMED_STOP, mDisplayName);
+ if (!mRunning) msg += " (packet listener stopped unexpectedly)";
+ mLog.log(msg);
+ }
+
+ @Override
+ protected void logError(String msg, Exception e) {
+ Log.e(mTag, msg, e);
+ addLogEntry(msg + e);
+ }
+
+ private void addLogEntry(String entry) {
+ mLog.log(entry);
+ }
+ }
+}
diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java
new file mode 100644
index 0000000..ad7f85d
--- /dev/null
+++ b/packages/NetworkStack/src/android/net/ip/IpClient.java
@@ -0,0 +1,1691 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ip;
+
+import static android.net.shared.IpConfigurationParcelableUtil.toStableParcelable;
+import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable;
+import static android.net.shared.LinkPropertiesParcelableUtil.toStableParcelable;
+
+import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.DhcpResults;
+import android.net.INetd;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.ProvisioningConfigurationParcelable;
+import android.net.ProxyInfo;
+import android.net.ProxyInfoParcelable;
+import android.net.RouteInfo;
+import android.net.apf.ApfCapabilities;
+import android.net.apf.ApfFilter;
+import android.net.dhcp.DhcpClient;
+import android.net.ip.IIpClientCallbacks;
+import android.net.metrics.IpConnectivityLog;
+import android.net.metrics.IpManagerEvent;
+import android.net.shared.InitialConfiguration;
+import android.net.shared.NetdService;
+import android.net.shared.ProvisioningConfiguration;
+import android.net.util.InterfaceParams;
+import android.net.util.SharedLog;
+import android.os.ConditionVariable;
+import android.os.INetworkManagementService;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IState;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.MessageUtils;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.internal.util.WakeupMessage;
+import com.android.server.net.NetlinkTracker;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+
+/**
+ * IpClient
+ *
+ * This class provides the interface to IP-layer provisioning and maintenance
+ * functionality that can be used by transport layers like Wi-Fi, Ethernet,
+ * et cetera.
+ *
+ * [ Lifetime ]
+ * IpClient is designed to be instantiated as soon as the interface name is
+ * known and can be as long-lived as the class containing it (i.e. declaring
+ * it "private final" is okay).
+ *
+ * @hide
+ */
+public class IpClient extends StateMachine {
+ private static final boolean DBG = false;
+
+ // For message logging.
+ private static final Class[] sMessageClasses = { IpClient.class, DhcpClient.class };
+ private static final SparseArray<String> sWhatToString =
+ MessageUtils.findMessageNames(sMessageClasses);
+ // Two static concurrent hashmaps of interface name to logging classes.
+ // One holds StateMachine logs and the other connectivity packet logs.
+ private static final ConcurrentHashMap<String, SharedLog> sSmLogs = new ConcurrentHashMap<>();
+ private static final ConcurrentHashMap<String, LocalLog> sPktLogs = new ConcurrentHashMap<>();
+
+ /**
+ * Dump all state machine and connectivity packet logs to the specified writer.
+ * @param skippedIfaces Interfaces for which logs should not be dumped.
+ */
+ public static void dumpAllLogs(PrintWriter writer, Set<String> skippedIfaces) {
+ for (String ifname : sSmLogs.keySet()) {
+ if (skippedIfaces.contains(ifname)) continue;
+
+ writer.println(String.format("--- BEGIN %s ---", ifname));
+
+ final SharedLog smLog = sSmLogs.get(ifname);
+ if (smLog != null) {
+ writer.println("State machine log:");
+ smLog.dump(null, writer, null);
+ }
+
+ writer.println("");
+
+ final LocalLog pktLog = sPktLogs.get(ifname);
+ if (pktLog != null) {
+ writer.println("Connectivity packet log:");
+ pktLog.readOnlyLocalLog().dump(null, writer, null);
+ }
+
+ writer.println(String.format("--- END %s ---", ifname));
+ }
+ }
+
+ // Use a wrapper class to log in order to ensure complete and detailed
+ // logging. This method is lighter weight than annotations/reflection
+ // and has the following benefits:
+ //
+ // - No invoked method can be forgotten.
+ // Any new method added to IpClient.Callback must be overridden
+ // here or it will never be called.
+ //
+ // - No invoking call site can be forgotten.
+ // Centralized logging in this way means call sites don't need to
+ // remember to log, and therefore no call site can be forgotten.
+ //
+ // - No variation in log format among call sites.
+ // Encourages logging of any available arguments, and all call sites
+ // are necessarily logged identically.
+ //
+ // NOTE: Log first because passed objects may or may not be thread-safe and
+ // once passed on to the callback they may be modified by another thread.
+ //
+ // TODO: Find an lighter weight approach.
+ public static class IpClientCallbacksWrapper {
+ private static final String PREFIX = "INVOKE ";
+ private final IIpClientCallbacks mCallback;
+ private final SharedLog mLog;
+
+ @VisibleForTesting
+ protected IpClientCallbacksWrapper(IIpClientCallbacks callback, SharedLog log) {
+ mCallback = callback;
+ mLog = log;
+ }
+
+ private void log(String msg) {
+ mLog.log(PREFIX + msg);
+ }
+
+ private void log(String msg, Throwable e) {
+ mLog.e(PREFIX + msg, e);
+ }
+
+ public void onPreDhcpAction() {
+ log("onPreDhcpAction()");
+ try {
+ mCallback.onPreDhcpAction();
+ } catch (RemoteException e) {
+ log("Failed to call onPreDhcpAction", e);
+ }
+ }
+
+ public void onPostDhcpAction() {
+ log("onPostDhcpAction()");
+ try {
+ mCallback.onPostDhcpAction();
+ } catch (RemoteException e) {
+ log("Failed to call onPostDhcpAction", e);
+ }
+ }
+
+ public void onNewDhcpResults(DhcpResults dhcpResults) {
+ log("onNewDhcpResults({" + dhcpResults + "})");
+ try {
+ mCallback.onNewDhcpResults(toStableParcelable(dhcpResults));
+ } catch (RemoteException e) {
+ log("Failed to call onNewDhcpResults", e);
+ }
+ }
+
+ public void onProvisioningSuccess(LinkProperties newLp) {
+ log("onProvisioningSuccess({" + newLp + "})");
+ try {
+ mCallback.onProvisioningSuccess(toStableParcelable(newLp));
+ } catch (RemoteException e) {
+ log("Failed to call onProvisioningSuccess", e);
+ }
+ }
+
+ public void onProvisioningFailure(LinkProperties newLp) {
+ log("onProvisioningFailure({" + newLp + "})");
+ try {
+ mCallback.onProvisioningFailure(toStableParcelable(newLp));
+ } catch (RemoteException e) {
+ log("Failed to call onProvisioningFailure", e);
+ }
+ }
+
+ public void onLinkPropertiesChange(LinkProperties newLp) {
+ log("onLinkPropertiesChange({" + newLp + "})");
+ try {
+ mCallback.onLinkPropertiesChange(toStableParcelable(newLp));
+ } catch (RemoteException e) {
+ log("Failed to call onLinkPropertiesChange", e);
+ }
+ }
+
+ public void onReachabilityLost(String logMsg) {
+ log("onReachabilityLost(" + logMsg + ")");
+ try {
+ mCallback.onReachabilityLost(logMsg);
+ } catch (RemoteException e) {
+ log("Failed to call onReachabilityLost", e);
+ }
+ }
+
+ public void onQuit() {
+ log("onQuit()");
+ try {
+ mCallback.onQuit();
+ } catch (RemoteException e) {
+ log("Failed to call onQuit", e);
+ }
+ }
+
+ public void installPacketFilter(byte[] filter) {
+ log("installPacketFilter(byte[" + filter.length + "])");
+ try {
+ mCallback.installPacketFilter(filter);
+ } catch (RemoteException e) {
+ log("Failed to call installPacketFilter", e);
+ }
+ }
+
+ public void startReadPacketFilter() {
+ log("startReadPacketFilter()");
+ try {
+ mCallback.startReadPacketFilter();
+ } catch (RemoteException e) {
+ log("Failed to call startReadPacketFilter", e);
+ }
+ }
+
+ public void setFallbackMulticastFilter(boolean enabled) {
+ log("setFallbackMulticastFilter(" + enabled + ")");
+ try {
+ mCallback.setFallbackMulticastFilter(enabled);
+ } catch (RemoteException e) {
+ log("Failed to call setFallbackMulticastFilter", e);
+ }
+ }
+
+ public void setNeighborDiscoveryOffload(boolean enable) {
+ log("setNeighborDiscoveryOffload(" + enable + ")");
+ try {
+ mCallback.setNeighborDiscoveryOffload(enable);
+ } catch (RemoteException e) {
+ log("Failed to call setNeighborDiscoveryOffload", e);
+ }
+ }
+ }
+
+ public static final String DUMP_ARG_CONFIRM = "confirm";
+
+ private static final int CMD_TERMINATE_AFTER_STOP = 1;
+ private static final int CMD_STOP = 2;
+ private static final int CMD_START = 3;
+ private static final int CMD_CONFIRM = 4;
+ private static final int EVENT_PRE_DHCP_ACTION_COMPLETE = 5;
+ // Triggered by NetlinkTracker to communicate netlink events.
+ private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6;
+ private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 7;
+ private static final int CMD_UPDATE_HTTP_PROXY = 8;
+ private static final int CMD_SET_MULTICAST_FILTER = 9;
+ private static final int EVENT_PROVISIONING_TIMEOUT = 10;
+ private static final int EVENT_DHCPACTION_TIMEOUT = 11;
+ private static final int EVENT_READ_PACKET_FILTER_COMPLETE = 12;
+
+ // Internal commands to use instead of trying to call transitionTo() inside
+ // a given State's enter() method. Calling transitionTo() from enter/exit
+ // encounters a Log.wtf() that can cause trouble on eng builds.
+ private static final int CMD_JUMP_STARTED_TO_RUNNING = 100;
+ private static final int CMD_JUMP_RUNNING_TO_STOPPING = 101;
+ private static final int CMD_JUMP_STOPPING_TO_STOPPED = 102;
+
+ // IpClient shares a handler with DhcpClient: commands must not overlap
+ public static final int DHCPCLIENT_CMD_BASE = 1000;
+
+ private static final int MAX_LOG_RECORDS = 500;
+ private static final int MAX_PACKET_RECORDS = 100;
+
+ private static final boolean NO_CALLBACKS = false;
+ private static final boolean SEND_CALLBACKS = true;
+
+ // This must match the interface prefix in clatd.c.
+ // TODO: Revert this hack once IpClient and Nat464Xlat work in concert.
+ private static final String CLAT_PREFIX = "v4-";
+
+ private static final int IMMEDIATE_FAILURE_DURATION = 0;
+
+ private static final int PROV_CHANGE_STILL_NOT_PROVISIONED = 1;
+ private static final int PROV_CHANGE_LOST_PROVISIONING = 2;
+ private static final int PROV_CHANGE_GAINED_PROVISIONING = 3;
+ private static final int PROV_CHANGE_STILL_PROVISIONED = 4;
+
+ private final State mStoppedState = new StoppedState();
+ private final State mStoppingState = new StoppingState();
+ private final State mStartedState = new StartedState();
+ private final State mRunningState = new RunningState();
+
+ private final String mTag;
+ private final Context mContext;
+ private final String mInterfaceName;
+ private final String mClatInterfaceName;
+ @VisibleForTesting
+ protected final IpClientCallbacksWrapper mCallback;
+ private final Dependencies mDependencies;
+ private final CountDownLatch mShutdownLatch;
+ private final ConnectivityManager mCm;
+ private final INetworkManagementService mNwService;
+ private final NetlinkTracker mNetlinkTracker;
+ private final WakeupMessage mProvisioningTimeoutAlarm;
+ private final WakeupMessage mDhcpActionTimeoutAlarm;
+ private final SharedLog mLog;
+ private final LocalLog mConnectivityPacketLog;
+ private final MessageHandlingLogger mMsgStateLogger;
+ private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
+ private final InterfaceController mInterfaceCtrl;
+
+ private InterfaceParams mInterfaceParams;
+
+ /**
+ * Non-final member variables accessed only from within our StateMachine.
+ */
+ private LinkProperties mLinkProperties;
+ private android.net.shared.ProvisioningConfiguration mConfiguration;
+ private IpReachabilityMonitor mIpReachabilityMonitor;
+ private DhcpClient mDhcpClient;
+ private DhcpResults mDhcpResults;
+ private String mTcpBufferSizes;
+ private ProxyInfo mHttpProxy;
+ private ApfFilter mApfFilter;
+ private boolean mMulticastFiltering;
+ private long mStartTimeMillis;
+
+ /**
+ * Reading the snapshot is an asynchronous operation initiated by invoking
+ * Callback.startReadPacketFilter() and completed when the WiFi Service responds with an
+ * EVENT_READ_PACKET_FILTER_COMPLETE message. The mApfDataSnapshotComplete condition variable
+ * signals when a new snapshot is ready.
+ */
+ private final ConditionVariable mApfDataSnapshotComplete = new ConditionVariable();
+
+ public static class Dependencies {
+ public INetworkManagementService getNMS() {
+ return INetworkManagementService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
+ }
+
+ public INetd getNetd() {
+ return NetdService.getInstance();
+ }
+
+ /**
+ * Get interface parameters for the specified interface.
+ */
+ public InterfaceParams getInterfaceParams(String ifname) {
+ return InterfaceParams.getByName(ifname);
+ }
+ }
+
+ public IpClient(Context context, String ifName, IIpClientCallbacks callback) {
+ this(context, ifName, callback, new Dependencies());
+ }
+
+ /**
+ * An expanded constructor, useful for dependency injection.
+ * TODO: migrate all test users to mock IpClient directly and remove this ctor.
+ */
+ public IpClient(Context context, String ifName, IIpClientCallbacks callback,
+ INetworkManagementService nwService) {
+ this(context, ifName, callback, new Dependencies() {
+ @Override
+ public INetworkManagementService getNMS() {
+ return nwService;
+ }
+ });
+ }
+
+ @VisibleForTesting
+ IpClient(Context context, String ifName, IIpClientCallbacks callback, Dependencies deps) {
+ super(IpClient.class.getSimpleName() + "." + ifName);
+ Preconditions.checkNotNull(ifName);
+ Preconditions.checkNotNull(callback);
+
+ mTag = getName();
+
+ mContext = context;
+ mInterfaceName = ifName;
+ mClatInterfaceName = CLAT_PREFIX + ifName;
+ mDependencies = deps;
+ mShutdownLatch = new CountDownLatch(1);
+ mCm = mContext.getSystemService(ConnectivityManager.class);
+ mNwService = deps.getNMS();
+
+ sSmLogs.putIfAbsent(mInterfaceName, new SharedLog(MAX_LOG_RECORDS, mTag));
+ mLog = sSmLogs.get(mInterfaceName);
+ sPktLogs.putIfAbsent(mInterfaceName, new LocalLog(MAX_PACKET_RECORDS));
+ mConnectivityPacketLog = sPktLogs.get(mInterfaceName);
+ mMsgStateLogger = new MessageHandlingLogger();
+ mCallback = new IpClientCallbacksWrapper(callback, mLog);
+
+ // TODO: Consider creating, constructing, and passing in some kind of
+ // InterfaceController.Dependencies class.
+ mInterfaceCtrl = new InterfaceController(mInterfaceName, deps.getNetd(), mLog);
+
+ mNetlinkTracker = new NetlinkTracker(
+ mInterfaceName,
+ new NetlinkTracker.Callback() {
+ @Override
+ public void update() {
+ sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED);
+ }
+ }) {
+ @Override
+ public void interfaceAdded(String iface) {
+ super.interfaceAdded(iface);
+ if (mClatInterfaceName.equals(iface)) {
+ mCallback.setNeighborDiscoveryOffload(false);
+ } else if (!mInterfaceName.equals(iface)) {
+ return;
+ }
+
+ final String msg = "interfaceAdded(" + iface + ")";
+ logMsg(msg);
+ }
+
+ @Override
+ public void interfaceRemoved(String iface) {
+ super.interfaceRemoved(iface);
+ // TODO: Also observe mInterfaceName going down and take some
+ // kind of appropriate action.
+ if (mClatInterfaceName.equals(iface)) {
+ // TODO: consider sending a message to the IpClient main
+ // StateMachine thread, in case "NDO enabled" state becomes
+ // tied to more things that 464xlat operation.
+ mCallback.setNeighborDiscoveryOffload(true);
+ } else if (!mInterfaceName.equals(iface)) {
+ return;
+ }
+
+ final String msg = "interfaceRemoved(" + iface + ")";
+ logMsg(msg);
+ }
+
+ private void logMsg(String msg) {
+ Log.d(mTag, msg);
+ getHandler().post(() -> mLog.log("OBSERVED " + msg));
+ }
+ };
+
+ mLinkProperties = new LinkProperties();
+ mLinkProperties.setInterfaceName(mInterfaceName);
+
+ mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
+ mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT);
+ mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
+ mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT);
+
+ // Anything the StateMachine may access must have been instantiated
+ // before this point.
+ configureAndStartStateMachine();
+
+ // Anything that may send messages to the StateMachine must only be
+ // configured to do so after the StateMachine has started (above).
+ startStateMachineUpdaters();
+ }
+
+ /**
+ * Make a IIpClient connector to communicate with this IpClient.
+ */
+ public IIpClient makeConnector() {
+ return new IpClientConnector();
+ }
+
+ class IpClientConnector extends IIpClient.Stub {
+ @Override
+ public void completedPreDhcpAction() {
+ checkNetworkStackCallingPermission();
+ IpClient.this.completedPreDhcpAction();
+ }
+ @Override
+ public void confirmConfiguration() {
+ checkNetworkStackCallingPermission();
+ IpClient.this.confirmConfiguration();
+ }
+ @Override
+ public void readPacketFilterComplete(byte[] data) {
+ checkNetworkStackCallingPermission();
+ IpClient.this.readPacketFilterComplete(data);
+ }
+ @Override
+ public void shutdown() {
+ checkNetworkStackCallingPermission();
+ IpClient.this.shutdown();
+ }
+ @Override
+ public void startProvisioning(ProvisioningConfigurationParcelable req) {
+ checkNetworkStackCallingPermission();
+ IpClient.this.startProvisioning(ProvisioningConfiguration.fromStableParcelable(req));
+ }
+ @Override
+ public void stop() {
+ checkNetworkStackCallingPermission();
+ IpClient.this.stop();
+ }
+ @Override
+ public void setTcpBufferSizes(String tcpBufferSizes) {
+ checkNetworkStackCallingPermission();
+ IpClient.this.setTcpBufferSizes(tcpBufferSizes);
+ }
+ @Override
+ public void setHttpProxy(ProxyInfoParcelable proxyInfo) {
+ checkNetworkStackCallingPermission();
+ IpClient.this.setHttpProxy(fromStableParcelable(proxyInfo));
+ }
+ @Override
+ public void setMulticastFilter(boolean enabled) {
+ checkNetworkStackCallingPermission();
+ IpClient.this.setMulticastFilter(enabled);
+ }
+ }
+
+ public String getInterfaceName() {
+ return mInterfaceName;
+ }
+
+ private void configureAndStartStateMachine() {
+ // CHECKSTYLE:OFF IndentationCheck
+ addState(mStoppedState);
+ addState(mStartedState);
+ addState(mRunningState, mStartedState);
+ addState(mStoppingState);
+ // CHECKSTYLE:ON IndentationCheck
+
+ setInitialState(mStoppedState);
+
+ super.start();
+ }
+
+ private void startStateMachineUpdaters() {
+ try {
+ mNwService.registerObserver(mNetlinkTracker);
+ } catch (RemoteException e) {
+ logError("Couldn't register NetlinkTracker: %s", e);
+ }
+ }
+
+ private void stopStateMachineUpdaters() {
+ try {
+ mNwService.unregisterObserver(mNetlinkTracker);
+ } catch (RemoteException e) {
+ logError("Couldn't unregister NetlinkTracker: %s", e);
+ }
+ }
+
+ @Override
+ protected void onQuitting() {
+ mCallback.onQuit();
+ mShutdownLatch.countDown();
+ }
+
+ /**
+ * Shut down this IpClient instance altogether.
+ */
+ public void shutdown() {
+ stop();
+ sendMessage(CMD_TERMINATE_AFTER_STOP);
+ }
+
+ /**
+ * Start provisioning with the provided parameters.
+ */
+ public void startProvisioning(ProvisioningConfiguration req) {
+ if (!req.isValid()) {
+ doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
+ return;
+ }
+
+ mInterfaceParams = mDependencies.getInterfaceParams(mInterfaceName);
+ if (mInterfaceParams == null) {
+ logError("Failed to find InterfaceParams for " + mInterfaceName);
+ doImmediateProvisioningFailure(IpManagerEvent.ERROR_INTERFACE_NOT_FOUND);
+ return;
+ }
+
+ mCallback.setNeighborDiscoveryOffload(true);
+ sendMessage(CMD_START, new android.net.shared.ProvisioningConfiguration(req));
+ }
+
+ /**
+ * Stop this IpClient.
+ *
+ * <p>This does not shut down the StateMachine itself, which is handled by {@link #shutdown()}.
+ */
+ public void stop() {
+ sendMessage(CMD_STOP);
+ }
+
+ /**
+ * Confirm the provisioning configuration.
+ */
+ public void confirmConfiguration() {
+ sendMessage(CMD_CONFIRM);
+ }
+
+ /**
+ * For clients using {@link ProvisioningConfiguration.Builder#withPreDhcpAction()}, must be
+ * called after {@link IIpClientCallbacks#onPreDhcpAction} to indicate that DHCP is clear to
+ * proceed.
+ */
+ public void completedPreDhcpAction() {
+ sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
+ }
+
+ /**
+ * Indicate that packet filter read is complete.
+ */
+ public void readPacketFilterComplete(byte[] data) {
+ sendMessage(EVENT_READ_PACKET_FILTER_COMPLETE, data);
+ }
+
+ /**
+ * Set the TCP buffer sizes to use.
+ *
+ * This may be called, repeatedly, at any time before or after a call to
+ * #startProvisioning(). The setting is cleared upon calling #stop().
+ */
+ public void setTcpBufferSizes(String tcpBufferSizes) {
+ sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes);
+ }
+
+ /**
+ * Set the HTTP Proxy configuration to use.
+ *
+ * This may be called, repeatedly, at any time before or after a call to
+ * #startProvisioning(). The setting is cleared upon calling #stop().
+ */
+ public void setHttpProxy(ProxyInfo proxyInfo) {
+ sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo);
+ }
+
+ /**
+ * Enable or disable the multicast filter. Attempts to use APF to accomplish the filtering,
+ * if not, Callback.setFallbackMulticastFilter() is called.
+ */
+ public void setMulticastFilter(boolean enabled) {
+ sendMessage(CMD_SET_MULTICAST_FILTER, enabled);
+ }
+
+ /**
+ * Dump logs of this IpClient.
+ */
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) {
+ // Execute confirmConfiguration() and take no further action.
+ confirmConfiguration();
+ return;
+ }
+
+ // Thread-unsafe access to mApfFilter but just used for debugging.
+ final ApfFilter apfFilter = mApfFilter;
+ final android.net.shared.ProvisioningConfiguration provisioningConfig = mConfiguration;
+ final ApfCapabilities apfCapabilities = (provisioningConfig != null)
+ ? provisioningConfig.mApfCapabilities : null;
+
+ IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
+ pw.println(mTag + " APF dump:");
+ pw.increaseIndent();
+ if (apfFilter != null) {
+ if (apfCapabilities.hasDataAccess()) {
+ // Request a new snapshot, then wait for it.
+ mApfDataSnapshotComplete.close();
+ mCallback.startReadPacketFilter();
+ if (!mApfDataSnapshotComplete.block(1000)) {
+ pw.print("TIMEOUT: DUMPING STALE APF SNAPSHOT");
+ }
+ }
+ apfFilter.dump(pw);
+
+ } else {
+ pw.print("No active ApfFilter; ");
+ if (provisioningConfig == null) {
+ pw.println("IpClient not yet started.");
+ } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) {
+ pw.println("Hardware does not support APF.");
+ } else {
+ pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities);
+ }
+ }
+ pw.decreaseIndent();
+ pw.println();
+ pw.println(mTag + " current ProvisioningConfiguration:");
+ pw.increaseIndent();
+ pw.println(Objects.toString(provisioningConfig, "N/A"));
+ pw.decreaseIndent();
+
+ final IpReachabilityMonitor iprm = mIpReachabilityMonitor;
+ if (iprm != null) {
+ pw.println();
+ pw.println(mTag + " current IpReachabilityMonitor state:");
+ pw.increaseIndent();
+ iprm.dump(pw);
+ pw.decreaseIndent();
+ }
+
+ pw.println();
+ pw.println(mTag + " StateMachine dump:");
+ pw.increaseIndent();
+ mLog.dump(fd, pw, args);
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println(mTag + " connectivity packet log:");
+ pw.println();
+ pw.println("Debug with python and scapy via:");
+ pw.println("shell$ python");
+ pw.println(">>> from scapy import all as scapy");
+ pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()");
+ pw.println();
+
+ pw.increaseIndent();
+ mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args);
+ pw.decreaseIndent();
+ }
+
+
+ /**
+ * Internals.
+ */
+
+ @Override
+ protected String getWhatToString(int what) {
+ return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what));
+ }
+
+ @Override
+ protected String getLogRecString(Message msg) {
+ final String logLine = String.format(
+ "%s/%d %d %d %s [%s]",
+ mInterfaceName, (mInterfaceParams == null) ? -1 : mInterfaceParams.index,
+ msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger);
+
+ final String richerLogLine = getWhatToString(msg.what) + " " + logLine;
+ mLog.log(richerLogLine);
+ if (DBG) {
+ Log.d(mTag, richerLogLine);
+ }
+
+ mMsgStateLogger.reset();
+ return logLine;
+ }
+
+ @Override
+ protected boolean recordLogRec(Message msg) {
+ // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy,
+ // and we already log any LinkProperties change that results in an
+ // invocation of IpClient.Callback#onLinkPropertiesChange().
+ final boolean shouldLog = (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED);
+ if (!shouldLog) {
+ mMsgStateLogger.reset();
+ }
+ return shouldLog;
+ }
+
+ private void logError(String fmt, Object... args) {
+ final String msg = "ERROR " + String.format(fmt, args);
+ Log.e(mTag, msg);
+ mLog.log(msg);
+ }
+
+ // This needs to be called with care to ensure that our LinkProperties
+ // are in sync with the actual LinkProperties of the interface. For example,
+ // we should only call this if we know for sure that there are no IP addresses
+ // assigned to the interface, etc.
+ private void resetLinkProperties() {
+ mNetlinkTracker.clearLinkProperties();
+ mConfiguration = null;
+ mDhcpResults = null;
+ mTcpBufferSizes = "";
+ mHttpProxy = null;
+
+ mLinkProperties = new LinkProperties();
+ mLinkProperties.setInterfaceName(mInterfaceName);
+ }
+
+ private void recordMetric(final int type) {
+ // We may record error metrics prior to starting.
+ // Map this to IMMEDIATE_FAILURE_DURATION.
+ final long duration = (mStartTimeMillis > 0)
+ ? (SystemClock.elapsedRealtime() - mStartTimeMillis)
+ : IMMEDIATE_FAILURE_DURATION;
+ mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration));
+ }
+
+ // For now: use WifiStateMachine's historical notion of provisioned.
+ @VisibleForTesting
+ static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) {
+ // For historical reasons, we should connect even if all we have is
+ // an IPv4 address and nothing else.
+ if (lp.hasIPv4Address() || lp.isProvisioned()) {
+ return true;
+ }
+ if (config == null) {
+ return false;
+ }
+
+ // When an InitialConfiguration is specified, ignore any difference with previous
+ // properties and instead check if properties observed match the desired properties.
+ return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes());
+ }
+
+ // TODO: Investigate folding all this into the existing static function
+ // LinkProperties.compareProvisioning() or some other single function that
+ // takes two LinkProperties objects and returns a ProvisioningChange
+ // object that is a correct and complete assessment of what changed, taking
+ // account of the asymmetries described in the comments in this function.
+ // Then switch to using it everywhere (IpReachabilityMonitor, etc.).
+ private int compareProvisioning(LinkProperties oldLp, LinkProperties newLp) {
+ int delta;
+ InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null;
+ final boolean wasProvisioned = isProvisioned(oldLp, config);
+ final boolean isProvisioned = isProvisioned(newLp, config);
+
+ if (!wasProvisioned && isProvisioned) {
+ delta = PROV_CHANGE_GAINED_PROVISIONING;
+ } else if (wasProvisioned && isProvisioned) {
+ delta = PROV_CHANGE_STILL_PROVISIONED;
+ } else if (!wasProvisioned && !isProvisioned) {
+ delta = PROV_CHANGE_STILL_NOT_PROVISIONED;
+ } else {
+ // (wasProvisioned && !isProvisioned)
+ //
+ // Note that this is true even if we lose a configuration element
+ // (e.g., a default gateway) that would not be required to advance
+ // into provisioned state. This is intended: if we have a default
+ // router and we lose it, that's a sure sign of a problem, but if
+ // we connect to a network with no IPv4 DNS servers, we consider
+ // that to be a network without DNS servers and connect anyway.
+ //
+ // See the comment below.
+ delta = PROV_CHANGE_LOST_PROVISIONING;
+ }
+
+ final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned();
+ final boolean lostIPv4Address = oldLp.hasIPv4Address() && !newLp.hasIPv4Address();
+ final boolean lostIPv6Router = oldLp.hasIPv6DefaultRoute() && !newLp.hasIPv6DefaultRoute();
+
+ // If bad wifi avoidance is disabled, then ignore IPv6 loss of
+ // provisioning. Otherwise, when a hotspot that loses Internet
+ // access sends out a 0-lifetime RA to its clients, the clients
+ // will disconnect and then reconnect, avoiding the bad hotspot,
+ // instead of getting stuck on the bad hotspot. http://b/31827713 .
+ //
+ // This is incorrect because if the hotspot then regains Internet
+ // access with a different prefix, TCP connections on the
+ // deprecated addresses will remain stuck.
+ //
+ // Note that we can still be disconnected by IpReachabilityMonitor
+ // if the IPv6 default gateway (but not the IPv6 DNS servers; see
+ // accompanying code in IpReachabilityMonitor) is unreachable.
+ final boolean ignoreIPv6ProvisioningLoss =
+ mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker
+ && mCm.getAvoidBadWifi();
+
+ // Additionally:
+ //
+ // Partial configurations (e.g., only an IPv4 address with no DNS
+ // servers and no default route) are accepted as long as DHCPv4
+ // succeeds. On such a network, isProvisioned() will always return
+ // false, because the configuration is not complete, but we want to
+ // connect anyway. It might be a disconnected network such as a
+ // Chromecast or a wireless printer, for example.
+ //
+ // Because on such a network isProvisioned() will always return false,
+ // delta will never be LOST_PROVISIONING. So check for loss of
+ // provisioning here too.
+ if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) {
+ delta = PROV_CHANGE_LOST_PROVISIONING;
+ }
+
+ // Additionally:
+ //
+ // If the previous link properties had a global IPv6 address and an
+ // IPv6 default route then also consider the loss of that default route
+ // to be a loss of provisioning. See b/27962810.
+ if (oldLp.hasGlobalIPv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) {
+ delta = PROV_CHANGE_LOST_PROVISIONING;
+ }
+
+ return delta;
+ }
+
+ private void dispatchCallback(int delta, LinkProperties newLp) {
+ switch (delta) {
+ case PROV_CHANGE_GAINED_PROVISIONING:
+ if (DBG) {
+ Log.d(mTag, "onProvisioningSuccess()");
+ }
+ recordMetric(IpManagerEvent.PROVISIONING_OK);
+ mCallback.onProvisioningSuccess(newLp);
+ break;
+
+ case PROV_CHANGE_LOST_PROVISIONING:
+ if (DBG) {
+ Log.d(mTag, "onProvisioningFailure()");
+ }
+ recordMetric(IpManagerEvent.PROVISIONING_FAIL);
+ mCallback.onProvisioningFailure(newLp);
+ break;
+
+ default:
+ if (DBG) {
+ Log.d(mTag, "onLinkPropertiesChange()");
+ }
+ mCallback.onLinkPropertiesChange(newLp);
+ break;
+ }
+ }
+
+ // Updates all IpClient-related state concerned with LinkProperties.
+ // Returns a ProvisioningChange for possibly notifying other interested
+ // parties that are not fronted by IpClient.
+ private int setLinkProperties(LinkProperties newLp) {
+ if (mApfFilter != null) {
+ mApfFilter.setLinkProperties(newLp);
+ }
+ if (mIpReachabilityMonitor != null) {
+ mIpReachabilityMonitor.updateLinkProperties(newLp);
+ }
+
+ int delta = compareProvisioning(mLinkProperties, newLp);
+ mLinkProperties = new LinkProperties(newLp);
+
+ if (delta == PROV_CHANGE_GAINED_PROVISIONING) {
+ // TODO: Add a proper ProvisionedState and cancel the alarm in
+ // its enter() method.
+ mProvisioningTimeoutAlarm.cancel();
+ }
+
+ return delta;
+ }
+
+ private LinkProperties assembleLinkProperties() {
+ // [1] Create a new LinkProperties object to populate.
+ LinkProperties newLp = new LinkProperties();
+ newLp.setInterfaceName(mInterfaceName);
+
+ // [2] Pull in data from netlink:
+ // - IPv4 addresses
+ // - IPv6 addresses
+ // - IPv6 routes
+ // - IPv6 DNS servers
+ //
+ // N.B.: this is fundamentally race-prone and should be fixed by
+ // changing NetlinkTracker from a hybrid edge/level model to an
+ // edge-only model, or by giving IpClient its own netlink socket(s)
+ // so as to track all required information directly.
+ LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties();
+ newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses());
+ for (RouteInfo route : netlinkLinkProperties.getRoutes()) {
+ newLp.addRoute(route);
+ }
+ addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers());
+
+ // [3] Add in data from DHCPv4, if available.
+ //
+ // mDhcpResults is never shared with any other owner so we don't have
+ // to worry about concurrent modification.
+ if (mDhcpResults != null) {
+ for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) {
+ newLp.addRoute(route);
+ }
+ addAllReachableDnsServers(newLp, mDhcpResults.dnsServers);
+ newLp.setDomains(mDhcpResults.domains);
+
+ if (mDhcpResults.mtu != 0) {
+ newLp.setMtu(mDhcpResults.mtu);
+ }
+ }
+
+ // [4] Add in TCP buffer sizes and HTTP Proxy config, if available.
+ if (!TextUtils.isEmpty(mTcpBufferSizes)) {
+ newLp.setTcpBufferSizes(mTcpBufferSizes);
+ }
+ if (mHttpProxy != null) {
+ newLp.setHttpProxy(mHttpProxy);
+ }
+
+ // [5] Add data from InitialConfiguration
+ if (mConfiguration != null && mConfiguration.mInitialConfig != null) {
+ InitialConfiguration config = mConfiguration.mInitialConfig;
+ // Add InitialConfiguration routes and dns server addresses once all addresses
+ // specified in the InitialConfiguration have been observed with Netlink.
+ if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) {
+ for (IpPrefix prefix : config.directlyConnectedRoutes) {
+ newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName));
+ }
+ }
+ addAllReachableDnsServers(newLp, config.dnsServers);
+ }
+ final LinkProperties oldLp = mLinkProperties;
+ if (DBG) {
+ Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s",
+ netlinkLinkProperties, newLp, oldLp));
+ }
+
+ // TODO: also learn via netlink routes specified by an InitialConfiguration and specified
+ // from a static IP v4 config instead of manually patching them in in steps [3] and [5].
+ return newLp;
+ }
+
+ private static void addAllReachableDnsServers(
+ LinkProperties lp, Iterable<InetAddress> dnses) {
+ // TODO: Investigate deleting this reachability check. We should be
+ // able to pass everything down to netd and let netd do evaluation
+ // and RFC6724-style sorting.
+ for (InetAddress dns : dnses) {
+ if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) {
+ lp.addDnsServer(dns);
+ }
+ }
+ }
+
+ // Returns false if we have lost provisioning, true otherwise.
+ private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
+ final LinkProperties newLp = assembleLinkProperties();
+ if (Objects.equals(newLp, mLinkProperties)) {
+ return true;
+ }
+ final int delta = setLinkProperties(newLp);
+ if (sendCallbacks) {
+ dispatchCallback(delta, newLp);
+ }
+ return (delta != PROV_CHANGE_LOST_PROVISIONING);
+ }
+
+ private void handleIPv4Success(DhcpResults dhcpResults) {
+ mDhcpResults = new DhcpResults(dhcpResults);
+ final LinkProperties newLp = assembleLinkProperties();
+ final int delta = setLinkProperties(newLp);
+
+ if (DBG) {
+ Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
+ }
+ mCallback.onNewDhcpResults(dhcpResults);
+ dispatchCallback(delta, newLp);
+ }
+
+ private void handleIPv4Failure() {
+ // TODO: Investigate deleting this clearIPv4Address() call.
+ //
+ // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances
+ // that could trigger a call to this function. If we missed handling
+ // that message in StartedState for some reason we would still clear
+ // any addresses upon entry to StoppedState.
+ mInterfaceCtrl.clearIPv4Address();
+ mDhcpResults = null;
+ if (DBG) {
+ Log.d(mTag, "onNewDhcpResults(null)");
+ }
+ mCallback.onNewDhcpResults(null);
+
+ handleProvisioningFailure();
+ }
+
+ private void handleProvisioningFailure() {
+ final LinkProperties newLp = assembleLinkProperties();
+ int delta = setLinkProperties(newLp);
+ // If we've gotten here and we're still not provisioned treat that as
+ // a total loss of provisioning.
+ //
+ // Either (a) static IP configuration failed or (b) DHCPv4 failed AND
+ // there was no usable IPv6 obtained before a non-zero provisioning
+ // timeout expired.
+ //
+ // Regardless: GAME OVER.
+ if (delta == PROV_CHANGE_STILL_NOT_PROVISIONED) {
+ delta = PROV_CHANGE_LOST_PROVISIONING;
+ }
+
+ dispatchCallback(delta, newLp);
+ if (delta == PROV_CHANGE_LOST_PROVISIONING) {
+ transitionTo(mStoppingState);
+ }
+ }
+
+ private void doImmediateProvisioningFailure(int failureType) {
+ logError("onProvisioningFailure(): %s", failureType);
+ recordMetric(failureType);
+ mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
+ }
+
+ private boolean startIPv4() {
+ // If we have a StaticIpConfiguration attempt to apply it and
+ // handle the result accordingly.
+ if (mConfiguration.mStaticIpConfig != null) {
+ if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) {
+ handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
+ } else {
+ return false;
+ }
+ } else {
+ // Start DHCPv4.
+ mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpClient.this, mInterfaceParams);
+ mDhcpClient.registerForPreDhcpNotification();
+ mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
+ }
+
+ return true;
+ }
+
+ private boolean startIPv6() {
+ return mInterfaceCtrl.setIPv6PrivacyExtensions(true)
+ && mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode)
+ && mInterfaceCtrl.enableIPv6();
+ }
+
+ private boolean applyInitialConfig(InitialConfiguration config) {
+ // TODO: also support specifying a static IPv4 configuration in InitialConfiguration.
+ for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIPv6)) {
+ if (!mInterfaceCtrl.addAddress(addr)) return false;
+ }
+
+ return true;
+ }
+
+ private boolean startIpReachabilityMonitor() {
+ try {
+ // TODO: Fetch these parameters from settings, and install a
+ // settings observer to watch for update and re-program these
+ // parameters (Q: is this level of dynamic updatability really
+ // necessary or does reading from settings at startup suffice?).
+ final int numSolicits = 5;
+ final int interSolicitIntervalMs = 750;
+ setNeighborParameters(mDependencies.getNetd(), mInterfaceName,
+ numSolicits, interSolicitIntervalMs);
+ } catch (Exception e) {
+ mLog.e("Failed to adjust neighbor parameters", e);
+ // Carry on using the system defaults (currently: 3, 1000);
+ }
+
+ try {
+ mIpReachabilityMonitor = new IpReachabilityMonitor(
+ mContext,
+ mInterfaceParams,
+ getHandler(),
+ mLog,
+ new IpReachabilityMonitor.Callback() {
+ @Override
+ public void notifyLost(InetAddress ip, String logMsg) {
+ mCallback.onReachabilityLost(logMsg);
+ }
+ },
+ mConfiguration.mUsingMultinetworkPolicyTracker);
+ } catch (IllegalArgumentException iae) {
+ // Failed to start IpReachabilityMonitor. Log it and call
+ // onProvisioningFailure() immediately.
+ //
+ // See http://b/31038971.
+ logError("IpReachabilityMonitor failure: %s", iae);
+ mIpReachabilityMonitor = null;
+ }
+
+ return (mIpReachabilityMonitor != null);
+ }
+
+ private void stopAllIP() {
+ // We don't need to worry about routes, just addresses, because:
+ // - disableIpv6() will clear autoconf IPv6 routes as well, and
+ // - we don't get IPv4 routes from netlink
+ // so we neither react to nor need to wait for changes in either.
+
+ mInterfaceCtrl.disableIPv6();
+ mInterfaceCtrl.clearAllAddresses();
+ }
+
+ class StoppedState extends State {
+ @Override
+ public void enter() {
+ stopAllIP();
+
+ resetLinkProperties();
+ if (mStartTimeMillis > 0) {
+ // Completed a life-cycle; send a final empty LinkProperties
+ // (cleared in resetLinkProperties() above) and record an event.
+ mCallback.onLinkPropertiesChange(new LinkProperties(mLinkProperties));
+ recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE);
+ mStartTimeMillis = 0;
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_TERMINATE_AFTER_STOP:
+ stopStateMachineUpdaters();
+ quit();
+ break;
+
+ case CMD_STOP:
+ break;
+
+ case CMD_START:
+ mConfiguration = (android.net.shared.ProvisioningConfiguration) msg.obj;
+ transitionTo(mStartedState);
+ break;
+
+ case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+ handleLinkPropertiesUpdate(NO_CALLBACKS);
+ break;
+
+ case CMD_UPDATE_TCP_BUFFER_SIZES:
+ mTcpBufferSizes = (String) msg.obj;
+ handleLinkPropertiesUpdate(NO_CALLBACKS);
+ break;
+
+ case CMD_UPDATE_HTTP_PROXY:
+ mHttpProxy = (ProxyInfo) msg.obj;
+ handleLinkPropertiesUpdate(NO_CALLBACKS);
+ break;
+
+ case CMD_SET_MULTICAST_FILTER:
+ mMulticastFiltering = (boolean) msg.obj;
+ break;
+
+ case DhcpClient.CMD_ON_QUIT:
+ // Everything is already stopped.
+ logError("Unexpected CMD_ON_QUIT (already stopped).");
+ break;
+
+ default:
+ return NOT_HANDLED;
+ }
+
+ mMsgStateLogger.handled(this, getCurrentState());
+ return HANDLED;
+ }
+ }
+
+ class StoppingState extends State {
+ @Override
+ public void enter() {
+ if (mDhcpClient == null) {
+ // There's no DHCPv4 for which to wait; proceed to stopped.
+ deferMessage(obtainMessage(CMD_JUMP_STOPPING_TO_STOPPED));
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_JUMP_STOPPING_TO_STOPPED:
+ transitionTo(mStoppedState);
+ break;
+
+ case CMD_STOP:
+ break;
+
+ case DhcpClient.CMD_CLEAR_LINKADDRESS:
+ mInterfaceCtrl.clearIPv4Address();
+ break;
+
+ case DhcpClient.CMD_ON_QUIT:
+ mDhcpClient = null;
+ transitionTo(mStoppedState);
+ break;
+
+ default:
+ deferMessage(msg);
+ }
+
+ mMsgStateLogger.handled(this, getCurrentState());
+ return HANDLED;
+ }
+ }
+
+ class StartedState extends State {
+ @Override
+ public void enter() {
+ mStartTimeMillis = SystemClock.elapsedRealtime();
+
+ if (mConfiguration.mProvisioningTimeoutMs > 0) {
+ final long alarmTime = SystemClock.elapsedRealtime()
+ + mConfiguration.mProvisioningTimeoutMs;
+ mProvisioningTimeoutAlarm.schedule(alarmTime);
+ }
+
+ if (readyToProceed()) {
+ deferMessage(obtainMessage(CMD_JUMP_STARTED_TO_RUNNING));
+ } else {
+ // Clear all IPv4 and IPv6 before proceeding to RunningState.
+ // Clean up any leftover state from an abnormal exit from
+ // tethering or during an IpClient restart.
+ stopAllIP();
+ }
+ }
+
+ @Override
+ public void exit() {
+ mProvisioningTimeoutAlarm.cancel();
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_JUMP_STARTED_TO_RUNNING:
+ transitionTo(mRunningState);
+ break;
+
+ case CMD_STOP:
+ transitionTo(mStoppingState);
+ break;
+
+ case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+ handleLinkPropertiesUpdate(NO_CALLBACKS);
+ if (readyToProceed()) {
+ transitionTo(mRunningState);
+ }
+ break;
+
+ case EVENT_PROVISIONING_TIMEOUT:
+ handleProvisioningFailure();
+ break;
+
+ default:
+ // It's safe to process messages out of order because the
+ // only message that can both
+ // a) be received at this time and
+ // b) affect provisioning state
+ // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above).
+ deferMessage(msg);
+ }
+
+ mMsgStateLogger.handled(this, getCurrentState());
+ return HANDLED;
+ }
+
+ private boolean readyToProceed() {
+ return (!mLinkProperties.hasIPv4Address() && !mLinkProperties.hasGlobalIPv6Address());
+ }
+ }
+
+ class RunningState extends State {
+ private ConnectivityPacketTracker mPacketTracker;
+ private boolean mDhcpActionInFlight;
+
+ @Override
+ public void enter() {
+ ApfFilter.ApfConfiguration apfConfig = new ApfFilter.ApfConfiguration();
+ apfConfig.apfCapabilities = mConfiguration.mApfCapabilities;
+ apfConfig.multicastFilter = mMulticastFiltering;
+ // Get the Configuration for ApfFilter from Context
+ apfConfig.ieee802_3Filter =
+ mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
+ apfConfig.ethTypeBlackList =
+ mContext.getResources().getIntArray(R.array.config_apfEthTypeBlackList);
+ mApfFilter = ApfFilter.maybeCreate(mContext, apfConfig, mInterfaceParams, mCallback);
+ // TODO: investigate the effects of any multicast filtering racing/interfering with the
+ // rest of this IP configuration startup.
+ if (mApfFilter == null) {
+ mCallback.setFallbackMulticastFilter(mMulticastFiltering);
+ }
+
+ mPacketTracker = createPacketTracker();
+ if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName);
+
+ if (mConfiguration.mEnableIPv6 && !startIPv6()) {
+ doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
+ enqueueJumpToStoppingState();
+ return;
+ }
+
+ if (mConfiguration.mEnableIPv4 && !startIPv4()) {
+ doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4);
+ enqueueJumpToStoppingState();
+ return;
+ }
+
+ final InitialConfiguration config = mConfiguration.mInitialConfig;
+ if ((config != null) && !applyInitialConfig(config)) {
+ // TODO introduce a new IpManagerEvent constant to distinguish this error case.
+ doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
+ enqueueJumpToStoppingState();
+ return;
+ }
+
+ if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) {
+ doImmediateProvisioningFailure(
+ IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR);
+ enqueueJumpToStoppingState();
+ return;
+ }
+ }
+
+ @Override
+ public void exit() {
+ stopDhcpAction();
+
+ if (mIpReachabilityMonitor != null) {
+ mIpReachabilityMonitor.stop();
+ mIpReachabilityMonitor = null;
+ }
+
+ if (mDhcpClient != null) {
+ mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP);
+ mDhcpClient.doQuit();
+ }
+
+ if (mPacketTracker != null) {
+ mPacketTracker.stop();
+ mPacketTracker = null;
+ }
+
+ if (mApfFilter != null) {
+ mApfFilter.shutdown();
+ mApfFilter = null;
+ }
+
+ resetLinkProperties();
+ }
+
+ private void enqueueJumpToStoppingState() {
+ deferMessage(obtainMessage(CMD_JUMP_RUNNING_TO_STOPPING));
+ }
+
+ private ConnectivityPacketTracker createPacketTracker() {
+ try {
+ return new ConnectivityPacketTracker(
+ getHandler(), mInterfaceParams, mConnectivityPacketLog);
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+
+ private void ensureDhcpAction() {
+ if (!mDhcpActionInFlight) {
+ mCallback.onPreDhcpAction();
+ mDhcpActionInFlight = true;
+ final long alarmTime = SystemClock.elapsedRealtime()
+ + mConfiguration.mRequestedPreDhcpActionMs;
+ mDhcpActionTimeoutAlarm.schedule(alarmTime);
+ }
+ }
+
+ private void stopDhcpAction() {
+ mDhcpActionTimeoutAlarm.cancel();
+ if (mDhcpActionInFlight) {
+ mCallback.onPostDhcpAction();
+ mDhcpActionInFlight = false;
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_JUMP_RUNNING_TO_STOPPING:
+ case CMD_STOP:
+ transitionTo(mStoppingState);
+ break;
+
+ case CMD_START:
+ logError("ALERT: START received in StartedState. Please fix caller.");
+ break;
+
+ case CMD_CONFIRM:
+ // TODO: Possibly introduce a second type of confirmation
+ // that both probes (a) on-link neighbors and (b) does
+ // a DHCPv4 RENEW. We used to do this on Wi-Fi framework
+ // roams.
+ if (mIpReachabilityMonitor != null) {
+ mIpReachabilityMonitor.probeAll();
+ }
+ break;
+
+ case EVENT_PRE_DHCP_ACTION_COMPLETE:
+ // It's possible to reach here if, for example, someone
+ // calls completedPreDhcpAction() after provisioning with
+ // a static IP configuration.
+ if (mDhcpClient != null) {
+ mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE);
+ }
+ break;
+
+ case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+ if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
+ transitionTo(mStoppingState);
+ }
+ break;
+
+ case CMD_UPDATE_TCP_BUFFER_SIZES:
+ mTcpBufferSizes = (String) msg.obj;
+ // This cannot possibly change provisioning state.
+ handleLinkPropertiesUpdate(SEND_CALLBACKS);
+ break;
+
+ case CMD_UPDATE_HTTP_PROXY:
+ mHttpProxy = (ProxyInfo) msg.obj;
+ // This cannot possibly change provisioning state.
+ handleLinkPropertiesUpdate(SEND_CALLBACKS);
+ break;
+
+ case CMD_SET_MULTICAST_FILTER: {
+ mMulticastFiltering = (boolean) msg.obj;
+ if (mApfFilter != null) {
+ mApfFilter.setMulticastFilter(mMulticastFiltering);
+ } else {
+ mCallback.setFallbackMulticastFilter(mMulticastFiltering);
+ }
+ break;
+ }
+
+ case EVENT_READ_PACKET_FILTER_COMPLETE: {
+ if (mApfFilter != null) {
+ mApfFilter.setDataSnapshot((byte[]) msg.obj);
+ }
+ mApfDataSnapshotComplete.open();
+ break;
+ }
+
+ case EVENT_DHCPACTION_TIMEOUT:
+ stopDhcpAction();
+ break;
+
+ case DhcpClient.CMD_PRE_DHCP_ACTION:
+ if (mConfiguration.mRequestedPreDhcpActionMs > 0) {
+ ensureDhcpAction();
+ } else {
+ sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
+ }
+ break;
+
+ case DhcpClient.CMD_CLEAR_LINKADDRESS:
+ mInterfaceCtrl.clearIPv4Address();
+ break;
+
+ case DhcpClient.CMD_CONFIGURE_LINKADDRESS: {
+ final LinkAddress ipAddress = (LinkAddress) msg.obj;
+ if (mInterfaceCtrl.setIPv4Address(ipAddress)) {
+ mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED);
+ } else {
+ logError("Failed to set IPv4 address.");
+ dispatchCallback(PROV_CHANGE_LOST_PROVISIONING,
+ new LinkProperties(mLinkProperties));
+ transitionTo(mStoppingState);
+ }
+ break;
+ }
+
+ // This message is only received when:
+ //
+ // a) initial address acquisition succeeds,
+ // b) renew succeeds or is NAK'd,
+ // c) rebind succeeds or is NAK'd, or
+ // c) the lease expires,
+ //
+ // but never when initial address acquisition fails. The latter
+ // condition is now governed by the provisioning timeout.
+ case DhcpClient.CMD_POST_DHCP_ACTION:
+ stopDhcpAction();
+
+ switch (msg.arg1) {
+ case DhcpClient.DHCP_SUCCESS:
+ handleIPv4Success((DhcpResults) msg.obj);
+ break;
+ case DhcpClient.DHCP_FAILURE:
+ handleIPv4Failure();
+ break;
+ default:
+ logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1);
+ }
+ break;
+
+ case DhcpClient.CMD_ON_QUIT:
+ // DHCPv4 quit early for some reason.
+ logError("Unexpected CMD_ON_QUIT.");
+ mDhcpClient = null;
+ break;
+
+ default:
+ return NOT_HANDLED;
+ }
+
+ mMsgStateLogger.handled(this, getCurrentState());
+ return HANDLED;
+ }
+ }
+
+ private static class MessageHandlingLogger {
+ public String processedInState;
+ public String receivedInState;
+
+ public void reset() {
+ processedInState = null;
+ receivedInState = null;
+ }
+
+ public void handled(State processedIn, IState receivedIn) {
+ processedInState = processedIn.getClass().getSimpleName();
+ receivedInState = receivedIn.getName();
+ }
+
+ public String toString() {
+ return String.format("rcvd_in=%s, proc_in=%s",
+ receivedInState, processedInState);
+ }
+ }
+
+ private static void setNeighborParameters(
+ INetd netd, String ifName, int numSolicits, int interSolicitIntervalMs)
+ throws RemoteException, IllegalArgumentException {
+ Preconditions.checkNotNull(netd);
+ Preconditions.checkArgument(!TextUtils.isEmpty(ifName));
+ Preconditions.checkArgument(numSolicits > 0);
+ Preconditions.checkArgument(interSolicitIntervalMs > 0);
+
+ for (int family : new Integer[]{INetd.IPV4, INetd.IPV6}) {
+ netd.setProcSysNet(family, INetd.NEIGH, ifName, "retrans_time_ms",
+ Integer.toString(interSolicitIntervalMs));
+ netd.setProcSysNet(family, INetd.NEIGH, ifName, "ucast_solicit",
+ Integer.toString(numSolicits));
+ }
+ }
+
+ // TODO: extract out into CollectionUtils.
+ static <T> boolean any(Iterable<T> coll, Predicate<T> fn) {
+ for (T t : coll) {
+ if (fn.test(t)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
+ return !any(coll, not(fn));
+ }
+
+ static <T> Predicate<T> not(Predicate<T> fn) {
+ return (t) -> !fn.test(t);
+ }
+
+ static <T> String join(String delimiter, Collection<T> coll) {
+ return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter));
+ }
+
+ static <T> T find(Iterable<T> coll, Predicate<T> fn) {
+ for (T t: coll) {
+ if (fn.test(t)) {
+ return t;
+ }
+ }
+ return null;
+ }
+
+ static <T> List<T> findAll(Collection<T> coll, Predicate<T> fn) {
+ return coll.stream().filter(fn).collect(Collectors.toList());
+ }
+}
diff --git a/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java b/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java
new file mode 100644
index 0000000..eb993a4
--- /dev/null
+++ b/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ip;
+
+import static android.net.netlink.NetlinkConstants.RTM_DELNEIGH;
+import static android.net.netlink.NetlinkConstants.hexify;
+import static android.net.netlink.NetlinkConstants.stringForNlMsgType;
+import static android.net.util.SocketUtils.makeNetlinkSocketAddress;
+
+import android.net.MacAddress;
+import android.net.netlink.NetlinkErrorMessage;
+import android.net.netlink.NetlinkMessage;
+import android.net.netlink.NetlinkSocket;
+import android.net.netlink.RtNetlinkNeighborMessage;
+import android.net.netlink.StructNdMsg;
+import android.net.util.PacketReader;
+import android.net.util.SharedLog;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Log;
+
+import com.android.internal.util.BitUtils;
+
+import libcore.io.IoUtils;
+
+import java.io.FileDescriptor;
+import java.net.InetAddress;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.StringJoiner;
+
+
+/**
+ * IpNeighborMonitor.
+ *
+ * Monitors the kernel rtnetlink neighbor notifications and presents to callers
+ * NeighborEvents describing each event. Callers can provide a consumer instance
+ * to both filter (e.g. by interface index and IP address) and handle the
+ * generated NeighborEvents.
+ *
+ * @hide
+ */
+public class IpNeighborMonitor extends PacketReader {
+ private static final String TAG = IpNeighborMonitor.class.getSimpleName();
+ private static final boolean DBG = false;
+ private static final boolean VDBG = false;
+
+ /**
+ * Make the kernel perform neighbor reachability detection (IPv4 ARP or IPv6 ND)
+ * for the given IP address on the specified interface index.
+ *
+ * @return 0 if the request was successfully passed to the kernel; otherwise return
+ * a non-zero error code.
+ */
+ public static int startKernelNeighborProbe(int ifIndex, InetAddress ip) {
+ final String msgSnippet = "probing ip=" + ip.getHostAddress() + "%" + ifIndex;
+ if (DBG) { Log.d(TAG, msgSnippet); }
+
+ final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage(
+ 1, ip, StructNdMsg.NUD_PROBE, ifIndex, null);
+
+ try {
+ NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_ROUTE, msg);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Error " + msgSnippet + ": " + e);
+ return -e.errno;
+ }
+
+ return 0;
+ }
+
+ public static class NeighborEvent {
+ final long elapsedMs;
+ final short msgType;
+ final int ifindex;
+ final InetAddress ip;
+ final short nudState;
+ final MacAddress macAddr;
+
+ public NeighborEvent(long elapsedMs, short msgType, int ifindex, InetAddress ip,
+ short nudState, MacAddress macAddr) {
+ this.elapsedMs = elapsedMs;
+ this.msgType = msgType;
+ this.ifindex = ifindex;
+ this.ip = ip;
+ this.nudState = nudState;
+ this.macAddr = macAddr;
+ }
+
+ boolean isConnected() {
+ return (msgType != RTM_DELNEIGH) && StructNdMsg.isNudStateConnected(nudState);
+ }
+
+ boolean isValid() {
+ return (msgType != RTM_DELNEIGH) && StructNdMsg.isNudStateValid(nudState);
+ }
+
+ @Override
+ public String toString() {
+ final StringJoiner j = new StringJoiner(",", "NeighborEvent{", "}");
+ return j.add("@" + elapsedMs)
+ .add(stringForNlMsgType(msgType))
+ .add("if=" + ifindex)
+ .add(ip.getHostAddress())
+ .add(StructNdMsg.stringForNudState(nudState))
+ .add("[" + macAddr + "]")
+ .toString();
+ }
+ }
+
+ public interface NeighborEventConsumer {
+ // Every neighbor event received on the netlink socket is passed in
+ // here. Subclasses should filter for events of interest.
+ public void accept(NeighborEvent event);
+ }
+
+ private final SharedLog mLog;
+ private final NeighborEventConsumer mConsumer;
+
+ public IpNeighborMonitor(Handler h, SharedLog log, NeighborEventConsumer cb) {
+ super(h, NetlinkSocket.DEFAULT_RECV_BUFSIZE);
+ mLog = log.forSubComponent(TAG);
+ mConsumer = (cb != null) ? cb : (event) -> { /* discard */ };
+ }
+
+ @Override
+ protected FileDescriptor createFd() {
+ FileDescriptor fd = null;
+
+ try {
+ fd = NetlinkSocket.forProto(OsConstants.NETLINK_ROUTE);
+ Os.bind(fd, makeNetlinkSocketAddress(0, OsConstants.RTMGRP_NEIGH));
+ NetlinkSocket.connectToKernel(fd);
+
+ if (VDBG) {
+ final SocketAddress nlAddr = Os.getsockname(fd);
+ Log.d(TAG, "bound to sockaddr_nl{" + nlAddr.toString() + "}");
+ }
+ } catch (ErrnoException|SocketException e) {
+ logError("Failed to create rtnetlink socket", e);
+ IoUtils.closeQuietly(fd);
+ return null;
+ }
+
+ return fd;
+ }
+
+ @Override
+ protected void handlePacket(byte[] recvbuf, int length) {
+ final long whenMs = SystemClock.elapsedRealtime();
+
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(recvbuf, 0, length);
+ byteBuffer.order(ByteOrder.nativeOrder());
+
+ parseNetlinkMessageBuffer(byteBuffer, whenMs);
+ }
+
+ private void parseNetlinkMessageBuffer(ByteBuffer byteBuffer, long whenMs) {
+ while (byteBuffer.remaining() > 0) {
+ final int position = byteBuffer.position();
+ final NetlinkMessage nlMsg = NetlinkMessage.parse(byteBuffer);
+ if (nlMsg == null || nlMsg.getHeader() == null) {
+ byteBuffer.position(position);
+ mLog.e("unparsable netlink msg: " + hexify(byteBuffer));
+ break;
+ }
+
+ final int srcPortId = nlMsg.getHeader().nlmsg_pid;
+ if (srcPortId != 0) {
+ mLog.e("non-kernel source portId: " + BitUtils.uint32(srcPortId));
+ break;
+ }
+
+ if (nlMsg instanceof NetlinkErrorMessage) {
+ mLog.e("netlink error: " + nlMsg);
+ continue;
+ } else if (!(nlMsg instanceof RtNetlinkNeighborMessage)) {
+ mLog.i("non-rtnetlink neighbor msg: " + nlMsg);
+ continue;
+ }
+
+ evaluateRtNetlinkNeighborMessage((RtNetlinkNeighborMessage) nlMsg, whenMs);
+ }
+ }
+
+ private void evaluateRtNetlinkNeighborMessage(
+ RtNetlinkNeighborMessage neighMsg, long whenMs) {
+ final short msgType = neighMsg.getHeader().nlmsg_type;
+ final StructNdMsg ndMsg = neighMsg.getNdHeader();
+ if (ndMsg == null) {
+ mLog.e("RtNetlinkNeighborMessage without ND message header!");
+ return;
+ }
+
+ final int ifindex = ndMsg.ndm_ifindex;
+ final InetAddress destination = neighMsg.getDestination();
+ final short nudState =
+ (msgType == RTM_DELNEIGH)
+ ? StructNdMsg.NUD_NONE
+ : ndMsg.ndm_state;
+
+ final NeighborEvent event = new NeighborEvent(
+ whenMs, msgType, ifindex, destination, nudState,
+ getMacAddress(neighMsg.getLinkLayerAddress()));
+
+ if (VDBG) {
+ Log.d(TAG, neighMsg.toString());
+ }
+ if (DBG) {
+ Log.d(TAG, event.toString());
+ }
+
+ mConsumer.accept(event);
+ }
+
+ private static MacAddress getMacAddress(byte[] linkLayerAddress) {
+ if (linkLayerAddress != null) {
+ try {
+ return MacAddress.fromBytes(linkLayerAddress);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Failed to parse link-layer address: " + hexify(linkLayerAddress));
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java b/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java
new file mode 100644
index 0000000..761db68
--- /dev/null
+++ b/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java
@@ -0,0 +1,393 @@
+/*
+ * Copyright (C) 2015 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.ip;
+
+import static android.net.metrics.IpReachabilityEvent.NUD_FAILED;
+import static android.net.metrics.IpReachabilityEvent.NUD_FAILED_ORGANIC;
+import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST;
+import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST_ORGANIC;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.RouteInfo;
+import android.net.ip.IpNeighborMonitor.NeighborEvent;
+import android.net.metrics.IpConnectivityLog;
+import android.net.metrics.IpReachabilityEvent;
+import android.net.netlink.StructNdMsg;
+import android.net.util.InterfaceParams;
+import android.net.util.SharedLog;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.DumpUtils.Dump;
+
+import java.io.PrintWriter;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * IpReachabilityMonitor.
+ *
+ * Monitors on-link IP reachability and notifies callers whenever any on-link
+ * addresses of interest appear to have become unresponsive.
+ *
+ * This code does not concern itself with "why" a neighbour might have become
+ * unreachable. Instead, it primarily reacts to the kernel's notion of IP
+ * reachability for each of the neighbours we know to be critically important
+ * to normal network connectivity. As such, it is often "just the messenger":
+ * the neighbours about which it warns are already deemed by the kernel to have
+ * become unreachable.
+ *
+ *
+ * How it works:
+ *
+ * 1. The "on-link neighbours of interest" found in a given LinkProperties
+ * instance are added to a "watch list" via #updateLinkProperties().
+ * This usually means all default gateways and any on-link DNS servers.
+ *
+ * 2. We listen continuously for netlink neighbour messages (RTM_NEWNEIGH,
+ * RTM_DELNEIGH), watching only for neighbours in the watch list.
+ *
+ * - A neighbour going into NUD_REACHABLE, NUD_STALE, NUD_DELAY, and
+ * even NUD_PROBE is perfectly normal; we merely record the new state.
+ *
+ * - A neighbour's entry may be deleted (RTM_DELNEIGH), for example due
+ * to garbage collection. This is not necessarily of immediate
+ * concern; we record the neighbour as moving to NUD_NONE.
+ *
+ * - A neighbour transitioning to NUD_FAILED (for any reason) is
+ * critically important and is handled as described below in #4.
+ *
+ * 3. All on-link neighbours in the watch list can be forcibly "probed" by
+ * calling #probeAll(). This should be called whenever it is important to
+ * verify that critical neighbours on the link are still reachable, e.g.
+ * when roaming between BSSIDs.
+ *
+ * - The kernel will send unicast ARP requests for IPv4 neighbours and
+ * unicast NS packets for IPv6 neighbours. The expected replies will
+ * likely be unicast.
+ *
+ * - The forced probing is done holding a wakelock. The kernel may,
+ * however, initiate probing of a neighbor on its own, i.e. whenever
+ * a neighbour has expired from NUD_DELAY.
+ *
+ * - The kernel sends:
+ *
+ * /proc/sys/net/ipv{4,6}/neigh/<ifname>/ucast_solicit
+ *
+ * number of probes (usually 3) every:
+ *
+ * /proc/sys/net/ipv{4,6}/neigh/<ifname>/retrans_time_ms
+ *
+ * number of milliseconds (usually 1000ms). This normally results in
+ * 3 unicast packets, 1 per second.
+ *
+ * - If no response is received to any of the probe packets, the kernel
+ * marks the neighbour as being in state NUD_FAILED, and the listening
+ * process in #2 will learn of it.
+ *
+ * 4. We call the supplied Callback#notifyLost() function if the loss of a
+ * neighbour in NUD_FAILED would cause IPv4 or IPv6 configuration to
+ * become incomplete (a loss of provisioning).
+ *
+ * - For example, losing all our IPv4 on-link DNS servers (or losing
+ * our only IPv6 default gateway) constitutes a loss of IPv4 (IPv6)
+ * provisioning; Callback#notifyLost() would be called.
+ *
+ * - Since it can be non-trivial to reacquire certain IP provisioning
+ * state it may be best for the link to disconnect completely and
+ * reconnect afresh.
+ *
+ * Accessing an instance of this class from multiple threads is NOT safe.
+ *
+ * @hide
+ */
+public class IpReachabilityMonitor {
+ private static final String TAG = "IpReachabilityMonitor";
+ private static final boolean DBG = false;
+ private static final boolean VDBG = false;
+
+ public interface Callback {
+ // This callback function must execute as quickly as possible as it is
+ // run on the same thread that listens to kernel neighbor updates.
+ //
+ // TODO: refactor to something like notifyProvisioningLost(String msg).
+ public void notifyLost(InetAddress ip, String logMsg);
+ }
+
+ /**
+ * Encapsulates IpReachabilityMonitor depencencies on systems that hinder unit testing.
+ * TODO: consider also wrapping MultinetworkPolicyTracker in this interface.
+ */
+ interface Dependencies {
+ void acquireWakeLock(long durationMs);
+
+ static Dependencies makeDefault(Context context, String iface) {
+ final String lockName = TAG + "." + iface;
+ final PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ final WakeLock lock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, lockName);
+
+ return new Dependencies() {
+ public void acquireWakeLock(long durationMs) {
+ lock.acquire(durationMs);
+ }
+ };
+ }
+ }
+
+ private final InterfaceParams mInterfaceParams;
+ private final IpNeighborMonitor mIpNeighborMonitor;
+ private final SharedLog mLog;
+ private final Callback mCallback;
+ private final Dependencies mDependencies;
+ private final boolean mUsingMultinetworkPolicyTracker;
+ private final ConnectivityManager mCm;
+ private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
+ private LinkProperties mLinkProperties = new LinkProperties();
+ private Map<InetAddress, NeighborEvent> mNeighborWatchList = new HashMap<>();
+ // Time in milliseconds of the last forced probe request.
+ private volatile long mLastProbeTimeMs;
+
+ public IpReachabilityMonitor(
+ Context context, InterfaceParams ifParams, Handler h, SharedLog log, Callback callback,
+ boolean usingMultinetworkPolicyTracker) {
+ this(context, ifParams, h, log, callback, usingMultinetworkPolicyTracker,
+ Dependencies.makeDefault(context, ifParams.name));
+ }
+
+ @VisibleForTesting
+ IpReachabilityMonitor(Context context, InterfaceParams ifParams, Handler h, SharedLog log,
+ Callback callback, boolean usingMultinetworkPolicyTracker, Dependencies dependencies) {
+ if (ifParams == null) throw new IllegalArgumentException("null InterfaceParams");
+
+ mInterfaceParams = ifParams;
+ mLog = log.forSubComponent(TAG);
+ mCallback = callback;
+ mUsingMultinetworkPolicyTracker = usingMultinetworkPolicyTracker;
+ mCm = context.getSystemService(ConnectivityManager.class);
+ mDependencies = dependencies;
+
+ mIpNeighborMonitor = new IpNeighborMonitor(h, mLog,
+ (NeighborEvent event) -> {
+ if (mInterfaceParams.index != event.ifindex) return;
+ if (!mNeighborWatchList.containsKey(event.ip)) return;
+
+ final NeighborEvent prev = mNeighborWatchList.put(event.ip, event);
+
+ // TODO: Consider what to do with other states that are not within
+ // NeighborEvent#isValid() (i.e. NUD_NONE, NUD_INCOMPLETE).
+ if (event.nudState == StructNdMsg.NUD_FAILED) {
+ mLog.w("ALERT neighbor went from: " + prev + " to: " + event);
+ handleNeighborLost(event);
+ }
+ });
+ mIpNeighborMonitor.start();
+ }
+
+ public void stop() {
+ mIpNeighborMonitor.stop();
+ clearLinkProperties();
+ }
+
+ public void dump(PrintWriter pw) {
+ DumpUtils.dumpAsync(
+ mIpNeighborMonitor.getHandler(),
+ new Dump() {
+ @Override
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(describeWatchList("\n"));
+ }
+ },
+ pw, "", 1000);
+ }
+
+ private String describeWatchList() { return describeWatchList(" "); }
+
+ private String describeWatchList(String sep) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("iface{" + mInterfaceParams + "}," + sep);
+ sb.append("ntable=[" + sep);
+ String delimiter = "";
+ for (Map.Entry<InetAddress, NeighborEvent> entry : mNeighborWatchList.entrySet()) {
+ sb.append(delimiter).append(entry.getKey().getHostAddress() + "/" + entry.getValue());
+ delimiter = "," + sep;
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ private static boolean isOnLink(List<RouteInfo> routes, InetAddress ip) {
+ for (RouteInfo route : routes) {
+ if (!route.hasGateway() && route.matches(ip)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void updateLinkProperties(LinkProperties lp) {
+ if (!mInterfaceParams.name.equals(lp.getInterfaceName())) {
+ // TODO: figure out whether / how to cope with interface changes.
+ Log.wtf(TAG, "requested LinkProperties interface '" + lp.getInterfaceName() +
+ "' does not match: " + mInterfaceParams.name);
+ return;
+ }
+
+ mLinkProperties = new LinkProperties(lp);
+ Map<InetAddress, NeighborEvent> newNeighborWatchList = new HashMap<>();
+
+ final List<RouteInfo> routes = mLinkProperties.getRoutes();
+ for (RouteInfo route : routes) {
+ if (route.hasGateway()) {
+ InetAddress gw = route.getGateway();
+ if (isOnLink(routes, gw)) {
+ newNeighborWatchList.put(gw, mNeighborWatchList.getOrDefault(gw, null));
+ }
+ }
+ }
+
+ for (InetAddress dns : lp.getDnsServers()) {
+ if (isOnLink(routes, dns)) {
+ newNeighborWatchList.put(dns, mNeighborWatchList.getOrDefault(dns, null));
+ }
+ }
+
+ mNeighborWatchList = newNeighborWatchList;
+ if (DBG) { Log.d(TAG, "watch: " + describeWatchList()); }
+ }
+
+ public void clearLinkProperties() {
+ mLinkProperties.clear();
+ mNeighborWatchList.clear();
+ if (DBG) { Log.d(TAG, "clear: " + describeWatchList()); }
+ }
+
+ private void handleNeighborLost(NeighborEvent event) {
+ final LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
+
+ InetAddress ip = null;
+ for (Map.Entry<InetAddress, NeighborEvent> entry : mNeighborWatchList.entrySet()) {
+ // TODO: Consider using NeighborEvent#isValid() here; it's more
+ // strict but may interact badly if other entries are somehow in
+ // NUD_INCOMPLETE (say, during network attach).
+ if (entry.getValue().nudState != StructNdMsg.NUD_FAILED) continue;
+
+ ip = entry.getKey();
+ for (RouteInfo route : mLinkProperties.getRoutes()) {
+ if (ip.equals(route.getGateway())) {
+ whatIfLp.removeRoute(route);
+ }
+ }
+
+ if (avoidingBadLinks() || !(ip instanceof Inet6Address)) {
+ // We should do this unconditionally, but alas we cannot: b/31827713.
+ whatIfLp.removeDnsServer(ip);
+ }
+ }
+
+ final boolean lostProvisioning =
+ (mLinkProperties.isIPv4Provisioned() && !whatIfLp.isIPv4Provisioned())
+ || (mLinkProperties.isIPv6Provisioned() && !whatIfLp.isIPv6Provisioned());
+
+ if (lostProvisioning) {
+ final String logMsg = "FAILURE: LOST_PROVISIONING, " + event;
+ Log.w(TAG, logMsg);
+ if (mCallback != null) {
+ // TODO: remove |ip| when the callback signature no longer has
+ // an InetAddress argument.
+ mCallback.notifyLost(ip, logMsg);
+ }
+ }
+ logNudFailed(lostProvisioning);
+ }
+
+ private boolean avoidingBadLinks() {
+ return !mUsingMultinetworkPolicyTracker || mCm.getAvoidBadWifi();
+ }
+
+ public void probeAll() {
+ final List<InetAddress> ipProbeList = new ArrayList<>(mNeighborWatchList.keySet());
+
+ if (!ipProbeList.isEmpty()) {
+ // Keep the CPU awake long enough to allow all ARP/ND
+ // probes a reasonable chance at success. See b/23197666.
+ //
+ // The wakelock we use is (by default) refcounted, and this version
+ // of acquire(timeout) queues a release message to keep acquisitions
+ // and releases balanced.
+ mDependencies.acquireWakeLock(getProbeWakeLockDuration());
+ }
+
+ for (InetAddress ip : ipProbeList) {
+ final int rval = IpNeighborMonitor.startKernelNeighborProbe(mInterfaceParams.index, ip);
+ mLog.log(String.format("put neighbor %s into NUD_PROBE state (rval=%d)",
+ ip.getHostAddress(), rval));
+ logEvent(IpReachabilityEvent.PROBE, rval);
+ }
+ mLastProbeTimeMs = SystemClock.elapsedRealtime();
+ }
+
+ private static long getProbeWakeLockDuration() {
+ // Ideally, this would be computed by examining the values of:
+ //
+ // /proc/sys/net/ipv[46]/neigh/<ifname>/ucast_solicit
+ //
+ // and:
+ //
+ // /proc/sys/net/ipv[46]/neigh/<ifname>/retrans_time_ms
+ //
+ // For now, just make some assumptions.
+ final long numUnicastProbes = 3;
+ final long retransTimeMs = 1000;
+ final long gracePeriodMs = 500;
+ return (numUnicastProbes * retransTimeMs) + gracePeriodMs;
+ }
+
+ private void logEvent(int probeType, int errorCode) {
+ int eventType = probeType | (errorCode & 0xff);
+ mMetricsLog.log(mInterfaceParams.name, new IpReachabilityEvent(eventType));
+ }
+
+ private void logNudFailed(boolean lostProvisioning) {
+ long duration = SystemClock.elapsedRealtime() - mLastProbeTimeMs;
+ boolean isFromProbe = (duration < getProbeWakeLockDuration());
+ int eventType = nudFailureEventType(isFromProbe, lostProvisioning);
+ mMetricsLog.log(mInterfaceParams.name, new IpReachabilityEvent(eventType));
+ }
+
+ /**
+ * Returns the NUD failure event type code corresponding to the given conditions.
+ */
+ private static int nudFailureEventType(boolean isFromProbe, boolean isProvisioningLost) {
+ if (isFromProbe) {
+ return isProvisioningLost ? PROVISIONING_LOST : NUD_FAILED;
+ } else {
+ return isProvisioningLost ? PROVISIONING_LOST_ORGANIC : NUD_FAILED_ORGANIC;
+ }
+ }
+}
diff --git a/packages/NetworkStack/src/android/net/util/ConnectivityPacketSummary.java b/packages/NetworkStack/src/android/net/util/ConnectivityPacketSummary.java
new file mode 100644
index 0000000..08c3f60
--- /dev/null
+++ b/packages/NetworkStack/src/android/net/util/ConnectivityPacketSummary.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2016 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.util;
+
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import static com.android.server.util.NetworkStackConstants.ARP_HWTYPE_ETHER;
+import static com.android.server.util.NetworkStackConstants.ARP_PAYLOAD_LEN;
+import static com.android.server.util.NetworkStackConstants.ARP_REPLY;
+import static com.android.server.util.NetworkStackConstants.ARP_REQUEST;
+import static com.android.server.util.NetworkStackConstants.DHCP4_CLIENT_PORT;
+import static com.android.server.util.NetworkStackConstants.ETHER_ADDR_LEN;
+import static com.android.server.util.NetworkStackConstants.ETHER_DST_ADDR_OFFSET;
+import static com.android.server.util.NetworkStackConstants.ETHER_HEADER_LEN;
+import static com.android.server.util.NetworkStackConstants.ETHER_SRC_ADDR_OFFSET;
+import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_ARP;
+import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_IPV4;
+import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_IPV6;
+import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_OFFSET;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_HEADER_MIN_LEN;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_MIN_LENGTH;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_MTU;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
+import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_LEN;
+import static com.android.server.util.NetworkStackConstants.IPV4_DST_ADDR_OFFSET;
+import static com.android.server.util.NetworkStackConstants.IPV4_FLAGS_OFFSET;
+import static com.android.server.util.NetworkStackConstants.IPV4_FRAGMENT_MASK;
+import static com.android.server.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
+import static com.android.server.util.NetworkStackConstants.IPV4_IHL_MASK;
+import static com.android.server.util.NetworkStackConstants.IPV4_PROTOCOL_OFFSET;
+import static com.android.server.util.NetworkStackConstants.IPV4_SRC_ADDR_OFFSET;
+import static com.android.server.util.NetworkStackConstants.IPV6_ADDR_LEN;
+import static com.android.server.util.NetworkStackConstants.IPV6_HEADER_LEN;
+import static com.android.server.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET;
+import static com.android.server.util.NetworkStackConstants.IPV6_SRC_ADDR_OFFSET;
+import static com.android.server.util.NetworkStackConstants.UDP_HEADER_LEN;
+
+import android.net.MacAddress;
+import android.net.dhcp.DhcpPacket;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.StringJoiner;
+
+
+/**
+ * Critical connectivity packet summarizing class.
+ *
+ * Outputs short descriptions of ARP, DHCPv4, and IPv6 RS/RA/NS/NA packets.
+ *
+ * @hide
+ */
+public class ConnectivityPacketSummary {
+ private static final String TAG = ConnectivityPacketSummary.class.getSimpleName();
+
+ private final byte[] mHwAddr;
+ private final byte[] mBytes;
+ private final int mLength;
+ private final ByteBuffer mPacket;
+ private final String mSummary;
+
+ public static String summarize(MacAddress hwaddr, byte[] buffer) {
+ return summarize(hwaddr, buffer, buffer.length);
+ }
+
+ // Methods called herein perform some but by no means all error checking.
+ // They may throw runtime exceptions on malformed packets.
+ public static String summarize(MacAddress macAddr, byte[] buffer, int length) {
+ if ((macAddr == null) || (buffer == null)) return null;
+ length = Math.min(length, buffer.length);
+ return (new ConnectivityPacketSummary(macAddr, buffer, length)).toString();
+ }
+
+ private ConnectivityPacketSummary(MacAddress macAddr, byte[] buffer, int length) {
+ mHwAddr = macAddr.toByteArray();
+ mBytes = buffer;
+ mLength = Math.min(length, mBytes.length);
+ mPacket = ByteBuffer.wrap(mBytes, 0, mLength);
+ mPacket.order(ByteOrder.BIG_ENDIAN);
+
+ final StringJoiner sj = new StringJoiner(" ");
+ // TODO: support other link-layers, or even no link-layer header.
+ parseEther(sj);
+ mSummary = sj.toString();
+ }
+
+ public String toString() {
+ return mSummary;
+ }
+
+ private void parseEther(StringJoiner sj) {
+ if (mPacket.remaining() < ETHER_HEADER_LEN) {
+ sj.add("runt:").add(asString(mPacket.remaining()));
+ return;
+ }
+
+ mPacket.position(ETHER_SRC_ADDR_OFFSET);
+ final ByteBuffer srcMac = (ByteBuffer) mPacket.slice().limit(ETHER_ADDR_LEN);
+ sj.add(ByteBuffer.wrap(mHwAddr).equals(srcMac) ? "TX" : "RX");
+ sj.add(getMacAddressString(srcMac));
+
+ mPacket.position(ETHER_DST_ADDR_OFFSET);
+ final ByteBuffer dstMac = (ByteBuffer) mPacket.slice().limit(ETHER_ADDR_LEN);
+ sj.add(">").add(getMacAddressString(dstMac));
+
+ mPacket.position(ETHER_TYPE_OFFSET);
+ final int etherType = asUint(mPacket.getShort());
+ switch (etherType) {
+ case ETHER_TYPE_ARP:
+ sj.add("arp");
+ parseARP(sj);
+ break;
+ case ETHER_TYPE_IPV4:
+ sj.add("ipv4");
+ parseIPv4(sj);
+ break;
+ case ETHER_TYPE_IPV6:
+ sj.add("ipv6");
+ parseIPv6(sj);
+ break;
+ default:
+ // Unknown ether type.
+ sj.add("ethtype").add(asString(etherType));
+ break;
+ }
+ }
+
+ private void parseARP(StringJoiner sj) {
+ if (mPacket.remaining() < ARP_PAYLOAD_LEN) {
+ sj.add("runt:").add(asString(mPacket.remaining()));
+ return;
+ }
+
+ if (asUint(mPacket.getShort()) != ARP_HWTYPE_ETHER ||
+ asUint(mPacket.getShort()) != ETHER_TYPE_IPV4 ||
+ asUint(mPacket.get()) != ETHER_ADDR_LEN ||
+ asUint(mPacket.get()) != IPV4_ADDR_LEN) {
+ sj.add("unexpected header");
+ return;
+ }
+
+ final int opCode = asUint(mPacket.getShort());
+
+ final String senderHwAddr = getMacAddressString(mPacket);
+ final String senderIPv4 = getIPv4AddressString(mPacket);
+ getMacAddressString(mPacket); // target hardware address, unused
+ final String targetIPv4 = getIPv4AddressString(mPacket);
+
+ if (opCode == ARP_REQUEST) {
+ sj.add("who-has").add(targetIPv4);
+ } else if (opCode == ARP_REPLY) {
+ sj.add("reply").add(senderIPv4).add(senderHwAddr);
+ } else {
+ sj.add("unknown opcode").add(asString(opCode));
+ }
+ }
+
+ private void parseIPv4(StringJoiner sj) {
+ if (!mPacket.hasRemaining()) {
+ sj.add("runt");
+ return;
+ }
+
+ final int startOfIpLayer = mPacket.position();
+ final int ipv4HeaderLength = (mPacket.get(startOfIpLayer) & IPV4_IHL_MASK) * 4;
+ if (mPacket.remaining() < ipv4HeaderLength ||
+ mPacket.remaining() < IPV4_HEADER_MIN_LEN) {
+ sj.add("runt:").add(asString(mPacket.remaining()));
+ return;
+ }
+ final int startOfTransportLayer = startOfIpLayer + ipv4HeaderLength;
+
+ mPacket.position(startOfIpLayer + IPV4_FLAGS_OFFSET);
+ final int flagsAndFragment = asUint(mPacket.getShort());
+ final boolean isFragment = (flagsAndFragment & IPV4_FRAGMENT_MASK) != 0;
+
+ mPacket.position(startOfIpLayer + IPV4_PROTOCOL_OFFSET);
+ final int protocol = asUint(mPacket.get());
+
+ mPacket.position(startOfIpLayer + IPV4_SRC_ADDR_OFFSET);
+ final String srcAddr = getIPv4AddressString(mPacket);
+
+ mPacket.position(startOfIpLayer + IPV4_DST_ADDR_OFFSET);
+ final String dstAddr = getIPv4AddressString(mPacket);
+
+ sj.add(srcAddr).add(">").add(dstAddr);
+
+ mPacket.position(startOfTransportLayer);
+ if (protocol == IPPROTO_UDP) {
+ sj.add("udp");
+ if (isFragment) sj.add("fragment");
+ else parseUDP(sj);
+ } else {
+ sj.add("proto").add(asString(protocol));
+ if (isFragment) sj.add("fragment");
+ }
+ }
+
+ private void parseIPv6(StringJoiner sj) {
+ if (mPacket.remaining() < IPV6_HEADER_LEN) {
+ sj.add("runt:").add(asString(mPacket.remaining()));
+ return;
+ }
+
+ final int startOfIpLayer = mPacket.position();
+
+ mPacket.position(startOfIpLayer + IPV6_PROTOCOL_OFFSET);
+ final int protocol = asUint(mPacket.get());
+
+ mPacket.position(startOfIpLayer + IPV6_SRC_ADDR_OFFSET);
+ final String srcAddr = getIPv6AddressString(mPacket);
+ final String dstAddr = getIPv6AddressString(mPacket);
+
+ sj.add(srcAddr).add(">").add(dstAddr);
+
+ mPacket.position(startOfIpLayer + IPV6_HEADER_LEN);
+ if (protocol == IPPROTO_ICMPV6) {
+ sj.add("icmp6");
+ parseICMPv6(sj);
+ } else {
+ sj.add("proto").add(asString(protocol));
+ }
+ }
+
+ private void parseICMPv6(StringJoiner sj) {
+ if (mPacket.remaining() < ICMPV6_HEADER_MIN_LEN) {
+ sj.add("runt:").add(asString(mPacket.remaining()));
+ return;
+ }
+
+ final int icmp6Type = asUint(mPacket.get());
+ final int icmp6Code = asUint(mPacket.get());
+ mPacket.getShort(); // checksum, unused
+
+ switch (icmp6Type) {
+ case ICMPV6_ROUTER_SOLICITATION:
+ sj.add("rs");
+ parseICMPv6RouterSolicitation(sj);
+ break;
+ case ICMPV6_ROUTER_ADVERTISEMENT:
+ sj.add("ra");
+ parseICMPv6RouterAdvertisement(sj);
+ break;
+ case ICMPV6_NEIGHBOR_SOLICITATION:
+ sj.add("ns");
+ parseICMPv6NeighborMessage(sj);
+ break;
+ case ICMPV6_NEIGHBOR_ADVERTISEMENT:
+ sj.add("na");
+ parseICMPv6NeighborMessage(sj);
+ break;
+ default:
+ sj.add("type").add(asString(icmp6Type));
+ sj.add("code").add(asString(icmp6Code));
+ break;
+ }
+ }
+
+ private void parseICMPv6RouterSolicitation(StringJoiner sj) {
+ final int RESERVED = 4;
+ if (mPacket.remaining() < RESERVED) {
+ sj.add("runt:").add(asString(mPacket.remaining()));
+ return;
+ }
+
+ mPacket.position(mPacket.position() + RESERVED);
+ parseICMPv6NeighborDiscoveryOptions(sj);
+ }
+
+ private void parseICMPv6RouterAdvertisement(StringJoiner sj) {
+ final int FLAGS_AND_TIMERS = 3 * 4;
+ if (mPacket.remaining() < FLAGS_AND_TIMERS) {
+ sj.add("runt:").add(asString(mPacket.remaining()));
+ return;
+ }
+
+ mPacket.position(mPacket.position() + FLAGS_AND_TIMERS);
+ parseICMPv6NeighborDiscoveryOptions(sj);
+ }
+
+ private void parseICMPv6NeighborMessage(StringJoiner sj) {
+ final int RESERVED = 4;
+ final int minReq = RESERVED + IPV6_ADDR_LEN;
+ if (mPacket.remaining() < minReq) {
+ sj.add("runt:").add(asString(mPacket.remaining()));
+ return;
+ }
+
+ mPacket.position(mPacket.position() + RESERVED);
+ sj.add(getIPv6AddressString(mPacket));
+ parseICMPv6NeighborDiscoveryOptions(sj);
+ }
+
+ private void parseICMPv6NeighborDiscoveryOptions(StringJoiner sj) {
+ // All ND options are TLV, where T is one byte and L is one byte equal
+ // to the length of T + L + V in units of 8 octets.
+ while (mPacket.remaining() >= ICMPV6_ND_OPTION_MIN_LENGTH) {
+ final int ndType = asUint(mPacket.get());
+ final int ndLength = asUint(mPacket.get());
+ final int ndBytes = ndLength * ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR - 2;
+ if (ndBytes < 0 || ndBytes > mPacket.remaining()) {
+ sj.add("<malformed>");
+ break;
+ }
+ final int position = mPacket.position();
+
+ switch (ndType) {
+ case ICMPV6_ND_OPTION_SLLA:
+ sj.add("slla");
+ sj.add(getMacAddressString(mPacket));
+ break;
+ case ICMPV6_ND_OPTION_TLLA:
+ sj.add("tlla");
+ sj.add(getMacAddressString(mPacket));
+ break;
+ case ICMPV6_ND_OPTION_MTU:
+ sj.add("mtu");
+ final short reserved = mPacket.getShort();
+ sj.add(asString(mPacket.getInt()));
+ break;
+ default:
+ // Skip.
+ break;
+ }
+
+ mPacket.position(position + ndBytes);
+ }
+ }
+
+ private void parseUDP(StringJoiner sj) {
+ if (mPacket.remaining() < UDP_HEADER_LEN) {
+ sj.add("runt:").add(asString(mPacket.remaining()));
+ return;
+ }
+
+ final int previous = mPacket.position();
+ final int srcPort = asUint(mPacket.getShort());
+ final int dstPort = asUint(mPacket.getShort());
+ sj.add(asString(srcPort)).add(">").add(asString(dstPort));
+
+ mPacket.position(previous + UDP_HEADER_LEN);
+ if (srcPort == DHCP4_CLIENT_PORT || dstPort == DHCP4_CLIENT_PORT) {
+ sj.add("dhcp4");
+ parseDHCPv4(sj);
+ }
+ }
+
+ private void parseDHCPv4(StringJoiner sj) {
+ final DhcpPacket dhcpPacket;
+ try {
+ dhcpPacket = DhcpPacket.decodeFullPacket(mBytes, mLength, DhcpPacket.ENCAP_L2);
+ sj.add(dhcpPacket.toString());
+ } catch (DhcpPacket.ParseException e) {
+ sj.add("parse error: " + e);
+ }
+ }
+
+ private static String getIPv4AddressString(ByteBuffer ipv4) {
+ return getIpAddressString(ipv4, IPV4_ADDR_LEN);
+ }
+
+ private static String getIPv6AddressString(ByteBuffer ipv6) {
+ return getIpAddressString(ipv6, IPV6_ADDR_LEN);
+ }
+
+ private static String getIpAddressString(ByteBuffer ip, int byteLength) {
+ if (ip == null || ip.remaining() < byteLength) return "invalid";
+
+ byte[] bytes = new byte[byteLength];
+ ip.get(bytes, 0, byteLength);
+ try {
+ InetAddress addr = InetAddress.getByAddress(bytes);
+ return addr.getHostAddress();
+ } catch (UnknownHostException uhe) {
+ return "unknown";
+ }
+ }
+
+ private static String getMacAddressString(ByteBuffer mac) {
+ if (mac == null || mac.remaining() < ETHER_ADDR_LEN) return "invalid";
+
+ byte[] bytes = new byte[ETHER_ADDR_LEN];
+ mac.get(bytes, 0, bytes.length);
+ Object[] printableBytes = new Object[bytes.length];
+ int i = 0;
+ for (byte b : bytes) printableBytes[i++] = new Byte(b);
+
+ final String MAC48_FORMAT = "%02x:%02x:%02x:%02x:%02x:%02x";
+ return String.format(MAC48_FORMAT, printableBytes);
+ }
+
+ /**
+ * Convenience method to convert an int to a String.
+ */
+ public static String asString(int i) {
+ return Integer.toString(i);
+ }
+
+ /**
+ * Convenience method to read a byte as an unsigned int.
+ */
+ public static int asUint(byte b) {
+ return (b & 0xff);
+ }
+
+ /**
+ * Convenience method to read a short as an unsigned int.
+ */
+ public static int asUint(short s) {
+ return (s & 0xffff);
+ }
+}
diff --git a/services/net/java/android/net/util/FdEventsReader.java b/packages/NetworkStack/src/android/net/util/FdEventsReader.java
similarity index 100%
rename from services/net/java/android/net/util/FdEventsReader.java
rename to packages/NetworkStack/src/android/net/util/FdEventsReader.java
diff --git a/services/net/java/android/net/util/PacketReader.java b/packages/NetworkStack/src/android/net/util/PacketReader.java
similarity index 100%
rename from services/net/java/android/net/util/PacketReader.java
rename to packages/NetworkStack/src/android/net/util/PacketReader.java
diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
index 057012d..4080ddf 100644
--- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java
+++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
@@ -20,6 +20,7 @@
import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR;
+import static com.android.server.util.PermissionUtil.checkDumpPermission;
import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission;
import android.annotation.NonNull;
@@ -38,6 +39,8 @@
import android.net.dhcp.DhcpServingParams;
import android.net.dhcp.DhcpServingParamsParcel;
import android.net.dhcp.IDhcpServerCallbacks;
+import android.net.ip.IIpClientCallbacks;
+import android.net.ip.IpClient;
import android.net.shared.PrivateDnsConfig;
import android.net.util.SharedLog;
import android.os.IBinder;
@@ -49,7 +52,11 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
/**
* Android service used to start the network stack when bound to via an intent.
@@ -79,6 +86,8 @@
private static final int NUM_VALIDATION_LOG_LINES = 20;
private final Context mContext;
private final ConnectivityManager mCm;
+ @GuardedBy("mIpClients")
+ private final ArrayList<WeakReference<IpClient>> mIpClients = new ArrayList<>();
private static final int MAX_VALIDATION_LOGS = 10;
@GuardedBy("mValidationLogs")
@@ -137,13 +146,58 @@
}
@Override
+ public void makeIpClient(String ifName, IIpClientCallbacks cb) throws RemoteException {
+ final IpClient ipClient = new IpClient(mContext, ifName, cb);
+
+ synchronized (mIpClients) {
+ final Iterator<WeakReference<IpClient>> it = mIpClients.iterator();
+ while (it.hasNext()) {
+ final IpClient ipc = it.next().get();
+ if (ipc == null) {
+ it.remove();
+ }
+ }
+ mIpClients.add(new WeakReference<>(ipClient));
+ }
+
+ cb.onIpClientCreated(ipClient.makeConnector());
+ }
+
+ @Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {
- checkNetworkStackCallingPermission();
+ checkDumpPermission();
final IndentingPrintWriter pw = new IndentingPrintWriter(fout, " ");
pw.println("NetworkStack logs:");
mLog.dump(fd, pw, args);
+ // Dump full IpClient logs for non-GCed clients
+ pw.println();
+ pw.println("Recently active IpClient logs:");
+ final ArrayList<IpClient> ipClients = new ArrayList<>();
+ final HashSet<String> dumpedIpClientIfaces = new HashSet<>();
+ synchronized (mIpClients) {
+ for (WeakReference<IpClient> ipcRef : mIpClients) {
+ final IpClient ipc = ipcRef.get();
+ if (ipc != null) {
+ ipClients.add(ipc);
+ }
+ }
+ }
+
+ for (IpClient ipc : ipClients) {
+ pw.println(ipc.getName());
+ pw.increaseIndent();
+ ipc.dump(fd, pw, args);
+ pw.decreaseIndent();
+ dumpedIpClientIfaces.add(ipc.getInterfaceName());
+ }
+
+ // State machine and connectivity metrics logs are kept for GCed IpClients
+ pw.println();
+ pw.println("Other IpClient logs:");
+ IpClient.dumpAllLogs(fout, dumpedIpClientIfaces);
+
pw.println();
pw.println("Validation logs (most recent first):");
synchronized (mValidationLogs) {
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
index 4077d93..6b31b82 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
@@ -24,6 +24,7 @@
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.metrics.ValidationProbeEvent.DNS_FAILURE;
@@ -80,7 +81,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.Protocol;
import com.android.internal.util.RingBufferIndices;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
@@ -150,29 +150,28 @@
}
}
- private static final int BASE = Protocol.BASE_NETWORK_MONITOR;
/**
* ConnectivityService has sent a notification to indicate that network has connected.
* Initiates Network Validation.
*/
- private static final int CMD_NETWORK_CONNECTED = BASE + 1;
+ private static final int CMD_NETWORK_CONNECTED = 1;
/**
* Message to self indicating it's time to evaluate a network's connectivity.
* arg1 = Token to ignore old messages.
*/
- private static final int CMD_REEVALUATE = BASE + 6;
+ private static final int CMD_REEVALUATE = 6;
/**
* ConnectivityService has sent a notification to indicate that network has disconnected.
*/
- private static final int CMD_NETWORK_DISCONNECTED = BASE + 7;
+ private static final int CMD_NETWORK_DISCONNECTED = 7;
/**
* Force evaluation even if it has succeeded in the past.
* arg1 = UID responsible for requesting this reeval. Will be billed for data.
*/
- private static final int CMD_FORCE_REEVALUATION = BASE + 8;
+ private static final int CMD_FORCE_REEVALUATION = 8;
/**
* Message to self indicating captive portal app finished.
@@ -181,7 +180,7 @@
* APP_RETURN_WANTED_AS_IS
* obj = mCaptivePortalLoggedInResponseToken as String
*/
- private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = BASE + 9;
+ private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = 9;
/**
* Message indicating sign-in app should be launched.
@@ -190,14 +189,14 @@
* ConnectivityService when the user touches the "sign into
* network" button in the wifi access point detail page.
*/
- private static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = BASE + 11;
+ private static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = 11;
/**
* Retest network to see if captive portal is still in place.
* arg1 = UID responsible for requesting this reeval. Will be billed for data.
* 0 indicates self-initiated, so nobody to blame.
*/
- private static final int CMD_CAPTIVE_PORTAL_RECHECK = BASE + 12;
+ private static final int CMD_CAPTIVE_PORTAL_RECHECK = 12;
/**
* ConnectivityService notifies NetworkMonitor of settings changes to
@@ -210,20 +209,20 @@
* states, including being ignored until after an ongoing captive portal
* validation phase is completed.
*/
- private static final int CMD_PRIVATE_DNS_SETTINGS_CHANGED = BASE + 13;
- private static final int CMD_EVALUATE_PRIVATE_DNS = BASE + 15;
+ private static final int CMD_PRIVATE_DNS_SETTINGS_CHANGED = 13;
+ private static final int CMD_EVALUATE_PRIVATE_DNS = 15;
/**
* Message to self indicating captive portal detection is completed.
* obj = CaptivePortalProbeResult for detection result;
*/
- public static final int CMD_PROBE_COMPLETE = BASE + 16;
+ public static final int CMD_PROBE_COMPLETE = 16;
/**
* ConnectivityService notifies NetworkMonitor of DNS query responses event.
* arg1 = returncode in OnDnsEvent which indicates the response code for the DNS query.
*/
- public static final int EVENT_DNS_NOTIFICATION = BASE + 17;
+ public static final int EVENT_DNS_NOTIFICATION = 17;
// Start mReevaluateDelayMs at this value and double.
private static final int INITIAL_REEVALUATE_DELAY_MS = 1000;
@@ -245,11 +244,9 @@
private final INetworkMonitorCallbacks mCallback;
private final Network mNetwork;
private final Network mNonPrivateDnsBypassNetwork;
- private final int mNetId;
private final TelephonyManager mTelephonyManager;
private final WifiManager mWifiManager;
private final ConnectivityManager mCm;
- private final NetworkRequest mDefaultRequest;
private final IpConnectivityLog mMetricsLog;
private final Dependencies mDependencies;
@@ -322,7 +319,7 @@
NetworkRequest defaultRequest, IpConnectivityLog logger, SharedLog validationLogs,
Dependencies deps) {
// Add suffix indicating which NetworkMonitor we're talking about.
- super(TAG + "/" + network.netId);
+ super(TAG + "/" + network.toString());
// Logs with a tag of the form given just above, e.g.
// <timestamp> 862 2402 D NetworkMonitor/NetworkAgentInfo [WIFI () - 100]: ...
@@ -335,11 +332,9 @@
mDependencies = deps;
mNonPrivateDnsBypassNetwork = network;
mNetwork = deps.getPrivateDnsBypassNetwork(network);
- mNetId = mNetwork.netId;
mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
- mDefaultRequest = defaultRequest;
// CHECKSTYLE:OFF IndentationCheck
addState(mDefaultState);
@@ -471,7 +466,7 @@
@Override
protected void log(String s) {
- if (DBG) Log.d(TAG + "/" + mNetwork.netId, s);
+ if (DBG) Log.d(TAG + "/" + mNetwork.toString(), s);
}
private void validationLog(int probeType, Object url, String msg) {
@@ -489,8 +484,7 @@
}
private boolean isValidationRequired() {
- return NetworkMonitorUtils.isValidationRequired(
- mDefaultRequest.networkCapabilities, mNetworkCapabilities);
+ return NetworkMonitorUtils.isValidationRequired(mNetworkCapabilities);
}
@@ -795,7 +789,7 @@
CustomIntentReceiver(String action, int token, int what) {
mToken = token;
mWhat = what;
- mAction = action + "_" + mNetId + "_" + token;
+ mAction = action + "_" + mNetwork.getNetworkHandle() + "_" + token;
mContext.registerReceiver(this, new IntentFilter(mAction));
}
public PendingIntent getPendingIntent() {
@@ -1088,11 +1082,6 @@
return mDependencies.getSetting(mContext, Settings.Global.CAPTIVE_PORTAL_USE_HTTPS, 1) == 1;
}
- private boolean getWifiScansAlwaysAvailableDisabled() {
- return mDependencies.getSetting(
- mContext, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 0;
- }
-
private String getCaptivePortalServerHttpsUrl() {
return mDependencies.getSetting(mContext,
Settings.Global.CAPTIVE_PORTAL_HTTPS_URL, DEFAULT_HTTPS_URL);
@@ -1484,7 +1473,7 @@
*/
private void sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal,
long requestTimestampMs, long responseTimestampMs) {
- if (getWifiScansAlwaysAvailableDisabled()) {
+ if (!mWifiManager.isScanAlwaysAvailable()) {
return;
}
@@ -1568,7 +1557,7 @@
private void logNetworkEvent(int evtype) {
int[] transports = mNetworkCapabilities.getTransportTypes();
- mMetricsLog.log(mNetId, transports, new NetworkEvent(evtype));
+ mMetricsLog.log(mNetwork, transports, new NetworkEvent(evtype));
}
private int networkEventType(ValidationStage s, EvaluationResult r) {
@@ -1590,7 +1579,8 @@
private void maybeLogEvaluationResult(int evtype) {
if (mEvaluationTimer.isRunning()) {
int[] transports = mNetworkCapabilities.getTransportTypes();
- mMetricsLog.log(mNetId, transports, new NetworkEvent(evtype, mEvaluationTimer.stop()));
+ mMetricsLog.log(mNetwork, transports,
+ new NetworkEvent(evtype, mEvaluationTimer.stop()));
mEvaluationTimer.reset();
}
}
@@ -1598,11 +1588,12 @@
private void logValidationProbe(long durationMs, int probeType, int probeResult) {
int[] transports = mNetworkCapabilities.getTransportTypes();
boolean isFirstValidation = validationStage().mIsFirstValidation;
- ValidationProbeEvent ev = new ValidationProbeEvent();
- ev.probeType = ValidationProbeEvent.makeProbeType(probeType, isFirstValidation);
- ev.returnCode = probeResult;
- ev.durationMs = durationMs;
- mMetricsLog.log(mNetId, transports, ev);
+ ValidationProbeEvent ev = new ValidationProbeEvent.Builder()
+ .setProbeType(probeType, isFirstValidation)
+ .setReturnCode(probeResult)
+ .setDurationMs(durationMs)
+ .build();
+ mMetricsLog.log(mNetwork, transports, ev);
}
@VisibleForTesting
@@ -1742,7 +1733,7 @@
boolean result = false;
// Reevaluation will generate traffic. Thus, set a minimal reevaluation timer to limit the
// possible traffic cost in metered network.
- if (mNetworkCapabilities.isMetered()
+ if (!mNetworkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED)
&& (SystemClock.elapsedRealtime() - getLastProbeTime()
< mDataStallMinEvaluateTime)) {
return false;
diff --git a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java
index bb5900c..eedaf30 100644
--- a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java
+++ b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java
@@ -32,12 +32,103 @@
public static final int IPV4_MAX_MTU = 65_535;
/**
+ * Ethernet constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc894
+ * - https://tools.ietf.org/html/rfc2464
+ * - https://tools.ietf.org/html/rfc7042
+ * - http://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml
+ * - http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml
+ */
+ public static final int ETHER_DST_ADDR_OFFSET = 0;
+ public static final int ETHER_SRC_ADDR_OFFSET = 6;
+ public static final int ETHER_ADDR_LEN = 6;
+ public static final int ETHER_TYPE_OFFSET = 12;
+ public static final int ETHER_TYPE_LENGTH = 2;
+ public static final int ETHER_TYPE_ARP = 0x0806;
+ public static final int ETHER_TYPE_IPV4 = 0x0800;
+ public static final int ETHER_TYPE_IPV6 = 0x86dd;
+ public static final int ETHER_HEADER_LEN = 14;
+
+ /**
+ * ARP constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc826
+ * - http://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml
+ */
+ public static final int ARP_PAYLOAD_LEN = 28; // For Ethernet+IPv4.
+ public static final int ARP_REQUEST = 1;
+ public static final int ARP_REPLY = 2;
+ public static final int ARP_HWTYPE_RESERVED_LO = 0;
+ public static final int ARP_HWTYPE_ETHER = 1;
+ public static final int ARP_HWTYPE_RESERVED_HI = 0xffff;
+
+ /**
+ * IPv4 constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc791
+ */
+ public static final int IPV4_HEADER_MIN_LEN = 20;
+ public static final int IPV4_IHL_MASK = 0xf;
+ public static final int IPV4_FLAGS_OFFSET = 6;
+ public static final int IPV4_FRAGMENT_MASK = 0x1fff;
+ public static final int IPV4_PROTOCOL_OFFSET = 9;
+ public static final int IPV4_SRC_ADDR_OFFSET = 12;
+ public static final int IPV4_DST_ADDR_OFFSET = 16;
+ public static final int IPV4_ADDR_LEN = 4;
+
+ /**
+ * IPv6 constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc2460
+ */
+ public static final int IPV6_ADDR_LEN = 16;
+ public static final int IPV6_HEADER_LEN = 40;
+ public static final int IPV6_PROTOCOL_OFFSET = 6;
+ public static final int IPV6_SRC_ADDR_OFFSET = 8;
+ public static final int IPV6_DST_ADDR_OFFSET = 24;
+
+ /**
+ * ICMPv6 constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc4443
+ * - https://tools.ietf.org/html/rfc4861
+ */
+ public static final int ICMPV6_HEADER_MIN_LEN = 4;
+ public static final int ICMPV6_ECHO_REPLY_TYPE = 129;
+ public static final int ICMPV6_ECHO_REQUEST_TYPE = 128;
+ public static final int ICMPV6_ROUTER_SOLICITATION = 133;
+ public static final int ICMPV6_ROUTER_ADVERTISEMENT = 134;
+ public static final int ICMPV6_NEIGHBOR_SOLICITATION = 135;
+ public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136;
+ public static final int ICMPV6_ND_OPTION_MIN_LENGTH = 8;
+ public static final int ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR = 8;
+ public static final int ICMPV6_ND_OPTION_SLLA = 1;
+ public static final int ICMPV6_ND_OPTION_TLLA = 2;
+ public static final int ICMPV6_ND_OPTION_MTU = 5;
+
+ /**
+ * UDP constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc768
+ */
+ public static final int UDP_HEADER_LEN = 8;
+
+
+ /**
* DHCP constants.
*
* See also:
* - https://tools.ietf.org/html/rfc2131
*/
public static final int INFINITE_LEASE = 0xffffffff;
+ public static final int DHCP4_CLIENT_PORT = 68;
private NetworkStackConstants() {
throw new UnsupportedOperationException("This class is not to be instantiated");
diff --git a/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java b/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java
index 733f873..82bf038 100644
--- a/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java
+++ b/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java
@@ -31,8 +31,21 @@
*/
public static void checkNetworkStackCallingPermission() {
// TODO: check that the calling PID is the system server.
- if (getCallingUid() != Process.SYSTEM_UID && getCallingUid() != Process.ROOT_UID) {
- throw new SecurityException("Invalid caller: " + getCallingUid());
+ final int caller = getCallingUid();
+ if (caller != Process.SYSTEM_UID && caller != Process.BLUETOOTH_UID) {
+ throw new SecurityException("Invalid caller: " + caller);
+ }
+ }
+
+ /**
+ * Check that the caller is allowed to dump the network stack, e.g. dumpsys.
+ * @throws SecurityException The caller is not allowed to dump the network stack.
+ */
+ public static void checkDumpPermission() {
+ final int caller = getCallingUid();
+ if (caller != Process.SYSTEM_UID && caller != Process.ROOT_UID
+ && caller != Process.SHELL_UID) {
+ throw new SecurityException("No dump permissions for caller: " + caller);
}
}
diff --git a/packages/NetworkStack/tests/Android.bp b/packages/NetworkStack/tests/Android.bp
index bd7ff2a..45fa2dc 100644
--- a/packages/NetworkStack/tests/Android.bp
+++ b/packages/NetworkStack/tests/Android.bp
@@ -16,9 +16,12 @@
android_test {
name: "NetworkStackTests",
+ certificate: "platform",
srcs: ["src/**/*.java"],
+ resource_dirs: ["res"],
static_libs: [
"android-support-test",
+ "frameworks-base-testutils",
"mockito-target-extended-minus-junit4",
"NetworkStackLib",
"testables",
@@ -26,10 +29,70 @@
libs: [
"android.test.runner",
"android.test.base",
+ "android.test.mock",
],
jni_libs: [
// For mockito extended
"libdexmakerjvmtiagent",
"libstaticjvmtiagent",
- ]
-}
\ No newline at end of file
+ // For ApfTest
+ "libartbase",
+ "libbacktrace",
+ "libbase",
+ "libbinder",
+ "libbinderthreadstate",
+ "libc++",
+ "libcrypto",
+ "libcutils",
+ "libdexfile",
+ "libhidl-gen-utils",
+ "libhidlbase",
+ "libhidltransport",
+ "libhwbinder",
+ "liblog",
+ "liblzma",
+ "libnativehelper",
+ "libnetworkstacktestsjni",
+ "libpackagelistparser",
+ "libpcre2",
+ "libprocessgroup",
+ "libselinux",
+ "libui",
+ "libutils",
+ "libvintf",
+ "libvndksupport",
+ "libtinyxml2",
+ "libunwindstack",
+ "libutilscallstack",
+ "libziparchive",
+ "libz",
+ "netd_aidl_interface-cpp",
+ ],
+}
+
+cc_library_shared {
+ name: "libnetworkstacktestsjni",
+ srcs: [
+ "jni/**/*.cpp"
+ ],
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ ],
+ include_dirs: [
+ "hardware/google/apf",
+ ],
+ shared_libs: [
+ "libbinder",
+ "liblog",
+ "libcutils",
+ "libnativehelper",
+ "netd_aidl_interface-cpp",
+ ],
+ static_libs: [
+ "libapf",
+ "libpcap",
+ ],
+
+}
diff --git a/packages/NetworkStack/tests/AndroidManifest.xml b/packages/NetworkStack/tests/AndroidManifest.xml
index 8b8474f..9cb2c21 100644
--- a/packages/NetworkStack/tests/AndroidManifest.xml
+++ b/packages/NetworkStack/tests/AndroidManifest.xml
@@ -15,6 +15,35 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.server.networkstack.tests">
+
+ <uses-permission android:name="android.permission.READ_LOGS" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.BROADCAST_STICKY" />
+ <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
+ <uses-permission android:name="android.permission.MANAGE_APP_TOKENS" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+ <uses-permission android:name="android.permission.REAL_GET_TASKS" />
+ <uses-permission android:name="android.permission.GET_DETAILED_TASKS" />
+ <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
+ <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
+ <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+ <uses-permission android:name="android.permission.MANAGE_USERS" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" />
+ <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD" />
+ <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" />
+ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
+ <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
+ <uses-permission android:name="android.permission.NETWORK_STACK" />
+
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
</application>
diff --git a/tests/net/jni/apf_jni.cpp b/packages/NetworkStack/tests/jni/apf_jni.cpp
similarity index 100%
rename from tests/net/jni/apf_jni.cpp
rename to packages/NetworkStack/tests/jni/apf_jni.cpp
diff --git a/tests/net/res/raw/apf.pcap b/packages/NetworkStack/tests/res/raw/apf.pcap
similarity index 100%
rename from tests/net/res/raw/apf.pcap
rename to packages/NetworkStack/tests/res/raw/apf.pcap
Binary files differ
diff --git a/tests/net/res/raw/apfPcap.pcap b/packages/NetworkStack/tests/res/raw/apfPcap.pcap
similarity index 100%
rename from tests/net/res/raw/apfPcap.pcap
rename to packages/NetworkStack/tests/res/raw/apfPcap.pcap
Binary files differ
diff --git a/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java
new file mode 100644
index 0000000..f76e412
--- /dev/null
+++ b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java
@@ -0,0 +1,1802 @@
+/*
+ * Copyright (C) 2012 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.apf;
+
+import static android.system.OsConstants.AF_UNIX;
+import static android.system.OsConstants.ARPHRD_ETHER;
+import static android.system.OsConstants.ETH_P_ARP;
+import static android.system.OsConstants.ETH_P_IP;
+import static android.system.OsConstants.ETH_P_IPV6;
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_STREAM;
+
+import static com.android.internal.util.BitUtils.bytesToBEInt;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.apf.ApfFilter.ApfConfiguration;
+import android.net.apf.ApfGenerator.IllegalInstructionException;
+import android.net.apf.ApfGenerator.Register;
+import android.net.ip.IIpClientCallbacks;
+import android.net.ip.IpClient;
+import android.net.ip.IpClient.IpClientCallbacksWrapper;
+import android.net.ip.IpClientCallbacks;
+import android.net.metrics.IpConnectivityLog;
+import android.net.metrics.RaEvent;
+import android.net.util.InterfaceParams;
+import android.net.util.SharedLog;
+import android.os.ConditionVariable;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.text.format.DateUtils;
+import android.util.Log;
+
+import com.android.internal.util.HexDump;
+import com.android.server.networkstack.tests.R;
+import com.android.server.util.NetworkStackConstants;
+
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+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.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Tests for APF program generator and interpreter.
+ *
+ * Build, install and run with:
+ * runtest frameworks-net -c android.net.apf.ApfTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ApfTest {
+ private static final int TIMEOUT_MS = 500;
+ private static final int MIN_APF_VERSION = 2;
+
+ @Mock IpConnectivityLog mLog;
+ @Mock Context mContext;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ // Load up native shared library containing APF interpreter exposed via JNI.
+ System.loadLibrary("networkstacktestsjni");
+ }
+
+ private static final String TAG = "ApfTest";
+ // Expected return codes from APF interpreter.
+ private static final int PASS = 1;
+ private static final int DROP = 0;
+ // Interpreter will just accept packets without link layer headers, so pad fake packet to at
+ // least the minimum packet size.
+ private static final int MIN_PKT_SIZE = 15;
+
+ private static final ApfCapabilities MOCK_APF_CAPABILITIES =
+ new ApfCapabilities(2, 1700, ARPHRD_ETHER);
+
+ private static final boolean DROP_MULTICAST = true;
+ private static final boolean ALLOW_MULTICAST = false;
+
+ private static final boolean DROP_802_3_FRAMES = true;
+ private static final boolean ALLOW_802_3_FRAMES = false;
+
+ // Constants for opcode encoding
+ private static final byte LI_OP = (byte)(13 << 3);
+ private static final byte LDDW_OP = (byte)(22 << 3);
+ private static final byte STDW_OP = (byte)(23 << 3);
+ private static final byte SIZE0 = (byte)(0 << 1);
+ private static final byte SIZE8 = (byte)(1 << 1);
+ private static final byte SIZE16 = (byte)(2 << 1);
+ private static final byte SIZE32 = (byte)(3 << 1);
+ private static final byte R1 = 1;
+
+ private static ApfConfiguration getDefaultConfig() {
+ ApfFilter.ApfConfiguration config = new ApfConfiguration();
+ config.apfCapabilities = MOCK_APF_CAPABILITIES;
+ config.multicastFilter = ALLOW_MULTICAST;
+ config.ieee802_3Filter = ALLOW_802_3_FRAMES;
+ config.ethTypeBlackList = new int[0];
+ return config;
+ }
+
+ private static String label(int code) {
+ switch (code) {
+ case PASS: return "PASS";
+ case DROP: return "DROP";
+ default: return "UNKNOWN";
+ }
+ }
+
+ private static void assertReturnCodesEqual(int expected, int got) {
+ assertEquals(label(expected), label(got));
+ }
+
+ private void assertVerdict(int expected, byte[] program, byte[] packet, int filterAge) {
+ assertReturnCodesEqual(expected, apfSimulate(program, packet, null, filterAge));
+ }
+
+ private void assertVerdict(int expected, byte[] program, byte[] packet) {
+ assertReturnCodesEqual(expected, apfSimulate(program, packet, null, 0));
+ }
+
+ private void assertPass(byte[] program, byte[] packet, int filterAge) {
+ assertVerdict(PASS, program, packet, filterAge);
+ }
+
+ private void assertPass(byte[] program, byte[] packet) {
+ assertVerdict(PASS, program, packet);
+ }
+
+ private void assertDrop(byte[] program, byte[] packet, int filterAge) {
+ assertVerdict(DROP, program, packet, filterAge);
+ }
+
+ private void assertDrop(byte[] program, byte[] packet) {
+ assertVerdict(DROP, program, packet);
+ }
+
+ private void assertProgramEquals(byte[] expected, byte[] program) throws AssertionError {
+ // assertArrayEquals() would only print one byte, making debugging difficult.
+ if (!java.util.Arrays.equals(expected, program)) {
+ throw new AssertionError(
+ "\nexpected: " + HexDump.toHexString(expected) +
+ "\nactual: " + HexDump.toHexString(program));
+ }
+ }
+
+ private void assertDataMemoryContents(
+ int expected, byte[] program, byte[] packet, byte[] data, byte[] expected_data)
+ throws IllegalInstructionException, Exception {
+ assertReturnCodesEqual(expected, apfSimulate(program, packet, data, 0 /* filterAge */));
+
+ // assertArrayEquals() would only print one byte, making debugging difficult.
+ if (!java.util.Arrays.equals(expected_data, data)) {
+ throw new Exception(
+ "\nprogram: " + HexDump.toHexString(program) +
+ "\ndata memory: " + HexDump.toHexString(data) +
+ "\nexpected: " + HexDump.toHexString(expected_data));
+ }
+ }
+
+ private void assertVerdict(int expected, ApfGenerator gen, byte[] packet, int filterAge)
+ throws IllegalInstructionException {
+ assertReturnCodesEqual(expected, apfSimulate(gen.generate(), packet, null,
+ filterAge));
+ }
+
+ private void assertPass(ApfGenerator gen, byte[] packet, int filterAge)
+ throws IllegalInstructionException {
+ assertVerdict(PASS, gen, packet, filterAge);
+ }
+
+ private void assertDrop(ApfGenerator gen, byte[] packet, int filterAge)
+ throws IllegalInstructionException {
+ assertVerdict(DROP, gen, packet, filterAge);
+ }
+
+ private void assertPass(ApfGenerator gen)
+ throws IllegalInstructionException {
+ assertVerdict(PASS, gen, new byte[MIN_PKT_SIZE], 0);
+ }
+
+ private void assertDrop(ApfGenerator gen)
+ throws IllegalInstructionException {
+ assertVerdict(DROP, gen, new byte[MIN_PKT_SIZE], 0);
+ }
+
+ /**
+ * Test each instruction by generating a program containing the instruction,
+ * generating bytecode for that program and running it through the
+ * interpreter to verify it functions correctly.
+ */
+ @Test
+ public void testApfInstructions() throws IllegalInstructionException {
+ // Empty program should pass because having the program counter reach the
+ // location immediately after the program indicates the packet should be
+ // passed to the AP.
+ ApfGenerator gen = new ApfGenerator(MIN_APF_VERSION);
+ assertPass(gen);
+
+ // Test jumping to pass label.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJump(gen.PASS_LABEL);
+ byte[] program = gen.generate();
+ assertEquals(1, program.length);
+ assertEquals((14 << 3) | (0 << 1) | 0, program[0]);
+ assertPass(program, new byte[MIN_PKT_SIZE], 0);
+
+ // Test jumping to drop label.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJump(gen.DROP_LABEL);
+ program = gen.generate();
+ assertEquals(2, program.length);
+ assertEquals((14 << 3) | (1 << 1) | 0, program[0]);
+ assertEquals(1, program[1]);
+ assertDrop(program, new byte[15], 15);
+
+ // Test jumping if equal to 0.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if not equal to 0.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if registers equal.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJumpIfR0EqualsR1(gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if registers not equal.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test load immediate.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test add.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addAdd(1234567890);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test subtract.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addAdd(-1234567890);
+ gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test or.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addOr(1234567890);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test and.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addAnd(123456789);
+ gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test left shift.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addLeftShift(1);
+ gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test right shift.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addRightShift(1);
+ gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test multiply.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 123456789);
+ gen.addMul(2);
+ gen.addJumpIfR0Equals(123456789 * 2, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test divide.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addDiv(2);
+ gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test divide by zero.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addDiv(0);
+ gen.addJump(gen.DROP_LABEL);
+ assertPass(gen);
+
+ // Test add.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 1234567890);
+ gen.addAddR1();
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test subtract.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, -1234567890);
+ gen.addAddR1();
+ gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test or.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 1234567890);
+ gen.addOrR1();
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test and.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addLoadImmediate(Register.R1, 123456789);
+ gen.addAndR1();
+ gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test left shift.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addLoadImmediate(Register.R1, 1);
+ gen.addLeftShiftR1();
+ gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test right shift.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addLoadImmediate(Register.R1, -1);
+ gen.addLeftShiftR1();
+ gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test multiply.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 123456789);
+ gen.addLoadImmediate(Register.R1, 2);
+ gen.addMulR1();
+ gen.addJumpIfR0Equals(123456789 * 2, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test divide.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addLoadImmediate(Register.R1, 2);
+ gen.addDivR1();
+ gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test divide by zero.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addDivR1();
+ gen.addJump(gen.DROP_LABEL);
+ assertPass(gen);
+
+ // Test byte load.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoad8(Register.R0, 1);
+ gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test out of bounds load.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoad8(Register.R0, 16);
+ gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
+ assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test half-word load.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoad16(Register.R0, 1);
+ gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test word load.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoad32(Register.R0, 1);
+ gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test byte indexed load.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 1);
+ gen.addLoad8Indexed(Register.R0, 0);
+ gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test out of bounds indexed load.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 8);
+ gen.addLoad8Indexed(Register.R0, 8);
+ gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
+ assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test half-word indexed load.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 1);
+ gen.addLoad16Indexed(Register.R0, 0);
+ gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test word indexed load.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 1);
+ gen.addLoad32Indexed(Register.R0, 0);
+ gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test jumping if greater than.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if less than.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJumpIfR0LessThan(0, gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJumpIfR0LessThan(1, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if any bits set.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
+ assertDrop(gen);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 3);
+ gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if register greater than.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 2);
+ gen.addLoadImmediate(Register.R1, 1);
+ gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if register less than.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 1);
+ gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if any bits set in register.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 3);
+ gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 3);
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
+ assertDrop(gen);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 3);
+ gen.addLoadImmediate(Register.R0, 3);
+ gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test load from memory.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadFromMemory(Register.R0, 0);
+ gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test store to memory.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 1234567890);
+ gen.addStoreToMemory(Register.R1, 12);
+ gen.addLoadFromMemory(Register.R0, 12);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test filter age pre-filled memory.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadFromMemory(Register.R0, gen.FILTER_AGE_MEMORY_SLOT);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen, new byte[MIN_PKT_SIZE], 1234567890);
+
+ // Test packet size pre-filled memory.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadFromMemory(Register.R0, gen.PACKET_SIZE_MEMORY_SLOT);
+ gen.addJumpIfR0Equals(MIN_PKT_SIZE, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test IPv4 header size pre-filled memory.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
+ gen.addJumpIfR0Equals(20, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x45}, 0);
+
+ // Test not.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addNot(Register.R0);
+ gen.addJumpIfR0Equals(~1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test negate.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addNeg(Register.R0);
+ gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test move.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 1234567890);
+ gen.addMove(Register.R0);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addMove(Register.R1);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test swap.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R1, 1234567890);
+ gen.addSwap();
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addSwap();
+ gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jump if bytes not equal.
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
+ program = gen.generate();
+ assertEquals(6, program.length);
+ assertEquals((13 << 3) | (1 << 1) | 0, program[0]);
+ assertEquals(1, program[1]);
+ assertEquals(((20 << 3) | (1 << 1) | 0) - 256, program[2]);
+ assertEquals(1, program[3]);
+ assertEquals(1, program[4]);
+ assertEquals(123, program[5]);
+ assertDrop(program, new byte[MIN_PKT_SIZE], 0);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
+ byte[] packet123 = {0,123,0,0,0,0,0,0,0,0,0,0,0,0,0};
+ assertPass(gen, packet123, 0);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
+ assertDrop(gen, packet123, 0);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,30,4,5}, gen.DROP_LABEL);
+ byte[] packet12345 = {0,1,2,3,4,5,0,0,0,0,0,0,0,0,0};
+ assertDrop(gen, packet12345, 0);
+ gen = new ApfGenerator(MIN_APF_VERSION);
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,3,4,5}, gen.DROP_LABEL);
+ assertPass(gen, packet12345, 0);
+ }
+
+ @Test(expected = ApfGenerator.IllegalInstructionException.class)
+ public void testApfGeneratorWantsV2OrGreater() throws Exception {
+ // The minimum supported APF version is 2.
+ new ApfGenerator(1);
+ }
+
+ @Test
+ public void testApfDataOpcodesWantApfV3() throws IllegalInstructionException, Exception {
+ ApfGenerator gen = new ApfGenerator(MIN_APF_VERSION);
+ try {
+ gen.addStoreData(Register.R0, 0);
+ fail();
+ } catch (IllegalInstructionException expected) {
+ /* pass */
+ }
+ try {
+ gen.addLoadData(Register.R0, 0);
+ fail();
+ } catch (IllegalInstructionException expected) {
+ /* pass */
+ }
+ }
+
+ /**
+ * Test that the generator emits immediates using the shortest possible encoding.
+ */
+ @Test
+ public void testImmediateEncoding() throws IllegalInstructionException {
+ ApfGenerator gen;
+
+ // 0-byte immediate: li R0, 0
+ gen = new ApfGenerator(4);
+ gen.addLoadImmediate(Register.R0, 0);
+ assertProgramEquals(new byte[]{LI_OP | SIZE0}, gen.generate());
+
+ // 1-byte immediate: li R0, 42
+ gen = new ApfGenerator(4);
+ gen.addLoadImmediate(Register.R0, 42);
+ assertProgramEquals(new byte[]{LI_OP | SIZE8, 42}, gen.generate());
+
+ // 2-byte immediate: li R1, 0x1234
+ gen = new ApfGenerator(4);
+ gen.addLoadImmediate(Register.R1, 0x1234);
+ assertProgramEquals(new byte[]{LI_OP | SIZE16 | R1, 0x12, 0x34}, gen.generate());
+
+ // 4-byte immediate: li R0, 0x12345678
+ gen = new ApfGenerator(3);
+ gen.addLoadImmediate(Register.R0, 0x12345678);
+ assertProgramEquals(
+ new byte[]{LI_OP | SIZE32, 0x12, 0x34, 0x56, 0x78},
+ gen.generate());
+ }
+
+ /**
+ * Test that the generator emits negative immediates using the shortest possible encoding.
+ */
+ @Test
+ public void testNegativeImmediateEncoding() throws IllegalInstructionException {
+ ApfGenerator gen;
+
+ // 1-byte negative immediate: li R0, -42
+ gen = new ApfGenerator(3);
+ gen.addLoadImmediate(Register.R0, -42);
+ assertProgramEquals(new byte[]{LI_OP | SIZE8, -42}, gen.generate());
+
+ // 2-byte negative immediate: li R1, -0x1122
+ gen = new ApfGenerator(3);
+ gen.addLoadImmediate(Register.R1, -0x1122);
+ assertProgramEquals(new byte[]{LI_OP | SIZE16 | R1, (byte)0xEE, (byte)0xDE},
+ gen.generate());
+
+ // 4-byte negative immediate: li R0, -0x11223344
+ gen = new ApfGenerator(3);
+ gen.addLoadImmediate(Register.R0, -0x11223344);
+ assertProgramEquals(
+ new byte[]{LI_OP | SIZE32, (byte)0xEE, (byte)0xDD, (byte)0xCC, (byte)0xBC},
+ gen.generate());
+ }
+
+ /**
+ * Test that the generator correctly emits positive and negative immediates for LDDW/STDW.
+ */
+ @Test
+ public void testLoadStoreDataEncoding() throws IllegalInstructionException {
+ ApfGenerator gen;
+
+ // Load data with no offset: lddw R0, [0 + r1]
+ gen = new ApfGenerator(3);
+ gen.addLoadData(Register.R0, 0);
+ assertProgramEquals(new byte[]{LDDW_OP | SIZE0}, gen.generate());
+
+ // Store data with 8bit negative offset: lddw r0, [-42 + r1]
+ gen = new ApfGenerator(3);
+ gen.addStoreData(Register.R0, -42);
+ assertProgramEquals(new byte[]{STDW_OP | SIZE8, -42}, gen.generate());
+
+ // Store data to R1 with 16bit negative offset: stdw r1, [-0x1122 + r0]
+ gen = new ApfGenerator(3);
+ gen.addStoreData(Register.R1, -0x1122);
+ assertProgramEquals(new byte[]{STDW_OP | SIZE16 | R1, (byte)0xEE, (byte)0xDE},
+ gen.generate());
+
+ // Load data to R1 with 32bit negative offset: lddw r1, [0xDEADBEEF + r0]
+ gen = new ApfGenerator(3);
+ gen.addLoadData(Register.R1, 0xDEADBEEF);
+ assertProgramEquals(
+ new byte[]{LDDW_OP | SIZE32 | R1, (byte)0xDE, (byte)0xAD, (byte)0xBE, (byte)0xEF},
+ gen.generate());
+ }
+
+ /**
+ * Test that the interpreter correctly executes STDW with a negative 8bit offset
+ */
+ @Test
+ public void testApfDataWrite() throws IllegalInstructionException, Exception {
+ byte[] packet = new byte[MIN_PKT_SIZE];
+ byte[] data = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
+ byte[] expected_data = data.clone();
+
+ // No memory access instructions: should leave the data segment untouched.
+ ApfGenerator gen = new ApfGenerator(3);
+ assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
+
+ // Expect value 0x87654321 to be stored starting from address -11 from the end of the
+ // data buffer, in big-endian order.
+ gen = new ApfGenerator(3);
+ gen.addLoadImmediate(Register.R0, 0x87654321);
+ gen.addLoadImmediate(Register.R1, -5);
+ gen.addStoreData(Register.R0, -6); // -5 + -6 = -11 (offset +5 with data_len=16)
+ expected_data[5] = (byte)0x87;
+ expected_data[6] = (byte)0x65;
+ expected_data[7] = (byte)0x43;
+ expected_data[8] = (byte)0x21;
+ assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
+ }
+
+ /**
+ * Test that the interpreter correctly executes LDDW with a negative 16bit offset
+ */
+ @Test
+ public void testApfDataRead() throws IllegalInstructionException, Exception {
+ // Program that DROPs if address 10 (-6) contains 0x87654321.
+ ApfGenerator gen = new ApfGenerator(3);
+ gen.addLoadImmediate(Register.R1, 1000);
+ gen.addLoadData(Register.R0, -1006); // 1000 + -1006 = -6 (offset +10 with data_len=16)
+ gen.addJumpIfR0Equals(0x87654321, gen.DROP_LABEL);
+ byte[] program = gen.generate();
+ byte[] packet = new byte[MIN_PKT_SIZE];
+
+ // Content is incorrect (last byte does not match) -> PASS
+ byte[] data = new byte[16];
+ data[10] = (byte)0x87;
+ data[11] = (byte)0x65;
+ data[12] = (byte)0x43;
+ data[13] = (byte)0x00; // != 0x21
+ byte[] expected_data = data.clone();
+ assertDataMemoryContents(PASS, program, packet, data, expected_data);
+
+ // Fix the last byte -> conditional jump taken -> DROP
+ data[13] = (byte)0x21;
+ expected_data = data;
+ assertDataMemoryContents(DROP, program, packet, data, expected_data);
+ }
+
+ /**
+ * Test that the interpreter correctly executes LDDW followed by a STDW.
+ * To cover a few more edge cases, LDDW has a 0bit offset, while STDW has a positive 8bit
+ * offset.
+ */
+ @Test
+ public void testApfDataReadModifyWrite() throws IllegalInstructionException, Exception {
+ ApfGenerator gen = new ApfGenerator(3);
+ gen.addLoadImmediate(Register.R1, -22);
+ gen.addLoadData(Register.R0, 0); // Load from address 32 -22 + 0 = 10
+ gen.addAdd(0x78453412); // 87654321 + 78453412 = FFAA7733
+ gen.addStoreData(Register.R0, 4); // Write back to address 32 -22 + 4 = 14
+
+ byte[] packet = new byte[MIN_PKT_SIZE];
+ byte[] data = new byte[32];
+ data[10] = (byte)0x87;
+ data[11] = (byte)0x65;
+ data[12] = (byte)0x43;
+ data[13] = (byte)0x21;
+ byte[] expected_data = data.clone();
+ expected_data[14] = (byte)0xFF;
+ expected_data[15] = (byte)0xAA;
+ expected_data[16] = (byte)0x77;
+ expected_data[17] = (byte)0x33;
+ assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
+ }
+
+ @Test
+ public void testApfDataBoundChecking() throws IllegalInstructionException, Exception {
+ byte[] packet = new byte[MIN_PKT_SIZE];
+ byte[] data = new byte[32];
+ byte[] expected_data = data;
+
+ // Program that DROPs unconditionally. This is our the baseline.
+ ApfGenerator gen = new ApfGenerator(3);
+ gen.addLoadImmediate(Register.R0, 3);
+ gen.addLoadData(Register.R1, 7);
+ gen.addJump(gen.DROP_LABEL);
+ assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data);
+
+ // Same program as before, but this time we're trying to load past the end of the data.
+ gen = new ApfGenerator(3);
+ gen.addLoadImmediate(Register.R0, 20);
+ gen.addLoadData(Register.R1, 15); // 20 + 15 > 32
+ gen.addJump(gen.DROP_LABEL); // Not reached.
+ assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
+
+ // Subtracting an immediate should work...
+ gen = new ApfGenerator(3);
+ gen.addLoadImmediate(Register.R0, 20);
+ gen.addLoadData(Register.R1, -4);
+ gen.addJump(gen.DROP_LABEL);
+ assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data);
+
+ // ...and underflowing simply wraps around to the end of the buffer...
+ gen = new ApfGenerator(3);
+ gen.addLoadImmediate(Register.R0, 20);
+ gen.addLoadData(Register.R1, -30);
+ gen.addJump(gen.DROP_LABEL);
+ assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data);
+
+ // ...but doesn't allow accesses before the start of the buffer
+ gen = new ApfGenerator(3);
+ gen.addLoadImmediate(Register.R0, 20);
+ gen.addLoadData(Register.R1, -1000);
+ gen.addJump(gen.DROP_LABEL); // Not reached.
+ assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
+ }
+
+ /**
+ * Generate some BPF programs, translate them to APF, then run APF and BPF programs
+ * over packet traces and verify both programs filter out the same packets.
+ */
+ @Test
+ public void testApfAgainstBpf() throws Exception {
+ String[] tcpdump_filters = new String[]{ "udp", "tcp", "icmp", "icmp6", "udp port 53",
+ "arp", "dst 239.255.255.250", "arp or tcp or udp port 53", "net 192.168.1.0/24",
+ "arp or icmp6 or portrange 53-54", "portrange 53-54 or portrange 100-50000",
+ "tcp[tcpflags] & (tcp-ack|tcp-fin) != 0 and (ip[2:2] > 57 or icmp)" };
+ String pcap_filename = stageFile(R.raw.apf);
+ for (String tcpdump_filter : tcpdump_filters) {
+ byte[] apf_program = Bpf2Apf.convert(compileToBpf(tcpdump_filter));
+ assertTrue("Failed to match for filter: " + tcpdump_filter,
+ compareBpfApf(tcpdump_filter, pcap_filename, apf_program));
+ }
+ }
+
+ /**
+ * Generate APF program, run pcap file though APF filter, then check all the packets in the file
+ * should be dropped.
+ */
+ @Test
+ public void testApfFilterPcapFile() throws Exception {
+ final byte[] MOCK_PCAP_IPV4_ADDR = {(byte) 172, 16, 7, (byte) 151};
+ String pcapFilename = stageFile(R.raw.apfPcap);
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_PCAP_IPV4_ADDR), 16);
+ LinkProperties lp = new LinkProperties();
+ lp.addLinkAddress(link);
+
+ ApfConfiguration config = getDefaultConfig();
+ ApfCapabilities MOCK_APF_PCAP_CAPABILITIES = new ApfCapabilities(4, 1700, ARPHRD_ETHER);
+ config.apfCapabilities = MOCK_APF_PCAP_CAPABILITIES;
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
+ apfFilter.setLinkProperties(lp);
+ byte[] program = ipClientCallback.getApfProgram();
+ byte[] data = new byte[ApfFilter.Counter.totalSize()];
+ final boolean result;
+
+ result = dropsAllPackets(program, data, pcapFilename);
+ Log.i(TAG, "testApfFilterPcapFile(): Data counters: " + HexDump.toHexString(data, false));
+
+ assertTrue("Failed to drop all packets by filter. \nAPF counters:" +
+ HexDump.toHexString(data, false), result);
+ }
+
+ private class MockIpClientCallback extends IpClientCallbacksWrapper {
+ private final ConditionVariable mGotApfProgram = new ConditionVariable();
+ private byte[] mLastApfProgram;
+
+ MockIpClientCallback() {
+ super(mock(IIpClientCallbacks.class), mock(SharedLog.class));
+ }
+
+ @Override
+ public void installPacketFilter(byte[] filter) {
+ mLastApfProgram = filter;
+ mGotApfProgram.open();
+ }
+
+ public void resetApfProgramWait() {
+ mGotApfProgram.close();
+ }
+
+ public byte[] getApfProgram() {
+ assertTrue(mGotApfProgram.block(TIMEOUT_MS));
+ return mLastApfProgram;
+ }
+
+ public void assertNoProgramUpdate() {
+ assertFalse(mGotApfProgram.block(TIMEOUT_MS));
+ }
+ }
+
+ private static class TestApfFilter extends ApfFilter {
+ public static final byte[] MOCK_MAC_ADDR = {1,2,3,4,5,6};
+
+ private FileDescriptor mWriteSocket;
+ private final long mFixedTimeMs = SystemClock.elapsedRealtime();
+
+ public TestApfFilter(Context context, ApfConfiguration config,
+ IpClientCallbacksWrapper ipClientCallback, IpConnectivityLog log) throws Exception {
+ super(context, config, InterfaceParams.getByName("lo"), ipClientCallback, log);
+ }
+
+ // Pretend an RA packet has been received and show it to ApfFilter.
+ public void pretendPacketReceived(byte[] packet) throws IOException, ErrnoException {
+ // ApfFilter's ReceiveThread will be waiting to read this.
+ Os.write(mWriteSocket, packet, 0, packet.length);
+ }
+
+ @Override
+ protected long currentTimeSeconds() {
+ return mFixedTimeMs / DateUtils.SECOND_IN_MILLIS;
+ }
+
+ @Override
+ void maybeStartFilter() {
+ mHardwareAddress = MOCK_MAC_ADDR;
+ installNewProgramLocked();
+
+ // Create two sockets, "readSocket" and "mWriteSocket" and connect them together.
+ FileDescriptor readSocket = new FileDescriptor();
+ mWriteSocket = new FileDescriptor();
+ try {
+ Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mWriteSocket, readSocket);
+ } catch (ErrnoException e) {
+ fail();
+ return;
+ }
+ // Now pass readSocket to ReceiveThread as if it was setup to read raw RAs.
+ // This allows us to pretend RA packets have been recieved via pretendPacketReceived().
+ mReceiveThread = new ReceiveThread(readSocket);
+ mReceiveThread.start();
+ }
+
+ @Override
+ public void shutdown() {
+ super.shutdown();
+ IoUtils.closeQuietly(mWriteSocket);
+ }
+ }
+
+ private static final int ETH_HEADER_LEN = 14;
+ private static final int ETH_DEST_ADDR_OFFSET = 0;
+ private static final int ETH_ETHERTYPE_OFFSET = 12;
+ private static final byte[] ETH_BROADCAST_MAC_ADDRESS =
+ {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
+
+ private static final int IPV4_VERSION_IHL_OFFSET = ETH_HEADER_LEN + 0;
+ private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9;
+ private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16;
+ private static final byte[] IPV4_BROADCAST_ADDRESS =
+ {(byte) 255, (byte) 255, (byte) 255, (byte) 255};
+
+ private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6;
+ private static final int IPV6_HEADER_LEN = 40;
+ private static final int IPV6_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 24;
+ // The IPv6 all nodes address ff02::1
+ private static final byte[] IPV6_ALL_NODES_ADDRESS =
+ { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
+ private static final byte[] IPV6_ALL_ROUTERS_ADDRESS =
+ { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 };
+
+ private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN;
+ private static final int ICMP6_ROUTER_SOLICITATION = 133;
+ private static final int ICMP6_ROUTER_ADVERTISEMENT = 134;
+ private static final int ICMP6_NEIGHBOR_SOLICITATION = 135;
+ private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136;
+
+ private static final int ICMP6_RA_HEADER_LEN = 16;
+ private static final int ICMP6_RA_ROUTER_LIFETIME_OFFSET =
+ ETH_HEADER_LEN + IPV6_HEADER_LEN + 6;
+ private static final int ICMP6_RA_CHECKSUM_OFFSET =
+ ETH_HEADER_LEN + IPV6_HEADER_LEN + 2;
+ private static final int ICMP6_RA_OPTION_OFFSET =
+ ETH_HEADER_LEN + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN;
+
+ private static final int ICMP6_PREFIX_OPTION_TYPE = 3;
+ private static final int ICMP6_PREFIX_OPTION_LEN = 32;
+ private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET = 4;
+ private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET = 8;
+
+ // From RFC6106: Recursive DNS Server option
+ private static final int ICMP6_RDNSS_OPTION_TYPE = 25;
+ // From RFC6106: DNS Search List option
+ private static final int ICMP6_DNSSL_OPTION_TYPE = 31;
+
+ // From RFC4191: Route Information option
+ private static final int ICMP6_ROUTE_INFO_OPTION_TYPE = 24;
+ // Above three options all have the same format:
+ private static final int ICMP6_4_BYTE_OPTION_LEN = 8;
+ private static final int ICMP6_4_BYTE_LIFETIME_OFFSET = 4;
+ private static final int ICMP6_4_BYTE_LIFETIME_LEN = 4;
+
+ private static final int UDP_HEADER_LEN = 8;
+ private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 22;
+
+ private static final int DHCP_CLIENT_PORT = 68;
+ private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 48;
+
+ private static final int ARP_HEADER_OFFSET = ETH_HEADER_LEN;
+ private static final byte[] ARP_IPV4_REQUEST_HEADER = {
+ 0, 1, // Hardware type: Ethernet (1)
+ 8, 0, // Protocol type: IP (0x0800)
+ 6, // Hardware size: 6
+ 4, // Protocol size: 4
+ 0, 1 // Opcode: request (1)
+ };
+ private static final byte[] ARP_IPV4_REPLY_HEADER = {
+ 0, 1, // Hardware type: Ethernet (1)
+ 8, 0, // Protocol type: IP (0x0800)
+ 6, // Hardware size: 6
+ 4, // Protocol size: 4
+ 0, 2 // Opcode: reply (2)
+ };
+ private static final int ARP_SOURCE_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 14;
+ private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 24;
+
+ private static final byte[] MOCK_IPV4_ADDR = {10, 0, 0, 1};
+ private static final byte[] MOCK_BROADCAST_IPV4_ADDR = {10, 0, 31, (byte) 255}; // prefix = 19
+ private static final byte[] MOCK_MULTICAST_IPV4_ADDR = {(byte) 224, 0, 0, 1};
+ private static final byte[] ANOTHER_IPV4_ADDR = {10, 0, 0, 2};
+ private static final byte[] IPV4_SOURCE_ADDR = {10, 0, 0, 3};
+ private static final byte[] ANOTHER_IPV4_SOURCE_ADDR = {(byte) 192, 0, 2, 1};
+ private static final byte[] BUG_PROBE_SOURCE_ADDR1 = {0, 0, 1, 2};
+ private static final byte[] BUG_PROBE_SOURCE_ADDR2 = {3, 4, 0, 0};
+ private static final byte[] IPV4_ANY_HOST_ADDR = {0, 0, 0, 0};
+
+ // Helper to initialize a default apfFilter.
+ private ApfFilter setupApfFilter(
+ IpClientCallbacksWrapper ipClientCallback, ApfConfiguration config) throws Exception {
+ LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
+ LinkProperties lp = new LinkProperties();
+ lp.addLinkAddress(link);
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
+ apfFilter.setLinkProperties(lp);
+ return apfFilter;
+ }
+
+ @Test
+ public void testApfFilterIPv4() throws Exception {
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
+ LinkProperties lp = new LinkProperties();
+ lp.addLinkAddress(link);
+
+ ApfConfiguration config = getDefaultConfig();
+ config.multicastFilter = DROP_MULTICAST;
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
+ apfFilter.setLinkProperties(lp);
+
+ byte[] program = ipClientCallback.getApfProgram();
+
+ // Verify empty packet of 100 zero bytes is passed
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ assertPass(program, packet.array());
+
+ // Verify unicast IPv4 packet is passed
+ put(packet, ETH_DEST_ADDR_OFFSET, TestApfFilter.MOCK_MAC_ADDR);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_IPV4_ADDR);
+ assertPass(program, packet.array());
+
+ // Verify L2 unicast to IPv4 broadcast addresses is dropped (b/30231088)
+ put(packet, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);
+ assertDrop(program, packet.array());
+ put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_BROADCAST_IPV4_ADDR);
+ assertDrop(program, packet.array());
+
+ // Verify multicast/broadcast IPv4, not DHCP to us, is dropped
+ put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
+ assertDrop(program, packet.array());
+ packet.put(IPV4_VERSION_IHL_OFFSET, (byte)0x45);
+ assertDrop(program, packet.array());
+ packet.put(IPV4_PROTOCOL_OFFSET, (byte)IPPROTO_UDP);
+ assertDrop(program, packet.array());
+ packet.putShort(UDP_DESTINATION_PORT_OFFSET, (short)DHCP_CLIENT_PORT);
+ assertDrop(program, packet.array());
+ put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_MULTICAST_IPV4_ADDR);
+ assertDrop(program, packet.array());
+ put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_BROADCAST_IPV4_ADDR);
+ assertDrop(program, packet.array());
+ put(packet, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);
+ assertDrop(program, packet.array());
+
+ // Verify broadcast IPv4 DHCP to us is passed
+ put(packet, DHCP_CLIENT_MAC_OFFSET, TestApfFilter.MOCK_MAC_ADDR);
+ assertPass(program, packet.array());
+
+ // Verify unicast IPv4 DHCP to us is passed
+ put(packet, ETH_DEST_ADDR_OFFSET, TestApfFilter.MOCK_MAC_ADDR);
+ assertPass(program, packet.array());
+
+ apfFilter.shutdown();
+ }
+
+ @Test
+ public void testApfFilterIPv6() throws Exception {
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ ApfConfiguration config = getDefaultConfig();
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
+ byte[] program = ipClientCallback.getApfProgram();
+
+ // Verify empty IPv6 packet is passed
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+ assertPass(program, packet.array());
+
+ // Verify empty ICMPv6 packet is passed
+ packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
+ assertPass(program, packet.array());
+
+ // Verify empty ICMPv6 NA packet is passed
+ packet.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_NEIGHBOR_ANNOUNCEMENT);
+ assertPass(program, packet.array());
+
+ // Verify ICMPv6 NA to ff02::1 is dropped
+ put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_NODES_ADDRESS);
+ assertDrop(program, packet.array());
+
+ // Verify ICMPv6 RS to any is dropped
+ packet.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_ROUTER_SOLICITATION);
+ assertDrop(program, packet.array());
+ put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_ROUTERS_ADDRESS);
+ assertDrop(program, packet.array());
+
+ apfFilter.shutdown();
+ }
+
+ @Test
+ public void testApfFilterMulticast() throws Exception {
+ final byte[] unicastIpv4Addr = {(byte)192,0,2,63};
+ final byte[] broadcastIpv4Addr = {(byte)192,0,2,(byte)255};
+ final byte[] multicastIpv4Addr = {(byte)224,0,0,1};
+ final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb};
+
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ LinkAddress link = new LinkAddress(InetAddress.getByAddress(unicastIpv4Addr), 24);
+ LinkProperties lp = new LinkProperties();
+ lp.addLinkAddress(link);
+
+ ApfConfiguration config = getDefaultConfig();
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
+ apfFilter.setLinkProperties(lp);
+
+ byte[] program = ipClientCallback.getApfProgram();
+
+ // Construct IPv4 and IPv6 multicast packets.
+ ByteBuffer mcastv4packet = ByteBuffer.wrap(new byte[100]);
+ mcastv4packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ put(mcastv4packet, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr);
+
+ ByteBuffer mcastv6packet = ByteBuffer.wrap(new byte[100]);
+ mcastv6packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+ mcastv6packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_UDP);
+ put(mcastv6packet, IPV6_DEST_ADDR_OFFSET, multicastIpv6Addr);
+
+ // Construct IPv4 broadcast packet.
+ ByteBuffer bcastv4packet1 = ByteBuffer.wrap(new byte[100]);
+ bcastv4packet1.put(ETH_BROADCAST_MAC_ADDRESS);
+ bcastv4packet1.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ put(bcastv4packet1, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr);
+
+ ByteBuffer bcastv4packet2 = ByteBuffer.wrap(new byte[100]);
+ bcastv4packet2.put(ETH_BROADCAST_MAC_ADDRESS);
+ bcastv4packet2.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ put(bcastv4packet2, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);
+
+ // Construct IPv4 broadcast with L2 unicast address packet (b/30231088).
+ ByteBuffer bcastv4unicastl2packet = ByteBuffer.wrap(new byte[100]);
+ bcastv4unicastl2packet.put(TestApfFilter.MOCK_MAC_ADDR);
+ bcastv4unicastl2packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ put(bcastv4unicastl2packet, IPV4_DEST_ADDR_OFFSET, broadcastIpv4Addr);
+
+ // Verify initially disabled multicast filter is off
+ assertPass(program, mcastv4packet.array());
+ assertPass(program, mcastv6packet.array());
+ assertPass(program, bcastv4packet1.array());
+ assertPass(program, bcastv4packet2.array());
+ assertPass(program, bcastv4unicastl2packet.array());
+
+ // Turn on multicast filter and verify it works
+ ipClientCallback.resetApfProgramWait();
+ apfFilter.setMulticastFilter(true);
+ program = ipClientCallback.getApfProgram();
+ assertDrop(program, mcastv4packet.array());
+ assertDrop(program, mcastv6packet.array());
+ assertDrop(program, bcastv4packet1.array());
+ assertDrop(program, bcastv4packet2.array());
+ assertDrop(program, bcastv4unicastl2packet.array());
+
+ // Turn off multicast filter and verify it's off
+ ipClientCallback.resetApfProgramWait();
+ apfFilter.setMulticastFilter(false);
+ program = ipClientCallback.getApfProgram();
+ assertPass(program, mcastv4packet.array());
+ assertPass(program, mcastv6packet.array());
+ assertPass(program, bcastv4packet1.array());
+ assertPass(program, bcastv4packet2.array());
+ assertPass(program, bcastv4unicastl2packet.array());
+
+ // Verify it can be initialized to on
+ ipClientCallback.resetApfProgramWait();
+ apfFilter.shutdown();
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
+ apfFilter.setLinkProperties(lp);
+ program = ipClientCallback.getApfProgram();
+ assertDrop(program, mcastv4packet.array());
+ assertDrop(program, mcastv6packet.array());
+ assertDrop(program, bcastv4packet1.array());
+ assertDrop(program, bcastv4unicastl2packet.array());
+
+ // Verify that ICMPv6 multicast is not dropped.
+ mcastv6packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
+ assertPass(program, mcastv6packet.array());
+
+ apfFilter.shutdown();
+ }
+
+ @Test
+ public void testApfFilterMulticastPingWhileDozing() throws Exception {
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ ApfFilter apfFilter = setupApfFilter(ipClientCallback, getDefaultConfig());
+
+ // Construct a multicast ICMPv6 ECHO request.
+ final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb};
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+ packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
+ packet.put(ICMP6_TYPE_OFFSET, (byte)ICMPV6_ECHO_REQUEST_TYPE);
+ put(packet, IPV6_DEST_ADDR_OFFSET, multicastIpv6Addr);
+
+ // Normally, we let multicast pings alone...
+ assertPass(ipClientCallback.getApfProgram(), packet.array());
+
+ // ...and even while dozing...
+ apfFilter.setDozeMode(true);
+ assertPass(ipClientCallback.getApfProgram(), packet.array());
+
+ // ...but when the multicast filter is also enabled, drop the multicast pings to save power.
+ apfFilter.setMulticastFilter(true);
+ assertDrop(ipClientCallback.getApfProgram(), packet.array());
+
+ // However, we should still let through all other ICMPv6 types.
+ ByteBuffer raPacket = ByteBuffer.wrap(packet.array().clone());
+ raPacket.put(ICMP6_TYPE_OFFSET, (byte) NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT);
+ assertPass(ipClientCallback.getApfProgram(), raPacket.array());
+
+ // Now wake up from doze mode to ensure that we no longer drop the packets.
+ // (The multicast filter is still enabled at this point).
+ apfFilter.setDozeMode(false);
+ assertPass(ipClientCallback.getApfProgram(), packet.array());
+
+ apfFilter.shutdown();
+ }
+
+ @Test
+ public void testApfFilter802_3() throws Exception {
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ ApfConfiguration config = getDefaultConfig();
+ ApfFilter apfFilter = setupApfFilter(ipClientCallback, config);
+ byte[] program = ipClientCallback.getApfProgram();
+
+ // Verify empty packet of 100 zero bytes is passed
+ // Note that eth-type = 0 makes it an IEEE802.3 frame
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ assertPass(program, packet.array());
+
+ // Verify empty packet with IPv4 is passed
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ assertPass(program, packet.array());
+
+ // Verify empty IPv6 packet is passed
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+ assertPass(program, packet.array());
+
+ // Now turn on the filter
+ ipClientCallback.resetApfProgramWait();
+ apfFilter.shutdown();
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ apfFilter = setupApfFilter(ipClientCallback, config);
+ program = ipClientCallback.getApfProgram();
+
+ // Verify that IEEE802.3 frame is dropped
+ // In this case ethtype is used for payload length
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)(100 - 14));
+ assertDrop(program, packet.array());
+
+ // Verify that IPv4 (as example of Ethernet II) frame will pass
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ assertPass(program, packet.array());
+
+ // Verify that IPv6 (as example of Ethernet II) frame will pass
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+ assertPass(program, packet.array());
+
+ apfFilter.shutdown();
+ }
+
+ @Test
+ public void testApfFilterEthTypeBL() throws Exception {
+ final int[] emptyBlackList = {};
+ final int[] ipv4BlackList = {ETH_P_IP};
+ final int[] ipv4Ipv6BlackList = {ETH_P_IP, ETH_P_IPV6};
+
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ ApfConfiguration config = getDefaultConfig();
+ ApfFilter apfFilter = setupApfFilter(ipClientCallback, config);
+ byte[] program = ipClientCallback.getApfProgram();
+
+ // Verify empty packet of 100 zero bytes is passed
+ // Note that eth-type = 0 makes it an IEEE802.3 frame
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ assertPass(program, packet.array());
+
+ // Verify empty packet with IPv4 is passed
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ assertPass(program, packet.array());
+
+ // Verify empty IPv6 packet is passed
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+ assertPass(program, packet.array());
+
+ // Now add IPv4 to the black list
+ ipClientCallback.resetApfProgramWait();
+ apfFilter.shutdown();
+ config.ethTypeBlackList = ipv4BlackList;
+ apfFilter = setupApfFilter(ipClientCallback, config);
+ program = ipClientCallback.getApfProgram();
+
+ // Verify that IPv4 frame will be dropped
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ assertDrop(program, packet.array());
+
+ // Verify that IPv6 frame will pass
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+ assertPass(program, packet.array());
+
+ // Now let us have both IPv4 and IPv6 in the black list
+ ipClientCallback.resetApfProgramWait();
+ apfFilter.shutdown();
+ config.ethTypeBlackList = ipv4Ipv6BlackList;
+ apfFilter = setupApfFilter(ipClientCallback, config);
+ program = ipClientCallback.getApfProgram();
+
+ // Verify that IPv4 frame will be dropped
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ assertDrop(program, packet.array());
+
+ // Verify that IPv6 frame will be dropped
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+ assertDrop(program, packet.array());
+
+ apfFilter.shutdown();
+ }
+
+ private byte[] getProgram(MockIpClientCallback cb, ApfFilter filter, LinkProperties lp) {
+ cb.resetApfProgramWait();
+ filter.setLinkProperties(lp);
+ return cb.getApfProgram();
+ }
+
+ private void verifyArpFilter(byte[] program, int filterResult) {
+ // Verify ARP request packet
+ assertPass(program, arpRequestBroadcast(MOCK_IPV4_ADDR));
+ assertVerdict(filterResult, program, arpRequestBroadcast(ANOTHER_IPV4_ADDR));
+ assertDrop(program, arpRequestBroadcast(IPV4_ANY_HOST_ADDR));
+
+ // Verify ARP reply packets from different source ip
+ assertDrop(program, arpReply(IPV4_ANY_HOST_ADDR, IPV4_ANY_HOST_ADDR));
+ assertPass(program, arpReply(ANOTHER_IPV4_SOURCE_ADDR, IPV4_ANY_HOST_ADDR));
+ assertPass(program, arpReply(BUG_PROBE_SOURCE_ADDR1, IPV4_ANY_HOST_ADDR));
+ assertPass(program, arpReply(BUG_PROBE_SOURCE_ADDR2, IPV4_ANY_HOST_ADDR));
+
+ // Verify unicast ARP reply packet is always accepted.
+ assertPass(program, arpReply(IPV4_SOURCE_ADDR, MOCK_IPV4_ADDR));
+ assertPass(program, arpReply(IPV4_SOURCE_ADDR, ANOTHER_IPV4_ADDR));
+ assertPass(program, arpReply(IPV4_SOURCE_ADDR, IPV4_ANY_HOST_ADDR));
+
+ // Verify GARP reply packets are always filtered
+ assertDrop(program, garpReply());
+ }
+
+ @Test
+ public void testApfFilterArp() throws Exception {
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ ApfConfiguration config = getDefaultConfig();
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
+
+ // Verify initially ARP request filter is off, and GARP filter is on.
+ verifyArpFilter(ipClientCallback.getApfProgram(), PASS);
+
+ // Inform ApfFilter of our address and verify ARP filtering is on
+ LinkAddress linkAddress = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 24);
+ LinkProperties lp = new LinkProperties();
+ assertTrue(lp.addLinkAddress(linkAddress));
+ verifyArpFilter(getProgram(ipClientCallback, apfFilter, lp), DROP);
+
+ // Inform ApfFilter of loss of IP and verify ARP filtering is off
+ verifyArpFilter(getProgram(ipClientCallback, apfFilter, new LinkProperties()), PASS);
+
+ apfFilter.shutdown();
+ }
+
+ private static byte[] arpReply(byte[] sip, byte[] tip) {
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
+ put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REPLY_HEADER);
+ put(packet, ARP_SOURCE_IP_ADDRESS_OFFSET, sip);
+ put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, tip);
+ return packet.array();
+ }
+
+ private static byte[] arpRequestBroadcast(byte[] tip) {
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
+ put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
+ put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REQUEST_HEADER);
+ put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, tip);
+ return packet.array();
+ }
+
+ private static byte[] garpReply() {
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
+ put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
+ put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REPLY_HEADER);
+ put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, IPV4_ANY_HOST_ADDR);
+ return packet.array();
+ }
+
+ // Verify that the last program pushed to the IpClient.Callback properly filters the
+ // given packet for the given lifetime.
+ private void verifyRaLifetime(byte[] program, ByteBuffer packet, int lifetime) {
+ final int FRACTION_OF_LIFETIME = 6;
+ final int ageLimit = lifetime / FRACTION_OF_LIFETIME;
+
+ // Verify new program should drop RA for 1/6th its lifetime and pass afterwards.
+ assertDrop(program, packet.array());
+ assertDrop(program, packet.array(), ageLimit);
+ assertPass(program, packet.array(), ageLimit + 1);
+ assertPass(program, packet.array(), lifetime);
+ // Verify RA checksum is ignored
+ final short originalChecksum = packet.getShort(ICMP6_RA_CHECKSUM_OFFSET);
+ packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)12345);
+ assertDrop(program, packet.array());
+ packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)-12345);
+ assertDrop(program, packet.array());
+ packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, originalChecksum);
+
+ // Verify other changes to RA make it not match filter
+ final byte originalFirstByte = packet.get(0);
+ packet.put(0, (byte)-1);
+ assertPass(program, packet.array());
+ packet.put(0, (byte)0);
+ assertDrop(program, packet.array());
+ packet.put(0, originalFirstByte);
+ }
+
+ // Test that when ApfFilter is shown the given packet, it generates a program to filter it
+ // for the given lifetime.
+ private void verifyRaLifetime(TestApfFilter apfFilter, MockIpClientCallback ipClientCallback,
+ ByteBuffer packet, int lifetime) throws IOException, ErrnoException {
+ // Verify new program generated if ApfFilter witnesses RA
+ ipClientCallback.resetApfProgramWait();
+ apfFilter.pretendPacketReceived(packet.array());
+ byte[] program = ipClientCallback.getApfProgram();
+ verifyRaLifetime(program, packet, lifetime);
+ }
+
+ private void verifyRaEvent(RaEvent expected) {
+ ArgumentCaptor<IpConnectivityLog.Event> captor =
+ ArgumentCaptor.forClass(IpConnectivityLog.Event.class);
+ verify(mLog, atLeastOnce()).log(captor.capture());
+ RaEvent got = lastRaEvent(captor.getAllValues());
+ if (!raEventEquals(expected, got)) {
+ assertEquals(expected, got); // fail for printing an assertion error message.
+ }
+ }
+
+ private RaEvent lastRaEvent(List<IpConnectivityLog.Event> events) {
+ RaEvent got = null;
+ for (Parcelable ev : events) {
+ if (ev instanceof RaEvent) {
+ got = (RaEvent) ev;
+ }
+ }
+ return got;
+ }
+
+ private boolean raEventEquals(RaEvent ev1, RaEvent ev2) {
+ return (ev1 != null) && (ev2 != null)
+ && (ev1.routerLifetime == ev2.routerLifetime)
+ && (ev1.prefixValidLifetime == ev2.prefixValidLifetime)
+ && (ev1.prefixPreferredLifetime == ev2.prefixPreferredLifetime)
+ && (ev1.routeInfoLifetime == ev2.routeInfoLifetime)
+ && (ev1.rdnssLifetime == ev2.rdnssLifetime)
+ && (ev1.dnsslLifetime == ev2.dnsslLifetime);
+ }
+
+ private void assertInvalidRa(TestApfFilter apfFilter, MockIpClientCallback ipClientCallback,
+ ByteBuffer packet) throws IOException, ErrnoException {
+ ipClientCallback.resetApfProgramWait();
+ apfFilter.pretendPacketReceived(packet.array());
+ ipClientCallback.assertNoProgramUpdate();
+ }
+
+ @Test
+ public void testApfFilterRa() throws Exception {
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ ApfConfiguration config = getDefaultConfig();
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
+ byte[] program = ipClientCallback.getApfProgram();
+
+ final int ROUTER_LIFETIME = 1000;
+ final int PREFIX_VALID_LIFETIME = 200;
+ final int PREFIX_PREFERRED_LIFETIME = 100;
+ final int RDNSS_LIFETIME = 300;
+ final int ROUTE_LIFETIME = 400;
+ // Note that lifetime of 2000 will be ignored in favor of shorter route lifetime of 1000.
+ final int DNSSL_LIFETIME = 2000;
+ final int VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET = ETH_HEADER_LEN;
+ // IPv6, traffic class = 0, flow label = 0x12345
+ final int VERSION_TRAFFIC_CLASS_FLOW_LABEL = 0x60012345;
+
+ // Verify RA is passed the first time
+ ByteBuffer basePacket = ByteBuffer.wrap(new byte[ICMP6_RA_OPTION_OFFSET]);
+ basePacket.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+ basePacket.putInt(VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET,
+ VERSION_TRAFFIC_CLASS_FLOW_LABEL);
+ basePacket.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
+ basePacket.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_ROUTER_ADVERTISEMENT);
+ basePacket.putShort(ICMP6_RA_ROUTER_LIFETIME_OFFSET, (short)ROUTER_LIFETIME);
+ basePacket.position(IPV6_DEST_ADDR_OFFSET);
+ basePacket.put(IPV6_ALL_NODES_ADDRESS);
+ assertPass(program, basePacket.array());
+
+ verifyRaLifetime(apfFilter, ipClientCallback, basePacket, ROUTER_LIFETIME);
+ verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, -1, -1));
+
+ ByteBuffer newFlowLabelPacket = ByteBuffer.wrap(new byte[ICMP6_RA_OPTION_OFFSET]);
+ basePacket.clear();
+ newFlowLabelPacket.put(basePacket);
+ // Check that changes are ignored in every byte of the flow label.
+ newFlowLabelPacket.putInt(VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET,
+ VERSION_TRAFFIC_CLASS_FLOW_LABEL + 0x11111);
+
+ // Ensure zero-length options cause the packet to be silently skipped.
+ // Do this before we test other packets. http://b/29586253
+ ByteBuffer zeroLengthOptionPacket = ByteBuffer.wrap(
+ new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
+ basePacket.clear();
+ zeroLengthOptionPacket.put(basePacket);
+ zeroLengthOptionPacket.put((byte)ICMP6_PREFIX_OPTION_TYPE);
+ zeroLengthOptionPacket.put((byte)0);
+ assertInvalidRa(apfFilter, ipClientCallback, zeroLengthOptionPacket);
+
+ // Generate several RAs with different options and lifetimes, and verify when
+ // ApfFilter is shown these packets, it generates programs to filter them for the
+ // appropriate lifetime.
+ ByteBuffer prefixOptionPacket = ByteBuffer.wrap(
+ new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_LEN]);
+ basePacket.clear();
+ prefixOptionPacket.put(basePacket);
+ prefixOptionPacket.put((byte)ICMP6_PREFIX_OPTION_TYPE);
+ prefixOptionPacket.put((byte)(ICMP6_PREFIX_OPTION_LEN / 8));
+ prefixOptionPacket.putInt(
+ ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET,
+ PREFIX_PREFERRED_LIFETIME);
+ prefixOptionPacket.putInt(
+ ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET,
+ PREFIX_VALID_LIFETIME);
+ verifyRaLifetime(
+ apfFilter, ipClientCallback, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME);
+ verifyRaEvent(new RaEvent(
+ ROUTER_LIFETIME, PREFIX_VALID_LIFETIME, PREFIX_PREFERRED_LIFETIME, -1, -1, -1));
+
+ ByteBuffer rdnssOptionPacket = ByteBuffer.wrap(
+ new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
+ basePacket.clear();
+ rdnssOptionPacket.put(basePacket);
+ rdnssOptionPacket.put((byte)ICMP6_RDNSS_OPTION_TYPE);
+ rdnssOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
+ rdnssOptionPacket.putInt(
+ ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, RDNSS_LIFETIME);
+ verifyRaLifetime(apfFilter, ipClientCallback, rdnssOptionPacket, RDNSS_LIFETIME);
+ verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, RDNSS_LIFETIME, -1));
+
+ ByteBuffer routeInfoOptionPacket = ByteBuffer.wrap(
+ new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
+ basePacket.clear();
+ routeInfoOptionPacket.put(basePacket);
+ routeInfoOptionPacket.put((byte)ICMP6_ROUTE_INFO_OPTION_TYPE);
+ routeInfoOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
+ routeInfoOptionPacket.putInt(
+ ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, ROUTE_LIFETIME);
+ verifyRaLifetime(apfFilter, ipClientCallback, routeInfoOptionPacket, ROUTE_LIFETIME);
+ verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, ROUTE_LIFETIME, -1, -1));
+
+ ByteBuffer dnsslOptionPacket = ByteBuffer.wrap(
+ new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
+ basePacket.clear();
+ dnsslOptionPacket.put(basePacket);
+ dnsslOptionPacket.put((byte)ICMP6_DNSSL_OPTION_TYPE);
+ dnsslOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
+ dnsslOptionPacket.putInt(
+ ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, DNSSL_LIFETIME);
+ verifyRaLifetime(apfFilter, ipClientCallback, dnsslOptionPacket, ROUTER_LIFETIME);
+ verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, -1, DNSSL_LIFETIME));
+
+ // Verify that current program filters all five RAs:
+ program = ipClientCallback.getApfProgram();
+ verifyRaLifetime(program, basePacket, ROUTER_LIFETIME);
+ verifyRaLifetime(program, newFlowLabelPacket, ROUTER_LIFETIME);
+ verifyRaLifetime(program, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME);
+ verifyRaLifetime(program, rdnssOptionPacket, RDNSS_LIFETIME);
+ verifyRaLifetime(program, routeInfoOptionPacket, ROUTE_LIFETIME);
+ verifyRaLifetime(program, dnsslOptionPacket, ROUTER_LIFETIME);
+
+ apfFilter.shutdown();
+ }
+
+ /**
+ * Stage a file for testing, i.e. make it native accessible. Given a resource ID,
+ * copy that resource into the app's data directory and return the path to it.
+ */
+ private String stageFile(int rawId) throws Exception {
+ File file = new File(InstrumentationRegistry.getContext().getFilesDir(), "staged_file");
+ new File(file.getParent()).mkdirs();
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ in = InstrumentationRegistry.getContext().getResources().openRawResource(rawId);
+ out = new FileOutputStream(file);
+ Streams.copy(in, out);
+ } finally {
+ if (in != null) in.close();
+ if (out != null) out.close();
+ }
+ return file.getAbsolutePath();
+ }
+
+ private static void put(ByteBuffer buffer, int position, byte[] bytes) {
+ final int original = buffer.position();
+ buffer.position(position);
+ buffer.put(bytes);
+ buffer.position(original);
+ }
+
+ @Test
+ public void testRaParsing() throws Exception {
+ final int maxRandomPacketSize = 512;
+ final Random r = new Random();
+ MockIpClientCallback cb = new MockIpClientCallback();
+ ApfConfiguration config = getDefaultConfig();
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mLog);
+ for (int i = 0; i < 1000; i++) {
+ byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
+ r.nextBytes(packet);
+ try {
+ apfFilter.new Ra(packet, packet.length);
+ } catch (ApfFilter.InvalidRaException e) {
+ } catch (Exception e) {
+ throw new Exception("bad packet: " + HexDump.toHexString(packet), e);
+ }
+ }
+ }
+
+ @Test
+ public void testRaProcessing() throws Exception {
+ final int maxRandomPacketSize = 512;
+ final Random r = new Random();
+ MockIpClientCallback cb = new MockIpClientCallback();
+ ApfConfiguration config = getDefaultConfig();
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mLog);
+ for (int i = 0; i < 1000; i++) {
+ byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
+ r.nextBytes(packet);
+ try {
+ apfFilter.processRa(packet, packet.length);
+ } catch (Exception e) {
+ throw new Exception("bad packet: " + HexDump.toHexString(packet), e);
+ }
+ }
+ }
+
+ /**
+ * Call the APF interpreter to run {@code program} on {@code packet} with persistent memory
+ * segment {@data} pretending the filter was installed {@code filter_age} seconds ago.
+ */
+ private native static int apfSimulate(byte[] program, byte[] packet, byte[] data,
+ int filter_age);
+
+ /**
+ * Compile a tcpdump human-readable filter (e.g. "icmp" or "tcp port 54") into a BPF
+ * prorgam and return a human-readable dump of the BPF program identical to "tcpdump -d".
+ */
+ private native static String compileToBpf(String filter);
+
+ /**
+ * Open packet capture file {@code pcap_filename} and filter the packets using tcpdump
+ * human-readable filter (e.g. "icmp" or "tcp port 54") compiled to a BPF program and
+ * at the same time using APF program {@code apf_program}. Return {@code true} if
+ * both APF and BPF programs filter out exactly the same packets.
+ */
+ private native static boolean compareBpfApf(String filter, String pcap_filename,
+ byte[] apf_program);
+
+
+ /**
+ * Open packet capture file {@code pcapFilename} and run it through APF filter. Then
+ * checks whether all the packets are dropped and populates data[] {@code data} with
+ * the APF counters.
+ */
+ private native static boolean dropsAllPackets(byte[] program, byte[] data, String pcapFilename);
+
+ @Test
+ public void testBroadcastAddress() throws Exception {
+ assertEqualsIp("255.255.255.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 0));
+ assertEqualsIp("0.0.0.0", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 32));
+ assertEqualsIp("0.0.3.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 22));
+ assertEqualsIp("0.255.255.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 8));
+
+ assertEqualsIp("255.255.255.255", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 0));
+ assertEqualsIp("10.0.0.1", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 32));
+ assertEqualsIp("10.0.0.255", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 24));
+ assertEqualsIp("10.0.255.255", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 16));
+ }
+
+ public void assertEqualsIp(String expected, int got) throws Exception {
+ int want = bytesToBEInt(InetAddress.getByName(expected).getAddress());
+ assertEquals(want, got);
+ }
+}
diff --git a/tests/net/java/android/net/apf/Bpf2Apf.java b/packages/NetworkStack/tests/src/android/net/apf/Bpf2Apf.java
similarity index 100%
rename from tests/net/java/android/net/apf/Bpf2Apf.java
rename to packages/NetworkStack/tests/src/android/net/apf/Bpf2Apf.java
diff --git a/tests/net/java/android/net/dhcp/DhcpPacketTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java
similarity index 100%
rename from tests/net/java/android/net/dhcp/DhcpPacketTest.java
rename to packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java
diff --git a/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
new file mode 100644
index 0000000..f21809f
--- /dev/null
+++ b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
@@ -0,0 +1,533 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ip;
+
+import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlarmManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.INetd;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.MacAddress;
+import android.net.RouteInfo;
+import android.net.shared.InitialConfiguration;
+import android.net.shared.ProvisioningConfiguration;
+import android.net.util.InterfaceParams;
+import android.os.INetworkManagementService;
+import android.provider.Settings;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.mock.MockContentResolver;
+
+import com.android.internal.R;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.net.BaseNetworkObserver;
+
+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.net.InetAddress;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Tests for IpClient.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpClientTest {
+ private static final int DEFAULT_AVOIDBADWIFI_CONFIG_VALUE = 1;
+
+ private static final String VALID = "VALID";
+ private static final String INVALID = "INVALID";
+ private static final String TEST_IFNAME = "test_wlan0";
+ private static final int TEST_IFINDEX = 1001;
+ // See RFC 7042#section-2.1.2 for EUI-48 documentation values.
+ private static final MacAddress TEST_MAC = MacAddress.fromString("00:00:5E:00:53:01");
+ private static final int TEST_TIMEOUT_MS = 400;
+
+ @Mock private Context mContext;
+ @Mock private ConnectivityManager mCm;
+ @Mock private INetworkManagementService mNMService;
+ @Mock private INetd mNetd;
+ @Mock private Resources mResources;
+ @Mock private IIpClientCallbacks mCb;
+ @Mock private AlarmManager mAlarm;
+ @Mock private IpClient.Dependencies mDependecies;
+ private MockContentResolver mContentResolver;
+
+ private BaseNetworkObserver mObserver;
+ private InterfaceParams mIfParams;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ when(mContext.getSystemService(eq(Context.ALARM_SERVICE))).thenReturn(mAlarm);
+ when(mContext.getSystemServiceName(ConnectivityManager.class))
+ .thenReturn(Context.CONNECTIVITY_SERVICE);
+ when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm);
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getInteger(R.integer.config_networkAvoidBadWifi))
+ .thenReturn(DEFAULT_AVOIDBADWIFI_CONFIG_VALUE);
+
+ mContentResolver = new MockContentResolver();
+ mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ when(mContext.getContentResolver()).thenReturn(mContentResolver);
+
+ mIfParams = null;
+
+ when(mDependecies.getNMS()).thenReturn(mNMService);
+ when(mDependecies.getNetd()).thenReturn(mNetd);
+ }
+
+ private void setTestInterfaceParams(String ifname) {
+ mIfParams = (ifname != null)
+ ? new InterfaceParams(ifname, TEST_IFINDEX, TEST_MAC)
+ : null;
+ when(mDependecies.getInterfaceParams(anyString())).thenReturn(mIfParams);
+ }
+
+ private IpClient makeIpClient(String ifname) throws Exception {
+ setTestInterfaceParams(ifname);
+ final IpClient ipc = new IpClient(mContext, ifname, mCb, mDependecies);
+ verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(ifname, false);
+ verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(ifname);
+ ArgumentCaptor<BaseNetworkObserver> arg =
+ ArgumentCaptor.forClass(BaseNetworkObserver.class);
+ verify(mNMService, times(1)).registerObserver(arg.capture());
+ mObserver = arg.getValue();
+ reset(mNMService);
+ reset(mNetd);
+ // Verify IpClient doesn't call onLinkPropertiesChange() when it starts.
+ verify(mCb, never()).onLinkPropertiesChange(any());
+ reset(mCb);
+ return ipc;
+ }
+
+ private static LinkProperties makeEmptyLinkProperties(String iface) {
+ final LinkProperties empty = new LinkProperties();
+ empty.setInterfaceName(iface);
+ return empty;
+ }
+
+ @Test
+ public void testNullInterfaceNameMostDefinitelyThrows() throws Exception {
+ setTestInterfaceParams(null);
+ try {
+ final IpClient ipc = new IpClient(mContext, null, mCb, mDependecies);
+ ipc.shutdown();
+ fail();
+ } catch (NullPointerException npe) {
+ // Phew; null interface names not allowed.
+ }
+ }
+
+ @Test
+ public void testNullCallbackMostDefinitelyThrows() throws Exception {
+ final String ifname = "lo";
+ setTestInterfaceParams(ifname);
+ try {
+ final IpClient ipc = new IpClient(mContext, ifname, null, mDependecies);
+ ipc.shutdown();
+ fail();
+ } catch (NullPointerException npe) {
+ // Phew; null callbacks not allowed.
+ }
+ }
+
+ @Test
+ public void testInvalidInterfaceDoesNotThrow() throws Exception {
+ setTestInterfaceParams(TEST_IFNAME);
+ final IpClient ipc = new IpClient(mContext, TEST_IFNAME, mCb, mDependecies);
+ ipc.shutdown();
+ }
+
+ @Test
+ public void testInterfaceNotFoundFailsImmediately() throws Exception {
+ setTestInterfaceParams(null);
+ final IpClient ipc = new IpClient(mContext, TEST_IFNAME, mCb, mDependecies);
+ ipc.startProvisioning(new ProvisioningConfiguration());
+ verify(mCb, times(1)).onProvisioningFailure(any());
+ ipc.shutdown();
+ }
+
+ @Test
+ public void testDefaultProvisioningConfiguration() throws Exception {
+ final String iface = TEST_IFNAME;
+ final IpClient ipc = makeIpClient(iface);
+
+ ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
+ .withoutIPv4()
+ // TODO: mock IpReachabilityMonitor's dependencies (NetworkInterface, PowerManager)
+ // and enable it in this test
+ .withoutIpReachabilityMonitor()
+ .build();
+
+ ipc.startProvisioning(config);
+ verify(mCb, times(1)).setNeighborDiscoveryOffload(true);
+ verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setFallbackMulticastFilter(false);
+ verify(mCb, never()).onProvisioningFailure(any());
+
+ ipc.shutdown();
+ verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(iface, false);
+ verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(iface);
+ verify(mCb, timeout(TEST_TIMEOUT_MS).times(1))
+ .onLinkPropertiesChange(argThat(
+ lp -> fromStableParcelable(lp).equals(makeEmptyLinkProperties(iface))));
+ }
+
+ @Test
+ public void testProvisioningWithInitialConfiguration() throws Exception {
+ final String iface = TEST_IFNAME;
+ final IpClient ipc = makeIpClient(iface);
+
+ String[] addresses = {
+ "fe80::a4be:f92:e1f7:22d1/64",
+ "fe80::f04a:8f6:6a32:d756/64",
+ "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"
+ };
+ String[] prefixes = { "fe80::/64", "fd2c:4e57:8e3c::/64" };
+
+ ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
+ .withoutIPv4()
+ .withoutIpReachabilityMonitor()
+ .withInitialConfiguration(conf(links(addresses), prefixes(prefixes), ips()))
+ .build();
+
+ ipc.startProvisioning(config);
+ verify(mCb, times(1)).setNeighborDiscoveryOffload(true);
+ verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setFallbackMulticastFilter(false);
+ verify(mCb, never()).onProvisioningFailure(any());
+
+ for (String addr : addresses) {
+ String[] parts = addr.split("/");
+ verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1))
+ .interfaceAddAddress(iface, parts[0], Integer.parseInt(parts[1]));
+ }
+
+ final int lastAddr = addresses.length - 1;
+
+ // Add N - 1 addresses
+ for (int i = 0; i < lastAddr; i++) {
+ mObserver.addressUpdated(iface, new LinkAddress(addresses[i]));
+ verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(any());
+ reset(mCb);
+ }
+
+ // Add Nth address
+ mObserver.addressUpdated(iface, new LinkAddress(addresses[lastAddr]));
+ LinkProperties want = linkproperties(links(addresses), routes(prefixes));
+ want.setInterfaceName(iface);
+ verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).onProvisioningSuccess(argThat(
+ lp -> fromStableParcelable(lp).equals(want)));
+
+ ipc.shutdown();
+ verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(iface, false);
+ verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(iface);
+ verify(mCb, timeout(TEST_TIMEOUT_MS).times(1))
+ .onLinkPropertiesChange(argThat(
+ lp -> fromStableParcelable(lp).equals(makeEmptyLinkProperties(iface))));
+ }
+
+ @Test
+ public void testIsProvisioned() throws Exception {
+ InitialConfiguration empty = conf(links(), prefixes());
+ IsProvisionedTestCase[] testcases = {
+ // nothing
+ notProvisionedCase(links(), routes(), dns(), null),
+ notProvisionedCase(links(), routes(), dns(), empty),
+
+ // IPv4
+ provisionedCase(links("192.0.2.12/24"), routes(), dns(), empty),
+
+ // IPv6
+ notProvisionedCase(
+ links("fe80::a4be:f92:e1f7:22d1/64", "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"),
+ routes(), dns(), empty),
+ notProvisionedCase(
+ links("fe80::a4be:f92:e1f7:22d1/64", "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"),
+ routes("fe80::/64", "fd2c:4e57:8e3c::/64"), dns("fd00:1234:5678::1000"), empty),
+ provisionedCase(
+ links("2001:db8:dead:beef:f00::a0/64", "fe80::1/64"),
+ routes("::/0"),
+ dns("2001:db8:dead:beef:f00::02"), empty),
+
+ // Initial configuration
+ provisionedCase(
+ links("fe80::e1f7:22d1/64", "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"),
+ routes("fe80::/64", "fd2c:4e57:8e3c::/64"),
+ dns(),
+ conf(links("fe80::e1f7:22d1/64", "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"),
+ prefixes( "fe80::/64", "fd2c:4e57:8e3c::/64"), ips()))
+ };
+
+ for (IsProvisionedTestCase testcase : testcases) {
+ if (IpClient.isProvisioned(testcase.lp, testcase.config) != testcase.isProvisioned) {
+ fail(testcase.errorMessage());
+ }
+ }
+ }
+
+ static class IsProvisionedTestCase {
+ boolean isProvisioned;
+ LinkProperties lp;
+ InitialConfiguration config;
+
+ String errorMessage() {
+ return String.format("expected %s with config %s to be %s, but was %s",
+ lp, config, provisioned(isProvisioned), provisioned(!isProvisioned));
+ }
+
+ static String provisioned(boolean isProvisioned) {
+ return isProvisioned ? "provisioned" : "not provisioned";
+ }
+ }
+
+ static IsProvisionedTestCase provisionedCase(Set<LinkAddress> lpAddrs, Set<RouteInfo> lpRoutes,
+ Set<InetAddress> lpDns, InitialConfiguration config) {
+ return provisioningTest(true, lpAddrs, lpRoutes, lpDns, config);
+ }
+
+ static IsProvisionedTestCase notProvisionedCase(Set<LinkAddress> lpAddrs,
+ Set<RouteInfo> lpRoutes, Set<InetAddress> lpDns, InitialConfiguration config) {
+ return provisioningTest(false, lpAddrs, lpRoutes, lpDns, config);
+ }
+
+ static IsProvisionedTestCase provisioningTest(boolean isProvisioned, Set<LinkAddress> lpAddrs,
+ Set<RouteInfo> lpRoutes, Set<InetAddress> lpDns, InitialConfiguration config) {
+ IsProvisionedTestCase testcase = new IsProvisionedTestCase();
+ testcase.isProvisioned = isProvisioned;
+ testcase.lp = new LinkProperties();
+ testcase.lp.setLinkAddresses(lpAddrs);
+ for (RouteInfo route : lpRoutes) {
+ testcase.lp.addRoute(route);
+ }
+ for (InetAddress dns : lpDns) {
+ testcase.lp.addDnsServer(dns);
+ }
+ testcase.config = config;
+ return testcase;
+ }
+
+ @Test
+ public void testInitialConfigurations() throws Exception {
+ InitialConfigurationTestCase[] testcases = {
+ validConf("valid IPv4 configuration",
+ links("192.0.2.12/24"), prefixes("192.0.2.0/24"), dns("192.0.2.2")),
+ validConf("another valid IPv4 configuration",
+ links("192.0.2.12/24"), prefixes("192.0.2.0/24"), dns()),
+ validConf("valid IPv6 configurations",
+ links("2001:db8:dead:beef:f00::a0/64", "fe80::1/64"),
+ prefixes("2001:db8:dead:beef::/64", "fe80::/64"),
+ dns("2001:db8:dead:beef:f00::02")),
+ validConf("valid IPv6 configurations",
+ links("fe80::1/64"), prefixes("fe80::/64"), dns()),
+ validConf("valid IPv6/v4 configuration",
+ links("2001:db8:dead:beef:f00::a0/48", "192.0.2.12/24"),
+ prefixes("2001:db8:dead:beef::/64", "192.0.2.0/24"),
+ dns("192.0.2.2", "2001:db8:dead:beef:f00::02")),
+ validConf("valid IPv6 configuration without any GUA.",
+ links("fd00:1234:5678::1/48"),
+ prefixes("fd00:1234:5678::/48"),
+ dns("fd00:1234:5678::1000")),
+
+ invalidConf("empty configuration", links(), prefixes(), dns()),
+ invalidConf("v4 addr and dns not in any prefix",
+ links("192.0.2.12/24"), prefixes("198.51.100.0/24"), dns("192.0.2.2")),
+ invalidConf("v4 addr not in any prefix",
+ links("198.51.2.12/24"), prefixes("198.51.100.0/24"), dns("192.0.2.2")),
+ invalidConf("v4 dns addr not in any prefix",
+ links("192.0.2.12/24"), prefixes("192.0.2.0/24"), dns("198.51.100.2")),
+ invalidConf("v6 addr not in any prefix",
+ links("2001:db8:dead:beef:f00::a0/64", "fe80::1/64"),
+ prefixes("2001:db8:dead:beef::/64"),
+ dns("2001:db8:dead:beef:f00::02")),
+ invalidConf("v6 dns addr not in any prefix",
+ links("fe80::1/64"), prefixes("fe80::/64"), dns("2001:db8:dead:beef:f00::02")),
+ invalidConf("default ipv6 route and no GUA",
+ links("fd01:1111:2222:3333::a0/128"), prefixes("::/0"), dns()),
+ invalidConf("invalid v6 prefix length",
+ links("2001:db8:dead:beef:f00::a0/128"), prefixes("2001:db8:dead:beef::/32"),
+ dns()),
+ invalidConf("another invalid v6 prefix length",
+ links("2001:db8:dead:beef:f00::a0/128"), prefixes("2001:db8:dead:beef::/72"),
+ dns())
+ };
+
+ for (InitialConfigurationTestCase testcase : testcases) {
+ if (testcase.config.isValid() != testcase.isValid) {
+ fail(testcase.errorMessage());
+ }
+ }
+ }
+
+ static class InitialConfigurationTestCase {
+ String descr;
+ boolean isValid;
+ InitialConfiguration config;
+ public String errorMessage() {
+ return String.format("%s: expected configuration %s to be %s, but was %s",
+ descr, config, validString(isValid), validString(!isValid));
+ }
+ static String validString(boolean isValid) {
+ return isValid ? VALID : INVALID;
+ }
+ }
+
+ static InitialConfigurationTestCase validConf(String descr, Set<LinkAddress> links,
+ Set<IpPrefix> prefixes, Set<InetAddress> dns) {
+ return confTestCase(descr, true, conf(links, prefixes, dns));
+ }
+
+ static InitialConfigurationTestCase invalidConf(String descr, Set<LinkAddress> links,
+ Set<IpPrefix> prefixes, Set<InetAddress> dns) {
+ return confTestCase(descr, false, conf(links, prefixes, dns));
+ }
+
+ static InitialConfigurationTestCase confTestCase(
+ String descr, boolean isValid, InitialConfiguration config) {
+ InitialConfigurationTestCase testcase = new InitialConfigurationTestCase();
+ testcase.descr = descr;
+ testcase.isValid = isValid;
+ testcase.config = config;
+ return testcase;
+ }
+
+ static LinkProperties linkproperties(Set<LinkAddress> addresses, Set<RouteInfo> routes) {
+ LinkProperties lp = new LinkProperties();
+ lp.setLinkAddresses(addresses);
+ for (RouteInfo route : routes) {
+ lp.addRoute(route);
+ }
+ return lp;
+ }
+
+ static InitialConfiguration conf(Set<LinkAddress> links, Set<IpPrefix> prefixes) {
+ return conf(links, prefixes, new HashSet<>());
+ }
+
+ static InitialConfiguration conf(
+ Set<LinkAddress> links, Set<IpPrefix> prefixes, Set<InetAddress> dns) {
+ InitialConfiguration conf = new InitialConfiguration();
+ conf.ipAddresses.addAll(links);
+ conf.directlyConnectedRoutes.addAll(prefixes);
+ conf.dnsServers.addAll(dns);
+ return conf;
+ }
+
+ static Set<RouteInfo> routes(String... routes) {
+ return mapIntoSet(routes, (r) -> new RouteInfo(new IpPrefix(r)));
+ }
+
+ static Set<IpPrefix> prefixes(String... prefixes) {
+ return mapIntoSet(prefixes, IpPrefix::new);
+ }
+
+ static Set<LinkAddress> links(String... addresses) {
+ return mapIntoSet(addresses, LinkAddress::new);
+ }
+
+ static Set<InetAddress> ips(String... addresses) {
+ return mapIntoSet(addresses, InetAddress::getByName);
+ }
+
+ static Set<InetAddress> dns(String... addresses) {
+ return ips(addresses);
+ }
+
+ static <A, B> Set<B> mapIntoSet(A[] in, Fn<A, B> fn) {
+ Set<B> out = new HashSet<>(in.length);
+ for (A item : in) {
+ try {
+ out.add(fn.call(item));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return out;
+ }
+
+ interface Fn<A,B> {
+ B call(A a) throws Exception;
+ }
+
+ @Test
+ public void testAll() {
+ List<String> list1 = Arrays.asList();
+ List<String> list2 = Arrays.asList("foo");
+ List<String> list3 = Arrays.asList("bar", "baz");
+ List<String> list4 = Arrays.asList("foo", "bar", "baz");
+
+ assertTrue(InitialConfiguration.all(list1, (x) -> false));
+ assertFalse(InitialConfiguration.all(list2, (x) -> false));
+ assertTrue(InitialConfiguration.all(list3, (x) -> true));
+ assertTrue(InitialConfiguration.all(list2, (x) -> x.charAt(0) == 'f'));
+ assertFalse(InitialConfiguration.all(list4, (x) -> x.charAt(0) == 'f'));
+ }
+
+ @Test
+ public void testAny() {
+ List<String> list1 = Arrays.asList();
+ List<String> list2 = Arrays.asList("foo");
+ List<String> list3 = Arrays.asList("bar", "baz");
+ List<String> list4 = Arrays.asList("foo", "bar", "baz");
+
+ assertFalse(InitialConfiguration.any(list1, (x) -> true));
+ assertTrue(InitialConfiguration.any(list2, (x) -> true));
+ assertTrue(InitialConfiguration.any(list2, (x) -> x.charAt(0) == 'f'));
+ assertFalse(InitialConfiguration.any(list3, (x) -> x.charAt(0) == 'f'));
+ assertTrue(InitialConfiguration.any(list4, (x) -> x.charAt(0) == 'f'));
+ }
+
+ @Test
+ public void testFindAll() {
+ List<String> list1 = Arrays.asList();
+ List<String> list2 = Arrays.asList("foo");
+ List<String> list3 = Arrays.asList("foo", "bar", "baz");
+
+ assertEquals(list1, IpClient.findAll(list1, (x) -> true));
+ assertEquals(list1, IpClient.findAll(list3, (x) -> false));
+ assertEquals(list3, IpClient.findAll(list3, (x) -> true));
+ assertEquals(list2, IpClient.findAll(list3, (x) -> x.charAt(0) == 'f'));
+ }
+}
diff --git a/packages/NetworkStack/tests/src/android/net/ip/IpReachabilityMonitorTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpReachabilityMonitorTest.java
new file mode 100644
index 0000000..e3b5ddf
--- /dev/null
+++ b/packages/NetworkStack/tests/src/android/net/ip/IpReachabilityMonitorTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ip;
+
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.util.InterfaceParams;
+import android.net.util.SharedLog;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+/**
+ * Tests for IpReachabilityMonitor.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpReachabilityMonitorTest {
+
+ @Mock IpReachabilityMonitor.Callback mCallback;
+ @Mock IpReachabilityMonitor.Dependencies mDependencies;
+ @Mock SharedLog mLog;
+ @Mock Context mContext;
+ Handler mHandler;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mLog.forSubComponent(anyString())).thenReturn(mLog);
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+
+ IpReachabilityMonitor makeMonitor() {
+ final InterfaceParams ifParams = new InterfaceParams("fake0", 1, null);
+ return new IpReachabilityMonitor(
+ mContext, ifParams, mHandler, mLog, mCallback, false, mDependencies);
+ }
+
+ @Test
+ public void testNothing() {
+ IpReachabilityMonitor monitor = makeMonitor();
+ }
+}
diff --git a/packages/NetworkStack/tests/src/android/net/util/ConnectivityPacketSummaryTest.java b/packages/NetworkStack/tests/src/android/net/util/ConnectivityPacketSummaryTest.java
new file mode 100644
index 0000000..dfaf52a
--- /dev/null
+++ b/packages/NetworkStack/tests/src/android/net/util/ConnectivityPacketSummaryTest.java
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2016 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.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.net.MacAddress;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import libcore.util.HexEncoding;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for ConnectivityPacketSummary.
+ *
+ * @hide
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ConnectivityPacketSummaryTest {
+ private static final MacAddress MYHWADDR = MacAddress.fromString("80:7a:bf:6f:48:f3");
+
+ private String getSummary(String hexBytes) {
+ hexBytes = hexBytes.replaceAll("\\s+", "");
+ final byte[] bytes = HexEncoding.decode(hexBytes.toCharArray(), false);
+ return ConnectivityPacketSummary.summarize(MYHWADDR, bytes);
+ }
+
+ @Test
+ public void testParseICMPv6DADProbe() {
+ final String packet =
+ // Ethernet
+ "3333FF6F48F3 807ABF6F48F3 86DD" +
+ // IPv6
+ "600000000018 3A FF" +
+ "00000000000000000000000000000000" +
+ "FF0200000000000000000001FF6F48F3" +
+ // ICMPv6
+ "87 00 A8E7" +
+ "00000000" +
+ "FE80000000000000827ABFFFFE6F48F3";
+
+ final String expected =
+ "TX 80:7a:bf:6f:48:f3 > 33:33:ff:6f:48:f3 ipv6" +
+ " :: > ff02::1:ff6f:48f3 icmp6" +
+ " ns fe80::827a:bfff:fe6f:48f3";
+
+ assertEquals(expected, getSummary(packet));
+ }
+
+ @Test
+ public void testParseICMPv6RS() {
+ final String packet =
+ // Ethernet
+ "333300000002 807ABF6F48F3 86DD" +
+ // IPv6
+ "600000000010 3A FF" +
+ "FE80000000000000827ABFFFFE6F48F3" +
+ "FF020000000000000000000000000002" +
+ // ICMPv6 RS
+ "85 00 6973" +
+ "00000000" +
+ "01 01 807ABF6F48F3";
+
+ final String expected =
+ "TX 80:7a:bf:6f:48:f3 > 33:33:00:00:00:02 ipv6" +
+ " fe80::827a:bfff:fe6f:48f3 > ff02::2 icmp6" +
+ " rs slla 80:7a:bf:6f:48:f3";
+
+ assertEquals(expected, getSummary(packet));
+ }
+
+ @Test
+ public void testParseICMPv6RA() {
+ final String packet =
+ // Ethernet
+ "807ABF6F48F3 100E7E263FC1 86DD" +
+ // IPv6
+ "600000000068 3A FF" +
+ "FE80000000000000FA000004FD000001" +
+ "FE80000000000000827ABFFFFE6F48F3" +
+ // ICMPv6 RA
+ "86 00 8141" +
+ "40 00 0E10" +
+ "00000000" +
+ "00000000" +
+ "01 01 00005E000265" +
+ "05 01 0000000005DC" +
+ "19 05 000000000E10" +
+ " 20014860486000000000000000008844" +
+ " 20014860486000000000000000008888" +
+ "03 04 40 C0" +
+ " 00278D00" +
+ " 00093A80" +
+ " 00000000" +
+ " 2401FA000004FD000000000000000000";
+
+ final String expected =
+ "RX 10:0e:7e:26:3f:c1 > 80:7a:bf:6f:48:f3 ipv6" +
+ " fe80::fa00:4:fd00:1 > fe80::827a:bfff:fe6f:48f3 icmp6" +
+ " ra slla 00:00:5e:00:02:65 mtu 1500";
+
+ assertEquals(expected, getSummary(packet));
+ }
+
+ @Test
+ public void testParseICMPv6NS() {
+ final String packet =
+ // Ethernet
+ "807ABF6F48F3 100E7E263FC1 86DD" +
+ // IPv6
+ "6C0000000020 3A FF" +
+ "FE80000000000000FA000004FD000001" +
+ "FF0200000000000000000001FF01C146" +
+ // ICMPv6 NS
+ "87 00 8AD4" +
+ "00000000" +
+ "2401FA000004FD0015EA6A5C7B01C146" +
+ "01 01 00005E000265";
+
+ final String expected =
+ "RX 10:0e:7e:26:3f:c1 > 80:7a:bf:6f:48:f3 ipv6" +
+ " fe80::fa00:4:fd00:1 > ff02::1:ff01:c146 icmp6" +
+ " ns 2401:fa00:4:fd00:15ea:6a5c:7b01:c146 slla 00:00:5e:00:02:65";
+
+ assertEquals(expected, getSummary(packet));
+ }
+
+ @Test
+ public void testInvalidICMPv6NDLength() {
+ final String packet =
+ // Ethernet
+ "807ABF6F48F3 100E7E263FC1 86DD" +
+ // IPv6
+ "600000000068 3A FF" +
+ "FE80000000000000FA000004FD000001" +
+ "FE80000000000000827ABFFFFE6F48F3" +
+ // ICMPv6 RA
+ "86 00 8141" +
+ "40 00 0E10" +
+ "00000000" +
+ "00000000" +
+ "01 01 00005E000265" +
+ "00 00 0102030405D6";
+
+ final String expected =
+ "RX 10:0e:7e:26:3f:c1 > 80:7a:bf:6f:48:f3 ipv6" +
+ " fe80::fa00:4:fd00:1 > fe80::827a:bfff:fe6f:48f3 icmp6" +
+ " ra slla 00:00:5e:00:02:65 <malformed>";
+
+ assertEquals(expected, getSummary(packet));
+ }
+
+ @Test
+ public void testParseICMPv6NA() {
+ final String packet =
+ // Ethernet
+ "00005E000265 807ABF6F48F3 86DD" +
+ "600000000020 3A FF" +
+ "2401FA000004FD0015EA6A5C7B01C146" +
+ "FE80000000000000FA000004FD000001" +
+ "88 00 E8126" +
+ "0000000" +
+ "2401FA000004FD0015EA6A5C7B01C146" +
+ "02 01 807ABF6F48F3";
+
+ final String expected =
+ "TX 80:7a:bf:6f:48:f3 > 00:00:5e:00:02:65 ipv6" +
+ " 2401:fa00:4:fd00:15ea:6a5c:7b01:c146 > fe80::fa00:4:fd00:1 icmp6" +
+ " na 2401:fa00:4:fd00:15ea:6a5c:7b01:c146 tlla 80:7a:bf:6f:48:f3";
+
+ assertEquals(expected, getSummary(packet));
+ }
+
+ @Test
+ public void testParseARPRequest() {
+ final String packet =
+ // Ethernet
+ "FFFFFFFFFFFF 807ABF6F48F3 0806" +
+ // ARP
+ "0001 0800 06 04" +
+ // Request
+ "0001" +
+ "807ABF6F48F3 64706ADB" +
+ "000000000000 64706FFD";
+
+ final String expected =
+ "TX 80:7a:bf:6f:48:f3 > ff:ff:ff:ff:ff:ff arp" +
+ " who-has 100.112.111.253";
+
+ assertEquals(expected, getSummary(packet));
+ }
+
+ @Test
+ public void testParseARPReply() {
+ final String packet =
+ // Ethernet
+ "807ABF6F48F3 288A1CA8DFC1 0806" +
+ // ARP
+ "0001 0800 06 04" +
+ // Reply
+ "0002" +
+ "288A1CA8DFC1 64706FFD"+
+ "807ABF6F48F3 64706ADB" +
+ // Ethernet padding to packet min size.
+ "0000000000000000000000000000";
+
+ final String expected =
+ "RX 28:8a:1c:a8:df:c1 > 80:7a:bf:6f:48:f3 arp" +
+ " reply 100.112.111.253 28:8a:1c:a8:df:c1";
+
+ assertEquals(expected, getSummary(packet));
+ }
+
+ @Test
+ public void testParseDHCPv4Discover() {
+ final String packet =
+ // Ethernet
+ "FFFFFFFFFFFF 807ABF6F48F3 0800" +
+ // IPv4
+ "451001580000400040113986" +
+ "00000000" +
+ "FFFFFFFF" +
+ // UDP
+ "0044 0043" +
+ "0144 5559" +
+ // DHCPv4
+ "01 01 06 00" +
+ "79F7ACA4" +
+ "0000 0000" +
+ "00000000" +
+ "00000000" +
+ "00000000" +
+ "00000000" +
+ "807ABF6F48F300000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "63 82 53 63" +
+ "35 01 01" +
+ "3D 07 01807ABF6F48F3" +
+ "39 02 05DC" +
+ "3C 12 616E64726F69642D646863702D372E312E32" +
+ "0C 18 616E64726F69642D36623030366333313333393835343139" +
+ "37 0A 01 03 06 0F 1A 1C 33 3A 3B 2B" +
+ "FF" +
+ "00";
+
+ final String expectedPrefix =
+ "TX 80:7a:bf:6f:48:f3 > ff:ff:ff:ff:ff:ff ipv4" +
+ " 0.0.0.0 > 255.255.255.255 udp" +
+ " 68 > 67 dhcp4" +
+ " 80:7a:bf:6f:48:f3 DISCOVER";
+
+ assertTrue(getSummary(packet).startsWith(expectedPrefix));
+ }
+
+ @Test
+ public void testParseDHCPv4Offer() {
+ final String packet =
+ // Ethernet
+ "807ABF6F48F3 288A1CA8DFC1 0800" +
+ // IPv4
+ "4500013D4D2C0000401188CB" +
+ "64706FFD" +
+ "64706ADB" +
+ // UDP
+ "0043 0044" +
+ "0129 371D" +
+ // DHCPv4
+ "02 01 06 01" +
+ "79F7ACA4" +
+ "0000 0000" +
+ "00000000" +
+ "64706ADB" +
+ "00000000" +
+ "00000000" +
+ "807ABF6F48F300000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "63 82 53 63" +
+ "35 01 02" +
+ "36 04 AC188A0B" +
+ "33 04 00000708" +
+ "01 04 FFFFF000" +
+ "03 04 64706FFE" +
+ "06 08 08080808" +
+ " 08080404" +
+ "FF0001076165313A363636FF";
+
+ final String expectedPrefix =
+ "RX 28:8a:1c:a8:df:c1 > 80:7a:bf:6f:48:f3 ipv4" +
+ " 100.112.111.253 > 100.112.106.219 udp" +
+ " 67 > 68 dhcp4" +
+ " 80:7a:bf:6f:48:f3 OFFER";
+
+ assertTrue(getSummary(packet).startsWith(expectedPrefix));
+ }
+
+ @Test
+ public void testParseDHCPv4Request() {
+ final String packet =
+ // Ethernet
+ "FFFFFFFFFFFF 807ABF6F48F3 0800" +
+ // IPv4
+ "45100164000040004011397A" +
+ "00000000" +
+ "FFFFFFFF" +
+ // UDP
+ "0044 0043" +
+ "0150 E5C7" +
+ // DHCPv4
+ "01 01 06 00" +
+ "79F7ACA4" +
+ "0001 0000" +
+ "00000000" +
+ "00000000" +
+ "00000000" +
+ "00000000" +
+ "807ABF6F48F300000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "63 82 53 63" +
+ "35 01 03" +
+ "3D 07 01807ABF6F48F3" +
+ "32 04 64706ADB" +
+ "36 04 AC188A0B" +
+ "39 02 05DC" +
+ "3C 12 616E64726F69642D646863702D372E312E32" +
+ "0C 18 616E64726F69642D36623030366333313333393835343139" +
+ "37 0A 01 03 06 0F 1A 1C 33 3A 3B 2B" +
+ "FF" +
+ "00";
+
+ final String expectedPrefix =
+ "TX 80:7a:bf:6f:48:f3 > ff:ff:ff:ff:ff:ff ipv4" +
+ " 0.0.0.0 > 255.255.255.255 udp" +
+ " 68 > 67 dhcp4" +
+ " 80:7a:bf:6f:48:f3 REQUEST";
+
+ assertTrue(getSummary(packet).startsWith(expectedPrefix));
+ }
+
+ @Test
+ public void testParseDHCPv4Ack() {
+ final String packet =
+ // Ethernet
+ "807ABF6F48F3 288A1CA8DFC1 0800" +
+ // IPv4
+ "4500013D4D3B0000401188BC" +
+ "64706FFD" +
+ "64706ADB" +
+ // UDP
+ "0043 0044" +
+ "0129 341C" +
+ // DHCPv4
+ "02 01 06 01" +
+ "79F7ACA4" +
+ "0001 0000" +
+ "00000000" +
+ "64706ADB" +
+ "00000000" +
+ "00000000" +
+ "807ABF6F48F300000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "63 82 53 63" +
+ "35 01 05" +
+ "36 04 AC188A0B" +
+ "33 04 00000708" +
+ "01 04 FFFFF000" +
+ "03 04 64706FFE" +
+ "06 08 08080808" +
+ " 08080404" +
+ "FF0001076165313A363636FF";
+
+ final String expectedPrefix =
+ "RX 28:8a:1c:a8:df:c1 > 80:7a:bf:6f:48:f3 ipv4" +
+ " 100.112.111.253 > 100.112.106.219 udp" +
+ " 67 > 68 dhcp4" +
+ " 80:7a:bf:6f:48:f3 ACK";
+
+ assertTrue(getSummary(packet).startsWith(expectedPrefix));
+ }
+}
diff --git a/tests/net/java/android/net/util/PacketReaderTest.java b/packages/NetworkStack/tests/src/android/net/util/PacketReaderTest.java
similarity index 100%
rename from tests/net/java/android/net/util/PacketReaderTest.java
rename to packages/NetworkStack/tests/src/android/net/util/PacketReaderTest.java
diff --git a/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml
index 9f30eda..716fc8d 100644
--- a/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml
+++ b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml
@@ -33,7 +33,7 @@
android:textAppearance="@style/AppEntitiesHeader.Text.HeaderTitle"/>
<LinearLayout
- android:id="@+id/all_apps_view"
+ android:id="@+id/app_views_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp"
@@ -61,4 +61,12 @@
android:layout_height="48dp"
android:gravity="center"/>
+ <TextView
+ android:id="@+id/empty_view"
+ android:layout_width="match_parent"
+ android:layout_height="106dp"
+ android:gravity="center"
+ android:visibility="gone"
+ android:textAppearance="@style/AppEntitiesHeader.Text.Summary"/>
+
</LinearLayout>
diff --git a/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java b/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java
index 73cb8db..330049f 100644
--- a/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java
+++ b/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java
@@ -77,9 +77,11 @@
private final Context mContext;
private final TextView mHeaderTitleView;
+ private final TextView mHeaderEmptyView;
private final Button mHeaderDetailsView;
private final AppEntityInfo[] mAppEntityInfos;
+ private final View mAppViewsContainer;
private final View[] mAppEntityViews;
private final ImageView[] mAppIconViews;
private final TextView[] mAppTitleViews;
@@ -87,6 +89,7 @@
private int mHeaderTitleRes;
private int mHeaderDetailsRes;
+ private int mHeaderEmptyRes;
private View.OnClickListener mDetailsOnClickListener;
/**
@@ -104,6 +107,8 @@
mContext = context;
mHeaderTitleView = appEntitiesHeaderView.findViewById(R.id.header_title);
mHeaderDetailsView = appEntitiesHeaderView.findViewById(R.id.header_details);
+ mHeaderEmptyView = appEntitiesHeaderView.findViewById(R.id.empty_view);
+ mAppViewsContainer = appEntitiesHeaderView.findViewById(R.id.app_views_container);
mAppEntityInfos = new AppEntityInfo[MAXIMUM_APPS];
mAppIconViews = new ImageView[MAXIMUM_APPS];
@@ -152,6 +157,14 @@
}
/**
+ * Sets the string resource id for the empty text.
+ */
+ public AppEntitiesHeaderController setHeaderEmptyRes(@StringRes int emptyRes) {
+ mHeaderEmptyRes = emptyRes;
+ return this;
+ }
+
+ /**
* Set an app entity at a specified position view.
*
* @param index the index at which the specified view is to be inserted
@@ -192,6 +205,12 @@
*/
public void apply() {
bindHeaderTitleView();
+
+ if (isAppEntityInfosEmpty()) {
+ setEmptyViewVisible(true);
+ return;
+ }
+ setEmptyViewVisible(false);
bindHeaderDetailsView();
// Rebind all apps view
@@ -245,4 +264,22 @@
mAppSummaryViews[index].setText(summary);
}
}
+
+ private void setEmptyViewVisible(boolean visible) {
+ if (mHeaderEmptyRes != 0) {
+ mHeaderEmptyView.setText(mHeaderEmptyRes);
+ }
+ mHeaderEmptyView.setVisibility(visible ? View.VISIBLE : View.GONE);
+ mHeaderDetailsView.setVisibility(visible ? View.GONE : View.VISIBLE);
+ mAppViewsContainer.setVisibility(visible ? View.GONE : View.VISIBLE);
+ }
+
+ private boolean isAppEntityInfosEmpty() {
+ for (AppEntityInfo info : mAppEntityInfos) {
+ if (info != null) {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 09fa284..ed3c11c 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -102,6 +102,20 @@
<!-- Bluetooth settings -->
+ <!-- Titles for Bluetooth HCI Snoop Logging -->
+ <string-array name="bt_hci_snoop_log_entries">
+ <item>Disabled</item>
+ <item>Enabled Filtered</item>
+ <item>Enabled</item>
+ </string-array>
+
+ <!-- Values for Bluetooth HCI Snoop Logging -->
+ <string-array name="bt_hci_snoop_log_values" translatable="false">
+ <item>disabled</item>
+ <item>filtered</item>
+ <item>full</item>
+ </string-array>
+
<!-- Titles for Bluetooth AVRCP Versions -->
<string-array name="bluetooth_avrcp_versions">
<item>AVRCP 1.4 (Default)</item>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 16bfcc9..c8f8d73 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -591,8 +591,8 @@
<string name="keep_screen_on_summary">Screen will never sleep while charging</string>
<!-- Setting Checkbox title whether to enable Bluetooth HCI snoop log -->
<string name="bt_hci_snoop_log">Enable Bluetooth HCI snoop log</string>
- <!-- setting Checkbox summary whether to capture all Bluetooth HCI packets in a file -->
- <string name="bt_hci_snoop_log_summary">Capture all Bluetooth HCI packets in a file (Toggle Bluetooth after changing this setting)</string>
+ <!-- setting Checkbox summary whether to capture all Bluetooth HCI packets in a file [CHAR_LIMIT=100] -->
+ <string name="bt_hci_snoop_log_summary">Capture Bluetooth packets. (Toggle Bluetooth after changing this setting)</string>
<!-- setting Checkbox title whether to enable OEM unlock [CHAR_LIMIT=35] -->
<string name="oem_unlock_enable">OEM unlocking</string>
<!-- setting Checkbox summary whether to enable OEM unlock [CHAR_LIMIT=50] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index 24d7011..c059156 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -74,6 +74,7 @@
device.refresh();
}
mIsProfileReady=true;
+ mProfileManager.callServiceConnectedListeners();
}
public void onServiceDisconnected(int profile) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index 577d98d..77dfbe9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -71,8 +71,8 @@
// Check current list of CachedDevices to see if any are Hearing Aid devices.
mDeviceManager.updateHearingAidsDevices();
-
mIsProfileReady=true;
+ mProfileManager.callServiceConnectedListeners();
}
public void onServiceDisconnected(int profile) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
index 959f9b2..a5c6f0c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
@@ -54,17 +54,17 @@
}
@Override
- public void connect() {
+ public boolean connect() {
//TODO(b/117129183): add callback to notify LocalMediaManager connection state.
- mIsConnected = mCachedDevice.setActive();
- super.connect();
- Log.d(TAG, "connect() device : " + getName() + ", is selected : " + mIsConnected);
+ final boolean isConnected = mCachedDevice.setActive();
+ setConnectedRecord();
+ Log.d(TAG, "connect() device : " + getName() + ", is selected : " + isConnected);
+ return isConnected;
}
@Override
public void disconnect() {
//TODO(b/117129183): disconnected last select device
- mIsConnected = false;
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java
index ab1cca0..fa2dd88 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java
@@ -18,6 +18,7 @@
import android.app.Notification;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.util.Log;
@@ -35,36 +36,48 @@
/**
* BluetoothMediaManager provide interface to get Bluetooth device list.
*/
-public class BluetoothMediaManager extends MediaManager implements BluetoothCallback {
+public class BluetoothMediaManager extends MediaManager implements BluetoothCallback,
+ LocalBluetoothProfileManager.ServiceListener {
private static final String TAG = "BluetoothMediaManager";
- private final DeviceAttributeChangeCallback mCachedDeviceCallback =
- new DeviceAttributeChangeCallback();
-
private LocalBluetoothManager mLocalBluetoothManager;
private LocalBluetoothProfileManager mProfileManager;
+ private CachedBluetoothDeviceManager mCachedBluetoothDeviceManager;
private MediaDevice mLastAddedDevice;
private MediaDevice mLastRemovedDevice;
+ private boolean mIsA2dpProfileReady = false;
+ private boolean mIsHearingAidProfileReady = false;
+
BluetoothMediaManager(Context context, LocalBluetoothManager localBluetoothManager,
Notification notification) {
super(context, notification);
mLocalBluetoothManager = localBluetoothManager;
mProfileManager = mLocalBluetoothManager.getProfileManager();
+ mCachedBluetoothDeviceManager = mLocalBluetoothManager.getCachedDeviceManager();
}
@Override
public void startScan() {
- mMediaDevices.clear();
mLocalBluetoothManager.getEventManager().registerCallback(this);
buildBluetoothDeviceList();
dispatchDeviceListAdded();
+
+ // The profile may not ready when calling startScan().
+ // Device status are all disconnected since profiles are not ready to connected.
+ // In this case, we observe onServiceConnected() in LocalBluetoothProfileManager.
+ // When A2dpProfile or HearingAidProfile is connected will call buildBluetoothDeviceList()
+ // again to find the connected devices.
+ if (!mIsA2dpProfileReady || !mIsHearingAidProfileReady) {
+ mProfileManager.addServiceListener(this);
+ }
}
private void buildBluetoothDeviceList() {
+ mMediaDevices.clear();
addConnectedA2dpDevices();
addConnectedHearingAidDevices();
}
@@ -77,12 +90,10 @@
}
final List<BluetoothDevice> devices = a2dpProfile.getConnectedDevices();
- final CachedBluetoothDeviceManager cachedBluetoothDeviceManager =
- mLocalBluetoothManager.getCachedDeviceManager();
for (BluetoothDevice device : devices) {
final CachedBluetoothDevice cachedDevice =
- cachedBluetoothDeviceManager.findDevice(device);
+ mCachedBluetoothDeviceManager.findDevice(device);
if (cachedDevice == null) {
Log.w(TAG, "Can't found CachedBluetoothDevice : " + device.getName());
@@ -96,6 +107,8 @@
addMediaDevice(cachedDevice);
}
}
+
+ mIsA2dpProfileReady = a2dpProfile.isProfileReady();
}
private void addConnectedHearingAidDevices() {
@@ -107,12 +120,10 @@
final List<Long> devicesHiSyncIds = new ArrayList<>();
final List<BluetoothDevice> devices = hapProfile.getConnectedDevices();
- final CachedBluetoothDeviceManager cachedBluetoothDeviceManager =
- mLocalBluetoothManager.getCachedDeviceManager();
for (BluetoothDevice device : devices) {
final CachedBluetoothDevice cachedDevice =
- cachedBluetoothDeviceManager.findDevice(device);
+ mCachedBluetoothDeviceManager.findDevice(device);
if (cachedDevice == null) {
Log.w(TAG, "Can't found CachedBluetoothDevice : " + device.getName());
@@ -130,13 +141,14 @@
addMediaDevice(cachedDevice);
}
}
+
+ mIsHearingAidProfileReady = hapProfile.isProfileReady();
}
private void addMediaDevice(CachedBluetoothDevice cachedDevice) {
MediaDevice mediaDevice = findMediaDevice(MediaDeviceUtils.getId(cachedDevice));
if (mediaDevice == null) {
mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice);
- cachedDevice.registerCallback(mCachedDeviceCallback);
mLastAddedDevice = mediaDevice;
mMediaDevices.add(mediaDevice);
}
@@ -145,16 +157,6 @@
@Override
public void stopScan() {
mLocalBluetoothManager.getEventManager().unregisterCallback(this);
- unregisterCachedDeviceCallback();
- }
-
- private void unregisterCachedDeviceCallback() {
- for (MediaDevice device : mMediaDevices) {
- if (device instanceof BluetoothMediaDevice) {
- ((BluetoothMediaDevice) device).getCachedDevice()
- .unregisterCallback(mCachedDeviceCallback);
- }
- }
}
@Override
@@ -166,8 +168,6 @@
final List<MediaDevice> removeDevicesList = new ArrayList<>();
for (MediaDevice device : mMediaDevices) {
if (device instanceof BluetoothMediaDevice) {
- ((BluetoothMediaDevice) device).getCachedDevice()
- .unregisterCallback(mCachedDeviceCallback);
removeDevicesList.add(device);
}
}
@@ -212,7 +212,6 @@
private void removeMediaDevice(CachedBluetoothDevice cachedDevice) {
final MediaDevice mediaDevice = findMediaDevice(MediaDeviceUtils.getId(cachedDevice));
if (mediaDevice != null) {
- cachedDevice.unregisterCallback(mCachedDeviceCallback);
mLastRemovedDevice = mediaDevice;
mMediaDevices.remove(mediaDevice);
}
@@ -252,10 +251,34 @@
dispatchDeviceRemoved(cachedDevice);
}
}
- class DeviceAttributeChangeCallback implements CachedBluetoothDevice.Callback {
- @Override
- public void onDeviceAttributesChanged() {
- dispatchDeviceAttributesChanged();
+
+ @Override
+ public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
+ Log.d(TAG, "onActiveDeviceChanged : device : "
+ + activeDevice + ", profile : " + bluetoothProfile);
+ if (BluetoothProfile.HEARING_AID == bluetoothProfile
+ || BluetoothProfile.A2DP == bluetoothProfile) {
+ final String id = activeDevice == null
+ ? PhoneMediaDevice.ID : MediaDeviceUtils.getId(activeDevice);
+ dispatchConnectedDeviceChanged(id);
}
}
+
+ @Override
+ public void onServiceConnected() {
+ if (!mIsA2dpProfileReady || !mIsHearingAidProfileReady) {
+ buildBluetoothDeviceList();
+ dispatchDeviceListAdded();
+ }
+
+ //Remove the listener once a2dpProfile and hearingAidProfile are ready.
+ if (mIsA2dpProfileReady && mIsHearingAidProfileReady) {
+ mProfileManager.removeServiceListener(this);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected() {
+
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
index 21a81e0..04f70cc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
@@ -16,6 +16,7 @@
package com.android.settingslib.media;
import android.content.Context;
+import android.widget.Toast;
import androidx.mediarouter.media.MediaRouter;
@@ -43,7 +44,7 @@
@Override
public int getIcon() {
- //TODO(b/117129183): This is not final icon for cast device, just for demo.
+ //TODO(b/121083246): This is not final icon for cast device, just for demo.
return R.drawable.ic_settings_print;
}
@@ -53,15 +54,15 @@
}
@Override
- public void connect() {
- //TODO(b/117129183): use MediaController2 to transfer media
- mIsConnected = true;
- super.connect();
+ public boolean connect() {
+ //TODO(b/121083246): use SystemApi to transfer media
+ setConnectedRecord();
+ Toast.makeText(mContext, "This is cast device !", Toast.LENGTH_SHORT).show();
+ return false;
}
@Override
public void disconnect() {
- //TODO(b/117129183): disconnected last select device
- mIsConnected = false;
+ //TODO(b/121083246): disconnected last select device
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 6907238..bc8e2c3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -22,6 +22,8 @@
import androidx.mediarouter.media.MediaRouteSelector;
import androidx.mediarouter.media.MediaRouter;
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* InfoMediaManager provide interface to get InfoMediaDevice list.
*/
@@ -29,9 +31,13 @@
private static final String TAG = "InfoMediaManager";
- private final MediaRouterCallback mMediaRouterCallback = new MediaRouterCallback();
+ @VisibleForTesting
+ final MediaRouterCallback mMediaRouterCallback = new MediaRouterCallback();
+ @VisibleForTesting
+ MediaRouteSelector mSelector;
+ @VisibleForTesting
+ MediaRouter mMediaRouter;
- private MediaRouter mMediaRouter;
private String mPackageName;
InfoMediaManager(Context context, String packageName, Notification notification) {
@@ -39,24 +45,20 @@
mMediaRouter = MediaRouter.getInstance(context);
mPackageName = packageName;
+ mSelector = new MediaRouteSelector.Builder()
+ .addControlCategory(getControlCategoryByPackageName(mPackageName))
+ .build();
}
@Override
public void startScan() {
mMediaDevices.clear();
- startScanCastDevice();
- }
-
- private void startScanCastDevice() {
- final MediaRouteSelector selector = new MediaRouteSelector.Builder()
- .addControlCategory(getControlCategoryByPackageName(mPackageName))
- .build();
-
- mMediaRouter.addCallback(selector, mMediaRouterCallback,
+ mMediaRouter.addCallback(mSelector, mMediaRouterCallback,
MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
}
- private String getControlCategoryByPackageName(String packageName) {
+ @VisibleForTesting
+ String getControlCategoryByPackageName(String packageName) {
//TODO(b/117129183): Use package name to get ControlCategory.
//Since api not ready, return fixed ControlCategory for prototype.
return "com.google.android.gms.cast.CATEGORY_CAST/4F8B3483";
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index c9479d4..44d945a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -16,12 +16,15 @@
package com.android.settingslib.media;
import android.app.Notification;
+import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.util.Log;
import androidx.annotation.IntDef;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import java.lang.annotation.Retention;
@@ -50,16 +53,20 @@
}
private final Collection<DeviceCallback> mCallbacks = new ArrayList<>();
- private final MediaDeviceCallback mMediaDeviceCallback = new MediaDeviceCallback();
+ @VisibleForTesting
+ final MediaDeviceCallback mMediaDeviceCallback = new MediaDeviceCallback();
private Context mContext;
- private List<MediaDevice> mMediaDevices = new ArrayList<>();
private BluetoothMediaManager mBluetoothMediaManager;
private InfoMediaManager mInfoMediaManager;
-
private LocalBluetoothManager mLocalBluetoothManager;
- private MediaDevice mLastConnectedDevice;
- private MediaDevice mPhoneDevice;
+
+ @VisibleForTesting
+ List<MediaDevice> mMediaDevices = new ArrayList<>();
+ @VisibleForTesting
+ MediaDevice mPhoneDevice;
+ @VisibleForTesting
+ MediaDevice mCurrentConnectedDevice;
/**
* Register to start receiving callbacks for MediaDevice events.
@@ -93,28 +100,40 @@
mInfoMediaManager = new InfoMediaManager(context, packageName, notification);
}
+ @VisibleForTesting
+ LocalMediaManager(Context context, LocalBluetoothManager localBluetoothManager,
+ BluetoothMediaManager bluetoothMediaManager, InfoMediaManager infoMediaManager) {
+ mContext = context;
+ mLocalBluetoothManager = localBluetoothManager;
+ mBluetoothMediaManager = bluetoothMediaManager;
+ mInfoMediaManager = infoMediaManager;
+ }
+
/**
* Connect the MediaDevice to transfer media
* @param connectDevice the MediaDevice
*/
public void connectDevice(MediaDevice connectDevice) {
- if (connectDevice == mLastConnectedDevice) {
+ final MediaDevice device = getMediaDeviceById(mMediaDevices, connectDevice.getId());
+ if (device == mCurrentConnectedDevice) {
+ Log.d(TAG, "connectDevice() this device all ready connected! : " + device.getName());
return;
}
- if (mLastConnectedDevice != null) {
- mLastConnectedDevice.disconnect();
+ //TODO(b/121083246): Update it once remote media API is ready.
+ if (mCurrentConnectedDevice != null && !(connectDevice instanceof InfoMediaDevice)) {
+ mCurrentConnectedDevice.disconnect();
}
- connectDevice.connect();
- if (connectDevice.isConnected()) {
- mLastConnectedDevice = connectDevice;
+ final boolean isConnected = device.connect();
+ if (isConnected) {
+ mCurrentConnectedDevice = device;
}
- final int state = connectDevice.isConnected()
+ final int state = isConnected
? MediaDeviceState.STATE_CONNECTED
: MediaDeviceState.STATE_DISCONNECTED;
- dispatchSelectedDeviceStateChanged(connectDevice, state);
+ dispatchSelectedDeviceStateChanged(device, state);
}
void dispatchSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state) {
@@ -189,6 +208,31 @@
return null;
}
+ /**
+ * Find the current connected MediaDevice.
+ *
+ * @return MediaDevice
+ */
+ public MediaDevice getCurrentConnectedDevice() {
+ return mCurrentConnectedDevice;
+ }
+
+ private MediaDevice updateCurrentConnectedDevice() {
+ for (MediaDevice device : mMediaDevices) {
+ if (device instanceof BluetoothMediaDevice) {
+ if (isConnected(((BluetoothMediaDevice) device).getCachedDevice())) {
+ return device;
+ }
+ }
+ }
+ return mMediaDevices.contains(mPhoneDevice) ? mPhoneDevice : null;
+ }
+
+ private boolean isConnected(CachedBluetoothDevice device) {
+ return device.isActiveDevice(BluetoothProfile.A2DP)
+ || device.isActiveDevice(BluetoothProfile.HEARING_AID);
+ }
+
class MediaDeviceCallback implements MediaManager.MediaDeviceCallback {
@Override
public void onDeviceAdded(MediaDevice device) {
@@ -201,8 +245,13 @@
@Override
public void onDeviceListAdded(List<MediaDevice> devices) {
- mMediaDevices.addAll(devices);
+ for (MediaDevice device : devices) {
+ if (getMediaDeviceById(mMediaDevices, device.getId()) == null) {
+ mMediaDevices.add(device);
+ }
+ }
addPhoneDeviceIfNecessary();
+ mCurrentConnectedDevice = updateCurrentConnectedDevice();
dispatchDeviceListUpdate();
}
@@ -226,6 +275,20 @@
public void onDeviceAttributesChanged() {
dispatchDeviceListUpdate();
}
+
+ @Override
+ public void onConnectedDeviceChanged(String id) {
+ final MediaDevice connectDevice = getMediaDeviceById(mMediaDevices, id);
+
+ if (connectDevice == mCurrentConnectedDevice) {
+ Log.d(TAG, "onConnectedDeviceChanged() this device all ready connected! : "
+ + connectDevice.getName());
+ return;
+ }
+ mCurrentConnectedDevice = connectDevice;
+
+ dispatchDeviceListUpdate();
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 33b621c..f35c30e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -41,7 +41,6 @@
private int mConnectedRecord;
- protected boolean mIsConnected = false;
protected Context mContext;
protected int mType;
@@ -57,15 +56,6 @@
}
/**
- * Check the MediaDevice is be connected to transfer.
- *
- * @return true if the MediaDevice is be connected to transfer, false otherwise.
- */
- public boolean isConnected() {
- return mIsConnected;
- }
-
- /**
* Get name from MediaDevice.
*
* @return name of MediaDevice.
@@ -87,8 +77,12 @@
/**
* Transfer MediaDevice for media
+ *
+ * @return result of transfer media
*/
- public void connect() {
+ public abstract boolean connect();
+
+ void setConnectedRecord() {
mConnectedRecord++;
ConnectionRecordManager.getInstance().setConnectionRecord(mContext, getId(),
mConnectedRecord);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
index 72b6b09..2c3a96c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
@@ -96,7 +96,7 @@
protected void dispatchDeviceListAdded() {
synchronized (mCallbacks) {
for (MediaDeviceCallback callback : mCallbacks) {
- callback.onDeviceListAdded(mMediaDevices);
+ callback.onDeviceListAdded(new ArrayList<>(mMediaDevices));
}
}
}
@@ -109,10 +109,10 @@
}
}
- protected void dispatchDeviceAttributesChanged() {
+ protected void dispatchConnectedDeviceChanged(String id) {
synchronized (mCallbacks) {
for (MediaDeviceCallback callback : mCallbacks) {
- callback.onDeviceAttributesChanged();
+ callback.onConnectedDeviceChanged(id);
}
}
}
@@ -153,5 +153,12 @@
* Callback for notifying MediaDevice attributes is changed.
*/
void onDeviceAttributesChanged();
+
+ /**
+ * Callback for notifying connected MediaDevice is changed.
+ *
+ * @param id the id of MediaDevice
+ */
+ void onConnectedDeviceChanged(String id);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java
new file mode 100644
index 0000000..e600cb8
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java
@@ -0,0 +1,40 @@
+/*
+ * 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 com.android.settingslib.media;
+
+/**
+ * Class to access MediaOutput constants.
+ */
+public class MediaOutputSliceConstants {
+
+ /**
+ * Key for the Media output setting.
+ */
+ public static final String KEY_MEDIA_OUTPUT = "media_output";
+
+ /**
+ * Activity Action: Show a settings dialog containing {@link MediaDevice} to transfer media.
+ */
+ public static final String ACTION_MEDIA_OUTPUT =
+ "com.android.settings.panel.action.MEDIA_OUTPUT";
+
+ /**
+ * An string extra specifying a media package name.
+ */
+ public static final String EXTRA_PACKAGE_NAME =
+ "com.android.settings.panel.extra.PACKAGE_NAME";
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index e0f3c2f..c808214 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -62,20 +62,22 @@
}
@Override
- public void connect() {
+ public boolean connect() {
final HearingAidProfile hapProfile = mProfileManager.getHearingAidProfile();
final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile();
+ boolean isConnected = false;
+
if (hapProfile != null && a2dpProfile != null) {
- mIsConnected = hapProfile.setActiveDevice(null) && a2dpProfile.setActiveDevice(null);
- super.connect();
+ isConnected = hapProfile.setActiveDevice(null) && a2dpProfile.setActiveDevice(null);
+ setConnectedRecord();
}
- Log.d(TAG, "connect() device : " + getName() + ", is selected : " + mIsConnected);
+ Log.d(TAG, "connect() device : " + getName() + ", is selected : " + isConnected);
+ return isConnected;
}
@Override
public void disconnect() {
//TODO(b/117129183): disconnected last select device
- mIsConnected = false;
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 27dc628..9b12a31 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -622,7 +622,7 @@
.append(KEY_PREFIX_FQDN)
.append(config.FQDN).toString();
} else {
- return getKey(config.SSID, config.BSSID, getSecurity(config));
+ return getKey(removeDoubleQuotes(config.SSID), config.BSSID, getSecurity(config));
}
}
@@ -1555,7 +1555,7 @@
mOsuFailure = mContext.getString(
R.string.osu_failure_provisioning_not_available);
break;
- case OSU_FAILURE_INVALID_SERVER_URL:
+ case OSU_FAILURE_INVALID_URL_FORMAT_FOR_OSU:
mOsuFailure = mContext.getString(R.string.osu_failure_invalid_server_url);
break;
case OSU_FAILURE_UNEXPECTED_COMMAND_TYPE:
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java
new file mode 100644
index 0000000..e5d1b18
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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 com.android.settingslib.media;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class BluetoothMediaDeviceTest {
+
+ @Mock
+ private CachedBluetoothDevice mDevice;
+
+ private Context mContext;
+ private BluetoothMediaDevice mBluetoothMediaDevice;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+
+ when(mDevice.isActiveDevice(BluetoothProfile.A2DP)).thenReturn(true);
+ when(mDevice.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
+
+ mBluetoothMediaDevice = new BluetoothMediaDevice(mContext, mDevice);
+ }
+
+ @Test
+ public void connect_setActiveSuccess_isConnectedReturnTrue() {
+ when(mDevice.setActive()).thenReturn(true);
+
+ assertThat(mBluetoothMediaDevice.connect()).isTrue();
+ }
+
+ @Test
+ public void connect_setActiveFail_isConnectedReturnFalse() {
+ when(mDevice.setActive()).thenReturn(false);
+
+ assertThat(mBluetoothMediaDevice.connect()).isFalse();
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaManagerTest.java
new file mode 100644
index 0000000..a20e22b
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaManagerTest.java
@@ -0,0 +1,416 @@
+/*
+ * 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 com.android.settingslib.media;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+
+import com.android.settingslib.bluetooth.A2dpProfile;
+import com.android.settingslib.bluetooth.BluetoothEventManager;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.HearingAidProfile;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public class BluetoothMediaManagerTest {
+
+ private static final String TEST_ADDRESS = "11:22:33:44:55:66";
+
+ @Mock
+ private LocalBluetoothManager mLocalBluetoothManager;
+ @Mock
+ private LocalBluetoothProfileManager mProfileManager;
+ @Mock
+ private A2dpProfile mA2dpProfile;
+ @Mock
+ private HearingAidProfile mHapProfile;
+ @Mock
+ private CachedBluetoothDeviceManager mCachedDeviceManager;
+ @Mock
+ private BluetoothEventManager mEventManager;
+ @Mock
+ private MediaManager.MediaDeviceCallback mCallback;
+
+ private BluetoothMediaManager mMediaManager;
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+
+ when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
+ when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
+ when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
+ when(mProfileManager.getHearingAidProfile()).thenReturn(mHapProfile);
+ when(mLocalBluetoothManager.getEventManager()).thenReturn(mEventManager);
+
+ mMediaManager = new BluetoothMediaManager(mContext, mLocalBluetoothManager, null);
+ }
+
+ @Test
+ public void startScan_haveA2dpProfileConnectedBluetoothDevice_shouldAddDevice() {
+ final List<BluetoothDevice> devices = new ArrayList<>();
+ final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
+ final BluetoothDevice bluetoothDevice = mock(BluetoothDevice.class);
+ devices.add(bluetoothDevice);
+
+ when(mA2dpProfile.getConnectedDevices()).thenReturn(devices);
+ when(mCachedDeviceManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice);
+ when(cachedDevice.isConnected()).thenReturn(true);
+
+ assertThat(mMediaManager.mMediaDevices).isEmpty();
+ mMediaManager.startScan();
+ assertThat(mMediaManager.mMediaDevices).hasSize(devices.size());
+ }
+
+ @Test
+ public void startScan_haveA2dpProfileDisconnectedBluetoothDevice_shouldNotAddDevice() {
+ final List<BluetoothDevice> devices = new ArrayList<>();
+ final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
+ final BluetoothDevice bluetoothDevice = mock(BluetoothDevice.class);
+ devices.add(bluetoothDevice);
+
+ when(mA2dpProfile.getConnectedDevices()).thenReturn(devices);
+ when(mCachedDeviceManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice);
+ when(cachedDevice.isConnected()).thenReturn(false);
+
+ assertThat(mMediaManager.mMediaDevices).isEmpty();
+ mMediaManager.startScan();
+ assertThat(mMediaManager.mMediaDevices).isEmpty();
+ }
+
+ @Test
+ public void startScan_noA2dpProfileBluetoothDevice_shouldNotAddDevice() {
+ final List<BluetoothDevice> devices = new ArrayList<>();
+
+ when(mA2dpProfile.getConnectedDevices()).thenReturn(devices);
+
+ assertThat(mMediaManager.mMediaDevices).isEmpty();
+ mMediaManager.startScan();
+ assertThat(mMediaManager.mMediaDevices).isEmpty();
+ }
+
+ @Test
+ public void startScan_haveHapProfileConnectedBluetoothDevice_shouldAddDevice() {
+ final List<BluetoothDevice> devices = new ArrayList<>();
+ final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
+ final BluetoothDevice bluetoothDevice = mock(BluetoothDevice.class);
+ devices.add(bluetoothDevice);
+
+ when(mHapProfile.getConnectedDevices()).thenReturn(devices);
+ when(mCachedDeviceManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice);
+ when(cachedDevice.isConnected()).thenReturn(true);
+
+ assertThat(mMediaManager.mMediaDevices).isEmpty();
+ mMediaManager.startScan();
+ assertThat(mMediaManager.mMediaDevices).hasSize(devices.size());
+ }
+
+ @Test
+ public void startScan_noHapProfileBluetoothDevice_shouldNotAddDevice() {
+ final List<BluetoothDevice> devices = new ArrayList<>();
+
+ when(mHapProfile.getConnectedDevices()).thenReturn(devices);
+
+ assertThat(mMediaManager.mMediaDevices).isEmpty();
+ mMediaManager.startScan();
+ assertThat(mMediaManager.mMediaDevices).isEmpty();
+ }
+
+ @Test
+ public void starScan_a2dpAndHapProfileNotReady_shouldRegisterCallback() {
+ final Collection<CachedBluetoothDevice> mDevices = new ArrayList<>();
+ final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
+ mDevices.add(cachedDevice);
+
+ when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices);
+ when(mA2dpProfile.isProfileReady()).thenReturn(false);
+ when(mHapProfile.isProfileReady()).thenReturn(false);
+
+ mMediaManager.startScan();
+
+ verify(mProfileManager).addServiceListener(mMediaManager);
+ }
+
+ @Test
+ public void starScan_a2dpAndHapProfileReady_shouldNotRegisterCallback() {
+ final Collection<CachedBluetoothDevice> mDevices = new ArrayList<>();
+ final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
+ mDevices.add(cachedDevice);
+
+ when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices);
+ when(mA2dpProfile.isProfileReady()).thenReturn(true);
+ when(mHapProfile.isProfileReady()).thenReturn(true);
+
+ mMediaManager.startScan();
+
+ verify(mProfileManager, never()).addServiceListener(mMediaManager);
+ }
+
+ @Test
+ public void onServiceConnected_a2dpAndHapProfileNotReady_doNothing() {
+ final Collection<CachedBluetoothDevice> mDevices = new ArrayList<>();
+ final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
+ mDevices.add(cachedDevice);
+
+ when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices);
+ when(mA2dpProfile.isProfileReady()).thenReturn(false);
+ when(mHapProfile.isProfileReady()).thenReturn(false);
+
+ mMediaManager.startScan();
+ mMediaManager.onServiceConnected();
+
+ verify(mProfileManager, never()).removeServiceListener(mMediaManager);
+ }
+
+ @Test
+ public void onDeviceAttributesChanged_a2dpAndHapProfileReady_shouldUnregisterCallback() {
+ final Collection<CachedBluetoothDevice> mDevices = new ArrayList<>();
+ final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
+ mDevices.add(cachedDevice);
+
+ when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices);
+ when(mA2dpProfile.isProfileReady()).thenReturn(true);
+ when(mHapProfile.isProfileReady()).thenReturn(true);
+
+ mMediaManager.startScan();
+ mMediaManager.onServiceConnected();
+
+ verify(mProfileManager).removeServiceListener(mMediaManager);
+ }
+
+ @Test
+ public void onBluetoothStateChanged_bluetoothStateIsOn_callOnDeviceListAdded() {
+ mMediaManager.registerCallback(mCallback);
+ mMediaManager.onBluetoothStateChanged(BluetoothAdapter.STATE_ON);
+
+ verify(mCallback).onDeviceListAdded(any());
+ }
+
+ @Test
+ public void onBluetoothStateChanged_bluetoothStateIsOff_callOnDeviceListRemoved() {
+ final BluetoothMediaDevice device1 = mock(BluetoothMediaDevice.class);
+ final BluetoothMediaDevice device2 = mock(BluetoothMediaDevice.class);
+ mMediaManager.mMediaDevices.add(device1);
+ mMediaManager.mMediaDevices.add(device2);
+
+ mMediaManager.registerCallback(mCallback);
+ mMediaManager.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);
+
+ assertThat(mMediaManager.mMediaDevices).isEmpty();
+ verify(mCallback).onDeviceListRemoved(any());
+ }
+
+ @Test
+ public void onDeviceAdded_cachedDeviceIsConnected_callOnDeviceAdded() {
+ final CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+
+ when(device.isConnectedHearingAidDevice()).thenReturn(true);
+ when(device.isConnectedA2dpDevice()).thenReturn(true);
+
+ assertThat(mMediaManager.mMediaDevices).isEmpty();
+ mMediaManager.registerCallback(mCallback);
+ mMediaManager.onDeviceAdded(device);
+
+ assertThat(mMediaManager.mMediaDevices).hasSize(1);
+ verify(mCallback).onDeviceAdded(any());
+
+ }
+
+ @Test
+ public void onDeviceAdded_cachedDeviceIsDisconnected_doNothing() {
+ final CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+
+ when(device.isConnectedHearingAidDevice()).thenReturn(false);
+ when(device.isConnectedA2dpDevice()).thenReturn(false);
+
+ assertThat(mMediaManager.mMediaDevices).isEmpty();
+ mMediaManager.registerCallback(mCallback);
+ mMediaManager.onDeviceAdded(device);
+
+ assertThat(mMediaManager.mMediaDevices).isEmpty();
+ verify(mCallback, never()).onDeviceAdded(any());
+
+ }
+
+ @Test
+ public void onDeviceDeleted_cachedDeviceIsConnected_doNothing() {
+ final CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+ final BluetoothMediaDevice bluetoothMediaDevice = mock(BluetoothMediaDevice.class);
+ mMediaManager.mMediaDevices.add(bluetoothMediaDevice);
+
+ when(device.isConnectedHearingAidDevice()).thenReturn(true);
+ when(device.isConnectedA2dpDevice()).thenReturn(true);
+ when(device.getAddress()).thenReturn(TEST_ADDRESS);
+ when(bluetoothMediaDevice.getId()).thenReturn(TEST_ADDRESS);
+
+ assertThat(mMediaManager.mMediaDevices).hasSize(1);
+ mMediaManager.registerCallback(mCallback);
+ mMediaManager.onDeviceDeleted(device);
+
+ assertThat(mMediaManager.mMediaDevices).hasSize(1);
+ verify(mCallback, never()).onDeviceRemoved(any());
+ }
+
+ @Test
+ public void onDeviceDeleted_cachedDeviceIsDisconnected_callOnDeviceRemoved() {
+ final CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+ final BluetoothMediaDevice bluetoothMediaDevice = mock(BluetoothMediaDevice.class);
+ mMediaManager.mMediaDevices.add(bluetoothMediaDevice);
+
+ when(device.isConnectedHearingAidDevice()).thenReturn(false);
+ when(device.isConnectedA2dpDevice()).thenReturn(false);
+ when(device.getAddress()).thenReturn(TEST_ADDRESS);
+ when(bluetoothMediaDevice.getId()).thenReturn(TEST_ADDRESS);
+
+ assertThat(mMediaManager.mMediaDevices).hasSize(1);
+ mMediaManager.registerCallback(mCallback);
+ mMediaManager.onDeviceDeleted(device);
+
+ assertThat(mMediaManager.mMediaDevices).isEmpty();
+ verify(mCallback).onDeviceRemoved(any());
+ }
+
+ @Test
+ public void onProfileConnectionStateChanged_cachedDeviceIsConnect_callOnDeviceAdded() {
+ final CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+
+ when(device.isConnectedHearingAidDevice()).thenReturn(true);
+ when(device.isConnectedA2dpDevice()).thenReturn(true);
+
+ assertThat(mMediaManager.mMediaDevices).isEmpty();
+ mMediaManager.registerCallback(mCallback);
+ mMediaManager.onProfileConnectionStateChanged(device, 0, 0);
+
+ assertThat(mMediaManager.mMediaDevices).hasSize(1);
+ verify(mCallback).onDeviceAdded(any());
+ }
+
+ @Test
+ public void onProfileConnectionStateChanged_cachedDeviceIsDisconnect_callOnDeviceRemoved() {
+ final CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+ final BluetoothMediaDevice bluetoothMediaDevice = mock(BluetoothMediaDevice.class);
+ mMediaManager.mMediaDevices.add(bluetoothMediaDevice);
+
+ when(device.isConnectedHearingAidDevice()).thenReturn(false);
+ when(device.isConnectedA2dpDevice()).thenReturn(false);
+ when(device.getAddress()).thenReturn(TEST_ADDRESS);
+ when(bluetoothMediaDevice.getId()).thenReturn(TEST_ADDRESS);
+
+ assertThat(mMediaManager.mMediaDevices).hasSize(1);
+ mMediaManager.registerCallback(mCallback);
+ mMediaManager.onProfileConnectionStateChanged(device, 0, 0);
+
+ assertThat(mMediaManager.mMediaDevices).isEmpty();
+ verify(mCallback).onDeviceRemoved(any());
+ }
+
+ @Test
+ public void onAclConnectionStateChanged_cachedDeviceIsConnect_callOnDeviceAdded() {
+ final CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+
+ when(device.isConnectedHearingAidDevice()).thenReturn(true);
+ when(device.isConnectedA2dpDevice()).thenReturn(true);
+
+ assertThat(mMediaManager.mMediaDevices).isEmpty();
+ mMediaManager.registerCallback(mCallback);
+ mMediaManager.onAclConnectionStateChanged(device, 0);
+
+ assertThat(mMediaManager.mMediaDevices).hasSize(1);
+ verify(mCallback).onDeviceAdded(any());
+ }
+
+ @Test
+ public void onAclConnectionStateChanged_cachedDeviceIsDisconnect_callOnDeviceRemoved() {
+ final CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+ final BluetoothMediaDevice bluetoothMediaDevice = mock(BluetoothMediaDevice.class);
+ mMediaManager.mMediaDevices.add(bluetoothMediaDevice);
+
+ when(device.isConnectedHearingAidDevice()).thenReturn(false);
+ when(device.isConnectedA2dpDevice()).thenReturn(false);
+ when(device.getAddress()).thenReturn(TEST_ADDRESS);
+ when(bluetoothMediaDevice.getId()).thenReturn(TEST_ADDRESS);
+
+ assertThat(mMediaManager.mMediaDevices).hasSize(1);
+ mMediaManager.registerCallback(mCallback);
+ mMediaManager.onAclConnectionStateChanged(device, 0);
+
+ assertThat(mMediaManager.mMediaDevices).isEmpty();
+ verify(mCallback).onDeviceRemoved(any());
+ }
+
+ @Test
+ public void onActiveDeviceChanged_isHapProfile_callOnActiveDeviceChanged() {
+ final CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+
+ when(device.getAddress()).thenReturn(TEST_ADDRESS);
+
+ mMediaManager.registerCallback(mCallback);
+ mMediaManager.onActiveDeviceChanged(device, BluetoothProfile.HEARING_AID);
+
+ verify(mCallback).onConnectedDeviceChanged(any());
+ }
+
+ @Test
+ public void onActiveDeviceChanged_isA2dpProfile_callOnActiveDeviceChanged() {
+ final CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+
+ when(device.getAddress()).thenReturn(TEST_ADDRESS);
+
+ mMediaManager.registerCallback(mCallback);
+ mMediaManager.onActiveDeviceChanged(device, BluetoothProfile.A2DP);
+
+ verify(mCallback).onConnectedDeviceChanged(any());
+ }
+
+ @Test
+ public void onActiveDeviceChanged_isNotA2dpAndHapProfile_doNothing() {
+ final CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+
+ when(device.getAddress()).thenReturn(TEST_ADDRESS);
+
+ mMediaManager.registerCallback(mCallback);
+ mMediaManager.onActiveDeviceChanged(device, BluetoothProfile.HEALTH);
+
+ verify(mCallback, never()).onConnectedDeviceChanged(any());
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
new file mode 100644
index 0000000..b11cf69
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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 com.android.settingslib.media;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import androidx.mediarouter.media.MediaRouteSelector;
+import androidx.mediarouter.media.MediaRouter;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class InfoMediaManagerTest {
+
+ private static final String TEST_PACKAGE_NAME = "com.test.packagename";
+ private static final String TEST_ID = "test_id";
+
+ @Mock
+ private MediaRouter mMediaRouter;
+ @Mock
+ private MediaRouteSelector mSelector;
+
+ private InfoMediaManager mInfoMediaManager;
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+
+ mInfoMediaManager = new InfoMediaManager(mContext, TEST_PACKAGE_NAME, null);
+ mInfoMediaManager.mMediaRouter = mMediaRouter;
+ mInfoMediaManager.mSelector = mSelector;
+ }
+
+ @Test
+ public void stopScan_shouldRemoveCallback() {
+ mInfoMediaManager.stopScan();
+
+ verify(mMediaRouter).removeCallback(mInfoMediaManager.mMediaRouterCallback);
+ }
+
+ @Test
+ public void startScan_shouldAddCallback() {
+ mInfoMediaManager.startScan();
+
+ verify(mMediaRouter).addCallback(mSelector, mInfoMediaManager.mMediaRouterCallback,
+ MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
+ }
+
+ @Test
+ public void onRouteAdded_mediaDeviceNotExistInList_addMediaDevice() {
+ final MediaRouter.RouteInfo info = mock(MediaRouter.RouteInfo.class);
+ when(info.getId()).thenReturn(TEST_ID);
+
+ final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
+ assertThat(mediaDevice).isNull();
+
+ mInfoMediaManager.mMediaRouterCallback.onRouteAdded(mMediaRouter, info);
+
+ final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
+ assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
+ }
+
+ @Test
+ public void onRouteAdded_mediaDeviceExistInList_doNothing() {
+ final MediaRouter.RouteInfo info = mock(MediaRouter.RouteInfo.class);
+ when(info.getId()).thenReturn(TEST_ID);
+ final InfoMediaDevice infoDevice = new InfoMediaDevice(mContext, info);
+ mInfoMediaManager.mMediaDevices.add(infoDevice);
+
+ final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
+ final int size = mInfoMediaManager.mMediaDevices.size();
+ assertThat(mediaDevice).isNotNull();
+
+ mInfoMediaManager.mMediaRouterCallback.onRouteAdded(mMediaRouter, info);
+
+ assertThat(mInfoMediaManager.mMediaDevices).hasSize(size);
+ }
+
+ @Test
+ public void onRouteRemoved_mediaDeviceExistInList_removeMediaDevice() {
+ final MediaRouter.RouteInfo info = mock(MediaRouter.RouteInfo.class);
+ when(info.getId()).thenReturn(TEST_ID);
+ final InfoMediaDevice infoDevice = new InfoMediaDevice(mContext, info);
+ mInfoMediaManager.mMediaDevices.add(infoDevice);
+
+ final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
+ assertThat(mediaDevice).isNotNull();
+ assertThat(mInfoMediaManager.mMediaDevices).hasSize(1);
+
+ mInfoMediaManager.mMediaRouterCallback.onRouteRemoved(mMediaRouter, info);
+
+ assertThat(mInfoMediaManager.mMediaDevices).isEmpty();
+ }
+
+ @Test
+ public void onRouteRemoved_mediaDeviceNotExistInList_doNothing() {
+ final MediaRouter.RouteInfo info = mock(MediaRouter.RouteInfo.class);
+ when(info.getId()).thenReturn(TEST_ID);
+
+ final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
+ final int size = mInfoMediaManager.mMediaDevices.size();
+ assertThat(mediaDevice).isNull();
+
+ mInfoMediaManager.mMediaRouterCallback.onRouteRemoved(mMediaRouter, info);
+
+ assertThat(mInfoMediaManager.mMediaDevices).hasSize(size);
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
new file mode 100644
index 0000000..3556814
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -0,0 +1,369 @@
+/*
+ * 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 com.android.settingslib.media;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import com.android.settingslib.bluetooth.A2dpProfile;
+import com.android.settingslib.bluetooth.HearingAidProfile;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public class LocalMediaManagerTest {
+
+ private static final String TEST_DEVICE_ID_1 = "device_id_1";
+ private static final String TEST_DEVICE_ID_2 = "device_id_2";
+ private static final String TEST_DEVICE_ID_3 = "device_id_3";
+ private static final String TEST_CURRENT_DEVICE_ID = "currentDevice_id";
+
+ @Mock
+ private BluetoothMediaManager mBluetoothMediaManager;
+ @Mock
+ private InfoMediaManager mInfoMediaManager;
+ @Mock
+ private LocalBluetoothManager mLocalBluetoothManager;
+ @Mock
+ private LocalMediaManager.DeviceCallback mCallback;
+ @Mock
+ private HearingAidProfile mHapProfile;
+ @Mock
+ private A2dpProfile mA2dpProfile;
+ @Mock
+ private LocalBluetoothProfileManager mLocalProfileManager;
+
+ private Context mContext;
+ private LocalMediaManager mLocalMediaManager;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+
+ when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalProfileManager);
+ when(mLocalProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
+ when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHapProfile);
+
+ mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager,
+ mBluetoothMediaManager, mInfoMediaManager);
+ }
+
+ @Test
+ public void startScan_mediaDevicesListShouldBeClear() {
+ final MediaDevice device = mock(MediaDevice.class);
+ mLocalMediaManager.mMediaDevices.add(device);
+
+ assertThat(mLocalMediaManager.mMediaDevices).hasSize(1);
+ mLocalMediaManager.startScan();
+ assertThat(mLocalMediaManager.mMediaDevices).isEmpty();
+ }
+
+ @Test
+ public void connectDevice_deviceNotEqualCurrentConnectedDevice_connectDevice() {
+ final MediaDevice currentDevice = mock(MediaDevice.class);
+ final MediaDevice device = mock(MediaDevice.class);
+ mLocalMediaManager.mMediaDevices.add(currentDevice);
+ mLocalMediaManager.mMediaDevices.add(device);
+ mLocalMediaManager.mCurrentConnectedDevice = currentDevice;
+
+ when(device.getId()).thenReturn(TEST_DEVICE_ID_1);
+ when(currentDevice.getId()).thenReturn(TEST_CURRENT_DEVICE_ID);
+
+ mLocalMediaManager.registerCallback(mCallback);
+ mLocalMediaManager.connectDevice(device);
+
+ verify(currentDevice).disconnect();
+ verify(device).connect();
+ verify(mCallback).onSelectedDeviceStateChanged(any(),
+ eq(LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED));
+ }
+
+ @Test
+ public void getMediaDeviceById_idExist_shouldReturnMediaDevice() {
+ final MediaDevice device1 = mock(MediaDevice.class);
+ final MediaDevice device2 = mock(MediaDevice.class);
+ mLocalMediaManager.mMediaDevices.add(device1);
+ mLocalMediaManager.mMediaDevices.add(device2);
+
+ when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
+ when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
+
+ final MediaDevice device = mLocalMediaManager
+ .getMediaDeviceById(mLocalMediaManager.mMediaDevices, TEST_DEVICE_ID_2);
+
+ assertThat(device.getId()).isEqualTo(TEST_DEVICE_ID_2);
+ }
+
+ @Test
+ public void getMediaDeviceById_idNotExist_shouldReturnNull() {
+ final MediaDevice device1 = mock(MediaDevice.class);
+ final MediaDevice device2 = mock(MediaDevice.class);
+ mLocalMediaManager.mMediaDevices.add(device1);
+ mLocalMediaManager.mMediaDevices.add(device2);
+
+ when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
+ when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
+
+ final MediaDevice device = mLocalMediaManager
+ .getMediaDeviceById(mLocalMediaManager.mMediaDevices, TEST_CURRENT_DEVICE_ID);
+
+ assertThat(device).isNull();
+ }
+
+ @Test
+ public void onDeviceAdded_mediaDeviceAndPhoneDeviceNotExistInList_addBothDevice() {
+ final MediaDevice device = mock(MediaDevice.class);
+
+ assertThat(mLocalMediaManager.mMediaDevices).isEmpty();
+ mLocalMediaManager.registerCallback(mCallback);
+ mLocalMediaManager.mMediaDeviceCallback.onDeviceAdded(device);
+
+ assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
+ verify(mCallback).onDeviceListUpdate(any());
+ }
+
+ @Test
+ public void onDeviceAdded_mediaDeviceNotExistAndPhoneDeviceExistInList_addMediaDevice() {
+ final MediaDevice device1 = mock(MediaDevice.class);
+ final MediaDevice device2 = mock(MediaDevice.class);
+ mLocalMediaManager.mPhoneDevice = mock(PhoneMediaDevice.class);
+ mLocalMediaManager.mMediaDevices.add(device1);
+ mLocalMediaManager.mMediaDevices.add(mLocalMediaManager.mPhoneDevice);
+
+ assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
+ mLocalMediaManager.registerCallback(mCallback);
+ mLocalMediaManager.mMediaDeviceCallback.onDeviceAdded(device2);
+
+ assertThat(mLocalMediaManager.mMediaDevices).hasSize(3);
+ verify(mCallback).onDeviceListUpdate(any());
+ }
+
+ @Test
+ public void onDeviceAdded_mediaDeviceAndPhoneDeviceExistInList_doNothing() {
+ final MediaDevice device1 = mock(MediaDevice.class);
+ mLocalMediaManager.mPhoneDevice = mock(PhoneMediaDevice.class);
+ mLocalMediaManager.mMediaDevices.add(device1);
+ mLocalMediaManager.mMediaDevices.add(mLocalMediaManager.mPhoneDevice);
+
+ assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
+ mLocalMediaManager.registerCallback(mCallback);
+ mLocalMediaManager.mMediaDeviceCallback.onDeviceAdded(device1);
+
+ assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
+ verify(mCallback, never()).onDeviceListUpdate(any());
+ }
+
+ @Test
+ public void onDeviceListAdded_phoneDeviceNotExistInList_addPhoneDeviceAndDevicesList() {
+ final List<MediaDevice> devices = new ArrayList<>();
+ final MediaDevice device1 = mock(MediaDevice.class);
+ final MediaDevice device2 = mock(MediaDevice.class);
+ devices.add(device1);
+ devices.add(device2);
+
+ when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
+ when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
+
+ assertThat(mLocalMediaManager.mMediaDevices).isEmpty();
+ mLocalMediaManager.registerCallback(mCallback);
+ mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices);
+
+ assertThat(mLocalMediaManager.mMediaDevices).hasSize(3);
+ verify(mCallback).onDeviceListUpdate(any());
+ }
+
+ @Test
+ public void onDeviceListAdded_phoneDeviceExistInList_addDeviceList() {
+ final List<MediaDevice> devices = new ArrayList<>();
+ final MediaDevice device1 = mock(MediaDevice.class);
+ final MediaDevice device2 = mock(MediaDevice.class);
+ final MediaDevice device3 = mock(MediaDevice.class);
+ mLocalMediaManager.mPhoneDevice = mock(PhoneMediaDevice.class);
+ devices.add(device1);
+ devices.add(device2);
+ mLocalMediaManager.mMediaDevices.add(device3);
+ mLocalMediaManager.mMediaDevices.add(mLocalMediaManager.mPhoneDevice);
+
+ when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
+ when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
+ when(device3.getId()).thenReturn(TEST_DEVICE_ID_3);
+ when(mLocalMediaManager.mPhoneDevice.getId()).thenReturn("test_phone_id");
+
+ assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
+ mLocalMediaManager.registerCallback(mCallback);
+ mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices);
+
+ assertThat(mLocalMediaManager.mMediaDevices).hasSize(4);
+ verify(mCallback).onDeviceListUpdate(any());
+ }
+
+ @Test
+ public void onDeviceRemoved_phoneDeviceIsLastDeviceAfterRemoveMediaDevice_removeBothDevice() {
+ final MediaDevice device1 = mock(MediaDevice.class);
+ mLocalMediaManager.mPhoneDevice = mock(PhoneMediaDevice.class);
+ mLocalMediaManager.mMediaDevices.add(device1);
+ mLocalMediaManager.mMediaDevices.add(mLocalMediaManager.mPhoneDevice);
+
+ assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
+ mLocalMediaManager.registerCallback(mCallback);
+ mLocalMediaManager.mMediaDeviceCallback.onDeviceRemoved(device1);
+
+ assertThat(mLocalMediaManager.mMediaDevices).isEmpty();
+ verify(mCallback).onDeviceListUpdate(any());
+ }
+
+ @Test
+ public void onDeviceRemoved_phoneDeviceNotLastDeviceAfterRemoveMediaDevice_removeMediaDevice() {
+ final MediaDevice device1 = mock(MediaDevice.class);
+ final MediaDevice device2 = mock(MediaDevice.class);
+ mLocalMediaManager.mPhoneDevice = mock(PhoneMediaDevice.class);
+ mLocalMediaManager.mMediaDevices.add(device1);
+ mLocalMediaManager.mMediaDevices.add(device2);
+ mLocalMediaManager.mMediaDevices.add(mLocalMediaManager.mPhoneDevice);
+
+ assertThat(mLocalMediaManager.mMediaDevices).hasSize(3);
+ mLocalMediaManager.registerCallback(mCallback);
+ mLocalMediaManager.mMediaDeviceCallback.onDeviceRemoved(device2);
+
+ assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
+ verify(mCallback).onDeviceListUpdate(any());
+ }
+
+ @Test
+ public void onDeviceRemoved_removeMediaDeviceNotInList_doNothing() {
+ final MediaDevice device1 = mock(MediaDevice.class);
+ final MediaDevice device2 = mock(MediaDevice.class);
+ mLocalMediaManager.mPhoneDevice = mock(PhoneMediaDevice.class);
+ mLocalMediaManager.mMediaDevices.add(device2);
+ mLocalMediaManager.mMediaDevices.add(mLocalMediaManager.mPhoneDevice);
+
+ assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
+ mLocalMediaManager.registerCallback(mCallback);
+ mLocalMediaManager.mMediaDeviceCallback.onDeviceRemoved(device1);
+
+ assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
+ verify(mCallback, never()).onDeviceListUpdate(any());
+ }
+
+ @Test
+ public void onDeviceListRemoved_phoneDeviceIsLastDeviceAfterRemoveDeviceList_removeAll() {
+ final List<MediaDevice> devices = new ArrayList<>();
+ final MediaDevice device1 = mock(MediaDevice.class);
+ final MediaDevice device2 = mock(MediaDevice.class);
+ mLocalMediaManager.mPhoneDevice = mock(PhoneMediaDevice.class);
+ devices.add(device1);
+ devices.add(device2);
+ mLocalMediaManager.mMediaDevices.add(device1);
+ mLocalMediaManager.mMediaDevices.add(device2);
+ mLocalMediaManager.mMediaDevices.add(mLocalMediaManager.mPhoneDevice);
+
+ assertThat(mLocalMediaManager.mMediaDevices).hasSize(3);
+ mLocalMediaManager.registerCallback(mCallback);
+ mLocalMediaManager.mMediaDeviceCallback.onDeviceListRemoved(devices);
+
+ assertThat(mLocalMediaManager.mMediaDevices).isEmpty();
+ verify(mCallback).onDeviceListUpdate(any());
+ }
+
+ @Test
+ public void onDeviceListRemoved_phoneDeviceNotLastDeviceAfterRemoveDeviceList_removeList() {
+ final List<MediaDevice> devices = new ArrayList<>();
+ final MediaDevice device1 = mock(MediaDevice.class);
+ final MediaDevice device2 = mock(MediaDevice.class);
+ final MediaDevice device3 = mock(MediaDevice.class);
+ mLocalMediaManager.mPhoneDevice = mock(PhoneMediaDevice.class);
+ devices.add(device1);
+ devices.add(device3);
+ mLocalMediaManager.mMediaDevices.add(device1);
+ mLocalMediaManager.mMediaDevices.add(device2);
+ mLocalMediaManager.mMediaDevices.add(device3);
+ mLocalMediaManager.mMediaDevices.add(mLocalMediaManager.mPhoneDevice);
+
+ assertThat(mLocalMediaManager.mMediaDevices).hasSize(4);
+ mLocalMediaManager.registerCallback(mCallback);
+ mLocalMediaManager.mMediaDeviceCallback.onDeviceListRemoved(devices);
+
+ assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
+ verify(mCallback).onDeviceListUpdate(any());
+ }
+
+ @Test
+ public void onDeviceAttributesChanged_shouldDispatchDeviceListUpdate() {
+ mLocalMediaManager.registerCallback(mCallback);
+
+ mLocalMediaManager.mMediaDeviceCallback.onDeviceAttributesChanged();
+
+ verify(mCallback).onDeviceListUpdate(any());
+ }
+
+ @Test
+ public void onConnectedDeviceChanged_connectedAndCurrentDeviceAreDifferent_notifyThemChanged() {
+ final MediaDevice device1 = mock(MediaDevice.class);
+ final MediaDevice device2 = mock(MediaDevice.class);
+
+ mLocalMediaManager.mMediaDevices.add(device1);
+ mLocalMediaManager.mMediaDevices.add(device2);
+ mLocalMediaManager.mCurrentConnectedDevice = device1;
+
+ when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
+ when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
+
+ mLocalMediaManager.registerCallback(mCallback);
+ mLocalMediaManager.mMediaDeviceCallback.onConnectedDeviceChanged(TEST_DEVICE_ID_2);
+
+ assertThat(mLocalMediaManager.getCurrentConnectedDevice()).isEqualTo(device2);
+ verify(mCallback).onDeviceListUpdate(any());
+ }
+
+ @Test
+ public void onConnectedDeviceChanged_connectedAndCurrentDeviceAreSame_doNothing() {
+ final MediaDevice device1 = mock(MediaDevice.class);
+ final MediaDevice device2 = mock(MediaDevice.class);
+
+ mLocalMediaManager.mMediaDevices.add(device1);
+ mLocalMediaManager.mMediaDevices.add(device2);
+ mLocalMediaManager.mCurrentConnectedDevice = device1;
+
+ when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
+ when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
+
+ mLocalMediaManager.registerCallback(mCallback);
+ mLocalMediaManager.mMediaDeviceCallback.onConnectedDeviceChanged(TEST_DEVICE_ID_1);
+
+ verify(mCallback, never()).onDeviceListUpdate(any());
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
index 9bbdd01..fc514f0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
@@ -25,8 +25,11 @@
import androidx.mediarouter.media.MediaRouter;
+import com.android.settingslib.bluetooth.A2dpProfile;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.HearingAidProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import org.junit.Before;
import org.junit.Test;
@@ -78,6 +81,14 @@
private MediaRouter.RouteInfo mRouteInfo2;
@Mock
private MediaRouter.RouteInfo mRouteInfo3;
+ @Mock
+ private LocalBluetoothProfileManager mProfileManager;
+ @Mock
+ private HearingAidProfile mHapProfile;
+ @Mock
+ private A2dpProfile mA2dpProfile;
+ @Mock
+ private BluetoothDevice mDevice;
private BluetoothMediaDevice mBluetoothMediaDevice1;
private BluetoothMediaDevice mBluetoothMediaDevice2;
@@ -109,6 +120,10 @@
when(mRouteInfo1.getName()).thenReturn(DEVICE_NAME_1);
when(mRouteInfo2.getName()).thenReturn(DEVICE_NAME_2);
when(mRouteInfo3.getName()).thenReturn(DEVICE_NAME_3);
+ when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
+ when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
+ when(mProfileManager.getHearingAidProfile()).thenReturn(mHapProfile);
+ when(mA2dpProfile.getActiveDevice()).thenReturn(mDevice);
mBluetoothMediaDevice1 = new BluetoothMediaDevice(mContext, mCachedDevice1);
mBluetoothMediaDevice2 = new BluetoothMediaDevice(mContext, mCachedDevice2);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceUtilsTest.java
new file mode 100644
index 0000000..6e29e13
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceUtilsTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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 com.android.settingslib.media;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import androidx.mediarouter.media.MediaRouter;
+
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class MediaDeviceUtilsTest {
+
+ private static final String TEST_ADDRESS = "11:22:33:44:55:66";
+ private static final String TEST_ROUTE_ID = "test_route_id";
+
+ @Mock
+ private CachedBluetoothDevice mDevice;
+ @Mock
+ private MediaRouter.RouteInfo mRouteInfo;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void getId_returnBluetoothDeviceAddress() {
+ when(mDevice.getAddress()).thenReturn(TEST_ADDRESS);
+
+ final String id = MediaDeviceUtils.getId(mDevice);
+
+ assertThat(id).isEqualTo(TEST_ADDRESS);
+ }
+
+ @Test
+ public void getId_returnRouteInfoId() {
+ when(mRouteInfo.getId()).thenReturn(TEST_ROUTE_ID);
+
+ final String id = MediaDeviceUtils.getId(mRouteInfo);
+
+ assertThat(id).isEqualTo(TEST_ROUTE_ID);
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java
new file mode 100644
index 0000000..98eccb5
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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 com.android.settingslib.media;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class MediaManagerTest {
+
+ private static final String TEST_ID = "test_id";
+
+ @Mock
+ private MediaManager.MediaDeviceCallback mCallback;
+ @Mock
+ private MediaDevice mDevice;
+
+ private MediaManager mMediaManager;
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+
+ when(mDevice.getId()).thenReturn(TEST_ID);
+
+ mMediaManager = new MediaManager(mContext, null) {
+ @Override
+ public void startScan() {
+
+ }
+
+ @Override
+ public void stopScan() {
+
+ }
+ };
+ }
+
+ @Test
+ public void dispatchDeviceAdded_registerCallback_shouldDispatchCallback() {
+ mMediaManager.registerCallback(mCallback);
+
+ mMediaManager.dispatchDeviceAdded(mDevice);
+
+ verify(mCallback).onDeviceAdded(mDevice);
+ }
+
+ @Test
+ public void dispatchDeviceRemoved_registerCallback_shouldDispatchCallback() {
+ mMediaManager.registerCallback(mCallback);
+
+ mMediaManager.dispatchDeviceRemoved(mDevice);
+
+ verify(mCallback).onDeviceRemoved(mDevice);
+ }
+
+ @Test
+ public void dispatchDeviceListAdded_registerCallback_shouldDispatchCallback() {
+ mMediaManager.registerCallback(mCallback);
+
+ mMediaManager.dispatchDeviceListAdded();
+
+ verify(mCallback).onDeviceListAdded(any());
+ }
+
+ @Test
+ public void dispatchDeviceListRemoved_registerCallback_shouldDispatchCallback() {
+ mMediaManager.registerCallback(mCallback);
+
+ mMediaManager.dispatchDeviceListRemoved(mMediaManager.mMediaDevices);
+
+ verify(mCallback).onDeviceListRemoved(mMediaManager.mMediaDevices);
+ }
+
+ @Test
+ public void dispatchActiveDeviceChanged_registerCallback_shouldDispatchCallback() {
+ mMediaManager.registerCallback(mCallback);
+
+ mMediaManager.dispatchConnectedDeviceChanged(TEST_ID);
+
+ verify(mCallback).onConnectedDeviceChanged(TEST_ID);
+ }
+
+ @Test
+ public void findMediaDevice_idExist_shouldReturnMediaDevice() {
+ mMediaManager.mMediaDevices.add(mDevice);
+
+ final MediaDevice device = mMediaManager.findMediaDevice(TEST_ID);
+
+ assertThat(device.getId()).isEqualTo(mDevice.getId());
+ }
+
+ @Test
+ public void findMediaDevice_idNotExist_shouldReturnNull() {
+ mMediaManager.mMediaDevices.add(mDevice);
+
+ final MediaDevice device = mMediaManager.findMediaDevice("123");
+
+ assertThat(device).isNull();
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
new file mode 100644
index 0000000..5ba33f5
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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 com.android.settingslib.media;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+
+import com.android.settingslib.bluetooth.A2dpProfile;
+import com.android.settingslib.bluetooth.HearingAidProfile;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class PhoneMediaDeviceTest {
+
+ @Mock
+ private LocalBluetoothProfileManager mLocalProfileManager;
+ @Mock
+ private LocalBluetoothManager mLocalBluetoothManager;
+ @Mock
+ private HearingAidProfile mHapProfile;
+ @Mock
+ private A2dpProfile mA2dpProfile;
+ @Mock
+ private BluetoothDevice mDevice;
+
+ private Context mContext;
+ private PhoneMediaDevice mPhoneMediaDevice;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+
+ when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalProfileManager);
+ when(mLocalProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
+ when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHapProfile);
+ when(mA2dpProfile.getActiveDevice()).thenReturn(mDevice);
+
+ mPhoneMediaDevice = new PhoneMediaDevice(mContext, mLocalBluetoothManager);
+ }
+
+ @Test
+ public void connect_phoneDeviceSetActiveSuccess_isConnectedReturnTrue() {
+ when(mA2dpProfile.setActiveDevice(null)).thenReturn(true);
+ when(mHapProfile.setActiveDevice(null)).thenReturn(true);
+
+ assertThat(mPhoneMediaDevice.connect()).isTrue();
+ }
+
+ @Test
+ public void connect_a2dpProfileSetActiveFail_isConnectedReturnFalse() {
+ when(mA2dpProfile.setActiveDevice(null)).thenReturn(false);
+ when(mHapProfile.setActiveDevice(null)).thenReturn(true);
+
+ assertThat(mPhoneMediaDevice.connect()).isFalse();
+ }
+
+ @Test
+ public void connect_hearingAidProfileSetActiveFail_isConnectedReturnFalse() {
+ when(mA2dpProfile.setActiveDevice(null)).thenReturn(true);
+ when(mHapProfile.setActiveDevice(null)).thenReturn(false);
+
+ assertThat(mPhoneMediaDevice.connect()).isFalse();
+ }
+
+ @Test
+ public void connect_hearingAidAndA2dpProfileSetActiveFail_isConnectedReturnFalse() {
+ when(mA2dpProfile.setActiveDevice(null)).thenReturn(false);
+ when(mHapProfile.setActiveDevice(null)).thenReturn(false);
+
+ assertThat(mPhoneMediaDevice.connect()).isFalse();
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java
index 8c18c35..4c68c14 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java
@@ -60,6 +60,7 @@
.setOnClickListener(v -> {
})
.build();
+ mController.setAppEntity(0, mAppEntityInfo);
}
@Test
@@ -172,6 +173,8 @@
mController.setAppEntity(0, mAppEntityInfo)
.setAppEntity(1, mAppEntityInfo)
.setAppEntity(2, mAppEntityInfo).apply();
+ final View appViewsContainer = mAppEntitiesHeaderView.findViewById(
+ R.id.app_views_container);
final View app1View = mAppEntitiesHeaderView.findViewById(R.id.app1_view);
final View app2View = mAppEntitiesHeaderView.findViewById(R.id.app2_view);
final View app3View = mAppEntitiesHeaderView.findViewById(R.id.app3_view);
@@ -181,8 +184,28 @@
assertThat(app3View.getVisibility()).isEqualTo(View.VISIBLE);
mController.clearAllAppEntities().apply();
- assertThat(app1View.getVisibility()).isEqualTo(View.GONE);
- assertThat(app2View.getVisibility()).isEqualTo(View.GONE);
- assertThat(app3View.getVisibility()).isEqualTo(View.GONE);
+
+ assertThat(appViewsContainer.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void apply_noAppEntitySet_shouldOnlyShowTitleAndEmptyView() {
+ mController.setHeaderTitleRes(R.string.expand_button_title)
+ .setAppEntity(0, mAppEntityInfo)
+ .setAppEntity(1, mAppEntityInfo)
+ .setAppEntity(2, mAppEntityInfo).apply();
+ final View titleView = mAppEntitiesHeaderView.findViewById(R.id.header_title);
+ final View detailsView = mAppEntitiesHeaderView.findViewById(R.id.header_details);
+ final View emptyView = mAppEntitiesHeaderView.findViewById(R.id.empty_view);
+ final View appViewsContainer = mAppEntitiesHeaderView.findViewById(
+ R.id.app_views_container);
+
+ mController.clearAllAppEntities().apply();
+
+ assertThat(titleView.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(emptyView.getVisibility()).isEqualTo(View.VISIBLE);
+
+ assertThat(detailsView.getVisibility()).isEqualTo(View.GONE);
+ assertThat(appViewsContainer.getVisibility()).isEqualTo(View.GONE);
}
}
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index de86789..dbeee1c 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -40,11 +40,8 @@
<bool name="def_wifi_display_on">false</bool>
<bool name="def_install_non_market_apps">false</bool>
<bool name="def_package_verifier_enable">true</bool>
- <!-- Comma-separated list of location providers.
- Network location is off by default because it requires
- user opt-in via Setup Wizard or Settings.
- -->
- <string name="def_location_providers_allowed" translatable="false">gps</string>
+ <!-- Comma-separated list of location providers -->
+ <string name="def_location_providers_allowed" translatable="false">gps,network</string>
<bool name="assisted_gps_enabled">true</bool>
<bool name="def_netstats_enabled">true</bool>
<bool name="def_usb_mass_storage_enabled">true</bool>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 5105ff4..4453121 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3232,7 +3232,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 172;
+ private static final int SETTINGS_VERSION = 173;
private final int mUserId;
@@ -4217,6 +4217,41 @@
currentVersion = 172;
}
+ if (currentVersion == 172) {
+ // Version 172: Set the default value for Secure Settings: LOCATION_MODE
+
+ final SettingsState secureSettings = getSecureSettingsLocked(userId);
+
+ final Setting locationMode = secureSettings.getSettingLocked(
+ Secure.LOCATION_MODE);
+
+ if (locationMode.isNull()) {
+ final Setting locationProvidersAllowed = secureSettings.getSettingLocked(
+ Secure.LOCATION_PROVIDERS_ALLOWED);
+
+ String defLocationMode = Integer.toString(
+ !TextUtils.isEmpty(locationProvidersAllowed.getValue())
+ ? Secure.LOCATION_MODE_HIGH_ACCURACY
+ : Secure.LOCATION_MODE_OFF);
+ secureSettings.insertSettingLocked(
+ Secure.LOCATION_MODE, defLocationMode,
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+
+ // also reset LOCATION_PROVIDERS_ALLOWED back to the default value - this
+ // setting is now only for debug/test purposes, and will likely be removed
+ // in a later release. LocationManagerService is responsible for adjusting
+ // these settings to the proper state.
+
+ String defLocationProvidersAllowed = getContext().getResources().getString(
+ R.string.def_location_providers_allowed);
+ secureSettings.insertSettingLocked(
+ Secure.LOCATION_PROVIDERS_ALLOWED, defLocationProvidersAllowed,
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+
+ currentVersion = 173;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b903142..c3c3f25 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -166,6 +166,8 @@
<uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
<uses-permission android:name="android.permission.SUSPEND_APPS" />
<uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" />
+ <!-- Permission needed to wipe the device for Test Harness Mode -->
+ <uses-permission android:name="android.permission.ENABLE_TEST_HARNESS_MODE" />
<uses-permission android:name="android.permission.MANAGE_APPOPS" />
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 2d7471d..a9ff21f 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -1962,8 +1962,7 @@
}
@Override
- public void onFinished(long durationMs, String title, String description)
- throws RemoteException {
+ public void onFinished() throws RemoteException {
// TODO(b/111441001): implement
}
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 98f0cbe..f2be2e7 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -458,6 +458,11 @@
heads-up notifications. -->
<bool name="config_smart_replies_in_notifications_show_in_heads_up">true</bool>
+ <!-- Smart replies in notifications: Minimum number of system generated smart replies that
+ should be shown in a notification. If we cannot show at least this many replies we instead
+ show none. -->
+ <integer name="config_smart_replies_in_notifications_min_num_system_generated_replies">0</integer>
+
<!-- Screenshot editing default activity. Must handle ACTION_EDIT image/png intents.
Blank sends the user to the Chooser first.
This name is in the ComponentName flattened format (package/class) -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f384d8f..13d27bb 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -150,6 +150,9 @@
<!-- Option to always allow USB debugging from the attached computer -->
<string name="usb_debugging_always">Always allow from this computer</string>
+ <!-- Button label for confirming acceptance of enabling USB debugging [CHAR LIMIT=15] -->
+ <string name="usb_debugging_allow">Allow</string>
+
<!-- Title of notification shown when trying to enable USB debugging but a secondary user is the current foreground user. -->
<string name="usb_debugging_secondary_user_title">USB debugging not allowed</string>
@@ -2276,7 +2279,7 @@
app for debugging. Will not be seen by users. [CHAR LIMIT=20] -->
<string name="heap_dump_tile_name">Dump SysUI Heap</string>
- <!-- Text on chip for multiple apps using a single app op [CHAR LIMIT=10] -->
+ <!-- Text on chip for multiple apps using a single app op [CHAR LIMIT=12] -->
<plurals name="ongoing_privacy_chip_multiple_apps">
<item quantity="one"><xliff:g id="num_apps" example="1">%d</xliff:g> app</item>
<item quantity="few"><xliff:g id="num_apps" example="3">%d</xliff:g> apps</item>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 1aff394..7218acf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -1,15 +1,9 @@
package com.android.keyguard;
-import android.content.ContentResolver;
import android.content.Context;
-import android.database.ContentObserver;
import android.graphics.Paint;
import android.graphics.Paint.Style;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.Settings;
import android.util.AttributeSet;
-import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -18,29 +12,19 @@
import androidx.annotation.VisibleForTesting;
-import com.android.keyguard.clock.BubbleClockController;
-import com.android.keyguard.clock.StretchAnalogClockController;
-import com.android.keyguard.clock.TypeClockController;
+import com.android.keyguard.clock.ClockManager;
import com.android.systemui.Dependency;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.policy.ExtensionController;
-import com.android.systemui.statusbar.policy.ExtensionController.Extension;
-import java.util.Objects;
import java.util.TimeZone;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
/**
* Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
*/
public class KeyguardClockSwitch extends RelativeLayout {
- private LayoutInflater mLayoutInflater;
-
- private final ContentResolver mContentResolver;
/**
* Optional/alternative clock injected via plugin.
*/
@@ -63,14 +47,6 @@
*/
private View mKeyguardStatusArea;
/**
- * Used to select between plugin or default implementations of ClockPlugin interface.
- */
- private Extension<ClockPlugin> mClockExtension;
- /**
- * Consumer that accepts the a new ClockPlugin implementation when the Extension reloads.
- */
- private final Consumer<ClockPlugin> mClockPluginConsumer = plugin -> setClockPlugin(plugin);
- /**
* Maintain state so that a newly connected plugin can be initialized.
*/
private float mDarkAmount;
@@ -94,16 +70,7 @@
}
};
- private final ContentObserver mContentObserver =
- new ContentObserver(new Handler(Looper.getMainLooper())) {
- @Override
- public void onChange(boolean selfChange) {
- super.onChange(selfChange);
- if (mClockExtension != null) {
- mClockExtension.reload();
- }
- }
- };
+ private ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
public KeyguardClockSwitch(Context context) {
this(context, null);
@@ -111,8 +78,6 @@
public KeyguardClockSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
- mLayoutInflater = LayoutInflater.from(context);
- mContentResolver = context.getContentResolver();
}
/**
@@ -133,45 +98,14 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- mClockExtension = Dependency.get(ExtensionController.class).newExtension(ClockPlugin.class)
- .withPlugin(ClockPlugin.class)
- .withCallback(mClockPluginConsumer)
- // Using withDefault even though this isn't the default as a workaround.
- // ExtensionBulider doesn't provide the ability to supply a ClockPlugin
- // instance based off of the value of a setting. Since multiple "default"
- // can be provided, using a supplier that changes the settings value.
- // A null return will cause Extension#reload to look at the next "default"
- // supplier.
- .withDefault(
- new SettingsGattedSupplier(
- mContentResolver,
- Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
- BubbleClockController.class.getName(),
- () -> BubbleClockController.build(mLayoutInflater)))
- .withDefault(
- new SettingsGattedSupplier(
- mContentResolver,
- Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
- StretchAnalogClockController.class.getName(),
- () -> StretchAnalogClockController.build(mLayoutInflater)))
- .withDefault(
- new SettingsGattedSupplier(
- mContentResolver,
- Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
- TypeClockController.class.getName(),
- () -> TypeClockController.build(mLayoutInflater)))
- .build();
- mContentResolver.registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
- false, mContentObserver);
+ Dependency.get(ClockManager.class).addOnClockChangedListener(mClockChangedListener);
Dependency.get(StatusBarStateController.class).addCallback(mStateListener);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- mClockExtension.destroy();
- mContentResolver.unregisterContentObserver(mContentObserver);
+ Dependency.get(ClockManager.class).removeOnClockChangedListener(mClockChangedListener);
Dependency.get(StatusBarStateController.class).removeCallback(mStateListener);
}
@@ -313,52 +247,12 @@
}
@VisibleForTesting (otherwise = VisibleForTesting.NONE)
- Consumer<ClockPlugin> getClockPluginConsumer() {
- return mClockPluginConsumer;
+ ClockManager.ClockChangedListener getClockChangedListener() {
+ return mClockChangedListener;
}
@VisibleForTesting (otherwise = VisibleForTesting.NONE)
StatusBarStateController.StateListener getStateListener() {
return mStateListener;
}
-
- /**
- * Supplier that only gets an instance when a settings value matches expected value.
- */
- private static class SettingsGattedSupplier implements Supplier<ClockPlugin> {
-
- private final ContentResolver mContentResolver;
- private final String mKey;
- private final String mValue;
- private final Supplier<ClockPlugin> mSupplier;
-
- /**
- * Constructs a supplier that changes secure setting key against value.
- *
- * @param contentResolver Used to look up settings value.
- * @param key Settings key.
- * @param value If the setting matches this values that get supplies a ClockPlugin
- * instance.
- * @param supplier Supplier of ClockPlugin instance, only used if the setting
- * matches value.
- */
- SettingsGattedSupplier(ContentResolver contentResolver, String key, String value,
- Supplier<ClockPlugin> supplier) {
- mContentResolver = contentResolver;
- mKey = key;
- mValue = value;
- mSupplier = supplier;
- }
-
- /**
- * Returns null if the settings value doesn't match the expected value.
- *
- * A null return causes Extension#reload to skip this supplier and move to the next.
- */
- @Override
- public ClockPlugin get() {
- final String currentValue = Settings.Secure.getString(mContentResolver, mKey);
- return Objects.equals(currentValue, mValue) ? mSupplier.get() : null;
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
new file mode 100644
index 0000000..3217ca6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
@@ -0,0 +1,202 @@
+/*
+ * 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 com.android.keyguard.clock;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.view.LayoutInflater;
+
+import com.android.systemui.plugins.ClockPlugin;
+import com.android.systemui.statusbar.policy.ExtensionController;
+import com.android.systemui.statusbar.policy.ExtensionController.Extension;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Manages custom clock faces.
+ */
+@Singleton
+public final class ClockManager {
+
+ private final LayoutInflater mLayoutInflater;
+ private final ContentResolver mContentResolver;
+
+ /**
+ * Observe settings changes to know when to switch the clock face.
+ */
+ private final ContentObserver mContentObserver =
+ new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+ if (mClockExtension != null) {
+ mClockExtension.reload();
+ }
+ }
+ };
+
+ private final ExtensionController mExtensionController;
+ /**
+ * Used to select between plugin or default implementations of ClockPlugin interface.
+ */
+ private Extension<ClockPlugin> mClockExtension;
+ /**
+ * Consumer that accepts the a new ClockPlugin implementation when the Extension reloads.
+ */
+ private final Consumer<ClockPlugin> mClockPluginConsumer = this::setClockPlugin;
+
+ private final List<ClockChangedListener> mListeners = new ArrayList<>();
+
+ @Inject
+ public ClockManager(Context context, ExtensionController extensionController) {
+ mExtensionController = extensionController;
+ mLayoutInflater = LayoutInflater.from(context);
+ mContentResolver = context.getContentResolver();
+ }
+
+ /**
+ * Add listener to be notified when clock implementation should change.
+ */
+ public void addOnClockChangedListener(ClockChangedListener listener) {
+ if (mListeners.isEmpty()) {
+ register();
+ }
+ mListeners.add(listener);
+ if (mClockExtension != null) {
+ mClockExtension.reload();
+ }
+ }
+
+ /**
+ * Remove listener added with {@link addOnClockChangedListener}.
+ */
+ public void removeOnClockChangedListener(ClockChangedListener listener) {
+ mListeners.remove(listener);
+ if (mListeners.isEmpty()) {
+ unregister();
+ }
+ }
+
+ private void setClockPlugin(ClockPlugin plugin) {
+ for (int i = 0; i < mListeners.size(); i++) {
+ // It probably doesn't make sense to supply the same plugin instances to multiple
+ // listeners. This should be fine for now since there is only a single listener.
+ mListeners.get(i).onClockChanged(plugin);
+ }
+ }
+
+ private void register() {
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
+ false, mContentObserver);
+ mClockExtension = mExtensionController.newExtension(ClockPlugin.class)
+ .withPlugin(ClockPlugin.class)
+ .withCallback(mClockPluginConsumer)
+ // Using withDefault even though this isn't the default as a workaround.
+ // ExtensionBuilder doesn't provide the ability to supply a ClockPlugin
+ // instance based off of the value of a setting. Since multiple "default"
+ // can be provided, using a supplier that changes the settings value.
+ // A null return will cause Extension#reload to look at the next "default"
+ // supplier.
+ .withDefault(
+ new SettingsGattedSupplier(
+ mContentResolver,
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
+ BubbleClockController.class.getName(),
+ () -> BubbleClockController.build(mLayoutInflater)))
+ .withDefault(
+ new SettingsGattedSupplier(
+ mContentResolver,
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
+ StretchAnalogClockController.class.getName(),
+ () -> StretchAnalogClockController.build(mLayoutInflater)))
+ .withDefault(
+ new SettingsGattedSupplier(
+ mContentResolver,
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
+ TypeClockController.class.getName(),
+ () -> TypeClockController.build(mLayoutInflater)))
+ .build();
+ }
+
+ private void unregister() {
+ mContentResolver.unregisterContentObserver(mContentObserver);
+ mClockExtension.destroy();
+ }
+
+ /**
+ * Listener for events that should cause the custom clock face to change.
+ */
+ public interface ClockChangedListener {
+ /**
+ * Called when custom clock should change.
+ *
+ * @param clock Custom clock face to use. A null value indicates the default clock face.
+ */
+ void onClockChanged(ClockPlugin clock);
+ }
+
+ /**
+ * Supplier that only gets an instance when a settings value matches expected value.
+ */
+ private static class SettingsGattedSupplier implements Supplier<ClockPlugin> {
+
+ private final ContentResolver mContentResolver;
+ private final String mKey;
+ private final String mValue;
+ private final Supplier<ClockPlugin> mSupplier;
+
+ /**
+ * Constructs a supplier that changes secure setting key against value.
+ *
+ * @param contentResolver Used to look up settings value.
+ * @param key Settings key.
+ * @param value If the setting matches this values that get supplies a ClockPlugin
+ * instance.
+ * @param supplier Supplier of ClockPlugin instance, only used if the setting
+ * matches value.
+ */
+ SettingsGattedSupplier(ContentResolver contentResolver, String key, String value,
+ Supplier<ClockPlugin> supplier) {
+ mContentResolver = contentResolver;
+ mKey = key;
+ mValue = value;
+ mSupplier = supplier;
+ }
+
+ /**
+ * Returns null if the settings value doesn't match the expected value.
+ *
+ * A null return causes Extension#reload to skip this supplier and move to the next.
+ */
+ @Override
+ public ClockPlugin get() {
+ final String currentValue = Settings.Secure.getString(mContentResolver, mKey);
+ return Objects.equals(currentValue, mValue) ? mSupplier.get() : null;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index ec6ecc6..d99f234 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -29,6 +29,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.Preconditions;
+import com.android.keyguard.clock.ClockManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.assist.AssistManager;
@@ -283,6 +284,7 @@
@Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler;
@Nullable
@Inject @Named(LEAK_REPORT_EMAIL_NAME) Lazy<String> mLeakReportEmail;
+ @Inject Lazy<ClockManager> mClockManager;
@Inject
public Dependency() {
@@ -449,6 +451,7 @@
mProviders.put(NotificationAlertingManager.class, mNotificationAlertingManager::get);
mProviders.put(ForegroundServiceNotificationListener.class,
mForegroundServiceNotificationListener::get);
+ mProviders.put(ClockManager.class, mClockManager::get);
// TODO(b/118592525): to support multi-display , we start to add something which is
// per-display, while others may be global. I think it's time to add
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 1d2d7fa..2aecc24 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -16,14 +16,18 @@
package com.android.systemui;
+import static android.view.Display.DEFAULT_DISPLAY;
+
import android.app.WallpaperManager;
import android.content.ComponentCallbacks2;
+import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region.Op;
+import android.hardware.display.DisplayManager;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Trace;
@@ -33,7 +37,6 @@
import android.view.DisplayInfo;
import android.view.Surface;
import android.view.SurfaceHolder;
-import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
@@ -94,7 +97,7 @@
float mYOffset = 0f;
float mScale = 1f;
- private Display mDefaultDisplay;
+ private Display mDisplay;
private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
boolean mVisible = true;
@@ -138,10 +141,20 @@
super.onCreate(surfaceHolder);
//noinspection ConstantConditions
- mDefaultDisplay = getSystemService(WindowManager.class).getDefaultDisplay();
+ final Context displayContext = getDisplayContext();
+ final int displayId = displayContext == null ? DEFAULT_DISPLAY :
+ displayContext.getDisplayId();
+ DisplayManager dm = getSystemService(DisplayManager.class);
+ if (dm != null) {
+ mDisplay = dm.getDisplay(displayId);
+ if (mDisplay == null) {
+ Log.e(TAG, "Cannot find display! Fallback to default.");
+ mDisplay = dm.getDisplay(DEFAULT_DISPLAY);
+ }
+ }
setOffsetNotificationsEnabled(false);
- updateSurfaceSize(surfaceHolder, getDefaultDisplayInfo(), false /* forDraw */);
+ updateSurfaceSize(surfaceHolder, getDisplayInfo(), false /* forDraw */);
}
@Override
@@ -165,9 +178,26 @@
hasWallpaper = false;
}
- // Set surface size equal to bitmap size, prevent memory waste
- int surfaceWidth = Math.max(MIN_BACKGROUND_WIDTH, mBackgroundWidth);
- int surfaceHeight = Math.max(MIN_BACKGROUND_HEIGHT, mBackgroundHeight);
+ // Expected surface size.
+ int surfaceWidth = Math.max(displayInfo.logicalWidth, mBackgroundWidth);
+ int surfaceHeight = Math.max(displayInfo.logicalHeight, mBackgroundHeight);
+
+ // Calculate the minimum drawing area of the surface, which saves memory and does not
+ // distort the image.
+ final float scale = Math.min(
+ (float) mBackgroundHeight / (float) surfaceHeight,
+ (float) mBackgroundWidth / (float) surfaceWidth);
+ surfaceHeight = (int) (scale * surfaceHeight);
+ surfaceWidth = (int) (scale * surfaceWidth);
+
+ // Set surface size to at least MIN size.
+ if (surfaceWidth < MIN_BACKGROUND_WIDTH || surfaceHeight < MIN_BACKGROUND_HEIGHT) {
+ final float scaleUp = Math.max(
+ (float) MIN_BACKGROUND_WIDTH / (float) surfaceWidth,
+ (float) MIN_BACKGROUND_HEIGHT / (float) surfaceHeight);
+ surfaceWidth = (int) ((float) surfaceWidth * scaleUp);
+ surfaceHeight = (int) ((float) surfaceHeight * scaleUp);
+ }
// Used a fixed size surface, because we are special. We can do
// this because we know the current design of window animations doesn't
@@ -267,8 +297,8 @@
}
@VisibleForTesting
- DisplayInfo getDefaultDisplayInfo() {
- mDefaultDisplay.getDisplayInfo(mTmpDisplayInfo);
+ DisplayInfo getDisplayInfo() {
+ mDisplay.getDisplayInfo(mTmpDisplayInfo);
return mTmpDisplayInfo;
}
@@ -278,7 +308,7 @@
}
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawWallpaper");
- DisplayInfo displayInfo = getDefaultDisplayInfo();
+ DisplayInfo displayInfo = getDisplayInfo();
int newRotation = displayInfo.rotation;
// Sometimes a wallpaper is not large enough to cover the screen in one dimension.
@@ -445,7 +475,7 @@
if (DEBUG) {
Log.d(TAG, "Wallpaper loaded: " + mBackground);
}
- updateSurfaceSize(getSurfaceHolder(), getDefaultDisplayInfo(),
+ updateSurfaceSize(getSurfaceHolder(), getDisplayInfo(),
false /* forDraw */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 3666400..9efa656 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -604,8 +604,14 @@
if (absDelta >= size) {
delta = delta > 0 ? maxScrollDistance : -maxScrollDistance;
} else {
- delta = maxScrollDistance * (float) Math.sin(
- (delta / size) * (Math.PI / 2));
+ int startPosition = mCallback.getConstrainSwipeStartPosition();
+ if (absDelta > startPosition) {
+ int signedStartPosition =
+ (int) (startPosition * Math.signum(delta));
+ delta = signedStartPosition
+ + maxScrollDistance * (float) Math.sin(
+ ((delta - signedStartPosition) / size) * (Math.PI / 2));
+ }
}
}
@@ -742,6 +748,14 @@
float getFalsingThresholdFactor();
/**
+ * @return The position, in pixels, at which a constrained swipe should start being
+ * constrained.
+ */
+ default int getConstrainSwipeStartPosition() {
+ return 0;
+ }
+
+ /**
* @return If true, the given view is draggable.
*/
default boolean canChildBeDragged(@NonNull View animView) { return true; }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 957d772..a457dee 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -267,8 +267,9 @@
BubbleView bubble = (BubbleView) mInflater.inflate(
R.layout.bubble_view, mStackView, false /* attachToRoot */);
bubble.setNotif(notif);
- if (shouldUseActivityView(mContext)) {
- bubble.setAppOverlayIntent(getAppOverlayIntent(notif));
+ PendingIntent bubbleIntent = getValidBubbleIntent(notif);
+ if (shouldUseActivityView(mContext) || bubbleIntent != null) {
+ bubble.setBubbleIntent(getValidBubbleIntent(notif));
}
mBubbles.put(bubble.getKey(), bubble);
mStackView.addBubble(bubble);
@@ -282,7 +283,7 @@
}
@Nullable
- private PendingIntent getAppOverlayIntent(NotificationEntry notif) {
+ private PendingIntent getValidBubbleIntent(NotificationEntry notif) {
Notification notification = notif.notification.getNotification();
if (canLaunchInActivityView(notification.getBubbleMetadata() != null
? notification.getBubbleMetadata().getIntent() : null)) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 9a11b96..dcd121b 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -38,9 +38,11 @@
import android.view.animation.AccelerateInterpolator;
import android.view.animation.OvershootInterpolator;
import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ViewClippingUtil;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -226,6 +228,19 @@
}
/**
+ * Sets the entry that should be expanded and expands if needed.
+ */
+ @VisibleForTesting
+ public void setExpandedBubble(NotificationEntry entry) {
+ for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
+ BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
+ if (entry.equals(bv.getEntry())) {
+ setExpandedBubble(bv);
+ }
+ }
+ }
+
+ /**
* Adds a bubble to the top of the stack.
*
* @param bubbleView the view to add to the stack.
@@ -456,7 +471,8 @@
if (mExpandedBubble.hasAppOverlayIntent()) {
// Bubble with activity view expanded state
ActivityView expandedView = mExpandedBubble.getActivityView();
- expandedView.setLayoutParams(new ViewGroup.LayoutParams(
+ // XXX: gets added to linear layout
+ expandedView.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, mExpandedBubbleHeight));
final PendingIntent intent = mExpandedBubble.getAppOverlayIntent();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
index 91893ef..7b6e79b 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
@@ -298,7 +298,7 @@
}
- public void setAppOverlayIntent(PendingIntent intent) {
+ public void setBubbleIntent(PendingIntent intent) {
mAppOverlayIntent = intent;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dock/DockManager.java b/packages/SystemUI/src/com/android/systemui/dock/DockManager.java
index fa5a114..d332f59 100644
--- a/packages/SystemUI/src/com/android/systemui/dock/DockManager.java
+++ b/packages/SystemUI/src/com/android/systemui/dock/DockManager.java
@@ -48,6 +48,11 @@
*/
void removeListener(DockEventListener callback);
+ /**
+ * Returns true if the device is in docking state.
+ */
+ boolean isDocked();
+
/** Callback for receiving dock events */
interface DockEventListener {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
index 9fc2234..5353ee6 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
@@ -22,7 +22,6 @@
import android.util.Log;
import com.android.internal.hardware.AmbientDisplayConfiguration;
-import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.dock.DockManager;
import com.android.systemui.doze.DozeMachine.State;
@@ -46,12 +45,12 @@
private int mDockState = DockManager.STATE_NONE;
public DozeDockHandler(Context context, DozeMachine machine, DozeHost dozeHost,
- AmbientDisplayConfiguration config, Handler handler) {
+ AmbientDisplayConfiguration config, Handler handler, DockManager dockManager) {
mMachine = machine;
mDozeHost = dozeHost;
mConfig = config;
mHandler = handler;
- mDockManager = SysUiServiceProvider.getComponent(context, DockManager.class);
+ mDockManager = dockManager;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index 58ae555..e338a34 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -27,8 +27,10 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.SystemUIApplication;
import com.android.systemui.classifier.FalsingManager;
+import com.android.systemui.dock.DockManager;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.util.AsyncSensorManager;
import com.android.systemui.util.wakelock.DelayedWakeLock;
@@ -44,6 +46,7 @@
Context context = dozeService;
SensorManager sensorManager = Dependency.get(AsyncSensorManager.class);
AlarmManager alarmManager = context.getSystemService(AlarmManager.class);
+ DockManager dockManager = SysUiServiceProvider.getComponent(context, DockManager.class);
DozeHost host = getHost(dozeService);
AmbientDisplayConfiguration config = new AmbientDisplayConfiguration(context);
@@ -63,13 +66,13 @@
new DozePauser(handler, machine, alarmManager, params.getPolicy()),
new DozeFalsingManagerAdapter(FalsingManager.getInstance(context)),
createDozeTriggers(context, sensorManager, host, alarmManager, config, params,
- handler, wakeLock, machine),
+ handler, wakeLock, machine, dockManager),
createDozeUi(context, host, wakeLock, machine, handler, alarmManager, params),
new DozeScreenState(wrappedService, handler, params, wakeLock),
createDozeScreenBrightness(context, wrappedService, sensorManager, host, params,
handler),
new DozeWallpaperState(context),
- new DozeDockHandler(context, machine, host, config, handler)
+ new DozeDockHandler(context, machine, host, config, handler, dockManager)
});
return machine;
@@ -86,10 +89,11 @@
private DozeTriggers createDozeTriggers(Context context, SensorManager sensorManager,
DozeHost host, AlarmManager alarmManager, AmbientDisplayConfiguration config,
- DozeParameters params, Handler handler, WakeLock wakeLock, DozeMachine machine) {
+ DozeParameters params, Handler handler, WakeLock wakeLock, DozeMachine machine,
+ DockManager dockManager) {
boolean allowPulseTriggers = true;
return new DozeTriggers(context, machine, host, alarmManager, config, params,
- sensorManager, handler, wakeLock, allowPulseTriggers);
+ sensorManager, handler, wakeLock, allowPulseTriggers, dockManager);
}
private DozeMachine.Part createDozeUi(Context context, DozeHost host, WakeLock wakeLock,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 78374a0..562edd6 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -214,6 +214,15 @@
mPickupSensor.setDisabled(disable);
}
+ /** Ignore the setting value of only the sensors that require the touchscreen. */
+ public void ignoreTouchScreenSensorsSettingInterferingWithDocking(boolean ignore) {
+ for (TriggerSensor sensor : mSensors) {
+ if (sensor.mRequiresTouchscreen) {
+ sensor.ignoreSetting(ignore);
+ }
+ }
+ }
+
/** Dump current state */
public void dump(PrintWriter pw) {
for (TriggerSensor s : mSensors) {
@@ -323,6 +332,7 @@
protected boolean mRequested;
protected boolean mRegistered;
protected boolean mDisabled;
+ protected boolean mIgnoresSetting;
public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason,
boolean reportsTouchCoordinates, boolean requiresTouchscreen) {
@@ -333,6 +343,13 @@
public TriggerSensor(Sensor sensor, String setting, boolean settingDef,
boolean configured, int pulseReason, boolean reportsTouchCoordinates,
boolean requiresTouchscreen) {
+ this(sensor, setting, settingDef, configured, pulseReason, reportsTouchCoordinates,
+ requiresTouchscreen, false /* ignoresSetting */);
+ }
+
+ private TriggerSensor(Sensor sensor, String setting, boolean settingDef,
+ boolean configured, int pulseReason, boolean reportsTouchCoordinates,
+ boolean requiresTouchscreen, boolean ignoresSetting) {
mSensor = sensor;
mSetting = setting;
mSettingDefault = settingDef;
@@ -340,6 +357,7 @@
mPulseReason = pulseReason;
mReportsTouchCoordinates = reportsTouchCoordinates;
mRequiresTouchscreen = requiresTouchscreen;
+ mIgnoresSetting = ignoresSetting;
}
public void setListening(boolean listen) {
@@ -354,9 +372,16 @@
updateListener();
}
+ public void ignoreSetting(boolean ignored) {
+ if (mIgnoresSetting == ignored) return;
+ mIgnoresSetting = ignored;
+ updateListener();
+ }
+
public void updateListener() {
if (!mConfigured || mSensor == null) return;
- if (mRequested && !mDisabled && enabledBySetting() && !mRegistered) {
+ if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting)
+ && !mRegistered) {
mRegistered = mSensorManager.requestTriggerSensor(this, mSensor);
if (DEBUG) Log.d(TAG, "requestTriggerSensor " + mRegistered);
} else if (mRegistered) {
@@ -382,6 +407,7 @@
.append(", mRequested=").append(mRequested)
.append(", mDisabled=").append(mDisabled)
.append(", mConfigured=").append(mConfigured)
+ .append(", mIgnoresSetting=").append(mIgnoresSetting)
.append(", mSensor=").append(mSensor).append("}").toString();
}
@@ -464,7 +490,8 @@
public void updateListener() {
if (!mConfigured) return;
AsyncSensorManager asyncSensorManager = (AsyncSensorManager) mSensorManager;
- if (mRequested && !mDisabled && enabledBySetting() && !mRegistered) {
+ if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting)
+ && !mRegistered) {
asyncSensorManager.registerPluginListener(mPluginSensor, mTriggerEventListener);
mRegistered = true;
if (DEBUG) Log.d(TAG, "registerPluginListener");
@@ -481,6 +508,7 @@
.append(", mRequested=").append(mRequested)
.append(", mDisabled=").append(mDisabled)
.append(", mConfigured=").append(mConfigured)
+ .append(", mIgnoresSetting=").append(mIgnoresSetting)
.append(", mSensor=").append(mPluginSensor).append("}").toString();
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index e2e448b..dc505b5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -33,8 +33,10 @@
import android.text.format.Formatter;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.hardware.AmbientDisplayConfiguration;
import com.android.internal.util.Preconditions;
+import com.android.systemui.dock.DockManager;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.util.Assert;
import com.android.systemui.util.wakelock.WakeLock;
@@ -71,6 +73,8 @@
private final boolean mAllowPulseTriggers;
private final UiModeManager mUiModeManager;
private final TriggerReceiver mBroadcastReceiver = new TriggerReceiver();
+ private final DockEventListener mDockEventListener = new DockEventListener();
+ private final DockManager mDockManager;
private long mNotificationPulseTime;
private boolean mPulsePending;
@@ -79,7 +83,7 @@
public DozeTriggers(Context context, DozeMachine machine, DozeHost dozeHost,
AlarmManager alarmManager, AmbientDisplayConfiguration config,
DozeParameters dozeParameters, SensorManager sensorManager, Handler handler,
- WakeLock wakeLock, boolean allowPulseTriggers) {
+ WakeLock wakeLock, boolean allowPulseTriggers, DockManager dockManager) {
mContext = context;
mMachine = machine;
mDozeHost = dozeHost;
@@ -93,6 +97,7 @@
config, wakeLock, this::onSensor, this::onProximityFar,
dozeParameters.getPolicy());
mUiModeManager = mContext.getSystemService(UiModeManager.class);
+ mDockManager = dockManager;
}
private void onNotification() {
@@ -129,7 +134,8 @@
}
}
- private void onSensor(int pulseReason, boolean sensorPerformedProxCheck,
+ @VisibleForTesting
+ void onSensor(int pulseReason, boolean sensorPerformedProxCheck,
float screenX, float screenY, float[] rawValues) {
boolean isDoubleTap = pulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP;
boolean isTap = pulseReason == DozeLog.PULSE_REASON_SENSOR_TAP;
@@ -159,7 +165,7 @@
} else {
mDozeHost.extendPulse();
}
- }, sensorPerformedProxCheck, pulseReason);
+ }, sensorPerformedProxCheck || mDockManager.isDocked(), pulseReason);
}
if (isPickup) {
@@ -223,6 +229,7 @@
case INITIALIZED:
mBroadcastReceiver.register(mContext);
mDozeHost.addCallback(mHostCallback);
+ mDockManager.addListener(mDockEventListener);
checkTriggersAtInit();
break;
case DOZE:
@@ -248,6 +255,7 @@
case FINISH:
mBroadcastReceiver.unregister(mContext);
mDozeHost.removeCallback(mHostCallback);
+ mDockManager.removeListener(mDockEventListener);
mDozeSensors.setListening(false);
mDozeSensors.setProxListening(false);
break;
@@ -423,6 +431,24 @@
}
}
+ private class DockEventListener implements DockManager.DockEventListener {
+ @Override
+ public void onEvent(int event) {
+ if (DEBUG) Log.d(TAG, "dock event = " + event);
+ switch (event) {
+ case DockManager.STATE_DOCKED:
+ case DockManager.STATE_DOCKED_HIDE:
+ mDozeSensors.ignoreTouchScreenSensorsSettingInterferingWithDocking(true);
+ break;
+ case DockManager.STATE_NONE:
+ mDozeSensors.ignoreTouchScreenSensorsSettingInterferingWithDocking(false);
+ break;
+ default:
+ // no-op
+ }
+ }
+ }
+
private DozeHost.Callback mHostCallback = new DozeHost.Callback() {
@Override
public void onNotificationAlerted() {
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
index 77e25e3..26c6d50 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
@@ -30,6 +30,7 @@
import com.android.systemui.Dependency
import com.android.systemui.R
import com.android.systemui.plugins.ActivityStarter
+import java.util.concurrent.TimeUnit
class OngoingPrivacyDialog constructor(
val context: Context,
@@ -60,7 +61,8 @@
setNegativeButton(R.string.ongoing_privacy_dialog_cancel, null)
setPositiveButton(R.string.ongoing_privacy_dialog_open_settings,
object : DialogInterface.OnClickListener {
- val intent = Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE)
+ val intent = Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE).putExtra(
+ Intent.EXTRA_DURATION_MILLIS, TimeUnit.MINUTES.toMillis(1))
@Suppress("DEPRECATION")
override fun onClick(dialog: DialogInterface?, which: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 1155a41..e1becdb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -188,7 +188,7 @@
state.secondaryLabel = r.getString(R.string.status_bar_airplane);
} else if (mobileDataEnabled) {
state.state = Tile.STATE_ACTIVE;
- state.secondaryLabel = getMobileDataDescription(cb);
+ state.secondaryLabel = getMobileDataSubscriptionName(cb);
} else {
state.state = Tile.STATE_INACTIVE;
state.secondaryLabel = r.getString(R.string.cell_data_off);
@@ -207,16 +207,16 @@
state.contentDescription = state.label + ", " + contentDescriptionSuffix;
}
- private CharSequence getMobileDataDescription(CallbackInfo cb) {
- if (cb.roaming && !TextUtils.isEmpty(cb.dataContentDescription)) {
+ private CharSequence getMobileDataSubscriptionName(CallbackInfo cb) {
+ if (cb.roaming && !TextUtils.isEmpty(cb.dataSubscriptionName)) {
String roaming = mContext.getString(R.string.data_connection_roaming);
- String dataDescription = cb.dataContentDescription;
+ String dataDescription = cb.dataSubscriptionName.toString();
return mContext.getString(R.string.mobile_data_text_format, roaming, dataDescription);
}
if (cb.roaming) {
return mContext.getString(R.string.data_connection_roaming);
}
- return cb.dataContentDescription;
+ return cb.dataSubscriptionName;
}
@Override
@@ -231,7 +231,7 @@
private static final class CallbackInfo {
boolean airplaneModeEnabled;
- String dataContentDescription;
+ CharSequence dataSubscriptionName;
boolean activityIn;
boolean activityOut;
boolean noSim;
@@ -249,7 +249,7 @@
// Not data sim, don't display.
return;
}
- mInfo.dataContentDescription = typeContentDescription;
+ mInfo.dataSubscriptionName = mController.getMobileDataNetworkName();
mInfo.activityIn = activityIn;
mInfo.activityOut = activityOut;
mInfo.roaming = roaming;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index d5f4d04..d2ce31d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -49,6 +49,7 @@
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -130,8 +131,11 @@
final int count =
getEntryManager().getNotificationData().getActiveNotifications().size();
final int rank = getEntryManager().getNotificationData().getRank(notificationKey);
+ NotificationVisibility.NotificationLocation location =
+ NotificationLogger.getNotificationLocation(
+ getEntryManager().getNotificationData().get(notificationKey));
final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey,
- rank, count, true);
+ rank, count, true, location);
try {
mBarService.onNotificationClick(notificationKey, nv);
} catch (RemoteException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 7d6231f..31d1621 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -54,6 +54,7 @@
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.RemoteInputView;
@@ -181,7 +182,11 @@
final int rank = mEntryManager.getNotificationData().getRank(key);
final Notification.Action action =
statusBarNotification.getNotification().actions[actionIndex];
- final NotificationVisibility nv = NotificationVisibility.obtain(key, rank, count, true);
+ NotificationVisibility.NotificationLocation location =
+ NotificationLogger.getNotificationLocation(
+ mEntryManager.getNotificationData().get(key));
+ final NotificationVisibility nv =
+ NotificationVisibility.obtain(key, rank, count, true, location);
try {
mBarService.onNotificationActionClick(key, buttonIndex, action, nv, false);
} catch (RemoteException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 9ef9c94..546b2e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -538,6 +538,7 @@
- getIntrinsicHeight());
}
float viewEnd = viewStart + fullHeight;
+ // TODO: fix this check for anchor scrolling.
if (expandingAnimated && mAmbientState.getScrollY() == 0
&& !mAmbientState.isOnKeyguard() && !iconState.isLastExpandIcon) {
// We are expanding animated. Because we switch to a linear interpolation in this case,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
index 573c1f8..a2abcd2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
@@ -23,6 +23,7 @@
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import java.util.Set;
@@ -74,8 +75,10 @@
boolean generatedByAssistant) {
final int count = mEntryManager.getNotificationData().getActiveNotifications().size();
final int rank = mEntryManager.getNotificationData().getRank(entry.key);
- final NotificationVisibility nv =
- NotificationVisibility.obtain(entry.key, rank, count, true);
+ NotificationVisibility.NotificationLocation location =
+ NotificationLogger.getNotificationLocation(entry);
+ final NotificationVisibility nv = NotificationVisibility.obtain(
+ entry.key, rank, count, true, location);
try {
mBarService.onNotificationActionClick(
entry.key, actionIndex, action, nv, generatedByAssistant);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 989e781..ef5e936 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -36,6 +36,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationData;
import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationInflater;
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -160,8 +161,10 @@
public void performRemoveNotification(StatusBarNotification n) {
final int rank = mNotificationData.getRank(n.getKey());
final int count = mNotificationData.getActiveNotifications().size();
+ NotificationVisibility.NotificationLocation location =
+ NotificationLogger.getNotificationLocation(getNotificationData().get(n.getKey()));
final NotificationVisibility nv = NotificationVisibility.obtain(n.getKey(), rank, count,
- true);
+ true, location);
removeNotificationInternal(
n.getKey(), null, nv, false /* forceRemove */, true /* removedByUser */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java
index cc302b1..0b8596f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java
@@ -27,13 +27,10 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.view.ViewGroup;
-import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.NotificationMessagingUtil;
import com.android.systemui.Dependency;
import com.android.systemui.R;
@@ -43,6 +40,7 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationUiAdjustment;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationInflater;
@@ -71,7 +69,6 @@
Dependency.get(NotificationInterruptionStateProvider.class);
private final Context mContext;
- private final IStatusBarService mBarService;
private final NotificationMessagingUtil mMessagingUtil;
private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =
this::logNotificationExpansion;
@@ -85,6 +82,7 @@
private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener;
private BindRowCallback mBindRowCallback;
private NotificationClicker mNotificationClicker;
+ private final NotificationLogger mNotificationLogger = Dependency.get(NotificationLogger.class);
@Inject
public NotificationRowBinder(Context context,
@@ -92,8 +90,6 @@
mContext = context;
mMessagingUtil = new NotificationMessagingUtil(context);
mAllowLongPress = allowLongPress;
- mBarService = IStatusBarService.Stub.asInterface(
- ServiceManager.getService(Context.STATUS_BAR_SERVICE));
}
private NotificationRemoteInputManager getRemoteInputManager() {
@@ -274,13 +270,7 @@
}
private void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
- mUiOffloadThread.submit(() -> {
- try {
- mBarService.onNotificationExpansionChanged(key, userAction, expanded);
- } catch (RemoteException e) {
- // Ignore.
- }
- });
+ mNotificationLogger.onExpansionChanged(key, userAction, expanded);
}
/** Callback for when a row is bound to an entry. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 3eec38e..9b1a2aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -23,9 +23,12 @@
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
@@ -36,12 +39,15 @@
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -66,6 +72,7 @@
private final UiOffloadThread mUiOffloadThread;
private final NotificationEntryManager mEntryManager;
private HeadsUpManager mHeadsUpManager;
+ private final ExpansionStateLogger mExpansionStateLogger;
protected Handler mHandler = new Handler();
protected IStatusBarService mBarService;
@@ -122,7 +129,8 @@
NotificationEntry entry = activeNotifications.get(i);
String key = entry.notification.getKey();
boolean isVisible = mListContainer.isInVisibleLocation(entry);
- NotificationVisibility visObj = NotificationVisibility.obtain(key, i, N, isVisible);
+ NotificationVisibility visObj = NotificationVisibility.obtain(key, i, N, isVisible,
+ getNotificationLocation(entry));
boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj);
if (isVisible) {
// Build new set of visible notifications.
@@ -144,6 +152,9 @@
recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
mCurrentlyVisibleNotifications.addAll(mTmpCurrentlyVisibleNotifications);
+ mExpansionStateLogger.onVisibilityChanged(
+ mTmpCurrentlyVisibleNotifications, mTmpCurrentlyVisibleNotifications);
+
recycleAllVisibilityObjects(mTmpNoLongerVisibleNotifications);
mTmpCurrentlyVisibleNotifications.clear();
mTmpNewlyVisibleNotifications.clear();
@@ -151,16 +162,52 @@
}
};
+ /**
+ * Returns the location of the notification referenced by the given {@link NotificationEntry}.
+ */
+ public static NotificationVisibility.NotificationLocation getNotificationLocation(
+ NotificationEntry entry) {
+ ExpandableNotificationRow row = entry.getRow();
+ ExpandableViewState childViewState = row.getViewState();
+
+ if (childViewState == null) {
+ return NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN;
+ }
+ return convertNotificationLocation(childViewState.location);
+ }
+
+ private static NotificationVisibility.NotificationLocation convertNotificationLocation(
+ int location) {
+ switch (location) {
+ case ExpandableViewState.LOCATION_FIRST_HUN:
+ return NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP;
+ case ExpandableViewState.LOCATION_HIDDEN_TOP:
+ return NotificationVisibility.NotificationLocation.LOCATION_HIDDEN_TOP;
+ case ExpandableViewState.LOCATION_MAIN_AREA:
+ return NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA;
+ case ExpandableViewState.LOCATION_BOTTOM_STACK_PEEKING:
+ return NotificationVisibility.NotificationLocation.LOCATION_BOTTOM_STACK_PEEKING;
+ case ExpandableViewState.LOCATION_BOTTOM_STACK_HIDDEN:
+ return NotificationVisibility.NotificationLocation.LOCATION_BOTTOM_STACK_HIDDEN;
+ case ExpandableViewState.LOCATION_GONE:
+ return NotificationVisibility.NotificationLocation.LOCATION_GONE;
+ default:
+ return NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN;
+ }
+ }
+
@Inject
public NotificationLogger(NotificationListener notificationListener,
UiOffloadThread uiOffloadThread,
NotificationEntryManager entryManager,
- StatusBarStateController statusBarStateController) {
+ StatusBarStateController statusBarStateController,
+ ExpansionStateLogger expansionStateLogger) {
mNotificationListener = notificationListener;
mUiOffloadThread = uiOffloadThread;
mEntryManager = entryManager;
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ mExpansionStateLogger = expansionStateLogger;
// Not expected to be destroyed, don't need to unsubscribe
statusBarStateController.addCallback(this);
@@ -173,6 +220,7 @@
if (removedByUser && visibility != null) {
logNotificationClear(entry.key, entry.notification, visibility);
}
+ mExpansionStateLogger.onEntryRemoved(entry.key);
}
@Override
@@ -319,8 +367,8 @@
}
}
- private NotificationVisibility[] cloneVisibilitiesAsArr(Collection<NotificationVisibility> c) {
-
+ private static NotificationVisibility[] cloneVisibilitiesAsArr(
+ Collection<NotificationVisibility> c) {
final NotificationVisibility[] array = new NotificationVisibility[c.size()];
int i = 0;
for(NotificationVisibility nv: c) {
@@ -348,9 +396,135 @@
}
/**
+ * Called when the notification is expanded / collapsed.
+ */
+ public void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded) {
+ mExpansionStateLogger.onExpansionChanged(key, isUserAction, isExpanded);
+ }
+
+ /**
* A listener that is notified when some child locations might have changed.
*/
public interface OnChildLocationsChangedListener {
void onChildLocationsChanged();
}
+
+ /**
+ * Logs the expansion state change when the notification is visible.
+ */
+ public static class ExpansionStateLogger {
+ /** Notification key -> state, should be accessed in UI offload thread only. */
+ private final Map<String, State> mExpansionStates = new ArrayMap<>();
+
+ /**
+ * Notification key -> last logged expansion state, should be accessed in UI thread only.
+ */
+ private final Map<String, Boolean> mLoggedExpansionState = new ArrayMap<>();
+ private final UiOffloadThread mUiOffloadThread;
+ @VisibleForTesting
+ IStatusBarService mBarService;
+
+ @Inject
+ public ExpansionStateLogger(UiOffloadThread uiOffloadThread) {
+ mUiOffloadThread = uiOffloadThread;
+ mBarService =
+ IStatusBarService.Stub.asInterface(
+ ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ }
+
+ @VisibleForTesting
+ void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded) {
+ State state = getState(key);
+ state.mIsUserAction = isUserAction;
+ state.mIsExpanded = isExpanded;
+ maybeNotifyOnNotificationExpansionChanged(key, state);
+ }
+
+ @VisibleForTesting
+ void onVisibilityChanged(
+ Collection<NotificationVisibility> newlyVisible,
+ Collection<NotificationVisibility> noLongerVisible) {
+ final NotificationVisibility[] newlyVisibleAr =
+ cloneVisibilitiesAsArr(newlyVisible);
+ final NotificationVisibility[] noLongerVisibleAr =
+ cloneVisibilitiesAsArr(noLongerVisible);
+
+ for (NotificationVisibility nv : newlyVisibleAr) {
+ State state = getState(nv.key);
+ state.mIsVisible = true;
+ maybeNotifyOnNotificationExpansionChanged(nv.key, state);
+ }
+ for (NotificationVisibility nv : noLongerVisibleAr) {
+ State state = getState(nv.key);
+ state.mIsVisible = false;
+ }
+ }
+
+ @VisibleForTesting
+ void onEntryRemoved(String key) {
+ mExpansionStates.remove(key);
+ mLoggedExpansionState.remove(key);
+ }
+
+ private State getState(String key) {
+ State state = mExpansionStates.get(key);
+ if (state == null) {
+ state = new State();
+ mExpansionStates.put(key, state);
+ }
+ return state;
+ }
+
+ private void maybeNotifyOnNotificationExpansionChanged(final String key, State state) {
+ if (!state.isFullySet()) {
+ return;
+ }
+ if (!state.mIsVisible) {
+ return;
+ }
+ Boolean loggedExpansionState = mLoggedExpansionState.get(key);
+ // Consider notification is initially collapsed, so only expanded is logged in the
+ // first time.
+ if (loggedExpansionState == null && !state.mIsExpanded) {
+ return;
+ }
+ if (loggedExpansionState != null
+ && state.mIsExpanded == loggedExpansionState) {
+ return;
+ }
+ mLoggedExpansionState.put(key, state.mIsExpanded);
+ final State stateToBeLogged = new State(state);
+ mUiOffloadThread.submit(() -> {
+ try {
+ mBarService.onNotificationExpansionChanged(
+ key, stateToBeLogged.mIsUserAction, stateToBeLogged.mIsExpanded,
+ // TODO (b/120767764): fill in location
+ ExpandableViewState.LOCATION_UNKNOWN /* notificationLocation */);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to call onNotificationExpansionChanged: ", e);
+ }
+ });
+ }
+
+ private static class State {
+ @Nullable
+ Boolean mIsUserAction;
+ @Nullable
+ Boolean mIsExpanded;
+ @Nullable
+ Boolean mIsVisible;
+
+ private State() {}
+
+ private State(State state) {
+ this.mIsUserAction = state.mIsUserAction;
+ this.mIsExpanded = state.mIsExpanded;
+ this.mIsVisible = state.mIsVisible;
+ }
+
+ private boolean isFullySet() {
+ return mIsUserAction != null && mIsExpanded != null && mIsVisible != null;
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 1b5013d..c246af5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -42,6 +42,8 @@
private ArrayList<ExpandableView> mDraggedViews = new ArrayList<>();
private int mScrollY;
+ private int mAnchorViewIndex;
+ private int mAnchorViewY;
private boolean mDimmed;
private ActivatableNotificationView mActivatedChild;
private float mOverScrollTopAmount;
@@ -130,6 +132,27 @@
this.mScrollY = scrollY;
}
+ /**
+ * Index of the child view whose Y position on screen is returned by {@link #getAnchorViewY()}.
+ * Other views are laid out outwards from this view in both directions.
+ */
+ public int getAnchorViewIndex() {
+ return mAnchorViewIndex;
+ }
+
+ public void setAnchorViewIndex(int anchorViewIndex) {
+ mAnchorViewIndex = anchorViewIndex;
+ }
+
+ /** Current Y position of the view at {@link #getAnchorViewIndex()}. */
+ public int getAnchorViewY() {
+ return mAnchorViewY;
+ }
+
+ public void setAnchorViewY(int anchorViewY) {
+ mAnchorViewY = anchorViewY;
+ }
+
/** Call when dragging begins. */
public void onBeginDrag(ExpandableView view) {
mDraggedViews.add(view);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index d066567..2129b81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -18,6 +18,7 @@
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
+import static com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.ANCHOR_SCROLLING;
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE;
import static com.android.systemui.statusbar.phone.NotificationIconAreaController.LOW_PRIORITY;
import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
@@ -176,10 +177,18 @@
private float mExpandedHeight;
private int mOwnScrollY;
+ private View mScrollAnchorView;
+ private int mScrollAnchorViewY;
private int mMaxLayoutHeight;
private VelocityTracker mVelocityTracker;
private OverScroller mScroller;
+ /** Last Y position reported by {@link #mScroller}, used to calculate scroll delta. */
+ private int mLastScrollerY;
+ /**
+ * True if the max position was set to a known position on the last call to {@link #mScroller}.
+ */
+ private boolean mIsScrollerBoundSet;
private Runnable mFinishScrollingCallback;
private int mTouchSlop;
private int mMinimumVelocity;
@@ -417,7 +426,12 @@
private int mStatusBarState;
private int mCachedBackgroundColor;
private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
- private Runnable mAnimateScroll = this::animateScroll;
+ private Runnable mReflingAndAnimateScroll = () -> {
+ if (ANCHOR_SCROLLING) {
+ maybeReflingScroller();
+ }
+ animateScroll();
+ };
private int mCornerRadius;
private int mSidePaddings;
private final Rect mBackgroundAnimationRect = new Rect();
@@ -511,6 +525,7 @@
mDebugPaint.setColor(0xffff0000);
mDebugPaint.setStrokeWidth(2);
mDebugPaint.setStyle(Paint.Style.STROKE);
+ mDebugPaint.setTextSize(25f);
}
mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll);
@@ -674,6 +689,30 @@
}
}
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+
+ if (DEBUG && ANCHOR_SCROLLING) {
+ if (mScrollAnchorView instanceof ExpandableNotificationRow) {
+ canvas.drawRect(0,
+ mScrollAnchorView.getTranslationY(),
+ getWidth(),
+ mScrollAnchorView.getTranslationY()
+ + ((ExpandableNotificationRow) mScrollAnchorView).getActualHeight(),
+ mDebugPaint);
+ canvas.drawText(Integer.toString(mScrollAnchorViewY), getWidth() - 200,
+ mScrollAnchorView.getTranslationY() + 30, mDebugPaint);
+ int y = (int) mShelf.getTranslationY();
+ canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
+ }
+ canvas.drawText(Integer.toString(getMaxNegativeScrollAmount()), getWidth() - 100,
+ getIntrinsicPadding() + 30, mDebugPaint);
+ canvas.drawText(Integer.toString(getMaxPositiveScrollAmount()), getWidth() - 100,
+ getHeight() - 30, mDebugPaint);
+ }
+ }
+
@ShadeViewRefactor(RefactorComponent.DECORATOR)
private void drawBackground(Canvas canvas) {
int lockScreenLeft = mSidePaddings;
@@ -970,7 +1009,12 @@
mAmbientState.setCurrentScrollVelocity(mScroller.isFinished()
? 0
: mScroller.getCurrVelocity());
- mAmbientState.setScrollY(mOwnScrollY);
+ if (ANCHOR_SCROLLING) {
+ mAmbientState.setAnchorViewIndex(indexOfChild(mScrollAnchorView));
+ mAmbientState.setAnchorViewY(mScrollAnchorViewY);
+ } else {
+ mAmbientState.setScrollY(mOwnScrollY);
+ }
mStackScrollAlgorithm.resetViewStates(mAmbientState);
if (!isCurrentlyAnimating() && !mNeedsAnimation) {
applyCurrentState();
@@ -1004,7 +1048,7 @@
float end = start + child.getActualHeight();
boolean clip = clipStart > start && clipStart < end
|| clipEnd >= start && clipEnd <= end;
- clip &= !(first && mOwnScrollY == 0);
+ clip &= !(first && isScrolledToTop());
child.setDistanceToTopRoundness(clip ? Math.max(start - clipStart, 0)
: ExpandableView.NO_ROUNDNESS);
first = false;
@@ -1016,19 +1060,21 @@
if (mChildrenToAddAnimated.isEmpty()) {
return;
}
- for (int i = 0; i < getChildCount(); i++) {
- ExpandableView child = (ExpandableView) getChildAt(i);
- if (mChildrenToAddAnimated.contains(child)) {
- int startingPosition = getPositionInLinearLayout(child);
- float increasedPaddingAmount = child.getIncreasedPaddingAmount();
- int padding = increasedPaddingAmount == 1.0f ? mIncreasedPaddingBetweenElements
- : increasedPaddingAmount == -1.0f ? 0 : mPaddingBetweenElements;
- int childHeight = getIntrinsicHeight(child) + padding;
- if (startingPosition < mOwnScrollY) {
- // This child starts off screen, so let's keep it offscreen to keep the
- // others visible
+ if (!ANCHOR_SCROLLING) {
+ for (int i = 0; i < getChildCount(); i++) {
+ ExpandableView child = (ExpandableView) getChildAt(i);
+ if (mChildrenToAddAnimated.contains(child)) {
+ int startingPosition = getPositionInLinearLayout(child);
+ float increasedPaddingAmount = child.getIncreasedPaddingAmount();
+ int padding = increasedPaddingAmount == 1.0f ? mIncreasedPaddingBetweenElements
+ : increasedPaddingAmount == -1.0f ? 0 : mPaddingBetweenElements;
+ int childHeight = getIntrinsicHeight(child) + padding;
+ if (startingPosition < mOwnScrollY) {
+ // This child starts off screen, so let's keep it offscreen to keep the
+ // others visible
- setOwnScrollY(mOwnScrollY + childHeight);
+ setOwnScrollY(mOwnScrollY + childHeight);
+ }
}
}
}
@@ -1047,12 +1093,16 @@
int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
- targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange()));
+ if (ANCHOR_SCROLLING) {
+ // TODO
+ } else {
+ targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange()));
- // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
- // that it is not visible anymore.
- if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
- setOwnScrollY(targetScroll);
+ // Only apply the scroll if we're scrolling the view upwards, or the view is so
+ // far up that it is not visible anymore.
+ if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
+ setOwnScrollY(targetScroll);
+ }
}
}
}
@@ -1073,9 +1123,13 @@
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
private void clampScrollPosition() {
- int scrollRange = getScrollRange();
- if (scrollRange < mOwnScrollY) {
- setOwnScrollY(scrollRange);
+ if (ANCHOR_SCROLLING) {
+ // TODO
+ } else {
+ int scrollRange = getScrollRange();
+ if (scrollRange < mOwnScrollY) {
+ setOwnScrollY(scrollRange);
+ }
}
}
@@ -1453,17 +1507,21 @@
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public boolean scrollTo(View v) {
ExpandableView expandableView = (ExpandableView) v;
- int positionInLinearLayout = getPositionInLinearLayout(v);
- int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
- int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
+ if (ANCHOR_SCROLLING) {
+ // TODO
+ } else {
+ int positionInLinearLayout = getPositionInLinearLayout(v);
+ int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
+ int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
- // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
- // that it is not visible anymore.
- if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
- mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY);
- mDontReportNextOverScroll = true;
- animateScroll();
- return true;
+ // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
+ // that it is not visible anymore.
+ if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
+ mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY);
+ mDontReportNextOverScroll = true;
+ animateScroll();
+ return true;
+ }
}
return false;
}
@@ -1484,16 +1542,20 @@
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
mBottomInset = insets.getSystemWindowInsetBottom();
- int range = getScrollRange();
- if (mOwnScrollY > range) {
- // HACK: We're repeatedly getting staggered insets here while the IME is
- // animating away. To work around that we'll wait until things have settled.
- removeCallbacks(mReclamp);
- postDelayed(mReclamp, 50);
- } else if (mForcedScroll != null) {
- // The scroll was requested before we got the actual inset - in case we need
- // to scroll up some more do so now.
- scrollTo(mForcedScroll);
+ if (ANCHOR_SCROLLING) {
+ // TODO
+ } else {
+ int range = getScrollRange();
+ if (mOwnScrollY > range) {
+ // HACK: We're repeatedly getting staggered insets here while the IME is
+ // animating away. To work around that we'll wait until things have settled.
+ removeCallbacks(mReclamp);
+ postDelayed(mReclamp, 50);
+ } else if (mForcedScroll != null) {
+ // The scroll was requested before we got the actual inset - in case we need
+ // to scroll up some more do so now.
+ scrollTo(mForcedScroll);
+ }
}
return insets;
}
@@ -1502,8 +1564,12 @@
private Runnable mReclamp = new Runnable() {
@Override
public void run() {
- int range = getScrollRange();
- mScroller.startScroll(mScrollX, mOwnScrollY, 0, range - mOwnScrollY);
+ if (ANCHOR_SCROLLING) {
+ // TODO
+ } else {
+ int range = getScrollRange();
+ mScroller.startScroll(mScrollX, mOwnScrollY, 0, range - mOwnScrollY);
+ }
mDontReportNextOverScroll = true;
mDontClampNextScroll = true;
animateScroll();
@@ -1581,20 +1647,39 @@
}
// Top overScroll might not grab all scrolling motion,
// we have to scroll as well.
- float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
- float newScrollY = mOwnScrollY + scrollAmount;
- if (newScrollY > range) {
- if (!mExpandedInThisMotion) {
- float currentBottomPixels = getCurrentOverScrolledPixels(false);
- // We overScroll on the top
- setOverScrolledPixels(currentBottomPixels + newScrollY - range,
- false /* onTop */,
- false /* animate */);
+ if (ANCHOR_SCROLLING) {
+ float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
+ // TODO: once we're recycling this will need to check the adapter position of the child
+ ExpandableView lastRow = getLastRowNotGone();
+ if (lastRow != null && !lastRow.isInShelf()) {
+ float distanceToMax = Math.max(0, getMaxPositiveScrollAmount());
+ if (scrollAmount > distanceToMax) {
+ float currentBottomPixels = getCurrentOverScrolledPixels(false);
+ // We overScroll on the bottom
+ setOverScrolledPixels(currentBottomPixels + (scrollAmount - distanceToMax),
+ false /* onTop */,
+ false /* animate */);
+ mScrollAnchorViewY -= distanceToMax;
+ scrollAmount = 0f;
+ }
}
- setOwnScrollY(range);
- scrollAmount = 0.0f;
+ return scrollAmount;
+ } else {
+ float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
+ float newScrollY = mOwnScrollY + scrollAmount;
+ if (newScrollY > range) {
+ if (!mExpandedInThisMotion) {
+ float currentBottomPixels = getCurrentOverScrolledPixels(false);
+ // We overScroll on the bottom
+ setOverScrolledPixels(currentBottomPixels + newScrollY - range,
+ false /* onTop */,
+ false /* animate */);
+ }
+ setOwnScrollY(range);
+ scrollAmount = 0.0f;
+ }
+ return scrollAmount;
}
- return scrollAmount;
}
/**
@@ -1615,18 +1700,37 @@
}
// Bottom overScroll might not grab all scrolling motion,
// we have to scroll as well.
- float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
- float newScrollY = mOwnScrollY + scrollAmount;
- if (newScrollY < 0) {
- float currentTopPixels = getCurrentOverScrolledPixels(true);
- // We overScroll on the top
- setOverScrolledPixels(currentTopPixels - newScrollY,
- true /* onTop */,
- false /* animate */);
- setOwnScrollY(0);
- scrollAmount = 0.0f;
+ if (ANCHOR_SCROLLING) {
+ float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
+ // TODO: once we're recycling this will need to check the adapter position of the child
+ ExpandableView firstChild = getFirstChildNotGone();
+ float top = firstChild.getTranslationY();
+ float distanceToTop = mScrollAnchorView.getTranslationY() - top - mScrollAnchorViewY;
+ if (distanceToTop < -scrollAmount) {
+ float currentTopPixels = getCurrentOverScrolledPixels(true);
+ // We overScroll on the top
+ setOverScrolledPixels(currentTopPixels + (-scrollAmount - distanceToTop),
+ true /* onTop */,
+ false /* animate */);
+ mScrollAnchorView = firstChild;
+ mScrollAnchorViewY = 0;
+ scrollAmount = 0f;
+ }
+ return scrollAmount;
+ } else {
+ float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
+ float newScrollY = mOwnScrollY + scrollAmount;
+ if (newScrollY < 0) {
+ float currentTopPixels = getCurrentOverScrolledPixels(true);
+ // We overScroll on the top
+ setOverScrolledPixels(currentTopPixels - newScrollY,
+ true /* onTop */,
+ false /* animate */);
+ setOwnScrollY(0);
+ scrollAmount = 0.0f;
+ }
+ return scrollAmount;
}
- return scrollAmount;
}
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@@ -1661,26 +1765,43 @@
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
private void animateScroll() {
if (mScroller.computeScrollOffset()) {
- int oldY = mOwnScrollY;
- int y = mScroller.getCurrY();
-
- if (oldY != y) {
- int range = getScrollRange();
- if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
- float currVelocity = mScroller.getCurrVelocity();
- if (currVelocity >= mMinimumVelocity) {
- mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
+ if (ANCHOR_SCROLLING) {
+ int oldY = mLastScrollerY;
+ int y = mScroller.getCurrY();
+ int deltaY = y - oldY;
+ if (deltaY != 0) {
+ int maxNegativeScrollAmount = getMaxNegativeScrollAmount();
+ int maxPositiveScrollAmount = getMaxPositiveScrollAmount();
+ if ((maxNegativeScrollAmount < 0 && deltaY < maxNegativeScrollAmount)
+ || (maxPositiveScrollAmount > 0 && deltaY > maxPositiveScrollAmount)) {
+ // This frame takes us into overscroll, so set the max overscroll based on
+ // the current velocity
+ setMaxOverScrollFromCurrentVelocity();
}
+ customOverScrollBy(deltaY, oldY, 0, (int) mMaxOverScroll);
+ mLastScrollerY = y;
}
+ } else {
+ int oldY = mOwnScrollY;
+ int y = mScroller.getCurrY();
- if (mDontClampNextScroll) {
- range = Math.max(range, oldY);
+ if (oldY != y) {
+ int range = getScrollRange();
+ if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
+ // This frame takes us into overscroll, so set the max overscroll based on
+ // the current velocity
+ setMaxOverScrollFromCurrentVelocity();
+ }
+
+ if (mDontClampNextScroll) {
+ range = Math.max(range, oldY);
+ }
+ customOverScrollBy(y - oldY, oldY, range,
+ (int) (mMaxOverScroll));
}
- customOverScrollBy(y - oldY, oldY, range,
- (int) (mMaxOverScroll));
}
- postOnAnimation(mAnimateScroll);
+ postOnAnimation(mReflingAndAnimateScroll);
} else {
mDontClampNextScroll = false;
if (mFinishScrollingCallback != null) {
@@ -1689,26 +1810,67 @@
}
}
- @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
- private boolean customOverScrollBy(int deltaY, int scrollY, int scrollRangeY,
- int maxOverScrollY) {
-
- int newScrollY = scrollY + deltaY;
- final int top = -maxOverScrollY;
- final int bottom = maxOverScrollY + scrollRangeY;
-
- boolean clampedY = false;
- if (newScrollY > bottom) {
- newScrollY = bottom;
- clampedY = true;
- } else if (newScrollY < top) {
- newScrollY = top;
- clampedY = true;
+ private void setMaxOverScrollFromCurrentVelocity() {
+ float currVelocity = mScroller.getCurrVelocity();
+ if (currVelocity >= mMinimumVelocity) {
+ mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
}
+ }
- onCustomOverScrolled(newScrollY, clampedY);
+ /**
+ * Scrolls by the given delta, overscrolling if needed. If called during a fling and the delta
+ * would cause us to exceed the provided maximum overscroll, springs back instead.
+ *
+ * This method performs the determination of whether we're exceeding the overscroll and clamps
+ * the scroll amount if so. The actual scrolling/overscrolling happens in
+ * {@link #onCustomOverScrolled(int, boolean)} (absolute scrolling) or
+ * {@link #onCustomOverScrolledBy(int, boolean)} (anchor scrolling).
+ *
+ * @param deltaY The (signed) number of pixels to scroll.
+ * @param scrollY The current scroll position (absolute scrolling only).
+ * @param scrollRangeY The maximum allowable scroll position (absolute scrolling only).
+ * @param maxOverScrollY The current (unsigned) limit on number of pixels to overscroll by.
+ */
+ @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
+ private void customOverScrollBy(int deltaY, int scrollY, int scrollRangeY, int maxOverScrollY) {
+ if (ANCHOR_SCROLLING) {
+ boolean clampedY = false;
+ if (deltaY < 0) {
+ int maxScrollAmount = getMaxNegativeScrollAmount();
+ if (maxScrollAmount > Integer.MIN_VALUE) {
+ maxScrollAmount -= maxOverScrollY;
+ if (deltaY < maxScrollAmount) {
+ deltaY = maxScrollAmount;
+ clampedY = true;
+ }
+ }
+ } else {
+ int maxScrollAmount = getMaxPositiveScrollAmount();
+ if (maxScrollAmount < Integer.MAX_VALUE) {
+ maxScrollAmount += maxOverScrollY;
+ if (deltaY > maxScrollAmount) {
+ deltaY = maxScrollAmount;
+ clampedY = true;
+ }
+ }
+ }
+ onCustomOverScrolledBy(deltaY, clampedY);
+ } else {
+ int newScrollY = scrollY + deltaY;
+ final int top = -maxOverScrollY;
+ final int bottom = maxOverScrollY + scrollRangeY;
- return clampedY;
+ boolean clampedY = false;
+ if (newScrollY > bottom) {
+ newScrollY = bottom;
+ clampedY = true;
+ } else if (newScrollY < top) {
+ newScrollY = top;
+ clampedY = true;
+ }
+
+ onCustomOverScrolled(newScrollY, clampedY);
+ }
}
/**
@@ -1826,8 +1988,46 @@
}
}
+ /**
+ * Scrolls by the given delta, overscrolling if needed. If called during a fling and the delta
+ * would cause us to exceed the provided maximum overscroll, springs back instead.
+ *
+ * @param deltaY The (signed) number of pixels to scroll.
+ * @param clampedY Whether this value was clamped by the calling method, meaning we've reached
+ * the overscroll limit.
+ */
+ private void onCustomOverScrolledBy(int deltaY, boolean clampedY) {
+ assert ANCHOR_SCROLLING;
+ mScrollAnchorViewY -= deltaY;
+ // Treat animating scrolls differently; see #computeScroll() for why.
+ if (!mScroller.isFinished()) {
+ if (clampedY) {
+ springBack();
+ } else {
+ float overScrollTop = getCurrentOverScrollAmount(true /* top */);
+ if (isScrolledToTop() && mScrollAnchorViewY > 0) {
+ notifyOverscrollTopListener(mScrollAnchorViewY,
+ isRubberbanded(true /* onTop */));
+ } else {
+ notifyOverscrollTopListener(overScrollTop, isRubberbanded(true /* onTop */));
+ }
+ }
+ }
+ updateScrollAnchor();
+ updateOnScrollChange();
+ }
+
+ /**
+ * Scrolls to the given position, overscrolling if needed. If called during a fling and the
+ * position exceeds the provided maximum overscroll, springs back instead.
+ *
+ * @param scrollY The target scroll position.
+ * @param clampedY Whether this value was clamped by the calling method, meaning we've reached
+ * the overscroll limit.
+ */
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
private void onCustomOverScrolled(int scrollY, boolean clampedY) {
+ assert !ANCHOR_SCROLLING;
// Treat animating scrolls differently; see #computeScroll() for why.
if (!mScroller.isFinished()) {
setOwnScrollY(scrollY);
@@ -1846,27 +2046,51 @@
}
}
+ /**
+ * Springs back from an overscroll by stopping the {@link #mScroller} and animating the
+ * overscroll amount back to zero.
+ */
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
private void springBack() {
- int scrollRange = getScrollRange();
- boolean overScrolledTop = mOwnScrollY <= 0;
- boolean overScrolledBottom = mOwnScrollY >= scrollRange;
- if (overScrolledTop || overScrolledBottom) {
- boolean onTop;
- float newAmount;
- if (overScrolledTop) {
- onTop = true;
- newAmount = -mOwnScrollY;
- setOwnScrollY(0);
- mDontReportNextOverScroll = true;
- } else {
- onTop = false;
- newAmount = mOwnScrollY - scrollRange;
- setOwnScrollY(scrollRange);
+ if (ANCHOR_SCROLLING) {
+ boolean overScrolledTop = isScrolledToTop() && mScrollAnchorViewY > 0;
+ int maxPositiveScrollAmount = getMaxPositiveScrollAmount();
+ boolean overscrolledBottom = maxPositiveScrollAmount < 0;
+ if (overScrolledTop || overscrolledBottom) {
+ float newAmount;
+ if (overScrolledTop) {
+ newAmount = mScrollAnchorViewY;
+ mScrollAnchorViewY = 0;
+ mDontReportNextOverScroll = true;
+ } else {
+ newAmount = -maxPositiveScrollAmount;
+ mScrollAnchorViewY -= maxPositiveScrollAmount;
+ }
+ setOverScrollAmount(newAmount, overScrolledTop, false);
+ setOverScrollAmount(0.0f, overScrolledTop, true);
+ mScroller.forceFinished(true);
}
- setOverScrollAmount(newAmount, onTop, false);
- setOverScrollAmount(0.0f, onTop, true);
- mScroller.forceFinished(true);
+ } else {
+ int scrollRange = getScrollRange();
+ boolean overScrolledTop = mOwnScrollY <= 0;
+ boolean overScrolledBottom = mOwnScrollY >= scrollRange;
+ if (overScrolledTop || overScrolledBottom) {
+ boolean onTop;
+ float newAmount;
+ if (overScrolledTop) {
+ onTop = true;
+ newAmount = -mOwnScrollY;
+ setOwnScrollY(0);
+ mDontReportNextOverScroll = true;
+ } else {
+ onTop = false;
+ newAmount = mOwnScrollY - scrollRange;
+ setOwnScrollY(scrollRange);
+ }
+ setOverScrollAmount(newAmount, onTop, false);
+ setOverScrollAmount(0.0f, onTop, true);
+ mScroller.forceFinished(true);
+ }
}
}
@@ -1971,6 +2195,17 @@
return null;
}
+ private ExpandableNotificationRow getLastRowNotGone() {
+ int childCount = getChildCount();
+ for (int i = childCount - 1; i >= 0; i--) {
+ View child = getChildAt(i);
+ if (child instanceof ExpandableNotificationRow && child.getVisibility() != View.GONE) {
+ return (ExpandableNotificationRow) child;
+ }
+ }
+ return null;
+ }
+
/**
* @return the number of children which have visibility unequal to GONE
*/
@@ -2081,8 +2316,8 @@
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
private void updateForwardAndBackwardScrollability() {
- boolean forwardScrollable = mScrollable && mOwnScrollY < getScrollRange();
- boolean backwardsScrollable = mScrollable && mOwnScrollY > 0;
+ boolean forwardScrollable = mScrollable && !isScrolledToBottom();
+ boolean backwardsScrollable = mScrollable && !isScrolledToTop();
boolean changed = forwardScrollable != mForwardScrollable
|| backwardsScrollable != mBackwardScrollable;
mForwardScrollable = forwardScrollable;
@@ -2403,18 +2638,24 @@
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
protected void fling(int velocityY) {
if (getChildCount() > 0) {
- int scrollRange = getScrollRange();
-
float topAmount = getCurrentOverScrollAmount(true);
float bottomAmount = getCurrentOverScrollAmount(false);
if (velocityY < 0 && topAmount > 0) {
- setOwnScrollY(mOwnScrollY - (int) topAmount);
+ if (ANCHOR_SCROLLING) {
+ mScrollAnchorViewY += topAmount;
+ } else {
+ setOwnScrollY(mOwnScrollY - (int) topAmount);
+ }
mDontReportNextOverScroll = true;
setOverScrollAmount(0, true, false);
mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
* mOverflingDistance + topAmount;
} else if (velocityY > 0 && bottomAmount > 0) {
- setOwnScrollY((int) (mOwnScrollY + bottomAmount));
+ if (ANCHOR_SCROLLING) {
+ mScrollAnchorViewY -= bottomAmount;
+ } else {
+ setOwnScrollY((int) (mOwnScrollY + bottomAmount));
+ }
setOverScrollAmount(0, false, false);
mMaxOverScroll = Math.abs(velocityY) / 1000f
* getRubberBandFactor(false /* onTop */) * mOverflingDistance
@@ -2423,18 +2664,138 @@
// it will be set once we reach the boundary
mMaxOverScroll = 0.0f;
}
- int minScrollY = Math.max(0, scrollRange);
- if (mExpandedInThisMotion) {
- minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand);
+ if (ANCHOR_SCROLLING) {
+ flingScroller(velocityY);
+ } else {
+ int scrollRange = getScrollRange();
+ int minScrollY = Math.max(0, scrollRange);
+ if (mExpandedInThisMotion) {
+ minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand);
+ }
+ mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0, minScrollY, 0,
+ mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2);
}
- mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0, minScrollY, 0,
- mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2);
animateScroll();
}
}
/**
+ * Flings the overscroller with the given velocity (anchor-based scrolling).
+ *
+ * Because anchor-based scrolling can't track the current scroll position, the overscroller is
+ * always started at startY = 0, and we interpret the positions it computes as relative to the
+ * start of the scroll.
+ */
+ private void flingScroller(int velocityY) {
+ assert ANCHOR_SCROLLING;
+ mIsScrollerBoundSet = false;
+ maybeFlingScroller(velocityY, true /* always fling */);
+ }
+
+ private void maybeFlingScroller(int velocityY, boolean alwaysFling) {
+ assert ANCHOR_SCROLLING;
+ // Attempt to determine the maximum amount to scroll before we reach the end.
+ // If the first view is not materialized (for an upwards scroll) or the last view is either
+ // not materialized or is pinned to the shade (for a downwards scroll), we don't know this
+ // amount, so we do an unbounded fling and rely on {@link #maybeReflingScroller()} to update
+ // the scroller once we approach the start/end of the list.
+ int minY = Integer.MIN_VALUE;
+ int maxY = Integer.MAX_VALUE;
+ if (velocityY < 0) {
+ minY = getMaxNegativeScrollAmount();
+ if (minY > Integer.MIN_VALUE) {
+ mIsScrollerBoundSet = true;
+ }
+ } else {
+ maxY = getMaxPositiveScrollAmount();
+ if (maxY < Integer.MAX_VALUE) {
+ mIsScrollerBoundSet = true;
+ }
+ }
+ if (mIsScrollerBoundSet || alwaysFling) {
+ mLastScrollerY = 0;
+ // x velocity is set to 1 to avoid overscroller bug
+ mScroller.fling(0, 0, 1, velocityY, 0, 0, minY, maxY, 0,
+ mExpandedInThisMotion && !isScrolledToTop() ? 0 : Integer.MAX_VALUE / 2);
+ }
+ }
+
+ /**
+ * Returns the maximum number of pixels we can scroll in the positive direction (downwards)
+ * before reaching the bottom of the list (discounting overscroll).
+ *
+ * If the return value is negative then we have overscrolled; this is a transient state which
+ * should immediately be handled by adjusting the anchor position and adding the extra space to
+ * the bottom overscroll amount.
+ *
+ * If we don't know how many pixels we have left to scroll (because the last row has not been
+ * materialized, or it's in the shelf so it doesn't have its "natural" position), we return
+ * {@link Integer#MAX_VALUE}.
+ */
+ private int getMaxPositiveScrollAmount() {
+ assert ANCHOR_SCROLLING;
+ // TODO: once we're recycling we need to check the adapter position of the last child.
+ ExpandableNotificationRow lastRow = getLastRowNotGone();
+ if (mScrollAnchorView != null && lastRow != null && !lastRow.isInShelf()) {
+ // distance from bottom of last child to bottom of notifications area is:
+ // distance from bottom of last child
+ return (int) (lastRow.getTranslationY() + lastRow.getActualHeight()
+ // to top of anchor view
+ - mScrollAnchorView.getTranslationY()
+ // plus distance from anchor view to top of notifications area
+ + mScrollAnchorViewY
+ // minus height of notifications area.
+ - (mMaxLayoutHeight - getIntrinsicPadding() - mFooterView.getActualHeight()));
+ } else {
+ return Integer.MAX_VALUE;
+ }
+ }
+
+ /**
+ * Returns the maximum number of pixels (as a negative number) we can scroll in the negative
+ * direction (upwards) before reaching the top of the list (discounting overscroll).
+ *
+ * If the return value is positive then we have overscrolled; this is a transient state which
+ * should immediately be handled by adjusting the anchor position and adding the extra space to
+ * the top overscroll amount.
+ *
+ * If we don't know how many pixels we have left to scroll (because the first row has not been
+ * materialized), we return {@link Integer#MIN_VALUE}.
+ */
+ private int getMaxNegativeScrollAmount() {
+ assert ANCHOR_SCROLLING;
+ // TODO: once we're recycling we need to check the adapter position of the first child.
+ ExpandableView firstChild = getFirstChildNotGone();
+ if (mScrollAnchorView != null && firstChild != null) {
+ // distance from top of first child to top of notifications area is:
+ // distance from top of anchor view
+ return (int) -(mScrollAnchorView.getTranslationY()
+ // to top of first child
+ - firstChild.getTranslationY()
+ // minus distance from top of anchor view to top of notifications area.
+ - mScrollAnchorViewY);
+ } else {
+ return Integer.MIN_VALUE;
+ }
+ }
+
+ /**
+ * During a fling, if we were unable to set the bounds of the fling due to the top/bottom view
+ * not being materialized or being pinned to the shelf, we need to check on every frame if we're
+ * able to set the bounds. If we are, we fling the scroller again with the newly computed
+ * bounds.
+ */
+ private void maybeReflingScroller() {
+ if (!mIsScrollerBoundSet) {
+ // Because mScroller is a flywheel scroller, we fling with the minimum possible
+ // velocity to establish direction, so as not to perceptibly affect the velocity.
+ maybeFlingScroller((int) Math.signum(mScroller.getCurrVelocity()),
+ false /* alwaysFling */);
+ }
+ }
+
+ /**
* @return Whether a fling performed on the top overscroll edge lead to the expanded
* overScroll view (i.e QS).
*/
@@ -2485,25 +2846,10 @@
}
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
- public int getFirstChildIntrinsicHeight() {
- final ExpandableView firstChild = getFirstChildNotGone();
- int firstChildMinHeight = firstChild != null
- ? firstChild.getIntrinsicHeight()
- : mEmptyShadeView != null
- ? mEmptyShadeView.getIntrinsicHeight()
- : mCollapsedSize;
- if (mOwnScrollY > 0) {
- firstChildMinHeight = Math.max(firstChildMinHeight - mOwnScrollY, mCollapsedSize);
- }
- return firstChildMinHeight;
- }
-
- @ShadeViewRefactor(RefactorComponent.COORDINATOR)
public float getTopPaddingOverflow() {
return mTopPaddingOverflow;
}
-
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
public int getPeekHeight() {
final ExpandableView firstChild = getFirstChildNotGone();
@@ -2711,30 +3057,51 @@
*/
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
private void updateScrollStateForRemovedChild(ExpandableView removedChild) {
- int startingPosition = getPositionInLinearLayout(removedChild);
- float increasedPaddingAmount = removedChild.getIncreasedPaddingAmount();
- int padding;
- if (increasedPaddingAmount >= 0) {
- padding = (int) NotificationUtils.interpolate(
- mPaddingBetweenElements,
- mIncreasedPaddingBetweenElements,
- increasedPaddingAmount);
+ if (ANCHOR_SCROLLING) {
+ if (removedChild == mScrollAnchorView) {
+ ExpandableView firstChild = getFirstChildNotGone();
+ if (firstChild != null) {
+ mScrollAnchorView = firstChild;
+ } else {
+ mScrollAnchorView = mShelf;
+ }
+ // Adjust anchor view Y by the distance between the old and new anchors
+ // so that there's no visible change.
+ mScrollAnchorViewY +=
+ mScrollAnchorView.getTranslationY() - removedChild.getTranslationY();
+ }
+ updateScrollAnchor();
+ // TODO: once we're recycling this will need to check the adapter position of the child
+ if (mScrollAnchorView == getFirstChildNotGone() && mScrollAnchorViewY > 0) {
+ mScrollAnchorViewY = 0;
+ }
+ updateOnScrollChange();
} else {
- padding = (int) NotificationUtils.interpolate(
- 0,
- mPaddingBetweenElements,
- 1.0f + increasedPaddingAmount);
- }
- int childHeight = getIntrinsicHeight(removedChild) + padding;
- int endPosition = startingPosition + childHeight;
- if (endPosition <= mOwnScrollY) {
- // This child is fully scrolled of the top, so we have to deduct its height from the
- // scrollPosition
- setOwnScrollY(mOwnScrollY - childHeight);
- } else if (startingPosition < mOwnScrollY) {
- // This child is currently being scrolled into, set the scroll position to the start of
- // this child
- setOwnScrollY(startingPosition);
+ int startingPosition = getPositionInLinearLayout(removedChild);
+ float increasedPaddingAmount = removedChild.getIncreasedPaddingAmount();
+ int padding;
+ if (increasedPaddingAmount >= 0) {
+ padding = (int) NotificationUtils.interpolate(
+ mPaddingBetweenElements,
+ mIncreasedPaddingBetweenElements,
+ increasedPaddingAmount);
+ } else {
+ padding = (int) NotificationUtils.interpolate(
+ 0,
+ mPaddingBetweenElements,
+ 1.0f + increasedPaddingAmount);
+ }
+ int childHeight = getIntrinsicHeight(removedChild) + padding;
+ int endPosition = startingPosition + childHeight;
+ if (endPosition <= mOwnScrollY) {
+ // This child is fully scrolled of the top, so we have to deduct its height from the
+ // scrollPosition
+ setOwnScrollY(mOwnScrollY - childHeight);
+ } else if (startingPosition < mOwnScrollY) {
+ // This child is currently being scrolled into, set the scroll position to the
+ // start of this child
+ setOwnScrollY(startingPosition);
+ }
}
}
@@ -2888,6 +3255,14 @@
generateAddAnimation(child, false /* fromMoreCard */);
updateAnimationState(child);
updateChronometerForChild(child);
+ if (ANCHOR_SCROLLING) {
+ // TODO: once we're recycling this will need to check the adapter position of the child
+ if (child == getFirstChildNotGone() && (isScrolledToTop() || !mIsExpanded)) {
+ // New child was added at the top while we're scrolled to the top;
+ // make it the new anchor view so that we stay at the top.
+ mScrollAnchorView = child;
+ }
+ }
}
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
@@ -3381,17 +3756,24 @@
final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
if (vscroll != 0) {
final int delta = (int) (vscroll * getVerticalScrollFactor());
- final int range = getScrollRange();
- int oldScrollY = mOwnScrollY;
- int newScrollY = oldScrollY - delta;
- if (newScrollY < 0) {
- newScrollY = 0;
- } else if (newScrollY > range) {
- newScrollY = range;
- }
- if (newScrollY != oldScrollY) {
- setOwnScrollY(newScrollY);
- return true;
+ if (ANCHOR_SCROLLING) {
+ mScrollAnchorViewY -= delta;
+ updateScrollAnchor();
+ clampScrollPosition();
+ updateOnScrollChange();
+ } else {
+ final int range = getScrollRange();
+ int oldScrollY = mOwnScrollY;
+ int newScrollY = oldScrollY - delta;
+ if (newScrollY < 0) {
+ newScrollY = 0;
+ } else if (newScrollY > range) {
+ newScrollY = range;
+ }
+ if (newScrollY != oldScrollY) {
+ setOwnScrollY(newScrollY);
+ return true;
+ }
}
}
}
@@ -3459,12 +3841,16 @@
if (mIsBeingDragged) {
// Scroll to follow the motion event
mLastMotionY = y;
- int range = getScrollRange();
- if (mExpandedInThisMotion) {
- range = Math.min(range, mMaxScrollAfterExpand);
- }
-
float scrollAmount;
+ int range;
+ if (ANCHOR_SCROLLING) {
+ range = 0; // unused in the methods it's being passed to
+ } else {
+ range = getScrollRange();
+ if (mExpandedInThisMotion) {
+ range = Math.min(range, mMaxScrollAfterExpand);
+ }
+ }
if (deltaY < 0) {
scrollAmount = overScrollDown(deltaY);
} else {
@@ -3501,9 +3887,13 @@
onOverScrollFling(false, initialVelocity);
}
} else {
- if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
- getScrollRange())) {
- animateScroll();
+ if (ANCHOR_SCROLLING) {
+ // TODO
+ } else {
+ if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
+ getScrollRange())) {
+ animateScroll();
+ }
}
}
}
@@ -3515,8 +3905,13 @@
break;
case MotionEvent.ACTION_CANCEL:
if (mIsBeingDragged && getChildCount() > 0) {
- if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
- animateScroll();
+ if (ANCHOR_SCROLLING) {
+ // TODO
+ } else {
+ if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
+ getScrollRange())) {
+ animateScroll();
+ }
}
mActivePointerId = INVALID_POINTER;
endDrag();
@@ -3585,12 +3980,6 @@
}
}
- @ShadeViewRefactor(RefactorComponent.INPUT)
- private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) {
- ev.offsetLocation(sourceView.getX(), sourceView.getY());
- ev.offsetLocation(-targetView.getX(), -targetView.getY());
- }
-
@Override
@ShadeViewRefactor(RefactorComponent.INPUT)
public boolean onInterceptTouchEvent(MotionEvent ev) {
@@ -3763,8 +4152,12 @@
setIsBeingDragged(false);
mActivePointerId = INVALID_POINTER;
recycleVelocityTracker();
- if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
- animateScroll();
+ if (ANCHOR_SCROLLING) {
+ // TODO
+ } else {
+ if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
+ animateScroll();
+ }
}
break;
case MotionEvent.ACTION_POINTER_UP:
@@ -3839,14 +4232,20 @@
case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
// fall through
case android.R.id.accessibilityActionScrollUp:
- final int viewportHeight = getHeight() - mPaddingBottom - mTopPadding - mPaddingTop
- - mShelf.getIntrinsicHeight();
- final int targetScrollY = Math.max(0,
- Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange()));
- if (targetScrollY != mOwnScrollY) {
- mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScrollY - mOwnScrollY);
- animateScroll();
- return true;
+ if (ANCHOR_SCROLLING) {
+ // TODO
+ } else {
+ final int viewportHeight =
+ getHeight() - mPaddingBottom - mTopPadding - mPaddingTop
+ - mShelf.getIntrinsicHeight();
+ final int targetScrollY = Math.max(0,
+ Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange()));
+ if (targetScrollY != mOwnScrollY) {
+ mScroller.startScroll(mScrollX, mOwnScrollY, 0,
+ targetScrollY - mOwnScrollY);
+ animateScroll();
+ return true;
+ }
}
break;
}
@@ -3905,13 +4304,23 @@
@Override
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
public boolean isScrolledToTop() {
- return mOwnScrollY == 0;
+ if (ANCHOR_SCROLLING) {
+ updateScrollAnchor();
+ // TODO: once we're recycling this will need to check the adapter position of the child
+ return mScrollAnchorView == getFirstChildNotGone() && mScrollAnchorViewY >= 0;
+ } else {
+ return mOwnScrollY == 0;
+ }
}
@Override
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
public boolean isScrolledToBottom() {
- return mOwnScrollY >= getScrollRange();
+ if (ANCHOR_SCROLLING) {
+ return getMaxPositiveScrollAmount() <= 0;
+ } else {
+ return mOwnScrollY >= getScrollRange();
+ }
}
@Override
@@ -3953,7 +4362,7 @@
resetCheckSnoozeLeavebehind();
mAmbientState.setExpansionChanging(false);
if (!mIsExpanded) {
- setOwnScrollY(0);
+ resetScrollPosition();
mStatusBar.resetUserExpandedStates();
clearTemporaryViews();
clearUserLockedViews();
@@ -4012,7 +4421,14 @@
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
public void resetScrollPosition() {
mScroller.abortAnimation();
- setOwnScrollY(0);
+ if (ANCHOR_SCROLLING) {
+ // TODO: once we're recycling this will need to modify the adapter position instead
+ mScrollAnchorView = getFirstChildNotGone();
+ mScrollAnchorViewY = 0;
+ updateOnScrollChange();
+ } else {
+ setOwnScrollY(0);
+ }
}
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
@@ -4081,6 +4497,7 @@
private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
if (view instanceof ExpandableNotificationRow && !onKeyguard()) {
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ // TODO: once we're recycling this will need to check the adapter position of the child
if (row.isUserLocked() && row != getFirstChildNotGone()) {
if (row.isSummaryWithChildren()) {
return;
@@ -4098,7 +4515,13 @@
layoutEnd -= mShelf.getIntrinsicHeight() + mPaddingBetweenElements;
}
if (endPosition > layoutEnd) {
- setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd));
+ if (ANCHOR_SCROLLING) {
+ mScrollAnchorViewY -= (endPosition - layoutEnd);
+ updateScrollAnchor();
+ updateOnScrollChange();
+ } else {
+ setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd));
+ }
mDisallowScrollingInThisMotion = true;
}
}
@@ -4663,9 +5086,13 @@
super.onInitializeAccessibilityEventInternal(event);
event.setScrollable(mScrollable);
event.setScrollX(mScrollX);
- event.setScrollY(mOwnScrollY);
event.setMaxScrollX(mScrollX);
- event.setMaxScrollY(getScrollRange());
+ if (ANCHOR_SCROLLING) {
+ // TODO
+ } else {
+ event.setScrollY(mOwnScrollY);
+ event.setMaxScrollY(getScrollRange());
+ }
}
@Override
@@ -4850,13 +5277,63 @@
}
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
- public void setOwnScrollY(int ownScrollY) {
+ private void setOwnScrollY(int ownScrollY) {
+ assert !ANCHOR_SCROLLING;
if (ownScrollY != mOwnScrollY) {
// We still want to call the normal scrolled changed for accessibility reasons
onScrollChanged(mScrollX, ownScrollY, mScrollX, mOwnScrollY);
mOwnScrollY = ownScrollY;
- updateForwardAndBackwardScrollability();
- requestChildrenUpdate();
+ updateOnScrollChange();
+ }
+ }
+
+ private void updateOnScrollChange() {
+ updateForwardAndBackwardScrollability();
+ requestChildrenUpdate();
+ }
+
+ private void updateScrollAnchor() {
+ int anchorIndex = indexOfChild(mScrollAnchorView);
+ // If the anchor view has been scrolled off the top, move to the next view.
+ while (mScrollAnchorViewY < 0) {
+ View nextAnchor = null;
+ for (int i = anchorIndex + 1; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (child.getVisibility() != View.GONE
+ && child instanceof ExpandableNotificationRow) {
+ anchorIndex = i;
+ nextAnchor = child;
+ break;
+ }
+ }
+ if (nextAnchor == null) {
+ break;
+ }
+ mScrollAnchorViewY +=
+ (int) (nextAnchor.getTranslationY() - mScrollAnchorView.getTranslationY());
+ mScrollAnchorView = nextAnchor;
+ }
+ // If the view above the anchor view is fully visible, make it the anchor view.
+ while (anchorIndex > 0 && mScrollAnchorViewY > 0) {
+ View prevAnchor = null;
+ for (int i = anchorIndex - 1; i >= 0; i--) {
+ View child = getChildAt(i);
+ if (child.getVisibility() != View.GONE
+ && child instanceof ExpandableNotificationRow) {
+ anchorIndex = i;
+ prevAnchor = child;
+ break;
+ }
+ }
+ if (prevAnchor == null) {
+ break;
+ }
+ float distanceToPreviousAnchor =
+ mScrollAnchorView.getTranslationY() - prevAnchor.getTranslationY();
+ if (distanceToPreviousAnchor < mScrollAnchorViewY) {
+ mScrollAnchorViewY -= (int) distanceToPreviousAnchor;
+ mScrollAnchorView = prevAnchor;
+ }
}
}
@@ -4872,6 +5349,9 @@
mAmbientState.setShelf(shelf);
mStateAnimator.setShelf(shelf);
shelf.bind(mAmbientState, this);
+ if (ANCHOR_SCROLLING) {
+ mScrollAnchorView = mShelf;
+ }
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@@ -5818,6 +6298,15 @@
}
@Override
+ public int getConstrainSwipeStartPosition() {
+ NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow();
+ if (menuRow != null) {
+ return Math.abs(menuRow.getMenuSnapTarget());
+ }
+ return 0;
+ }
+
+ @Override
public boolean canChildBeDismissed(View v) {
return NotificationStackScrollLayout.this.canChildBeDismissed(v);
}
@@ -6013,7 +6502,11 @@
public void expansionStateChanged(boolean isExpanding) {
mExpandingNotification = isExpanding;
if (!mExpandedInThisMotion) {
- mMaxScrollAfterExpand = mOwnScrollY;
+ if (ANCHOR_SCROLLING) {
+ // TODO
+ } else {
+ mMaxScrollAfterExpand = mOwnScrollY;
+ }
mExpandedInThisMotion = true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 25fb7f9..2a88080 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -41,6 +41,8 @@
*/
public class StackScrollAlgorithm {
+ static final boolean ANCHOR_SCROLLING = false;
+
private static final String LOG_TAG = "StackScrollAlgorithm";
private final ViewGroup mHostView;
@@ -236,6 +238,10 @@
scrollY = Math.max(0, scrollY);
state.scrollY = (int) (scrollY + bottomOverScroll);
+ if (ANCHOR_SCROLLING) {
+ state.anchorViewY = (int) (ambientState.getAnchorViewY() - bottomOverScroll);
+ }
+
//now init the visible children and update paddings
int childCount = hostView.getChildCount();
state.visibleChildren.clear();
@@ -252,6 +258,11 @@
// iterating over it again, it's filled with the actual resolved value.
for (int i = 0; i < childCount; i++) {
+ if (ANCHOR_SCROLLING) {
+ if (i == ambientState.getAnchorViewIndex()) {
+ state.anchorViewIndex = state.visibleChildren.size();
+ }
+ }
ExpandableView v = (ExpandableView) hostView.getChildAt(i);
if (v.getVisibility() != View.GONE) {
if (v == ambientState.getShelf()) {
@@ -350,28 +361,74 @@
private void updatePositionsForState(StackScrollAlgorithmState algorithmState,
AmbientState ambientState) {
- // The y coordinate of the current child.
- float currentYPosition = -algorithmState.scrollY;
- int childCount = algorithmState.visibleChildren.size();
- for (int i = 0; i < childCount; i++) {
- currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition);
+ if (ANCHOR_SCROLLING) {
+ float currentYPosition = algorithmState.anchorViewY;
+ int childCount = algorithmState.visibleChildren.size();
+ for (int i = algorithmState.anchorViewIndex; i < childCount; i++) {
+ if (i > algorithmState.anchorViewIndex && ambientState.beginsNewSection(i)) {
+ currentYPosition += mGapHeight;
+ }
+ currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition,
+ false /* reverse */);
+ }
+ currentYPosition = algorithmState.anchorViewY;
+ for (int i = algorithmState.anchorViewIndex - 1; i >= 0; i--) {
+ if (ambientState.beginsNewSection(i + 1)) {
+ currentYPosition -= mGapHeight;
+ }
+ currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition,
+ true /* reverse */);
+ }
+ } else {
+ // The y coordinate of the current child.
+ float currentYPosition = -algorithmState.scrollY;
+ int childCount = algorithmState.visibleChildren.size();
+ for (int i = 0; i < childCount; i++) {
+ if (ambientState.beginsNewSection(i)) {
+ currentYPosition += mGapHeight;
+ }
+ currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition,
+ false /* reverse */);
+ }
}
}
+ /**
+ * Populates the {@link ExpandableViewState} for a single child.
+ *
+ * @param i The index of the child in
+ * {@link StackScrollAlgorithmState#visibleChildren}.
+ * @param algorithmState The overall output state of the algorithm.
+ * @param ambientState The input state provided to the algorithm.
+ * @param currentYPosition The Y position of the current pass of the algorithm. For a forward
+ * pass, this should be the top of the child; for a reverse pass, the
+ * bottom of the child.
+ * @param reverse Whether we're laying out children in the reverse direction (Y
+ * positions
+ * decreasing) instead of the forward direction (Y positions
+ * increasing).
+ * @return The Y position after laying out the child. This will be the {@code currentYPosition}
+ * for the next call to this method, after adjusting for any gaps between children.
+ */
protected float updateChild(
int i,
StackScrollAlgorithmState algorithmState,
AmbientState ambientState,
- float currentYPosition) {
+ float currentYPosition,
+ boolean reverse) {
ExpandableView child = algorithmState.visibleChildren.get(i);
ExpandableViewState childViewState = child.getViewState();
childViewState.location = ExpandableViewState.LOCATION_UNKNOWN;
int paddingAfterChild = getPaddingAfterChild(algorithmState, child);
int childHeight = getMaxAllowedChildHeight(child);
- if (ambientState.beginsNewSection(i)) {
- currentYPosition += mGapHeight;
+ if (reverse) {
+ childViewState.yTranslation = currentYPosition - (childHeight + paddingAfterChild);
+ if (currentYPosition <= 0) {
+ childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
+ }
+ } else {
+ childViewState.yTranslation = currentYPosition;
}
- childViewState.yTranslation = currentYPosition;
boolean isFooterView = child instanceof FooterView;
boolean isEmptyShadeView = child instanceof EmptyShadeView;
@@ -396,9 +453,13 @@
clampPositionToShelf(child, childViewState, ambientState);
}
- currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild;
- if (currentYPosition <= 0) {
- childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
+ if (reverse) {
+ currentYPosition = childViewState.yTranslation;
+ } else {
+ currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild;
+ if (currentYPosition <= 0) {
+ childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
+ }
}
if (childViewState.location == ExpandableViewState.LOCATION_UNKNOWN) {
Log.wtf(LOG_TAG, "Failed to assign location for child " + i);
@@ -464,6 +525,7 @@
// To check if the row need to do translation according to scroll Y
// heads up show full of row's content and any scroll y indicate that the
// translationY need to move up the HUN.
+ // TODO: fix this check for anchor scrolling.
if (!mIsExpanded && isTopEntry && ambientState.getScrollY() > 0) {
childState.yTranslation -= ambientState.getScrollY();
}
@@ -607,10 +669,18 @@
public class StackScrollAlgorithmState {
/**
- * The scroll position of the algorithm
+ * The scroll position of the algorithm (absolute scrolling).
*/
public int scrollY;
+ /** The index of the anchor view (anchor scrolling). */
+ public int anchorViewIndex;
+
+ /**
+ * The Y position, relative to the top of the screen, of the anchor view (anchor scrolling).
+ */
+ public int anchorViewY;
+
/**
* The children from the host view which are not gone.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
index 2a11c26..d022808 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
@@ -97,10 +97,11 @@
}
return mClickableChildren
.stream()
- .filter(v -> v.isAttachedToWindow())
+ .filter(View::isAttachedToWindow)
.map(v -> new Pair<>(distance(v, event), v))
.min(Comparator.comparingInt(f -> f.first))
- .get().second;
+ .map(data -> data.second)
+ .orElse(null);
}
private int distance(View v, MotionEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 4f61009..86326be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -64,6 +64,7 @@
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -304,8 +305,11 @@
final int count =
mEntryManager.getNotificationData().getActiveNotifications().size();
final int rank = mEntryManager.getNotificationData().getRank(notificationKey);
+ NotificationVisibility.NotificationLocation location =
+ NotificationLogger.getNotificationLocation(
+ mEntryManager.getNotificationData().get(notificationKey));
final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey,
- rank, count, true);
+ rank, count, true, location);
try {
mBarService.onNotificationClick(notificationKey, nv);
} catch (RemoteException ex) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
index 88f9048..ffaa236 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
@@ -69,13 +69,13 @@
private final WindowManager mWindowManager;
private final IActivityManager mActivityManager;
private final DozeParameters mDozeParameters;
+ private final WindowManager.LayoutParams mLpChanged;
+ private final boolean mKeyguardScreenRotation;
private ViewGroup mStatusBarView;
private WindowManager.LayoutParams mLp;
- private WindowManager.LayoutParams mLpChanged;
private boolean mHasTopUi;
private boolean mHasTopUiChanged;
private int mBarHeight;
- private final boolean mKeyguardScreenRotation;
private float mScreenBrightnessDoze;
private final State mCurrentState = new State();
private OtherwisedCollapsedListener mListener;
@@ -97,6 +97,7 @@
mKeyguardScreenRotation = shouldEnableKeyguardScreenRotation();
mDozeParameters = dozeParameters;
mScreenBrightnessDoze = mDozeParameters.getScreenBrightnessDoze();
+ mLpChanged = new WindowManager.LayoutParams();
Dependency.get(StatusBarStateController.class).addCallback(
mStateListener, StatusBarStateController.RANK_STATUS_BAR_WINDOW_CONTROLLER);
Dependency.get(ConfigurationController.class).addCallback(this);
@@ -138,7 +139,6 @@
mStatusBarView = statusBarView;
mBarHeight = barHeight;
mWindowManager.addView(mStatusBarView, mLp);
- mLpChanged = new WindowManager.LayoutParams();
mLpChanged.copyFrom(mLp);
onThemeChanged();
}
@@ -228,7 +228,9 @@
private void applyHeight(State state) {
boolean expanded = isExpanded(state);
if (state.forcePluginOpen) {
- mListener.setWouldOtherwiseCollapse(expanded);
+ if (mListener != null) {
+ mListener.setWouldOtherwiseCollapse(expanded);
+ }
expanded = true;
}
if (expanded) {
@@ -247,7 +249,7 @@
private void applyFitsSystemWindows(State state) {
boolean fitsSystemWindows = !state.isKeyguardShowingAndNotOccluded();
- if (mStatusBarView.getFitsSystemWindows() != fitsSystemWindows) {
+ if (mStatusBarView != null && mStatusBarView.getFitsSystemWindows() != fitsSystemWindows) {
mStatusBarView.setFitsSystemWindows(fitsSystemWindows);
mStatusBarView.requestApplyInsets();
}
@@ -289,7 +291,7 @@
applyBrightness(state);
applyHasTopUi(state);
applyNotTouchable(state);
- if (mLp.copyFrom(mLpChanged) != 0) {
+ if (mLp != null && mLp.copyFrom(mLpChanged) != 0) {
mWindowManager.updateViewLayout(mStatusBarView, mLp);
}
if (mHasTopUi != mHasTopUiChanged) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 9c4db34..7881df9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -188,6 +188,7 @@
LayoutInflater.from(context).inflate(R.layout.remote_input, root, false);
v.mController = controller;
v.mEntry = entry;
+ v.mEditText.setTextOperationUser(computeTextOperationUser(entry.notification.getUser()));
v.setTag(VIEW_TAG);
return v;
@@ -298,7 +299,6 @@
if (mWrapper != null) {
mWrapper.setRemoteInputVisible(true);
}
- mEditText.setTextOperationUser(computeTextOperationUser(mEntry.notification.getUser()));
mEditText.setInnerFocusable(true);
mEditText.mShowImeOnInputConnection = true;
mEditText.setText(mEntry.remoteInputText);
@@ -328,7 +328,6 @@
mResetting = true;
mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
- mEditText.setTextOperationUser(null);
mEditText.getText().clear();
mEditText.setEnabled(true);
mSendButton.setVisibility(VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
index 3bd0d45..db04620 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
@@ -46,18 +46,21 @@
private static final String KEY_EDIT_CHOICES_BEFORE_SENDING =
"edit_choices_before_sending";
private static final String KEY_SHOW_IN_HEADS_UP = "show_in_heads_up";
+ private static final String KEY_MIN_NUM_REPLIES = "min_num_system_generated_replies";
private final boolean mDefaultEnabled;
private final boolean mDefaultRequiresP;
private final int mDefaultMaxSqueezeRemeasureAttempts;
private final boolean mDefaultEditChoicesBeforeSending;
private final boolean mDefaultShowInHeadsUp;
+ private final int mDefaultMinNumSystemGeneratedReplies;
private boolean mEnabled;
private boolean mRequiresTargetingP;
private int mMaxSqueezeRemeasureAttempts;
private boolean mEditChoicesBeforeSending;
private boolean mShowInHeadsUp;
+ private int mMinNumSystemGeneratedReplies;
private final Context mContext;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -78,6 +81,8 @@
R.bool.config_smart_replies_in_notifications_edit_choices_before_sending);
mDefaultShowInHeadsUp = resources.getBoolean(
R.bool.config_smart_replies_in_notifications_show_in_heads_up);
+ mDefaultMinNumSystemGeneratedReplies = resources.getInteger(
+ R.integer.config_smart_replies_in_notifications_min_num_system_generated_replies);
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS),
@@ -105,6 +110,8 @@
mEditChoicesBeforeSending = mParser.getBoolean(
KEY_EDIT_CHOICES_BEFORE_SENDING, mDefaultEditChoicesBeforeSending);
mShowInHeadsUp = mParser.getBoolean(KEY_SHOW_IN_HEADS_UP, mDefaultShowInHeadsUp);
+ mMinNumSystemGeneratedReplies =
+ mParser.getInt(KEY_MIN_NUM_REPLIES, mDefaultMinNumSystemGeneratedReplies);
}
}
@@ -155,4 +162,12 @@
public boolean getShowInHeadsUp() {
return mShowInHeadsUp;
}
+
+ /**
+ * Returns the minimum number of system generated replies to show in a notification.
+ * If we cannot show at least this many system generated replies we should show none.
+ */
+ public int getMinNumSystemGeneratedReplies() {
+ return mMinNumSystemGeneratedReplies;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index a76cf16..c4f027f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -88,6 +88,12 @@
private View mSmartReplyContainer;
+ /**
+ * Whether the smart replies in this view were generated by the notification assistant. If not
+ * they're provided by the app.
+ */
+ private boolean mSmartRepliesGeneratedByAssistant = false;
+
@ColorInt
private int mCurrentBackgroundColor;
@ColorInt
@@ -202,6 +208,7 @@
getContext(), this, i, smartReplies, smartReplyController, entry);
addView(replyButton);
}
+ this.mSmartRepliesGeneratedByAssistant = smartReplies.fromAssistant;
}
}
reallocateCandidateButtonQueueForSqueezing();
@@ -310,8 +317,6 @@
headsUpManager.removeNotification(entry.key, true);
}));
- // TODO(b/119010281): handle accessibility
-
// Mark this as an Action button
final LayoutParams lp = (LayoutParams) button.getLayoutParams();
lp.buttonType = SmartButtonType.ACTION;
@@ -346,10 +351,11 @@
mCandidateButtonQueueForSqueezing.clear();
}
- int measuredWidth = mPaddingLeft + mPaddingRight;
- int maxChildHeight = 0;
+ SmartSuggestionMeasures accumulatedMeasures = new SmartSuggestionMeasures(
+ mPaddingLeft + mPaddingRight,
+ 0 /* maxChildHeight */,
+ mSingleLineButtonPaddingHorizontal);
int displayedChildCount = 0;
- int buttonPaddingHorizontal = mSingleLineButtonPaddingHorizontal;
// Set up a list of suggestions where actions come before replies. Note that the Buttons
// themselves have already been added to the view hierarchy in an order such that Smart
@@ -362,11 +368,15 @@
smartSuggestions.addAll(smartReplies);
List<View> coveredSuggestions = new ArrayList<>();
+ // SmartSuggestionMeasures for all action buttons, this will be filled in when the first
+ // reply button is added.
+ SmartSuggestionMeasures actionsMeasures = null;
+
for (View child : smartSuggestions) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- child.setPadding(buttonPaddingHorizontal, child.getPaddingTop(),
- buttonPaddingHorizontal, child.getPaddingBottom());
+ child.setPadding(accumulatedMeasures.mButtonPaddingHorizontal, child.getPaddingTop(),
+ accumulatedMeasures.mButtonPaddingHorizontal, child.getPaddingBottom());
child.measure(MEASURE_SPEC_ANY_LENGTH, heightMeasureSpec);
coveredSuggestions.add(child);
@@ -382,45 +392,52 @@
}
// Remember the current measurements in case the current button doesn't fit in.
- final int originalMaxChildHeight = maxChildHeight;
- final int originalMeasuredWidth = measuredWidth;
- final int originalButtonPaddingHorizontal = buttonPaddingHorizontal;
+ SmartSuggestionMeasures originalMeasures = accumulatedMeasures.clone();
+ if (actionsMeasures == null && lp.buttonType == SmartButtonType.REPLY) {
+ // We've added all actions (we go through actions first), now add their
+ // measurements.
+ actionsMeasures = accumulatedMeasures.clone();
+ }
final int spacing = displayedChildCount == 0 ? 0 : mSpacing;
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
- measuredWidth += spacing + childWidth;
- maxChildHeight = Math.max(maxChildHeight, childHeight);
+ accumulatedMeasures.mMeasuredWidth += spacing + childWidth;
+ accumulatedMeasures.mMaxChildHeight =
+ Math.max(accumulatedMeasures.mMaxChildHeight, childHeight);
// Do we need to increase the number of lines in smart reply buttons to two?
final boolean increaseToTwoLines =
- buttonPaddingHorizontal == mSingleLineButtonPaddingHorizontal
- && (lineCount == 2 || measuredWidth > targetWidth);
+ (accumulatedMeasures.mButtonPaddingHorizontal
+ == mSingleLineButtonPaddingHorizontal)
+ && (lineCount == 2 || accumulatedMeasures.mMeasuredWidth > targetWidth);
if (increaseToTwoLines) {
- measuredWidth += (displayedChildCount + 1) * mSingleToDoubleLineButtonWidthIncrease;
- buttonPaddingHorizontal = mDoubleLineButtonPaddingHorizontal;
+ accumulatedMeasures.mMeasuredWidth +=
+ (displayedChildCount + 1) * mSingleToDoubleLineButtonWidthIncrease;
+ accumulatedMeasures.mButtonPaddingHorizontal =
+ mDoubleLineButtonPaddingHorizontal;
}
// If the last button doesn't fit into the remaining width, try squeezing preceding
// smart reply buttons.
- if (measuredWidth > targetWidth) {
+ if (accumulatedMeasures.mMeasuredWidth > targetWidth) {
// Keep squeezing preceding and current smart reply buttons until they all fit.
- while (measuredWidth > targetWidth
+ while (accumulatedMeasures.mMeasuredWidth > targetWidth
&& !mCandidateButtonQueueForSqueezing.isEmpty()) {
final Button candidate = mCandidateButtonQueueForSqueezing.poll();
final int squeezeReduction = squeezeButton(candidate, heightMeasureSpec);
if (squeezeReduction != SQUEEZE_FAILED) {
- maxChildHeight = Math.max(maxChildHeight, candidate.getMeasuredHeight());
- measuredWidth -= squeezeReduction;
+ accumulatedMeasures.mMaxChildHeight =
+ Math.max(accumulatedMeasures.mMaxChildHeight,
+ candidate.getMeasuredHeight());
+ accumulatedMeasures.mMeasuredWidth -= squeezeReduction;
}
}
// If the current button still doesn't fit after squeezing all buttons, undo the
// last squeezing round.
- if (measuredWidth > targetWidth) {
- measuredWidth = originalMeasuredWidth;
- maxChildHeight = originalMaxChildHeight;
- buttonPaddingHorizontal = originalButtonPaddingHorizontal;
+ if (accumulatedMeasures.mMeasuredWidth > targetWidth) {
+ accumulatedMeasures = originalMeasures;
// Mark all buttons from the last squeezing round as "failed to squeeze", so
// that they're re-measured without squeezing later.
@@ -442,16 +459,75 @@
displayedChildCount++;
}
+ if (mSmartRepliesGeneratedByAssistant) {
+ if (!gotEnoughSmartReplies(smartReplies)) {
+ // We don't have enough smart replies - hide all of them.
+ for (View smartReplyButton : smartReplies) {
+ final LayoutParams lp = (LayoutParams) smartReplyButton.getLayoutParams();
+ lp.show = false;
+ }
+ // Reset our measures back to when we had only added actions (before adding
+ // replies).
+ accumulatedMeasures = actionsMeasures;
+ }
+ }
+
// We're done squeezing buttons, so we can clear the priority queue.
mCandidateButtonQueueForSqueezing.clear();
// Finally, we need to re-measure some buttons.
- remeasureButtonsIfNecessary(buttonPaddingHorizontal, maxChildHeight);
+ remeasureButtonsIfNecessary(accumulatedMeasures.mButtonPaddingHorizontal,
+ accumulatedMeasures.mMaxChildHeight);
setMeasuredDimension(
- resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth), widthMeasureSpec),
- resolveSize(Math.max(getSuggestedMinimumHeight(),
- mPaddingTop + maxChildHeight + mPaddingBottom), heightMeasureSpec));
+ resolveSize(Math.max(getSuggestedMinimumWidth(),
+ accumulatedMeasures.mMeasuredWidth),
+ widthMeasureSpec),
+ resolveSize(Math.max(getSuggestedMinimumHeight(), mPaddingTop
+ + accumulatedMeasures.mMaxChildHeight + mPaddingBottom),
+ heightMeasureSpec));
+ }
+
+ /**
+ * Fields we keep track of inside onMeasure() to correctly measure the SmartReplyView depending
+ * on which suggestions are added.
+ */
+ private static class SmartSuggestionMeasures {
+ int mMeasuredWidth = -1;
+ int mMaxChildHeight = -1;
+ int mButtonPaddingHorizontal = -1;
+
+ SmartSuggestionMeasures(int measuredWidth, int maxChildHeight,
+ int buttonPaddingHorizontal) {
+ this.mMeasuredWidth = measuredWidth;
+ this.mMaxChildHeight = maxChildHeight;
+ this.mButtonPaddingHorizontal = buttonPaddingHorizontal;
+ }
+
+ public SmartSuggestionMeasures clone() {
+ return new SmartSuggestionMeasures(
+ mMeasuredWidth, mMaxChildHeight, mButtonPaddingHorizontal);
+ }
+ }
+
+ /**
+ * Returns whether our notification contains at least N smart replies (or 0) where N is
+ * determined by {@link SmartReplyConstants}.
+ */
+ private boolean gotEnoughSmartReplies(List<View> smartReplies) {
+ int numShownReplies = 0;
+ for (View smartReplyButton : smartReplies) {
+ final LayoutParams lp = (LayoutParams) smartReplyButton.getLayoutParams();
+ if (lp.show) {
+ numShownReplies++;
+ }
+ }
+ if (numShownReplies == 0
+ || numShownReplies >= mConstants.getMinNumSystemGeneratedReplies()) {
+ // We have enough replies, yay!
+ return true;
+ }
+ return false;
}
private List<View> filterActionsOrReplies(SmartButtonType buttonType) {
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
index ed2ad79..12006fa 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
@@ -75,7 +75,7 @@
final AlertController.AlertParams ap = mAlertParams;
ap.mTitle = getString(R.string.usb_debugging_title);
ap.mMessage = getString(R.string.usb_debugging_message, fingerprints);
- ap.mPositiveButtonText = getString(android.R.string.ok);
+ ap.mPositiveButtonText = getString(R.string.usb_debugging_allow);
ap.mNegativeButtonText = getString(android.R.string.cancel);
ap.mPositiveButtonListener = this;
ap.mNegativeButtonListener = this;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java b/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java
index 712ea27..8b00eee 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java
@@ -25,9 +25,9 @@
import android.content.pm.ResolveInfo;
import android.media.IRemoteVolumeController;
import android.media.MediaMetadata;
-import android.media.session.ISessionController;
import android.media.session.MediaController;
import android.media.session.MediaController.PlaybackInfo;
+import android.media.session.MediaSession;
import android.media.session.MediaSession.QueueItem;
import android.media.session.MediaSession.Token;
import android.media.session.MediaSessionManager;
@@ -113,17 +113,17 @@
r.controller.setVolumeTo(level, 0);
}
- private void onRemoteVolumeChangedH(ISessionController session, int flags) {
- final MediaController controller = new MediaController(mContext, session);
+ private void onRemoteVolumeChangedH(MediaSession.Token sessionToken, int flags) {
+ final MediaController controller = new MediaController(mContext, sessionToken);
if (D.BUG) Log.d(TAG, "remoteVolumeChangedH " + controller.getPackageName() + " "
+ Util.audioManagerFlagsToString(flags));
final Token token = controller.getSessionToken();
mCallbacks.onRemoteVolumeChanged(token, flags);
}
- private void onUpdateRemoteControllerH(ISessionController session) {
- final MediaController controller = session != null ? new MediaController(mContext, session)
- : null;
+ private void onUpdateRemoteControllerH(MediaSession.Token sessionToken) {
+ final MediaController controller =
+ sessionToken != null ? new MediaController(mContext, sessionToken) : null;
final String pkg = controller != null ? controller.getPackageName() : null;
if (D.BUG) Log.d(TAG, "updateRemoteControllerH " + pkg);
// this may be our only indication that a remote session is changed, refresh
@@ -332,15 +332,16 @@
private final IRemoteVolumeController mRvc = new IRemoteVolumeController.Stub() {
@Override
- public void remoteVolumeChanged(ISessionController session, int flags)
+ public void remoteVolumeChanged(MediaSession.Token sessionToken, int flags)
throws RemoteException {
- mHandler.obtainMessage(H.REMOTE_VOLUME_CHANGED, flags, 0, session).sendToTarget();
+ mHandler.obtainMessage(H.REMOTE_VOLUME_CHANGED, flags, 0,
+ sessionToken).sendToTarget();
}
@Override
- public void updateRemoteController(final ISessionController session)
+ public void updateRemoteController(final MediaSession.Token sessionToken)
throws RemoteException {
- mHandler.obtainMessage(H.UPDATE_REMOTE_CONTROLLER, session).sendToTarget();
+ mHandler.obtainMessage(H.UPDATE_REMOTE_CONTROLLER, sessionToken).sendToTarget();
}
};
@@ -360,10 +361,10 @@
onActiveSessionsUpdatedH(mMgr.getActiveSessions(null));
break;
case REMOTE_VOLUME_CHANGED:
- onRemoteVolumeChangedH((ISessionController) msg.obj, msg.arg1);
+ onRemoteVolumeChangedH((MediaSession.Token) msg.obj, msg.arg1);
break;
case UPDATE_REMOTE_CONTROLLER:
- onUpdateRemoteControllerH((ISessionController) msg.obj);
+ onUpdateRemoteControllerH((MediaSession.Token) msg.obj);
break;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index fbc1c20..d80b444 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -39,6 +39,7 @@
import android.widget.FrameLayout;
import android.widget.TextClock;
+import com.android.keyguard.clock.ClockManager;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.statusbar.StatusBarState;
@@ -51,8 +52,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.function.Consumer;
-
@SmallTest
@RunWith(AndroidTestingRunner.class)
// Need to run on the main thread because KeyguardSliceView$Row init checks for
@@ -85,7 +84,7 @@
TextClock pluginView = new TextClock(getContext());
when(plugin.getView()).thenReturn(pluginView);
- mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
verify(mClockView).setVisibility(GONE);
assertThat(plugin.getView().getParent()).isEqualTo(mClockContainer);
@@ -102,7 +101,7 @@
TextClock pluginView = new TextClock(getContext());
when(plugin.getBigClockView()).thenReturn(pluginView);
// WHEN the plugin is connected
- mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
// THEN the big clock container is visible and it is the parent of the
// big clock view.
assertThat(bigClockContainer.getVisibility()).isEqualTo(VISIBLE);
@@ -112,7 +111,7 @@
@Test
public void onPluginConnected_nullView() {
ClockPlugin plugin = mock(ClockPlugin.class);
- mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
verify(mClockView, never()).setVisibility(GONE);
}
@@ -121,11 +120,11 @@
// GIVEN a plugin has already connected
ClockPlugin plugin1 = mock(ClockPlugin.class);
when(plugin1.getView()).thenReturn(new TextClock(getContext()));
- mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin1);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin1);
// WHEN a second plugin is connected
ClockPlugin plugin2 = mock(ClockPlugin.class);
when(plugin2.getView()).thenReturn(new TextClock(getContext()));
- mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin2);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin2);
// THEN only the view from the second plugin should be a child of KeyguardClockSwitch.
assertThat(plugin2.getView().getParent()).isEqualTo(mClockContainer);
assertThat(plugin1.getView().getParent()).isNull();
@@ -137,7 +136,7 @@
mKeyguardClockSwitch.setDarkAmount(0.5f);
// WHEN a plugin is connected
ClockPlugin plugin = mock(ClockPlugin.class);
- mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
// THEN dark amount should be initalized on the plugin.
verify(plugin).setDarkAmount(0.5f);
}
@@ -149,8 +148,8 @@
when(plugin.getView()).thenReturn(pluginView);
mClockView.setVisibility(GONE);
- mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
- mKeyguardClockSwitch.getClockPluginConsumer().accept(null);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(null);
verify(mClockView).setVisibility(VISIBLE);
assertThat(plugin.getView().getParent()).isNull();
@@ -167,8 +166,8 @@
TextClock pluginView = new TextClock(getContext());
when(plugin.getBigClockView()).thenReturn(pluginView);
// WHEN the plugin is connected and then disconnected
- mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
- mKeyguardClockSwitch.getClockPluginConsumer().accept(null);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(null);
// THEN the big lock container is GONE and the big clock view doesn't have
// a parent.
assertThat(bigClockContainer.getVisibility()).isEqualTo(GONE);
@@ -178,8 +177,8 @@
@Test
public void onPluginDisconnected_nullView() {
ClockPlugin plugin = mock(ClockPlugin.class);
- mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
- mKeyguardClockSwitch.getClockPluginConsumer().accept(null);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(null);
verify(mClockView, never()).setVisibility(GONE);
}
@@ -188,13 +187,13 @@
// GIVEN two plugins are connected
ClockPlugin plugin1 = mock(ClockPlugin.class);
when(plugin1.getView()).thenReturn(new TextClock(getContext()));
- Consumer<ClockPlugin> consumer = mKeyguardClockSwitch.getClockPluginConsumer();
- consumer.accept(plugin1);
+ ClockManager.ClockChangedListener listener = mKeyguardClockSwitch.getClockChangedListener();
+ listener.onClockChanged(plugin1);
ClockPlugin plugin2 = mock(ClockPlugin.class);
when(plugin2.getView()).thenReturn(new TextClock(getContext()));
- consumer.accept(plugin2);
+ listener.onClockChanged(plugin2);
// WHEN the second plugin is disconnected
- consumer.accept(null);
+ listener.onClockChanged(null);
// THEN the default clock should be shown.
verify(mClockView).setVisibility(VISIBLE);
assertThat(plugin1.getView().getParent()).isNull();
@@ -213,7 +212,7 @@
ClockPlugin plugin = mock(ClockPlugin.class);
TextClock pluginView = new TextClock(getContext());
when(plugin.getView()).thenReturn(pluginView);
- mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
mKeyguardClockSwitch.setTextColor(Color.WHITE);
@@ -237,7 +236,7 @@
TextClock pluginView = new TextClock(getContext());
when(plugin.getView()).thenReturn(pluginView);
Style style = mock(Style.class);
- mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+ mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
mKeyguardClockSwitch.setStyle(style);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
index 53ad0b5..fc57909 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
@@ -68,7 +68,7 @@
public Engine onCreateEngine() {
return new DrawableEngine() {
@Override
- DisplayInfo getDefaultDisplayInfo() {
+ DisplayInfo getDisplayInfo() {
return mDisplayInfo;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index fa5cf04..60a20cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.bubbles;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.atLeastOnce;
@@ -160,6 +161,12 @@
stackView.expandStack();
assertTrue(mBubbleController.isStackExpanded());
+ stackView.setExpandedBubble(mRow.getEntry());
+ assertEquals(stackView.getExpandedBubble().getEntry(), mRow.getEntry());
+
+ stackView.setExpandedBubble(mRow2.getEntry());
+ assertEquals(stackView.getExpandedBubble().getEntry(), mRow2.getEntry());
+
mBubbleController.collapseStack();
assertFalse(mBubbleController.isStackExpanded());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java b/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java
index b368876..839b5e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java
@@ -32,6 +32,11 @@
this.mCallback = null;
}
+ @Override
+ public boolean isDocked() {
+ return false;
+ }
+
public void setDockEvent(int event) {
mCallback.onEvent(event);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
index f45500a..e4558df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
@@ -36,6 +36,8 @@
when(params.getPickupVibrationThreshold()).thenReturn(0);
when(params.getProxCheckBeforePulse()).thenReturn(true);
when(params.getPickupSubtypePerformsProxCheck(anyInt())).thenReturn(true);
+ when(params.getPolicy()).thenReturn(mock(AlwaysOnDisplayPolicy.class));
+ when(params.doubleTapReportsTouchCoordinates()).thenReturn(false);
doneHolder[0] = true;
return params;
@@ -48,9 +50,14 @@
when(config.doubleTapGestureEnabled(anyInt())).thenReturn(false);
when(config.pickupGestureEnabled(anyInt())).thenReturn(false);
when(config.pulseOnNotificationEnabled(anyInt())).thenReturn(true);
+ when(config.alwaysOnEnabled(anyInt())).thenReturn(false);
when(config.doubleTapSensorType()).thenReturn(null);
+ when(config.tapSensorType()).thenReturn(null);
+ when(config.longPressSensorType()).thenReturn(null);
+
when(config.dozePickupSensorAvailable()).thenReturn(false);
+ when(config.wakeScreenGestureAvailable()).thenReturn(false);
doneHolder[0] = true;
return config;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
index 926ff69..0fc0953 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
@@ -75,7 +75,7 @@
mContext.putComponent(DockManager.class, mDockManagerFake);
mDockHandler = new DozeDockHandler(mContext, mMachine, mHost, mConfig,
- Handler.createAsync(Looper.myLooper()));
+ Handler.createAsync(Looper.myLooper()), mDockManagerFake);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 31fc625..7b358b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -18,8 +18,10 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -34,6 +36,8 @@
import com.android.internal.hardware.AmbientDisplayConfiguration;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dock.DockManager;
+import com.android.systemui.dock.DockManagerFake;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.util.wakelock.WakeLock;
import com.android.systemui.util.wakelock.WakeLockFake;
@@ -59,6 +63,7 @@
private WakeLock mWakeLock;
private Instrumentation mInstrumentation;
private AlarmManager mAlarmManager;
+ private DockManagerFake mDockManagerFake;
@BeforeClass
public static void setupSuite() {
@@ -76,9 +81,12 @@
mParameters = DozeConfigurationUtil.createMockParameters();
mSensors = new FakeSensorManager(mContext);
mWakeLock = new WakeLockFake();
+ mDockManagerFake = spy(new DockManagerFake());
+ mContext.putComponent(DockManager.class, mDockManagerFake);
mTriggers = new DozeTriggers(mContext, mMachine, mHost, mAlarmManager, mConfig, mParameters,
- mSensors, Handler.createAsync(Looper.myLooper()), mWakeLock, true);
+ mSensors, Handler.createAsync(Looper.myLooper()), mWakeLock, true,
+ mDockManagerFake);
}
@Test
@@ -102,4 +110,38 @@
verify(mMachine).requestPulse(anyInt());
}
+ @Test
+ public void testDockEventListener_registerAndUnregister() {
+ mTriggers.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED);
+
+ verify(mDockManagerFake).addListener(any());
+
+ mTriggers.transitionTo(DozeMachine.State.DOZE, DozeMachine.State.FINISH);
+
+ verify(mDockManagerFake).removeListener(any());
+ }
+
+ @Test
+ public void testOnSensor_whenUndockedWithNearAndDoubleTapScreen_shouldNotWakeUp() {
+ mSensors.getMockProximitySensor().sendProximityResult(false /* far */);
+
+ mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP,
+ false /* sensorPerformedProxCheck */, 50 /* screenX */, 50 /* screenY */,
+ null /* rawValues */);
+
+ verify(mMachine, never()).wakeUp();
+ }
+
+ @Test
+ public void testOnSensor_whenDockedWithNearAndDoubleTapScreen_shouldWakeUp() {
+ doReturn(true).when(mDockManagerFake).isDocked();
+ mSensors.getMockProximitySensor().sendProximityResult(false /* far */);
+
+ mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP,
+ false /* sensorPerformedProxCheck */, 50 /* screenX */, 50 /* screenY */,
+ null /* rawValues */);
+
+ verify(mMachine).wakeUp();
+ }
+
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
new file mode 100644
index 0000000..2f6b221
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
@@ -0,0 +1,147 @@
+/**
+ * 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.systemui.statusbar.notification.logging;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.os.RemoteException;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
+import com.android.systemui.Dependency;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.UiOffloadThread;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Collections;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class ExpansionStateLoggerTest extends SysuiTestCase {
+ private static final String NOTIFICATION_KEY = "notin_key";
+
+ private NotificationLogger.ExpansionStateLogger mLogger;
+ @Mock
+ private IStatusBarService mBarService;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mLogger = new NotificationLogger.ExpansionStateLogger(
+ Dependency.get(UiOffloadThread.class));
+ mLogger.mBarService = mBarService;
+ }
+
+ @Test
+ public void testVisible() throws RemoteException {
+ mLogger.onVisibilityChanged(
+ Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
+ Collections.emptyList());
+ waitForUiOffloadThread();
+
+ verify(mBarService, Mockito.never()).onNotificationExpansionChanged(
+ eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean(), anyInt());
+ }
+
+ @Test
+ public void testExpanded() throws RemoteException {
+ mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
+ waitForUiOffloadThread();
+
+ verify(mBarService, Mockito.never()).onNotificationExpansionChanged(
+ eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean(), anyInt());
+ }
+
+ @Test
+ public void testVisibleAndNotExpanded() throws RemoteException {
+ mLogger.onExpansionChanged(NOTIFICATION_KEY, true, false);
+ mLogger.onVisibilityChanged(
+ Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
+ Collections.emptyList());
+ waitForUiOffloadThread();
+
+ verify(mBarService, Mockito.never()).onNotificationExpansionChanged(
+ eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean(), anyInt());
+ }
+
+ @Test
+ public void testVisibleAndExpanded() throws RemoteException {
+ mLogger.onExpansionChanged(NOTIFICATION_KEY, true, true);
+ mLogger.onVisibilityChanged(
+ Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
+ Collections.emptyList());
+ waitForUiOffloadThread();
+
+ verify(mBarService).onNotificationExpansionChanged(
+ NOTIFICATION_KEY, true, true, ExpandableViewState.LOCATION_UNKNOWN);
+ }
+
+ @Test
+ public void testExpandedAndVisible_expandedBeforeVisible() throws RemoteException {
+ mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
+ mLogger.onVisibilityChanged(
+ Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
+ Collections.emptyList());
+ waitForUiOffloadThread();
+
+ verify(mBarService).onNotificationExpansionChanged(
+ NOTIFICATION_KEY, false, true, ExpandableViewState.LOCATION_UNKNOWN);
+ }
+
+ @Test
+ public void testExpandedAndVisible_visibleBeforeExpanded() throws RemoteException {
+ mLogger.onVisibilityChanged(
+ Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
+ Collections.emptyList());
+ mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
+ waitForUiOffloadThread();
+
+ verify(mBarService).onNotificationExpansionChanged(
+ NOTIFICATION_KEY, false, true, ExpandableViewState.LOCATION_UNKNOWN);
+ }
+
+ @Test
+ public void testExpandedAndVisible_logOnceOnly() throws RemoteException {
+ mLogger.onVisibilityChanged(
+ Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
+ Collections.emptyList());
+ mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
+ mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
+ waitForUiOffloadThread();
+
+ verify(mBarService).onNotificationExpansionChanged(
+ NOTIFICATION_KEY, false, true, ExpandableViewState.LOCATION_UNKNOWN);
+ }
+
+ private NotificationVisibility createNotificationVisibility(String key, boolean visibility) {
+ return NotificationVisibility.obtain(key, 0, 0, visibility);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index 6472589..db2706b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -72,6 +72,7 @@
@Mock private IStatusBarService mBarService;
@Mock private NotificationData mNotificationData;
@Mock private ExpandableNotificationRow mRow;
+ @Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger;
// Dependency mocks:
@Mock private NotificationEntryManager mEntryManager;
@@ -98,7 +99,8 @@
mEntry.setRow(mRow);
mLogger = new TestableNotificationLogger(mListener, Dependency.get(UiOffloadThread.class),
- mEntryManager, mock(StatusBarStateController.class), mBarService);
+ mEntryManager, mock(StatusBarStateController.class), mBarService,
+ mExpansionStateLogger);
mLogger.setUpWithContainer(mListContainer);
verify(mEntryManager).addNotificationEntryListener(mEntryListenerCaptor.capture());
mNotificationEntryListener = mEntryListenerCaptor.getValue();
@@ -166,8 +168,10 @@
UiOffloadThread uiOffloadThread,
NotificationEntryManager entryManager,
StatusBarStateController statusBarStateController,
- IStatusBarService barService) {
- super(notificationListener, uiOffloadThread, entryManager, statusBarStateController);
+ IStatusBarService barService,
+ ExpansionStateLogger expansionStateLogger) {
+ super(notificationListener, uiOffloadThread, entryManager, statusBarStateController,
+ expansionStateLogger);
mBarService = barService;
// Make this on the current thread so we can wait for it during tests.
mHandler = Handler.createAsync(Looper.myLooper());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java
index 667a508..4dee438 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java
@@ -17,7 +17,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -171,6 +170,19 @@
ev.recycle();
}
+ @Test
+ public void testViewNotAttachedNoCrash() {
+ View view = mockViewAt(0, 20, 10, 10);
+ when(view.isAttachedToWindow()).thenReturn(false);
+ mNearestTouchFrame.addView(view);
+ mNearestTouchFrame.onMeasure(0, 0);
+
+ MotionEvent ev = MotionEvent.obtain(0, 0, 0, 5 /* x */, 18 /* y */, 0);
+ mNearestTouchFrame.onTouchEvent(ev);
+ verify(view, never()).onTouchEvent(eq(ev));
+ ev.recycle();
+ }
+
private View mockViewAt(int x, int y, int width, int height) {
View v = spy(new View(mContext));
doAnswer(invocation -> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 0d4046b..17611ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -159,6 +159,8 @@
private NotificationFilter mNotificationFilter;
@Mock
private NotificationAlertingManager mNotificationAlertingManager;
+ @Mock
+ private NotificationLogger.ExpansionStateLogger mExpansionStateLogger;
private TestableStatusBar mStatusBar;
private FakeMetricsLogger mMetricsLogger;
@@ -207,7 +209,8 @@
mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
mEntryManager = new TestableNotificationEntryManager(mContext);
mNotificationLogger = new NotificationLogger(mNotificationListener,
- Dependency.get(UiOffloadThread.class), mEntryManager, mStatusBarStateController);
+ Dependency.get(UiOffloadThread.class), mEntryManager, mStatusBarStateController,
+ mExpansionStateLogger);
mDependency.injectTestDependency(NotificationLogger.class, mNotificationLogger);
DozeLog.traceDozing(mContext, false /* dozing */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
index 98d0c6b..9996a9e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
@@ -95,4 +95,11 @@
public void testAdd_updatesVisibilityFlags() {
verify(mStatusBarView).setSystemUiVisibility(anyInt());
}
+
+ @Test
+ public void testSetForcePluginOpen_beforeStatusBarInitialization() {
+ mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager,
+ mActivityManager, mDozeParameters);
+ mStatusBarWindowController.setForcePluginOpen(true);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
index 3cbf902..03b7c95 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
@@ -55,6 +55,9 @@
resources.addOverride(
R.bool.config_smart_replies_in_notifications_edit_choices_before_sending, false);
resources.addOverride(R.bool.config_smart_replies_in_notifications_show_in_heads_up, true);
+ resources.addOverride(
+ R.integer.config_smart_replies_in_notifications_min_num_system_generated_replies,
+ 2);
mConstants = new SmartReplyConstants(Handler.createAsync(Looper.myLooper()), mContext);
}
@@ -178,6 +181,19 @@
Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS, flags);
}
+ @Test
+ public void testGetMinNumSystemGeneratedRepliesWithNoConfig() {
+ assertTrue(mConstants.isEnabled());
+ assertEquals(2, mConstants.getMinNumSystemGeneratedReplies());
+ }
+
+ @Test
+ public void testGetMinNumSystemGeneratedRepliesWithValidConfig() {
+ overrideSetting("enabled=true,min_num_system_generated_replies=5");
+ triggerConstantsOnChange();
+ assertEquals(5, mConstants.getMinNumSystemGeneratedReplies());
+ }
+
private void triggerConstantsOnChange() {
// Since Settings.Global is mocked in TestableContext, we need to manually trigger the
// content observer.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index 1066bc1..d1c4d01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -96,6 +96,7 @@
@Mock private SmartReplyController mLogger;
private NotificationEntry mEntry;
private Notification mNotification;
+ @Mock private SmartReplyConstants mConstants;
@Mock ActivityStarter mActivityStarter;
@Mock HeadsUpManager mHeadsUpManager;
@@ -108,10 +109,14 @@
mDependency.get(KeyguardDismissUtil.class).setDismissHandler(action -> action.onDismiss());
mDependency.injectMockDependency(ShadeController.class);
mDependency.injectTestDependency(ActivityStarter.class, mActivityStarter);
+ mDependency.injectTestDependency(SmartReplyConstants.class, mConstants);
mContainer = new View(mContext, null);
mView = SmartReplyView.inflate(mContext, null);
+ // Any number of replies are fine.
+ when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(0);
+ when(mConstants.getMaxSqueezeRemeasureAttempts()).thenReturn(3);
final Resources res = mContext.getResources();
mSingleLinePaddingHorizontal = res.getDimensionPixelSize(
@@ -403,7 +408,7 @@
}
private void setSmartReplies(CharSequence[] choices) {
- setSmartReplies(choices, false);
+ setSmartReplies(choices, false /* fromAssistant */);
}
private void setSmartReplies(CharSequence[] choices, boolean fromAssistant) {
@@ -440,9 +445,14 @@
}
private void setSmartRepliesAndActions(CharSequence[] choices, String[] actionTitles) {
- setSmartReplies(choices);
+ setSmartRepliesAndActions(choices, actionTitles, false /* fromAssistant */);
+ }
+
+ private void setSmartRepliesAndActions(
+ CharSequence[] choices, String[] actionTitles, boolean fromAssistant) {
+ setSmartReplies(choices, fromAssistant);
mView.addSmartActions(
- new SmartReplyView.SmartActions(createActions(actionTitles), false),
+ new SmartReplyView.SmartActions(createActions(actionTitles), fromAssistant),
mLogger,
mEntry,
mHeadsUpManager);
@@ -943,4 +953,78 @@
expectedView.getChildAt(3), mView.getChildAt(4)); // a1
assertReplyButtonHidden(mView.getChildAt(5)); // long action
}
+
+ @Test
+ public void testMeasure_minNumSystemGeneratedSmartReplies_notEnoughReplies() {
+ when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(3);
+
+ // Add 2 replies when the minimum is 3 -> we should end up with 0 replies.
+ String[] choices = new String[] {"reply1", "reply2"};
+ String[] actions = new String[] {"action1"};
+
+ ViewGroup expectedView = buildExpectedView(new String[] {}, 1,
+ createActions(new String[] {"action1"}));
+ expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+ setSmartRepliesAndActions(choices, actions, true /* fromAssistant */);
+ mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+ assertEqualMeasures(expectedView, mView);
+ // smart replies
+ assertReplyButtonHidden(mView.getChildAt(0));
+ assertReplyButtonHidden(mView.getChildAt(1));
+ // smart actions
+ assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(2));
+ }
+
+ @Test
+ public void testMeasure_minNumSystemGeneratedSmartReplies_enoughReplies() {
+ when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(2);
+
+ // Add 2 replies when the minimum is 3 -> we should end up with 0 replies.
+ String[] choices = new String[] {"reply1", "reply2"};
+ String[] actions = new String[] {"action1"};
+
+ ViewGroup expectedView = buildExpectedView(new String[] {"reply1", "reply2"}, 1,
+ createActions(new String[] {"action1"}));
+ expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+ setSmartRepliesAndActions(choices, actions, true /* fromAssistant */);
+ mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+ assertEqualMeasures(expectedView, mView);
+ // smart replies
+ assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
+ assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
+ // smart actions
+ assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(2));
+ }
+
+ /**
+ * Ensure actions that are squeezed when shown together with smart replies are unsqueezed if the
+ * replies are never added (because of the SmartReplyConstants.getMinNumSystemGeneratedReplies()
+ * flag).
+ */
+ @Test
+ public void testMeasure_minNumSystemGeneratedSmartReplies_unSqueezeActions() {
+ when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(2);
+
+ // Add 2 replies when the minimum is 3 -> we should end up with 0 replies.
+ String[] choices = new String[] {"This is a very long two-line reply."};
+ String[] actions = new String[] {"Short action"};
+
+ // The action should be displayed on one line only - since it fits!
+ ViewGroup expectedView = buildExpectedView(new String[] {}, 1 /* lineCount */,
+ createActions(new String[] {"Short action"}));
+ expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+ setSmartRepliesAndActions(choices, actions, true /* fromAssistant */);
+ mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+ assertEqualMeasures(expectedView, mView);
+ // smart replies
+ assertReplyButtonHidden(mView.getChildAt(0));
+ // smart actions
+ assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(1));
+ }
}
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-af/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-af/strings.xml
new file mode 100644
index 0000000..9ac5fd6
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-af/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Eksperiment met verstek navigasiebalk (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-am/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-am/strings.xml
new file mode 100644
index 0000000..c15f374
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-am/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"ነባሪ የዳሰሳ አሞሌ ሙከራ (48 ዲፒ)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ar/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ar/strings.xml
new file mode 100644
index 0000000..26abf03
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ar/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"تجربة شريط التنقل التلقائي (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-as/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-as/strings.xml
new file mode 100644
index 0000000..cfd21b1
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-as/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"ডিফ’ল্ট নেভিগে’শ্বন বাৰ সম্পৰীক্ষা (৪৮ডিপি)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-az/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-az/strings.xml
new file mode 100644
index 0000000..a7e9110
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-az/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Defolt Naviqasiya Paneli Təcrübəsi (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-b+sr+Latn/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..1222625
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Eksperiment sa tankom trakom za navigaciju (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-be/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-be/strings.xml
new file mode 100644
index 0000000..da91427
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-be/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Эксперымент са стандартнай панэллю навігацыі (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-bg/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-bg/strings.xml
new file mode 100644
index 0000000..7295457
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-bg/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Експеримент със стандартна лента за навигация (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-bn/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-bn/strings.xml
new file mode 100644
index 0000000..1515805
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-bn/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"ডিফল্ট নেভিগেশন বার সম্পর্কিত পরীক্ষা (৪৮ ডিপি)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-bs/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-bs/strings.xml
new file mode 100644
index 0000000..dea0884
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-bs/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Eksperiment sa zadanom trakom za navigaciju (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ca/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ca/strings.xml
new file mode 100644
index 0000000..d422c86
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ca/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Experiment amb barra de navegació predeterminada (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-cs/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-cs/strings.xml
new file mode 100644
index 0000000..61d4131
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-cs/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Výchozí navigační panel – experiment (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-da/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-da/strings.xml
new file mode 100644
index 0000000..78c5ff3
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-da/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Test med standardnavigationslinje (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-de/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-de/strings.xml
new file mode 100644
index 0000000..a0779b5
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-de/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Test mit Standard-Navigationsleiste (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-el/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-el/strings.xml
new file mode 100644
index 0000000..b34cf5afb
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-el/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Πείραμα προεπιλεγμένης γραμμής πλοήγησης (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-en-rAU/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..aa3bf66
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-en-rAU/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Default Navigation Bar Experiment (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-en-rCA/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..aa3bf66
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-en-rCA/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Default Navigation Bar Experiment (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-en-rGB/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..aa3bf66
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-en-rGB/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Default Navigation Bar Experiment (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-en-rIN/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..aa3bf66
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-en-rIN/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Default Navigation Bar Experiment (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-es-rUS/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..81297d0
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-es-rUS/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Experimento de la barra navegación predeterminada (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-es/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-es/strings.xml
new file mode 100644
index 0000000..81297d0
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-es/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Experimento de la barra navegación predeterminada (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-et/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-et/strings.xml
new file mode 100644
index 0000000..8a442be
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-et/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Vaikenavigeerimisriba katse (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-eu/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-eu/strings.xml
new file mode 100644
index 0000000..fdf4c4e
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-eu/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Nabigazio-barra lehenetsiaren esperimentua (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-fa/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-fa/strings.xml
new file mode 100644
index 0000000..d521f79
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-fa/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"آزمایش نوار پیمایش پیشفرض (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-fi/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-fi/strings.xml
new file mode 100644
index 0000000..2e538a3
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-fi/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Oletusnavigointipalkin kokeilu (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-fr-rCA/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..171a37e
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-fr-rCA/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Expérience de barre de navigation par défaut (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-fr/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-fr/strings.xml
new file mode 100644
index 0000000..bcbf94d
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-fr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Test relatif à la barre de navigation par défaut (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-gl/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-gl/strings.xml
new file mode 100644
index 0000000..b9a0165
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-gl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Experimento da barra de navegación predeterminada (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-gu/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-gu/strings.xml
new file mode 100644
index 0000000..f4cdb38
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-gu/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"ડિફૉલ્ટ નૅવિગેશન બારનો પ્રયોગ (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-hi/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-hi/strings.xml
new file mode 100644
index 0000000..ee8a739
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-hi/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"डिफ़ॉल्ट नेविगेशन बार प्रयोग (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-hr/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-hr/strings.xml
new file mode 100644
index 0000000..63a5b044
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-hr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Eksperiment s tankom navigacijskom trakom (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-hu/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-hu/strings.xml
new file mode 100644
index 0000000..110347c
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-hu/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Kísérleti alapértelmezett navigációs sáv (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-hy/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-hy/strings.xml
new file mode 100644
index 0000000..9ef0d82
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-hy/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Նավարկման կանխադրված գոտու փորձարկում (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-in/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-in/strings.xml
new file mode 100644
index 0000000..c48e0a7
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-in/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Eksperimen Menu Navigasi Default (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-is/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-is/strings.xml
new file mode 100644
index 0000000..1950f5d
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-is/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Tilraun með sjálfgefna yfirlitsstiku (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-it/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-it/strings.xml
new file mode 100644
index 0000000..45985d9
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-it/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Esperimento Barra di navigazione predefinita (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-iw/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-iw/strings.xml
new file mode 100644
index 0000000..1d99dfe
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-iw/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"ניסוי של סרגל ניווט דק (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ja/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ja/strings.xml
new file mode 100644
index 0000000..80e6954
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ja/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"デフォルト ナビゲーション バー テスト(48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ka/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ka/strings.xml
new file mode 100644
index 0000000..f623c11
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ka/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"ნავიგაციის ნაგულისხმევი ზოლის ექსპერიმენტი (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-kk/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-kk/strings.xml
new file mode 100644
index 0000000..f42d9ac
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-kk/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Әдепкі навигация жолағы (эксперимент) (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-km/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-km/strings.xml
new file mode 100644
index 0000000..a1316b7
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-km/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"ការពិសោធនៃរបាររុករកលំនាំដើម (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-kn/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-kn/strings.xml
new file mode 100644
index 0000000..15f3f4a
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-kn/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"ಡೀಫಾಲ್ಟ್ ನ್ಯಾವಿಗೇಶನ್ ಬಾರ್ ಪ್ರಯೋಗ (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ko/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ko/strings.xml
new file mode 100644
index 0000000..08a93df
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ko/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"기본 탐색 메뉴 실험(48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ky/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ky/strings.xml
new file mode 100644
index 0000000..e352c7f3
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ky/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Демейки чабыттоо тилкесин сыноо (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-lo/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-lo/strings.xml
new file mode 100644
index 0000000..6dfeacf
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-lo/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"ການທົດສອບແຖບນໍາທາງແບບບາງ (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-lt/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-lt/strings.xml
new file mode 100644
index 0000000..0752655
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-lt/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Numatytosios naršymo juostos eksperimentas (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-lv/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-lv/strings.xml
new file mode 100644
index 0000000..c72c9eb
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-lv/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Noklusējuma navigācijas joslas eksperiments (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-mk/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-mk/strings.xml
new file mode 100644
index 0000000..de21e96
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-mk/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Експеримент со стандардна лента за навигација (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ml/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ml/strings.xml
new file mode 100644
index 0000000..8870ae7
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ml/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"സ്ലിം നാവിഗേഷൻ ബാർ പരീക്ഷണം (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-mn/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-mn/strings.xml
new file mode 100644
index 0000000..29377d1
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-mn/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Өгөгдмөл навигацийн самбарын туршилт (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-mr/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-mr/strings.xml
new file mode 100644
index 0000000..3057fe7
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-mr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"डीफॉल्ट नॅव्हिगेशन बार प्रयोग (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ms/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ms/strings.xml
new file mode 100644
index 0000000..047a6ce
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ms/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Pengalaman Bar Navigasi Lalai (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-my/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-my/strings.xml
new file mode 100644
index 0000000..37b559a
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-my/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"မူရင်း လမ်းညွှန်ဘား စမ်းသပ်မှု (၄၈dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-nb/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-nb/strings.xml
new file mode 100644
index 0000000..e6cc2af
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-nb/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Eksperiment med standard navigasjonsrad (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ne/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ne/strings.xml
new file mode 100644
index 0000000..5df6f56
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ne/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"पूर्वनिर्धारित नेभिगेसन पट्टीको परीक्षण (४८dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-nl/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-nl/strings.xml
new file mode 100644
index 0000000..f97a352
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-nl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Experiment voor standaard navigatiebalk (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-or/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-or/strings.xml
new file mode 100644
index 0000000..e602159
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-or/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"ଡିଫଲ୍ଟ ନାଭିଗେସନ୍ ବାର୍ର ପ୍ରୟୋଗ (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-pa/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-pa/strings.xml
new file mode 100644
index 0000000..0c26291
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-pa/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"ਪੂਰਵ-ਨਿਰਧਾਰਤ ਦਿਸ਼ਾ-ਨਿਰਦੇਸ਼ ਪੱਟੀ ਪ੍ਰਯੋਗ (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-pl/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-pl/strings.xml
new file mode 100644
index 0000000..395e678
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-pl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Eksperyment z domyślnym paskiem nawigacyjnym (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-pt-rBR/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000..06f1966
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-pt-rBR/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Experimento de barra de navegação padrão (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-pt-rPT/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..e2bb116
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-pt-rPT/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Experiência de barra de navegação predefinida (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-pt/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-pt/strings.xml
new file mode 100644
index 0000000..06f1966
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-pt/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Experimento de barra de navegação padrão (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ro/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ro/strings.xml
new file mode 100644
index 0000000..2547137
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ro/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Experiment cu bară de navigare prestabilită (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ru/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ru/strings.xml
new file mode 100644
index 0000000..b5e26b3
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ru/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Экспериментальная панель навигации по умолчанию (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-si/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-si/strings.xml
new file mode 100644
index 0000000..7ef2c3e
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-si/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"පෙරනිමි සංචලන තීරු පරීක්ෂණය (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-sk/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-sk/strings.xml
new file mode 100644
index 0000000..9946848
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-sk/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Experiment s predvoleným navigačným panelom (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-sl/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-sl/strings.xml
new file mode 100644
index 0000000..34b33ae
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-sl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Preizkus s privzeto vrstico za krmarjenje (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-sq/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-sq/strings.xml
new file mode 100644
index 0000000..428e43d
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-sq/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Eksperimenti i shiritit të parazgjedhur të navigimit (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-sr/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-sr/strings.xml
new file mode 100644
index 0000000..a1f914c
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-sr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Експеримент са танком траком за навигацију (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-sv/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-sv/strings.xml
new file mode 100644
index 0000000..96f4f56
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-sv/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Standardnavigeringsfält för experiment (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-sw/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-sw/strings.xml
new file mode 100644
index 0000000..3ca80c2
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-sw/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Jaribio Chaguomsingi la Sehemu ya Viungo Muhimu (dp 48)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ta/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ta/strings.xml
new file mode 100644
index 0000000..2ff46e8
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ta/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"இயல்புநிலை வழிசெலுத்துதல் பட்டி சோதனை (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-te/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-te/strings.xml
new file mode 100644
index 0000000..99a32ed
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-te/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"డిఫాల్ట్ నావిగేషన్ పట్టీ ప్రయోగం (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-th/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-th/strings.xml
new file mode 100644
index 0000000..acfb209
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-th/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"การทดสอบแถบนำทางเริ่มต้น (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-tl/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-tl/strings.xml
new file mode 100644
index 0000000..6284190
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-tl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Eksperimentong Default na Navigation Bar (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-tr/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-tr/strings.xml
new file mode 100644
index 0000000..518e801
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-tr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Varsayılan Gezinme Çubuğu Denemesi (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-uk/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-uk/strings.xml
new file mode 100644
index 0000000..bff10a6
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-uk/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Експеримент зі стандартною панеллю навігації (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ur/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ur/strings.xml
new file mode 100644
index 0000000..ff19804
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-ur/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"ڈیفالٹ نیویگیشن بار کا تجربہ (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-uz/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-uz/strings.xml
new file mode 100644
index 0000000..d208c7b
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-uz/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Standart navigatsiya paneli tajribasi (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-vi/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-vi/strings.xml
new file mode 100644
index 0000000..067de92
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-vi/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Thử nghiệm thanh điều hướng mặc định (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-zh-rCN/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..75a1207
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-zh-rCN/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"默认导航栏实验 (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-zh-rHK/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..32ff1b2
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-zh-rHK/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"預設導覽列實驗 (48 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-zh-rTW/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..f8fe18c
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-zh-rTW/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"預設導覽列實驗 (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-zu/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-zu/strings.xml
new file mode 100644
index 0000000..0e56660
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-zu/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Ukuhlolwa kwebha yokuzulazula ezenzakalelayo (48dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-af/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-af/strings.xml
new file mode 100644
index 0000000..7e52c2a
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-af/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Eksperiment met dun navigasiebalk (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-am/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-am/strings.xml
new file mode 100644
index 0000000..533bf95
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-am/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"የቀጭን ዳሰሳ አሞሌ ሙከራ (24 ዲፒ)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ar/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ar/strings.xml
new file mode 100644
index 0000000..b338de5
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ar/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"تجربة شريط التنقل الصغير الحجم (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-as/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-as/strings.xml
new file mode 100644
index 0000000..1c15d70
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-as/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"লাহী নেভিগে’শ্বন বাৰ সম্পৰীক্ষা (২৪ডিপি)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-az/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-az/strings.xml
new file mode 100644
index 0000000..1696946
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-az/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Ensiz Naviqasiya Paneli Təcrübəsi (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-b+sr+Latn/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..fafec1d
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Eksperiment sa tankom trakom za navigaciju (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-be/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-be/strings.xml
new file mode 100644
index 0000000..ba572a5
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-be/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Эксперымент з тонкай панэллю навігацыі (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-bg/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-bg/strings.xml
new file mode 100644
index 0000000..752eb1d
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-bg/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Експеримент с тънка лента за навигация (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-bn/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-bn/strings.xml
new file mode 100644
index 0000000..b486c9f
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-bn/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"স্লিম নেভিগেশন বার সম্পর্কিত পরীক্ষা (২৪ ডিপি)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-bs/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-bs/strings.xml
new file mode 100644
index 0000000..25fb785
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-bs/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Eksperiment s tankom trakom za navigaciju (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ca/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ca/strings.xml
new file mode 100644
index 0000000..d859f0a
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ca/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Experiment amb barra de navegació fina (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-cs/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-cs/strings.xml
new file mode 100644
index 0000000..d81c6e3
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-cs/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Úzký navigační panel – experiment (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-da/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-da/strings.xml
new file mode 100644
index 0000000..2f10bfe
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-da/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Test med smal navigationslinje (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-de/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-de/strings.xml
new file mode 100644
index 0000000..2c0937b
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-de/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Test mit schmaler Navigationsleiste (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-el/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-el/strings.xml
new file mode 100644
index 0000000..ef522b3
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-el/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Πείραμα λεπτής γραμμής πλοήγησης (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rAU/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..d9958ec
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rAU/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Slim Navigation Bar Experiment (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rCA/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..d9958ec
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rCA/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Slim Navigation Bar Experiment (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rGB/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..d9958ec
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rGB/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Slim Navigation Bar Experiment (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rIN/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..d9958ec
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rIN/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Slim Navigation Bar Experiment (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-es-rUS/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..7c47655
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-es-rUS/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Experimento de la barra navegación delgada (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-es/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-es/strings.xml
new file mode 100644
index 0000000..7c47655
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-es/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Experimento de la barra navegación delgada (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-et/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-et/strings.xml
new file mode 100644
index 0000000..bf23377
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-et/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Kitsa navigeerimisriba katse (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-eu/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-eu/strings.xml
new file mode 100644
index 0000000..cc4276ed
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-eu/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Nabigazio-barra finaren esperimentua (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-fa/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-fa/strings.xml
new file mode 100644
index 0000000..3c584d4
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-fa/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"آزمایش نوار پیمایش باریک (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-fi/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-fi/strings.xml
new file mode 100644
index 0000000..33236aa
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-fi/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Ohuen navigointipalkin kokeilu (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-fr-rCA/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..523a593
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-fr-rCA/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Expérience de barre de navigation mince (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-fr/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-fr/strings.xml
new file mode 100644
index 0000000..f5a75c7
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-fr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Test relatif à la barre de navigation fine (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-gl/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-gl/strings.xml
new file mode 100644
index 0000000..8a4b2ab
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-gl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Experimento de barra de navegación estreita (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-gu/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-gu/strings.xml
new file mode 100644
index 0000000..aa3466d
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-gu/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"સ્લિમ નૅવિગેશન બારનો પ્રયોગ (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-hi/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-hi/strings.xml
new file mode 100644
index 0000000..f3a5b02
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-hi/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"स्लिम नेविगेशन बार प्रयोग (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-hr/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-hr/strings.xml
new file mode 100644
index 0000000..6532bf8
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-hr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Eksperiment s tankom navigacijskom trakom (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-hu/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-hu/strings.xml
new file mode 100644
index 0000000..2a8adf9
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-hu/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Kísérleti keskeny navigációs sáv (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-hy/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-hy/strings.xml
new file mode 100644
index 0000000..a48dfc5
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-hy/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Նավարկման նեղ գոտու փորձարկում (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-in/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-in/strings.xml
new file mode 100644
index 0000000..961b639
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-in/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Eksperimen Menu Navigasi Ramping (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-is/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-is/strings.xml
new file mode 100644
index 0000000..659f126
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-is/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Tilraun með þunna yfirlitsstiku (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-it/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-it/strings.xml
new file mode 100644
index 0000000..57df77d
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-it/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Esperimento Barra di navigazione sottile (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-iw/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-iw/strings.xml
new file mode 100644
index 0000000..5fbc0a2
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-iw/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"ניסוי של סרגל ניווט דק (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ja/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ja/strings.xml
new file mode 100644
index 0000000..ab2d149
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ja/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"スリム ナビゲーション バー テスト(24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ka/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ka/strings.xml
new file mode 100644
index 0000000..2605327
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ka/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"ნავიგაციის მჭიდრო ზოლის ექსპერიმენტი (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-kk/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-kk/strings.xml
new file mode 100644
index 0000000..99c6085
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-kk/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Жіңішке навигация жолағы (эксперимент) (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-km/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-km/strings.xml
new file mode 100644
index 0000000..f63a7bc
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-km/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"ការពិសោធនៃរបាររុករកស្ដើង (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-kn/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-kn/strings.xml
new file mode 100644
index 0000000..74a72df
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-kn/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"ಸ್ಲಿಮ್ ನ್ಯಾವಿಗೇಶನ್ ಬಾರ್ ಪ್ರಯೋಗ (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ko/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ko/strings.xml
new file mode 100644
index 0000000..0b8acb5
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ko/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"슬림한 탐색 메뉴 실험(24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ky/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ky/strings.xml
new file mode 100644
index 0000000..5629ecd
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ky/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Чакан чабыттоо тилкесин сыноо (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-lo/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-lo/strings.xml
new file mode 100644
index 0000000..71846dc
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-lo/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"ການທົດສອບແຖບນໍາທາງແບບບາງ (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-lt/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-lt/strings.xml
new file mode 100644
index 0000000..e340412
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-lt/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Plonos naršymo juostos eksperimentas (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-lv/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-lv/strings.xml
new file mode 100644
index 0000000..eb963eb
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-lv/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Plānas navigācijas joslas eksperiments (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-mk/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-mk/strings.xml
new file mode 100644
index 0000000..9e57314
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-mk/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Експеримент со тенка лента за навигација (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ml/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ml/strings.xml
new file mode 100644
index 0000000..41862af
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ml/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"സ്ലിം നാവിഗേഷൻ ബാർ പരീക്ഷണം (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-mn/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-mn/strings.xml
new file mode 100644
index 0000000..e83a219
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-mn/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Нимгэн навигацийн самбарын туршилт (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-mr/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-mr/strings.xml
new file mode 100644
index 0000000..e9ac6ee
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-mr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"स्लिम नॅव्हिगेशन बार प्रयोग (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ms/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ms/strings.xml
new file mode 100644
index 0000000..134293c
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ms/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Pengalaman Bar Navigasi Nipis (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-my/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-my/strings.xml
new file mode 100644
index 0000000..a525c08
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-my/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"လမ်းညွှန်ဘားအသေး စမ်းသပ်မှု (၂၄dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-nb/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-nb/strings.xml
new file mode 100644
index 0000000..e431c90
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-nb/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Eksperiment med tynn navigasjonsrad (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ne/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ne/strings.xml
new file mode 100644
index 0000000..ff71d9a
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ne/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"पातलो नेभिगेसन पट्टीको परीक्षण (२४dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-nl/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-nl/strings.xml
new file mode 100644
index 0000000..b3a09cc
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-nl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Experiment voor smalle navigatiebalk (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-or/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-or/strings.xml
new file mode 100644
index 0000000..98ee79b
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-or/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"ସ୍ଲିମ୍ ନାଭିଗେସନ୍ ବାର୍ର ପ୍ରୟୋଗ (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pa/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pa/strings.xml
new file mode 100644
index 0000000..06e4fd5
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pa/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"ਸਲਿਮ ਦਿਸ਼ਾ-ਨਿਰਦੇਸ਼ ਪੱਟੀ ਪ੍ਰਯੋਗ (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pl/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pl/strings.xml
new file mode 100644
index 0000000..e466973
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Eksperyment z wąskim paskiem nawigacyjnym (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pt-rBR/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000..df3c38a
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pt-rBR/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Experimento de barra de navegação fina (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pt-rPT/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..dc72eab
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pt-rPT/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Experiência de barra de navegação fina (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pt/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pt/strings.xml
new file mode 100644
index 0000000..df3c38a
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pt/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Experimento de barra de navegação fina (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ro/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ro/strings.xml
new file mode 100644
index 0000000..dc9200c
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ro/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Experiment cu bară de navigare subțire (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ru/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ru/strings.xml
new file mode 100644
index 0000000..99d5ce4
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ru/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Экспериментальная узкая панель навигации (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-si/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-si/strings.xml
new file mode 100644
index 0000000..501010f
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-si/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"සිහින් සංචලන තීරු පරීක්ෂණය (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sk/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sk/strings.xml
new file mode 100644
index 0000000..eaddff1
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sk/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Experiment s úzkym navigačným panelom (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sl/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sl/strings.xml
new file mode 100644
index 0000000..69d777e
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Preizkus z vitko vrstico za krmarjenje (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sq/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sq/strings.xml
new file mode 100644
index 0000000..9109a0e
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sq/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Eksperimenti i shiritit të hollë të navigimit (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sr/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sr/strings.xml
new file mode 100644
index 0000000..eb68b0b
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Експеримент са танком траком за навигацију (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sv/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sv/strings.xml
new file mode 100644
index 0000000..ed9ffa2
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sv/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Experimentellt tunt navigeringsfält (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sw/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sw/strings.xml
new file mode 100644
index 0000000..bf120d6
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sw/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Jaribio la Sehemu ya Viungo Muhimu Inayoweza Kupunguzwa (dp 24)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ta/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ta/strings.xml
new file mode 100644
index 0000000..29590cf
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ta/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"மெலிதான வழிசெலுத்துதல் பட்டி சோதனை (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-te/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-te/strings.xml
new file mode 100644
index 0000000..fa95dec
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-te/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"సన్నని నావిగేషన్ పట్టీ ప్రయోగం (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-th/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-th/strings.xml
new file mode 100644
index 0000000..e46eda2
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-th/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"การทดสอบแถบนำทางแบบบาง (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-tl/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-tl/strings.xml
new file mode 100644
index 0000000..3457f4b
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-tl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Eksperimentong Slim na Navigation Bar (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-tr/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-tr/strings.xml
new file mode 100644
index 0000000..bab4695
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-tr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"İnce Gezinme Çubuğu Denemesi (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-uk/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-uk/strings.xml
new file mode 100644
index 0000000..8f790be
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-uk/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Експеримент із тонкою панеллю навігації (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ur/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ur/strings.xml
new file mode 100644
index 0000000..a34cf80
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ur/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"پتلے نیویگیشن بار کا تجربہ (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-uz/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-uz/strings.xml
new file mode 100644
index 0000000..a20309a
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-uz/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Ingichka navigatsiya paneli tajribasi (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-vi/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-vi/strings.xml
new file mode 100644
index 0000000..530feeb
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-vi/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Thử nghiệm thanh điều hướng mỏng (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-zh-rCN/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..6472d4f
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-zh-rCN/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"精简导航栏实验 (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-zh-rHK/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..181e41c
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-zh-rHK/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"精簡導覽列實驗 (24 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-zh-rTW/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..a6d53f4
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-zh-rTW/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"細長導覽列實驗 (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-zu/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-zu/strings.xml
new file mode 100644
index 0000000..338d906
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-zu/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Ukuhlolwa kwebha yokuzulazula encane (24dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-af/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-af/strings.xml
new file mode 100644
index 0000000..e3d7313
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-af/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Eksperiment met dun navigasiebalk (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-am/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-am/strings.xml
new file mode 100644
index 0000000..f3128dd
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-am/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"የቀጭን ዳሰሳ አሞሌ ሙከራ (32 ዲፒ)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ar/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ar/strings.xml
new file mode 100644
index 0000000..670d09e
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ar/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"تجربة شريط التنقل الصغير الحجم (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-as/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-as/strings.xml
new file mode 100644
index 0000000..42d135a
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-as/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"লাহী নেভিগে’শ্বন বাৰ সম্পৰীক্ষা (৩২ডিপি)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-az/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-az/strings.xml
new file mode 100644
index 0000000..ef07c44
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-az/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Ensiz Naviqasiya Paneli Təcrübəsi (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-b+sr+Latn/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..024e5d5
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Eksperiment sa tankom trakom za navigaciju (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-be/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-be/strings.xml
new file mode 100644
index 0000000..71ffb20
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-be/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Эксперымент з тонкай панэллю навігацыі (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-bg/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-bg/strings.xml
new file mode 100644
index 0000000..fc159b2
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-bg/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Експеримент с тънка лента за навигация (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-bn/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-bn/strings.xml
new file mode 100644
index 0000000..cba2ffb
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-bn/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"স্লিম নেভিগেশন বার সম্পর্কিত পরীক্ষা (৩২ ডিপি)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-bs/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-bs/strings.xml
new file mode 100644
index 0000000..378c39b
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-bs/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Eksperiment s tankom trakom za navigaciju (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ca/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ca/strings.xml
new file mode 100644
index 0000000..6b5c71b
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ca/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Experiment amb barra de navegació fina (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-cs/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-cs/strings.xml
new file mode 100644
index 0000000..c779583
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-cs/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Úzký navigační panel – experiment (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-da/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-da/strings.xml
new file mode 100644
index 0000000..2f83f76
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-da/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Test med smal navigationslinje (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-de/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-de/strings.xml
new file mode 100644
index 0000000..2446b8a
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-de/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Test mit schmaler Navigationsleiste (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-el/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-el/strings.xml
new file mode 100644
index 0000000..97c52cf4
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-el/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Πείραμα λεπτής γραμμής πλοήγησης (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-en-rAU/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..90e7fae
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-en-rAU/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Slim Navigation Bar Experiment (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-en-rCA/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..90e7fae
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-en-rCA/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Slim Navigation Bar Experiment (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-en-rGB/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..90e7fae
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-en-rGB/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Slim Navigation Bar Experiment (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-en-rIN/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..90e7fae
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-en-rIN/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Slim Navigation Bar Experiment (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-es-rUS/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..02bb563
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-es-rUS/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Experimento de la barra navegación delgada (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-es/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-es/strings.xml
new file mode 100644
index 0000000..02bb563
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-es/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Experimento de la barra navegación delgada (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-et/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-et/strings.xml
new file mode 100644
index 0000000..d979cc3
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-et/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Kitsa navigeerimisriba katse (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-eu/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-eu/strings.xml
new file mode 100644
index 0000000..a9b869c
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-eu/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Nabigazio-barra finaren esperimentua (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-fa/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-fa/strings.xml
new file mode 100644
index 0000000..fb724e0
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-fa/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"آزمایش نوار پیمایش باریک (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-fi/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-fi/strings.xml
new file mode 100644
index 0000000..88fa2b9
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-fi/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Ohuen navigointipalkin kokeilu (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-fr-rCA/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..2a73bd3
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-fr-rCA/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Expérience de barre de navigation mince (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-fr/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-fr/strings.xml
new file mode 100644
index 0000000..1530d81
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-fr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Test relatif à la barre de navigation fine (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-gl/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-gl/strings.xml
new file mode 100644
index 0000000..174f501
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-gl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Experimento da barra de navegación estreita (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-gu/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-gu/strings.xml
new file mode 100644
index 0000000..1e9de62
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-gu/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"સ્લિમ નૅવિગેશન બારનો પ્રયોગ (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-hi/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-hi/strings.xml
new file mode 100644
index 0000000..f52b1fd
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-hi/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"स्लिम नेविगेशन बार प्रयोग (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-hr/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-hr/strings.xml
new file mode 100644
index 0000000..04aed15
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-hr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Eksperiment s tankom navigacijskom trakom (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-hu/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-hu/strings.xml
new file mode 100644
index 0000000..500b45e
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-hu/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Kísérleti keskeny navigációs sáv (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-hy/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-hy/strings.xml
new file mode 100644
index 0000000..a5f684b
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-hy/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Նավարկման նեղ գոտու փորձարկում (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-in/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-in/strings.xml
new file mode 100644
index 0000000..fe78b4c
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-in/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Eksperimen Menu Navigasi Ramping (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-is/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-is/strings.xml
new file mode 100644
index 0000000..1d6da50
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-is/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Tilraun með þunna yfirlitsstiku (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-it/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-it/strings.xml
new file mode 100644
index 0000000..dc3530e
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-it/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Esperimento Barra di navigazione sottile (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-iw/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-iw/strings.xml
new file mode 100644
index 0000000..8832941
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-iw/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"ניסוי של סרגל ניווט דק (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ja/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ja/strings.xml
new file mode 100644
index 0000000..7bab58a
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ja/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"スリム ナビゲーション バー テスト(32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ka/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ka/strings.xml
new file mode 100644
index 0000000..8a7f3bf
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ka/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"ნავიგაციის მჭიდრო ზოლის ექსპერიმენტი (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-kk/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-kk/strings.xml
new file mode 100644
index 0000000..182ac1a
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-kk/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Жіңішке навигация жолағы (эксперимент) (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-km/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-km/strings.xml
new file mode 100644
index 0000000..0a52f66
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-km/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"ការពិសោធនៃរបាររុករកស្ដើង (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-kn/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-kn/strings.xml
new file mode 100644
index 0000000..118303f
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-kn/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"ಸ್ಲಿಮ್ ನ್ಯಾವಿಗೇಶನ್ ಬಾರ್ ಪ್ರಯೋಗ (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ko/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ko/strings.xml
new file mode 100644
index 0000000..11f28ce
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ko/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"슬림한 탐색 메뉴 실험(32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ky/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ky/strings.xml
new file mode 100644
index 0000000..4418c4d
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ky/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Чакан чабыттоо тилкесин сыноо (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-lo/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-lo/strings.xml
new file mode 100644
index 0000000..090ab09
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-lo/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"ການທົດສອບແຖບນໍາທາງແບບບາງ (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-lt/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-lt/strings.xml
new file mode 100644
index 0000000..cb72a0f
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-lt/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Plonos naršymo juostos eksperimentas (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-lv/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-lv/strings.xml
new file mode 100644
index 0000000..164e7d2
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-lv/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Plānas navigācijas joslas eksperiments (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-mk/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-mk/strings.xml
new file mode 100644
index 0000000..e10d685
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-mk/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Експеримент со тенка лента за навигација (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ml/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ml/strings.xml
new file mode 100644
index 0000000..878ea10
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ml/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"സ്ലിം നാവിഗേഷൻ ബാർ പരീക്ഷണം (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-mn/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-mn/strings.xml
new file mode 100644
index 0000000..a808c5c
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-mn/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Нимгэн навигацийн самбарын туршилт (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-mr/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-mr/strings.xml
new file mode 100644
index 0000000..8454ba7
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-mr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"स्लिम नॅव्हिगेशन बार प्रयोग (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ms/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ms/strings.xml
new file mode 100644
index 0000000..6494245
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ms/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Pengalaman Bar Navigasi Nipis (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-my/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-my/strings.xml
new file mode 100644
index 0000000..11586c0
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-my/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"လမ်းညွှန်ဘားအသေး စမ်းသပ်မှု (၃၂dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-nb/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-nb/strings.xml
new file mode 100644
index 0000000..625c605
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-nb/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Eksperiment med tynn navigasjonsrad (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ne/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ne/strings.xml
new file mode 100644
index 0000000..b0d019c
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ne/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"पातलो नेभिगेसन पट्टीको परीक्षण (३२dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-nl/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-nl/strings.xml
new file mode 100644
index 0000000..9d14cef
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-nl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Experiment voor smalle navigatiebalk (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-or/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-or/strings.xml
new file mode 100644
index 0000000..e941c52
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-or/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"ସ୍ଲିମ୍ ନାଭିଗେସନ୍ ବାର୍ର ପ୍ରୟୋଗ (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-pa/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-pa/strings.xml
new file mode 100644
index 0000000..c85e9d6
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-pa/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"ਸਲਿਮ ਦਿਸ਼ਾ-ਨਿਰਦੇਸ਼ ਪੱਟੀ ਪ੍ਰਯੋਗ (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-pl/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-pl/strings.xml
new file mode 100644
index 0000000..a254e62
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-pl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Eksperyment z wąskim paskiem nawigacyjnym (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-pt-rBR/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000..8bab19c
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-pt-rBR/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Experimento de barra de navegação fina (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-pt-rPT/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..133d97d
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-pt-rPT/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Experiência de barra de navegação fina (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-pt/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-pt/strings.xml
new file mode 100644
index 0000000..8bab19c
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-pt/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Experimento de barra de navegação fina (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ro/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ro/strings.xml
new file mode 100644
index 0000000..f1518718
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ro/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Experiment cu bară de navigare subțire (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ru/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ru/strings.xml
new file mode 100644
index 0000000..3031ae0
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ru/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Экспериментальная узкая панель навигации (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-si/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-si/strings.xml
new file mode 100644
index 0000000..a257a76
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-si/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"සිහින් සංචලන තීරු පරීක්ෂණය (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-sk/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-sk/strings.xml
new file mode 100644
index 0000000..f9fe39b
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-sk/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Experiment s úzkym navigačným panelom (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-sl/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-sl/strings.xml
new file mode 100644
index 0000000..5849b03
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-sl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Preizkus z vitko vrstico za krmarjenje (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-sq/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-sq/strings.xml
new file mode 100644
index 0000000..719f233
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-sq/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Eksperimenti i shiritit të hollë të navigimit (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-sr/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-sr/strings.xml
new file mode 100644
index 0000000..3d67560
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-sr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Експеримент са танком траком за навигацију (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-sv/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-sv/strings.xml
new file mode 100644
index 0000000..daee00a
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-sv/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Experimentellt tunt navigeringsfält (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-sw/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-sw/strings.xml
new file mode 100644
index 0000000..cf44256
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-sw/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Jaribio la Sehemu ya Viungo Muhimu Inayoweza Kupunguzwa (dp 32)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ta/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ta/strings.xml
new file mode 100644
index 0000000..e69442a
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ta/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"மெலிதான வழிசெலுத்துதல் பட்டி சோதனை (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-te/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-te/strings.xml
new file mode 100644
index 0000000..5a39ccf
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-te/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"సన్నని నావిగేషన్ పట్టీ ప్రయోగం (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-th/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-th/strings.xml
new file mode 100644
index 0000000..e197554
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-th/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"การทดสอบแถบนำทางแบบบาง (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-tl/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-tl/strings.xml
new file mode 100644
index 0000000..bbaa96f
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-tl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Eksperimentong Slim na Navigation Bar (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-tr/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-tr/strings.xml
new file mode 100644
index 0000000..d315cf4
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-tr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"İnce Gezinme Çubuğu Denemesi (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-uk/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-uk/strings.xml
new file mode 100644
index 0000000..e75f766
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-uk/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Експеримент із тонкою панеллю навігації (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ur/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ur/strings.xml
new file mode 100644
index 0000000..743903d
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-ur/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"پتلے نیویگیشن بار کا تجربہ (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-uz/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-uz/strings.xml
new file mode 100644
index 0000000..d8519a7
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-uz/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Ingichka navigatsiya paneli tajribasi (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-vi/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-vi/strings.xml
new file mode 100644
index 0000000..bc22ff8
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-vi/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Thử nghiệm thanh điều hướng mỏng (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-zh-rCN/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..b0e6f58
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-zh-rCN/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"精简导航栏实验 (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-zh-rHK/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..5dd0d79
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-zh-rHK/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"精簡導覽列實驗 (32 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-zh-rTW/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..b1f6b06
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-zh-rTW/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"細長導覽列實驗 (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-zu/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-zu/strings.xml
new file mode 100644
index 0000000..9a8bb4f
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-zu/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Ukuhlolwa kwebha yokuzulazula encane (32dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-af/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-af/strings.xml
new file mode 100644
index 0000000..e815299
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-af/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Eksperiment met dun navigasiebalk (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-am/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-am/strings.xml
new file mode 100644
index 0000000..5644a85
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-am/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"የቀጭን ዳሰሳ አሞሌ ሙከራ (40 ዲፒ)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ar/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ar/strings.xml
new file mode 100644
index 0000000..40a11de
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ar/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"تجربة شريط التنقل الصغير الحجم (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-as/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-as/strings.xml
new file mode 100644
index 0000000..1c4003f
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-as/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"লাহী নেভিগে’শ্বন বাৰ সম্পৰীক্ষা (৪০ডিপি)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-az/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-az/strings.xml
new file mode 100644
index 0000000..5897e75
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-az/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Ensiz Naviqasiya Paneli Təcrübəsi (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-b+sr+Latn/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..1f49607
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Eksperiment sa tankom trakom za navigaciju (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-be/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-be/strings.xml
new file mode 100644
index 0000000..2c46430
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-be/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Эксперымент з тонкай панэллю навігацыі (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-bg/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-bg/strings.xml
new file mode 100644
index 0000000..25a1496
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-bg/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Експеримент с тънка лента за навигация (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-bn/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-bn/strings.xml
new file mode 100644
index 0000000..66ba5b0
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-bn/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"স্লিম নেভিগেশন বার সম্পর্কিত পরীক্ষা (৪০ ডিপি)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-bs/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-bs/strings.xml
new file mode 100644
index 0000000..21597ce
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-bs/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Eksperiment s tankom trakom za navigaciju (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ca/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ca/strings.xml
new file mode 100644
index 0000000..34ce751
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ca/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Experiment amb barra de navegació fina (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-cs/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-cs/strings.xml
new file mode 100644
index 0000000..cd87611
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-cs/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Úzký navigační panel – experiment (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-da/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-da/strings.xml
new file mode 100644
index 0000000..fcbd7b5
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-da/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Test med smal navigationslinje (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-de/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-de/strings.xml
new file mode 100644
index 0000000..8ee35b7
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-de/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Test mit schmaler Navigationsleiste (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-el/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-el/strings.xml
new file mode 100644
index 0000000..c016e83
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-el/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Πείραμα λεπτής γραμμής πλοήγησης (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-en-rAU/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..b3621cb
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-en-rAU/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Slim Navigation Bar Experiment (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-en-rCA/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..b3621cb
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-en-rCA/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Slim Navigation Bar Experiment (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-en-rGB/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..b3621cb
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-en-rGB/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Slim Navigation Bar Experiment (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-en-rIN/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..b3621cb
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-en-rIN/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Slim Navigation Bar Experiment (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-es-rUS/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..d1cc0fe
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-es-rUS/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Experimento de la barra navegación delgada (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-es/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-es/strings.xml
new file mode 100644
index 0000000..d1cc0fe
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-es/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Experimento de la barra navegación delgada (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-et/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-et/strings.xml
new file mode 100644
index 0000000..64dc9ae
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-et/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Kitsa navigeerimisriba katse (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-eu/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-eu/strings.xml
new file mode 100644
index 0000000..98a4dd9
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-eu/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Nabigazio-barra finaren esperimentua (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-fa/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-fa/strings.xml
new file mode 100644
index 0000000..987ab4a
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-fa/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"آزمایش نوار پیمایش باریک (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-fi/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-fi/strings.xml
new file mode 100644
index 0000000..e850c3f
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-fi/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Ohuen navigointipalkin kokeilu (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-fr-rCA/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..c196ef9
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-fr-rCA/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Expérience de barre de navigation mince (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-fr/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-fr/strings.xml
new file mode 100644
index 0000000..dd1ec9c
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-fr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Test relatif à la barre de navigation fine (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-gl/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-gl/strings.xml
new file mode 100644
index 0000000..5c44987
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-gl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Experimento da barra de navegación estreita (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-gu/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-gu/strings.xml
new file mode 100644
index 0000000..ed1f5ce
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-gu/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"સ્લિમ નૅવિગેશન બારનો પ્રયોગ (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-hi/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-hi/strings.xml
new file mode 100644
index 0000000..64141ce
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-hi/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"स्लिम नेविगेशन बार प्रयोग (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-hr/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-hr/strings.xml
new file mode 100644
index 0000000..41ec2e7
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-hr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Eksperiment s tankom navigacijskom trakom (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-hu/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-hu/strings.xml
new file mode 100644
index 0000000..3fb2907
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-hu/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Kísérleti keskeny navigációs sáv (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-hy/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-hy/strings.xml
new file mode 100644
index 0000000..04d6995
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-hy/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Նավարկման նեղ գոտու փորձարկում (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-in/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-in/strings.xml
new file mode 100644
index 0000000..2f01577
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-in/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Eksperimen Menu Navigasi Ramping (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-is/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-is/strings.xml
new file mode 100644
index 0000000..5ccf607
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-is/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Tilraun með þunna yfirlitsstiku (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-it/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-it/strings.xml
new file mode 100644
index 0000000..1522bd3
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-it/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Esperimento Barra di navigazione sottile (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-iw/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-iw/strings.xml
new file mode 100644
index 0000000..cf40958
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-iw/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"ניסוי של סרגל ניווט דק (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ja/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ja/strings.xml
new file mode 100644
index 0000000..51797e6
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ja/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"スリム ナビゲーション バー テスト(40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ka/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ka/strings.xml
new file mode 100644
index 0000000..575b453
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ka/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"ნავიგაციის მჭიდრო ზოლის ექსპერიმენტი (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-kk/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-kk/strings.xml
new file mode 100644
index 0000000..4febe3f
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-kk/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Жіңішке навигация жолағы (эксперимент) (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-km/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-km/strings.xml
new file mode 100644
index 0000000..58b9e16
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-km/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"ការពិសោធនៃរបាររុករកស្ដើង (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-kn/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-kn/strings.xml
new file mode 100644
index 0000000..35a6776
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-kn/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"ಸ್ಲಿಮ್ ನ್ಯಾವಿಗೇಶನ್ ಬಾರ್ ಪ್ರಯೋಗ (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ko/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ko/strings.xml
new file mode 100644
index 0000000..bced462
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ko/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"슬림한 탐색 메뉴 실험(40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ky/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ky/strings.xml
new file mode 100644
index 0000000..8b34990
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ky/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Чакан чабыттоо тилкесин сыноо (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-lo/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-lo/strings.xml
new file mode 100644
index 0000000..b7fe3cf
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-lo/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"ການທົດສອບແຖບນໍາທາງແບບບາງ (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-lt/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-lt/strings.xml
new file mode 100644
index 0000000..808a90c
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-lt/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Plonos naršymo juostos eksperimentas (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-lv/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-lv/strings.xml
new file mode 100644
index 0000000..002e8687
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-lv/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Plānas navigācijas joslas eksperiments (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-mk/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-mk/strings.xml
new file mode 100644
index 0000000..148b5ed
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-mk/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Експеримент со тенка лента за навигација (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ml/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ml/strings.xml
new file mode 100644
index 0000000..be37488
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ml/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"സ്ലിം നാവിഗേഷൻ ബാർ പരീക്ഷണം (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-mn/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-mn/strings.xml
new file mode 100644
index 0000000..41e2ce1
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-mn/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Нимгэн навигацийн самбарын туршилт (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-mr/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-mr/strings.xml
new file mode 100644
index 0000000..d946257
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-mr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"स्लिम नॅव्हिगेशन बार प्रयोग (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ms/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ms/strings.xml
new file mode 100644
index 0000000..a442be3
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ms/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Pengalaman Bar Navigasi Nipis (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-my/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-my/strings.xml
new file mode 100644
index 0000000..f12cf41
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-my/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"လမ်းညွှန်ဘားအသေး စမ်းသပ်မှု (၄၀dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-nb/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-nb/strings.xml
new file mode 100644
index 0000000..d138836
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-nb/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Eksperiment med tynn navigasjonsrad (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ne/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ne/strings.xml
new file mode 100644
index 0000000..9fce6c2
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ne/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"पातलो नेभिगेसन पट्टीको परीक्षण (४०dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-nl/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-nl/strings.xml
new file mode 100644
index 0000000..8eae8ce
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-nl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Experiment voor smalle navigatiebalk (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-or/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-or/strings.xml
new file mode 100644
index 0000000..aff8e9c
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-or/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"ସ୍ଲିମ୍ ନାଭିଗେସନ୍ ବାର୍ର ପ୍ରୟୋଗ (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-pa/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-pa/strings.xml
new file mode 100644
index 0000000..c23a03f
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-pa/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"ਸਲਿਮ ਦਿਸ਼ਾ-ਨਿਰਦੇਸ਼ ਪੱਟੀ ਪ੍ਰਯੋਗ (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-pl/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-pl/strings.xml
new file mode 100644
index 0000000..7cd3e4e
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-pl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Eksperyment z wąskim paskiem nawigacyjnym (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-pt-rBR/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000..dbc47fd
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-pt-rBR/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Experimento de barra de navegação fina (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-pt-rPT/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..82ef087
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-pt-rPT/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Experiência de barra de navegação fina (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-pt/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-pt/strings.xml
new file mode 100644
index 0000000..dbc47fd
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-pt/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Experimento de barra de navegação fina (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ro/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ro/strings.xml
new file mode 100644
index 0000000..18a96e0
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ro/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Experiment cu bară de navigare subțire (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ru/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ru/strings.xml
new file mode 100644
index 0000000..195ab10
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ru/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Экспериментальная узкая панель навигации (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-si/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-si/strings.xml
new file mode 100644
index 0000000..0bda351
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-si/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"සිහින් සංචලන තීරු පරීක්ෂණය (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-sk/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-sk/strings.xml
new file mode 100644
index 0000000..9aebdfbe
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-sk/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Experiment s úzkym navigačným panelom (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-sl/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-sl/strings.xml
new file mode 100644
index 0000000..68f6e6d
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-sl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Preizkus z vitko vrstico za krmarjenje (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-sq/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-sq/strings.xml
new file mode 100644
index 0000000..18c1ff0
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-sq/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Eksperimenti i shiritit të hollë të navigimit (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-sr/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-sr/strings.xml
new file mode 100644
index 0000000..8067165
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-sr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Експеримент са танком траком за навигацију (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-sv/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-sv/strings.xml
new file mode 100644
index 0000000..d91626e
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-sv/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Experimentellt tunt navigeringsfält (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-sw/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-sw/strings.xml
new file mode 100644
index 0000000..f3ca208
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-sw/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Jaribio la Sehemu ya Viungo Muhimu Inayoweza Kupunguzwa (dp 40)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ta/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ta/strings.xml
new file mode 100644
index 0000000..04bee67
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ta/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"மெலிதான வழிசெலுத்துதல் பட்டி சோதனை (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-te/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-te/strings.xml
new file mode 100644
index 0000000..fc0cd55
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-te/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"సన్నని నావిగేషన్ పట్టీ ప్రయోగం (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-th/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-th/strings.xml
new file mode 100644
index 0000000..33d46e4
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-th/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"การทดสอบแถบนำทางแบบบาง (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-tl/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-tl/strings.xml
new file mode 100644
index 0000000..bf6451f
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-tl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Eksperimentong Slim na Navigation Bar (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-tr/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-tr/strings.xml
new file mode 100644
index 0000000..7918059
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-tr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"İnce Gezinme Çubuğu Denemesi (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-uk/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-uk/strings.xml
new file mode 100644
index 0000000..b45b9b4
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-uk/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Експеримент із тонкою панеллю навігації (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ur/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ur/strings.xml
new file mode 100644
index 0000000..9281ff8
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-ur/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"پتلے نیویگیشن بار کا تجربہ (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-uz/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-uz/strings.xml
new file mode 100644
index 0000000..46e7334
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-uz/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Ingichka navigatsiya paneli tajribasi (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-vi/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-vi/strings.xml
new file mode 100644
index 0000000..ea0f155
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-vi/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Thử nghiệm thanh điều hướng mỏng (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-zh-rCN/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..5746854
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-zh-rCN/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"精简导航栏实验 (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-zh-rHK/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..aad885b
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-zh-rHK/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"精簡導覽列實驗 (40 dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-zh-rTW/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..590e4f5
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-zh-rTW/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"細長導覽列實驗 (40dp)"</string>
+</resources>
diff --git a/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-zu/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-zu/strings.xml
new file mode 100644
index 0000000..e077f08
--- /dev/null
+++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-zu/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Ukuhlolwa kwebha yokuzulazula encane (40dp)"</string>
+</resources>
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index 8261fe8..efa4e79 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -73,6 +73,10 @@
// The view switched to summary mode (most relevant for notifications)
TYPE_COLLAPSE = 14;
+
+ // The notification was adjusted by the assistant. Enum value is
+ // out of sequence due to b/122737498.
+ TYPE_NOTIFICATION_ASSISTANT_ADJUSTMENT = 1573;
}
// Types of alerts, as bit field values
@@ -232,6 +236,17 @@
BLOCKING_HELPER_CLICK_UNDO = 7;
}
+ // The (visual) location of a Notification.
+ enum NotificationLocation {
+ LOCATION_UNKNOWN = 0;
+ LOCATION_FIRST_HEADS_UP = 1; // visible heads-up
+ LOCATION_HIDDEN_TOP = 2; // hidden/scrolled away on the top
+ LOCATION_MAIN_AREA = 3; // visible in the shade
+ LOCATION_BOTTOM_STACK_PEEKING = 4; // in the bottom stack, and peeking
+ LOCATION_BOTTOM_STACK_HIDDEN = 5; // in the bottom stack, and hidden
+ LOCATION_GONE = 6; // the view isn't laid out at all
+ }
+
// Known visual elements: views or controls.
enum View {
// Unknown view
@@ -4049,6 +4064,8 @@
// - AUTOFILL_INVALID_DATASET_AUTHENTICATION
// NOTE: starting on OS Q, it also added the following fields:
// Tag FIELD_AUTOFILL_TEXT_LEN: length of the error message provided by the service
+ // Tag FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS: number of requests made to the augmented
+ // autofill service
AUTOFILL_REQUEST = 907;
// Tag of a field for a package of an autofill service
@@ -6820,6 +6837,31 @@
// OS: Q
MOBILE_NETWORK_LIST = 1627;
+ // OPEN: Settings > Display > Adaptive sleep
+ // OS: Q
+ SETTINGS_ADAPTIVE_SLEEP = 1628;
+
+ // Tagged data for SMART_REPLY_VISIBLE and NOTIFICATION_ITEM_ACTION.
+ // The UI location of the notification containing the smart suggestions.
+ // This is a NotificationLocation object (see the NotificationLocation
+ // enum).
+ // OS: Q
+ NOTIFICATION_LOCATION = 1629;
+
+ // The autofill system made request to the system-provided augmented autofill service.
+ // OS: Q
+ // Package: Package of app that is autofilled
+ // Tag FIELD_CLASS_NAME: Class name of the activity that is autofilled.
+ // Tag FIELD_AUTOFILL_SERVICE: Package of the augmented autofill service that processed the
+ // request
+ // Tag FIELD_AUTOFILL_SESSION_ID: id of the autofill session associated with this metric.
+ // Tag FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
+ AUTOFILL_AUGMENTED_REQUEST = 1630;
+
+ // Tag of a field for the number of augmented autofill requests in a session
+ // OS: Q
+ FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS = 1631;
+
// ---- 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 1212676..6ff2b35 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -216,9 +216,9 @@
// Package: android
NOTE_SOFTAP_CONFIG_CHANGED = 50;
- // Notify the user that connected to app suggested network.
+ // Notify the user that an app suggested network is available for connection.
// Package: android
- NOTE_CONNECTED_TO_NETWORK_SUGGESTION = 51;
+ NOTE_NETWORK_SUGGESTION_AVAILABLE = 51;
// ADD_NEW_IDS_ABOVE_THIS_LINE
// Legacy IDs with arbitrary values appear below
diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto
index bcc43a7..79b63bc 100644
--- a/proto/src/wifi.proto
+++ b/proto/src/wifi.proto
@@ -488,6 +488,9 @@
// Counts the occurrences of each Wifi usability score provided by external app
repeated WifiUsabilityScoreCount wifi_usability_score_count = 127;
+
+ // List of PNO scan stats, one element for each mobility state
+ repeated DeviceMobilityStatePnoScanStats mobility_state_pno_stats_list = 128;
}
// Information that gets logged for every WiFi connection.
@@ -1816,4 +1819,33 @@
// The list of timestamped wifi usability stats
repeated WifiUsabilityStatsEntry stats = 2;
-}
\ No newline at end of file
+}
+
+message DeviceMobilityStatePnoScanStats {
+ // see WifiManager.DEVICE_MOBILITY_STATE_* constants
+ enum DeviceMobilityState {
+ // Unknown mobility
+ UNKNOWN = 0;
+
+ // High movement
+ HIGH_MVMT = 1;
+
+ // Low movement
+ LOW_MVMT = 2;
+
+ // Stationary
+ STATIONARY = 3;
+ }
+
+ // The device mobility state
+ optional DeviceMobilityState device_mobility_state = 1;
+
+ // The number of times that this state was entered
+ optional int32 num_times_entered_state = 2;
+
+ // The total duration elapsed while in this mobility state, in ms
+ optional int64 total_duration_ms = 3;
+
+ // the total duration elapsed while in this mobility state with PNO scans running, in ms
+ optional int64 pno_duration_ms = 4;
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 6eba914..2c075dc 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -53,6 +53,7 @@
import android.view.accessibility.IAccessibilityInteractionConnection;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.DumpUtils;
import com.android.server.accessibility.AccessibilityManagerService.RemoteAccessibilityConnection;
@@ -751,7 +752,7 @@
}
@Override
- public float getMagnificationScale() {
+ public float getMagnificationScale(int displayId) {
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
return 1.0f;
@@ -759,14 +760,14 @@
}
final long identity = Binder.clearCallingIdentity();
try {
- return mSystemSupport.getMagnificationController().getScale();
+ return mSystemSupport.getMagnificationController().getScale(displayId);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
- public Region getMagnificationRegion() {
+ public Region getMagnificationRegion(int displayId) {
synchronized (mLock) {
final Region region = Region.obtain();
if (!isCalledForCurrentUserLocked()) {
@@ -775,22 +776,22 @@
MagnificationController magnificationController =
mSystemSupport.getMagnificationController();
boolean registeredJustForThisCall =
- registerMagnificationIfNeeded(magnificationController);
+ registerMagnificationIfNeeded(displayId, magnificationController);
final long identity = Binder.clearCallingIdentity();
try {
- magnificationController.getMagnificationRegion(region);
+ magnificationController.getMagnificationRegion(displayId, region);
return region;
} finally {
Binder.restoreCallingIdentity(identity);
if (registeredJustForThisCall) {
- magnificationController.unregister();
+ magnificationController.unregister(displayId);
}
}
}
}
@Override
- public float getMagnificationCenterX() {
+ public float getMagnificationCenterX(int displayId) {
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
return 0.0f;
@@ -798,21 +799,21 @@
MagnificationController magnificationController =
mSystemSupport.getMagnificationController();
boolean registeredJustForThisCall =
- registerMagnificationIfNeeded(magnificationController);
+ registerMagnificationIfNeeded(displayId, magnificationController);
final long identity = Binder.clearCallingIdentity();
try {
- return magnificationController.getCenterX();
+ return magnificationController.getCenterX(displayId);
} finally {
Binder.restoreCallingIdentity(identity);
if (registeredJustForThisCall) {
- magnificationController.unregister();
+ magnificationController.unregister(displayId);
}
}
}
}
@Override
- public float getMagnificationCenterY() {
+ public float getMagnificationCenterY(int displayId) {
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
return 0.0f;
@@ -820,31 +821,31 @@
MagnificationController magnificationController =
mSystemSupport.getMagnificationController();
boolean registeredJustForThisCall =
- registerMagnificationIfNeeded(magnificationController);
+ registerMagnificationIfNeeded(displayId, magnificationController);
final long identity = Binder.clearCallingIdentity();
try {
- return magnificationController.getCenterY();
+ return magnificationController.getCenterY(displayId);
} finally {
Binder.restoreCallingIdentity(identity);
if (registeredJustForThisCall) {
- magnificationController.unregister();
+ magnificationController.unregister(displayId);
}
}
}
}
- private boolean registerMagnificationIfNeeded(
+ private boolean registerMagnificationIfNeeded(int displayId,
MagnificationController magnificationController) {
- if (!magnificationController.isRegisteredLocked()
+ if (!magnificationController.isRegistered(displayId)
&& mSecurityPolicy.canControlMagnification(this)) {
- magnificationController.register();
+ magnificationController.register(displayId);
return true;
}
return false;
}
@Override
- public boolean resetMagnification(boolean animate) {
+ public boolean resetMagnification(int displayId, boolean animate) {
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
return false;
@@ -857,16 +858,16 @@
try {
MagnificationController magnificationController =
mSystemSupport.getMagnificationController();
- return (magnificationController.reset(animate)
- || !magnificationController.isMagnifying());
+ return (magnificationController.reset(displayId, animate)
+ || !magnificationController.isMagnifying(displayId));
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
- public boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
- boolean animate) {
+ public boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX,
+ float centerY, boolean animate) {
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
return false;
@@ -878,11 +879,11 @@
try {
MagnificationController magnificationController =
mSystemSupport.getMagnificationController();
- if (!magnificationController.isRegisteredLocked()) {
- magnificationController.register();
+ if (!magnificationController.isRegistered(displayId)) {
+ magnificationController.register(displayId);
}
return magnificationController
- .setScaleAndCenter(scale, centerX, centerY, animate, mId);
+ .setScaleAndCenter(displayId, scale, centerX, centerY, animate, mId);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -890,12 +891,12 @@
}
@Override
- public void setMagnificationCallbackEnabled(boolean enabled) {
- mInvocationHandler.setMagnificationCallbackEnabled(enabled);
+ public void setMagnificationCallbackEnabled(int displayId, boolean enabled) {
+ mInvocationHandler.setMagnificationCallbackEnabled(displayId, enabled);
}
- public boolean isMagnificationCallbackEnabled() {
- return mInvocationHandler.mIsMagnificationCallbackEnabled;
+ public boolean isMagnificationCallbackEnabled(int displayId) {
+ return mInvocationHandler.isMagnificationCallbackEnabled(displayId);
}
@Override
@@ -1106,10 +1107,10 @@
InvocationHandler.MSG_CLEAR_ACCESSIBILITY_CACHE);
}
- public void notifyMagnificationChangedLocked(@NonNull Region region,
+ public void notifyMagnificationChangedLocked(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
mInvocationHandler
- .notifyMagnificationChangedLocked(region, scale, centerX, centerY);
+ .notifyMagnificationChangedLocked(displayId, region, scale, centerX, centerY);
}
public void notifySoftKeyboardShowModeChangedLocked(int showState) {
@@ -1128,12 +1129,12 @@
* Called by the invocation handler to notify the service that the
* state of magnification has changed.
*/
- private void notifyMagnificationChangedInternal(@NonNull Region region,
+ private void notifyMagnificationChangedInternal(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
if (listener != null) {
try {
- listener.onMagnificationChanged(region, scale, centerX, centerY);
+ listener.onMagnificationChanged(displayId, region, scale, centerX, centerY);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error sending magnification changes to " + mService, re);
}
@@ -1251,7 +1252,9 @@
private static final int MSG_ON_ACCESSIBILITY_BUTTON_CLICKED = 7;
private static final int MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED = 8;
- private boolean mIsMagnificationCallbackEnabled = false;
+ /** List of magnification callback states, mapping from displayId -> Boolean */
+ @GuardedBy("mlock")
+ private final SparseArray<Boolean> mMagnificationCallbackState = new SparseArray<>(0);
private boolean mIsSoftKeyboardCallbackEnabled = false;
public InvocationHandler(Looper looper) {
@@ -1277,7 +1280,8 @@
final float scale = (float) args.arg2;
final float centerX = (float) args.arg3;
final float centerY = (float) args.arg4;
- notifyMagnificationChangedInternal(region, scale, centerX, centerY);
+ final int displayId = args.argi1;
+ notifyMagnificationChangedInternal(displayId, region, scale, centerX, centerY);
args.recycle();
} break;
@@ -1301,11 +1305,12 @@
}
}
- public void notifyMagnificationChangedLocked(@NonNull Region region, float scale,
- float centerX, float centerY) {
- if (!mIsMagnificationCallbackEnabled) {
- // Callback is disabled, don't bother packing args.
- return;
+ public void notifyMagnificationChangedLocked(int displayId, @NonNull Region region,
+ float scale, float centerX, float centerY) {
+ synchronized (mLock) {
+ if (mMagnificationCallbackState.get(displayId) == null) {
+ return;
+ }
}
final SomeArgs args = SomeArgs.obtain();
@@ -1313,13 +1318,26 @@
args.arg2 = scale;
args.arg3 = centerX;
args.arg4 = centerY;
+ args.argi1 = displayId;
final Message msg = obtainMessage(MSG_ON_MAGNIFICATION_CHANGED, args);
msg.sendToTarget();
}
- public void setMagnificationCallbackEnabled(boolean enabled) {
- mIsMagnificationCallbackEnabled = enabled;
+ public void setMagnificationCallbackEnabled(int displayId, boolean enabled) {
+ synchronized (mLock) {
+ if (enabled) {
+ mMagnificationCallbackState.put(displayId, true);
+ } else {
+ mMagnificationCallbackState.remove(displayId);
+ }
+ }
+ }
+
+ public boolean isMagnificationCallbackEnabled(int displayId) {
+ synchronized (mLock) {
+ return mMagnificationCallbackState.get(displayId) != null;
+ }
}
public void notifySoftKeyboardShowModeChangedLocked(int showState) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 763c16f..dbe86c1 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -208,6 +208,8 @@
private final WindowManagerInternal mWindowManagerService;
+ private final DisplayManager mDisplayManager;
+
private AppWidgetManagerInternal mAppWidgetService;
private final SecurityPolicy mSecurityPolicy;
@@ -304,10 +306,12 @@
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
mMainHandler = new MainHandler(mContext.getMainLooper());
mGlobalActionPerformer = new GlobalActionPerformer(mContext, mWindowManagerService);
+ mDisplayManager = mContext.getSystemService(DisplayManager.class);
registerBroadcastReceivers();
new AccessibilityContentObserver(mMainHandler).register(
context.getContentResolver());
+ registerDisplayListener(mMainHandler);
}
@Override
@@ -523,6 +527,30 @@
}, 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) {
@@ -968,17 +996,18 @@
* Called by the MagnificationController when the state of display
* magnification changes.
*
+ * @param displayId The logical display id.
* @param region the new magnified region, may be empty if
* magnification is not enabled (e.g. scale is 1)
* @param scale the new scale
* @param centerX the new screen-relative center X coordinate
* @param centerY the new screen-relative center Y coordinate
*/
- public void notifyMagnificationChanged(@NonNull Region region,
+ public void notifyMagnificationChanged(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
synchronized (mLock) {
notifyClearAccessibilityCacheLocked();
- notifyMagnificationChangedLocked(region, scale, centerX, centerY);
+ notifyMagnificationChangedLocked(displayId, region, scale, centerX, centerY);
}
}
@@ -1203,12 +1232,12 @@
}
}
- private void notifyMagnificationChangedLocked(@NonNull Region region,
+ private void notifyMagnificationChangedLocked(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
final UserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
final AccessibilityServiceConnection service = state.mBoundServices.get(i);
- service.notifyMagnificationChangedLocked(region, scale, centerX, centerY);
+ service.notifyMagnificationChangedLocked(displayId, region, scale, centerX, centerY);
}
}
@@ -2191,15 +2220,44 @@
return;
}
- if (!mUiAutomationManager.suppressingAccessibilityServicesLocked()
- && (userState.mIsDisplayMagnificationEnabled
- || userState.mIsNavBarMagnificationEnabled
- || userHasListeningMagnificationServicesLocked(userState))) {
- // Initialize the magnification controller if necessary
- getMagnificationController();
- mMagnificationController.register();
- } else if (mMagnificationController != null) {
- mMagnificationController.unregister();
+ if (mUiAutomationManager.suppressingAccessibilityServicesLocked()
+ && mMagnificationController != null) {
+ mMagnificationController.unregisterAll();
+ return;
+ }
+
+ // register all display if global magnification is enabled.
+ final Display[] displays = mDisplayManager.getDisplays();
+ 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;
+ }
+ 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;
+ }
+ final int displayId = display.getDisplayId();
+ if (userHasListeningMagnificationServicesLocked(userState, displayId)) {
+ getMagnificationController().register(displayId);
+ } else if (mMagnificationController != null) {
+ mMagnificationController.unregister(displayId);
+ }
}
}
@@ -2222,12 +2280,13 @@
* Returns whether the specified user has any services that are capable of
* controlling magnification and are actively listening for magnification updates.
*/
- private boolean userHasListeningMagnificationServicesLocked(UserState userState) {
+ private boolean userHasListeningMagnificationServicesLocked(UserState userState,
+ int displayId) {
final List<AccessibilityServiceConnection> services = userState.mBoundServices;
for (int i = 0, count = services.size(); i < count; i++) {
final AccessibilityServiceConnection service = services.get(i);
if (mSecurityPolicy.canControlMagnification(service)
- && service.isMagnificationCallbackEnabled()) {
+ && service.isMagnificationCallbackEnabled(displayId)) {
return true;
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 86132a8..a19a847 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -109,7 +109,7 @@
UserState userState = mUserStateWeakReference.get();
if (userState == null) return;
userState.removeServiceLocked(this);
- mSystemSupport.getMagnificationController().resetIfNeeded(mId);
+ mSystemSupport.getMagnificationController().resetAllIfNeeded(mId);
resetLocked();
}
@@ -256,7 +256,7 @@
userState.serviceDisconnectedLocked(this);
}
resetLocked();
- mSystemSupport.getMagnificationController().resetIfNeeded(mId);
+ mSystemSupport.getMagnificationController().resetAllIfNeeded(mId);
mSystemSupport.onClientChangeLocked(false);
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
index 6a97fbb..e784056 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
@@ -19,7 +19,6 @@
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -32,6 +31,7 @@
import android.text.TextUtils;
import android.util.MathUtils;
import android.util.Slog;
+import android.util.SparseArray;
import android.view.Display;
import android.view.MagnificationSpec;
import android.view.View;
@@ -39,6 +39,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.wm.WindowManagerInternal;
@@ -68,9 +69,7 @@
private final Object mLock;
- private final AccessibilityManagerService mAms;
-
- private final SettingsBridge mSettingsBridge;
+ private final ControllerContext mControllerCtx;
private final ScreenStateObserver mScreenStateObserver;
@@ -78,11 +77,9 @@
private final long mMainThreadId;
- private Handler mHandler;
-
- private final WindowManagerInternal mWindowManager;
-
- private final DisplayMagnification mDisplay;
+ /** List of display Magnification, mapping from displayId -> DisplayMagnification. */
+ @GuardedBy("mLock")
+ private final SparseArray<DisplayMagnification> mDisplays = new SparseArray<>(0);
/**
* This class implements {@link WindowManagerInternal.MagnificationCallbacks} and holds
@@ -107,46 +104,82 @@
// Flag indicating that we are registered with window manager.
private boolean mRegistered;
private boolean mUnregisterPending;
+ private boolean mDeleteAfterUnregister;
private final int mDisplayId;
private static final int INVALID_ID = -1;
private int mIdOfLastServiceToMagnify = INVALID_ID;
-
- DisplayMagnification(int displayId, SpecAnimationBridge specAnimation) {
+ DisplayMagnification(int displayId) {
mDisplayId = displayId;
- mSpecAnimationBridge = specAnimation;
+ mSpecAnimationBridge = new SpecAnimationBridge(mControllerCtx, mLock, mDisplayId);
}
- void register() {
- synchronized (mLock) {
- if (!mRegistered) {
- mWindowManager.setMagnificationCallbacks(this);
- mSpecAnimationBridge.setEnabled(true);
- // Obtain initial state.
- mWindowManager.getMagnificationRegion(mMagnificationRegion);
- mMagnificationRegion.getBounds(mMagnificationBounds);
- mRegistered = true;
- }
+ /**
+ * Registers magnification callback and get current magnification region from
+ * window manager.
+ *
+ * @return true if callback registers successful.
+ */
+ @GuardedBy("mLock")
+ boolean register() {
+ mRegistered = mControllerCtx.getWindowManager().setMagnificationCallbacks(
+ mDisplayId, this);
+ if (!mRegistered) {
+ Slog.w(LOG_TAG, "set magnification callbacks fail, displayId:" + mDisplayId);
+ return false;
}
+ mSpecAnimationBridge.setEnabled(true);
+ // Obtain initial state.
+ mControllerCtx.getWindowManager().getMagnificationRegion(
+ mDisplayId, mMagnificationRegion);
+ mMagnificationRegion.getBounds(mMagnificationBounds);
+ return true;
}
- void unregister() {
- synchronized (mLock) {
- if (!isMagnifying()) {
- unregisterInternalLocked();
- } else {
- mUnregisterPending = true;
- reset(true);
- }
+ /**
+ * Unregisters magnification callback from window manager. Callbacks to
+ * {@link MagnificationController#unregisterCallbackLocked(int, boolean)} after
+ * unregistered.
+ *
+ * @param delete true if this instance should be removed from the SparseArray in
+ * MagnificationController after unregistered, for example, display removed.
+ */
+ @GuardedBy("mLock")
+ void unregister(boolean delete) {
+ if (mRegistered) {
+ mSpecAnimationBridge.setEnabled(false);
+ mControllerCtx.getWindowManager().setMagnificationCallbacks(
+ mDisplayId, null);
+ mMagnificationRegion.setEmpty();
+ mRegistered = false;
+ unregisterCallbackLocked(mDisplayId, delete);
}
+ mUnregisterPending = false;
}
- boolean isRegisteredLocked() {
+ /**
+ * Reset magnification status with animation enabled. {@link #unregister(boolean)} will be
+ * called after animation finished.
+ *
+ * @param delete true if this instance should be removed from the SparseArray in
+ * MagnificationController after unregistered, for example, display removed.
+ */
+ @GuardedBy("mLock")
+ void unregisterPending(boolean delete) {
+ mDeleteAfterUnregister = delete;
+ mUnregisterPending = true;
+ reset(true);
+ }
+
+ boolean isRegistered() {
return mRegistered;
}
+ boolean isMagnifying() {
+ return mCurrentMagnificationSpec.scale > 1.0f;
+ }
float getScale() {
return mCurrentMagnificationSpec.scale;
@@ -156,18 +189,20 @@
return mCurrentMagnificationSpec.offsetX;
}
- float getCenterX() {
- synchronized (mLock) {
- return (mMagnificationBounds.width() / 2.0f
- + mMagnificationBounds.left - getOffsetX()) / getScale();
- }
+ float getOffsetY() {
+ return mCurrentMagnificationSpec.offsetY;
}
+ @GuardedBy("mLock")
+ float getCenterX() {
+ return (mMagnificationBounds.width() / 2.0f
+ + mMagnificationBounds.left - getOffsetX()) / getScale();
+ }
+
+ @GuardedBy("mLock")
float getCenterY() {
- synchronized (mLock) {
- return (mMagnificationBounds.height() / 2.0f
- + mMagnificationBounds.top - getOffsetY()) / getScale();
- }
+ return (mMagnificationBounds.height() / 2.0f
+ + mMagnificationBounds.top - getOffsetY()) / getScale();
}
/**
@@ -203,64 +238,35 @@
return mSpecAnimationBridge.mSentMagnificationSpec.offsetY;
}
- boolean resetIfNeeded(boolean animate) {
- synchronized (mLock) {
- if (isMagnifying()) {
- reset(animate);
- return true;
- }
- return false;
- }
- }
-
- float getOffsetY() {
- return mCurrentMagnificationSpec.offsetY;
- }
-
- boolean isMagnifying() {
- return mCurrentMagnificationSpec.scale > 1.0f;
- }
-
- void unregisterInternalLocked() {
- if (mRegistered) {
- mSpecAnimationBridge.setEnabled(false);
- mWindowManager.setMagnificationCallbacks(null);
- mMagnificationRegion.setEmpty();
-
- mRegistered = false;
- }
- mUnregisterPending = false;
- }
-
-
@Override
public void onMagnificationRegionChanged(Region magnificationRegion) {
final Message m = PooledLambda.obtainMessage(
- DisplayMagnification.this::updateMagnificationRegion,
+ DisplayMagnification::updateMagnificationRegion, this,
Region.obtain(magnificationRegion));
- mHandler.sendMessage(m);
+ mControllerCtx.getHandler().sendMessage(m);
}
@Override
public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) {
final Message m = PooledLambda.obtainMessage(
- DisplayMagnification.this::requestRectangleOnScreen, left, top, right, bottom);
- mHandler.sendMessage(m);
+ DisplayMagnification::requestRectangleOnScreen, this,
+ left, top, right, bottom);
+ mControllerCtx.getHandler().sendMessage(m);
}
@Override
public void onRotationChanged(int rotation) {
// Treat as context change and reset
- final Message m = PooledLambda.obtainMessage(DisplayMagnification.this::resetIfNeeded,
- true);
- mHandler.sendMessage(m);
+ final Message m = PooledLambda.obtainMessage(MagnificationController::resetIfNeeded,
+ MagnificationController.this, mDisplayId, true);
+ mControllerCtx.getHandler().sendMessage(m);
}
@Override
public void onUserContextChanged() {
- final Message m = PooledLambda.obtainMessage(DisplayMagnification.this::resetIfNeeded,
- true);
- mHandler.sendMessage(m);
+ final Message m = PooledLambda.obtainMessage(MagnificationController::resetIfNeeded,
+ MagnificationController.this, mDisplayId, true);
+ mControllerCtx.getHandler().sendMessage(m);
}
/**
@@ -298,8 +304,9 @@
mSpecAnimationBridge.updateSentSpecMainThread(spec, animate);
} else {
final Message m = PooledLambda.obtainMessage(
- this.mSpecAnimationBridge::updateSentSpecMainThread, spec, animate);
- mHandler.sendMessage(m);
+ SpecAnimationBridge::updateSentSpecMainThread,
+ mSpecAnimationBridge, spec, animate);
+ mControllerCtx.getHandler().sendMessage(m);
}
}
@@ -313,30 +320,26 @@
}
void onMagnificationChangedLocked() {
- mAms.notifyMagnificationChanged(mMagnificationRegion,
+ mControllerCtx.getAms().notifyMagnificationChanged(mDisplayId, mMagnificationRegion,
getScale(), getCenterX(), getCenterY());
if (mUnregisterPending && !isMagnifying()) {
- unregisterInternalLocked();
+ unregister(mDeleteAfterUnregister);
}
}
+ @GuardedBy("mLock")
boolean magnificationRegionContains(float x, float y) {
- synchronized (mLock) {
- return mMagnificationRegion.contains((int) x, (int) y);
-
- }
+ return mMagnificationRegion.contains((int) x, (int) y);
}
+ @GuardedBy("mLock")
void getMagnificationBounds(@NonNull Rect outBounds) {
- synchronized (mLock) {
- outBounds.set(mMagnificationBounds);
- }
+ outBounds.set(mMagnificationBounds);
}
+ @GuardedBy("mLock")
void getMagnificationRegion(@NonNull Region outRegion) {
- synchronized (mLock) {
- outRegion.set(mMagnificationRegion);
- }
+ outRegion.set(mMagnificationRegion);
}
void requestRectangleOnScreen(int left, int top, int right, int bottom) {
@@ -392,94 +395,76 @@
outFrame.scale(1.0f / scale);
}
- /**
- * Resets magnification if last magnifying service is disabled.
- *
- * @param connectionId the connection ID be disabled.
- * @return {@code true} on success, {@code false} on failure
- */
- boolean resetIfNeeded(int connectionId) {
- if (mIdOfLastServiceToMagnify == connectionId) {
- return resetIfNeeded(true /*animate*/);
- }
- return false;
- }
-
+ @GuardedBy("mLock")
void setForceShowMagnifiableBounds(boolean show) {
if (mRegistered) {
- mWindowManager.setForceShowMagnifiableBounds(show);
+ mControllerCtx.getWindowManager().setForceShowMagnifiableBounds(
+ mDisplayId, show);
}
}
+ @GuardedBy("mLock")
boolean reset(boolean animate) {
- synchronized (mLock) {
- if (!mRegistered) {
- return false;
- }
- final MagnificationSpec spec = mCurrentMagnificationSpec;
- final boolean changed = !spec.isNop();
- if (changed) {
- spec.clear();
- onMagnificationChangedLocked();
- }
- mIdOfLastServiceToMagnify = INVALID_ID;
- sendSpecToAnimation(spec, animate);
- return changed;
+ if (!mRegistered) {
+ return false;
}
+ final MagnificationSpec spec = mCurrentMagnificationSpec;
+ final boolean changed = !spec.isNop();
+ if (changed) {
+ spec.clear();
+ onMagnificationChangedLocked();
+ }
+ mIdOfLastServiceToMagnify = INVALID_ID;
+ sendSpecToAnimation(spec, animate);
+ return changed;
}
-
+ @GuardedBy("mLock")
boolean setScale(float scale, float pivotX, float pivotY,
boolean animate, int id) {
-
- synchronized (mLock) {
- if (!mRegistered) {
- return false;
- }
- // Constrain scale immediately for use in the pivot calculations.
- scale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
-
- final Rect viewport = mTempRect;
- mMagnificationRegion.getBounds(viewport);
- final MagnificationSpec spec = mCurrentMagnificationSpec;
- final float oldScale = spec.scale;
- final float oldCenterX
- = (viewport.width() / 2.0f - spec.offsetX + viewport.left) / oldScale;
- final float oldCenterY
- = (viewport.height() / 2.0f - spec.offsetY + viewport.top) / oldScale;
- final float normPivotX = (pivotX - spec.offsetX) / oldScale;
- final float normPivotY = (pivotY - spec.offsetY) / oldScale;
- final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
- final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
- final float centerX = normPivotX + offsetX;
- final float centerY = normPivotY + offsetY;
- mIdOfLastServiceToMagnify = id;
-
- return setScaleAndCenter(scale, centerX, centerY, animate, id);
+ if (!mRegistered) {
+ return false;
}
+ // Constrain scale immediately for use in the pivot calculations.
+ scale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
+
+ final Rect viewport = mTempRect;
+ mMagnificationRegion.getBounds(viewport);
+ final MagnificationSpec spec = mCurrentMagnificationSpec;
+ final float oldScale = spec.scale;
+ final float oldCenterX =
+ (viewport.width() / 2.0f - spec.offsetX + viewport.left) / oldScale;
+ final float oldCenterY =
+ (viewport.height() / 2.0f - spec.offsetY + viewport.top) / oldScale;
+ final float normPivotX = (pivotX - spec.offsetX) / oldScale;
+ final float normPivotY = (pivotY - spec.offsetY) / oldScale;
+ final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
+ final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
+ final float centerX = normPivotX + offsetX;
+ final float centerY = normPivotY + offsetY;
+ mIdOfLastServiceToMagnify = id;
+ return setScaleAndCenter(scale, centerX, centerY, animate, id);
}
+ @GuardedBy("mLock")
boolean setScaleAndCenter(float scale, float centerX, float centerY,
boolean animate, int id) {
-
- synchronized (mLock) {
- if (!mRegistered) {
- return false;
- }
- if (DEBUG) {
- Slog.i(LOG_TAG,
- "setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX
- + ", centerY = " + centerY + ", animate = " + animate
- + ", id = " + id
- + ")");
- }
- final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
- sendSpecToAnimation(mCurrentMagnificationSpec, animate);
- if (isMagnifying() && (id != INVALID_ID)) {
- mIdOfLastServiceToMagnify = id;
- }
- return changed;
+ if (!mRegistered) {
+ return false;
}
+ if (DEBUG) {
+ Slog.i(LOG_TAG,
+ "setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX
+ + ", centerY = " + centerY + ", animate = " + animate
+ + ", id = " + id
+ + ")");
+ }
+ final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
+ sendSpecToAnimation(mCurrentMagnificationSpec, animate);
+ if (isMagnifying() && (id != INVALID_ID)) {
+ mIdOfLastServiceToMagnify = id;
+ }
+ return changed;
}
/**
@@ -527,22 +512,21 @@
return changed;
}
+ @GuardedBy("mLock")
void offsetMagnifiedRegion(float offsetX, float offsetY, int id) {
- synchronized (mLock) {
- if (!mRegistered) {
- return;
- }
-
- final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
- final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
- if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) {
- onMagnificationChangedLocked();
- }
- if (id != INVALID_ID) {
- mIdOfLastServiceToMagnify = id;
- }
- sendSpecToAnimation(mCurrentMagnificationSpec, false);
+ if (!mRegistered) {
+ return;
}
+
+ final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
+ final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
+ if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) {
+ onMagnificationChangedLocked();
+ }
+ if (id != INVALID_ID) {
+ mIdOfLastServiceToMagnify = id;
+ }
+ sendSpecToAnimation(mCurrentMagnificationSpec, false);
}
boolean updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY) {
@@ -593,44 +577,38 @@
@Override
public String toString() {
- return "DisplayMagnification{" +
- "mCurrentMagnificationSpec=" + mCurrentMagnificationSpec +
- ", mMagnificationRegion=" + mMagnificationRegion +
- ", mMagnificationBounds=" + mMagnificationBounds +
- ", mDisplayId=" + mDisplayId +
- ", mUserId=" + mUserId +
- ", mIdOfLastServiceToMagnify=" + mIdOfLastServiceToMagnify +
- ", mRegistered=" + mRegistered +
- ", mUnregisterPending=" + mUnregisterPending +
- '}';
+ return "DisplayMagnification["
+ + "mCurrentMagnificationSpec=" + mCurrentMagnificationSpec
+ + ", mMagnificationRegion=" + mMagnificationRegion
+ + ", mMagnificationBounds=" + mMagnificationBounds
+ + ", mDisplayId=" + mDisplayId
+ + ", mIdOfLastServiceToMagnify=" + mIdOfLastServiceToMagnify
+ + ", mRegistered=" + mRegistered
+ + ", mUnregisterPending=" + mUnregisterPending
+ + ']';
}
-
}
- public MagnificationController(Context context, AccessibilityManagerService ams, Object lock) {
- this(context, ams, lock, null, LocalServices.getService(WindowManagerInternal.class),
- new ValueAnimator(), new SettingsBridge(context.getContentResolver()));
- mHandler = new Handler(context.getMainLooper());
+ /**
+ * MagnificationController Constructor
+ */
+ public MagnificationController(@NonNull Context context,
+ @NonNull AccessibilityManagerService ams, @NonNull Object lock) {
+ this(new ControllerContext(context, ams,
+ LocalServices.getService(WindowManagerInternal.class),
+ new Handler(context.getMainLooper()),
+ context.getResources().getInteger(R.integer.config_longAnimTime)), lock);
}
- public MagnificationController(
- Context context,
- AccessibilityManagerService ams,
- Object lock,
- Handler handler,
- WindowManagerInternal windowManagerInternal,
- ValueAnimator valueAnimator,
- SettingsBridge settingsBridge) {
- mHandler = handler;
- mWindowManager = windowManagerInternal;
- mMainThreadId = context.getMainLooper().getThread().getId();
- mAms = ams;
- mScreenStateObserver = new ScreenStateObserver(context, this);
+ /**
+ * Constructor for tests
+ */
+ @VisibleForTesting
+ public MagnificationController(@NonNull ControllerContext ctx, @NonNull Object lock) {
+ mControllerCtx = ctx;
mLock = lock;
- mSettingsBridge = settingsBridge;
- //TODO (multidisplay): Magnification is supported only for the default display.
- mDisplay = new DisplayMagnification(Display.DEFAULT_DISPLAY,
- new SpecAnimationBridge(context, mLock, mWindowManager, valueAnimator));
+ mMainThreadId = mControllerCtx.getContext().getMainLooper().getThread().getId();
+ mScreenStateObserver = new ScreenStateObserver(mControllerCtx.getContext(), this);
}
/**
@@ -639,54 +617,114 @@
*
* This tracking imposes a cost on the system, so we avoid tracking this data unless it's
* required.
+ *
+ * @param displayId The logical display id.
*/
- public void register() {
+ public void register(int displayId) {
synchronized (mLock) {
- mScreenStateObserver.register();
+ DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ display = new DisplayMagnification(displayId);
+ }
+ if (display.isRegistered()) {
+ return;
+ }
+ if (display.register()) {
+ mDisplays.put(displayId, display);
+ mScreenStateObserver.registerIfNecessary();
+ }
}
- mDisplay.register();
}
/**
* Stop requiring tracking the magnification region. We may remain registered while we
* reset magnification.
- */
- public void unregister() {
- synchronized (mLock) {
- mScreenStateObserver.unregister();
- }
- mDisplay.unregister();
- }
-
- /**
- * Check if we are registered. Note that we may be planning to unregister at any moment.
*
- * @return {@code true} if the controller is registered. {@code false} otherwise.
+ * @param displayId The logical display id.
*/
- public boolean isRegisteredLocked() {
- return mDisplay.isRegisteredLocked();
+ public void unregister(int displayId) {
+ synchronized (mLock) {
+ unregisterLocked(displayId, false);
+ }
}
/**
+ * Stop tracking all displays' magnification region.
+ */
+ public void unregisterAll() {
+ synchronized (mLock) {
+ // display will be removed from array after unregister, we need to clone it to
+ // prevent error.
+ final SparseArray<DisplayMagnification> displays = mDisplays.clone();
+ for (int i = 0; i < displays.size(); i++) {
+ unregisterLocked(displays.keyAt(i), false);
+ }
+ }
+ }
+
+ /**
+ * Remove the display magnification with given id.
+ *
+ * @param displayId The logical display id.
+ */
+ public void onDisplayRemoved(int displayId) {
+ synchronized (mLock) {
+ unregisterLocked(displayId, true);
+ }
+ }
+
+ /**
+ * Check if we are registered on specified display. Note that we may be planning to unregister
+ * at any moment.
+ *
+ * @return {@code true} if the controller is registered on specified display.
+ * {@code false} otherwise.
+ *
+ * @param displayId The logical display id.
+ */
+ public boolean isRegistered(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return false;
+ }
+ return display.isRegistered();
+ }
+ }
+
+ /**
+ * @param displayId The logical display id.
* @return {@code true} if magnification is active, e.g. the scale
* is > 1, {@code false} otherwise
*/
- public boolean isMagnifying() {
- return mDisplay.isMagnifying();
+ public boolean isMagnifying(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return false;
+ }
+ return display.isMagnifying();
+ }
}
/**
* Returns whether the magnification region contains the specified
* screen-relative coordinates.
*
+ * @param displayId The logical display id.
* @param x the screen-relative X coordinate to check
* @param y the screen-relative Y coordinate to check
* @return {@code true} if the coordinate is contained within the
* magnified region, or {@code false} otherwise
*/
- public boolean magnificationRegionContains(float x, float y) {
- return mDisplay.magnificationRegionContains(x, y);
-
+ public boolean magnificationRegionContains(int displayId, float x, float y) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return false;
+ }
+ return display.magnificationRegionContains(x, y);
+ }
}
/**
@@ -694,11 +732,18 @@
* magnification region. If magnification is not enabled, the returned
* bounds will be empty.
*
+ * @param displayId The logical display id.
* @param outBounds rect to populate with the bounds of the magnified
* region
*/
- public void getMagnificationBounds(@NonNull Rect outBounds) {
- mDisplay.getMagnificationBounds(outBounds);
+ public void getMagnificationBounds(int displayId, @NonNull Rect outBounds) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return;
+ }
+ display.getMagnificationBounds(outBounds);
+ }
}
/**
@@ -706,76 +751,122 @@
* region. If magnification is not enabled, then the returned region
* will be empty.
*
+ * @param displayId The logical display id.
* @param outRegion the region to populate
*/
- public void getMagnificationRegion(@NonNull Region outRegion) {
- mDisplay.getMagnificationRegion(outRegion);
+ public void getMagnificationRegion(int displayId, @NonNull Region outRegion) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return;
+ }
+ display.getMagnificationRegion(outRegion);
+ }
}
/**
* Returns the magnification scale. If an animation is in progress,
* this reflects the end state of the animation.
*
+ * @param displayId The logical display id.
* @return the scale
*/
- public float getScale() {
- return mDisplay.getScale();
+ public float getScale(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return 1.0f;
+ }
+ return display.getScale();
+ }
}
/**
* Returns the X offset of the magnification viewport. If an animation
* is in progress, this reflects the end state of the animation.
*
+ * @param displayId The logical display id.
* @return the X offset
*/
- public float getOffsetX() {
- return mDisplay.getOffsetX();
+ public float getOffsetX(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return 0.0f;
+ }
+ return display.getOffsetX();
+ }
}
-
/**
* Returns the screen-relative X coordinate of the center of the
* magnification viewport.
*
+ * @param displayId The logical display id.
* @return the X coordinate
*/
- public float getCenterX() {
- return mDisplay.getCenterX();
+ public float getCenterX(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return 0.0f;
+ }
+ return display.getCenterX();
+ }
}
/**
* Returns the Y offset of the magnification viewport. If an animation
* is in progress, this reflects the end state of the animation.
*
+ * @param displayId The logical display id.
* @return the Y offset
*/
- public float getOffsetY() {
- return mDisplay.getOffsetY();
+ public float getOffsetY(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return 0.0f;
+ }
+ return display.getOffsetY();
+ }
}
/**
* Returns the screen-relative Y coordinate of the center of the
* magnification viewport.
*
+ * @param displayId The logical display id.
* @return the Y coordinate
*/
- public float getCenterY() {
- return mDisplay.getCenterY();
+ public float getCenterY(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return 0.0f;
+ }
+ return display.getCenterY();
+ }
}
/**
* Resets the magnification scale and center, optionally animating the
* transition.
*
+ * @param displayId The logical display id.
* @param animate {@code true} to animate the transition, {@code false}
* to transition immediately
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
- public boolean reset(boolean animate) {
-
- return mDisplay.reset(animate);
-
+ public boolean reset(int displayId, boolean animate) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return false;
+ }
+ return display.reset(animate);
+ }
}
/**
@@ -783,6 +874,7 @@
* optionally animating the transition. If animation is disabled, the
* transition is immediate.
*
+ * @param displayId The logical display id.
* @param scale the target scale, must be >= 1
* @param pivotX the screen-relative X coordinate around which to scale
* @param pivotY the screen-relative Y coordinate around which to scale
@@ -792,15 +884,22 @@
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
- public boolean setScale(float scale, float pivotX, float pivotY, boolean animate, int id) {
- return mDisplay.
- setScale(scale, pivotX, pivotY, animate, id);
+ public boolean setScale(int displayId, float scale, float pivotX, float pivotY,
+ boolean animate, int id) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return false;
+ }
+ return display.setScale(scale, pivotX, pivotY, animate, id);
+ }
}
/**
* Sets the center of the magnified region, optionally animating the
* transition. If animation is disabled, the transition is immediate.
*
+ * @param displayId The logical display id.
* @param centerX the screen-relative X coordinate around which to
* center
* @param centerY the screen-relative Y coordinate around which to
@@ -811,9 +910,14 @@
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
- public boolean setCenter(float centerX, float centerY, boolean animate, int id) {
- return mDisplay.
- setScaleAndCenter(Float.NaN, centerX, centerY, animate, id);
+ public boolean setCenter(int displayId, float centerX, float centerY, boolean animate, int id) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return false;
+ }
+ return display.setScaleAndCenter(Float.NaN, centerX, centerY, animate, id);
+ }
}
/**
@@ -821,6 +925,7 @@
* animating the transition. If animation is disabled, the transition
* is immediate.
*
+ * @param displayId The logical display id.
* @param scale the target scale, or {@link Float#NaN} to leave unchanged
* @param centerX the screen-relative X coordinate around which to
* center and scale, or {@link Float#NaN} to leave unchanged
@@ -832,53 +937,66 @@
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
- public boolean setScaleAndCenter(
- float scale, float centerX, float centerY, boolean animate, int id) {
- return mDisplay.
- setScaleAndCenter(scale, centerX, centerY, animate, id);
+ public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
+ boolean animate, int id) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return false;
+ }
+ return display.setScaleAndCenter(scale, centerX, centerY, animate, id);
+ }
}
/**
* Offsets the magnified region. Note that the offsetX and offsetY values actually move in the
* opposite direction as the offsets passed in here.
*
+ * @param displayId The logical display id.
* @param offsetX the amount in pixels to offset the region in the X direction, in current
* screen pixels.
* @param offsetY the amount in pixels to offset the region in the Y direction, in current
* screen pixels.
* @param id the ID of the service requesting the change
*/
- public void offsetMagnifiedRegion(float offsetX, float offsetY, int id) {
- mDisplay.offsetMagnifiedRegion(offsetX, offsetY,
- id);
+ public void offsetMagnifiedRegion(int displayId, float offsetX, float offsetY, int id) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return;
+ }
+ display.offsetMagnifiedRegion(offsetX, offsetY, id);
+ }
}
/**
* Get the ID of the last service that changed the magnification spec.
*
+ * @param displayId The logical display id.
* @return The id
*/
- public int getIdOfLastServiceToMagnify() {
- return mDisplay.getIdOfLastServiceToMagnify();
+ public int getIdOfLastServiceToMagnify(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return -1;
+ }
+ return display.getIdOfLastServiceToMagnify();
+ }
}
/**
- * Persists the current magnification scale to the current user's settings.
+ * Persists the default display magnification scale to the current user's settings.
*/
public void persistScale() {
- persistScale(Display.DEFAULT_DISPLAY);
- }
- /**
- * Persists the current magnification scale to the current user's settings.
- */
- public void persistScale(int displayId) {
- final float scale = mDisplay.getScale();
+ // TODO: b/123047354, Need support multi-display?
+ final float scale = getScale(Display.DEFAULT_DISPLAY);
final int userId = mUserId;
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
- mSettingsBridge.putMagnificationScale(scale, displayId, userId);
+ mControllerCtx.putMagnificationScale(scale, userId);
return null;
}
}.execute();
@@ -892,7 +1010,7 @@
* scale if none is available
*/
public float getPersistedScale() {
- return mSettingsBridge.getMagnificationScale(Display.DEFAULT_DISPLAY, mUserId);
+ return mControllerCtx.getMagnificationScale(mUserId);
}
/**
@@ -901,50 +1019,136 @@
* @param userId the currently active user ID
*/
public void setUserId(int userId) {
- if (mUserId != userId) {
- mUserId = userId;
+ if (mUserId == userId) {
+ return;
+ }
+ mUserId = userId;
+ resetAllIfNeeded(false);
+ }
- synchronized (mLock) {
- if (isMagnifying()) {
- reset(false);
- }
+ /**
+ * Resets all displays' magnification if last magnifying service is disabled.
+ *
+ * @param connectionId
+ */
+ public void resetAllIfNeeded(int connectionId) {
+ synchronized (mLock) {
+ for (int i = 0; i < mDisplays.size(); i++) {
+ resetIfNeeded(mDisplays.keyAt(i), connectionId);
}
}
}
- /**
+ /**
* Resets magnification if magnification and auto-update are both enabled.
*
+ * @param displayId The logical display id.
* @param animate whether the animate the transition
- * @return whether was {@link #isMagnifying magnifying}
+ * @return whether was {@link #isMagnifying(int) magnifying}
*/
- public boolean resetIfNeeded(boolean animate) {
- return mDisplay.resetIfNeeded(animate);
+ boolean resetIfNeeded(int displayId, boolean animate) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null || !display.isMagnifying()) {
+ return false;
+ }
+ display.reset(animate);
+ return true;
+ }
}
/**
* Resets magnification if last magnifying service is disabled.
*
+ * @param displayId The logical display id.
* @param connectionId the connection ID be disabled.
* @return {@code true} on success, {@code false} on failure
*/
- public boolean resetIfNeeded(int connectionId) {
- return mDisplay.resetIfNeeded(connectionId);
+ boolean resetIfNeeded(int displayId, int connectionId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null || !display.isMagnifying()
+ || connectionId != display.getIdOfLastServiceToMagnify()) {
+ return false;
+ }
+ display.reset(true);
+ return true;
+ }
}
- void setForceShowMagnifiableBounds(boolean show) {
- mDisplay.setForceShowMagnifiableBounds(show);
+ void setForceShowMagnifiableBounds(int displayId, boolean show) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return;
+ }
+ display.setForceShowMagnifiableBounds(show);
+ }
}
private void onScreenTurnedOff() {
final Message m = PooledLambda.obtainMessage(
- mDisplay::resetIfNeeded, false);
- mHandler.sendMessage(m);
+ MagnificationController::resetAllIfNeeded, this, false);
+ mControllerCtx.getHandler().sendMessage(m);
+ }
+
+ private void resetAllIfNeeded(boolean animate) {
+ synchronized (mLock) {
+ for (int i = 0; i < mDisplays.size(); i++) {
+ resetIfNeeded(mDisplays.keyAt(i), animate);
+ }
+ }
+ }
+
+ private void unregisterLocked(int displayId, boolean delete) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return;
+ }
+ if (!display.isRegistered()) {
+ if (delete) {
+ mDisplays.remove(displayId);
+ }
+ return;
+ }
+ if (!display.isMagnifying()) {
+ display.unregister(delete);
+ } else {
+ display.unregisterPending(delete);
+ }
+ }
+
+ /**
+ * Callbacks from DisplayMagnification after display magnification unregistered. It will remove
+ * DisplayMagnification instance if delete is true, and unregister screen state if
+ * there is no registered display magnification.
+ */
+ private void unregisterCallbackLocked(int displayId, boolean delete) {
+ if (delete) {
+ mDisplays.remove(displayId);
+ }
+ // unregister screen state if necessary
+ boolean hasRegister = false;
+ for (int i = 0; i < mDisplays.size(); i++) {
+ final DisplayMagnification display = mDisplays.valueAt(i);
+ hasRegister = display.isRegistered();
+ if (hasRegister) {
+ break;
+ }
+ }
+ if (!hasRegister) {
+ mScreenStateObserver.unregister();
+ }
}
@Override
public String toString() {
- return mDisplay.toString();
+ StringBuilder builder = new StringBuilder();
+ builder.append("MagnificationController[");
+ builder.append("mUserId=").append(mUserId);
+ builder.append(", mDisplays=").append(mDisplays);
+ builder.append("]");
+ return builder.toString();
}
/**
@@ -952,7 +1156,7 @@
* updates to the window manager.
*/
private static class SpecAnimationBridge implements ValueAnimator.AnimatorUpdateListener {
- private final WindowManagerInternal mWindowManager;
+ private final ControllerContext mControllerCtx;
/**
* The magnification spec that was sent to the window manager. This should
@@ -973,16 +1177,17 @@
private final Object mLock;
+ private final int mDisplayId;
+
@GuardedBy("mLock")
private boolean mEnabled = false;
- private SpecAnimationBridge(Context context, Object lock, WindowManagerInternal wm,
- ValueAnimator animator) {
+ private SpecAnimationBridge(ControllerContext ctx, Object lock, int displayId) {
+ mControllerCtx = ctx;
mLock = lock;
- mWindowManager = wm;
- final long animationDuration = context.getResources().getInteger(
- R.integer.config_longAnimTime);
- mValueAnimator = animator;
+ mDisplayId = displayId;
+ final long animationDuration = mControllerCtx.getAnimationDuration();
+ mValueAnimator = mControllerCtx.newValueAnimator();
mValueAnimator.setDuration(animationDuration);
mValueAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
mValueAnimator.setFloatValues(0.0f, 1.0f);
@@ -999,7 +1204,8 @@
mEnabled = enabled;
if (!mEnabled) {
mSentMagnificationSpec.clear();
- mWindowManager.setMagnificationSpec(mSentMagnificationSpec);
+ mControllerCtx.getWindowManager().setMagnificationSpec(
+ mDisplayId, mSentMagnificationSpec);
}
}
}
@@ -1031,7 +1237,8 @@
}
mSentMagnificationSpec.setTo(spec);
- mWindowManager.setMagnificationSpec(spec);
+ mControllerCtx.getWindowManager().setMagnificationSpec(
+ mDisplayId, mSentMagnificationSpec);
}
}
@@ -1054,9 +1261,7 @@
mTmpMagnificationSpec.offsetY = mStartMagnificationSpec.offsetY +
(mEndMagnificationSpec.offsetY - mStartMagnificationSpec.offsetY)
* fract;
- synchronized (mLock) {
- setMagnificationSpecLocked(mTmpMagnificationSpec);
- }
+ setMagnificationSpecLocked(mTmpMagnificationSpec);
}
}
}
@@ -1072,7 +1277,7 @@
mController = controller;
}
- public void register() {
+ public void registerIfNecessary() {
if (!mRegistered) {
mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF));
mRegistered = true;
@@ -1092,26 +1297,97 @@
}
}
- // Extra class to get settings so tests can mock it
- public static class SettingsBridge {
- private final ContentResolver mContentResolver;
+ /**
+ * This class holds resources used between the classes in MagnificationController, and
+ * functions for tests to mock it.
+ */
+ @VisibleForTesting
+ public static class ControllerContext {
+ private final Context mContext;
+ private final AccessibilityManagerService mAms;
+ private final WindowManagerInternal mWindowManager;
+ private final Handler mHandler;
+ private final Long mAnimationDuration;
- public SettingsBridge(ContentResolver contentResolver) {
- mContentResolver = contentResolver;
+ /**
+ * Constructor for ControllerContext.
+ */
+ public ControllerContext(@NonNull Context context,
+ @NonNull AccessibilityManagerService ams,
+ @NonNull WindowManagerInternal windowManager,
+ @NonNull Handler handler,
+ long animationDuration) {
+ mContext = context;
+ mAms = ams;
+ mWindowManager = windowManager;
+ mHandler = handler;
+ mAnimationDuration = animationDuration;
}
- public void putMagnificationScale(float value, int displayId, int userId) {
- Settings.Secure.putFloatForUser(mContentResolver,
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE + (
- Display.DEFAULT_DISPLAY == displayId ? "" : displayId),
- value, userId);
+ /**
+ * @return A context.
+ */
+ @NonNull
+ public Context getContext() {
+ return mContext;
}
- public float getMagnificationScale(int displayId, int userId) {
- return Settings.Secure.getFloatForUser(mContentResolver,
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE
- + (Display.DEFAULT_DISPLAY == displayId ? "" : displayId),
+ /**
+ * @return AccessibilityManagerService
+ */
+ @NonNull
+ public AccessibilityManagerService getAms() {
+ return mAms;
+ }
+
+ /**
+ * @return WindowManagerInternal
+ */
+ @NonNull
+ public WindowManagerInternal getWindowManager() {
+ return mWindowManager;
+ }
+
+ /**
+ * @return Handler for main looper
+ */
+ @NonNull
+ public Handler getHandler() {
+ return mHandler;
+ }
+
+ /**
+ * Create a new ValueAnimator.
+ *
+ * @return ValueAnimator
+ */
+ @NonNull
+ public ValueAnimator newValueAnimator() {
+ return new ValueAnimator();
+ }
+
+ /**
+ * Write Settings of magnification scale.
+ */
+ public void putMagnificationScale(float value, int userId) {
+ Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, value, userId);
+ }
+
+ /**
+ * Get Settings of magnification scale.
+ */
+ public float getMagnificationScale(int userId) {
+ return Settings.Secure.getFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
DEFAULT_MAGNIFICATION_SCALE, userId);
}
+
+ /**
+ * @return Configuration of animation duration.
+ */
+ public long getAnimationDuration() {
+ return mAnimationDuration;
+ }
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
index 80049e8..49db488 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -43,6 +43,7 @@
import android.util.MathUtils;
import android.util.Slog;
import android.util.TypedValue;
+import android.view.Display;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
@@ -251,14 +252,16 @@
mScreenStateReceiver.unregister();
}
// Check if need to reset when MagnificationGestureHandler is the last magnifying service.
- mMagnificationController.resetIfNeeded(
+ mMagnificationController.resetAllIfNeeded(
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
clearAndTransitionToStateDetecting();
}
void notifyShortcutTriggered() {
if (mDetectShortcutTrigger) {
- boolean wasMagnifying = mMagnificationController.resetIfNeeded(/* animate */ true);
+ // TODO: multi-display support for magnification gesture handler
+ boolean wasMagnifying = mMagnificationController.resetIfNeeded(Display.DEFAULT_DISPLAY,
+ /* animate */ true);
if (wasMagnifying) {
clearAndTransitionToStateDetecting();
} else {
@@ -419,8 +422,9 @@
Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX
+ " scrollY: " + distanceY);
}
- mMagnificationController.offsetMagnifiedRegion(distanceX, distanceY,
- AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+ // TODO: multi-display support for magnification gesture handler
+ mMagnificationController.offsetMagnifiedRegion(Display.DEFAULT_DISPLAY, distanceX,
+ distanceY, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
return /* event consumed: */ true;
}
@@ -436,7 +440,8 @@
return mScaling;
}
- final float initialScale = mMagnificationController.getScale();
+ // TODO: multi-display support for magnification gesture handler
+ final float initialScale = mMagnificationController.getScale(Display.DEFAULT_DISPLAY);
final float targetScale = initialScale * detector.getScaleFactor();
// Don't allow a gesture to move the user further outside the
@@ -458,7 +463,8 @@
final float pivotX = detector.getFocusX();
final float pivotY = detector.getFocusY();
if (DEBUG_PANNING_SCALING) Slog.i(LOG_TAG, "Scaled content to: " + scale + "x");
- mMagnificationController.setScale(scale, pivotX, pivotY, false,
+ // TODO: multi-display support for magnification gesture handler
+ mMagnificationController.setScale(Display.DEFAULT_DISPLAY, scale, pivotX, pivotY, false,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
return /* handled: */ true;
}
@@ -518,8 +524,10 @@
}
final float eventX = event.getX();
final float eventY = event.getY();
- if (mMagnificationController.magnificationRegionContains(eventX, eventY)) {
- mMagnificationController.setCenter(eventX, eventY,
+ // TODO: multi-display support for magnification gesture handler
+ if (mMagnificationController.magnificationRegionContains(
+ Display.DEFAULT_DISPLAY, eventX, eventY)) {
+ mMagnificationController.setCenter(Display.DEFAULT_DISPLAY, eventX, eventY,
/* animate */ mLastMoveOutsideMagnifiedRegion,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
mLastMoveOutsideMagnifiedRegion = false;
@@ -657,8 +665,9 @@
mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
+ // TODO: multi-display support for magnification gesture handler
if (!mMagnificationController.magnificationRegionContains(
- event.getX(), event.getY())) {
+ Display.DEFAULT_DISPLAY, event.getX(), event.getY())) {
transitionToDelegatingStateAndClear();
@@ -667,11 +676,16 @@
// 3tap and hold
afterLongTapTimeoutTransitionToDraggingState(event);
+ } else if (isTapOutOfDistanceSlop()) {
+
+ transitionToDelegatingStateAndClear();
+
} else if (mDetectTripleTap
// If magnified, delay an ACTION_DOWN for mMultiTapMaxDelay
// to ensure reachability of
// STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN)
- || mMagnificationController.isMagnifying()) {
+ // TODO: multi-display support for magnification gesture handler
+ || mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) {
afterMultiTapTimeoutTransitionToDelegatingState();
@@ -683,7 +697,8 @@
}
break;
case ACTION_POINTER_DOWN: {
- if (mMagnificationController.isMagnifying()) {
+ // TODO: multi-display support for magnification gesture handler
+ if (mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) {
transitionTo(mPanningScalingState);
clear();
} else {
@@ -712,8 +727,9 @@
mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD);
+ // TODO: multi-display support for magnification gesture handler
if (!mMagnificationController.magnificationRegionContains(
- event.getX(), event.getY())) {
+ Display.DEFAULT_DISPLAY, event.getX(), event.getY())) {
transitionToDelegatingStateAndClear();
@@ -864,7 +880,8 @@
clear();
// Toggle zoom
- if (mMagnificationController.isMagnifying()) {
+ // TODO: multi-display support for magnification gesture handler
+ if (mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) {
zoomOff();
} else {
zoomOn(up.getX(), up.getY());
@@ -876,8 +893,9 @@
if (DEBUG_DETECTING) Slog.i(LOG_TAG, "onTripleTapAndHold()");
clear();
+ // TODO: multi-display support for magnification gesture handler
mViewportDraggingState.mZoomedInBeforeDrag =
- mMagnificationController.isMagnifying();
+ mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY);
zoomOn(down.getX(), down.getY());
@@ -904,7 +922,33 @@
if (DEBUG_DETECTING) Slog.i(LOG_TAG, "setShortcutTriggered(" + state + ")");
mShortcutTriggered = state;
- mMagnificationController.setForceShowMagnifiableBounds(state);
+ // TODO: multi-display support for magnification gesture handler
+ mMagnificationController.setForceShowMagnifiableBounds(Display.DEFAULT_DISPLAY, state);
+ }
+
+ /**
+ * Detects if last action down is out of distance slop between with previous
+ * one, when triple tap is enabled.
+ *
+ * @return true if tap is out of distance slop
+ */
+ boolean isTapOutOfDistanceSlop() {
+ if (!mDetectTripleTap) return false;
+ if (mPreLastDown == null || mLastDown == null) {
+ return false;
+ }
+ final boolean outOfDistanceSlop =
+ GestureUtils.distance(mPreLastDown, mLastDown) > mMultiTapMaxDistance;
+ if (tapCount() > 0) {
+ return outOfDistanceSlop;
+ }
+ // There's no tap in the queue here. We still need to check if this is the case that
+ // user tap screen quickly and out of distance slop.
+ if (outOfDistanceSlop
+ && !GestureUtils.isTimedOut(mPreLastDown, mLastDown, mMultiTapMaxDelay)) {
+ return true;
+ }
+ return false;
}
}
@@ -914,7 +958,8 @@
final float scale = MathUtils.constrain(
mMagnificationController.getPersistedScale(),
MIN_SCALE, MAX_SCALE);
- mMagnificationController.setScaleAndCenter(
+ // TODO: multi-display support for magnification gesture handler
+ mMagnificationController.setScaleAndCenter(Display.DEFAULT_DISPLAY,
scale, centerX, centerY,
/* animate */ true,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
@@ -922,8 +967,8 @@
private void zoomOff() {
if (DEBUG_DETECTING) Slog.i(LOG_TAG, "zoomOff()");
-
- mMagnificationController.reset(/* animate */ true);
+ // TODO: multi-display support for magnification gesture handler
+ mMagnificationController.reset(Display.DEFAULT_DISPLAY, /* animate */ true);
}
private static MotionEvent recycleAndNullify(@Nullable MotionEvent event) {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index c992da4..2e45fa7 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -18,14 +18,13 @@
import static android.Manifest.permission.MANAGE_AUTO_FILL;
import static android.content.Context.AUTOFILL_MANAGER_SERVICE;
-import static android.util.DebugUtils.flagsToString;
import static android.view.autofill.AutofillManager.MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS;
+import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString;
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sFullScreenMode;
import static com.android.server.autofill.Helper.sVerbose;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -61,6 +60,7 @@
import android.util.SparseArray;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillManager.SmartSuggestionMode;
import android.view.autofill.AutofillManagerInternal;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManager;
@@ -80,8 +80,6 @@
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.Arrays;
import java.util.List;
@@ -102,26 +100,6 @@
private static final Object sLock = AutofillManagerService.class;
- /**
- * IME supports Smart Suggestions.
- */
- // NOTE: must be public because of flagsToString()
- public static final int FLAG_SMART_SUGGESTION_IME = 0x1;
-
- /**
- * System supports Smarts Suggestions (as a popup-window similar to standard Autofill).
- */
- // NOTE: must be public because of flagsToString()
- public static final int FLAG_SMART_SUGGESTION_SYSTEM = 0x2;
-
- /** @hide */
- @IntDef(flag = true, prefix = { "FLAG_SMART_SUGGESTION_" }, value = {
- FLAG_SMART_SUGGESTION_IME,
- FLAG_SMART_SUGGESTION_SYSTEM
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface SmartSuggestionMode {}
-
static final String RECEIVER_BUNDLE_EXTRA_SESSIONS = "sessions";
private static final char COMPAT_PACKAGE_DELIMITER = ':';
@@ -484,7 +462,7 @@
Settings.Global.AUTOFILL_SMART_SUGGESTION_EMULATION_FLAGS, 0);
if (sDebug) {
Slog.d(TAG, "setSmartSuggestionEmulationFromSettings(): "
- + smartSuggestionFlagsToString(flags));
+ + getSmartSuggestionModeToString(flags));
}
synchronized (mLock) {
@@ -698,10 +676,6 @@
}
}
- static String smartSuggestionFlagsToString(int flags) {
- return flagsToString(AutofillManagerService.class, "FLAG_SMART_SUGGESTION_", flags);
- }
-
private final class LocalService extends AutofillManagerInternal {
@Override
public void onBackKeyPressed() {
@@ -1251,7 +1225,7 @@
pw.println(getWhitelistedCompatModePackagesFromSettings());
if (mSupportedSmartSuggestionModes != 0) {
pw.print("Smart Suggestion modes: ");
- pw.println(smartSuggestionFlagsToString(mSupportedSmartSuggestionModes));
+ pw.println(getSmartSuggestionModeToString(mSupportedSmartSuggestionModes));
}
if (showHistory) {
pw.println(); pw.println("Requests history:"); pw.println();
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 954b67e..8886ee2 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -63,6 +63,7 @@
import android.util.TimeUtils;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillManager.SmartSuggestionMode;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManagerClient;
@@ -72,7 +73,6 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.LocalServices;
import com.android.server.autofill.AutofillManagerService.AutofillCompatState;
-import com.android.server.autofill.AutofillManagerService.SmartSuggestionMode;
import com.android.server.autofill.RemoteAugmentedAutofillService.RemoteAugmentedAutofillServiceCallbacks;
import com.android.server.autofill.ui.AutoFillUI;
import com.android.server.infra.AbstractPerUserSystemService;
@@ -855,7 +855,6 @@
@GuardedBy("mLock")
@SmartSuggestionMode int getSupportedSmartSuggestionModesLocked() {
- // TODO(b/111330312): once we support IME, we need to set it per-user (OR'ed with master)
return mMaster.getSupportedSmartSuggestionModesLocked();
}
@@ -1049,7 +1048,7 @@
componentName, mUserId, new RemoteAugmentedAutofillServiceCallbacks() {
@Override
public void onServiceDied(@NonNull RemoteAugmentedAutofillService service) {
- // TODO(b/111330312): properly implement
+ // TODO(b/123100811): properly implement
Slog.w(TAG, "remote augmented autofill service died");
}
}, mMaster.isInstantServiceAllowed(), mMaster.verbose);
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 5d8d8fa..9b863a9 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -113,7 +113,7 @@
scheduleAsyncRequest((s) -> s.onDestroyAllFillWindowsRequest());
}
- // TODO(b/111330312): inline into PendingAutofillRequest if it doesn't have any other subclass
+ // TODO(b/123100811): inline into PendingAutofillRequest if it doesn't have any other subclass
private abstract static class MyPendingRequest
extends PendingRequest<RemoteAugmentedAutofillService, IAugmentedAutofillService> {
protected final int mSessionId;
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 7dfd8fe..194332a 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -23,11 +23,10 @@
import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED;
import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED;
+import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM;
+import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-import static com.android.server.autofill.AutofillManagerService.FLAG_SMART_SUGGESTION_IME;
-import static com.android.server.autofill.AutofillManagerService.FLAG_SMART_SUGGESTION_SYSTEM;
-import static com.android.server.autofill.AutofillManagerService.smartSuggestionFlagsToString;
import static com.android.server.autofill.Helper.getNumericValue;
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sVerbose;
@@ -88,6 +87,7 @@
import android.view.KeyEvent;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillManager.SmartSuggestionMode;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManagerClient;
import android.view.autofill.IAutofillWindowPresenter;
@@ -97,7 +97,6 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.ArrayUtils;
-import com.android.server.autofill.AutofillManagerService.SmartSuggestionMode;
import com.android.server.autofill.ui.AutoFillUI;
import com.android.server.autofill.ui.PendingUi;
@@ -250,7 +249,7 @@
/**
* Destroys the augmented Autofill UI.
*/
- // TODO(b/111330312): this runnable is called when the Autofill session is destroyed, the
+ // TODO(b/123099468): this runnable is called when the Autofill session is destroyed, the
// main reason being the cases where user tap HOME.
// Right now it's completely destroying the UI, but we need to decide whether / how to
// properly recover it later (for example, if the user switches back to the activity,
@@ -261,6 +260,12 @@
private Runnable mAugmentedAutofillDestroyer;
/**
+ * List of {@link MetricsEvent#AUTOFILL_AUGMENTED_REQUEST} metrics.
+ */
+ @GuardedBy("mLock")
+ private ArrayList<LogMaker> mAugmentedRequestsLogs;
+
+ /**
* Receiver of assist data from the app's {@link Activity}.
*/
private final IAssistDataReceiver mAssistReceiver = new IAssistDataReceiver.Stub() {
@@ -2541,8 +2546,8 @@
}
mService.resetLastResponse();
- // The default autofill service cannot fullfill the request, let's check if the intelligence
- // service can.
+ // The default autofill service cannot fullfill the request, let's check if the augmented
+ // autofill service can.
mAugmentedAutofillDestroyer = triggerAugmentedAutofillLocked();
if (mAugmentedAutofillDestroyer == null) {
if (sVerbose) {
@@ -2553,7 +2558,7 @@
notifyUnavailableToClient(AutofillManager.STATE_FINISHED);
removeSelf();
} else {
- // TODO(b/111330312, b/119638958): must set internal state so when user focus other
+ // TODO(b/123099468, b/119638958): must set internal state so when user focus other
// fields it does not generate a new call to the standard autofill service (right now
// it does). Must also add CTS tests to exercise this scenario.
if (sVerbose) {
@@ -2568,7 +2573,7 @@
*
* @return callback to destroy the autofill UI, or {@code null} if not supported.
*/
- // TODO(b/111330312): might need to call it in other places, like when the service returns a
+ // TODO(b/123099468): might need to call it in other places, like when the service returns a
// non-null response but without datasets (for example, just SaveInfo)
@GuardedBy("mLock")
private Runnable triggerAugmentedAutofillLocked() {
@@ -2588,14 +2593,12 @@
// Define which mode will be used
final int mode;
- if ((supportedModes & FLAG_SMART_SUGGESTION_IME) != 0) {
- // TODO(b/111330312): support it :-)
- Slog.w(TAG, "Smart Suggestions on IME not supported yet");
- return null;
- } else if ((supportedModes & FLAG_SMART_SUGGESTION_SYSTEM) != 0) {
+ if ((supportedModes & FLAG_SMART_SUGGESTION_SYSTEM) != 0) {
mode = FLAG_SMART_SUGGESTION_SYSTEM;
+ } else if ((supportedModes & AutofillManager.FLAG_SMART_SUGGESTION_LEGACY) != 0) {
+ mode = AutofillManager.FLAG_SMART_SUGGESTION_LEGACY;
} else {
- Slog.w(TAG, "Unsupported Smart Suggestion Mode: " + supportedModes);
+ Slog.w(TAG, "Unsupported Smart Suggestion mode: " + supportedModes);
return null;
}
@@ -2605,8 +2608,10 @@
}
if (sVerbose) {
- Slog.v(TAG, "calling IntelligenseService on view " + mCurrentViewId
- + " using suggestion mode " + smartSuggestionFlagsToString(mode)
+ Slog.v(TAG, "calling Augmented Autofill Service ("
+ + remoteService.getComponentName().toShortString() + ") on view "
+ + mCurrentViewId + " using suggestion mode "
+ + getSmartSuggestionModeToString(mode)
+ " when server returned null for session " + this.id);
}
@@ -2620,8 +2625,15 @@
final AutofillValue currentValue = mViewStates.get(mCurrentViewId).getCurrentValue();
// TODO(b/111330312): we might need to add a new state in the AutofillManager to optimize
- // furgher AFM -> AFMS calls.
- // TODO(b/119638958): add CTS tests
+ // further AFM -> AFMS calls.
+
+ if (mAugmentedRequestsLogs == null) {
+ mAugmentedRequestsLogs = new ArrayList<>();
+ }
+ final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_AUGMENTED_REQUEST,
+ remoteService.getComponentName().getPackageName());
+ mAugmentedRequestsLogs.add(log);
+
remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, mCurrentViewId,
currentValue);
@@ -2912,6 +2924,11 @@
if (mAugmentedAutofillDestroyer != null) {
pw.print(prefix); pw.println("has mAugmentedAutofillDestroyer");
}
+ if (mAugmentedRequestsLogs != null) {
+ pw.print(prefix); pw.print("number augmented requests: ");
+ pw.println(mAugmentedRequestsLogs.size());
+ }
+
mRemoteFillService.dump(prefix, pw);
}
@@ -3053,8 +3070,26 @@
mMetricsLogger.write(log);
}
}
- mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_FINISHED)
- .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_REQUESTS, totalRequests));
+
+ final int totalAugmentedRequests = mAugmentedRequestsLogs == null ? 0
+ : mAugmentedRequestsLogs.size();
+ if (totalAugmentedRequests > 0) {
+ if (sVerbose) {
+ Slog.v(TAG, "destroyLocked(): logging " + totalRequests + " augmented requests");
+ }
+ for (int i = 0; i < totalAugmentedRequests; i++) {
+ final LogMaker log = mAugmentedRequestsLogs.get(i);
+ mMetricsLogger.write(log);
+ }
+ }
+
+ final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_SESSION_FINISHED)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_REQUESTS, totalRequests);
+ if (totalAugmentedRequests > 0) {
+ log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS,
+ totalAugmentedRequests);
+ }
+ mMetricsLogger.write(log);
return mRemoteFillService;
}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index ff378b3..14322ec 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -124,6 +124,11 @@
*/
@VisibleForTesting
protected void startServiceForUser(int userId) {
+ if (mServiceUsers.get(userId) != null) {
+ Slog.i(TAG, "userId " + userId + " already started, so not starting again");
+ return;
+ }
+
UserBackupManagerService userBackupManagerService =
UserBackupManagerService.createAndInitializeService(
userId, mContext, mTrampoline, mTransportWhitelist);
@@ -155,7 +160,12 @@
}
}
- SparseArray<UserBackupManagerService> getServiceUsers() {
+ /**
+ * Returns a lst of users currently unlocked that have a
+ * {@link UserBackupManagerService} registered.
+ */
+ @VisibleForTesting
+ public SparseArray<UserBackupManagerService> getServiceUsers() {
return mServiceUsers;
}
diff --git a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
index 4a1e5b9..2241569 100644
--- a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
+++ b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
@@ -164,7 +164,7 @@
int N = pkgs.size();
for (int a = N-1; a >= 0; a--) {
PackageInfo pkg = pkgs.get(a);
- if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, pm)) {
+ if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, userId)) {
pkgs.remove(a);
}
}
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index 4ca2545..4f58d79 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -32,7 +32,6 @@
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
-import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -45,7 +44,6 @@
import android.provider.Settings;
import android.util.Slog;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;
import java.io.File;
@@ -65,8 +63,8 @@
* following two ways:
*
* <ul>
- * <li>Temporary - create the file {@link #BACKUP_SUPPRESS_FILENAME}, or
- * <li>Permanent - set the system property {@link #BACKUP_DISABLE_PROPERTY} to true.
+ * <li>Temporary - create the file {@link #BACKUP_SUPPRESS_FILENAME}, or
+ * <li>Permanent - set the system property {@link #BACKUP_DISABLE_PROPERTY} to true.
* </ul>
*
* Temporary disabling is controlled by {@link #setBackupServiceActive(int, boolean)} through
@@ -91,10 +89,9 @@
private final Context mContext;
- @GuardedBy("mStateLock")
- private final File mSuppressFile;
-
private final boolean mGlobalDisable;
+ // Lock to write backup suppress files.
+ // TODD(b/121198006): remove this object and synchronized all methods on "this".
private final Object mStateLock = new Object();
private volatile BackupManagerService mService;
@@ -104,9 +101,6 @@
public Trampoline(Context context) {
mContext = context;
mGlobalDisable = isBackupDisabled();
- mSuppressFile = getSuppressFile();
- mSuppressFile.getParentFile().mkdirs();
-
mHandlerThread = new HandlerThread(BACKUP_THREAD, Process.THREAD_PRIORITY_BACKGROUND);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
@@ -132,11 +126,42 @@
return Binder.getCallingUid();
}
- protected File getSuppressFile() {
- return new File(new File(Environment.getDataDirectory(), "backup"),
+ protected File getSuppressFileForUser(int userId) {
+ return new File(UserBackupManagerFiles.getBaseStateDir(userId),
BACKUP_SUPPRESS_FILENAME);
}
+ protected void createBackupSuppressFileForUser(int userId) throws IOException {
+ synchronized (mStateLock) {
+ getSuppressFileForUser(userId).getParentFile().mkdirs();
+ getSuppressFileForUser(userId).createNewFile();
+ }
+ }
+
+ private void deleteBackupSuppressFileForUser(int userId) {
+ if (!getSuppressFileForUser(userId).delete()) {
+ Slog.w(TAG, "Failed deleting backup suppressed file for user: " + userId);
+ }
+ }
+
+ // A user is ready for a backup if it's unlocked and is not suppressed by a device
+ // admin (device owner or profile owner).
+ private boolean isUserReadyForBackup(int userId) {
+ return mService != null && mService.getServiceUsers().get(userId) != null
+ && !isBackupSuppressedForUser(userId);
+ }
+
+ private boolean isBackupSuppressedForUser(int userId) {
+ // If backup is disabled for system user, it's disabled for all other users on device.
+ if (getSuppressFileForUser(UserHandle.USER_SYSTEM).exists()) {
+ return true;
+ }
+ if (userId != UserHandle.USER_SYSTEM) {
+ return getSuppressFileForUser(userId).exists();
+ }
+ return false;
+ }
+
protected Context getContext() {
return mContext;
}
@@ -162,12 +187,9 @@
Slog.i(TAG, "Backup service not supported");
return;
}
-
synchronized (mStateLock) {
- if (!mSuppressFile.exists()) {
+ if (mService == null) {
mService = createBackupManagerService();
- } else {
- Slog.i(TAG, "Backup service inactive");
}
}
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
@@ -190,10 +212,11 @@
}
private void startServiceForUser(int userId) {
- BackupManagerService service = mService;
- if (service != null) {
+ // We know that the user is unlocked here because it is called from setBackupServiceActive
+ // and unlockUser which have these guarantees. So we can check if the file exists.
+ if (mService != null && !isBackupSuppressedForUser(userId)) {
Slog.i(TAG, "Starting service for user: " + userId);
- service.startServiceForUser(userId);
+ mService.startServiceForUser(userId);
}
}
@@ -209,10 +232,9 @@
postToHandler(
() -> {
- BackupManagerService service = mService;
- if (service != null) {
+ if (mService != null) {
Slog.i(TAG, "Stopping service for user: " + userId);
- service.stopServiceForUser(userId);
+ mService.stopServiceForUser(userId);
}
});
}
@@ -221,6 +243,8 @@
* Only privileged callers should be changing the backup state. This method only acts on {@link
* UserHandle#USER_SYSTEM} and is a no-op if passed non-system users. Deactivating backup in the
* system user also deactivates backup in all users.
+ *
+ * This call will only work if the calling {@code userID} is unlocked.
*/
public void setBackupServiceActive(int userId, boolean makeActive) {
int caller = binderGetCallingUid();
@@ -246,16 +270,21 @@
synchronized (mStateLock) {
Slog.i(TAG, "Making backup " + (makeActive ? "" : "in") + "active");
if (makeActive) {
- mService = createBackupManagerService();
- mSuppressFile.delete();
+ if (mService == null) {
+ mService = createBackupManagerService();
+ }
+ deleteBackupSuppressFileForUser(userId);
startServiceForUser(userId);
} else {
- mService = null;
try {
- mSuppressFile.createNewFile();
+ //TODO(b/121198006): what if this throws an exception?
+ createBackupSuppressFileForUser(userId);
} catch (IOException e) {
Slog.e(TAG, "Unable to persist backup service inactivity");
}
+ //TODO(b/121198006): loop through active users that have work profile and
+ // stop them as well.
+ stopUser(userId);
}
}
}
@@ -271,20 +300,15 @@
*/
@Override
public boolean isBackupServiceActive(int userId) {
- // TODO: http://b/22388012
- if (userId == UserHandle.USER_SYSTEM) {
- synchronized (mStateLock) {
- return mService != null;
- }
+ synchronized (mStateLock) {
+ return isUserReadyForBackup(userId);
}
- return false;
}
@Override
public void dataChangedForUser(int userId, String packageName) throws RemoteException {
- BackupManagerService svc = mService;
- if (svc != null) {
- svc.dataChanged(userId, packageName);
+ if (isUserReadyForBackup(userId)) {
+ mService.dataChanged(userId, packageName);
}
}
@@ -296,18 +320,16 @@
@Override
public void initializeTransportsForUser(
int userId, String[] transportNames, IBackupObserver observer) throws RemoteException {
- BackupManagerService svc = mService;
- if (svc != null) {
- svc.initializeTransports(userId, transportNames, observer);
+ if (isUserReadyForBackup(userId)) {
+ mService.initializeTransports(userId, transportNames, observer);
}
}
@Override
public void clearBackupDataForUser(int userId, String transportName, String packageName)
throws RemoteException {
- BackupManagerService svc = mService;
- if (svc != null) {
- svc.clearBackupData(userId, transportName, packageName);
+ if (isUserReadyForBackup(userId)) {
+ mService.clearBackupData(userId, transportName, packageName);
}
}
@@ -320,9 +342,8 @@
@Override
public void agentConnectedForUser(int userId, String packageName, IBinder agent)
throws RemoteException {
- BackupManagerService svc = mService;
- if (svc != null) {
- svc.agentConnected(userId, packageName, agent);
+ if (isUserReadyForBackup(userId)) {
+ mService.agentConnected(userId, packageName, agent);
}
}
@@ -333,9 +354,8 @@
@Override
public void agentDisconnectedForUser(int userId, String packageName) throws RemoteException {
- BackupManagerService svc = mService;
- if (svc != null) {
- svc.agentDisconnected(userId, packageName);
+ if (isUserReadyForBackup(userId)) {
+ mService.agentDisconnected(userId, packageName);
}
}
@@ -347,9 +367,8 @@
@Override
public void restoreAtInstallForUser(int userId, String packageName, int token)
throws RemoteException {
- BackupManagerService svc = mService;
- if (svc != null) {
- svc.restoreAtInstall(userId, packageName, token);
+ if (isUserReadyForBackup(userId)) {
+ mService.restoreAtInstall(userId, packageName, token);
}
}
@@ -361,9 +380,8 @@
@Override
public void setBackupEnabledForUser(@UserIdInt int userId, boolean isEnabled)
throws RemoteException {
- BackupManagerService svc = mService;
- if (svc != null) {
- svc.setBackupEnabled(userId, isEnabled);
+ if (isUserReadyForBackup(userId)) {
+ mService.setBackupEnabled(userId, isEnabled);
}
}
@@ -374,9 +392,8 @@
@Override
public void setAutoRestoreForUser(int userId, boolean doAutoRestore) throws RemoteException {
- BackupManagerService svc = mService;
- if (svc != null) {
- svc.setAutoRestore(userId, doAutoRestore);
+ if (isUserReadyForBackup(userId)) {
+ mService.setAutoRestore(userId, doAutoRestore);
}
}
@@ -387,8 +404,7 @@
@Override
public boolean isBackupEnabledForUser(@UserIdInt int userId) throws RemoteException {
- BackupManagerService svc = mService;
- return (svc != null) ? svc.isBackupEnabled(userId) : false;
+ return isUserReadyForBackup(userId) && mService.isBackupEnabled(userId);
}
@Override
@@ -398,21 +414,20 @@
@Override
public boolean setBackupPassword(String currentPw, String newPw) throws RemoteException {
- BackupManagerService svc = mService;
- return (svc != null) ? svc.setBackupPassword(currentPw, newPw) : false;
+ int userId = binderGetCallingUserId();
+ return (isUserReadyForBackup(userId)) && mService.setBackupPassword(currentPw, newPw);
}
@Override
public boolean hasBackupPassword() throws RemoteException {
- BackupManagerService svc = mService;
- return (svc != null) ? svc.hasBackupPassword() : false;
+ int userId = binderGetCallingUserId();
+ return (isUserReadyForBackup(userId)) && mService.hasBackupPassword();
}
@Override
public void backupNowForUser(@UserIdInt int userId) throws RemoteException {
- BackupManagerService svc = mService;
- if (svc != null) {
- svc.backupNow(userId);
+ if (isUserReadyForBackup(userId)) {
+ mService.backupNow(userId);
}
}
@@ -425,9 +440,8 @@
boolean includeApks, boolean includeObbs, boolean includeShared, boolean doWidgets,
boolean allApps, boolean allIncludesSystem, boolean doCompress, boolean doKeyValue,
String[] packageNames) throws RemoteException {
- BackupManagerService svc = mService;
- if (svc != null) {
- svc.adbBackup(userId, fd, includeApks, includeObbs, includeShared, doWidgets,
+ if (isUserReadyForBackup(userId)) {
+ mService.adbBackup(userId, fd, includeApks, includeObbs, includeShared, doWidgets,
allApps, allIncludesSystem, doCompress, doKeyValue, packageNames);
}
}
@@ -435,17 +449,15 @@
@Override
public void fullTransportBackupForUser(int userId, String[] packageNames)
throws RemoteException {
- BackupManagerService svc = mService;
- if (svc != null) {
- svc.fullTransportBackup(userId, packageNames);
+ if (isUserReadyForBackup(userId)) {
+ mService.fullTransportBackup(userId, packageNames);
}
}
@Override
public void adbRestore(@UserIdInt int userId, ParcelFileDescriptor fd) throws RemoteException {
- BackupManagerService svc = mService;
- if (svc != null) {
- svc.adbRestore(userId, fd);
+ if (isUserReadyForBackup(userId)) {
+ mService.adbRestore(userId, fd);
}
}
@@ -458,9 +470,8 @@
String encryptionPassword,
IFullBackupRestoreObserver observer)
throws RemoteException {
- BackupManagerService svc = mService;
- if (svc != null) {
- svc.acknowledgeAdbBackupOrRestore(userId, token, allow,
+ if (isUserReadyForBackup(userId)) {
+ mService.acknowledgeAdbBackupOrRestore(userId, token, allow,
curPassword, encryptionPassword, observer);
}
}
@@ -468,8 +479,7 @@
@Override
public void acknowledgeFullBackupOrRestore(int token, boolean allow, String curPassword,
String encryptionPassword, IFullBackupRestoreObserver observer)
- throws RemoteException {
- BackupManagerService svc = mService;
+ throws RemoteException {
acknowledgeFullBackupOrRestoreForUser(
binderGetCallingUserId(), token, allow, curPassword, encryptionPassword, observer);
}
@@ -477,8 +487,7 @@
@Override
public String getCurrentTransportForUser(int userId) throws RemoteException {
- BackupManagerService svc = mService;
- return (svc != null) ? svc.getCurrentTransport(userId) : null;
+ return (isUserReadyForBackup(userId)) ? mService.getCurrentTransport(userId) : null;
}
@Override
@@ -493,14 +502,13 @@
@Override
@Nullable
public ComponentName getCurrentTransportComponentForUser(int userId) {
- BackupManagerService svc = mService;
- return (svc != null) ? svc.getCurrentTransportComponent(userId) : null;
+ return (isUserReadyForBackup(userId)) ? mService.getCurrentTransportComponent(userId)
+ : null;
}
@Override
public String[] listAllTransportsForUser(int userId) throws RemoteException {
- BackupManagerService svc = mService;
- return (svc != null) ? svc.listAllTransports(userId) : null;
+ return (isUserReadyForBackup(userId)) ? mService.listAllTransports(userId) : null;
}
@Override
@@ -510,14 +518,14 @@
@Override
public ComponentName[] listAllTransportComponentsForUser(int userId) throws RemoteException {
- BackupManagerService svc = mService;
- return (svc != null) ? svc.listAllTransportComponents(userId) : null;
+ return (isUserReadyForBackup(userId)) ? mService.listAllTransportComponents(userId)
+ : null;
}
@Override
public String[] getTransportWhitelist() {
- BackupManagerService svc = mService;
- return (svc != null) ? svc.getTransportWhitelist() : null;
+ int userId = binderGetCallingUserId();
+ return (isUserReadyForBackup(userId)) ? mService.getTransportWhitelist() : null;
}
@Override
@@ -529,9 +537,9 @@
String currentDestinationString,
@Nullable Intent dataManagementIntent,
String dataManagementLabel) {
- BackupManagerService svc = mService;
- if (svc != null) {
- svc.updateTransportAttributes(
+
+ if (isUserReadyForBackup(userId)) {
+ mService.updateTransportAttributes(
userId,
transportComponent,
name,
@@ -545,8 +553,8 @@
@Override
public String selectBackupTransportForUser(int userId, String transport)
throws RemoteException {
- BackupManagerService svc = mService;
- return (svc != null) ? svc.selectBackupTransport(userId, transport) : null;
+ return (isUserReadyForBackup(userId)) ? mService.selectBackupTransport(userId, transport)
+ : null;
}
@Override
@@ -557,9 +565,8 @@
@Override
public void selectBackupTransportAsyncForUser(int userId, ComponentName transport,
ISelectBackupTransportCallback listener) throws RemoteException {
- BackupManagerService svc = mService;
- if (svc != null) {
- svc.selectBackupTransportAsync(userId, transport, listener);
+ if (isUserReadyForBackup(userId)) {
+ mService.selectBackupTransportAsync(userId, transport, listener);
} else {
if (listener != null) {
try {
@@ -574,8 +581,8 @@
@Override
public Intent getConfigurationIntentForUser(int userId, String transport)
throws RemoteException {
- BackupManagerService svc = mService;
- return (svc != null) ? svc.getConfigurationIntent(userId, transport) : null;
+ return isUserReadyForBackup(userId) ? mService.getConfigurationIntent(userId, transport)
+ : null;
}
@Override
@@ -586,8 +593,8 @@
@Override
public String getDestinationStringForUser(int userId, String transport) throws RemoteException {
- BackupManagerService svc = mService;
- return (svc != null) ? svc.getDestinationString(userId, transport) : null;
+ return isUserReadyForBackup(userId) ? mService.getDestinationString(userId, transport)
+ : null;
}
@Override
@@ -598,8 +605,8 @@
@Override
public Intent getDataManagementIntentForUser(int userId, String transport)
throws RemoteException {
- BackupManagerService svc = mService;
- return (svc != null) ? svc.getDataManagementIntent(userId, transport) : null;
+ return isUserReadyForBackup(userId) ? mService.getDataManagementIntent(userId, transport)
+ : null;
}
@Override
@@ -611,8 +618,8 @@
@Override
public String getDataManagementLabelForUser(int userId, String transport)
throws RemoteException {
- BackupManagerService svc = mService;
- return (svc != null) ? svc.getDataManagementLabel(userId, transport) : null;
+ return isUserReadyForBackup(userId) ? mService.getDataManagementLabel(userId, transport)
+ : null;
}
@Override
@@ -624,44 +631,47 @@
@Override
public IRestoreSession beginRestoreSessionForUser(
int userId, String packageName, String transportID) throws RemoteException {
- BackupManagerService svc = mService;
- return (svc != null) ? svc.beginRestoreSession(userId, packageName, transportID) : null;
+ return isUserReadyForBackup(userId) ? mService.beginRestoreSession(userId, packageName,
+ transportID) : null;
}
@Override
- public void opComplete(int token, long result) throws RemoteException {
- BackupManagerService svc = mService;
- if (svc != null) {
- svc.opComplete(binderGetCallingUserId(), token, result);
+ public void opCompleteForUser(int userId, int token, long result) throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ mService.opComplete(userId, token, result);
}
}
@Override
+ public void opComplete(int token, long result) throws RemoteException {
+ opCompleteForUser(binderGetCallingUserId(), token, result);
+ }
+
+ @Override
public long getAvailableRestoreTokenForUser(int userId, String packageName) {
- BackupManagerService svc = mService;
- return (svc != null) ? svc.getAvailableRestoreToken(userId, packageName) : 0;
+ return isUserReadyForBackup(userId) ? mService.getAvailableRestoreToken(userId,
+ packageName) : 0;
}
@Override
public boolean isAppEligibleForBackupForUser(int userId, String packageName) {
- BackupManagerService svc = mService;
- return (svc != null) ? svc.isAppEligibleForBackup(userId, packageName) : false;
+ return isUserReadyForBackup(userId) && mService.isAppEligibleForBackup(userId,
+ packageName);
}
@Override
public String[] filterAppsEligibleForBackupForUser(int userId, String[] packages) {
- BackupManagerService svc = mService;
- return (svc != null) ? svc.filterAppsEligibleForBackup(userId, packages) : null;
+ return isUserReadyForBackup(userId) ? mService.filterAppsEligibleForBackup(userId,
+ packages) : null;
}
@Override
public int requestBackupForUser(@UserIdInt int userId, String[] packages, IBackupObserver
observer, IBackupManagerMonitor monitor, int flags) throws RemoteException {
- BackupManagerService svc = mService;
- if (svc == null) {
+ if (!isUserReadyForBackup(userId)) {
return BackupManager.ERROR_BACKUP_NOT_ALLOWED;
}
- return svc.requestBackup(userId, packages, observer, monitor, flags);
+ return mService.requestBackup(userId, packages, observer, monitor, flags);
}
@Override
@@ -673,9 +683,8 @@
@Override
public void cancelBackupsForUser(@UserIdInt int userId) throws RemoteException {
- BackupManagerService svc = mService;
- if (svc != null) {
- svc.cancelBackups(userId);
+ if (isUserReadyForBackup(userId)) {
+ mService.cancelBackups(userId);
}
}
@@ -687,10 +696,9 @@
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
-
- BackupManagerService svc = mService;
- if (svc != null) {
- svc.dump(fd, pw, args);
+ int userId = binderGetCallingUserId();
+ if (isUserReadyForBackup(userId)) {
+ mService.dump(fd, pw, args);
} else {
pw.println("Inactive");
}
@@ -699,14 +707,12 @@
// Full backup/restore entry points - non-Binder; called directly
// by the full-backup scheduled job
/* package */ boolean beginFullBackup(@UserIdInt int userId, FullBackupJob scheduledJob) {
- BackupManagerService svc = mService;
- return (svc != null) ? svc.beginFullBackup(userId, scheduledJob) : false;
+ return (isUserReadyForBackup(userId)) && mService.beginFullBackup(userId, scheduledJob);
}
/* package */ void endFullBackup(@UserIdInt int userId) {
- BackupManagerService svc = mService;
- if (svc != null) {
- svc.endFullBackup(userId);
+ if (isUserReadyForBackup(userId)) {
+ mService.endFullBackup(userId);
}
}
}
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 79f8a7e..115e924 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -804,7 +804,7 @@
public BackupAgent makeMetadataAgent() {
PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(mPackageManager, mUserId);
pmAgent.attach(mContext);
- pmAgent.onCreate();
+ pmAgent.onCreate(UserHandle.of(mUserId));
return pmAgent;
}
@@ -815,7 +815,7 @@
PackageManagerBackupAgent pmAgent =
new PackageManagerBackupAgent(mPackageManager, packages, mUserId);
pmAgent.attach(mContext);
- pmAgent.onCreate();
+ pmAgent.onCreate(UserHandle.of(mUserId));
return pmAgent;
}
@@ -910,10 +910,10 @@
long lastBackup = in.readLong();
foundApps.add(pkgName); // all apps that we've addressed already
try {
- PackageInfo pkg = mPackageManager.getPackageInfo(pkgName, 0);
+ PackageInfo pkg = mPackageManager.getPackageInfoAsUser(pkgName, 0, mUserId);
if (AppBackupUtils.appGetsFullBackup(pkg)
- && AppBackupUtils.appIsEligibleForBackup(
- pkg.applicationInfo, mPackageManager)) {
+ && AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo,
+ mUserId)) {
schedule.add(new FullBackupEntry(pkgName, lastBackup));
} else {
if (DEBUG) {
@@ -933,8 +933,8 @@
// scan to make sure that we're tracking all full-backup candidates properly
for (PackageInfo app : apps) {
if (AppBackupUtils.appGetsFullBackup(app)
- && AppBackupUtils.appIsEligibleForBackup(
- app.applicationInfo, mPackageManager)) {
+ && AppBackupUtils.appIsEligibleForBackup(app.applicationInfo,
+ mUserId)) {
if (!foundApps.contains(app.packageName)) {
if (MORE_DEBUG) {
Slog.i(TAG, "New full backup app " + app.packageName + " found");
@@ -960,7 +960,7 @@
schedule = new ArrayList<>(apps.size());
for (PackageInfo info : apps) {
if (AppBackupUtils.appGetsFullBackup(info) && AppBackupUtils.appIsEligibleForBackup(
- info.applicationInfo, mPackageManager)) {
+ info.applicationInfo, mUserId)) {
schedule.add(new FullBackupEntry(info.packageName, 0));
}
}
@@ -1222,8 +1222,8 @@
mPackageManager.getPackageInfoAsUser(
packageName, /* flags */ 0, mUserId);
if (AppBackupUtils.appGetsFullBackup(app)
- && AppBackupUtils.appIsEligibleForBackup(
- app.applicationInfo, mPackageManager)) {
+ && AppBackupUtils.appIsEligibleForBackup(app.applicationInfo,
+ mUserId)) {
enqueueFullBackup(packageName, now);
scheduleNextFullBackupJob(0);
} else {
@@ -1618,8 +1618,7 @@
try {
PackageInfo packageInfo = mPackageManager.getPackageInfoAsUser(packageName,
PackageManager.GET_SIGNING_CERTIFICATES, mUserId);
- if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo,
- mPackageManager)) {
+ if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo, mUserId)) {
BackupObserverUtils.sendBackupOnPackageResult(observer, packageName,
BackupManager.ERROR_BACKUP_NOT_ALLOWED);
continue;
@@ -2095,7 +2094,8 @@
}
try {
- PackageInfo appInfo = mPackageManager.getPackageInfo(entry.packageName, 0);
+ PackageInfo appInfo = mPackageManager.getPackageInfoAsUser(
+ entry.packageName, 0, mUserId);
if (!AppBackupUtils.appGetsFullBackup(appInfo)) {
// The head app isn't supposed to get full-data backups [any more];
// so we cull it and force a loop around to consider the new head
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
index 31786d7..0a7159b 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
@@ -286,7 +286,8 @@
Iterator<Entry<String, PackageInfo>> iter = packagesToBackup.entrySet().iterator();
while (iter.hasNext()) {
PackageInfo pkg = iter.next().getValue();
- if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, pm)
+ if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo,
+ mUserBackupManagerService.getUserId())
|| AppBackupUtils.appIsStopped(pkg.applicationInfo)) {
iter.remove();
if (DEBUG) {
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index 0fb4f93..86e679f 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -143,6 +143,7 @@
private final int mBackupRunnerOpToken;
private final OnTaskFinishedListener mListener;
private final TransportClient mTransportClient;
+ private final int mUserId;
// This is true when a backup operation for some package is in progress.
private volatile boolean mIsDoingBackup;
@@ -173,6 +174,7 @@
mAgentTimeoutParameters = Preconditions.checkNotNull(
backupManagerService.getAgentTimeoutParameters(),
"Timeout parameters cannot be null");
+ mUserId = backupManagerService.getUserId();
if (backupManagerService.isBackupOperationInProgress()) {
if (DEBUG) {
@@ -187,9 +189,10 @@
for (String pkg : whichPackages) {
try {
PackageManager pm = backupManagerService.getPackageManager();
- PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNING_CERTIFICATES);
+ PackageInfo info = pm.getPackageInfoAsUser(pkg,
+ PackageManager.GET_SIGNING_CERTIFICATES, mUserId);
mCurrentPackage = info;
- if (!AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, pm)) {
+ if (!AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, mUserId)) {
// Cull any packages that have indicated that backups are not permitted,
// that run as system-domain uids but do not define their own backup agents,
// as well as any explicit mention of the 'special' shared-storage agent
@@ -633,7 +636,7 @@
unregisterTask();
if (mJob != null) {
- mJob.finishBackupPass(backupManagerService.getUserId());
+ mJob.finishBackupPass(mUserId);
}
synchronized (backupManagerService.getQueueLock()) {
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index cfc129e..294eb01 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -489,7 +489,7 @@
throw AgentException.permanent(e);
}
ApplicationInfo applicationInfo = packageInfo.applicationInfo;
- if (!AppBackupUtils.appIsEligibleForBackup(applicationInfo, mPackageManager)) {
+ if (!AppBackupUtils.appIsEligibleForBackup(applicationInfo, mUserId)) {
mReporter.onPackageNotEligibleForBackup(packageName);
throw AgentException.permanent();
}
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index b3d9fbc..c5389fa 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -230,7 +230,7 @@
PackageManagerInternal.class);
RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy(
mBackupManagerService.getPackageManager(), allowApks, info, signatures,
- pmi);
+ pmi, mUserId);
mManifestSignatures.put(info.packageName, signatures);
mPackagePolicies.put(pkg, restorePolicy);
mPackageInstallers.put(pkg, info.installerPackageName);
@@ -332,8 +332,9 @@
}
try {
- mTargetApp = mBackupManagerService.getPackageManager()
- .getApplicationInfoAsUser(pkg, 0, mUserId);
+ mTargetApp =
+ mBackupManagerService.getPackageManager()
+ .getApplicationInfoAsUser(pkg, 0, mUserId);
// If we haven't sent any data to this app yet, we probably
// need to clear it first. Check that.
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index d01f77b..7763d7b 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -232,7 +232,7 @@
continue;
}
- if (AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, pm)) {
+ if (AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, mUserId)) {
mAcceptSet.add(info);
}
} catch (NameNotFoundException e) {
diff --git a/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java b/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
index 054879b..2db8928 100644
--- a/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
+++ b/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
@@ -18,19 +18,25 @@
import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
import static com.android.server.backup.BackupManagerService.TAG;
+import static com.android.server.backup.UserBackupManagerService.PACKAGE_MANAGER_SENTINEL;
import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
import android.annotation.Nullable;
+import android.app.AppGlobals;
import android.app.backup.BackupTransport;
import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.content.pm.SigningInfo;
import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.backup.IBackupTransport;
import com.android.internal.util.ArrayUtils;
import com.android.server.backup.transport.TransportClient;
@@ -39,7 +45,6 @@
* Utility methods wrapping operations on ApplicationInfo and PackageInfo.
*/
public class AppBackupUtils {
-
private static final boolean DEBUG = false;
/**
@@ -54,15 +59,30 @@
* <li>it is the special shared-storage backup package used for 'adb backup'
* </ol>
*/
- public static boolean appIsEligibleForBackup(ApplicationInfo app, PackageManager pm) {
+ public static boolean appIsEligibleForBackup(ApplicationInfo app, int userId) {
+ return appIsEligibleForBackup(app, AppGlobals.getPackageManager(), userId);
+ }
+
+ @VisibleForTesting
+ static boolean appIsEligibleForBackup(ApplicationInfo app,
+ IPackageManager packageManager, int userId) {
// 1. their manifest states android:allowBackup="false"
if ((app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0) {
return false;
}
- // 2. they run as a system-level uid but do not supply their own backup agent
- if ((app.uid < Process.FIRST_APPLICATION_UID) && (app.backupAgentName == null)) {
- return false;
+ // 2. they run as a system-level uid
+ if ((app.uid < Process.FIRST_APPLICATION_UID)) {
+ // and the backup is happening for non-system user
+ if (userId != UserHandle.USER_SYSTEM && !app.packageName.equals(
+ PACKAGE_MANAGER_SENTINEL)) {
+ return false;
+ }
+
+ // or do not supply their own backup agent
+ if (app.backupAgentName == null) {
+ return false;
+ }
}
// 3. it is the special shared-storage backup package used for 'adb backup'
@@ -75,9 +95,7 @@
return false;
}
- // Everything else checks out; the only remaining roadblock would be if the
- // package were disabled
- return !appIsDisabled(app, pm);
+ return !appIsDisabled(app, packageManager, userId);
}
/**
@@ -99,9 +117,9 @@
PackageInfo packageInfo = pm.getPackageInfoAsUser(packageName,
PackageManager.GET_SIGNING_CERTIFICATES, userId);
ApplicationInfo applicationInfo = packageInfo.applicationInfo;
- if (!appIsEligibleForBackup(applicationInfo, pm)
+ if (!appIsEligibleForBackup(applicationInfo, userId)
|| appIsStopped(applicationInfo)
- || appIsDisabled(applicationInfo, pm)) {
+ || appIsDisabled(applicationInfo, userId)) {
return false;
}
if (transportClient != null) {
@@ -123,8 +141,22 @@
}
/** Avoid backups of 'disabled' apps. */
- public static boolean appIsDisabled(ApplicationInfo app, PackageManager pm) {
- switch (pm.getApplicationEnabledSetting(app.packageName)) {
+ static boolean appIsDisabled(ApplicationInfo app, int userId) {
+ return appIsDisabled(app, AppGlobals.getPackageManager(), userId);
+ }
+
+ @VisibleForTesting
+ static boolean appIsDisabled(ApplicationInfo app,
+ IPackageManager packageManager, int userId) {
+ int enabledSetting;
+ try {
+ enabledSetting = packageManager.getApplicationEnabledSetting(app.packageName, userId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get application enabled setting: " + e);
+ return false;
+ }
+
+ switch (enabledSetting) {
case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER:
case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
diff --git a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
index 0f4b681..f4b235a 100644
--- a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
+++ b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
@@ -383,11 +383,12 @@
* @param allowApks - allow restore set to include apks.
* @param info - file metadata.
* @param signatures - array of signatures parsed from backup file.
+ * @param userId - ID of the user for which restore is performed.
* @return a restore policy constant.
*/
public RestorePolicy chooseRestorePolicy(PackageManager packageManager,
boolean allowApks, FileMetadata info, Signature[] signatures,
- PackageManagerInternal pmi) {
+ PackageManagerInternal pmi, int userId) {
if (signatures == null) {
return RestorePolicy.IGNORE;
}
@@ -396,8 +397,8 @@
// Okay, got the manifest info we need...
try {
- PackageInfo pkgInfo = packageManager.getPackageInfo(
- info.packageName, PackageManager.GET_SIGNING_CERTIFICATES);
+ PackageInfo pkgInfo = packageManager.getPackageInfoAsUser(
+ info.packageName, PackageManager.GET_SIGNING_CERTIFICATES, userId);
// Fall through to IGNORE if the app explicitly disallows backup
final int flags = pkgInfo.applicationInfo.flags;
if ((flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0) {
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 0fa996e..fcd136c 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -64,6 +64,7 @@
import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.ThreadLocalWorkSource;
import android.os.Trace;
import android.os.UserHandle;
import android.os.WorkSource;
@@ -1131,23 +1132,23 @@
final IBinder mListener;
final WorkSource mWorkSource;
final int mUid;
+ final int mCreatorUid;
final String mTag;
final BroadcastStats mBroadcastStats;
final FilterStats mFilterStats;
final int mAlarmType;
- InFlight(AlarmManagerService service, PendingIntent pendingIntent, IAlarmListener listener,
- WorkSource workSource, int uid, String alarmPkg, int alarmType, String tag,
- long nowELAPSED) {
- mPendingIntent = pendingIntent;
+ InFlight(AlarmManagerService service, Alarm alarm, long nowELAPSED) {
+ mPendingIntent = alarm.operation;
mWhenElapsed = nowELAPSED;
- mListener = listener != null ? listener.asBinder() : null;
- mWorkSource = workSource;
- mUid = uid;
- mTag = tag;
- mBroadcastStats = (pendingIntent != null)
- ? service.getStatsLocked(pendingIntent)
- : service.getStatsLocked(uid, alarmPkg);
+ mListener = alarm.listener != null ? alarm.listener.asBinder() : null;
+ mWorkSource = alarm.workSource;
+ mUid = alarm.uid;
+ mCreatorUid = alarm.creatorUid;
+ mTag = alarm.statsTag;
+ mBroadcastStats = (alarm.operation != null)
+ ? service.getStatsLocked(alarm.operation)
+ : service.getStatsLocked(alarm.uid, alarm.packageName);
FilterStats fs = mBroadcastStats.filterStats.get(mTag);
if (fs == null) {
fs = new FilterStats(mBroadcastStats, mTag);
@@ -1155,7 +1156,7 @@
}
fs.lastTime = nowELAPSED;
mFilterStats = fs;
- mAlarmType = alarmType;
+ mAlarmType = alarm.type;
}
@Override
@@ -1165,6 +1166,7 @@
+ ", when=" + mWhenElapsed
+ ", workSource=" + mWorkSource
+ ", uid=" + mUid
+ + ", creatorUid=" + mCreatorUid
+ ", tag=" + mTag
+ ", broadcastStats=" + mBroadcastStats
+ ", filterStats=" + mFilterStats
@@ -3811,12 +3813,10 @@
/**
* Attribute blame for a WakeLock.
- * @param pi PendingIntent to attribute blame to if ws is null.
* @param ws WorkSource to attribute blame.
- * @param knownUid attribution uid; < 0 if we need to derive it from the PendingIntent sender
+ * @param knownUid attribution uid; < 0 values are ignored.
*/
- void setWakelockWorkSource(PendingIntent pi, WorkSource ws, int type, String tag,
- int knownUid, boolean first) {
+ void setWakelockWorkSource(WorkSource ws, int knownUid, String tag, boolean first) {
try {
mWakeLock.setHistoryTag(first ? tag : null);
@@ -3825,11 +3825,8 @@
return;
}
- final int uid = (knownUid >= 0)
- ? knownUid
- : ActivityManager.getService().getUidForIntentSender(pi.getTarget());
- if (uid >= 0) {
- mWakeLock.setWorkSource(new WorkSource(uid));
+ if (knownUid >= 0) {
+ mWakeLock.setWorkSource(new WorkSource(knownUid));
return;
}
} catch (Exception e) {
@@ -3839,6 +3836,14 @@
mWakeLock.setWorkSource(null);
}
+ private static int getAlarmAttributionUid(Alarm alarm) {
+ if (alarm.workSource != null && !alarm.workSource.isEmpty()) {
+ return alarm.workSource.getAttributionUid();
+ }
+
+ return alarm.creatorUid;
+ }
+
@VisibleForTesting
class AlarmHandler extends Handler {
public static final int ALARM_EVENT = 1;
@@ -4285,8 +4290,8 @@
// the next of our alarms is now in flight. reattribute the wakelock.
if (mInFlight.size() > 0) {
InFlight inFlight = mInFlight.get(0);
- setWakelockWorkSource(inFlight.mPendingIntent, inFlight.mWorkSource,
- inFlight.mAlarmType, inFlight.mTag, -1, false);
+ setWakelockWorkSource(inFlight.mWorkSource, inFlight.mCreatorUid, inFlight.mTag,
+ false);
} else {
// should never happen
mLog.w("Alarm wakelock still held but sent queue empty");
@@ -4369,64 +4374,70 @@
*/
@GuardedBy("mLock")
public void deliverLocked(Alarm alarm, long nowELAPSED, boolean allowWhileIdle) {
- if (alarm.operation != null) {
- // PendingIntent alarm
- mSendCount++;
+ final long workSourceToken = ThreadLocalWorkSource.setUid(
+ getAlarmAttributionUid(alarm));
+ try {
+ if (alarm.operation != null) {
+ // PendingIntent alarm
+ mSendCount++;
- try {
- alarm.operation.send(getContext(), 0,
- mBackgroundIntent.putExtra(
- Intent.EXTRA_ALARM_COUNT, alarm.count),
- mDeliveryTracker, mHandler, null,
- allowWhileIdle ? mIdleOptions : null);
- } catch (PendingIntent.CanceledException e) {
- if (alarm.repeatInterval > 0) {
- // This IntentSender is no longer valid, but this
- // is a repeating alarm, so toss it
- removeImpl(alarm.operation, null);
+ try {
+ alarm.operation.send(getContext(), 0,
+ mBackgroundIntent.putExtra(
+ Intent.EXTRA_ALARM_COUNT, alarm.count),
+ mDeliveryTracker, mHandler, null,
+ allowWhileIdle ? mIdleOptions : null);
+ } catch (PendingIntent.CanceledException e) {
+ if (alarm.repeatInterval > 0) {
+ // This IntentSender is no longer valid, but this
+ // is a repeating alarm, so toss it
+ removeImpl(alarm.operation, null);
+ }
+ // No actual delivery was possible, so the delivery tracker's
+ // 'finished' callback won't be invoked. We also don't need
+ // to do any wakelock or stats tracking, so we have nothing
+ // left to do here but go on to the next thing.
+ mSendFinishCount++;
+ return;
}
- // No actual delivery was possible, so the delivery tracker's
- // 'finished' callback won't be invoked. We also don't need
- // to do any wakelock or stats tracking, so we have nothing
- // left to do here but go on to the next thing.
- mSendFinishCount++;
- return;
- }
- } else {
- // Direct listener callback alarm
- mListenerCount++;
+ } else {
+ // Direct listener callback alarm
+ mListenerCount++;
- if (RECORD_ALARMS_IN_HISTORY) {
- if (alarm.listener == mTimeTickTrigger) {
- mTickHistory[mNextTickHistory++] = nowELAPSED;
- if (mNextTickHistory >= TICK_HISTORY_DEPTH) {
- mNextTickHistory = 0;
+ if (RECORD_ALARMS_IN_HISTORY) {
+ if (alarm.listener == mTimeTickTrigger) {
+ mTickHistory[mNextTickHistory++] = nowELAPSED;
+ if (mNextTickHistory >= TICK_HISTORY_DEPTH) {
+ mNextTickHistory = 0;
+ }
}
}
- }
- try {
- if (DEBUG_LISTENER_CALLBACK) {
- Slog.v(TAG, "Alarm to uid=" + alarm.uid
- + " listener=" + alarm.listener.asBinder());
+ try {
+ if (DEBUG_LISTENER_CALLBACK) {
+ Slog.v(TAG, "Alarm to uid=" + alarm.uid
+ + " listener=" + alarm.listener.asBinder());
+ }
+ alarm.listener.doAlarm(this);
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(AlarmHandler.LISTENER_TIMEOUT,
+ alarm.listener.asBinder()),
+ mConstants.LISTENER_TIMEOUT);
+ } catch (Exception e) {
+ if (DEBUG_LISTENER_CALLBACK) {
+ Slog.i(TAG, "Alarm undeliverable to listener "
+ + alarm.listener.asBinder(), e);
+ }
+ // As in the PendingIntent.CanceledException case, delivery of the
+ // alarm was not possible, so we have no wakelock or timeout or
+ // stats management to do. It threw before we posted the delayed
+ // timeout message, so we're done here.
+ mListenerFinishCount++;
+ return;
}
- alarm.listener.doAlarm(this);
- mHandler.sendMessageDelayed(
- mHandler.obtainMessage(AlarmHandler.LISTENER_TIMEOUT,
- alarm.listener.asBinder()),
- mConstants.LISTENER_TIMEOUT);
- } catch (Exception e) {
- if (DEBUG_LISTENER_CALLBACK) {
- Slog.i(TAG, "Alarm undeliverable to listener "
- + alarm.listener.asBinder(), e);
- }
- // As in the PendingIntent.CanceledException case, delivery of the
- // alarm was not possible, so we have no wakelock or timeout or
- // stats management to do. It threw before we posted the delayed
- // timeout message, so we're done here.
- mListenerFinishCount++;
- return;
}
+ } finally {
+ ThreadLocalWorkSource.restore(workSourceToken);
}
// The alarm is now in flight; now arrange wakelock and stats tracking
@@ -4434,15 +4445,11 @@
Slog.d(TAG, "mBroadcastRefCount -> " + (mBroadcastRefCount + 1));
}
if (mBroadcastRefCount == 0) {
- setWakelockWorkSource(alarm.operation, alarm.workSource,
- alarm.type, alarm.statsTag, (alarm.operation == null) ? alarm.uid : -1,
- true);
+ setWakelockWorkSource(alarm.workSource, alarm.creatorUid, alarm.statsTag, true);
mWakeLock.acquire();
mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 1).sendToTarget();
}
- final InFlight inflight = new InFlight(AlarmManagerService.this,
- alarm.operation, alarm.listener, alarm.workSource, alarm.uid,
- alarm.packageName, alarm.type, alarm.statsTag, nowELAPSED);
+ final InFlight inflight = new InFlight(AlarmManagerService.this, alarm, nowELAPSED);
mInFlight.add(inflight);
mBroadcastRefCount++;
if (allowWhileIdle) {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 00550d9..aa74600 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -100,7 +100,7 @@
import android.net.shared.NetworkMonitorUtils;
import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker;
-import android.net.util.NetdService;
+import android.net.shared.NetdService;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -2640,8 +2640,7 @@
}
private boolean networkRequiresValidation(NetworkAgentInfo nai) {
- return isValidationRequired(
- mDefaultRequest.networkCapabilities, nai.networkCapabilities);
+ return isValidationRequired(nai.networkCapabilities);
}
private void handleFreshlyValidatedNetwork(NetworkAgentInfo nai) {
@@ -3183,6 +3182,15 @@
return mMultinetworkPolicyTracker.getAvoidBadWifi();
}
+ @Override
+ public boolean getAvoidBadWifi() {
+ if (!checkNetworkStackPermission()) {
+ throw new SecurityException("avoidBadWifi requires NETWORK_STACK permission");
+ }
+ return avoidBadWifi();
+ }
+
+
private void rematchForAvoidBadWifiUpdate() {
rematchAllNetworksAndRequests(null, 0);
for (NetworkAgentInfo nai: mNetworkAgentInfos.values()) {
@@ -6346,6 +6354,20 @@
}
}
+ @GuardedBy("mVpns")
+ private Vpn getVpnIfOwner() {
+ final int uid = Binder.getCallingUid();
+ final int user = UserHandle.getUserId(uid);
+
+ final Vpn vpn = mVpns.get(user);
+ if (vpn == null) {
+ return null;
+ } else {
+ final VpnInfo info = vpn.getVpnInfo();
+ return (info == null || info.ownerUid != uid) ? null : vpn;
+ }
+ }
+
/**
* Caller either needs to be an active VPN, or hold the NETWORK_STACK permission
* for testing.
@@ -6354,14 +6376,10 @@
if (checkNetworkStackPermission()) {
return null;
}
- final int uid = Binder.getCallingUid();
- final int user = UserHandle.getUserId(uid);
synchronized (mVpns) {
- Vpn vpn = mVpns.get(user);
- try {
- if (vpn.getVpnInfo().ownerUid == uid) return vpn;
- } catch (NullPointerException e) {
- /* vpn is null, or VPN is not connected and getVpnInfo() is null. */
+ Vpn vpn = getVpnIfOwner();
+ if (vpn != null) {
+ return vpn;
}
}
throw new SecurityException("App must either be an active VPN or have the NETWORK_STACK "
@@ -6390,4 +6408,20 @@
return uid;
}
+
+ @Override
+ public boolean isCallerCurrentAlwaysOnVpnApp() {
+ synchronized (mVpns) {
+ Vpn vpn = getVpnIfOwner();
+ return vpn != null && vpn.getAlwaysOn();
+ }
+ }
+
+ @Override
+ public boolean isCallerCurrentAlwaysOnVpnLockdownApp() {
+ synchronized (mVpns) {
+ Vpn vpn = getVpnIfOwner();
+ return vpn != null && vpn.getLockdown();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 121a830..39030aa 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -341,6 +341,29 @@
@VisibleForTesting
static final int STATE_QUICK_DOZE_DELAY = 7;
+ private static final int ACTIVE_REASON_UNKNOWN = 0;
+ private static final int ACTIVE_REASON_MOTION = 1;
+ private static final int ACTIVE_REASON_SCREEN = 2;
+ private static final int ACTIVE_REASON_CHARGING = 3;
+ private static final int ACTIVE_REASON_UNLOCKED = 4;
+ private static final int ACTIVE_REASON_FROM_BINDER_CALL = 5;
+ private static final int ACTIVE_REASON_FORCED = 6;
+ private static final int ACTIVE_REASON_ALARM = 7;
+ @VisibleForTesting
+ static final int SET_IDLE_FACTOR_RESULT_UNINIT = -1;
+ @VisibleForTesting
+ static final int SET_IDLE_FACTOR_RESULT_IGNORED = 0;
+ @VisibleForTesting
+ static final int SET_IDLE_FACTOR_RESULT_OK = 1;
+ @VisibleForTesting
+ static final int SET_IDLE_FACTOR_RESULT_NOT_SUPPORT = 2;
+ @VisibleForTesting
+ static final int SET_IDLE_FACTOR_RESULT_INVALID = 3;
+ @VisibleForTesting
+ static final long MIN_STATE_STEP_ALARM_CHANGE = 60 * 1000;
+ @VisibleForTesting
+ static final float MIN_PRE_IDLE_FACTOR_CHANGE = 0.05f;
+
@VisibleForTesting
static String stateToString(int state) {
switch (state) {
@@ -405,6 +428,7 @@
private long mNextSensingTimeoutAlarmTime;
private long mCurIdleBudget;
private long mMaintenanceStartTime;
+ private long mIdleStartTime;
private int mActiveIdleOpCount;
private PowerManager.WakeLock mActiveIdleWakeLock; // held when there are operations in progress
@@ -415,6 +439,17 @@
private boolean mAlarmsActive;
private boolean mReportedMaintenanceActivity;
+ /* Factor to apply to INACTIVE_TIMEOUT and IDLE_AFTER_INACTIVE_TIMEOUT in order to enter
+ * STATE_IDLE faster or slower. Don't apply this to SENSING_TIMEOUT or LOCATING_TIMEOUT because:
+ * - Both of them are shorter
+ * - Device sensor might take time be to become be stabilized
+ * Also don't apply the factor if the device is in motion because device motion provides a
+ * stronger signal than a prediction algorithm.
+ */
+ private float mPreIdleFactor;
+ private float mLastPreIdleFactor;
+ private int mActiveReason;
+
public final AtomicFile mConfigFile;
private final RemoteCallbackList<IMaintenanceActivityListener> mMaintenanceActivityListeners =
@@ -760,6 +795,10 @@
* exit doze. Default = true
*/
private static final String KEY_WAIT_FOR_UNLOCK = "wait_for_unlock";
+ private static final String KEY_PRE_IDLE_FACTOR_LONG =
+ "pre_idle_factor_long";
+ private static final String KEY_PRE_IDLE_FACTOR_SHORT =
+ "pre_idle_factor_short";
/**
* This is the time, after becoming inactive, that we go in to the first
@@ -987,6 +1026,16 @@
*/
public long NOTIFICATION_WHITELIST_DURATION;
+ /**
+ * Pre idle time factor use to make idle delay longer
+ */
+ public float PRE_IDLE_FACTOR_LONG;
+
+ /**
+ * Pre idle time factor use to make idle delay shorter
+ */
+ public float PRE_IDLE_FACTOR_SHORT;
+
public boolean WAIT_FOR_UNLOCK;
private final ContentResolver mResolver;
@@ -1082,6 +1131,8 @@
NOTIFICATION_WHITELIST_DURATION = mParser.getDurationMillis(
KEY_NOTIFICATION_WHITELIST_DURATION, 30 * 1000L);
WAIT_FOR_UNLOCK = mParser.getBoolean(KEY_WAIT_FOR_UNLOCK, false);
+ PRE_IDLE_FACTOR_LONG = mParser.getFloat(KEY_PRE_IDLE_FACTOR_LONG, 1.67f);
+ PRE_IDLE_FACTOR_SHORT = mParser.getFloat(KEY_PRE_IDLE_FACTOR_SHORT, 0.33f);
}
}
@@ -1196,6 +1247,12 @@
pw.print(" "); pw.print(KEY_WAIT_FOR_UNLOCK); pw.print("=");
pw.println(WAIT_FOR_UNLOCK);
+
+ pw.print(" "); pw.print(KEY_PRE_IDLE_FACTOR_LONG); pw.print("=");
+ pw.println(PRE_IDLE_FACTOR_LONG);
+
+ pw.print(" "); pw.print(KEY_PRE_IDLE_FACTOR_SHORT); pw.print("=");
+ pw.println(PRE_IDLE_FACTOR_SHORT);
}
}
@@ -1244,6 +1301,8 @@
private static final int MSG_FINISH_IDLE_OP = 8;
private static final int MSG_REPORT_TEMP_APP_WHITELIST_CHANGED = 9;
private static final int MSG_SEND_CONSTRAINT_MONITORING = 10;
+ private static final int MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR = 11;
+ private static final int MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR = 12;
final class MyHandler extends Handler {
MyHandler(Looper looper) {
@@ -1373,6 +1432,13 @@
constraint.stopMonitoring();
}
} break;
+ case MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR: {
+ updatePreIdleFactor();
+ } break;
+ case MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR: {
+ updatePreIdleFactor();
+ maybeDoImmediateMaintenance();
+ } break;
}
}
}
@@ -1526,6 +1592,28 @@
DeviceIdleController.this.unregisterMaintenanceActivityListener(listener);
}
+ @Override public int setPreIdleTimeoutMode(int mode) {
+ getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER,
+ null);
+ long ident = Binder.clearCallingIdentity();
+ try {
+ return DeviceIdleController.this.setPreIdleTimeoutMode(mode);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override public void resetPreIdleTimeoutMode() {
+ getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER,
+ null);
+ long ident = Binder.clearCallingIdentity();
+ try {
+ DeviceIdleController.this.resetPreIdleTimeoutMode();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
@Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
DeviceIdleController.this.dump(fd, pw, args);
}
@@ -1768,9 +1856,12 @@
// Start out assuming we are charging. If we aren't, we will at least get
// a battery update the next time the level drops.
mCharging = true;
+ mActiveReason = ACTIVE_REASON_UNKNOWN;
mState = STATE_ACTIVE;
mLightState = LIGHT_STATE_ACTIVE;
mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
+ mPreIdleFactor = 1.0f;
+ mLastPreIdleFactor = 1.0f;
}
mBinderService = new BinderService();
@@ -2394,6 +2485,7 @@
public void exitIdleInternal(String reason) {
synchronized (this) {
+ mActiveReason = ACTIVE_REASON_FROM_BINDER_CALL;
becomeActiveLocked(reason, Binder.getCallingUid());
}
}
@@ -2463,6 +2555,7 @@
} else if (screenOn) {
mScreenOn = true;
if (!mForceIdle && (!mScreenLocked || !mConstants.WAIT_FOR_UNLOCK)) {
+ mActiveReason = ACTIVE_REASON_SCREEN;
becomeActiveLocked("screen", Process.myUid());
}
}
@@ -2485,6 +2578,7 @@
} else if (charging) {
mCharging = charging;
if (!mForceIdle) {
+ mActiveReason = ACTIVE_REASON_CHARGING;
becomeActiveLocked("charging", Process.myUid());
}
}
@@ -2516,6 +2610,7 @@
if (mScreenLocked != showing) {
mScreenLocked = showing;
if (mScreenOn && !mForceIdle && !mScreenLocked) {
+ mActiveReason = ACTIVE_REASON_UNLOCKED;
becomeActiveLocked("unlocked", Process.myUid());
}
}
@@ -2587,7 +2682,11 @@
mState = STATE_INACTIVE;
if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE");
resetIdleManagementLocked();
- scheduleAlarmLocked(mInactiveTimeout, false);
+ long delay = mInactiveTimeout;
+ if (shouldUseIdleTimeoutFactorLocked()) {
+ delay = (long) (mPreIdleFactor * delay);
+ }
+ scheduleAlarmLocked(delay, false);
EventLogTags.writeDeviceIdle(mState, "no activity");
}
}
@@ -2605,6 +2704,7 @@
mNextIdlePendingDelay = 0;
mNextIdleDelay = 0;
mNextLightIdleDelay = 0;
+ mIdleStartTime = 0;
cancelAlarmLocked();
cancelSensingTimeoutAlarmLocked();
cancelLocatingLocked();
@@ -2621,6 +2721,7 @@
if (mForceIdle) {
mForceIdle = false;
if (mScreenOn || mCharging) {
+ mActiveReason = ACTIVE_REASON_FORCED;
becomeActiveLocked("exit-force", Process.myUid());
}
}
@@ -2740,6 +2841,7 @@
if ((now+mConstants.MIN_TIME_TO_ALARM) > mAlarmManager.getNextWakeFromIdleTime()) {
// Whoops, there is an upcoming alarm. We don't actually want to go idle.
if (mState != STATE_ACTIVE) {
+ mActiveReason = ACTIVE_REASON_ALARM;
becomeActiveLocked("alarm", Process.myUid());
becomeInactiveIfAppropriateLocked();
}
@@ -2763,7 +2865,11 @@
// We have now been inactive long enough, it is time to start looking
// for motion and sleep some more while doing so.
startMonitoringMotionLocked();
- scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false);
+ long delay = mConstants.IDLE_AFTER_INACTIVE_TIMEOUT;
+ if (shouldUseIdleTimeoutFactorLocked()) {
+ delay = (long) (mPreIdleFactor * delay);
+ }
+ scheduleAlarmLocked(delay, false);
moveToStateLocked(STATE_IDLE_PENDING, reason);
break;
case STATE_IDLE_PENDING:
@@ -2834,6 +2940,7 @@
" ms.");
mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);
if (DEBUG) Slog.d(TAG, "Setting mNextIdleDelay = " + mNextIdleDelay);
+ mIdleStartTime = SystemClock.elapsedRealtime();
mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) {
mNextIdleDelay = mConstants.IDLE_TIMEOUT;
@@ -2934,6 +3041,127 @@
}
}
+ @VisibleForTesting
+ int setPreIdleTimeoutMode(int mode) {
+ return setPreIdleTimeoutFactor(getPreIdleTimeoutByMode(mode));
+ }
+
+ @VisibleForTesting
+ float getPreIdleTimeoutByMode(int mode) {
+ switch (mode) {
+ case PowerManager.PRE_IDLE_TIMEOUT_MODE_LONG: {
+ return mConstants.PRE_IDLE_FACTOR_LONG;
+ }
+ case PowerManager.PRE_IDLE_TIMEOUT_MODE_SHORT: {
+ return mConstants.PRE_IDLE_FACTOR_SHORT;
+ }
+ case PowerManager.PRE_IDLE_TIMEOUT_MODE_NORMAL: {
+ return 1.0f;
+ }
+ default: {
+ Slog.w(TAG, "Invalid time out factor mode: " + mode);
+ return 1.0f;
+ }
+ }
+ }
+
+ @VisibleForTesting
+ float getPreIdleTimeoutFactor() {
+ return mPreIdleFactor;
+ }
+
+ @VisibleForTesting
+ int setPreIdleTimeoutFactor(float ratio) {
+ if (!mDeepEnabled) {
+ if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: Deep Idle disable");
+ return SET_IDLE_FACTOR_RESULT_NOT_SUPPORT;
+ } else if (ratio <= MIN_PRE_IDLE_FACTOR_CHANGE) {
+ if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: Invalid input");
+ return SET_IDLE_FACTOR_RESULT_INVALID;
+ } else if (Math.abs(ratio - mPreIdleFactor) < MIN_PRE_IDLE_FACTOR_CHANGE) {
+ if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: New factor same as previous factor");
+ return SET_IDLE_FACTOR_RESULT_IGNORED;
+ }
+ synchronized (this) {
+ mLastPreIdleFactor = mPreIdleFactor;
+ mPreIdleFactor = ratio;
+ }
+ if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: " + ratio);
+ postUpdatePreIdleFactor();
+ return SET_IDLE_FACTOR_RESULT_OK;
+ }
+
+ @VisibleForTesting
+ void resetPreIdleTimeoutMode() {
+ synchronized (this) {
+ mLastPreIdleFactor = mPreIdleFactor;
+ mPreIdleFactor = 1.0f;
+ }
+ if (DEBUG) Slog.d(TAG, "resetPreIdleTimeoutMode to 1.0");
+ postResetPreIdleTimeoutFactor();
+ }
+
+ private void postUpdatePreIdleFactor() {
+ mHandler.sendEmptyMessage(MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR);
+ }
+
+ private void postResetPreIdleTimeoutFactor() {
+ mHandler.sendEmptyMessage(MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR);
+ }
+
+ @VisibleForTesting
+ void updatePreIdleFactor() {
+ synchronized (this) {
+ if (!shouldUseIdleTimeoutFactorLocked()) {
+ return;
+ }
+ if (mState == STATE_INACTIVE || mState == STATE_IDLE_PENDING) {
+ if (mNextAlarmTime == 0) {
+ return;
+ }
+ long delay = mNextAlarmTime - SystemClock.elapsedRealtime();
+ if (delay < MIN_STATE_STEP_ALARM_CHANGE) {
+ return;
+ }
+ long newDelay = (long) (delay / mLastPreIdleFactor * mPreIdleFactor);
+ if (Math.abs(delay - newDelay) < MIN_STATE_STEP_ALARM_CHANGE) {
+ return;
+ }
+ scheduleAlarmLocked(newDelay, false);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void maybeDoImmediateMaintenance() {
+ synchronized (this) {
+ if (mState == STATE_IDLE) {
+ long duration = SystemClock.elapsedRealtime() - mIdleStartTime;
+ /* Let's trgger a immediate maintenance,
+ * if it has been idle for a long time */
+ if (duration > mConstants.IDLE_TIMEOUT) {
+ scheduleAlarmLocked(0, false);
+ }
+ }
+ }
+ }
+
+ private boolean shouldUseIdleTimeoutFactorLocked() {
+ // exclude ACTIVE_REASON_MOTION, for exclude device in pocket case
+ if (mActiveReason == ACTIVE_REASON_MOTION) {
+ return false;
+ }
+ return true;
+ }
+
+ /** Must only be used in tests. */
+ @VisibleForTesting
+ void setIdleStartTimeForTest(long idleStartTime) {
+ synchronized (this) {
+ mIdleStartTime = idleStartTime;
+ }
+ }
+
void reportMaintenanceActivityIfNeededLocked() {
boolean active = mJobsActive;
if (active == mReportedMaintenanceActivity) {
@@ -2945,6 +3173,11 @@
mHandler.sendMessage(msg);
}
+ @VisibleForTesting
+ long getNextAlarmTime() {
+ return mNextAlarmTime;
+ }
+
boolean isOpsInactiveLocked() {
return mActiveIdleOpCount <= 0 && !mJobsActive && !mAlarmsActive;
}
@@ -2994,6 +3227,7 @@
scheduleReportActiveLocked(type, Process.myUid());
addEvent(EVENT_NORMAL, type);
}
+ mActiveReason = ACTIVE_REASON_MOTION;
mState = STATE_ACTIVE;
mInactiveTimeout = timeout;
mCurIdleBudget = 0;
@@ -3401,6 +3635,11 @@
+ "and any [-d] is ignored");
pw.println(" motion");
pw.println(" Simulate a motion event to bring the device out of deep doze");
+ pw.println(" pre-idle-factor [0|1|2]");
+ pw.println(" Set a new factor to idle time before step to idle"
+ + "(inactive_to and idle_after_inactive_to)");
+ pw.println(" reset-pre-idle-factor");
+ pw.println(" Reset factor to idle time to default");
}
class Shell extends ShellCommand {
@@ -3571,6 +3810,7 @@
}
}
if (becomeActive) {
+ mActiveReason = ACTIVE_REASON_FORCED;
becomeActiveLocked((arg == null ? "all" : arg) + "-disabled",
Process.myUid());
}
@@ -3820,6 +4060,52 @@
Binder.restoreCallingIdentity(token);
}
}
+ } else if ("pre-idle-factor".equals(cmd)) {
+ getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+ null);
+ synchronized (this) {
+ long token = Binder.clearCallingIdentity();
+ int ret = SET_IDLE_FACTOR_RESULT_UNINIT;
+ try {
+ String arg = shell.getNextArg();
+ boolean valid = false;
+ int mode = 0;
+ if (arg != null) {
+ mode = Integer.parseInt(arg);
+ ret = setPreIdleTimeoutMode(mode);
+ if (ret == SET_IDLE_FACTOR_RESULT_OK) {
+ pw.println("pre-idle-factor: " + mode);
+ valid = true;
+ } else if (ret == SET_IDLE_FACTOR_RESULT_NOT_SUPPORT) {
+ valid = true;
+ pw.println("Deep idle not supported");
+ } else if (ret == SET_IDLE_FACTOR_RESULT_IGNORED) {
+ valid = true;
+ pw.println("Idle timeout factor not changed");
+ }
+ }
+ if (!valid) {
+ pw.println("Unknown idle timeout factor: " + arg
+ + ",(error code: " + ret + ")");
+ }
+ } catch (NumberFormatException e) {
+ pw.println("Unknown idle timeout factor"
+ + ",(error code: " + ret + ")");
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ } else if ("reset-pre-idle-factor".equals(cmd)) {
+ getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+ null);
+ synchronized (this) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ resetPreIdleTimeoutMode();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
} else {
return shell.handleDefaultCommands(cmd);
}
@@ -4053,6 +4339,9 @@
if (mAlarmsActive) {
pw.print(" mAlarmsActive="); pw.println(mAlarmsActive);
}
+ if (Math.abs(mPreIdleFactor - 1.0f) > MIN_PRE_IDLE_FACTOR_CHANGE) {
+ pw.print(" mPreIdleFactor="); pw.println(mPreIdleFactor);
+ }
}
}
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index 126bf65..371276f 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -44,7 +44,7 @@
import android.net.Network;
import android.net.NetworkUtils;
import android.net.TrafficStats;
-import android.net.util.NetdService;
+import android.net.shared.NetdService;
import android.os.Binder;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index add5e5f..8dcc1d5 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -46,6 +46,7 @@
import android.content.pm.Signature;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.hardware.location.ActivityRecognitionHardware;
import android.location.Address;
import android.location.Criteria;
import android.location.GeocoderParams;
@@ -66,7 +67,6 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
@@ -87,11 +87,11 @@
import com.android.internal.content.PackageMonitor;
import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
-import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.location.AbstractLocationProvider;
+import com.android.server.location.ActivityRecognitionProxy;
import com.android.server.location.GeocoderProxy;
import com.android.server.location.GeofenceManager;
import com.android.server.location.GeofenceProxy;
@@ -246,7 +246,7 @@
public LocationManagerService(Context context) {
super();
mContext = context;
- mHandler = BackgroundThread.getHandler();
+ mHandler = FgThread.getHandler();
// Let the package manager query which are the default location
// providers as they get certain permissions granted by default.
@@ -736,6 +736,25 @@
Slog.d(TAG, "Unable to bind FLP Geofence proxy.");
}
+ // bind to hardware activity recognition
+ boolean activityRecognitionHardwareIsSupported = ActivityRecognitionHardware.isSupported();
+ ActivityRecognitionHardware activityRecognitionHardware = null;
+ if (activityRecognitionHardwareIsSupported) {
+ activityRecognitionHardware = ActivityRecognitionHardware.getInstance(mContext);
+ } else {
+ Slog.d(TAG, "Hardware Activity-Recognition not supported.");
+ }
+ ActivityRecognitionProxy proxy = ActivityRecognitionProxy.createAndBind(
+ mContext,
+ activityRecognitionHardwareIsSupported,
+ activityRecognitionHardware,
+ com.android.internal.R.bool.config_enableActivityRecognitionHardwareOverlay,
+ com.android.internal.R.string.config_activityRecognitionHardwarePackageName,
+ com.android.internal.R.array.config_locationProviderPackageNames);
+ if (proxy == null) {
+ Slog.d(TAG, "Unable to bind ActivityRecognitionProxy.");
+ }
+
String[] testProviderStrings = resources.getStringArray(
com.android.internal.R.array.config_testLocationProviders);
for (String testProviderString : testProviderStrings) {
@@ -830,6 +849,15 @@
mAllowed = !mIsManagedBySettings;
mEnabled = false;
mProperties = null;
+
+ if (mIsManagedBySettings) {
+ // since we assume providers are disabled by default
+ Settings.Secure.putStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+ "-" + mName,
+ mCurrentUserId);
+ }
}
@GuardedBy("mLock")
@@ -954,7 +982,7 @@
public void onReportLocation(Location location) {
// no security check necessary because this is coming from an internal-only interface
// move calls coming from below LMS onto a different thread to avoid deadlock
- runInternal(() -> {
+ mHandler.post(() -> {
synchronized (mLock) {
handleLocationChangedLocked(location, this);
}
@@ -965,7 +993,7 @@
@Override
public void onReportLocation(List<Location> locations) {
// move calls coming from below LMS onto a different thread to avoid deadlock
- runInternal(() -> {
+ mHandler.post(() -> {
synchronized (mLock) {
LocationProvider gpsProvider = getLocationProviderLocked(GPS_PROVIDER);
if (gpsProvider == null || !gpsProvider.isUseableLocked()) {
@@ -991,7 +1019,7 @@
@Override
public void onSetEnabled(boolean enabled) {
// move calls coming from below LMS onto a different thread to avoid deadlock
- runInternal(() -> {
+ mHandler.post(() -> {
synchronized (mLock) {
if (enabled == mEnabled) {
return;
@@ -1113,16 +1141,6 @@
mUseable = false;
updateProviderUseableLocked(this);
}
-
- // binder transactions coming from below LMS (ie location providers) need to be moved onto
- // a different thread to avoid potential deadlock as code reenters the location providers
- private void runInternal(Runnable runnable) {
- if (Looper.myLooper() == mHandler.getLooper()) {
- runnable.run();
- } else {
- mHandler.post(runnable);
- }
- }
}
private class MockLocationProvider extends LocationProvider {
@@ -1278,7 +1296,11 @@
// are high power (has a high power provider with an interval under a threshold).
for (UpdateRecord updateRecord : mUpdateRecords.values()) {
LocationProvider provider = getLocationProviderLocked(updateRecord.mProvider);
- if (provider == null || !provider.isUseableLocked()) {
+ if (provider == null) {
+ continue;
+ }
+ if (!provider.isUseableLocked()
+ && !updateRecord.mRealRequest.isLocationSettingsIgnored()) {
continue;
}
@@ -1960,14 +1982,22 @@
ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
if (records != null) {
for (UpdateRecord record : records) {
- if (isCurrentProfileLocked(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
- // Sends a notification message to the receiver
- if (!record.mReceiver.callProviderEnabledLocked(provider.getName(), useable)) {
- if (deadReceivers == null) {
- deadReceivers = new ArrayList<>();
- }
- deadReceivers.add(record.mReceiver);
+ if (!isCurrentProfileLocked(
+ UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
+ continue;
+ }
+
+ // requests that ignore location settings will never provider notifications
+ if (record.mRealRequest.isLocationSettingsIgnored()) {
+ continue;
+ }
+
+ // Sends a notification message to the receiver
+ if (!record.mReceiver.callProviderEnabledLocked(provider.getName(), useable)) {
+ if (deadReceivers == null) {
+ deadReceivers = new ArrayList<>();
}
+ deadReceivers.add(record.mReceiver);
}
}
}
@@ -2007,39 +2037,47 @@
Binder.restoreCallingIdentity(identity);
}
- if (provider.isUseableLocked() && records != null && !records.isEmpty()) {
+ if (records != null && !records.isEmpty()) {
// initialize the low power mode to true and set to false if any of the records requires
providerRequest.lowPowerMode = true;
for (UpdateRecord record : records) {
- if (isCurrentProfileLocked(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
- if (checkLocationAccess(
- record.mReceiver.mIdentity.mPid,
- record.mReceiver.mIdentity.mUid,
- record.mReceiver.mIdentity.mPackageName,
- record.mReceiver.mAllowedResolutionLevel)) {
- LocationRequest locationRequest = record.mRealRequest;
- long interval = locationRequest.getInterval();
+ if (!isCurrentProfileLocked(
+ UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
+ continue;
+ }
+ if (!checkLocationAccess(
+ record.mReceiver.mIdentity.mPid,
+ record.mReceiver.mIdentity.mUid,
+ record.mReceiver.mIdentity.mPackageName,
+ record.mReceiver.mAllowedResolutionLevel)) {
+ continue;
+ }
+ if (!provider.isUseableLocked()
+ && !record.mRealRequest.isLocationSettingsIgnored()) {
+ continue;
+ }
- if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) {
- if (!record.mIsForegroundUid) {
- interval = Math.max(interval, backgroundThrottleInterval);
- }
- if (interval != locationRequest.getInterval()) {
- locationRequest = new LocationRequest(locationRequest);
- locationRequest.setInterval(interval);
- }
- }
+ LocationRequest locationRequest = record.mRealRequest;
+ long interval = locationRequest.getInterval();
- record.mRequest = locationRequest;
- providerRequest.locationRequests.add(locationRequest);
- if (!locationRequest.isLowPowerMode()) {
- providerRequest.lowPowerMode = false;
- }
- if (interval < providerRequest.interval) {
- providerRequest.reportLocation = true;
- providerRequest.interval = interval;
- }
+ if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) {
+ if (!record.mIsForegroundUid) {
+ interval = Math.max(interval, backgroundThrottleInterval);
}
+ if (interval != locationRequest.getInterval()) {
+ locationRequest = new LocationRequest(locationRequest);
+ locationRequest.setInterval(interval);
+ }
+ }
+
+ record.mRequest = locationRequest;
+ providerRequest.locationRequests.add(locationRequest);
+ if (!locationRequest.isLowPowerMode()) {
+ providerRequest.lowPowerMode = false;
+ }
+ if (interval < providerRequest.interval) {
+ providerRequest.reportLocation = true;
+ providerRequest.interval = interval;
}
}
@@ -2307,6 +2345,10 @@
mContext.enforceCallingOrSelfPermission(
Manifest.permission.UPDATE_APP_OPS_STATS, null);
}
+ if (request.isLocationSettingsIgnored()) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.WRITE_SECURE_SETTINGS, null);
+ }
boolean callerHasLocationHardwarePermission =
mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
== PERMISSION_GRANTED;
@@ -2376,12 +2418,14 @@
oldRecord.disposeLocked(false);
}
- if (provider.isUseableLocked()) {
- applyRequirementsLocked(name);
- } else {
- // Notify the listener that updates are currently disabled
+ if (!provider.isUseableLocked() && !request.isLocationSettingsIgnored()) {
+ // Notify the listener that updates are currently disabled - but only if the request
+ // does not ignore location settings
receiver.callProviderEnabledLocked(name, false);
}
+
+ applyRequirementsLocked(name);
+
// Update the monitoring here just in case multiple location requests were added to the
// same receiver (this request may be high power and the initial might not have been).
receiver.updateMonitoring(true);
@@ -2865,33 +2909,11 @@
long identity = Binder.clearCallingIdentity();
try {
- boolean enabled;
- try {
- enabled = Settings.Secure.getIntForUser(
+ return Settings.Secure.getIntForUser(
mContext.getContentResolver(),
Settings.Secure.LOCATION_MODE,
+ Settings.Secure.LOCATION_MODE_OFF,
userId) != Settings.Secure.LOCATION_MODE_OFF;
- } catch (Settings.SettingNotFoundException e) {
- // OS upgrade case where mode isn't set yet
- enabled = !TextUtils.isEmpty(Settings.Secure.getStringForUser(
- mContext.getContentResolver(),
- Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
- userId));
-
- try {
- Settings.Secure.putIntForUser(
- mContext.getContentResolver(),
- Settings.Secure.LOCATION_MODE,
- enabled
- ? Settings.Secure.LOCATION_MODE_HIGH_ACCURACY
- : Settings.Secure.LOCATION_MODE_OFF,
- userId);
- } catch (RuntimeException ex) {
- // any problem with writing should not be propagated
- Slog.e(TAG, "error updating location mode", ex);
- }
- }
- return enabled;
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -2981,26 +3003,29 @@
return;
}
- if (!provider.isPassiveLocked()) {
- // notify passive provider of the new location
- mPassiveProvider.updateLocation(location);
+ // only notify passive provider and update last location for locations that come from
+ // useable providers
+ if (provider.isUseableLocked()) {
+ if (!provider.isPassiveLocked()) {
+ mPassiveProvider.updateLocation(location);
+ }
}
if (D) Log.d(TAG, "incoming location: " + location);
long now = SystemClock.elapsedRealtime();
- updateLastLocationLocked(location, provider.getName());
- // mLastLocation should have been updated from the updateLastLocationLocked call above.
- Location lastLocation = mLastLocation.get(provider.getName());
- if (lastLocation == null) {
- Log.e(TAG, "handleLocationChangedLocked() updateLastLocation failed");
- return;
+ if (provider.isUseableLocked()) {
+ updateLastLocationLocked(location, provider.getName());
}
// Update last known coarse interval location if enough time has passed.
- Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(provider.getName());
+ Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(
+ provider.getName());
if (lastLocationCoarseInterval == null) {
lastLocationCoarseInterval = new Location(location);
- mLastLocationCoarseInterval.put(provider.getName(), lastLocationCoarseInterval);
+
+ if (provider.isUseableLocked()) {
+ mLastLocationCoarseInterval.put(provider.getName(), lastLocationCoarseInterval);
+ }
}
long timeDiffNanos = location.getElapsedRealtimeNanos()
- lastLocationCoarseInterval.getElapsedRealtimeNanos();
@@ -3031,6 +3056,10 @@
Receiver receiver = r.mReceiver;
boolean receiverDead = false;
+ if (!provider.isUseableLocked() && !r.mRealRequest.isLocationSettingsIgnored()) {
+ continue;
+ }
+
int receiverUserId = UserHandle.getUserId(receiver.mIdentity.mUid);
if (!isCurrentProfileLocked(receiverUserId)
&& !isLocationProviderLocked(receiver.mIdentity.mUid)) {
@@ -3066,7 +3095,7 @@
if (receiver.mAllowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
notifyLocation = coarseLocation; // use coarse location
} else {
- notifyLocation = lastLocation; // use fine location
+ notifyLocation = location; // use fine location
}
if (notifyLocation != null) {
Location lastLoc = r.mLastFixBroadcast;
diff --git a/services/core/java/com/android/server/LooperStatsService.java b/services/core/java/com/android/server/LooperStatsService.java
index cee98c1..4a8706e 100644
--- a/services/core/java/com/android/server/LooperStatsService.java
+++ b/services/core/java/com/android/server/LooperStatsService.java
@@ -122,6 +122,10 @@
"exception_count"));
pw.println(header);
for (LooperStats.ExportedEntry entry : entries) {
+ if (entry.messageName.startsWith(LooperStats.DEBUG_ENTRY_PREFIX)) {
+ // Do not dump debug entries.
+ continue;
+ }
pw.printf("%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
packageMap.mapUid(entry.workSourceUid),
entry.threadName,
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index aed0684..89ff338 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -39,14 +39,6 @@
import static android.net.NetworkStats.UID_ALL;
import static android.net.TrafficStats.UID_TETHERING;
-import static com.android.server.NetworkManagementService.NetdResponseCode.ClatdStatusResult;
-import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceGetCfgResult;
-import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceListResult;
-import static com.android.server.NetworkManagementService.NetdResponseCode.IpFwdStatusResult;
-import static com.android.server.NetworkManagementService.NetdResponseCode.TetherDnsFwdTgtListResult;
-import static com.android.server.NetworkManagementService.NetdResponseCode.TetherInterfaceListResult;
-import static com.android.server.NetworkManagementService.NetdResponseCode.TetherStatusResult;
-import static com.android.server.NetworkManagementService.NetdResponseCode.TetheringStatsListResult;
import static com.android.server.NetworkManagementService.NetdResponseCode.TtyListResult;
import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED;
@@ -54,7 +46,9 @@
import android.app.ActivityManager;
import android.content.Context;
import android.net.ConnectivityManager;
+import android.net.InetAddresses;
import android.net.INetd;
+import android.net.INetdUnsolicitedEventListener;
import android.net.INetworkManagementEventObserver;
import android.net.ITetheringStatsProvider;
import android.net.InterfaceConfiguration;
@@ -68,7 +62,7 @@
import android.net.RouteInfo;
import android.net.TetherStatsParcel;
import android.net.UidRange;
-import android.net.util.NetdService;
+import android.net.shared.NetdService;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Handler;
@@ -213,6 +207,8 @@
private INetd mNetdService;
+ private final NetdUnsolicitedEventListener mNetdUnsolicitedEventListener;
+
private IBatteryStats mBatteryStats;
private final Thread mThread;
@@ -330,6 +326,8 @@
mDaemonHandler = new Handler(FgThread.get().getLooper());
+ mNetdUnsolicitedEventListener = new NetdUnsolicitedEventListener();
+
// Add ourself to the Watchdog monitors.
Watchdog.getInstance().addMonitor(this);
@@ -348,6 +346,7 @@
mFgHandler = null;
mThread = null;
mServices = null;
+ mNetdUnsolicitedEventListener = null;
}
static NetworkManagementService create(Context context, String socket, SystemServices services)
@@ -454,7 +453,6 @@
// our sanity-checking state.
mActiveAlerts.remove(iface);
mActiveQuotas.remove(iface);
-
invokeForAllObservers(o -> o.interfaceRemoved(iface));
}
@@ -560,7 +558,7 @@
return;
}
// No current code examines the interface parameter in a global alert. Just pass null.
- notifyLimitReached(LIMIT_GLOBAL_ALERT, null);
+ mDaemonHandler.post(() -> notifyLimitReached(LIMIT_GLOBAL_ALERT, null));
}
}
@@ -591,6 +589,12 @@
private void connectNativeNetdService() {
mNetdService = mServices.getNetd();
+ try {
+ mNetdService.registerUnsolicitedEventListener(mNetdUnsolicitedEventListener);
+ if (DBG) Slog.d(TAG, "Register unsolicited event listener");
+ } catch (RemoteException | ServiceSpecificException e) {
+ Slog.e(TAG, "Failed to set Netd unsolicited event listener " + e);
+ }
}
/**
@@ -717,14 +721,96 @@
/**
* Notify our observers of a route change.
*/
- private void notifyRouteChange(String action, RouteInfo route) {
- if (action.equals("updated")) {
+ private void notifyRouteChange(boolean updated, RouteInfo route) {
+ if (updated) {
invokeForAllObservers(o -> o.routeUpdated(route));
} else {
invokeForAllObservers(o -> o.routeRemoved(route));
}
}
+ private class NetdUnsolicitedEventListener extends INetdUnsolicitedEventListener.Stub {
+ @Override
+ public void onInterfaceClassActivityChanged(boolean isActive,
+ int label, long timestamp, int uid) throws RemoteException {
+ final long timestampNanos;
+ if (timestamp <= 0) {
+ timestampNanos = SystemClock.elapsedRealtimeNanos();
+ } else {
+ timestampNanos = timestamp;
+ }
+ mDaemonHandler.post(() -> notifyInterfaceClassActivity(label,
+ isActive ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH
+ : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+ timestampNanos, uid, false));
+ }
+
+ @Override
+ public void onQuotaLimitReached(String alertName, String ifName)
+ throws RemoteException {
+ mDaemonHandler.post(() -> notifyLimitReached(alertName, ifName));
+ }
+
+ @Override
+ public void onInterfaceDnsServerInfo(String ifName,
+ long lifetime, String[] servers) throws RemoteException {
+ mDaemonHandler.post(() -> notifyInterfaceDnsServerInfo(ifName, lifetime, servers));
+ }
+
+ @Override
+ public void onInterfaceAddressUpdated(String addr,
+ String ifName, int flags, int scope) throws RemoteException {
+ final LinkAddress address = new LinkAddress(addr, flags, scope);
+ mDaemonHandler.post(() -> notifyAddressUpdated(ifName, address));
+ }
+
+ @Override
+ public void onInterfaceAddressRemoved(String addr,
+ String ifName, int flags, int scope) throws RemoteException {
+ final LinkAddress address = new LinkAddress(addr, flags, scope);
+ mDaemonHandler.post(() -> notifyAddressRemoved(ifName, address));
+ }
+
+ @Override
+ public void onInterfaceAdded(String ifName) throws RemoteException {
+ mDaemonHandler.post(() -> notifyInterfaceAdded(ifName));
+ }
+
+ @Override
+ public void onInterfaceRemoved(String ifName) throws RemoteException {
+ mDaemonHandler.post(() -> notifyInterfaceRemoved(ifName));
+ }
+
+ @Override
+ public void onInterfaceChanged(String ifName, boolean up)
+ throws RemoteException {
+ mDaemonHandler.post(() -> notifyInterfaceStatusChanged(ifName, up));
+ }
+
+ @Override
+ public void onInterfaceLinkStateChanged(String ifName, boolean up)
+ throws RemoteException {
+ mDaemonHandler.post(() -> notifyInterfaceLinkStateChanged(ifName, up));
+ }
+
+ @Override
+ public void onRouteChanged(boolean updated,
+ String route, String gateway, String ifName) throws RemoteException {
+ final RouteInfo processRoute = new RouteInfo(new IpPrefix(route),
+ ("".equals(gateway)) ? null : InetAddresses.parseNumericAddress(gateway),
+ ifName);
+ mDaemonHandler.post(() -> notifyRouteChange(updated, processRoute));
+ }
+
+ @Override
+ public void onStrictCleartextDetected(int uid, String hex) throws RemoteException {
+ // Don't need to post to mDaemonHandler because the only thing
+ // that notifyCleartextNetwork does is post to a handler
+ ActivityManager.getService().notifyCleartextNetwork(uid,
+ HexDump.hexStringToByteArray(hex));
+ }
+ }
+
//
// Netd Callback handling
//
@@ -912,7 +998,7 @@
InetAddress gateway = null;
if (via != null) gateway = InetAddress.parseNumericAddress(via);
RouteInfo route = new RouteInfo(new IpPrefix(cooked[3]), gateway, dev);
- notifyRouteChange(cooked[2], route);
+ notifyRouteChange(cooked[2].equals("updated"), route);
return true;
} catch (IllegalArgumentException e) {}
}
@@ -1375,13 +1461,9 @@
if (ConnectivityManager.isNetworkTypeMobile(type)) {
mNetworkActive = false;
}
- mDaemonHandler.post(new Runnable() {
- @Override public void run() {
- notifyInterfaceClassActivity(type,
- DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
- SystemClock.elapsedRealtimeNanos(), -1, false);
- }
- });
+ mDaemonHandler.post(() -> notifyInterfaceClassActivity(type,
+ DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+ SystemClock.elapsedRealtimeNanos(), -1, false));
}
}
@@ -1404,13 +1486,9 @@
throw new IllegalStateException(e);
}
mActiveIdleTimers.remove(iface);
- mDaemonHandler.post(new Runnable() {
- @Override public void run() {
- notifyInterfaceClassActivity(params.type,
- DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
- SystemClock.elapsedRealtimeNanos(), -1, false);
- }
- });
+ mDaemonHandler.post(() -> notifyInterfaceClassActivity(params.type,
+ DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+ SystemClock.elapsedRealtimeNanos(), -1, false));
}
}
diff --git a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
index 1e9a007..190fff1 100644
--- a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
+++ b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
@@ -31,6 +31,19 @@
*/
byte[] getFrpCredentialHandle();
+ /** Stores the data used to enable the Test Harness Mode after factory-resetting. */
+ void setTestHarnessModeData(byte[] data);
+
+ /**
+ * Retrieves the data used to place the device into Test Harness Mode.
+ *
+ * @throws IllegalStateException if the underlying storage is corrupt or inaccessible.
+ */
+ byte[] getTestHarnessModeData();
+
+ /** Clear out the Test Harness Mode data. */
+ void clearTestHarnessModeData();
+
/** Update the OEM unlock enabled bit, bypassing user restriction checks. */
void forceOemUnlockEnabled(boolean enabled);
}
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 21093b9..bd5ad96 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -16,6 +16,8 @@
package com.android.server;
+import static com.android.internal.util.Preconditions.checkArgument;
+
import android.Manifest;
import android.app.ActivityManager;
import android.content.Context;
@@ -28,12 +30,10 @@
import android.os.UserManager;
import android.service.persistentdata.IPersistentDataBlockService;
import android.service.persistentdata.PersistentDataBlockManager;
-import android.util.Log;
import android.util.Slog;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
import libcore.io.IoUtils;
@@ -65,6 +65,40 @@
*
* Clients can read any number of bytes from the currently written block up to its total size by
* invoking {@link IPersistentDataBlockService#read}
+ *
+ * The persistent data block is currently laid out as follows:
+ * | ---------BEGINNING OF PARTITION-------------|
+ * | Partition digest (32 bytes) |
+ * | --------------------------------------------|
+ * | PARTITION_TYPE_MARKER (4 bytes) |
+ * | --------------------------------------------|
+ * | FRP data block length (4 bytes) |
+ * | --------------------------------------------|
+ * | FRP data (variable length) |
+ * | --------------------------------------------|
+ * | ... |
+ * | --------------------------------------------|
+ * | Test mode data block (10000 bytes) |
+ * | --------------------------------------------|
+ * | | Test mode data length (4 bytes) |
+ * | --------------------------------------------|
+ * | | Test mode data (variable length) |
+ * | | ... |
+ * | --------------------------------------------|
+ * | FRP credential handle block (1000 bytes) |
+ * | --------------------------------------------|
+ * | | FRP credential handle length (4 bytes)|
+ * | --------------------------------------------|
+ * | | FRP credential handle (variable len) |
+ * | | ... |
+ * | --------------------------------------------|
+ * | OEM Unlock bit (1 byte) |
+ * | ---------END OF PARTITION-------------------|
+ *
+ * TODO: now that the persistent partition contains several blocks, next time someone wants a new
+ * block, we should look at adding more generic block definitions and get rid of the various raw
+ * XXX_RESERVED_SIZE and XXX_DATA_SIZE constants. That will ensure the code is easier to maintain
+ * and less likely to introduce out-of-bounds read/write.
*/
public class PersistentDataBlockService extends SystemService {
private static final String TAG = PersistentDataBlockService.class.getSimpleName();
@@ -73,10 +107,16 @@
private static final int HEADER_SIZE = 8;
// Magic number to mark block device as adhering to the format consumed by this service
private static final int PARTITION_TYPE_MARKER = 0x19901873;
- /** Size of the block reserved for FPR credential, including 4 bytes for the size header. */
+ /** Size of the block reserved for FRP credential, including 4 bytes for the size header. */
private static final int FRP_CREDENTIAL_RESERVED_SIZE = 1000;
/** Maximum size of the FRP credential handle that can be stored. */
private static final int MAX_FRP_CREDENTIAL_HANDLE_SIZE = FRP_CREDENTIAL_RESERVED_SIZE - 4;
+ /**
+ * Size of the block reserved for Test Harness Mode data, including 4 bytes for the size header.
+ */
+ private static final int TEST_MODE_RESERVED_SIZE = 10000;
+ /** Maximum size of the Test Harness Mode data that can be stored. */
+ private static final int MAX_TEST_MODE_DATA_SIZE = TEST_MODE_RESERVED_SIZE - 4;
// Limit to 100k as blocks larger than this might cause strain on Binder.
private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
@@ -221,6 +261,14 @@
return mBlockDeviceSize;
}
+ private long getFrpCredentialDataOffset() {
+ return getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE;
+ }
+
+ private long getTestHarnessModeDataOffset() {
+ return getFrpCredentialDataOffset() - TEST_MODE_RESERVED_SIZE;
+ }
+
private boolean enforceChecksumValidity() {
byte[] storedDigest = new byte[DIGEST_SIZE_BYTES];
@@ -383,7 +431,7 @@
private long doGetMaximumDataBlockSize() {
long actualSize = getBlockDeviceSize() - HEADER_SIZE - DIGEST_SIZE_BYTES
- - FRP_CREDENTIAL_RESERVED_SIZE - 1;
+ - TEST_MODE_RESERVED_SIZE - FRP_CREDENTIAL_RESERVED_SIZE - 1;
return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
}
@@ -391,6 +439,13 @@
private native int nativeWipe(String path);
private final IBinder mService = new IPersistentDataBlockService.Stub() {
+
+ /**
+ * Write the data to the persistent data block.
+ *
+ * @return a positive integer of the number of bytes that were written if successful,
+ * otherwise a negative integer indicating there was a problem
+ */
@Override
public int write(byte[] data) throws RemoteException {
enforceUid(Binder.getCallingUid());
@@ -597,12 +652,51 @@
@Override
public void setFrpCredentialHandle(byte[] handle) {
- Preconditions.checkArgument(handle == null || handle.length > 0,
- "handle must be null or non-empty");
- Preconditions.checkArgument(handle == null
- || handle.length <= MAX_FRP_CREDENTIAL_HANDLE_SIZE,
- "handle must not be longer than " + MAX_FRP_CREDENTIAL_HANDLE_SIZE);
+ writeInternal(handle, getFrpCredentialDataOffset(), MAX_FRP_CREDENTIAL_HANDLE_SIZE);
+ }
+ @Override
+ public byte[] getFrpCredentialHandle() {
+ return readInternal(getFrpCredentialDataOffset(), MAX_FRP_CREDENTIAL_HANDLE_SIZE);
+ }
+
+ @Override
+ public void setTestHarnessModeData(byte[] data) {
+ writeInternal(data, getTestHarnessModeDataOffset(), MAX_TEST_MODE_DATA_SIZE);
+ }
+
+ @Override
+ public byte[] getTestHarnessModeData() {
+ byte[] data = readInternal(getTestHarnessModeDataOffset(), MAX_TEST_MODE_DATA_SIZE);
+ if (data == null) {
+ return new byte[0];
+ }
+ return data;
+ }
+
+ @Override
+ public void clearTestHarnessModeData() {
+ int size = Math.min(MAX_TEST_MODE_DATA_SIZE, getTestHarnessModeData().length) + 4;
+ writeDataBuffer(getTestHarnessModeDataOffset(), ByteBuffer.allocate(size));
+ }
+
+ private void writeInternal(byte[] data, long offset, int dataLength) {
+ checkArgument(data == null || data.length > 0, "data must be null or non-empty");
+ checkArgument(
+ data == null || data.length <= dataLength,
+ "data must not be longer than " + dataLength);
+
+ ByteBuffer dataBuffer = ByteBuffer.allocate(dataLength + 4);
+ dataBuffer.putInt(data == null ? 0 : data.length);
+ if (data != null) {
+ dataBuffer.put(data);
+ }
+ dataBuffer.flip();
+
+ writeDataBuffer(offset, dataBuffer);
+ }
+
+ private void writeDataBuffer(long offset, ByteBuffer dataBuffer) {
FileOutputStream outputStream;
try {
outputStream = new FileOutputStream(new File(mDataBlockFile));
@@ -610,25 +704,15 @@
Slog.e(TAG, "partition not available", e);
return;
}
-
- ByteBuffer data = ByteBuffer.allocate(FRP_CREDENTIAL_RESERVED_SIZE);
- data.putInt(handle == null ? 0 : handle.length);
- if (handle != null) {
- data.put(handle);
- }
- data.flip();
-
synchronized (mLock) {
if (!mIsWritable) {
IoUtils.closeQuietly(outputStream);
return;
}
-
try {
FileChannel channel = outputStream.getChannel();
-
- channel.position(getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE);
- channel.write(data);
+ channel.position(offset);
+ channel.write(dataBuffer);
outputStream.flush();
} catch (IOException e) {
Slog.e(TAG, "unable to access persistent partition", e);
@@ -641,8 +725,7 @@
}
}
- @Override
- public byte[] getFrpCredentialHandle() {
+ private byte[] readInternal(long offset, int maxLength) {
if (!enforceChecksumValidity()) {
throw new IllegalStateException("invalid checksum");
}
@@ -652,14 +735,14 @@
inputStream = new DataInputStream(
new FileInputStream(new File(mDataBlockFile)));
} catch (FileNotFoundException e) {
- throw new IllegalStateException("frp partition not available");
+ throw new IllegalStateException("persistent partition not available");
}
try {
synchronized (mLock) {
- inputStream.skip(getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE);
+ inputStream.skip(offset);
int length = inputStream.readInt();
- if (length <= 0 || length > MAX_FRP_CREDENTIAL_HANDLE_SIZE) {
+ if (length <= 0 || length > maxLength) {
return null;
}
byte[] bytes = new byte[length];
@@ -667,7 +750,7 @@
return bytes;
}
} catch (IOException e) {
- throw new IllegalStateException("frp handle not readable", e);
+ throw new IllegalStateException("persistent partition not readable", e);
} finally {
IoUtils.closeQuietly(inputStream);
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 7731c04..9d810cd 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1706,8 +1706,8 @@
int uid, String packageName, int[] ops) {
long maxTime = 0;
final List<AppOpsManager.PackageOps> pkgs = manager.getOpsForPackage(uid, packageName, ops);
- for (AppOpsManager.PackageOps pkg : CollectionUtils.defeatNullable(pkgs)) {
- for (AppOpsManager.OpEntry op : CollectionUtils.defeatNullable(pkg.getOps())) {
+ for (AppOpsManager.PackageOps pkg : CollectionUtils.emptyIfNull(pkgs)) {
+ for (AppOpsManager.OpEntry op : CollectionUtils.emptyIfNull(pkg.getOps())) {
maxTime = Math.max(maxTime, op.getLastAccessTime());
}
}
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index ccead6c..c7b9a3c 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -18,6 +18,7 @@
import static com.android.internal.util.dump.DumpUtils.writeStringIfNotNull;
+import android.annotation.TestApi;
import android.app.ActivityManager;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
@@ -26,6 +27,7 @@
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.Resources;
+import android.debug.AdbProtoEnums;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.os.Environment;
@@ -37,21 +39,36 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.service.adb.AdbDebuggingManagerProto;
+import android.util.AtomicFile;
import android.util.Base64;
import android.util.Slog;
+import android.util.StatsLog;
+import android.util.Xml;
import com.android.internal.R;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.server.FgThread;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
/**
* Provides communication to the Android Debug Bridge daemon to allow, deny, or clear public keysi
@@ -63,6 +80,7 @@
private static final String ADBD_SOCKET = "adbd";
private static final String ADB_DIRECTORY = "misc/adb";
+ // This file contains keys that will always be allowed to connect to the device via adb.
private static final String ADB_KEYS_FILE = "adb_keys";
private static final int BUFFER_SIZE = 4096;
@@ -71,12 +89,25 @@
private AdbDebuggingThread mThread;
private boolean mAdbEnabled = false;
private String mFingerprints;
+ private String mConnectedKey;
+ private String mConfirmComponent;
public AdbDebuggingManager(Context context) {
mHandler = new AdbDebuggingHandler(FgThread.get().getLooper());
mContext = context;
}
+ /**
+ * Constructor that accepts the component to be invoked to confirm if the user wants to allow
+ * an adb connection from the key.
+ */
+ @TestApi
+ protected AdbDebuggingManager(Context context, String confirmComponent) {
+ mHandler = new AdbDebuggingHandler(FgThread.get().getLooper());
+ mContext = context;
+ mConfirmComponent = confirmComponent;
+ }
+
class AdbDebuggingThread extends Thread {
private boolean mStopped;
private LocalSocket mSocket;
@@ -135,7 +166,9 @@
byte[] buffer = new byte[BUFFER_SIZE];
while (true) {
int count = mInputStream.read(buffer);
- if (count < 0) {
+ // if less than 2 bytes are read the if statements below will throw an
+ // IndexOutOfBoundsException.
+ if (count < 2) {
break;
}
@@ -146,6 +179,11 @@
AdbDebuggingHandler.MESSAGE_ADB_CONFIRM);
msg.obj = key;
mHandler.sendMessage(msg);
+ } else if (buffer[0] == 'D' && buffer[1] == 'C') {
+ Slog.d(TAG, "Received disconnected message");
+ Message msg = mHandler.obtainMessage(
+ AdbDebuggingHandler.MESSAGE_ADB_DISCONNECT);
+ mHandler.sendMessage(msg);
} else {
Slog.e(TAG, "Wrong message: "
+ (new String(Arrays.copyOfRange(buffer, 0, 2))));
@@ -202,15 +240,41 @@
}
class AdbDebuggingHandler extends Handler {
- private static final int MESSAGE_ADB_ENABLED = 1;
- private static final int MESSAGE_ADB_DISABLED = 2;
- private static final int MESSAGE_ADB_ALLOW = 3;
- private static final int MESSAGE_ADB_DENY = 4;
- private static final int MESSAGE_ADB_CONFIRM = 5;
- private static final int MESSAGE_ADB_CLEAR = 6;
+ // The time to schedule the job to keep the key store updated with a currently connected
+ // key. This job is required because a deveoper could keep a device connected to their
+ // system beyond the time within which a subsequent connection is allowed. But since the
+ // last connection time is only written when a device is connected and disconnected then
+ // if the device is rebooted while connected to the development system it would appear as
+ // though the adb grant for the system is no longer authorized and the developer would need
+ // to manually allow the connection again.
+ private static final long UPDATE_KEY_STORE_JOB_INTERVAL = 86400000;
+
+ static final int MESSAGE_ADB_ENABLED = 1;
+ static final int MESSAGE_ADB_DISABLED = 2;
+ static final int MESSAGE_ADB_ALLOW = 3;
+ static final int MESSAGE_ADB_DENY = 4;
+ static final int MESSAGE_ADB_CONFIRM = 5;
+ static final int MESSAGE_ADB_CLEAR = 6;
+ static final int MESSAGE_ADB_DISCONNECT = 7;
+ static final int MESSAGE_ADB_PERSIST_KEY_STORE = 8;
+ static final int MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME = 9;
+
+ private AdbKeyStore mAdbKeyStore;
AdbDebuggingHandler(Looper looper) {
super(looper);
+ mAdbKeyStore = new AdbKeyStore();
+ }
+
+ /**
+ * Constructor that accepts the AdbDebuggingThread to which responses should be sent
+ * and the AdbKeyStore to be used to store the temporary grants.
+ */
+ @TestApi
+ AdbDebuggingHandler(Looper looper, AdbDebuggingThread thread, AdbKeyStore adbKeyStore) {
+ super(looper);
+ mThread = thread;
+ mAdbKeyStore = adbKeyStore;
}
public void handleMessage(Message msg) {
@@ -251,12 +315,15 @@
break;
}
- if (msg.arg1 == 1) {
- writeKey(key);
- }
-
+ boolean alwaysAllow = msg.arg1 == 1;
if (mThread != null) {
mThread.sendResponse("OK");
+ if (alwaysAllow) {
+ mConnectedKey = key;
+ mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
+ scheduleJobToUpdateAdbKeyStore();
+ }
+ logAdbConnectionChanged(key, AdbProtoEnums.USER_ALLOWED, alwaysAllow);
}
break;
}
@@ -264,36 +331,88 @@
case MESSAGE_ADB_DENY:
if (mThread != null) {
mThread.sendResponse("NO");
+ logAdbConnectionChanged(null, AdbProtoEnums.USER_DENIED, false);
}
break;
case MESSAGE_ADB_CONFIRM: {
+ String key = (String) msg.obj;
if ("trigger_restart_min_framework".equals(
SystemProperties.get("vold.decrypt"))) {
Slog.d(TAG, "Deferring adb confirmation until after vold decrypt");
if (mThread != null) {
mThread.sendResponse("NO");
+ logAdbConnectionChanged(key, AdbProtoEnums.DENIED_VOLD_DECRYPT, false);
}
break;
}
- String key = (String) msg.obj;
String fingerprints = getFingerprints(key);
if ("".equals(fingerprints)) {
if (mThread != null) {
mThread.sendResponse("NO");
+ logAdbConnectionChanged(key, AdbProtoEnums.DENIED_INVALID_KEY, false);
}
break;
}
- mFingerprints = fingerprints;
- startConfirmation(key, mFingerprints);
+ // Check if the key should be allowed without user interaction.
+ if (mAdbKeyStore.isKeyAuthorized(key)) {
+ if (mThread != null) {
+ mThread.sendResponse("OK");
+ mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
+ logAdbConnectionChanged(key, AdbProtoEnums.AUTOMATICALLY_ALLOWED, true);
+ mConnectedKey = key;
+ scheduleJobToUpdateAdbKeyStore();
+ }
+ } else {
+ logAdbConnectionChanged(key, AdbProtoEnums.AWAITING_USER_APPROVAL, false);
+ mFingerprints = fingerprints;
+ startConfirmation(key, mFingerprints);
+ }
break;
}
- case MESSAGE_ADB_CLEAR:
+ case MESSAGE_ADB_CLEAR: {
deleteKeyFile();
+ mConnectedKey = null;
+ mAdbKeyStore.deleteKeyStore();
+ cancelJobToUpdateAdbKeyStore();
break;
+ }
+
+ case MESSAGE_ADB_DISCONNECT: {
+ if (mConnectedKey != null) {
+ mAdbKeyStore.setLastConnectionTime(mConnectedKey,
+ System.currentTimeMillis());
+ cancelJobToUpdateAdbKeyStore();
+ }
+ logAdbConnectionChanged(mConnectedKey, AdbProtoEnums.DISCONNECTED,
+ (mConnectedKey != null));
+ mConnectedKey = null;
+ break;
+ }
+
+ case MESSAGE_ADB_PERSIST_KEY_STORE: {
+ mAdbKeyStore.persistKeyStore();
+ break;
+ }
+
+ case MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME: {
+ if (mConnectedKey != null) {
+ mAdbKeyStore.setLastConnectionTime(mConnectedKey,
+ System.currentTimeMillis());
+ scheduleJobToUpdateAdbKeyStore();
+ }
+ break;
+ }
}
}
+
+ private void logAdbConnectionChanged(String key, int state, boolean alwaysAllow) {
+ long lastConnectionTime = mAdbKeyStore.getLastConnectionTime(key);
+ long authWindow = mAdbKeyStore.getAllowedConnectionTime();
+ StatsLog.write(StatsLog.ADB_CONNECTION_CHANGED, lastConnectionTime, authWindow, state,
+ alwaysAllow);
+ }
}
private String getFingerprints(String key) {
@@ -335,7 +454,8 @@
UserInfo userInfo = UserManager.get(mContext).getUserInfo(currentUserId);
String componentString;
if (userInfo.isAdmin()) {
- componentString = Resources.getSystem().getString(
+ componentString = mConfirmComponent != null
+ ? mConfirmComponent : Resources.getSystem().getString(
com.android.internal.R.string.config_customAdbPublicKeyConfirmationComponent);
} else {
// If the current foreground user is not the admin user we send a different
@@ -397,7 +517,10 @@
return intent;
}
- private File getUserKeyFile() {
+ /**
+ * Returns a new File with the specified name in the adb directory.
+ */
+ private File getAdbFile(String fileName) {
File dataDir = Environment.getDataDirectory();
File adbDir = new File(dataDir, ADB_DIRECTORY);
@@ -406,7 +529,11 @@
return null;
}
- return new File(adbDir, ADB_KEYS_FILE);
+ return new File(adbDir, fileName);
+ }
+
+ private File getUserKeyFile() {
+ return getAdbFile(ADB_KEYS_FILE);
}
private void writeKey(String key) {
@@ -476,6 +603,36 @@
}
/**
+ * Sends a message to the handler to persist the key store.
+ */
+ private void sendPersistKeyStoreMessage() {
+ Message msg = mHandler.obtainMessage(AdbDebuggingHandler.MESSAGE_ADB_PERSIST_KEY_STORE);
+ mHandler.sendMessage(msg);
+ }
+
+ /**
+ * Schedules a job to update the connection time of the currently connected key. This is
+ * intended for cases such as development devices that are left connected to a user's
+ * system beyond the window within which a connection is allowed without user interaction.
+ * A job should be rescheduled daily so that if the device is rebooted while connected to
+ * the user's system the last time in the key store will show within 24 hours which should
+ * be within the allowed window.
+ */
+ private void scheduleJobToUpdateAdbKeyStore() {
+ Message message = mHandler.obtainMessage(
+ AdbDebuggingHandler.MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME);
+ mHandler.sendMessageDelayed(message, AdbDebuggingHandler.UPDATE_KEY_STORE_JOB_INTERVAL);
+ }
+
+ /**
+ * Cancels the scheduled job to update the connection time of the currently connected key.
+ * This should be invoked once the adb session is disconnected.
+ */
+ private void cancelJobToUpdateAdbKeyStore() {
+ mHandler.removeMessages(AdbDebuggingHandler.MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME);
+ }
+
+ /**
* Dump the USB debugging state.
*/
public void dump(DualDumpOutputStream dump, String idName, long id) {
@@ -501,4 +658,231 @@
dump.end(token);
}
+
+ /**
+ * Handles adb keys for which the user has granted the 'always allow' option. This class ensures
+ * these grants are revoked after a period of inactivity as specified in the
+ * ADB_ALLOWED_CONNECTION_TIME setting.
+ */
+ class AdbKeyStore {
+ private Map<String, Long> mKeyMap;
+ private File mKeyFile;
+ private AtomicFile mAtomicKeyFile;
+ // This file contains keys that will be allowed to connect without user interaction as long
+ // as a subsequent connection occurs within the allowed duration.
+ private static final String ADB_TEMP_KEYS_FILE = "adb_temp_keys.xml";
+ private static final String XML_TAG_ADB_KEY = "adbKey";
+ private static final String XML_ATTRIBUTE_KEY = "key";
+ private static final String XML_ATTRIBUTE_LAST_CONNECTION = "lastConnection";
+
+ /**
+ * Value returned by {@code getLastConnectionTime} when there is no previously saved
+ * connection time for the specified key.
+ */
+ public static final long NO_PREVIOUS_CONNECTION = 0;
+
+ /**
+ * Constructor that uses the default location for the persistent adb key store.
+ */
+ AdbKeyStore() {
+ initKeyFile();
+ mKeyMap = getKeyMapFromFile();
+ }
+
+ /**
+ * Constructor that uses the specified file as the location for the persistent adb key
+ * store.
+ */
+ AdbKeyStore(File keyFile) {
+ mKeyFile = keyFile;
+ initKeyFile();
+ mKeyMap = getKeyMapFromFile();
+ }
+
+ /**
+ * Initializes the key file that will be used to persist the adb grants.
+ */
+ private void initKeyFile() {
+ if (mKeyFile == null) {
+ mKeyFile = getAdbFile(ADB_TEMP_KEYS_FILE);
+ }
+ // getAdbFile can return null if the adb file cannot be obtained
+ if (mKeyFile != null) {
+ mAtomicKeyFile = new AtomicFile(mKeyFile);
+ }
+ }
+
+ /**
+ * Returns the key map with the keys and last connection times from the key file.
+ */
+ private Map<String, Long> getKeyMapFromFile() {
+ Map<String, Long> keyMap = new HashMap<String, Long>();
+ // if the AtomicFile could not be instantiated before attempt again; if it still fails
+ // return an empty key map.
+ if (mAtomicKeyFile == null) {
+ initKeyFile();
+ if (mAtomicKeyFile == null) {
+ Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for reading");
+ return keyMap;
+ }
+ }
+ if (!mAtomicKeyFile.exists()) {
+ return keyMap;
+ }
+ try (FileInputStream keyStream = mAtomicKeyFile.openRead()) {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(keyStream, StandardCharsets.UTF_8.name());
+ XmlUtils.beginDocument(parser, XML_TAG_ADB_KEY);
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ String tagName = parser.getName();
+ if (tagName == null) {
+ break;
+ } else if (!tagName.equals(XML_TAG_ADB_KEY)) {
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ String key = parser.getAttributeValue(null, XML_ATTRIBUTE_KEY);
+ long connectionTime;
+ try {
+ connectionTime = Long.valueOf(
+ parser.getAttributeValue(null, XML_ATTRIBUTE_LAST_CONNECTION));
+ } catch (NumberFormatException e) {
+ Slog.e(TAG,
+ "Caught a NumberFormatException parsing the last connection time: "
+ + e);
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ keyMap.put(key, connectionTime);
+ }
+ } catch (IOException | XmlPullParserException e) {
+ Slog.e(TAG, "Caught an exception parsing the XML key file: ", e);
+ }
+ return keyMap;
+ }
+
+ /**
+ * Writes the key map to the key file.
+ */
+ public void persistKeyStore() {
+ // if there is nothing in the key map then ensure any keys left in the key store files
+ // are deleted as well.
+ if (mKeyMap.size() == 0) {
+ deleteKeyStore();
+ return;
+ }
+ if (mAtomicKeyFile == null) {
+ initKeyFile();
+ if (mAtomicKeyFile == null) {
+ Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for writing");
+ return;
+ }
+ }
+ FileOutputStream keyStream = null;
+ try {
+ XmlSerializer serializer = new FastXmlSerializer();
+ keyStream = mAtomicKeyFile.startWrite();
+ serializer.setOutput(keyStream, StandardCharsets.UTF_8.name());
+ serializer.startDocument(null, true);
+ long allowedTime = getAllowedConnectionTime();
+ long systemTime = System.currentTimeMillis();
+ Iterator keyMapIterator = mKeyMap.entrySet().iterator();
+ while (keyMapIterator.hasNext()) {
+ Map.Entry<String, Long> keyEntry = (Map.Entry) keyMapIterator.next();
+ long connectionTime = keyEntry.getValue();
+ if (systemTime < (connectionTime + allowedTime)) {
+ serializer.startTag(null, XML_TAG_ADB_KEY);
+ serializer.attribute(null, XML_ATTRIBUTE_KEY, keyEntry.getKey());
+ serializer.attribute(null, XML_ATTRIBUTE_LAST_CONNECTION,
+ String.valueOf(keyEntry.getValue()));
+ serializer.endTag(null, XML_TAG_ADB_KEY);
+ } else {
+ keyMapIterator.remove();
+ }
+ }
+ serializer.endDocument();
+ mAtomicKeyFile.finishWrite(keyStream);
+ } catch (IOException e) {
+ Slog.e(TAG, "Caught an exception writing the key map: ", e);
+ mAtomicKeyFile.failWrite(keyStream);
+ }
+ }
+
+ /**
+ * Removes all of the entries in the key map and deletes the key file.
+ */
+ public void deleteKeyStore() {
+ mKeyMap.clear();
+ if (mAtomicKeyFile == null) {
+ return;
+ }
+ mAtomicKeyFile.delete();
+ }
+
+ /**
+ * Returns the time of the last connection from the specified key, or {@code
+ * NO_PREVIOUS_CONNECTION} if the specified key does not have an active adb grant.
+ */
+ public long getLastConnectionTime(String key) {
+ return mKeyMap.getOrDefault(key, NO_PREVIOUS_CONNECTION);
+ }
+
+ /**
+ * Sets the time of the last connection for the specified key to the provided time.
+ */
+ public void setLastConnectionTime(String key, long connectionTime) {
+ // Do not set the connection time to a value that is earlier than what was previously
+ // stored as the last connection time.
+ if (mKeyMap.containsKey(key) && mKeyMap.get(key) >= connectionTime) {
+ return;
+ }
+ mKeyMap.put(key, connectionTime);
+ sendPersistKeyStoreMessage();
+ }
+
+ /**
+ * Returns whether the specified key should be authroized to connect without user
+ * interaction. This requires that the user previously connected this device and selected
+ * the option to 'Always allow', and the time since the last connection is within the
+ * allowed window.
+ */
+ public boolean isKeyAuthorized(String key) {
+ long lastConnectionTime = getLastConnectionTime(key);
+ if (lastConnectionTime == NO_PREVIOUS_CONNECTION) {
+ return false;
+ }
+ long allowedConnectionTime = getAllowedConnectionTime();
+ // if the allowed connection time is 0 then revert to the previous behavior of always
+ // allowing previously granted adb grants.
+ if (allowedConnectionTime == 0 || (System.currentTimeMillis() < (lastConnectionTime
+ + allowedConnectionTime))) {
+ return true;
+ } else {
+ // since this key is no longer auhorized remove it from the Map
+ removeKey(key);
+ return false;
+ }
+ }
+
+ /**
+ * Returns the connection time within which a connection from an allowed key is
+ * automatically allowed without user interaction.
+ */
+ public long getAllowedConnectionTime() {
+ return Settings.Global.getLong(mContext.getContentResolver(),
+ Settings.Global.ADB_ALLOWED_CONNECTION_TIME,
+ Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME);
+ }
+
+ /**
+ * Removes the specified key from the key store.
+ */
+ public void removeKey(String key) {
+ if (!mKeyMap.containsKey(key)) {
+ return;
+ }
+ mKeyMap.remove(key);
+ sendPersistKeyStoreMessage();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 089847d..b24290f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -64,6 +64,7 @@
import static android.os.Process.SIGNAL_USR1;
import static android.os.Process.SYSTEM_UID;
import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
+import static android.os.Process.ZYGOTE_PROCESS;
import static android.os.Process.getTotalMemory;
import static android.os.Process.isThreadInProcess;
import static android.os.Process.killProcess;
@@ -75,7 +76,6 @@
import static android.os.Process.sendSignal;
import static android.os.Process.setThreadPriority;
import static android.os.Process.setThreadScheduler;
-import static android.os.Process.zygoteProcess;
import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES;
import static android.provider.Settings.Global.DEBUG_APP;
import static android.provider.Settings.Global.NETWORK_ACCESS_TIMEOUT_MS;
@@ -2162,7 +2162,7 @@
? Collections.emptyList()
: Arrays.asList(exemptions.split(","));
}
- if (!zygoteProcess.setApiBlacklistExemptions(mExemptions)) {
+ if (!ZYGOTE_PROCESS.setApiBlacklistExemptions(mExemptions)) {
Slog.e(TAG, "Failed to set API blacklist exemptions!");
// leave mExemptionsStr as is, so we don't try to send the same list again.
mExemptions = Collections.emptyList();
@@ -2175,7 +2175,7 @@
}
if (logSampleRate != -1 && logSampleRate != mLogSampleRate) {
mLogSampleRate = logSampleRate;
- zygoteProcess.setHiddenApiAccessLogSampleRate(mLogSampleRate);
+ ZYGOTE_PROCESS.setHiddenApiAccessLogSampleRate(mLogSampleRate);
}
mPolicy = getValidEnforcementPolicy(Settings.Global.HIDDEN_API_POLICY);
}
@@ -2864,16 +2864,18 @@
* @param userId
* @param event
* @param appToken ActivityRecord's appToken.
+ * @param taskRoot TaskRecord's root
*/
public void updateActivityUsageStats(ComponentName activity, int userId, int event,
- IBinder appToken) {
+ IBinder appToken, ComponentName taskRoot) {
if (DEBUG_SWITCH) {
Slog.d(TAG_SWITCH, "updateActivityUsageStats: comp="
+ activity + " hash=" + appToken.hashCode() + " event=" + event);
}
synchronized (this) {
if (mUsageStatsService != null) {
- mUsageStatsService.reportEvent(activity, userId, event, appToken.hashCode());
+ mUsageStatsService.reportEvent(activity, userId, event, appToken.hashCode(),
+ taskRoot);
}
}
}
@@ -2911,7 +2913,7 @@
if (mUsageStatsService != null) {
mUsageStatsService.reportEvent(service, userId,
started ? UsageEvents.Event.FOREGROUND_SERVICE_START
- : UsageEvents.Event.FOREGROUND_SERVICE_STOP, 0);
+ : UsageEvents.Event.FOREGROUND_SERVICE_STOP, 0, null);
}
}
}
@@ -4880,7 +4882,7 @@
ArraySet<String> completedIsas = new ArraySet<String>();
for (String abi : Build.SUPPORTED_ABIS) {
- zygoteProcess.establishZygoteConnectionForAbi(abi);
+ ZYGOTE_PROCESS.establishZygoteConnectionForAbi(abi);
final String instructionSet = VMRuntime.getInstructionSet(abi);
if (!completedIsas.contains(instructionSet)) {
try {
@@ -17521,10 +17523,10 @@
@Override
public void updateActivityUsageStats(ComponentName activity, int userId, int event,
- IBinder appToken) {
+ IBinder appToken, ComponentName taskRoot) {
synchronized (ActivityManagerService.this) {
ActivityManagerService.this.updateActivityUsageStats(activity, userId, event,
- appToken);
+ appToken, taskRoot);
}
}
diff --git a/services/core/java/com/android/server/am/AppCompactor.java b/services/core/java/com/android/server/am/AppCompactor.java
index fe27c49..fd402cc 100644
--- a/services/core/java/com/android/server/am/AppCompactor.java
+++ b/services/core/java/com/android/server/am/AppCompactor.java
@@ -64,7 +64,7 @@
final private String COMPACT_ACTION_FILE = "file";
final private String COMPACT_ACTION_ANON = "anon";
- final private String COMPACT_ACTION_FULL = "full";
+ final private String COMPACT_ACTION_FULL = "all";
final private String compactActionSome;
final private String compactActionFull;
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 65aacdc..353749f 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -45,6 +45,7 @@
import android.os.UserHandle;
import android.util.EventLog;
import android.util.Slog;
+import android.util.StatsLog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -157,6 +158,9 @@
static final int BROADCAST_INTENT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG;
static final int BROADCAST_TIMEOUT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG + 1;
+ // log latency metrics for ordered broadcasts during BOOT_COMPLETED processing
+ boolean mLogLatencyMetrics = true;
+
final BroadcastHandler mHandler;
private final class BroadcastHandler extends Handler {
@@ -941,6 +945,12 @@
// adjustments.
mService.updateOomAdjLocked();
}
+
+ // when we have no more ordered broadcast on this queue, stop logging
+ if (mService.mUserController.mBootCompleted && mLogLatencyMetrics) {
+ mLogLatencyMetrics = false;
+ }
+
return;
}
r = mOrderedBroadcasts.get(0);
@@ -1036,6 +1046,13 @@
if (recIdx == 0) {
r.dispatchTime = r.receiverTime;
r.dispatchClockTime = System.currentTimeMillis();
+
+ if (mLogLatencyMetrics) {
+ StatsLog.write(
+ StatsLog.BROADCAST_DISPATCH_LATENCY_REPORTED,
+ r.dispatchClockTime - r.enqueueClockTime);
+ }
+
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index dc03369..be910d4 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -58,6 +58,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.Trace;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Slog;
@@ -184,6 +185,7 @@
@GuardedBy("mService")
final void updateOomAdjLocked() {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "updateOomAdj");
mService.mOomAdjProfiler.oomAdjStarted();
final ProcessRecord TOP_APP = mService.getTopAppLocked();
final long now = SystemClock.uptimeMillis();
@@ -568,6 +570,7 @@
}
}
mService.mOomAdjProfiler.oomAdjEnded();
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
private final ComputeOomAdjWindowCallback mTmpComputeOomAdjWindowCallback =
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index c2117a7..128d98b 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -78,6 +78,7 @@
@VisibleForTesting
static final String[] sDeviceConfigScopes = new String[] {
DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT,
+ DeviceConfig.NAMESPACE_NETD_NATIVE,
};
private final String[] mGlobalSettings;
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index bcce052..c981e68 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -240,6 +240,8 @@
private final LockPatternUtils mLockPatternUtils;
+ volatile boolean mBootCompleted;
+
UserController(ActivityManagerService service) {
this(new Injector(service));
}
@@ -567,6 +569,7 @@
Bundle extras, boolean ordered, boolean sticky, int sendingUser)
throws RemoteException {
Slog.i(UserController.TAG, "Finished processing BOOT_COMPLETED for u" + userId);
+ mBootCompleted = true;
}
}, 0, null, null,
new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index 714a807..708de73 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -27,7 +27,9 @@
import android.content.ContentResolver;
import android.database.ContentObserver;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
+import android.os.Debug;
import android.os.Environment;
import android.os.Message;
import android.os.Process;
@@ -38,6 +40,7 @@
import android.util.Slog;
import android.util.TimeUtils;
import android.util.Xml;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.AtomicDirectory;
import com.android.internal.os.BackgroundThread;
@@ -45,6 +48,7 @@
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.FgThread;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -59,10 +63,12 @@
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
@@ -105,6 +111,7 @@
// TODO (bug:122218838): Validate changed time is handled correctly
final class HistoricalRegistry {
private static final boolean DEBUG = false;
+ private static final boolean KEEP_WTF_LOG = Build.IS_DEBUGGABLE;
private static final String LOG_TAG = HistoricalRegistry.class.getSimpleName();
@@ -123,7 +130,7 @@
private static final int MSG_WRITE_PENDING_HISTORY = 1;
// See mMode
- private static final int DEFAULT_MODE = AppOpsManager.HISTORICAL_MODE_DISABLED;
+ private static final int DEFAULT_MODE = AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE;
// See mBaseSnapshotInterval
private static final long DEFAULT_SNAPSHOT_INTERVAL_MILLIS = TimeUnit.MINUTES.toMillis(15);
@@ -131,11 +138,13 @@
// See mIntervalCompressionMultiplier
private static final long DEFAULT_COMPRESSION_STEP = 10;
+ private static final String HISTORY_FILE_SUFFIX = ".xml";
+
/**
* Whether history is enabled.
*/
@GuardedBy("mInMemoryLock")
- private int mMode = AppOpsManager.HISTORICAL_MODE_DISABLED;
+ private int mMode = AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE;
/**
* This granularity has been chosen to allow clean delineation for intervals
@@ -172,10 +181,16 @@
HistoricalRegistry(@NonNull Object lock) {
mInMemoryLock = lock;
if (mMode != AppOpsManager.HISTORICAL_MODE_DISABLED) {
- synchronized (mInMemoryLock) {
- // When starting always adjust history to now.
- mPendingHistoryOffsetMillis = System.currentTimeMillis()
- - mPersistence.getLastPersistTimeMillisDLocked();
+ synchronized (mOnDiskLock) {
+ synchronized (mInMemoryLock) {
+ // When starting always adjust history to now.
+ final long lastPersistTimeMills =
+ mPersistence.getLastPersistTimeMillisDLocked();
+ if (lastPersistTimeMills > 0) {
+ mPendingHistoryOffsetMillis =
+ System.currentTimeMillis() - lastPersistTimeMills;
+ }
+ }
}
}
}
@@ -236,8 +251,8 @@
+ "=" + setting + " resetting!");
}
- void dump(String prefix, PrintWriter pw, int filterUid,
- String filterPackage, int filterOp) {
+ void dump(String prefix, PrintWriter pw, int filterUid,
+ String filterPackage, int filterOp) {
synchronized (mOnDiskLock) {
synchronized (mInMemoryLock) {
pw.println();
@@ -522,6 +537,7 @@
persistPendingHistory(pendingWrites);
}
}
+
private void persistPendingHistory(@NonNull List<HistoricalOps> pendingWrites) {
synchronized (mOnDiskLock) {
BackgroundThread.getHandler().removeMessages(MSG_WRITE_PENDING_HISTORY);
@@ -574,8 +590,6 @@
private static final String LOG_TAG = Persistence.class.getSimpleName();
- private static final String HISTORY_FILE_SUFFIX = ".xml";
-
private static final String TAG_HISTORY = "history";
private static final String TAG_OPS = "ops";
private static final String TAG_UID = "uid";
@@ -603,7 +617,7 @@
}
private final AtomicDirectory mHistoricalAppOpsDir = new AtomicDirectory(
- new File(new File(Environment.getDataSystemDeDirectory(), "appops"), "history"));
+ new File(new File(Environment.getDataSystemDirectory(), "appops"), "history"));
private File generateFile(@NonNull File baseDir, int depth) {
final long globalBeginMillis = computeGlobalIntervalBeginMillis(depth);
@@ -622,10 +636,20 @@
try {
final File newBaseDir = mHistoricalAppOpsDir.startWrite();
final File oldBaseDir = mHistoricalAppOpsDir.getBackupDirectory();
- handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir, ops, 0);
+ final HistoricalFilesInvariant filesInvariant;
+ if (DEBUG) {
+ filesInvariant = new HistoricalFilesInvariant();
+ filesInvariant.startTracking(oldBaseDir);
+ }
+ final Set<String> oldFileNames = getHistoricalFileNames(oldBaseDir);
+ handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir, ops,
+ oldFileNames, 0);
+ if (DEBUG) {
+ filesInvariant.stopTracking(newBaseDir);
+ }
mHistoricalAppOpsDir.finishWrite();
} catch (Throwable t) {
- Slog.wtf(LOG_TAG, "Failed to write historical app ops, restoring backup", t);
+ wtf("Failed to write historical app ops, restoring backup", t, null);
mHistoricalAppOpsDir.failWrite();
}
}
@@ -649,15 +673,23 @@
}
long getLastPersistTimeMillisDLocked() {
+ File baseDir = null;
try {
- final File baseDir = mHistoricalAppOpsDir.startRead();
- final File file = generateFile(baseDir, 0);
- if (file.exists()) {
- return file.lastModified();
+ baseDir = mHistoricalAppOpsDir.startRead();
+ final File[] files = baseDir.listFiles();
+ if (files != null && files.length > 0) {
+ final Set<File> historyFiles = new ArraySet<>();
+ Collections.addAll(historyFiles, files);
+ for (int i = 0;; i++) {
+ final File file = generateFile(baseDir, i);
+ if (historyFiles.contains(file)) {
+ return file.lastModified();
+ }
+ }
}
mHistoricalAppOpsDir.finishRead();
- } catch (IOException e) {
- Slog.wtf("Error reading historical app ops. Deleting history.", e);
+ } catch (Throwable e) {
+ wtf("Error reading historical app ops. Deleting history.", e, baseDir);
mHistoricalAppOpsDir.delete();
}
return 0;
@@ -680,27 +712,27 @@
private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsBaseDLocked(
int filterUid, @NonNull String filterPackageName, @Nullable String[] filterOpNames,
long filterBeginTimeMillis, long filterEndTimeMillis) {
+ File baseDir = null;
try {
- final File baseDir = mHistoricalAppOpsDir.startRead();
- final File[] files = baseDir.listFiles();
- if (files == null) {
- return null;
+ baseDir = mHistoricalAppOpsDir.startRead();
+ final HistoricalFilesInvariant filesInvariant;
+ if (DEBUG) {
+ filesInvariant = new HistoricalFilesInvariant();
+ filesInvariant.startTracking(baseDir);
}
- final ArraySet<File> historyFiles = new ArraySet<>(files.length);
- for (File file : files) {
- if (file.isFile() && file.getName().endsWith(HISTORY_FILE_SUFFIX)) {
- historyFiles.add(file);
- }
- }
+ final Set<String> historyFiles = getHistoricalFileNames(baseDir);
final long[] globalContentOffsetMillis = {0};
final LinkedList<HistoricalOps> ops = collectHistoricalOpsRecursiveDLocked(
baseDir, filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis,
filterEndTimeMillis, globalContentOffsetMillis, null /*outOps*/,
0 /*depth*/, historyFiles);
+ if (DEBUG) {
+ filesInvariant.stopTracking(baseDir);
+ }
mHistoricalAppOpsDir.finishRead();
return ops;
- } catch (IOException | XmlPullParserException e) {
- Slog.wtf("Error reading historical app ops. Deleting history.", e);
+ } catch (Throwable t) {
+ wtf("Error reading historical app ops. Deleting history.", t, baseDir);
mHistoricalAppOpsDir.delete();
}
return null;
@@ -711,7 +743,7 @@
@Nullable String[] filterOpNames, long filterBeginTimeMillis,
long filterEndTimeMillis, @NonNull long[] globalContentOffsetMillis,
@Nullable LinkedList<HistoricalOps> outOps, int depth,
- @NonNull ArraySet<File> historyFiles)
+ @NonNull Set<String> historyFiles)
throws IOException, XmlPullParserException {
final long previousIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
depth) * mBaseSnapshotInterval;
@@ -761,17 +793,9 @@
return outOps;
}
- private boolean createHardLinkToExistingFile(@NonNull File fromFile, @NonNull File toFile)
- throws IOException {
- if (!fromFile.exists()) {
- return false;
- }
- Files.createLink(toFile.toPath(), fromFile.toPath());
- return true;
- }
-
private void handlePersistHistoricalOpsRecursiveDLocked(@NonNull File newBaseDir,
- @NonNull File oldBaseDir, @Nullable List<HistoricalOps> passedOps, int depth)
+ @NonNull File oldBaseDir, @Nullable List<HistoricalOps> passedOps,
+ @NonNull Set<String> oldFileNames, int depth)
throws IOException, XmlPullParserException {
final long previousIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
depth) * mBaseSnapshotInterval;
@@ -779,12 +803,15 @@
depth + 1) * mBaseSnapshotInterval;
if (passedOps == null || passedOps.isEmpty()) {
- // If there is an old file we need to copy it over to the new state.
- final File oldFile = generateFile(oldBaseDir, depth);
- final File newFile = generateFile(newBaseDir, depth);
- if (createHardLinkToExistingFile(oldFile, newFile)) {
+ if (!oldFileNames.isEmpty()) {
+ // If there is an old file we need to copy it over to the new state.
+ final File oldFile = generateFile(oldBaseDir, depth);
+ if (oldFileNames.remove(oldFile.getName())) {
+ final File newFile = generateFile(newBaseDir, depth);
+ Files.createLink(newFile.toPath(), oldFile.toPath());
+ }
handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir,
- passedOps, depth + 1);
+ passedOps, oldFileNames, depth + 1);
}
return;
}
@@ -900,9 +927,11 @@
enforceOpsWellFormed(overflowedOps);
}
+ final File newFile = generateFile(newBaseDir, depth);
+ oldFileNames.remove(newFile.getName());
+
if (persistedOps != null) {
normalizeSnapshotForSlotDuration(persistedOps, slotDurationMillis);
- final File newFile = generateFile(newBaseDir, depth);
writeHistoricalOpsDLocked(persistedOps, intervalOverflowMillis, newFile);
if (DEBUG) {
Slog.i(LOG_TAG, "Persisted at depth: " + depth
@@ -912,7 +941,7 @@
}
handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir,
- overflowedOps, depth + 1);
+ overflowedOps, oldFileNames, depth + 1);
}
private @NonNull List<HistoricalOps> readHistoricalOpsLocked(File baseDir,
@@ -920,11 +949,11 @@
@Nullable String filterPackageName, @Nullable String[] filterOpNames,
long filterBeginTimeMillis, long filterEndTimeMillis,
@Nullable long[] cumulativeOverflowMillis, int depth,
- @NonNull ArraySet<File> historyFiles)
+ @NonNull Set<String> historyFiles)
throws IOException, XmlPullParserException {
final File file = generateFile(baseDir, depth);
if (historyFiles != null) {
- historyFiles.remove(file);
+ historyFiles.remove(file.getName());
}
if (filterBeginTimeMillis >= filterEndTimeMillis
|| filterEndTimeMillis < intervalBeginMillis) {
@@ -1085,7 +1114,7 @@
throws IOException, XmlPullParserException {
final int op = XmlUtils.readIntAttribute(parser, ATTR_NAME);
if (filterOpNames != null && !ArrayUtils.contains(filterOpNames,
- AppOpsManager.opToName(op))) {
+ AppOpsManager.opToPublicName(op))) {
XmlUtils.skipCurrentTag(parser);
return null;
}
@@ -1364,6 +1393,61 @@
}
return builder.toString();
}
+
+ private static Set<String> getHistoricalFileNames(@NonNull File historyDir) {
+ final File[] files = historyDir.listFiles();
+ if (files == null) {
+ return Collections.emptySet();
+ }
+ final ArraySet<String> fileNames = new ArraySet<>(files.length);
+ for (File file : files) {
+ fileNames.add(file.getName());
+
+ }
+ return fileNames;
+ }
+ }
+
+ private static class HistoricalFilesInvariant {
+ private final @NonNull List<File> mBeginFiles = new ArrayList<>();
+
+ public void startTracking(@NonNull File folder) {
+ final File[] files = folder.listFiles();
+ if (files != null) {
+ Collections.addAll(mBeginFiles, files);
+ }
+ }
+
+ public void stopTracking(@NonNull File folder) {
+ final List<File> endFiles = new ArrayList<>();
+ final File[] files = folder.listFiles();
+ if (files != null) {
+ Collections.addAll(endFiles, files);
+ }
+ final long beginOldestFileOffsetMillis = getOldestFileOffsetMillis(mBeginFiles);
+ final long endOldestFileOffsetMillis = getOldestFileOffsetMillis(endFiles);
+ if (endOldestFileOffsetMillis < beginOldestFileOffsetMillis) {
+ final String message = "History loss detected!"
+ + "\nold files: " + mBeginFiles;
+ wtf(message, null, folder);
+ throw new IllegalStateException(message);
+ }
+ }
+
+ private static long getOldestFileOffsetMillis(@NonNull List<File> files) {
+ if (files.isEmpty()) {
+ return 0;
+ }
+ String longestName = files.get(0).getName();
+ final int fileCount = files.size();
+ for (int i = 1; i < fileCount; i++) {
+ final File file = files.get(i);
+ if (file.getName().length() > longestName.length()) {
+ longestName = file.getName();
+ }
+ }
+ return Long.parseLong(longestName.replace(HISTORY_FILE_SUFFIX, ""));
+ }
}
private final class StringDumpVisitor implements AppOpsManager.HistoricalOpsVisitor {
@@ -1493,4 +1577,30 @@
}
}
}
+
+ private static void wtf(@Nullable String message, @Nullable Throwable t,
+ @Nullable File storage) {
+ Slog.wtf(LOG_TAG, message, t);
+ if (KEEP_WTF_LOG) {
+ try {
+ final File file = new File(new File(Environment.getDataSystemDirectory(), "appops"),
+ "wtf" + TimeUtils.formatForLogging(System.currentTimeMillis()));
+ if (file.createNewFile()) {
+ try (PrintWriter writer = new PrintWriter(file)) {
+ if (t != null) {
+ writer.append('\n').append(t.toString());
+ }
+ writer.append('\n').append(Debug.getCallers(10));
+ if (storage != null) {
+ writer.append("\nfiles: " + Arrays.toString(storage.listFiles()));
+ } else {
+ writer.append("\nfiles: none");
+ }
+ }
+ }
+ } catch (IOException e) {
+ /* ignore */
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index f60d6b0..27edbbf 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -47,6 +47,7 @@
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.StatsLog;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -73,7 +74,6 @@
private final Context mContext;
private final PowerManager mPowerManager;
- private final ActivityManager mActivityManager;
private final Object mLock;
@GuardedBy("mLock")
private final SparseArray<UserState> mUserStates = new SparseArray<>();
@@ -85,7 +85,6 @@
super(context);
mContext = Preconditions.checkNotNull(context);
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
mLock = new Object();
mAttentionHandler = new AttentionHandler();
}
@@ -96,7 +95,7 @@
}
@Override
- public void onStopUser(int userId) {
+ public void onSwitchUser(int userId) {
cancelAndUnbindLocked(peekUserStateLocked(userId),
AttentionService.ATTENTION_FAILURE_UNKNOWN);
}
@@ -178,11 +177,16 @@
userState.mAttentionCheckCache = new AttentionCheckCache(
SystemClock.uptimeMillis(), result,
timestamp);
+
+ StatsLog.write(StatsLog.ATTENTION_MANAGER_SERVICE_RESULT_REPORTED,
+ result);
}
@Override
public void onFailure(int requestCode, int error) {
callback.onFailure(requestCode, error);
+ StatsLog.write(StatsLog.ATTENTION_MANAGER_SERVICE_RESULT_REPORTED,
+ error);
}
@Override
@@ -201,11 +205,20 @@
/** Cancels the specified attention check. */
public void cancelAttentionCheck(int requestCode) {
- final UserState userState = getOrCreateCurrentUserStateLocked();
- try {
- userState.mService.cancelAttentionCheck(requestCode);
- } catch (RemoteException e) {
- Slog.e(LOG_TAG, "Cannot call into the AttentionService");
+ synchronized (mLock) {
+ final UserState userState = getOrCreateCurrentUserStateLocked();
+ if (userState.mService == null) {
+ if (userState.mPendingAttentionCheck != null
+ && userState.mPendingAttentionCheck.mRequestCode == requestCode) {
+ userState.mPendingAttentionCheck = null;
+ }
+ return;
+ }
+ try {
+ userState.mService.cancelAttentionCheck(requestCode);
+ } catch (RemoteException e) {
+ Slog.e(LOG_TAG, "Cannot call into the AttentionService");
+ }
}
}
@@ -224,7 +237,7 @@
@GuardedBy("mLock")
private UserState getOrCreateCurrentUserStateLocked() {
- return getOrCreateUserStateLocked(mActivityManager.getCurrentUser());
+ return getOrCreateUserStateLocked(ActivityManager.getCurrentUser());
}
@GuardedBy("mLock")
@@ -239,7 +252,7 @@
@GuardedBy("mLock")
UserState peekCurrentUserStateLocked() {
- return peekUserStateLocked(mActivityManager.getCurrentUser());
+ return peekUserStateLocked(ActivityManager.getCurrentUser());
}
@GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 11299b6..de389bc 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1223,11 +1223,13 @@
private void checkMuteAffectedStreams() {
// any stream with a min level > 0 is not muteable by definition
- // STREAM_VOICE_CALL can be muted by applications that has the the MODIFY_PHONE_STATE permission.
+ // STREAM_VOICE_CALL and STREAM_BLUETOOTH_SCO can be muted by applications
+ // that has the the MODIFY_PHONE_STATE permission.
for (int i = 0; i < mStreamStates.length; i++) {
final VolumeStreamState vss = mStreamStates[i];
if (vss.mIndexMin > 0 &&
- vss.mStreamType != AudioSystem.STREAM_VOICE_CALL) {
+ (vss.mStreamType != AudioSystem.STREAM_VOICE_CALL &&
+ vss.mStreamType != AudioSystem.STREAM_BLUETOOTH_SCO)) {
mMuteAffectedStreams &= ~(1 << vss.mStreamType);
}
}
@@ -1711,10 +1713,11 @@
return;
}
- // If adjust is mute and the stream is STREAM_VOICE_CALL, make sure
+ // If adjust is mute and the stream is STREAM_VOICE_CALL or STREAM_BLUETOOTH_SCO, make sure
// that the calling app have the MODIFY_PHONE_STATE permission.
if (isMuteAdjust &&
- streamType == AudioSystem.STREAM_VOICE_CALL &&
+ (streamType == AudioSystem.STREAM_VOICE_CALL ||
+ streamType == AudioSystem.STREAM_BLUETOOTH_SCO) &&
mContext.checkCallingOrSelfPermission(
android.Manifest.permission.MODIFY_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED) {
@@ -2038,12 +2041,14 @@
+ " CHANGE_ACCESSIBILITY_VOLUME callingPackage=" + callingPackage);
return;
}
- if ((streamType == AudioManager.STREAM_VOICE_CALL) &&
+ if ((streamType == AudioManager.STREAM_VOICE_CALL ||
+ streamType == AudioManager.STREAM_BLUETOOTH_SCO) &&
(index == 0) &&
(mContext.checkCallingOrSelfPermission(
android.Manifest.permission.MODIFY_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED)) {
- Log.w(TAG, "Trying to call setStreamVolume() for STREAM_VOICE_CALL and index 0 without"
+ Log.w(TAG, "Trying to call setStreamVolume() for STREAM_VOICE_CALL or"
+ + " STREAM_BLUETOOTH_SCO and index 0 without"
+ " MODIFY_PHONE_STATE callingPackage=" + callingPackage);
return;
}
@@ -4746,12 +4751,18 @@
private int getA2dpCodec(BluetoothDevice device) {
synchronized (mA2dpAvrcpLock) {
- if (mA2dp != null) {
- BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device);
- BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig();
- return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType());
+ if (mA2dp == null) {
+ return AudioSystem.AUDIO_FORMAT_DEFAULT;
}
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device);
+ if (btCodecStatus == null) {
+ return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ }
+ BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig();
+ if (btCodecConfig == null) {
+ return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ }
+ return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType());
}
}
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
index ac74598..79b56c6 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -20,7 +20,6 @@
import android.net.ConnectivityMetricsEvent;
import android.net.IIpConnectivityMetrics;
import android.net.INetdEventCallback;
-import android.net.ip.IpClient;
import android.net.metrics.ApfProgramEvent;
import android.net.metrics.IpConnectivityLog;
import android.os.Binder;
@@ -270,8 +269,6 @@
// Dump the rolling buffer of metrics event and pretty print events using a human readable
// format. Also print network dns/connect statistics and default network event time series.
static final String CMD_LIST = "list";
- // Dump all IpClient logs ("ipclient").
- static final String CMD_IPCLIENT = IpClient.DUMP_ARG;
// By default any other argument will fall into the default case which is the equivalent
// of calling both the "list" and "ipclient" commands. This includes most notably bug
// reports collected by dumpsys.cpp with the "-a" argument.
@@ -295,20 +292,9 @@
case CMD_PROTO:
cmdListAsProto(pw);
return;
- case CMD_IPCLIENT: {
- final String[] ipclientArgs = ((args != null) && (args.length > 1))
- ? Arrays.copyOfRange(args, 1, args.length)
- : null;
- IpClient.dumpAllLogs(pw, ipclientArgs);
- return;
- }
case CMD_LIST:
- cmdList(pw);
- return;
default:
cmdList(pw);
- pw.println("");
- IpClient.dumpAllLogs(pw, null);
return;
}
}
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index 0f8fc17..8a3cdca 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -185,13 +185,13 @@
}
void start(int slot) {
+ mSlot = slot;
int error = isValid();
if (error == SUCCESS) {
- mSlot = slot;
Log.d(TAG, "Starting keepalive " + mSlot + " on " + mNai.name());
mNai.asyncChannel.sendMessage(CMD_START_PACKET_KEEPALIVE, slot, mInterval, mPacket);
} else {
- notifyMessenger(NO_KEEPALIVE, error);
+ handleStopKeepalive(mNai, mSlot, error);
return;
}
}
@@ -277,6 +277,7 @@
return;
}
ki.stop(reason);
+ Log.d(TAG, "Stopped keepalive " + slot + " on " + networkName);
networkKeepalives.remove(slot);
if (networkKeepalives.isEmpty()) {
mKeepalives.remove(nai);
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 602aedb..c72c9dd 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -60,7 +60,6 @@
import android.net.NetworkUtils;
import android.net.RouteInfo;
import android.net.UidRange;
-import android.net.Uri;
import android.net.VpnService;
import android.os.Binder;
import android.os.Build.VERSION_CODES;
@@ -71,7 +70,6 @@
import android.os.Looper;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
-import android.os.PatternMatcher;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -100,6 +98,8 @@
import com.android.server.LocalServices;
import com.android.server.net.BaseNetworkObserver;
+import libcore.io.IoUtils;
+
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -121,8 +121,6 @@
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicInteger;
-import libcore.io.IoUtils;
-
/**
* @hide
*/
@@ -346,11 +344,18 @@
*
* @return {@code true} if VPN lockdown is enabled.
*/
- public boolean getLockdown() {
+ public synchronized boolean getLockdown() {
return mLockdown;
}
/**
+ * Returns whether VPN is configured as always-on.
+ */
+ public synchronized boolean getAlwaysOn() {
+ return mAlwaysOn;
+ }
+
+ /**
* Checks if a VPN app supports always-on mode.
*
* In order to support the always-on feature, an app has to
diff --git a/services/core/java/com/android/server/display/ColorDisplayService.java b/services/core/java/com/android/server/display/ColorDisplayService.java
index 9223739..3a58160 100644
--- a/services/core/java/com/android/server/display/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/ColorDisplayService.java
@@ -28,6 +28,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
+import android.annotation.UserIdInt;
import android.app.AlarmManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -51,6 +52,7 @@
import android.provider.Settings.System;
import android.util.MathUtils;
import android.util.Slog;
+import android.view.SurfaceControl;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.AnimationUtils;
@@ -866,6 +868,12 @@
if (mDisplayWhiteBalanceListener != null && oldActivated != activated) {
mDisplayWhiteBalanceListener.onDisplayWhiteBalanceStatusChanged(activated);
}
+
+ // If disabled, clear the tint. If enabled, do nothing more here and let the next
+ // temperature update set the correct tint.
+ if (!activated) {
+ applyTint(mDisplayWhiteBalanceTintController, false);
+ }
}
private boolean isDisplayWhiteBalanceSettingEnabled() {
@@ -878,6 +886,21 @@
return dtm.isDeviceColorManaged();
}
+ private int getTransformCapabilitiesInternal() {
+ int availabilityFlags = ColorDisplayManager.CAPABILITY_NONE;
+ if (SurfaceControl.getProtectedContentSupport()) {
+ availabilityFlags |= ColorDisplayManager.CAPABILITY_PROTECTED_CONTENT;
+ }
+ final Resources res = getContext().getResources();
+ if (res.getBoolean(R.bool.config_setColorTransformAccelerated)) {
+ availabilityFlags |= ColorDisplayManager.CAPABILITY_HARDWARE_ACCELERATION_GLOBAL;
+ }
+ if (res.getBoolean(R.bool.config_setColorTransformAcceleratedPerLayer)) {
+ availabilityFlags |= ColorDisplayManager.CAPABILITY_HARDWARE_ACCELERATION_PER_APP;
+ }
+ return availabilityFlags;
+ }
+
/**
* Returns the last time the night display transform activation state was changed, or {@link
* LocalDateTime#MIN} if night display has never been activated.
@@ -1226,10 +1249,10 @@
* Adds a {@link WeakReference<ColorTransformController>} for a newly started activity, and
* invokes {@link ColorTransformController#applyAppSaturation(float[], float[])} if needed.
*/
- public boolean attachColorTransformController(String packageName, int uid,
+ public boolean attachColorTransformController(String packageName, @UserIdInt int userId,
WeakReference<ColorTransformController> controller) {
return mAppSaturationController
- .addColorTransformController(packageName, uid, controller);
+ .addColorTransformController(packageName, userId, controller);
}
}
@@ -1318,6 +1341,18 @@
}
}
+ public int getTransformCapabilities() {
+ getContext().enforceCallingPermission(
+ Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
+ "Permission required to query transform capabilities");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return getTransformCapabilitiesInternal();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 63214ed..cac1a95 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -268,7 +268,7 @@
mTvSystemAudioModeSupport = false;
// Record the last state of System Audio Control before going to standby
synchronized (mLock) {
- mService.writeStringSetting(
+ mService.writeStringSystemProperty(
Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL,
mSystemAudioActivated ? "true" : "false");
}
@@ -330,7 +330,7 @@
@ServiceThreadOnly
protected void setPreferredAddress(int addr) {
assertRunOnServiceThread();
- mService.writeStringSetting(
+ mService.writeStringSystemProperty(
Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, String.valueOf(addr));
}
@@ -469,7 +469,7 @@
protected boolean handleRequestArcInitiate(HdmiCecMessage message) {
assertRunOnServiceThread();
removeAction(ArcInitiationActionFromAvr.class);
- if (!mService.readBooleanSetting(Constants.PROPERTY_ARC_SUPPORT, true)) {
+ if (!mService.readBooleanSystemProperty(Constants.PROPERTY_ARC_SUPPORT, true)) {
mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
} else if (!isDirectConnectToTv()) {
HdmiLogger.debug("AVR device is not directly connected with TV");
@@ -829,7 +829,7 @@
boolean currentMuteStatus =
mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC);
if (currentMuteStatus == newSystemAudioMode) {
- if (mService.readBooleanSetting(
+ if (mService.readBooleanSystemProperty(
Constants.PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE, true)
|| newSystemAudioMode) {
mService.getAudioManager()
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 7a0c279..ef7d241 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -100,7 +100,7 @@
@ServiceThreadOnly
protected void setPreferredAddress(int addr) {
assertRunOnServiceThread();
- mService.writeStringSetting(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
+ mService.writeStringSystemProperty(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
String.valueOf(addr));
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 46219d5..f3a1e46 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -657,9 +657,13 @@
Global.putInt(cr, key, toInt(value));
}
- void writeStringSetting(String key, String value) {
- ContentResolver cr = getContext().getContentResolver();
- Global.putString(cr, key, value);
+ void writeStringSystemProperty(String key, String value) {
+ SystemProperties.set(key, value);
+ }
+
+ @VisibleForTesting
+ boolean readBooleanSystemProperty(String key, boolean defVal) {
+ return SystemProperties.getBoolean(key, defVal);
}
private void initializeCec(int initiatedBy) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index d4b8eb2..944a95d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
-import android.content.ComponentName;
import android.view.inputmethod.InputMethodInfo;
import com.android.server.LocalServices;
@@ -42,11 +41,6 @@
public abstract void hideCurrentInputMethod();
/**
- * Switches to VR InputMethod defined in the packageName of {@param componentName}.
- */
- public abstract void startVrInputMethodNoCheck(ComponentName componentName);
-
- /**
* Returns the list of installed input methods for the specified user.
*
* @param userId The user ID to be queried.
@@ -76,10 +70,6 @@
}
@Override
- public void startVrInputMethodNoCheck(ComponentName componentName) {
- }
-
- @Override
public List<InputMethodInfo> getInputMethodListAsUser(int userId) {
return Collections.emptyList();
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 9041693..3fd3945 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -91,8 +91,6 @@
import android.os.UserManager;
import android.os.UserManagerInternal;
import android.provider.Settings;
-import android.service.vr.IVrManager;
-import android.service.vr.IVrStateCallbacks;
import android.text.TextUtils;
import android.text.style.SuggestionSpan;
import android.util.ArrayMap;
@@ -203,7 +201,6 @@
static final int MSG_CREATE_SESSION = 1050;
static final int MSG_START_INPUT = 2000;
- static final int MSG_START_VR_INPUT = 2010;
static final int MSG_UNBIND_CLIENT = 3000;
static final int MSG_BIND_CLIENT = 3010;
@@ -381,28 +378,6 @@
}
}
- /**
- * VR state callback.
- * Listens for when VR mode finishes.
- */
- private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
- @Override
- public void onVrStateChanged(boolean enabled) {
- if (!enabled) {
- restoreNonVrImeFromSettingsNoCheck();
- }
- }
- };
-
- private void restoreNonVrImeFromSettingsNoCheck() {
- // switch back to non-VR InputMethod from settings.
- synchronized (mMethodMap) {
- if (!mIsVrImeStarted) return;
- mIsVrImeStarted = false;
- updateFromSettingsLocked(false);
- }
- }
-
private static final class ClientDeathRecipient implements IBinder.DeathRecipient {
private final InputMethodManagerService mImms;
private final IInputMethodClient mClient;
@@ -597,9 +572,6 @@
final ImeDisplayValidator mImeDisplayValidator;
- /** True if VR IME started by {@link #startVrInputMethodNoCheck}. */
- boolean mIsVrImeStarted;
-
/**
* If non-null, this is the input method service we are currently connected
* to.
@@ -977,31 +949,6 @@
}
/**
- * Start a VR InputMethod that matches IME with package name of {@param component}.
- * Note: This method is called from {@link android.app.VrManager}.
- */
- private void startVrInputMethodNoCheck(@Nullable ComponentName component) {
- if (component == null) {
- // clear the current VR-only IME (if any) and restore normal IME.
- restoreNonVrImeFromSettingsNoCheck();
- return;
- }
-
- synchronized (mMethodMap) {
- String packageName = component.getPackageName();
- for (InputMethodInfo info : mMethodList) {
- if (TextUtils.equals(info.getPackageName(), packageName) && info.isVrOnly()) {
- // set this is as current inputMethod without updating settings.
- setInputMethodEnabledLocked(info.getId(), true);
- setInputMethodLocked(info.getId(), NOT_A_SUBTYPE_ID);
- mIsVrImeStarted = true;
- break;
- }
- }
- }
- }
-
- /**
* Handles {@link Intent#ACTION_LOCALE_CHANGED}.
*
* <p>Note: For historical reasons, {@link Intent#ACTION_LOCALE_CHANGED} has been sent to all
@@ -1243,7 +1190,7 @@
// Uh oh, current input method is no longer around!
// Pick another one...
Slog.i(TAG, "Current input method removed: " + curInputMethodId);
- updateSystemUiLocked(mCurToken, 0 /* vis */, mBackDisposition);
+ updateSystemUiLocked(0 /* vis */, mBackDisposition);
if (!chooseNewDefaultIMELocked()) {
changed = true;
curIm = null;
@@ -1452,15 +1399,6 @@
AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId);
mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
mSettings, context);
- // Register VR-state listener.
- IVrManager vrManager = (IVrManager) ServiceManager.getService(Context.VR_SERVICE);
- if (vrManager != null) {
- try {
- vrManager.registerListener(mVrStateCallbacks);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to register VR mode state listener.");
- }
- }
}
private void resetDefaultImeLocked(Context context) {
@@ -1572,7 +1510,7 @@
if (mStatusBar != null) {
mStatusBar.setIconVisibility(mSlotIme, false);
}
- updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
+ updateSystemUiLocked(mImeWindowVis, mBackDisposition);
mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
com.android.internal.R.bool.show_ongoing_ime_switcher);
if (mShowOngoingImeSwitcherForPhones) {
@@ -1607,7 +1545,7 @@
// 1) it comes from the system process
// 2) the calling process' user id is identical to the current user id IMMS thinks.
@GuardedBy("mMethodMap")
- private boolean calledFromValidUserLocked(boolean allowCrossProfileAccess) {
+ private boolean calledFromValidUserLocked() {
final int uid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(uid);
if (DEBUG) {
@@ -1623,7 +1561,7 @@
if (userId == mSettings.getCurrentUserId()) {
return true;
}
- if (allowCrossProfileAccess && mSettings.isCurrentProfile(userId)) {
+ if (!PER_PROFILE_IME_ENABLED && mSettings.isCurrentProfile(userId)) {
return true;
}
@@ -1654,16 +1592,11 @@
* @return true if and only if non-null valid token is specified.
*/
@GuardedBy("mMethodMap")
- private boolean calledWithValidTokenLocked(@Nullable IBinder token) {
- if (token == null && Binder.getCallingPid() == Process.myPid()) {
- if (DEBUG) {
- // TODO(b/34851776): Basically it's the caller's fault if we reach here.
- Slog.d(TAG, "Bug 34851776 is detected callers=" + Debug.getCallers(10));
- }
- return false;
+ private boolean calledWithValidTokenLocked(@NonNull IBinder token) {
+ if (token == null) {
+ throw new InvalidParameterException("token must not be null.");
}
- if (token == null || token != mCurToken) {
- // TODO(b/34886274): The semantics of this verification is actually not well-defined.
+ if (token != mCurToken) {
Slog.e(TAG, "Ignoring " + Debug.getCaller() + " due to an invalid token."
+ " uid:" + Binder.getCallingUid() + " token:" + token);
return false;
@@ -1693,25 +1626,7 @@
}
final long ident = Binder.clearCallingIdentity();
try {
- return getInputMethodListLocked(false /* isVrOnly */, resolvedUserIds[0]);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
-
- @Override
- public List<InputMethodInfo> getVrInputMethodList() {
- final int callingUserId = UserHandle.getCallingUserId();
- synchronized (mMethodMap) {
- final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId,
- mSettings.getCurrentUserId(), null);
- if (resolvedUserIds.length != 1) {
- return Collections.emptyList();
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- return getInputMethodListLocked(true /* isVrOnly */, resolvedUserIds[0]);
+ return getInputMethodListLocked(resolvedUserIds[0]);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -1737,8 +1652,7 @@
}
@GuardedBy("mMethodMap")
- private List<InputMethodInfo> getInputMethodListLocked(boolean isVrOnly,
- @UserIdInt int userId) {
+ private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId) {
final ArrayList<InputMethodInfo> methodList;
if (userId == mSettings.getCurrentUserId()) {
// Create a copy.
@@ -1752,7 +1666,6 @@
queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
methodList);
}
- methodList.removeIf(imi -> imi.isVrOnly() != isVrOnly);
return methodList;
}
@@ -2026,8 +1939,8 @@
}
// Compute the final shown display ID with validated cs.selfReportedDisplayId for this
// session & other conditions.
- final int displayIdToShowIme = computeImeDisplayIdForTarget(
- cs.selfReportedDisplayId, mIsVrImeStarted, mImeDisplayValidator);
+ final int displayIdToShowIme = computeImeDisplayIdForTarget(cs.selfReportedDisplayId,
+ mImeDisplayValidator);
if (mCurClient != cs) {
// Was the keyguard locked when switching over to the new client?
@@ -2136,17 +2049,11 @@
* Find the display where the IME should be shown.
*
* @param displayId the ID of the display where the IME client target is.
- * @param isVrImeStarted {@code true} if VR IME started, {@code false} otherwise.
* @param checker instance of {@link ImeDisplayValidator} which is used for
* checking display config to adjust the final target display.
* @return The ID of the display where the IME should be shown.
*/
- static int computeImeDisplayIdForTarget(int displayId, boolean isVrImeStarted,
- @NonNull ImeDisplayValidator checker) {
- // For VR IME, we always show in default display.
- if (isVrImeStarted) {
- return DEFAULT_DISPLAY;
- }
+ static int computeImeDisplayIdForTarget(int displayId, @NonNull ImeDisplayValidator checker) {
if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY) {
// We always assume that the default display id suitable to show the IME window.
return DEFAULT_DISPLAY;
@@ -2260,7 +2167,7 @@
sessionState.session.finishSession();
} catch (RemoteException e) {
Slog.w(TAG, "Session failed to close due to remote exception", e);
- updateSystemUiLocked(mCurToken, 0 /* vis */, mBackDisposition);
+ updateSystemUiLocked(0 /* vis */, mBackDisposition);
}
sessionState.session = null;
}
@@ -2419,14 +2326,14 @@
@BinderThread
@SuppressWarnings("deprecation")
- private void setImeWindowStatus(IBinder token, int vis, int backDisposition) {
+ private void setImeWindowStatus(@NonNull IBinder token, int vis, int backDisposition) {
synchronized (mMethodMap) {
if (!calledWithValidTokenLocked(token)) {
return;
}
mImeWindowVis = vis;
mBackDisposition = backDisposition;
- updateSystemUiLocked(mCurToken, vis, backDisposition);
+ updateSystemUiLocked(vis, backDisposition);
}
final boolean dismissImeOnBackKeyPressed;
@@ -2447,7 +2354,7 @@
}
@BinderThread
- private void reportStartInput(IBinder token, IBinder startInputToken) {
+ private void reportStartInput(@NonNull IBinder token, IBinder startInputToken) {
synchronized (mMethodMap) {
if (!calledWithValidTokenLocked(token)) {
return;
@@ -2461,11 +2368,10 @@
}
// Caution! This method is called in this class. Handle multi-user carefully
- private void updateSystemUiLocked(IBinder token, int vis, int backDisposition) {
- if (!calledWithValidTokenLocked(token)) {
+ private void updateSystemUiLocked(int vis, int backDisposition) {
+ if (mCurToken == null) {
return;
}
-
// TODO: Move this clearing calling identity block to setImeWindowStatus after making sure
// all updateSystemUi happens on system previlege.
final long ident = Binder.clearCallingIdentity();
@@ -2477,7 +2383,7 @@
// mImeWindowVis should be updated before calling shouldShowImeSwitcherLocked().
final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis);
if (mStatusBar != null) {
- mStatusBar.setImeWindowStatus(token, vis, backDisposition,
+ mStatusBar.setImeWindowStatus(mCurToken, vis, backDisposition,
needsToShowImeSwitcher);
}
final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
@@ -2519,54 +2425,6 @@
}
}
- @Override
- public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
- synchronized (mMethodMap) {
- if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
- return;
- }
- final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
- for (int i = 0; i < spans.length; ++i) {
- SuggestionSpan ss = spans[i];
- if (!TextUtils.isEmpty(ss.getNotificationTargetClassName())) {
- mSecureSuggestionSpans.put(ss, currentImi);
- }
- }
- }
- }
-
- @Override
- public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
- synchronized (mMethodMap) {
- if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
- return false;
- }
- final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span);
- // TODO: Do not send the intent if the process of the targetImi is already dead.
- if (targetImi != null) {
- final String[] suggestions = span.getSuggestions();
- if (index < 0 || index >= suggestions.length) return false;
- final String className = span.getNotificationTargetClassName();
- final Intent intent = new Intent();
- // Ensures that only a class in the original IME package will receive the
- // notification.
- intent.setClassName(targetImi.getPackageName(), className);
- intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED);
- intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, originalString);
- intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, suggestions[index]);
- intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, span.hashCode());
- final long ident = Binder.clearCallingIdentity();
- try {
- mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- return true;
- }
- }
- return false;
- }
-
void updateFromSettingsLocked(boolean enabledMayChange) {
updateInputMethodsFromSettingsLocked(enabledMayChange);
updateKeyboardFromSettingsLocked();
@@ -2667,7 +2525,7 @@
setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
if (mCurMethod != null) {
try {
- updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
+ updateSystemUiLocked(mImeWindowVis, mBackDisposition);
mCurMethod.changeInputMethodSubtype(newSubtype);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call changeInputMethodSubtype");
@@ -2705,7 +2563,7 @@
ResultReceiver resultReceiver) {
int uid = Binder.getCallingUid();
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
+ if (!calledFromValidUserLocked()) {
return false;
}
final long ident = Binder.clearCallingIdentity();
@@ -2790,7 +2648,7 @@
ResultReceiver resultReceiver) {
int uid = Binder.getCallingUid();
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
+ if (!calledFromValidUserLocked()) {
return false;
}
final long ident = Binder.clearCallingIdentity();
@@ -2899,7 +2757,7 @@
int unverifiedTargetSdkVersion) {
final int callingUserId = UserHandle.getCallingUserId();
final int userId;
- if (PER_PROFILE_IME_ENABLED && attribute != null && attribute.targetInputMethodUser != null
+ if (attribute != null && attribute.targetInputMethodUser != null
&& attribute.targetInputMethodUser.getIdentifier() != callingUserId) {
mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
"Using EditorInfo.user requires INTERACT_ACROSS_USERS_FULL.");
@@ -2916,9 +2774,6 @@
}
InputBindResult res = null;
synchronized (mMethodMap) {
- // Needs to check the validity before clearing calling identity
- // Note that cross-profile access is always allowed here to allow profile-switching.
- final boolean calledFromValidUser = calledFromValidUserLocked(true);
final int windowDisplayId =
mWindowManagerInternal.getDisplayIdForWindow(windowToken);
final long ident = Binder.clearCallingIdentity();
@@ -2962,10 +2817,12 @@
return InputBindResult.NOT_IME_TARGET_WINDOW;
}
- if (!calledFromValidUser) {
+ // cross-profile access is always allowed here to allow profile-switching.
+ if (!mSettings.isCurrentProfile(userId)) {
Slog.w(TAG, "A background user is requesting window. Hiding IME.");
- Slog.w(TAG, "If you want to interect with IME, you need "
- + "android.permission.INTERACT_ACROSS_USERS_FULL");
+ Slog.w(TAG, "If you need to impersonate a foreground user/profile from"
+ + " a background user, use EditorInfo.targetInputMethodUser with"
+ + " INTERACT_ACROSS_USERS_FULL permission.");
hideCurrentInputLocked(0, null);
return InputBindResult.INVALID_USER;
}
@@ -3142,7 +2999,7 @@
public void showInputMethodPickerFromClient(
IInputMethodClient client, int auxiliarySubtypeMode) {
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
+ if (!calledFromValidUserLocked()) {
return;
}
if(!canShowInputMethodPickerLocked(client)) {
@@ -3183,7 +3040,7 @@
}
@BinderThread
- private void setInputMethod(IBinder token, String id) {
+ private void setInputMethod(@NonNull IBinder token, String id) {
synchronized (mMethodMap) {
if (!calledWithValidTokenLocked(token)) {
return;
@@ -3193,7 +3050,8 @@
}
@BinderThread
- private void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
+ private void setInputMethodAndSubtype(@NonNull IBinder token, String id,
+ InputMethodSubtype subtype) {
synchronized (mMethodMap) {
if (!calledWithValidTokenLocked(token)) {
return;
@@ -3213,7 +3071,7 @@
IInputMethodClient client, String inputMethodId) {
synchronized (mMethodMap) {
// TODO(yukawa): Should we verify the display ID?
- if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
+ if (!calledFromValidUserLocked()) {
return;
}
executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
@@ -3222,7 +3080,7 @@
}
@BinderThread
- private boolean switchToPreviousInputMethod(IBinder token) {
+ private boolean switchToPreviousInputMethod(@NonNull IBinder token) {
synchronized (mMethodMap) {
if (!calledWithValidTokenLocked(token)) {
return false;
@@ -3294,7 +3152,7 @@
}
@BinderThread
- private boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) {
+ private boolean switchToNextInputMethod(@NonNull IBinder token, boolean onlyCurrentIme) {
synchronized (mMethodMap) {
if (!calledWithValidTokenLocked(token)) {
return false;
@@ -3328,7 +3186,7 @@
@Override
public InputMethodSubtype getLastInputMethodSubtype() {
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
+ if (!calledFromValidUserLocked()) {
return null;
}
final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
@@ -3366,7 +3224,7 @@
}
}
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
+ if (!calledFromValidUserLocked()) {
return;
}
if (!mSystemReady) {
@@ -3681,9 +3539,6 @@
case MSG_SET_INTERACTIVE:
handleSetInteractive(msg.arg1 != 0);
return true;
- case MSG_START_VR_INPUT:
- startVrInputMethodNoCheck((ComponentName) msg.obj);
- return true;
case MSG_REPORT_FULLSCREEN_MODE: {
final boolean fullscreen = msg.arg1 != 0;
final ClientState clientState = (ClientState)msg.obj;
@@ -3712,7 +3567,7 @@
private void handleSetInteractive(final boolean interactive) {
synchronized (mMethodMap) {
mIsInteractive = interactive;
- updateSystemUiLocked(mCurToken, interactive ? mImeWindowVis : 0, mBackDisposition);
+ updateSystemUiLocked(interactive ? mImeWindowVis : 0, mBackDisposition);
// Inform the current client of the change in active status
if (mCurClient != null && mCurClient.client != null) {
@@ -3770,6 +3625,9 @@
try {
final InputMethodInfo imi = new InputMethodInfo(context, ri,
additionalSubtypeMap.get(imeId));
+ if (imi.isVrOnly()) {
+ continue; // Skip VR-only IME, which isn't supported for now.
+ }
methodList.add(imi);
methodMap.put(imi.getId(), imi);
if (DEBUG) {
@@ -4043,7 +3901,7 @@
attrs.privateFlags |= PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
attrs.setTitle("Select input method");
w.setAttributes(attrs);
- updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
+ updateSystemUiLocked(mImeWindowVis, mBackDisposition);
mSwitchingDialog.show();
}
}
@@ -4103,7 +3961,7 @@
mSwitchingDialogTitleView = null;
}
- updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
+ updateSystemUiLocked(mImeWindowVis, mBackDisposition);
mDialogBuilder = null;
mIms = null;
}
@@ -4153,17 +4011,7 @@
private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
boolean setSubtypeOnly) {
- // Updates to InputMethod are transient in VR mode. Its not included in history.
- final boolean isVrInput = imi != null && imi.isVrOnly();
- if (!isVrInput) {
- // Update the history of InputMethod and Subtype
- mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
- }
-
- if (isVrInput) {
- // Updates to InputMethod are transient in VR mode. Any changes to Settings are skipped.
- return;
- }
+ mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
// Set Subtype here
if (imi == null || subtypeId < 0) {
@@ -4212,7 +4060,7 @@
public InputMethodSubtype getCurrentInputMethodSubtype() {
synchronized (mMethodMap) {
// TODO: Make this work even for non-current users?
- if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
+ if (!calledFromValidUserLocked()) {
return null;
}
return getCurrentInputMethodSubtypeLocked();
@@ -4258,28 +4106,9 @@
return mCurrentSubtype;
}
- @Override
- public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
- synchronized (mMethodMap) {
- // TODO: Make this work even for non-current users?
- if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
- return false;
- }
- if (subtype != null && mCurMethodId != null) {
- InputMethodInfo imi = mMethodMap.get(mCurMethodId);
- int subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode());
- if (subtypeId != NOT_A_SUBTYPE_ID) {
- setInputMethodLocked(mCurMethodId, subtypeId);
- return true;
- }
- }
- return false;
- }
- }
-
private List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
synchronized (mMethodMap) {
- return getInputMethodListLocked(false, userId);
+ return getInputMethodListLocked(userId);
}
}
@@ -4311,11 +4140,6 @@
}
@Override
- public void startVrInputMethodNoCheck(@Nullable ComponentName componentName) {
- mService.mHandler.obtainMessage(MSG_START_VR_INPUT, componentName).sendToTarget();
- }
-
- @Override
public List<InputMethodInfo> getInputMethodListAsUser(int userId) {
return mService.getInputMethodListAsUser(userId);
}
@@ -4381,7 +4205,7 @@
}
@BinderThread
- private void reportFullscreenMode(IBinder token, boolean fullscreen) {
+ private void reportFullscreenMode(@NonNull IBinder token, boolean fullscreen) {
synchronized (mMethodMap) {
if (!calledWithValidTokenLocked(token)) {
return;
@@ -4702,7 +4526,7 @@
mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter());
for (int userId : userIds) {
final List<InputMethodInfo> methods = all
- ? getInputMethodListLocked(false, userId)
+ ? getInputMethodListLocked(userId)
: getEnabledInputMethodListLocked(userId);
if (userIds.length > 1) {
pr.print("User #");
@@ -4816,8 +4640,10 @@
private static final class InputMethodPrivilegedOperationsImpl
extends IInputMethodPrivilegedOperations.Stub {
private final InputMethodManagerService mImms;
+ @NonNull
private final IBinder mToken;
- InputMethodPrivilegedOperationsImpl(InputMethodManagerService imms, IBinder token) {
+ InputMethodPrivilegedOperationsImpl(InputMethodManagerService imms,
+ @NonNull IBinder token) {
mImms = imms;
mToken = token;
}
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index f304ceb..3222143 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -53,7 +53,6 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
-import android.text.style.SuggestionSpan;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -160,11 +159,6 @@
}
@Override
- public void startVrInputMethodNoCheck(ComponentName componentName) {
- reportNotSupported();
- }
-
- @Override
public List<InputMethodInfo> getInputMethodListAsUser(
@UserIdInt int userId) {
return userIdToInputMethodInfoMapper.getAsList(userId);
@@ -1245,13 +1239,6 @@
@BinderThread
@Override
- public List<InputMethodInfo> getVrInputMethodList() {
- reportNotSupported();
- return Collections.emptyList();
- }
-
- @BinderThread
- @Override
public List<InputMethodInfo> getEnabledInputMethodList() {
return mInputMethodInfoMap.getAsList(UserHandle.getUserId(Binder.getCallingUid()));
}
@@ -1525,20 +1512,6 @@
@BinderThread
@Override
- public void registerSuggestionSpansForNotification(SuggestionSpan[] suggestionSpans) {
- reportNotSupported();
- }
-
- @BinderThread
- @Override
- public boolean notifySuggestionPicked(
- SuggestionSpan span, String originalString, int index) {
- reportNotSupported();
- return false;
- }
-
- @BinderThread
- @Override
public InputMethodSubtype getCurrentInputMethodSubtype() {
reportNotSupported();
return null;
@@ -1546,13 +1519,6 @@
@BinderThread
@Override
- public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
- reportNotSupported();
- return false;
- }
-
- @BinderThread
- @Override
public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
reportNotSupported();
}
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index cc0ac9a..bd12075 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -443,6 +443,16 @@
"qc_window_size_rare_ms";
private static final String KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS =
"qc_max_execution_time_ms";
+ private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE =
+ "qc_max_job_count_active";
+ private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING =
+ "qc_max_job_count_working";
+ private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT =
+ "qc_max_job_count_frequent";
+ private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE =
+ "qc_max_job_count_rare";
+ private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME =
+ "qc_max_count_per_allowed_time";
private static final int DEFAULT_MIN_IDLE_COUNT = 1;
private static final int DEFAULT_MIN_CHARGING_COUNT = 1;
@@ -479,6 +489,15 @@
24 * 60 * 60 * 1000L; // 24 hours
private static final long DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS =
4 * 60 * 60 * 1000L; // 4 hours
+ private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE =
+ 200; // 1200/hr
+ private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING =
+ 1200; // 600/hr
+ private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT =
+ 1800; // 225/hr
+ private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE =
+ 2400; // 100/hr
+ private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 20;
/**
* Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
@@ -682,6 +701,41 @@
public long QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS =
DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS;
+ /**
+ * The maximum number of jobs an app can run within this particular standby bucket's
+ * window size.
+ */
+ public int QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE =
+ DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE;
+
+ /**
+ * The maximum number of jobs an app can run within this particular standby bucket's
+ * window size.
+ */
+ public int QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING =
+ DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING;
+
+ /**
+ * The maximum number of jobs an app can run within this particular standby bucket's
+ * window size.
+ */
+ public int QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT =
+ DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT;
+
+ /**
+ * The maximum number of jobs an app can run within this particular standby bucket's
+ * window size.
+ */
+ public int QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE =
+ DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE;
+
+ /**
+ * The maximum number of jobs that can run within the past
+ * {@link #QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS}.
+ */
+ public int QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME =
+ DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME;
+
private final KeyValueListParser mParser = new KeyValueListParser(',');
void updateConstantsLocked(String value) {
@@ -767,6 +821,21 @@
QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = mParser.getDurationMillis(
KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS,
DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS);
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = mParser.getInt(
+ KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE,
+ DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE);
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = mParser.getInt(
+ KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING,
+ DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING);
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = mParser.getInt(
+ KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT,
+ DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT);
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = mParser.getInt(
+ KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE,
+ DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE);
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = mParser.getInt(
+ KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME,
+ DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME);
}
void dump(IndentingPrintWriter pw) {
@@ -823,6 +892,16 @@
QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS).println();
pw.printPair(KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS,
QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS).println();
+ pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE,
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE).println();
+ pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING,
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING).println();
+ pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT,
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT).println();
+ pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE,
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE).println();
+ pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME,
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME).println();
pw.decreaseIndent();
}
@@ -872,6 +951,16 @@
QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS);
proto.write(ConstantsProto.QuotaController.MAX_EXECUTION_TIME_MS,
QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS);
+ proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_ACTIVE,
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE);
+ proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_WORKING,
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING);
+ proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_FREQUENT,
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT);
+ proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_RARE,
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE);
+ proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_PER_ALLOWED_TIME,
+ QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME);
proto.end(qcToken);
proto.end(token);
diff --git a/services/core/java/com/android/server/job/controllers/QuotaController.java b/services/core/java/com/android/server/job/controllers/QuotaController.java
index c16d1b4..5a0b991 100644
--- a/services/core/java/com/android/server/job/controllers/QuotaController.java
+++ b/services/core/java/com/android/server/job/controllers/QuotaController.java
@@ -230,10 +230,10 @@
@VisibleForTesting
static class ExecutionStats {
/**
- * The time at which this record should be considered invalid, in the elapsed realtime
- * timebase.
+ * The time after which this record should be considered invalid (out of date), in the
+ * elapsed realtime timebase.
*/
- public long invalidTimeElapsed;
+ public long expirationTimeElapsed;
public long windowSizeMs;
@@ -241,29 +241,45 @@
public long executionTimeInWindowMs;
public int bgJobCountInWindow;
- /** The total amount of time the app ran in the last {@link MAX_PERIOD_MS}. */
+ /** The total amount of time the app ran in the last {@link #MAX_PERIOD_MS}. */
public long executionTimeInMaxPeriodMs;
public int bgJobCountInMaxPeriod;
/**
- * The time after which the sum of all the app's sessions plus {@link mQuotaBufferMs} equals
- * the quota. This is only valid if
- * executionTimeInWindowMs >= {@link mAllowedTimePerPeriodMs} or
- * executionTimeInMaxPeriodMs >= {@link mMaxExecutionTimeMs}.
+ * The time after which the sum of all the app's sessions plus {@link #mQuotaBufferMs}
+ * equals the quota. This is only valid if
+ * executionTimeInWindowMs >= {@link #mAllowedTimePerPeriodMs} or
+ * executionTimeInMaxPeriodMs >= {@link #mMaxExecutionTimeMs}.
*/
public long quotaCutoffTimeElapsed;
+ /**
+ * The time after which {@link #jobCountInAllowedTime} should be considered invalid, in the
+ * elapsed realtime timebase.
+ */
+ public long jobCountExpirationTimeElapsed;
+
+ /**
+ * The number of jobs that ran in at least the last {@link #mAllowedTimePerPeriodMs}.
+ * It may contain a few stale entries since cleanup won't happen exactly every
+ * {@link #mAllowedTimePerPeriodMs}.
+ */
+ public int jobCountInAllowedTime;
+
@Override
public String toString() {
return new StringBuilder()
- .append("invalidTime=").append(invalidTimeElapsed).append(", ")
+ .append("expirationTime=").append(expirationTimeElapsed).append(", ")
.append("windowSize=").append(windowSizeMs).append(", ")
.append("executionTimeInWindow=").append(executionTimeInWindowMs).append(", ")
.append("bgJobCountInWindow=").append(bgJobCountInWindow).append(", ")
.append("executionTimeInMaxPeriod=").append(executionTimeInMaxPeriodMs)
.append(", ")
.append("bgJobCountInMaxPeriod=").append(bgJobCountInMaxPeriod).append(", ")
- .append("quotaCutoffTime=").append(quotaCutoffTimeElapsed)
+ .append("quotaCutoffTime=").append(quotaCutoffTimeElapsed).append(", ")
+ .append("jobCountExpirationTime").append(jobCountExpirationTimeElapsed)
+ .append(", ")
+ .append("jobCountInAllowedTime").append(jobCountInAllowedTime)
.toString();
}
@@ -271,13 +287,15 @@
public boolean equals(Object obj) {
if (obj instanceof ExecutionStats) {
ExecutionStats other = (ExecutionStats) obj;
- return this.invalidTimeElapsed == other.invalidTimeElapsed
+ return this.expirationTimeElapsed == other.expirationTimeElapsed
&& this.windowSizeMs == other.windowSizeMs
&& this.executionTimeInWindowMs == other.executionTimeInWindowMs
&& this.bgJobCountInWindow == other.bgJobCountInWindow
&& this.executionTimeInMaxPeriodMs == other.executionTimeInMaxPeriodMs
&& this.bgJobCountInMaxPeriod == other.bgJobCountInMaxPeriod
- && this.quotaCutoffTimeElapsed == other.quotaCutoffTimeElapsed;
+ && this.quotaCutoffTimeElapsed == other.quotaCutoffTimeElapsed
+ && this.jobCountExpirationTimeElapsed == other.jobCountExpirationTimeElapsed
+ && this.jobCountInAllowedTime == other.jobCountInAllowedTime;
} else {
return false;
}
@@ -286,13 +304,15 @@
@Override
public int hashCode() {
int result = 0;
- result = 31 * result + hashLong(invalidTimeElapsed);
+ result = 31 * result + hashLong(expirationTimeElapsed);
result = 31 * result + hashLong(windowSizeMs);
result = 31 * result + hashLong(executionTimeInWindowMs);
result = 31 * result + bgJobCountInWindow;
result = 31 * result + hashLong(executionTimeInMaxPeriodMs);
result = 31 * result + bgJobCountInMaxPeriod;
result = 31 * result + hashLong(quotaCutoffTimeElapsed);
+ result = 31 * result + hashLong(jobCountExpirationTimeElapsed);
+ result = 31 * result + jobCountInAllowedTime;
return result;
}
}
@@ -320,7 +340,7 @@
/**
* List of jobs that started while the UID was in the TOP state. There will be no more than
- * 16 ({@link JobSchedulerService.MAX_JOB_CONTEXTS_COUNT}) running at once, so an ArraySet is
+ * 16 ({@link JobSchedulerService#MAX_JOB_CONTEXTS_COUNT}) running at once, so an ArraySet is
* fine.
*/
private final ArraySet<JobStatus> mTopStartedJobs = new ArraySet<>();
@@ -343,7 +363,7 @@
private long mAllowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
/**
- * The maximum amount of time an app can have its jobs running within a {@link MAX_PERIOD_MS}
+ * The maximum amount of time an app can have its jobs running within a {@link #MAX_PERIOD_MS}
* window.
*/
private long mMaxExecutionTimeMs = 4 * 60 * MINUTE_IN_MILLIS;
@@ -355,17 +375,20 @@
private long mQuotaBufferMs = 30 * 1000L; // 30 seconds
/**
- * {@link mAllowedTimePerPeriodMs} - {@link mQuotaBufferMs}. This can be used to determine when
- * an app will have enough quota to transition from out-of-quota to in-quota.
+ * {@link #mAllowedTimePerPeriodMs} - {@link #mQuotaBufferMs}. This can be used to determine
+ * when an app will have enough quota to transition from out-of-quota to in-quota.
*/
private long mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
/**
- * {@link mMaxExecutionTimeMs} - {@link mQuotaBufferMs}. This can be used to determine when an
+ * {@link #mMaxExecutionTimeMs} - {@link #mQuotaBufferMs}. This can be used to determine when an
* app will have enough quota to transition from out-of-quota to in-quota.
*/
private long mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
+ /** The maximum number of jobs that can run within the past {@link #mAllowedTimePerPeriodMs}. */
+ private int mMaxJobCountPerAllowedTime = 20;
+
private long mNextCleanupTimeElapsed = 0;
private final AlarmManager.OnAlarmListener mSessionCleanupAlarmListener =
new AlarmManager.OnAlarmListener() {
@@ -412,6 +435,23 @@
/** The maximum period any bucket can have. */
private static final long MAX_PERIOD_MS = 24 * 60 * MINUTE_IN_MILLIS;
+ /**
+ * The maximum number of jobs based on its standby bucket. For each max value count in the
+ * array, the app will not be allowed to run more than that many number of jobs within the
+ * latest time interval of its rolling window size.
+ *
+ * @see #mBucketPeriodsMs
+ */
+ private final int[] mMaxBucketJobCounts = new int[] {
+ 200, // ACTIVE -- 1200/hr
+ 1200, // WORKING -- 600/hr
+ 1800, // FREQUENT -- 225/hr
+ 2400 // RARE -- 100/hr
+ };
+
+ /** The minimum number of jobs that any bucket will be allowed to run. */
+ private static final int MIN_BUCKET_JOB_COUNT = 100;
+
/** An app has reached its quota. The message should contain a {@link Package} object. */
private static final int MSG_REACHED_QUOTA = 0;
/** Drop any old timing sessions. */
@@ -463,17 +503,21 @@
@Override
public void prepareForExecutionLocked(JobStatus jobStatus) {
if (DEBUG) Slog.d(TAG, "Prepping for " + jobStatus.toShortString());
+
+ final int uid = jobStatus.getSourceUid();
+ if (mActivityManagerInternal.getUidProcessState(uid) <= ActivityManager.PROCESS_STATE_TOP) {
+ mTopStartedJobs.add(jobStatus);
+ // Top jobs won't count towards quota so there's no need to involve the Timer.
+ return;
+ }
+
final int userId = jobStatus.getSourceUserId();
final String packageName = jobStatus.getSourcePackageName();
- final int uid = jobStatus.getSourceUid();
Timer timer = mPkgTimers.get(userId, packageName);
if (timer == null) {
timer = new Timer(uid, userId, packageName);
mPkgTimers.add(userId, packageName, timer);
}
- if (mActivityManagerInternal.getUidProcessState(uid) == ActivityManager.PROCESS_STATE_TOP) {
- mTopStartedJobs.add(jobStatus);
- }
timer.startTrackingJob(jobStatus);
}
@@ -548,6 +592,36 @@
mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
changed = true;
}
+ int newMaxCountPerAllowedPeriod = Math.max(10,
+ mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME);
+ if (mMaxJobCountPerAllowedTime != newMaxCountPerAllowedPeriod) {
+ mMaxJobCountPerAllowedTime = newMaxCountPerAllowedPeriod;
+ changed = true;
+ }
+ int newActiveMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
+ Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE));
+ if (mMaxBucketJobCounts[ACTIVE_INDEX] != newActiveMaxJobCount) {
+ mMaxBucketJobCounts[ACTIVE_INDEX] = newActiveMaxJobCount;
+ changed = true;
+ }
+ int newWorkingMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
+ Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING));
+ if (mMaxBucketJobCounts[WORKING_INDEX] != newWorkingMaxJobCount) {
+ mMaxBucketJobCounts[WORKING_INDEX] = newWorkingMaxJobCount;
+ changed = true;
+ }
+ int newFrequentMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
+ Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT));
+ if (mMaxBucketJobCounts[FREQUENT_INDEX] != newFrequentMaxJobCount) {
+ mMaxBucketJobCounts[FREQUENT_INDEX] = newFrequentMaxJobCount;
+ changed = true;
+ }
+ int newRareMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
+ Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE));
+ if (mMaxBucketJobCounts[RARE_INDEX] != newRareMaxJobCount) {
+ mMaxBucketJobCounts[RARE_INDEX] = newRareMaxJobCount;
+ changed = true;
+ }
if (changed) {
// Update job bookkeeping out of band.
@@ -631,18 +705,39 @@
return isTopStartedJob(jobStatus)
|| isUidInForeground(jobStatus.getSourceUid())
|| isWithinQuotaLocked(
- jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
+ jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
}
- private boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName,
+ @VisibleForTesting
+ boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName,
final int standbyBucket) {
if (standbyBucket == NEVER_INDEX) return false;
// This check is needed in case the flag is toggled after a job has been registered.
if (!mShouldThrottle) return true;
// Quota constraint is not enforced while charging or when parole is on.
- return mChargeTracker.isCharging() || mInParole
- || getRemainingExecutionTimeLocked(userId, packageName, standbyBucket) > 0;
+ if (mChargeTracker.isCharging() || mInParole) {
+ return true;
+ }
+
+ return getRemainingExecutionTimeLocked(userId, packageName, standbyBucket) > 0
+ && isUnderJobCountQuotaLocked(userId, packageName, standbyBucket);
+ }
+
+ private boolean isUnderJobCountQuotaLocked(final int userId, @NonNull final String packageName,
+ final int standbyBucket) {
+ ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket, false);
+ return isUnderJobCountQuotaLocked(stats, standbyBucket);
+ }
+
+ private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats,
+ final int standbyBucket) {
+ final long now = sElapsedRealtimeClock.millis();
+ final boolean isUnderAllowedTimeQuota =
+ (stats.jobCountExpirationTimeElapsed <= now
+ || stats.jobCountInAllowedTime < mMaxJobCountPerAllowedTime);
+ return isUnderAllowedTimeQuota
+ && (stats.bgJobCountInWindow < mMaxBucketJobCounts[standbyBucket]);
}
@VisibleForTesting
@@ -679,6 +774,13 @@
@NonNull
ExecutionStats getExecutionStatsLocked(final int userId, @NonNull final String packageName,
final int standbyBucket) {
+ return getExecutionStatsLocked(userId, packageName, standbyBucket, true);
+ }
+
+ @NonNull
+ private ExecutionStats getExecutionStatsLocked(final int userId,
+ @NonNull final String packageName, final int standbyBucket,
+ final boolean refreshStatsIfOld) {
if (standbyBucket == NEVER_INDEX) {
Slog.wtf(TAG, "getExecutionStatsLocked called for a NEVER app.");
return new ExecutionStats();
@@ -693,14 +795,16 @@
stats = new ExecutionStats();
appStats[standbyBucket] = stats;
}
- final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket];
- Timer timer = mPkgTimers.get(userId, packageName);
- if ((timer != null && timer.isActive())
- || stats.invalidTimeElapsed <= sElapsedRealtimeClock.millis()
- || stats.windowSizeMs != bucketWindowSizeMs) {
- // The stats are no longer valid.
- stats.windowSizeMs = bucketWindowSizeMs;
- updateExecutionStatsLocked(userId, packageName, stats);
+ if (refreshStatsIfOld) {
+ final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket];
+ Timer timer = mPkgTimers.get(userId, packageName);
+ if ((timer != null && timer.isActive())
+ || stats.expirationTimeElapsed <= sElapsedRealtimeClock.millis()
+ || stats.windowSizeMs != bucketWindowSizeMs) {
+ // The stats are no longer valid.
+ stats.windowSizeMs = bucketWindowSizeMs;
+ updateExecutionStatsLocked(userId, packageName, stats);
+ }
}
return stats;
@@ -717,14 +821,14 @@
Timer timer = mPkgTimers.get(userId, packageName);
final long nowElapsed = sElapsedRealtimeClock.millis();
- stats.invalidTimeElapsed = nowElapsed + MAX_PERIOD_MS;
+ stats.expirationTimeElapsed = nowElapsed + MAX_PERIOD_MS;
if (timer != null && timer.isActive()) {
stats.executionTimeInWindowMs =
stats.executionTimeInMaxPeriodMs = timer.getCurrentDuration(nowElapsed);
stats.bgJobCountInWindow = stats.bgJobCountInMaxPeriod = timer.getBgJobCount();
// If the timer is active, the value will be stale at the next method call, so
// invalidate now.
- stats.invalidTimeElapsed = nowElapsed;
+ stats.expirationTimeElapsed = nowElapsed;
if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
nowElapsed - mAllowedTimeIntoQuotaMs);
@@ -800,7 +904,7 @@
break;
}
}
- stats.invalidTimeElapsed = nowElapsed + emptyTimeMs;
+ stats.expirationTimeElapsed = nowElapsed + emptyTimeMs;
}
private void invalidateAllExecutionStatsLocked(final int userId,
@@ -811,13 +915,35 @@
for (int i = 0; i < appStats.length; ++i) {
ExecutionStats stats = appStats[i];
if (stats != null) {
- stats.invalidTimeElapsed = nowElapsed;
+ stats.expirationTimeElapsed = nowElapsed;
}
}
}
}
@VisibleForTesting
+ void incrementJobCount(final int userId, @NonNull final String packageName, int count) {
+ final long now = sElapsedRealtimeClock.millis();
+ ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName);
+ if (appStats == null) {
+ appStats = new ExecutionStats[mBucketPeriodsMs.length];
+ mExecutionStatsCache.add(userId, packageName, appStats);
+ }
+ for (int i = 0; i < appStats.length; ++i) {
+ ExecutionStats stats = appStats[i];
+ if (stats == null) {
+ stats = new ExecutionStats();
+ appStats[i] = stats;
+ }
+ if (stats.jobCountExpirationTimeElapsed <= now) {
+ stats.jobCountExpirationTimeElapsed = now + mAllowedTimePerPeriodMs;
+ stats.jobCountInAllowedTime = 0;
+ }
+ stats.jobCountInAllowedTime += count;
+ }
+ }
+
+ @VisibleForTesting
void saveTimingSession(final int userId, @NonNull final String packageName,
@NonNull final TimingSession session) {
synchronized (mLock) {
@@ -1023,9 +1149,12 @@
final String pkgString = string(userId, packageName);
ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
+ final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket);
+
QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
if (stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs
- && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs) {
+ && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs
+ && isUnderJobCountQuota) {
// Already in quota. Why was this method called?
if (DEBUG) {
Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString
@@ -1042,18 +1171,22 @@
mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget();
return;
}
+
if (alarmListener == null) {
alarmListener = new QcAlarmListener(userId, packageName);
mInQuotaAlarmListeners.add(userId, packageName, alarmListener);
}
// The time this app will have quota again.
- long inQuotaTimeElapsed =
- stats.quotaCutoffTimeElapsed + stats.windowSizeMs;
+ long inQuotaTimeElapsed = stats.quotaCutoffTimeElapsed + stats.windowSizeMs;
if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeMs) {
inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
stats.quotaCutoffTimeElapsed + MAX_PERIOD_MS);
}
+ if (!isUnderJobCountQuota) {
+ inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
+ stats.jobCountExpirationTimeElapsed + mAllowedTimePerPeriodMs);
+ }
// Only schedule the alarm if:
// 1. There isn't one currently scheduled
// 2. The new alarm is significantly earlier than the previous alarm (which could be the
@@ -1228,6 +1361,7 @@
mRunningBgJobs.add(jobStatus);
if (shouldTrackLocked()) {
mBgJobCount++;
+ incrementJobCount(mPkg.userId, mPkg.packageName, 1);
if (mRunningBgJobs.size() == 1) {
// Started tracking the first job.
mStartTimeElapsed = sElapsedRealtimeClock.millis();
@@ -1324,6 +1458,7 @@
// repeatedly plugged in and unplugged, or an app changes foreground state
// very frequently, the job count for a package may be artificially high.
mBgJobCount = mRunningBgJobs.size();
+ incrementJobCount(mPkg.userId, mPkg.packageName, mBgJobCount);
// Starting the timer means that all cached execution stats are now
// incorrect.
invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName);
@@ -1604,6 +1739,12 @@
@VisibleForTesting
@NonNull
+ int[] getBucketMaxJobCounts() {
+ return mMaxBucketJobCounts;
+ }
+
+ @VisibleForTesting
+ @NonNull
long[] getBucketWindowSizes() {
return mBucketPeriodsMs;
}
@@ -1631,6 +1772,11 @@
}
@VisibleForTesting
+ int getMaxJobCountPerAllowedTime() {
+ return mMaxJobCountPerAllowedTime;
+ }
+
+ @VisibleForTesting
@Nullable
List<TimingSession> getTimingSessions(int userId, String packageName) {
return mTimingSessions.get(userId, packageName);
diff --git a/services/core/java/com/android/server/location/ActivityRecognitionProxy.java b/services/core/java/com/android/server/location/ActivityRecognitionProxy.java
new file mode 100644
index 0000000..22fabb2
--- /dev/null
+++ b/services/core/java/com/android/server/location/ActivityRecognitionProxy.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2014 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.location;
+
+import android.content.Context;
+import android.hardware.location.ActivityRecognitionHardware;
+import android.hardware.location.IActivityRecognitionHardwareClient;
+import android.hardware.location.IActivityRecognitionHardwareWatcher;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.server.ServiceWatcher;
+
+/**
+ * Proxy class to bind GmsCore to the ActivityRecognitionHardware.
+ *
+ * @hide
+ */
+public class ActivityRecognitionProxy {
+
+ private static final String TAG = "ActivityRecognitionProxy";
+
+ /**
+ * Creates an instance of the proxy and binds it to the appropriate FusedProvider.
+ *
+ * @return An instance of the proxy if it could be bound, null otherwise.
+ */
+ public static ActivityRecognitionProxy createAndBind(
+ Context context,
+ boolean activityRecognitionHardwareIsSupported,
+ ActivityRecognitionHardware activityRecognitionHardware,
+ int overlaySwitchResId,
+ int defaultServicePackageNameResId,
+ int initialPackageNameResId) {
+ ActivityRecognitionProxy activityRecognitionProxy = new ActivityRecognitionProxy(
+ context,
+ activityRecognitionHardwareIsSupported,
+ activityRecognitionHardware,
+ overlaySwitchResId,
+ defaultServicePackageNameResId,
+ initialPackageNameResId);
+
+ if (activityRecognitionProxy.mServiceWatcher.start()) {
+ return activityRecognitionProxy;
+ } else {
+ return null;
+ }
+ }
+
+ private final ServiceWatcher mServiceWatcher;
+ private final boolean mIsSupported;
+ private final ActivityRecognitionHardware mInstance;
+
+ private ActivityRecognitionProxy(
+ Context context,
+ boolean activityRecognitionHardwareIsSupported,
+ ActivityRecognitionHardware activityRecognitionHardware,
+ int overlaySwitchResId,
+ int defaultServicePackageNameResId,
+ int initialPackageNameResId) {
+ mIsSupported = activityRecognitionHardwareIsSupported;
+ mInstance = activityRecognitionHardware;
+
+ mServiceWatcher = new ServiceWatcher(
+ context,
+ TAG,
+ "com.android.location.service.ActivityRecognitionProvider",
+ overlaySwitchResId,
+ defaultServicePackageNameResId,
+ initialPackageNameResId,
+ BackgroundThread.getHandler()) {
+ @Override
+ protected void onBind() {
+ runOnBinder(ActivityRecognitionProxy.this::initializeService);
+ }
+ };
+ }
+
+ private void initializeService(IBinder binder) {
+ try {
+ String descriptor = binder.getInterfaceDescriptor();
+
+ if (IActivityRecognitionHardwareWatcher.class.getCanonicalName().equals(
+ descriptor)) {
+ IActivityRecognitionHardwareWatcher watcher =
+ IActivityRecognitionHardwareWatcher.Stub.asInterface(binder);
+ if (mInstance != null) {
+ watcher.onInstanceChanged(mInstance);
+ }
+ } else if (IActivityRecognitionHardwareClient.class.getCanonicalName()
+ .equals(descriptor)) {
+ IActivityRecognitionHardwareClient client =
+ IActivityRecognitionHardwareClient.Stub.asInterface(binder);
+ client.onAvailabilityChanged(mIsSupported, mInstance);
+ } else {
+ Log.e(TAG, "Invalid descriptor found on connection: " + descriptor);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 8734ceb6..a9ae74f 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -947,6 +947,10 @@
public void setSeparateProfileChallengeEnabled(int userId, boolean enabled,
String managedUserPassword) {
checkWritePermission(userId);
+ if (!mLockPatternUtils.hasSecureLockScreen()) {
+ throw new UnsupportedOperationException(
+ "This operation requires secure lock screen feature.");
+ }
synchronized (mSeparateChallengeLock) {
setSeparateProfileChallengeEnabledLocked(userId, enabled, managedUserPassword);
}
@@ -1305,6 +1309,10 @@
public void setLockCredential(String credential, int type, String savedCredential,
int requestedQuality, int userId)
throws RemoteException {
+ if (!mLockPatternUtils.hasSecureLockScreen()) {
+ throw new UnsupportedOperationException(
+ "This operation requires secure lock screen feature");
+ }
checkWritePermission(userId);
synchronized (mSeparateChallengeLock) {
setLockCredentialInternal(credential, type, savedCredential, requestedQuality, userId);
@@ -2906,6 +2914,10 @@
@Override
public boolean setLockCredentialWithToken(String credential, int type, long tokenHandle,
byte[] token, int requestedQuality, int userId) {
+ if (!mLockPatternUtils.hasSecureLockScreen()) {
+ throw new UnsupportedOperationException(
+ "This operation requires secure lock screen feature.");
+ }
try {
return LockSettingsService.this.setLockCredentialWithToken(credential, type,
tokenHandle, token, requestedQuality, userId);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index 07f23ce..6163077 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -18,6 +18,7 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+
import static com.android.internal.widget.LockPatternUtils.stringToPattern;
import android.app.ActivityManager;
@@ -58,6 +59,18 @@
mCurrentUserId = ActivityManager.getService().getCurrentUser().id;
parseArgs();
+ if (!mLockPatternUtils.hasSecureLockScreen()) {
+ switch (cmd) {
+ case COMMAND_HELP:
+ case COMMAND_GET_DISABLED:
+ case COMMAND_SET_DISABLED:
+ break;
+ default:
+ getErrPrintWriter().println(
+ "The device does not support lock screen - ignoring the command.");
+ return -1;
+ }
+ }
if (!checkCredential()) {
return -1;
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 7fffe8e..af790f2 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -27,14 +27,14 @@
import android.media.Rating;
import android.media.VolumeProvider;
import android.media.session.ControllerCallbackLink;
-import android.media.session.ISession;
-import android.media.session.ISessionController;
+import android.media.session.ControllerLink;
import android.media.session.MediaController;
import android.media.session.MediaController.PlaybackInfo;
import android.media.session.MediaSession;
import android.media.session.MediaSession.QueueItem;
import android.media.session.PlaybackState;
import android.media.session.SessionCallbackLink;
+import android.media.session.SessionLink;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -77,8 +77,9 @@
private final int mUserId;
private final String mPackageName;
private final String mTag;
- private final ControllerStub mController;
- private final SessionStub mSession;
+ private final ControllerLink mController;
+ private final MediaSession.Token mSessionToken;
+ private final SessionLink mSession;
private final SessionCb mSessionCb;
private final MediaSessionService.ServiceImpl mService;
private final Context mContext;
@@ -127,8 +128,9 @@
mUserId = userId;
mPackageName = ownerPackageName;
mTag = tag;
- mController = new ControllerStub();
- mSession = new SessionStub();
+ mController = new ControllerLink(new ControllerStub());
+ mSessionToken = new MediaSession.Token(mController);
+ mSession = new SessionLink(new SessionStub());
mSessionCb = new SessionCb(cb);
mService = service;
mContext = mService.getContext();
@@ -139,24 +141,33 @@
}
/**
- * Get the binder for the {@link MediaSession}.
+ * Get the session link for the {@link MediaSession}.
*
- * @return The session binder apps talk to.
+ * @return The session link apps talk to.
*/
- public ISession getSessionBinder() {
+ public SessionLink getSessionBinder() {
return mSession;
}
/**
- * Get the binder for the {@link MediaController}.
+ * Get the controller link for the {@link MediaController}.
*
- * @return The controller binder apps talk to.
+ * @return The controller link apps talk to.
*/
- public ISessionController getControllerBinder() {
+ public ControllerLink getControllerLink() {
return mController;
}
/**
+ * Get the session token for creating {@link MediaController}.
+ *
+ * @return The session token.
+ */
+ public MediaSession.Token getSessionToken() {
+ return mSessionToken;
+ }
+
+ /**
* Get the info for this session.
*
* @return Info that identifies this session.
@@ -642,7 +653,7 @@
if (mDestroyed) {
return;
}
- PlaybackInfo info = mController.getVolumeAttributes();
+ PlaybackInfo info = getVolumeAttributes();
for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
try {
@@ -750,6 +761,25 @@
return -1;
}
+ private PlaybackInfo getVolumeAttributes() {
+ int volumeType;
+ AudioAttributes attributes;
+ synchronized (mLock) {
+ if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
+ int current = mOptimisticVolume != -1 ? mOptimisticVolume : mCurrentVolume;
+ return new PlaybackInfo(mVolumeType, mVolumeControlType, mMaxVolume, current,
+ mAudioAttrs);
+ }
+ volumeType = mVolumeType;
+ attributes = mAudioAttrs;
+ }
+ int stream = AudioAttributes.toLegacyStreamType(attributes);
+ int max = mAudioManager.getStreamMaxVolume(stream);
+ int current = mAudioManager.getStreamVolume(stream);
+ return new PlaybackInfo(volumeType, VolumeProvider.VOLUME_CONTROL_ABSOLUTE, max,
+ current, attributes);
+ }
+
private final Runnable mClearOptimisticVolumeRunnable = new Runnable() {
@Override
public void run() {
@@ -761,9 +791,9 @@
}
};
- private final class SessionStub extends ISession.Stub {
+ private final class SessionStub extends SessionLink.SessionStub {
@Override
- public void destroy() {
+ public void destroySession() {
final long token = Binder.clearCallingIdentity();
try {
mService.destroySession(MediaSessionRecord.this);
@@ -779,7 +809,7 @@
}
@Override
- public ISessionController getController() {
+ public ControllerLink getController() {
return mController;
}
@@ -798,8 +828,8 @@
@Override
public void setFlags(int flags) {
if ((flags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
- int pid = getCallingPid();
- int uid = getCallingUid();
+ int pid = Binder.getCallingPid();
+ int uid = Binder.getCallingUid();
mService.enforcePhoneStatePermission(pid, uid);
}
mFlags = flags;
@@ -1183,7 +1213,7 @@
}
}
- class ControllerStub extends ISessionController.Stub {
+ class ControllerStub extends ControllerLink.ControllerStub {
@Override
public void sendCommand(String packageName, ControllerCallbackLink caller,
String command, Bundle args, ResultReceiver cb) {
@@ -1199,7 +1229,7 @@
}
@Override
- public void registerCallbackListener(String packageName, ControllerCallbackLink cb) {
+ public void registerCallback(String packageName, ControllerCallbackLink cb) {
synchronized (mLock) {
// If this session is already destroyed tell the caller and
// don't add them.
@@ -1223,7 +1253,7 @@
}
@Override
- public void unregisterCallbackListener(ControllerCallbackLink cb) {
+ public void unregisterCallback(ControllerCallbackLink cb) {
synchronized (mLock) {
int index = getControllerHolderIndexForCb(cb);
if (index != -1) {
@@ -1257,22 +1287,7 @@
@Override
public PlaybackInfo getVolumeAttributes() {
- int volumeType;
- AudioAttributes attributes;
- synchronized (mLock) {
- if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
- int current = mOptimisticVolume != -1 ? mOptimisticVolume : mCurrentVolume;
- return new PlaybackInfo(mVolumeType, mVolumeControlType, mMaxVolume, current,
- mAudioAttrs);
- }
- volumeType = mVolumeType;
- attributes = mAudioAttrs;
- }
- int stream = AudioAttributes.toLegacyStreamType(attributes);
- int max = mAudioManager.getStreamMaxVolume(stream);
- int current = mAudioManager.getStreamVolume(stream);
- return new PlaybackInfo(volumeType, VolumeProvider.VOLUME_CONTROL_ABSOLUTE, max,
- current, attributes);
+ return MediaSessionRecord.this.getVolumeAttributes();
}
@Override
@@ -1449,11 +1464,6 @@
public int getRatingType() {
return mRatingType;
}
-
- @Override
- public boolean isTransportControlEnabled() {
- return MediaSessionRecord.this.isTransportControlEnabled();
- }
}
private class ControllerCallbackLinkHolder {
diff --git a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
index f374c6d..1541b1d 100644
--- a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
@@ -45,16 +45,17 @@
import android.media.MediaController2;
import android.media.Session2CommandGroup;
import android.media.Session2Token;
+import android.media.session.ControllerLink;
import android.media.session.IActiveSessionsListener;
import android.media.session.ICallback;
import android.media.session.IOnMediaKeyListener;
import android.media.session.IOnVolumeKeyLongPressListener;
-import android.media.session.ISession;
import android.media.session.ISession2TokensListener;
import android.media.session.ISessionManager;
import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
import android.media.session.SessionCallbackLink;
+import android.media.session.SessionLink;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -288,7 +289,7 @@
return;
}
try {
- mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
+ mRvc.remoteVolumeChanged(session.getSessionToken(), flags);
} catch (Exception e) {
Log.wtf(TAG, "Error sending volume change to system UI.", e);
}
@@ -614,7 +615,7 @@
int size = records.size();
ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
for (int i = 0; i < size; i++) {
- tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
+ tokens.add(records.get(i).getSessionToken());
}
pushRemoteVolumeUpdateLocked(userId);
for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
@@ -641,7 +642,7 @@
return;
}
MediaSessionRecord record = user.mPriorityStack.getDefaultRemoteSession(userId);
- mRvc.updateRemoteController(record == null ? null : record.getControllerBinder());
+ mRvc.updateRemoteController(record == null ? null : record.getSessionToken());
} catch (RemoteException e) {
Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
}
@@ -858,7 +859,7 @@
MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked();
if (mediaButtonSession != null) {
mCallback.onAddressedPlayerChangedToMediaSession(
- new MediaSession.Token(mediaButtonSession.getControllerBinder()));
+ mediaButtonSession.getSessionToken());
} else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
mCurrentFullUserRecord.mLastMediaButtonReceiver
@@ -978,7 +979,7 @@
private boolean mVoiceButtonHandled = false;
@Override
- public ISession createSession(String packageName, SessionCallbackLink cb, String tag,
+ public SessionLink createSession(String packageName, SessionCallbackLink cb, String tag,
int userId) throws RemoteException {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
@@ -1023,18 +1024,18 @@
}
@Override
- public List<IBinder> getSessions(ComponentName componentName, int userId) {
+ public List<ControllerLink> getSessions(ComponentName componentName, int userId) {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
- ArrayList<IBinder> binders = new ArrayList<IBinder>();
+ ArrayList<ControllerLink> binders = new ArrayList<>();
synchronized (mLock) {
List<MediaSessionRecord> records = getActiveSessionsLocked(resolvedUserId);
for (MediaSessionRecord record : records) {
- binders.add(record.getControllerBinder().asBinder());
+ binders.add(record.getControllerLink());
}
}
return binders;
@@ -1798,7 +1799,7 @@
if (mCurrentFullUserRecord.mCallback != null) {
try {
mCurrentFullUserRecord.mCallback.onMediaKeyEventDispatchedToMediaSession(
- keyEvent, new MediaSession.Token(session.getControllerBinder()));
+ keyEvent, session.getSessionToken());
} catch (RemoteException e) {
Log.w(TAG, "Failed to send callback", e);
}
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 8c274e4..bffd60b 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -962,12 +962,64 @@
@Override
public long getIfaceStats(String iface, int type) {
- return nativeGetIfaceStat(iface, type, checkBpfStatsEnable());
+ long nativeIfaceStats = nativeGetIfaceStat(iface, type, checkBpfStatsEnable());
+ if (nativeIfaceStats == -1) {
+ return nativeIfaceStats;
+ } else {
+ // When tethering offload is in use, nativeIfaceStats does not contain usage from
+ // offload, add it back here.
+ // When tethering offload is not in use, nativeIfaceStats contains tethering usage.
+ // this does not cause double-counting of tethering traffic, because
+ // NetdTetheringStatsProvider returns zero NetworkStats
+ // when called with STATS_PER_IFACE.
+ return nativeIfaceStats + getTetherStats(iface, type);
+ }
}
@Override
public long getTotalStats(int type) {
- return nativeGetTotalStat(type, checkBpfStatsEnable());
+ long nativeTotalStats = nativeGetTotalStat(type, checkBpfStatsEnable());
+ if (nativeTotalStats == -1) {
+ return nativeTotalStats;
+ } else {
+ // Refer to comment in getIfaceStats
+ return nativeTotalStats + getTetherStats(IFACE_ALL, type);
+ }
+ }
+
+ private long getTetherStats(String iface, int type) {
+ final NetworkStats tetherSnapshot;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ tetherSnapshot = getNetworkStatsTethering(STATS_PER_IFACE);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error get TetherStats: " + e);
+ return 0;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ HashSet<String> limitIfaces;
+ if (iface == IFACE_ALL) {
+ limitIfaces = null;
+ } else {
+ limitIfaces = new HashSet<String>();
+ limitIfaces.add(iface);
+ }
+ NetworkStats.Entry entry = tetherSnapshot.getTotal(null, limitIfaces);
+ if (LOGD) Slog.d(TAG, "TetherStats: iface=" + iface + " type=" + type +
+ " entry=" + entry);
+ switch (type) {
+ case 0: // TYPE_RX_BYTES
+ return entry.rxBytes;
+ case 1: // TYPE_RX_PACKETS
+ return entry.rxPackets;
+ case 2: // TYPE_TX_BYTES
+ return entry.txBytes;
+ case 3: // TYPE_TX_PACKETS
+ return entry.txPackets;
+ default:
+ return 0;
+ }
}
private boolean checkBpfStatsEnable() {
diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java
index ee60daa..c2dc554 100644
--- a/services/core/java/com/android/server/notification/NotificationDelegate.java
+++ b/services/core/java/com/android/server/notification/NotificationDelegate.java
@@ -42,7 +42,8 @@
void onNotificationVisibilityChanged(
NotificationVisibility[] newlyVisibleKeys,
NotificationVisibility[] noLongerVisibleKeys);
- void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded);
+ void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded,
+ int notificationLocation);
void onNotificationDirectReplied(String key);
void onNotificationSettingsViewed(String key);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 1798617..47a5597 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -868,7 +868,7 @@
@Override
public void onNotificationExpansionChanged(String key,
- boolean userAction, boolean expanded) {
+ boolean userAction, boolean expanded, int notificationLocation) {
synchronized (mNotificationLock) {
NotificationRecord r = mNotificationsByKey.get(key);
if (r != null) {
@@ -878,7 +878,6 @@
if (r.hasBeenVisiblyExpanded()) {
logSmartSuggestionsVisible(r);
}
- final long now = System.currentTimeMillis();
if (userAction) {
MetricsLogger.action(r.getItemLogMaker()
.setType(expanded ? MetricsEvent.TYPE_DETAIL
@@ -888,9 +887,6 @@
r.recordExpanded();
reportUserInteraction(r);
}
- EventLogTags.writeNotificationExpansion(key,
- userAction ? 1 : 0, expanded ? 1 : 0,
- r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now));
mAssistants.notifyAssistantExpansionChangedLocked(r.sbn, userAction, expanded);
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 5598741..02fc51f 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -1263,7 +1263,7 @@
public LogMaker getAdjustmentLogMaker() {
return getLogMaker()
.setCategory(MetricsEvent.NOTIFICATION_ITEM)
- .setType(MetricsEvent.NOTIFICATION_ASSISTANT_ADJUSTMENT);
+ .setType(MetricsEvent.TYPE_NOTIFICATION_ASSISTANT_ADJUSTMENT);
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/os/BugreportManagerService.java b/services/core/java/com/android/server/os/BugreportManagerService.java
index e241591..bee7a8b 100644
--- a/services/core/java/com/android/server/os/BugreportManagerService.java
+++ b/services/core/java/com/android/server/os/BugreportManagerService.java
@@ -37,7 +37,6 @@
@Override
public void onStart() {
mService = new BugreportManagerServiceImpl(getContext());
- // TODO(b/111441001): Needs sepolicy to be submitted first.
- // publishBinderService(Context.BUGREPORT_SERVICE, mService);
+ publishBinderService(Context.BUGREPORT_SERVICE, mService);
}
}
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 1178cc1..f736056 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -44,6 +44,7 @@
*/
class BugreportManagerServiceImpl extends IDumpstate.Stub {
private static final String TAG = "BugreportManagerService";
+ private static final String BUGREPORT_SERVICE = "bugreportd";
private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
private IDumpstate mDs = null;
@@ -64,6 +65,8 @@
throw new UnsupportedOperationException("setListener is not allowed on this service");
}
+ // TODO(b/111441001): Intercept onFinished here in system server and shutdown
+ // the bugreportd service.
@Override
@RequiresPermission(android.Manifest.permission.DUMP)
public void startBugreport(int callingUidUnused, String callingPackage,
@@ -84,6 +87,14 @@
bugreportFd, screenshotFd, bugreportMode, listener);
}
+ @Override
+ @RequiresPermission(android.Manifest.permission.DUMP)
+ public void cancelBugreport() throws RemoteException {
+ // This tells init to cancel bugreportd service.
+ SystemProperties.set("ctl.stop", BUGREPORT_SERVICE);
+ mDs = null;
+ }
+
private boolean validate(@BugreportParams.BugreportMode int mode) {
if (mode != BugreportParams.BUGREPORT_MODE_FULL
&& mode != BugreportParams.BUGREPORT_MODE_INTERACTIVE
@@ -107,7 +118,7 @@
*/
private IDumpstate getDumpstateService() {
// Start bugreport service.
- SystemProperties.set("ctl.start", "bugreport");
+ SystemProperties.set("ctl.start", BUGREPORT_SERVICE);
IDumpstate ds = null;
boolean timedOut = false;
diff --git a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
index 2ae424d..5b765df 100644
--- a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
+++ b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
@@ -22,63 +22,117 @@
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
+import android.os.Process;
import android.os.ServiceManager;
+import android.util.ByteStringUtils;
+import android.util.EventLog;
import android.util.Log;
import com.android.server.pm.dex.DexLogger;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
- * Scheduled job to trigger logging of app dynamic code loading. This runs daily while idle and
- * charging. The actual logging is performed by {@link DexLogger}.
+ * Scheduled jobs related to logging of app dynamic code loading. The idle logging job runs daily
+ * while idle and charging and calls {@link DexLogger} to write dynamic code information to the
+ * event log. The audit watching job scans the event log periodically while idle to find AVC audit
+ * messages indicating use of dynamic native code and adds the information to {@link DexLogger}.
* {@hide}
*/
public class DynamicCodeLoggingService extends JobService {
private static final String TAG = DynamicCodeLoggingService.class.getName();
- private static final int JOB_ID = 2030028;
- private static final long PERIOD_MILLIS = TimeUnit.DAYS.toMillis(1);
-
- private volatile boolean mStopRequested = false;
-
private static final boolean DEBUG = false;
+ private static final int IDLE_LOGGING_JOB_ID = 2030028;
+ private static final int AUDIT_WATCHING_JOB_ID = 203142925;
+
+ private static final long IDLE_LOGGING_PERIOD_MILLIS = TimeUnit.DAYS.toMillis(1);
+ private static final long AUDIT_WATCHING_PERIOD_MILLIS = TimeUnit.HOURS.toMillis(2);
+
+ private static final int AUDIT_AVC = 1400; // Defined in linux/audit.h
+ private static final String AVC_PREFIX = "type=" + AUDIT_AVC + " ";
+
+ private static final Pattern EXECUTE_NATIVE_AUDIT_PATTERN =
+ Pattern.compile(".*\\bavc: granted \\{ execute(?:_no_trans|) \\} .*"
+ + "\\bpath=(?:\"([^\" ]*)\"|([0-9A-F]+)) .*"
+ + "\\bscontext=u:r:untrusted_app_2(?:5|7):.*"
+ + "\\btcontext=u:object_r:app_data_file:.*"
+ + "\\btclass=file\\b.*");
+
+ private volatile boolean mIdleLoggingStopRequested = false;
+ private volatile boolean mAuditWatchingStopRequested = false;
+
/**
- * Schedule our job with the {@link JobScheduler}.
+ * Schedule our jobs with the {@link JobScheduler}.
*/
public static void schedule(Context context) {
ComponentName serviceName = new ComponentName(
"android", DynamicCodeLoggingService.class.getName());
JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
- js.schedule(new JobInfo.Builder(JOB_ID, serviceName)
+ js.schedule(new JobInfo.Builder(IDLE_LOGGING_JOB_ID, serviceName)
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
- .setPeriodic(PERIOD_MILLIS)
+ .setPeriodic(IDLE_LOGGING_PERIOD_MILLIS)
.build());
+ js.schedule(new JobInfo.Builder(AUDIT_WATCHING_JOB_ID, serviceName)
+ .setRequiresDeviceIdle(true)
+ .setRequiresBatteryNotLow(true)
+ .setPeriodic(AUDIT_WATCHING_PERIOD_MILLIS)
+ .build());
+
if (DEBUG) {
- Log.d(TAG, "Job scheduled");
+ Log.d(TAG, "Jobs scheduled");
}
}
@Override
public boolean onStartJob(JobParameters params) {
+ int jobId = params.getJobId();
if (DEBUG) {
- Log.d(TAG, "onStartJob");
+ Log.d(TAG, "onStartJob " + jobId);
}
- mStopRequested = false;
- new IdleLoggingThread(params).start();
- return true; // Job is running on another thread
+ switch (jobId) {
+ case IDLE_LOGGING_JOB_ID:
+ mIdleLoggingStopRequested = false;
+ new IdleLoggingThread(params).start();
+ return true; // Job is running on another thread
+ case AUDIT_WATCHING_JOB_ID:
+ mAuditWatchingStopRequested = false;
+ new AuditWatchingThread(params).start();
+ return true; // Job is running on another thread
+ default:
+ // Shouldn't happen, but indicate nothing is running.
+ return false;
+ }
}
@Override
public boolean onStopJob(JobParameters params) {
+ int jobId = params.getJobId();
if (DEBUG) {
- Log.d(TAG, "onStopJob");
+ Log.d(TAG, "onStopJob " + jobId);
}
- mStopRequested = true;
- return true; // Requests job be re-scheduled.
+ switch (jobId) {
+ case IDLE_LOGGING_JOB_ID:
+ mIdleLoggingStopRequested = true;
+ return true; // Requests job be re-scheduled.
+ case AUDIT_WATCHING_JOB_ID:
+ mAuditWatchingStopRequested = true;
+ return true; // Requests job be re-scheduled.
+ default:
+ return false;
+ }
+ }
+
+ private static DexLogger getDexLogger() {
+ PackageManagerService pm = (PackageManagerService) ServiceManager.getService("package");
+ return pm.getDexManager().getDexLogger();
}
private class IdleLoggingThread extends Thread {
@@ -92,14 +146,13 @@
@Override
public void run() {
if (DEBUG) {
- Log.d(TAG, "Starting logging run");
+ Log.d(TAG, "Starting IdleLoggingJob run");
}
- PackageManagerService pm = (PackageManagerService) ServiceManager.getService("package");
- DexLogger dexLogger = pm.getDexManager().getDexLogger();
+ DexLogger dexLogger = getDexLogger();
for (String packageName : dexLogger.getAllPackagesWithDynamicCodeLoading()) {
- if (mStopRequested) {
- Log.w(TAG, "Stopping logging run at scheduler request");
+ if (mIdleLoggingStopRequested) {
+ Log.w(TAG, "Stopping IdleLoggingJob run at scheduler request");
return;
}
@@ -108,8 +161,128 @@
jobFinished(mParams, /* reschedule */ false);
if (DEBUG) {
- Log.d(TAG, "Finished logging run");
+ Log.d(TAG, "Finished IdleLoggingJob run");
}
}
}
+
+ private class AuditWatchingThread extends Thread {
+ private final JobParameters mParams;
+
+ AuditWatchingThread(JobParameters params) {
+ super("DynamicCodeLoggingService_AuditWatchingJob");
+ mParams = params;
+ }
+
+ @Override
+ public void run() {
+ if (DEBUG) {
+ Log.d(TAG, "Starting AuditWatchingJob run");
+ }
+
+ if (processAuditEvents()) {
+ jobFinished(mParams, /* reschedule */ false);
+ if (DEBUG) {
+ Log.d(TAG, "Finished AuditWatchingJob run");
+ }
+ }
+ }
+
+ private boolean processAuditEvents() {
+ // Scan the event log for SELinux (avc) audit messages indicating when an
+ // (untrusted) app has executed native code from an app data
+ // file. Matches are recorded in DexLogger.
+ //
+ // These messages come from the kernel audit system via logd. (Note that
+ // some devices may not generate these messages at all, or the format may
+ // be different, in which case nothing will be recorded.)
+ //
+ // The messages use the auditd tag and the uid of the app that executed
+ // the code.
+ //
+ // A typical message might look like this:
+ // type=1400 audit(0.0:521): avc: granted { execute } for comm="executable"
+ // path="/data/data/com.dummy.app/executable" dev="sda13" ino=1655302
+ // scontext=u:r:untrusted_app_27:s0:c66,c257,c512,c768
+ // tcontext=u:object_r:app_data_file:s0:c66,c257,c512,c768 tclass=file
+ //
+ // The information we want is the uid and the path. (Note this may be
+ // either a quoted string, as shown above, or a sequence of hex-encoded
+ // bytes.)
+ //
+ // On each run we process all the matching events in the log. This may
+ // mean re-processing events we have already seen, and in any case there
+ // may be duplicate events for the same app+file. These are de-duplicated
+ // by DexLogger.
+ //
+ // Note that any app can write a message to the event log, including one
+ // that looks exactly like an AVC audit message, so the information may
+ // be spoofed by an app; in such a case the uid we see will be the app
+ // that generated the spoof message.
+
+ try {
+ int[] tags = { EventLog.getTagCode("auditd") };
+ if (tags[0] == -1) {
+ // auditd is not a registered tag on this system, so there can't be any messages
+ // of interest.
+ return true;
+ }
+
+ DexLogger dexLogger = getDexLogger();
+
+ List<EventLog.Event> events = new ArrayList<>();
+ EventLog.readEvents(tags, events);
+
+ for (int i = 0; i < events.size(); ++i) {
+ if (mAuditWatchingStopRequested) {
+ Log.w(TAG, "Stopping AuditWatchingJob run at scheduler request");
+ return false;
+ }
+
+ EventLog.Event event = events.get(i);
+
+ // Discard clearly unrelated messages as quickly as we can.
+ int uid = event.getUid();
+ if (!Process.isApplicationUid(uid)) {
+ continue;
+ }
+ Object data = event.getData();
+ if (!(data instanceof String)) {
+ continue;
+ }
+ String message = (String) data;
+ if (!message.startsWith(AVC_PREFIX)) {
+ continue;
+ }
+
+ // And then use a regular expression to verify it's one of the messages we're
+ // interested in and to extract the path of the file being loaded.
+ Matcher matcher = EXECUTE_NATIVE_AUDIT_PATTERN.matcher(message);
+ if (!matcher.matches()) {
+ continue;
+ }
+ String path = matcher.group(1);
+ if (path == null) {
+ // If the path contains spaces or various weird characters the kernel
+ // hex-encodes the bytes; we need to undo that.
+ path = unhex(matcher.group(2));
+ }
+ dexLogger.recordNative(uid, path);
+ }
+
+ return true;
+ } catch (Exception e) {
+ Log.e(TAG, "AuditWatchingJob failed", e);
+ return true;
+ }
+ }
+ }
+
+ private static String unhex(String hexEncodedPath) {
+ byte[] bytes = ByteStringUtils.fromHexToByteArray(hexEncodedPath);
+ if (bytes == null || bytes.length == 0) {
+ return "";
+ }
+ return new String(bytes);
+ }
}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 8a6105c..efafdfa 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -611,6 +611,31 @@
}
}
+ public boolean snapshotAppData(String pkg, @UserIdInt int userId, int storageFlags)
+ throws InstallerException {
+ if (!checkBeforeRemote()) return false;
+
+ try {
+ mInstalld.snapshotAppData(null, pkg, userId, storageFlags);
+ return true;
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
+ public boolean restoreAppDataSnapshot(String pkg, @AppIdInt int appId, long ceDataInode,
+ String seInfo, @UserIdInt int userId, int storageFlags) throws InstallerException {
+ if (!checkBeforeRemote()) return false;
+
+ try {
+ mInstalld.restoreAppDataSnapshot(null, pkg, appId, ceDataInode, seInfo, userId,
+ storageFlags);
+ return true;
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
private static void assertValidInstructionSet(String instructionSet)
throws InstallerException {
for (String abi : Build.SUPPORTED_ABIS) {
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index b6dae19..a33f14b 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -33,6 +33,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.ILauncherApps;
import android.content.pm.IOnAppsChangedListener;
+import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps.ShortcutQuery;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -42,6 +43,8 @@
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutServiceInternal;
import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
+import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
import android.content.pm.UserInfo;
import android.graphics.Rect;
import android.net.Uri;
@@ -56,21 +59,31 @@
import android.os.UserManager;
import android.os.UserManagerInternal;
import android.provider.Settings;
+import android.util.ByteStringUtils;
import android.util.Log;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
+import com.android.internal.util.StatLogger;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.wm.ActivityTaskManagerInternal;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
/**
* Service that manages requests and callbacks for launchers that support
@@ -108,6 +121,17 @@
static class LauncherAppsImpl extends ILauncherApps.Stub {
private static final boolean DEBUG = false;
private static final String TAG = "LauncherAppsService";
+
+ // Stats
+ @VisibleForTesting
+ interface Stats {
+ int INIT_VOUCHED_SIGNATURES = 0;
+ int COUNT = INIT_VOUCHED_SIGNATURES + 1;
+ }
+ private final StatLogger mStatLogger = new StatLogger(new String[] {
+ "initVouchedSignatures"
+ });
+
private final Context mContext;
private final UserManager mUm;
private final UserManagerInternal mUserManagerInternal;
@@ -117,11 +141,16 @@
private final PackageCallbackList<IOnAppsChangedListener> mListeners
= new PackageCallbackList<IOnAppsChangedListener>();
private final DevicePolicyManager mDpm;
+ private final ConcurrentHashMap<UserHandle, Set<String>> mVouchedSignaturesByUser;
+ private final Set<String> mVouchProviders;
private final MyPackageMonitor mPackageMonitor = new MyPackageMonitor();
+ private final VouchesChangedMonitor mVouchesChangedMonitor = new VouchesChangedMonitor();
private final Handler mCallbackHandler;
+ private final Object mVouchedSignaturesLocked = new Object();
+
public LauncherAppsImpl(Context context) {
mContext = context;
mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
@@ -136,6 +165,9 @@
mShortcutServiceInternal.addListener(mPackageMonitor);
mCallbackHandler = BackgroundThread.getHandler();
mDpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ mVouchedSignaturesByUser = new ConcurrentHashMap<>();
+ mVouchProviders = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
+ mVouchesChangedMonitor.register(mContext, UserHandle.ALL, true, mCallbackHandler);
}
@VisibleForTesting
@@ -355,7 +387,7 @@
}
ApplicationInfo appInfo = pmInt.getApplicationInfo(packageName, /*flags*/ 0,
callingUid, user.getIdentifier());
- if (shouldShowHiddenApp(appInfo)) {
+ if (shouldShowHiddenApp(user, appInfo)) {
ResolveInfo info = getHiddenAppActivityInfo(packageName, callingUid, user);
if (info != null) {
result.add(info);
@@ -371,7 +403,7 @@
user.getIdentifier(), callingUid);
for (ApplicationInfo applicationInfo : installedPackages) {
if (!visiblePackages.contains(applicationInfo.packageName)) {
- if (!shouldShowHiddenApp(applicationInfo)) {
+ if (!shouldShowHiddenApp(user, applicationInfo)) {
continue;
}
ResolveInfo info = getHiddenAppActivityInfo(applicationInfo.packageName,
@@ -387,13 +419,130 @@
}
}
- private static boolean shouldShowHiddenApp(ApplicationInfo appInfo) {
+ private boolean shouldShowHiddenApp(UserHandle user, ApplicationInfo appInfo) {
if (appInfo == null || appInfo.isSystemApp() || appInfo.isUpdatedSystemApp()) {
return false;
}
+ if (!mVouchedSignaturesByUser.containsKey(user)) {
+ initVouchedSignatures(user);
+ }
+ if (mVouchProviders.contains(appInfo.packageName)) {
+ // If it's a vouching packages then we must show hidden app
+ return true;
+ }
+ // If app's signature is in vouch list, do not show hidden app
+ final Set<String> vouches = mVouchedSignaturesByUser.get(user);
+ try {
+ final PackageInfo pkgInfo = mContext.getPackageManager().getPackageInfo(
+ appInfo.packageName, PackageManager.GET_SIGNING_CERTIFICATES);
+ final Signature[] signatures = getLatestSignatures(pkgInfo.signingInfo);
+ // If any of the signatures appears in vouches, then we don't show hidden app
+ for (Signature signature : signatures) {
+ final String certDigest = computePackageCertDigest(signature);
+ if (vouches.contains(certDigest)) {
+ return false;
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Should not happen
+ }
return true;
}
+ @VisibleForTesting
+ static String computePackageCertDigest(Signature signature) {
+ MessageDigest messageDigest;
+ try {
+ messageDigest = MessageDigest.getInstance("SHA1");
+ } catch (NoSuchAlgorithmException e) {
+ // Should not happen
+ return null;
+ }
+ messageDigest.update(signature.toByteArray());
+ final byte[] digest = messageDigest.digest();
+ return ByteStringUtils.toHexString(digest);
+ }
+
+ @VisibleForTesting
+ static Signature[] getLatestSignatures(SigningInfo signingInfo) {
+ if (signingInfo.hasMultipleSigners()) {
+ return signingInfo.getApkContentsSigners();
+ } else {
+ final Signature[] signatures = signingInfo.getSigningCertificateHistory();
+ return new Signature[]{signatures[0]};
+ }
+ }
+
+ private void updateVouches(String packageName, UserHandle user) {
+ final PackageManagerInternal pmInt =
+ LocalServices.getService(PackageManagerInternal.class);
+ ApplicationInfo appInfo = pmInt.getApplicationInfo(packageName,
+ PackageManager.GET_META_DATA, Binder.getCallingUid(), user.getIdentifier());
+ if (appInfo == null) {
+ Log.w(TAG, "appInfo " + packageName + " is null");
+ return;
+ }
+ updateVouches(appInfo, user);
+ }
+
+ private void updateVouches(ApplicationInfo appInfo, UserHandle user) {
+ if (appInfo == null || appInfo.metaData == null) {
+ // No meta-data
+ return;
+ }
+ int tokenResourceId = appInfo.metaData.getInt(LauncherApps.VOUCHED_CERTS_KEY);
+ if (tokenResourceId == 0) {
+ // No xml file
+ return;
+ }
+ mVouchProviders.add(appInfo.packageName);
+ Set<String> vouches = mVouchedSignaturesByUser.get(user);
+ try {
+ List<String> signatures = Arrays.asList(
+ mContext.getPackageManager().getResourcesForApplication(
+ appInfo.packageName).getStringArray(tokenResourceId));
+ for (String signature : signatures) {
+ vouches.add(signature.toUpperCase());
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Should not happen
+ }
+ }
+
+ private void initVouchedSignatures(UserHandle user) {
+ synchronized (mVouchedSignaturesLocked) {
+ if (mVouchedSignaturesByUser.contains(user)) {
+ return;
+ }
+ final long startTime = mStatLogger.getTime();
+
+ Set<String> vouches = Collections.newSetFromMap(
+ new ConcurrentHashMap<String, Boolean>());
+
+ final int callingUid = injectBinderCallingUid();
+ long ident = Binder.clearCallingIdentity();
+ try {
+ final PackageManagerInternal pmInt =
+ LocalServices.getService(PackageManagerInternal.class);
+ List<ApplicationInfo> installedPackages = pmInt.getInstalledApplications(
+ PackageManager.GET_META_DATA, user.getIdentifier(), callingUid);
+ for (ApplicationInfo appInfo : installedPackages) {
+ updateVouches(appInfo, user);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ mVouchedSignaturesByUser.putIfAbsent(user, vouches);
+ mStatLogger.logDurationStat(Stats.INIT_VOUCHED_SIGNATURES, startTime);
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
+ mStatLogger.dump(pw, " ");
+ }
+
@Override
public ActivityInfo resolveActivity(
String callingPackage, ComponentName component, UserHandle user)
@@ -760,6 +909,18 @@
mCallbackHandler.post(r);
}
+ private class VouchesChangedMonitor extends PackageMonitor {
+ @Override
+ public void onPackageAdded(String packageName, int uid) {
+ updateVouches(packageName, new UserHandle(getChangingUserId()));
+ }
+
+ @Override
+ public void onPackageModified(String packageName) {
+ updateVouches(packageName, new UserHandle(getChangingUserId()));
+ }
+ }
+
private class MyPackageMonitor extends PackageMonitor implements ShortcutChangeListener {
// TODO Simplify with lambdas.
diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS
new file mode 100644
index 0000000..60d7925
--- /dev/null
+++ b/services/core/java/com/android/server/pm/OWNERS
@@ -0,0 +1,76 @@
+hackbod@android.com
+hackbod@google.com
+jsharkey@android.com
+jsharkey@google.com
+narayan@google.com
+patb@google.com
+svetoslavganov@android.com
+svetoslavganov@google.com
+toddke@android.com
+toddke@google.com
+
+# dex
+per-file AbstractStatsBase.java = agampe@google.com
+per-file AbstractStatsBase.java = calin@google.com
+per-file AbstractStatsBase.java = ngeoffray@google.com
+per-file BackgroundDexOptService.java = agampe@google.com
+per-file BackgroundDexOptService.java = calin@google.com
+per-file BackgroundDexOptService.java = ngeoffray@google.com
+per-file CompilerStats.java = agampe@google.com
+per-file CompilerStats.java = calin@google.com
+per-file CompilerStats.java = ngeoffray@google.com
+per-file InstructionSets.java = agampe@google.com
+per-file InstructionSets.java = calin@google.com
+per-file InstructionSets.java = ngeoffray@google.com
+per-file OtaDexoptService.java = agampe@google.com
+per-file OtaDexoptService.java = calin@google.com
+per-file OtaDexoptService.java = ngeoffray@google.com
+per-file OtaDexoptShellCommand.java = agampe@google.com
+per-file OtaDexoptShellCommand.java = calin@google.com
+per-file OtaDexoptShellCommand.java = ngeoffray@google.com
+per-file PackageDexOptimizer.java = agampe@google.com
+per-file PackageDexOptimizer.java = calin@google.com
+per-file PackageDexOptimizer.java = ngeoffray@google.com
+per-file PackageManagerServiceCompilerMapping.java = agampe@google.com
+per-file PackageManagerServiceCompilerMapping.java = calin@google.com
+per-file PackageManagerServiceCompilerMapping.java = ngeoffray@google.com
+per-file PackageUsage.java = agampe@google.com
+per-file PackageUsage.java = calin@google.com
+per-file PackageUsage.java = ngeoffray@google.com
+
+# multi user / cross profile
+per-file CrossProfileAppsServiceImpl.java = omakoto@google.com
+per-file CrossProfileAppsServiceImpl.java = yamasani@google.com
+per-file CrossProfileAppsService.java = omakoto@google.com
+per-file CrossProfileAppsService.java = yamasani@google.com
+per-file CrossProfileIntentFilter.java = omakoto@google.com
+per-file CrossProfileIntentFilter.java = yamasani@google.com
+per-file CrossProfileIntentResolver.java = omakoto@google.com
+per-file CrossProfileIntentResolver.java = yamasani@google.com
+per-file UserManagerService.java = omakoto@google.com
+per-file UserManagerService.java = yamasani@google.com
+per-file UserRestrictionsUtils.java = omakoto@google.com
+per-file UserRestrictionsUtils.java = yamasani@google.com
+
+# security
+per-file KeySetHandle.java = cbrubaker@google.com
+per-file KeySetManagerService.java = cbrubaker@google.com
+per-file PackageKeySetData.java = cbrubaker@google.com
+per-file PackageSignatures.java = cbrubaker@google.com
+per-file SELinuxMMAC.java = cbrubaker@google.com
+
+# shortcuts
+per-file LauncherAppsService.java = omakoto@google.com
+per-file ShareTargetInfo.java = omakoto@google.com
+per-file ShortcutBitmapSaver.java = omakoto@google.com
+per-file ShortcutDumpFiles.java = omakoto@google.com
+per-file ShortcutLauncher.java = omakoto@google.com
+per-file ShortcutNonPersistentUser.java = omakoto@google.com
+per-file ShortcutPackage.java = omakoto@google.com
+per-file ShortcutPackageInfo.java = omakoto@google.com
+per-file ShortcutPackageItem.java = omakoto@google.com
+per-file ShortcutParser.java = omakoto@google.com
+per-file ShortcutRequestPinProcessor.java = omakoto@google.com
+per-file ShortcutService.java = omakoto@google.com
+per-file ShortcutUser.java = omakoto@google.com
+
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index bf4e272..0ab2a73 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -310,7 +310,6 @@
in.setInput(fis, StandardCharsets.UTF_8.name());
int type;
- PackageInstallerSession currentSession = null;
while ((type = in.next()) != END_DOCUMENT) {
if (type == START_TAG) {
final String tag = in.getName();
@@ -320,9 +319,7 @@
session = PackageInstallerSession.readFromXml(in, mInternalCallback,
mContext, mPm, mInstallThread.getLooper(), mStagingManager,
mSessionsDir, this);
- currentSession = session;
} catch (Exception e) {
- currentSession = null;
Slog.e(TAG, "Could not read session", e);
continue;
}
@@ -347,10 +344,6 @@
addHistoricalSessionLocked(session);
}
mAllocatedSessions.put(session.sessionId, true);
- } else if (currentSession != null
- && PackageInstallerSession.TAG_CHILD_SESSION.equals(tag)) {
- currentSession.addChildSessionIdInternal(
- PackageInstallerSession.readChildSessionIdFromXml(in));
}
}
}
@@ -1132,6 +1125,7 @@
public void onStagedSessionChanged(PackageInstallerSession session) {
writeSessionsAsync();
+ // TODO(b/118865310): don't send broadcast if system is not ready.
mPm.sendSessionUpdatedBroadcast(session.generateInfo(false), session.userId);
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 12d335d..b8825bb 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -992,8 +992,12 @@
// Read transfers from the original owner stay open, but as the session's data
// cannot be modified anymore, there is no leak of information. For staged sessions,
- // further validation may be performed by the staging manager.
+ // further validation is performed by the staging manager.
if (!params.isMultiPackage) {
+ if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
+ // For APEX, validation is done by StagingManager post-commit.
+ return;
+ }
final PackageInfo pkgInfo = mPm.getPackageInfo(
params.appPackageName, PackageManager.GET_SIGNATURES
| PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
@@ -1001,16 +1005,7 @@
resolveStageDirLocked();
try {
- if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
- // TODO(b/118865310): Remove this when APEX validation is done via
- // StagingManager.
- validateApexInstallLocked(pkgInfo);
- } else {
- // Verify that stage looks sane with respect to existing application.
- // This currently only ensures packageName, versionCode, and certificate
- // consistency.
- validateApkInstallLocked(pkgInfo);
- }
+ validateApkInstallLocked(pkgInfo);
} catch (PackageManagerException e) {
throw e;
} catch (Throwable e) {
@@ -1301,54 +1296,6 @@
(params.installFlags & PackageManager.DONT_KILL_APP) != 0;
}
- @GuardedBy("mLock")
- private void validateApexInstallLocked(@Nullable PackageInfo pkgInfo)
- throws PackageManagerException {
- mResolvedStagedFiles.clear();
- mResolvedInheritedFiles.clear();
-
- try {
- resolveStageDirLocked();
- } catch (IOException e) {
- throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
- "Failed to resolve stage location", e);
- }
-
- final File[] addedFiles = mResolvedStageDir.listFiles(sAddedFilter);
- if (ArrayUtils.isEmpty(addedFiles)) {
- throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged");
- }
-
- if (addedFiles.length > 1) {
- throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
- "Only one APEX file at a time might be installed");
- }
- File addedFile = addedFiles[0];
- final ApkLite apk;
- try {
- apk = PackageParser.parseApkLite(
- addedFile, PackageParser.PARSE_COLLECT_CERTIFICATES);
- } catch (PackageParserException e) {
- throw PackageManagerException.from(e);
- }
-
- mPackageName = apk.packageName;
- mVersionCode = apk.getLongVersionCode();
- mSigningDetails = apk.signingDetails;
- mResolvedBaseFile = addedFile;
-
- // STOPSHIP: Ensure that we remove the non-staged version of APEX installs in production
- // because we currently do not verify that signatures are consistent with the previously
- // installed version in that case.
- //
- // When that happens, this hack can be reverted and we can rely on APEXd to map between
- // APEX files and their package names instead of parsing it out of the AndroidManifest
- // such as here.
- if (params.appPackageName == null) {
- params.appPackageName = mPackageName;
- }
- }
-
/**
* Validate install by confirming that all application packages are have
* consistent package name, version code, and signing certificates.
@@ -1911,22 +1858,30 @@
}
@Override
- public void addChildSessionId(int sessionId) {
- final PackageInstallerSession session = mSessionProvider.getSession(sessionId);
- if (session == null) {
+ public void addChildSessionId(int childSessionId) {
+ final PackageInstallerSession childSession = mSessionProvider.getSession(childSessionId);
+ if (childSession == null) {
throw new RemoteException("Unable to add child.",
- new PackageManagerException("Child session " + sessionId + " does not exist"),
+ new PackageManagerException("Child session " + childSessionId
+ + " does not exist"),
+ false, true).rethrowAsRuntimeException();
+ }
+ // Session groups must be consistent wrt to isStaged parameter. Non-staging session
+ // cannot be grouped with staging sessions.
+ if (this.params.isStaged ^ childSession.params.isStaged) {
+ throw new RemoteException("Unable to add child.",
+ new PackageManagerException("Child session " + childSessionId
+ + " and parent session " + this.sessionId + " do not have consistent"
+ + " staging session settings."),
false, true).rethrowAsRuntimeException();
}
synchronized (mLock) {
- final int indexOfSession = mChildSessionIds.indexOfKey(sessionId);
+ final int indexOfSession = mChildSessionIds.indexOfKey(childSessionId);
if (indexOfSession >= 0) {
return;
}
- session.setParentSessionId(this.sessionId);
- // TODO: sanity check, if parent session is staged then child session should be
- // marked as staged.
- addChildSessionIdInternal(sessionId);
+ childSession.setParentSessionId(this.sessionId);
+ addChildSessionIdInternal(childSessionId);
}
}
@@ -2057,6 +2012,11 @@
return mStagedSessionFailed;
}
+ /** {@hide} */
+ @StagedSessionErrorCode int getStagedSessionErrorCode() {
+ return mStagedSessionErrorCode;
+ }
+
private void destroyInternal() {
synchronized (mLock) {
mSealed = true;
@@ -2221,35 +2181,6 @@
out.endTag(null, TAG_SESSION);
}
- private static String[] readGrantedRuntimePermissions(XmlPullParser in)
- throws IOException, XmlPullParserException {
- List<String> permissions = null;
-
- final int outerDepth = in.getDepth();
- int type;
- while ((type = in.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || in.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
- if (TAG_GRANTED_RUNTIME_PERMISSION.equals(in.getName())) {
- String permission = readStringAttribute(in, ATTR_NAME);
- if (permissions == null) {
- permissions = new ArrayList<>();
- }
- permissions.add(permission);
- }
- }
-
- if (permissions == null) {
- return null;
- }
-
- String[] permissionsArray = new String[permissions.size()];
- permissions.toArray(permissionsArray);
- return permissionsArray;
- }
-
// Sanity check to be performed when the session is restored from an external file. Only one
// of the session states should be true, or none of them.
private static boolean isStagedSessionStateValid(boolean isReady, boolean isApplied,
@@ -2273,8 +2204,6 @@
* @param sessionProvider
* @return The newly created session
*/
- // TODO(patb,109941548): modify readFromXml to consume to the next tag session tag so we
- // can have a complete session for the constructor
public static PackageInstallerSession readFromXml(@NonNull XmlPullParser in,
@NonNull PackageInstallerService.InternalCallback callback, @NonNull Context context,
@NonNull PackageManagerService pm, Looper installerThread,
@@ -2314,8 +2243,6 @@
params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID);
params.installReason = readIntAttribute(in, ATTR_INSTALL_REASON);
- params.grantedRuntimePermissions = readGrantedRuntimePermissions(in);
-
final File appIconFile = buildAppIconFile(sessionId, sessionsDir);
if (appIconFile.exists()) {
params.appIcon = BitmapFactory.decodeFile(appIconFile.getAbsolutePath());
@@ -2324,16 +2251,51 @@
final boolean isReady = readBooleanAttribute(in, ATTR_IS_READY);
final boolean isFailed = readBooleanAttribute(in, ATTR_IS_FAILED);
final boolean isApplied = readBooleanAttribute(in, ATTR_IS_APPLIED);
- final int stagedSessionErrorCode = readIntAttribute(in, ATTR_STAGED_SESSION_ERROR_CODE);
+ final int stagedSessionErrorCode = readIntAttribute(in, ATTR_STAGED_SESSION_ERROR_CODE,
+ SessionInfo.NO_ERROR);
if (!isStagedSessionStateValid(isReady, isApplied, isFailed)) {
throw new IllegalArgumentException("Can't restore staged session with invalid state.");
}
+ // Parse sub tags of this session, typically used for repeated values / arrays.
+ // Sub tags can come in any order, therefore we need to keep track of what we find while
+ // parsing and only set the right values at the end.
+
+ // Store the current depth. We should stop parsing when we reach an end tag at the same
+ // depth.
+ List<String> permissions = new ArrayList<>();
+ List<Integer> childSessionIds = new ArrayList<>();
+ int outerDepth = in.getDepth();
+ int type;
+ while ((type = in.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || in.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ if (TAG_GRANTED_RUNTIME_PERMISSION.equals(in.getName())) {
+ permissions.add(readStringAttribute(in, ATTR_NAME));
+ }
+ if (TAG_CHILD_SESSION.equals(in.getName())) {
+ childSessionIds.add(readIntAttribute(in, ATTR_SESSION_ID, SessionInfo.INVALID_ID));
+ }
+ }
+
+ if (permissions.size() > 0) {
+ params.grantedRuntimePermissions = permissions.stream().toArray(String[]::new);
+ }
+
+ int[] childSessionIdsArray;
+ if (childSessionIds.size() > 0) {
+ childSessionIdsArray = childSessionIds.stream().mapToInt(i -> i).toArray();
+ } else {
+ childSessionIdsArray = EMPTY_CHILD_SESSION_ARRAY;
+ }
+
return new PackageInstallerSession(callback, context, pm, sessionProvider,
installerThread, stagingManager, sessionId, userId, installerPackageName,
installerUid, params, createdMillis, stageDir, stageCid, prepared, sealed,
- EMPTY_CHILD_SESSION_ARRAY, parentSessionId, isReady, isFailed, isApplied,
+ childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied,
stagedSessionErrorCode);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 09fe26d..b8342cf 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -42,6 +42,7 @@
import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
+import static android.content.pm.PackageManager.INSTALL_ALLOW_DOWNGRADE;
import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION;
@@ -201,6 +202,7 @@
import android.content.pm.dex.ArtManager;
import android.content.pm.dex.DexMetadataHelper;
import android.content.pm.dex.IArtManager;
+import android.content.rollback.IRollbackManager;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
@@ -13872,6 +13874,38 @@
}
}
+ // If this is an update to a package that might be potentially downgraded, then we
+ // need to check with the rollback manager whether there's any userdata that might
+ // need to be restored for the package.
+ //
+ // TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL.
+ if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && !doRestore && update) {
+ IRollbackManager rm = IRollbackManager.Stub.asInterface(
+ ServiceManager.getService(Context.ROLLBACK_SERVICE));
+
+ final String packageName = res.pkg.applicationInfo.packageName;
+ final String seInfo = res.pkg.applicationInfo.seInfo;
+ final PackageSetting ps;
+ int appId = -1;
+ long ceDataInode = -1;
+ synchronized (mSettings) {
+ ps = mSettings.getPackageLPr(packageName);
+ if (ps != null) {
+ appId = ps.appId;
+ ceDataInode = ps.getCeDataInode(userId);
+ }
+ }
+
+ if (ps != null) {
+ try {
+ rm.restoreUserData(packageName, userId, appId, ceDataInode, seInfo, token);
+ } catch (RemoteException re) {
+ // Cannot happen, the RollbackManager is hosted in the same process.
+ }
+ doRestore = true;
+ }
+ }
+
if (!doRestore) {
// No restore possible, or the Backup Manager was mysteriously not
// available -- just fire the post-install work request directly.
@@ -14569,6 +14603,9 @@
enableRollbackIntent.putExtra(
PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALL_FLAGS,
installFlags);
+ enableRollbackIntent.putExtra(
+ PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS,
+ resolveUserIds(args.user.getIdentifier()));
enableRollbackIntent.setDataAndType(Uri.fromFile(new File(origin.resolvedPath)),
PACKAGE_MIME_TYPE);
enableRollbackIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -23791,6 +23828,11 @@
}
return mArtManagerService.compileLayouts(pkg);
}
+
+ @Override
+ public void finishPackageInstall(int token, boolean didLaunch) {
+ PackageManagerService.this.finishPackageInstall(token, didLaunch);
+ }
}
@GuardedBy("mPackages")
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 7bab0bb..5311c2a 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -41,7 +41,9 @@
import com.android.internal.os.BackgroundThread;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import java.util.stream.Collectors;
/**
* This class handles staged install sessions, i.e. install sessions that require packages to
@@ -126,12 +128,24 @@
return false;
}
- private static boolean submitSessionToApexService(int sessionId, ApexInfoList apexInfoList) {
+ private static boolean submitSessionToApexService(@NonNull PackageInstallerSession session,
+ List<PackageInstallerSession> childSessions,
+ ApexInfoList apexInfoList) {
+ return sendSubmitStagedSessionRequest(
+ session.sessionId,
+ childSessions != null
+ ? childSessions.stream().mapToInt(s -> s.sessionId).toArray() :
+ new int[]{},
+ apexInfoList);
+ }
+
+ private static boolean sendSubmitStagedSessionRequest(
+ int sessionId, int[] childSessionIds, ApexInfoList apexInfoList) {
final IApexService apex = IApexService.Stub.asInterface(
ServiceManager.getService("apexservice"));
boolean success;
try {
- success = apex.submitStagedSession(sessionId, new int[0], apexInfoList);
+ success = apex.submitStagedSession(sessionId, childSessionIds, apexInfoList);
} catch (RemoteException re) {
Slog.e(TAG, "Unable to contact apexservice", re);
return false;
@@ -139,31 +153,49 @@
return success;
}
+ private static boolean isApexSession(@NonNull PackageInstallerSession session) {
+ return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0;
+ }
+
private void preRebootVerification(@NonNull PackageInstallerSession session) {
boolean success = true;
- if ((session.params.installFlags & PackageManager.INSTALL_APEX) != 0) {
- final ApexInfoList apexInfoList = new ApexInfoList();
+ final ApexInfoList apexInfoList = new ApexInfoList();
+ // APEX checks. For single-package sessions, check if they contain an APEX. For
+ // multi-package sessions, find all the child sessions that contain an APEX.
+ if (!session.isMultiPackage()
+ && isApexSession(session)) {
+ success = submitSessionToApexService(session, null, apexInfoList);
+ } else if (session.isMultiPackage()) {
+ List<PackageInstallerSession> childSessions =
+ Arrays.stream(session.getChildSessionIds())
+ // Retrieve cached sessions matching ids.
+ .mapToObj(i -> mStagedSessions.get(i))
+ // Filter only the ones containing APEX.
+ .filter(childSession -> isApexSession(childSession))
+ .collect(Collectors.toList());
+ if (!childSessions.isEmpty()) {
+ success = submitSessionToApexService(session, childSessions, apexInfoList);
+ } // else this is a staged multi-package session with no APEX files.
+ }
- if (!submitSessionToApexService(session.sessionId, apexInfoList)) {
- success = false;
- } else {
- // For APEXes, we validate the signature here before we mark the session as ready,
- // so we fail the session early if there is a signature mismatch. For APKs, the
- // signature verification will be done by the package manager at the point at which
- // it applies the staged install.
- //
- // TODO: Decide whether we want to fail fast by detecting signature mismatches right
- // away.
- for (ApexInfo apexPackage : apexInfoList.apexInfos) {
- if (!validateApexSignatureLocked(apexPackage.packagePath,
- apexPackage.packageName)) {
- success = false;
- break;
- }
+ if (success && (apexInfoList.apexInfos.length > 0)) {
+ // For APEXes, we validate the signature here before we mark the session as ready,
+ // so we fail the session early if there is a signature mismatch. For APKs, the
+ // signature verification will be done by the package manager at the point at which
+ // it applies the staged install.
+ //
+ // TODO: Decide whether we want to fail fast by detecting signature mismatches for APKs,
+ // right away.
+ for (ApexInfo apexPackage : apexInfoList.apexInfos) {
+ if (!validateApexSignatureLocked(apexPackage.packagePath,
+ apexPackage.packageName)) {
+ success = false;
+ break;
}
}
}
+
if (success) {
session.setStagedSessionReady();
} else {
@@ -206,15 +238,59 @@
}
}
- void abortSession(@NonNull PackageInstallerSession sessionInfo) {
- updateStoredSession(sessionInfo);
+ void abortSession(@NonNull PackageInstallerSession session) {
synchronized (mStagedSessions) {
- mStagedSessions.remove(sessionInfo.sessionId);
+ updateStoredSession(session);
+ mStagedSessions.remove(session.sessionId);
}
}
+ @GuardedBy("mStagedSessions")
+ private boolean isMultiPackageSessionComplete(@NonNull PackageInstallerSession session) {
+ // This method assumes that the argument is either a parent session of a multi-package
+ // i.e. isMultiPackage() returns true, or that it is a child session, i.e.
+ // hasParentSessionId() returns true.
+ if (session.isMultiPackage()) {
+ // Parent session of a multi-package group. Check that we restored all the children.
+ for (int childSession : session.getChildSessionIds()) {
+ if (mStagedSessions.get(childSession) == null) {
+ return false;
+ }
+ }
+ return true;
+ }
+ if (session.hasParentSessionId()) {
+ PackageInstallerSession parent = mStagedSessions.get(session.getParentSessionId());
+ if (parent == null) {
+ return false;
+ }
+ return isMultiPackageSessionComplete(parent);
+ }
+ Slog.wtf(TAG, "Attempting to restore an invalid multi-package session.");
+ return false;
+ }
+
void restoreSession(@NonNull PackageInstallerSession session) {
- updateStoredSession(session);
+ PackageInstallerSession sessionToResume = session;
+ synchronized (mStagedSessions) {
+ mStagedSessions.append(session.sessionId, session);
+ // For multi-package sessions, we don't know in which order they will be restored. We
+ // need to wait until we have restored all the session in a group before restoring them.
+ if (session.isMultiPackage() || session.hasParentSessionId()) {
+ if (!isMultiPackageSessionComplete(session)) {
+ // Still haven't recovered all sessions of the group, return.
+ return;
+ }
+ // Group recovered, find the parent if necessary and resume the installation.
+ if (session.hasParentSessionId()) {
+ sessionToResume = mStagedSessions.get(session.getParentSessionId());
+ }
+ }
+ }
+ checkStateAndResume(sessionToResume);
+ }
+
+ private void checkStateAndResume(@NonNull PackageInstallerSession session) {
// Check the state of the session and decide what to do next.
if (session.isStagedSessionFailed() || session.isStagedSessionApplied()) {
// Final states, nothing to do.
@@ -227,6 +303,8 @@
} else {
// Session had already being marked ready. Start the checks to verify if there is any
// follow-up work.
+ // TODO(b/118865310): should this be synchronous to ensure it completes before
+ // systemReady() finishes?
mBgHandler.post(() -> resumeSession(session));
}
}
diff --git a/services/core/java/com/android/server/pm/dex/DexLogger.java b/services/core/java/com/android/server/pm/dex/DexLogger.java
index 78fa82c..59cc0cf 100644
--- a/services/core/java/com/android/server/pm/dex/DexLogger.java
+++ b/services/core/java/com/android/server/pm/dex/DexLogger.java
@@ -16,11 +16,15 @@
package com.android.server.pm.dex;
+import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_DEX;
+import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_NATIVE;
+
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.os.FileUtils;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.util.ByteStringUtils;
import android.util.EventLog;
@@ -35,20 +39,23 @@
import com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode;
import java.io.File;
+import java.io.IOException;
import java.util.Map;
import java.util.Set;
/**
- * This class is responsible for logging data about secondary dex files.
- * The data logged includes hashes of the name and content of each file.
+ * This class is responsible for logging data about secondary dex files and, despite the name,
+ * native code executed from an app's private directory. The data logged includes hashes of the
+ * name and content of each file.
*/
public class DexLogger {
private static final String TAG = "DexLogger";
- // Event log tag & subtag used for SafetyNet logging of dynamic
- // code loading (DCL) - see b/63927552.
+ // Event log tag & subtags used for SafetyNet logging of dynamic code loading (DCL) -
+ // see b/63927552.
private static final int SNET_TAG = 0x534e4554;
- private static final String DCL_SUBTAG = "dcl";
+ private static final String DCL_DEX_SUBTAG = "dcl";
+ private static final String DCL_NATIVE_SUBTAG = "dcln";
private final IPackageManager mPackageManager;
private final PackageDynamicCodeLoading mPackageDynamicCodeLoading;
@@ -114,12 +121,11 @@
}
int storageFlags;
- if (appInfo.deviceProtectedDataDir != null
- && FileUtils.contains(appInfo.deviceProtectedDataDir, filePath)) {
- storageFlags = StorageManager.FLAG_STORAGE_DE;
- } else if (appInfo.credentialProtectedDataDir != null
- && FileUtils.contains(appInfo.credentialProtectedDataDir, filePath)) {
+
+ if (fileIsUnder(filePath, appInfo.credentialProtectedDataDir)) {
storageFlags = StorageManager.FLAG_STORAGE_CE;
+ } else if (fileIsUnder(filePath, appInfo.deviceProtectedDataDir)) {
+ storageFlags = StorageManager.FLAG_STORAGE_DE;
} else {
Slog.e(TAG, "Could not infer CE/DE storage for path " + filePath);
needWrite |= mPackageDynamicCodeLoading.removeFile(packageName, filePath, userId);
@@ -139,6 +145,9 @@
+ ": " + e.getMessage());
}
+ String subtag = fileInfo.mFileType == FILE_TYPE_DEX
+ ? DCL_DEX_SUBTAG
+ : DCL_NATIVE_SUBTAG;
String fileName = new File(filePath).getName();
String message = PackageUtils.computeSha256Digest(fileName.getBytes());
@@ -165,7 +174,7 @@
}
if (loadingUid != -1) {
- writeDclEvent(loadingUid, message);
+ writeDclEvent(subtag, loadingUid, message);
}
}
}
@@ -175,21 +184,58 @@
}
}
+ private boolean fileIsUnder(String filePath, String directoryPath) {
+ if (directoryPath == null) {
+ return false;
+ }
+
+ try {
+ return FileUtils.contains(new File(directoryPath).getCanonicalPath(),
+ new File(filePath).getCanonicalPath());
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
@VisibleForTesting
PackageDynamicCode getPackageDynamicCodeInfo(String packageName) {
return mPackageDynamicCodeLoading.getPackageDynamicCodeInfo(packageName);
}
@VisibleForTesting
- void writeDclEvent(int uid, String message) {
- EventLog.writeEvent(SNET_TAG, DCL_SUBTAG, uid, message);
+ void writeDclEvent(String subtag, int uid, String message) {
+ EventLog.writeEvent(SNET_TAG, subtag, uid, message);
}
- void record(int loaderUserId, String dexPath,
- String owningPackageName, String loadingPackageName) {
+ void recordDex(int loaderUserId, String dexPath, String owningPackageName,
+ String loadingPackageName) {
if (mPackageDynamicCodeLoading.record(owningPackageName, dexPath,
- PackageDynamicCodeLoading.FILE_TYPE_DEX, loaderUserId,
- loadingPackageName)) {
+ FILE_TYPE_DEX, loaderUserId, loadingPackageName)) {
+ mPackageDynamicCodeLoading.maybeWriteAsync();
+ }
+ }
+
+ /**
+ * Record that an app running in the specified uid has executed native code from the file at
+ * {@link path}.
+ */
+ public void recordNative(int loadingUid, String path) {
+ String[] packages;
+ try {
+ packages = mPackageManager.getPackagesForUid(loadingUid);
+ if (packages == null || packages.length == 0) {
+ return;
+ }
+ } catch (RemoteException e) {
+ // Can't happen, we're local.
+ return;
+ }
+
+ String loadingPackageName = packages[0];
+ int loadingUserId = UserHandle.getUserId(loadingUid);
+
+ if (mPackageDynamicCodeLoading.record(loadingPackageName, path,
+ FILE_TYPE_NATIVE, loadingUserId, loadingPackageName)) {
mPackageDynamicCodeLoading.maybeWriteAsync();
}
}
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index b546836..1a2b115 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -235,7 +235,7 @@
continue;
}
- mDexLogger.record(loaderUserId, dexPath, searchResult.mOwningPackageName,
+ mDexLogger.recordDex(loaderUserId, dexPath, searchResult.mOwningPackageName,
loadingAppInfo.packageName);
if (classLoaderContexts != null) {
diff --git a/services/core/java/com/android/server/pm/dex/OWNERS b/services/core/java/com/android/server/pm/dex/OWNERS
new file mode 100644
index 0000000..fcc1f6c
--- /dev/null
+++ b/services/core/java/com/android/server/pm/dex/OWNERS
@@ -0,0 +1,4 @@
+agampe@google.com
+calin@google.com
+ngeoffray@google.com
+sehr@google.com
diff --git a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
index 6d4bc82..cc26c9b 100644
--- a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
+++ b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
@@ -53,6 +53,9 @@
// is represented in the text file format.)
static final int FILE_TYPE_DEX = 'D';
+ // Type code to indicate a secondary file containing native code.
+ static final int FILE_TYPE_NATIVE = 'N';
+
private static final String TAG = "PackageDynamicCodeLoading";
private static final String FILE_VERSION_HEADER = "DCL1";
@@ -107,7 +110,7 @@
*/
boolean record(String owningPackageName, String filePath, int fileType, int ownerUserId,
String loadingPackageName) {
- if (fileType != FILE_TYPE_DEX) {
+ if (!isValidFileType(fileType)) {
throw new IllegalArgumentException("Bad file type: " + fileType);
}
synchronized (mLock) {
@@ -120,6 +123,10 @@
}
}
+ private static boolean isValidFileType(int fileType) {
+ return fileType == FILE_TYPE_DEX || fileType == FILE_TYPE_NATIVE;
+ }
+
/**
* Return all packages that contain records of secondary dex files. (Note that data updates
* asynchronously, so {@link #getPackageDynamicCodeInfo} may still return null if passed
@@ -407,7 +414,7 @@
if (packages.length == 0) {
throw new IOException("Malformed line: " + line);
}
- if (type != FILE_TYPE_DEX) {
+ if (!isValidFileType(type)) {
throw new IOException("Unknown file type: " + line);
}
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index d5af313..20d6d4e 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -1290,6 +1290,7 @@
return mContext.getPackageManager().getPackageInfo(pkg,
DEFAULT_PACKAGE_INFO_QUERY_FLAGS | extraFlags);
} catch (NameNotFoundException e) {
+ Slog.e(TAG, "PackageNot found: " + pkg, e);
return null;
}
}
diff --git a/services/core/java/com/android/server/pm/permission/OWNERS b/services/core/java/com/android/server/pm/permission/OWNERS
index 88b97ea..01dc01e 100644
--- a/services/core/java/com/android/server/pm/permission/OWNERS
+++ b/services/core/java/com/android/server/pm/permission/OWNERS
@@ -1,4 +1,4 @@
-per-file DefaultPermissionGrantPolicy.java = bpoiesz@google.com
+moltmann@google.com
per-file DefaultPermissionGrantPolicy.java = hackbod@android.com
per-file DefaultPermissionGrantPolicy.java = jsharkey@android.com
per-file DefaultPermissionGrantPolicy.java = svetoslavganov@google.com
diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java
index 5516b23..c0517fd 100644
--- a/services/core/java/com/android/server/role/RoleManagerService.java
+++ b/services/core/java/com/android/server/role/RoleManagerService.java
@@ -342,10 +342,21 @@
@WorkerThread
private void notifyRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId) {
RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = getListeners(userId);
- if (listeners == null) {
- return;
+ if (listeners != null) {
+ notifyRoleHoldersChangedForListeners(listeners, roleName, userId);
}
+ RemoteCallbackList<IOnRoleHoldersChangedListener> allUserListeners = getListeners(
+ UserHandle.USER_ALL);
+ if (allUserListeners != null) {
+ notifyRoleHoldersChangedForListeners(allUserListeners, roleName, userId);
+ }
+ }
+
+ @WorkerThread
+ private void notifyRoleHoldersChangedForListeners(
+ @NonNull RemoteCallbackList<IOnRoleHoldersChangedListener> listeners,
+ @NonNull String roleName, @UserIdInt int userId) {
int broadcastCount = listeners.beginBroadcast();
try {
for (int i = 0; i < broadcastCount; i++) {
@@ -395,7 +406,7 @@
Slog.e(LOG_TAG, "user " + userId + " does not exist");
return Collections.emptyList();
}
- userId = handleIncomingUser(userId, "getRoleHoldersAsUser");
+ userId = handleIncomingUser(userId, "getRoleHoldersAsUser", false);
getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
"getRoleHoldersAsUser");
@@ -423,7 +434,7 @@
Slog.e(LOG_TAG, "user " + userId + " does not exist");
return;
}
- userId = handleIncomingUser(userId, "addRoleHolderAsUser");
+ userId = handleIncomingUser(userId, "addRoleHolderAsUser", false);
getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
"addRoleHolderAsUser");
@@ -440,7 +451,7 @@
Slog.e(LOG_TAG, "user " + userId + " does not exist");
return;
}
- userId = handleIncomingUser(userId, "removeRoleHolderAsUser");
+ userId = handleIncomingUser(userId, "removeRoleHolderAsUser", false);
getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
"removeRoleHolderAsUser");
@@ -457,7 +468,7 @@
Slog.e(LOG_TAG, "user " + userId + " does not exist");
return;
}
- userId = handleIncomingUser(userId, "clearRoleHoldersAsUser");
+ userId = handleIncomingUser(userId, "clearRoleHoldersAsUser", false);
getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
"clearRoleHoldersAsUser");
@@ -468,11 +479,12 @@
public void addOnRoleHoldersChangedListenerAsUser(
@NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) {
Preconditions.checkNotNull(listener, "listener cannot be null");
- if (!mUserManagerInternal.exists(userId)) {
+ if (userId != UserHandle.USER_ALL && !mUserManagerInternal.exists(userId)) {
Slog.e(LOG_TAG, "user " + userId + " does not exist");
return;
}
- userId = handleIncomingUser(userId, "addOnRoleHoldersChangedListenerAsUser");
+ userId = handleIncomingUser(userId, "addOnRoleHoldersChangedListenerAsUser",
+ true);
getContext().enforceCallingOrSelfPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS,
"addOnRoleHoldersChangedListenerAsUser");
@@ -485,11 +497,12 @@
public void removeOnRoleHoldersChangedListenerAsUser(
@NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) {
Preconditions.checkNotNull(listener, "listener cannot be null");
- if (!mUserManagerInternal.exists(userId)) {
+ if (userId != UserHandle.USER_ALL && !mUserManagerInternal.exists(userId)) {
Slog.e(LOG_TAG, "user " + userId + " does not exist");
return;
}
- userId = handleIncomingUser(userId, "removeOnRoleHoldersChangedListenerAsUser");
+ userId = handleIncomingUser(userId, "removeOnRoleHoldersChangedListenerAsUser",
+ true);
getContext().enforceCallingOrSelfPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS,
"removeOnRoleHoldersChangedListenerAsUser");
@@ -553,9 +566,10 @@
}
@CheckResult
- private int handleIncomingUser(@UserIdInt int userId, @NonNull String name) {
+ private int handleIncomingUser(@UserIdInt int userId, @NonNull String name,
+ boolean allowAll) {
return ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
- false, true, name, null);
+ allowAll, true, name, null);
}
@Override
diff --git a/services/core/java/com/android/server/rollback/RollbackData.java b/services/core/java/com/android/server/rollback/RollbackData.java
index f0589aa..4015c4f 100644
--- a/services/core/java/com/android/server/rollback/RollbackData.java
+++ b/services/core/java/com/android/server/rollback/RollbackData.java
@@ -29,6 +29,11 @@
*/
class RollbackData {
/**
+ * A unique identifier for this rollback.
+ */
+ public final int rollbackId;
+
+ /**
* The per-package rollback information.
*/
public final List<PackageRollbackInfo> packages = new ArrayList<>();
@@ -44,7 +49,8 @@
*/
public Instant timestamp;
- RollbackData(File backupDir) {
+ 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 d12f7ed..7f515bf 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -16,7 +16,6 @@
package com.android.server.rollback;
-import android.Manifest;
import android.app.AppOpsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -32,10 +31,10 @@
import android.content.pm.PackageParser;
import android.content.pm.ParceledListSlice;
import android.content.pm.StringParceledListSlice;
+import android.content.pm.VersionedPackage;
import android.content.rollback.IRollbackManager;
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.RollbackInfo;
-import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
@@ -43,24 +42,21 @@
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.storage.StorageManager;
import android.util.Log;
+import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
+import com.android.server.pm.Installer;
+import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.PackageManagerServiceUtils;
-import libcore.io.IoUtils;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
import java.io.File;
import java.io.IOException;
-import java.io.PrintWriter;
-import java.nio.file.Files;
+import java.security.SecureRandom;
import java.time.Instant;
-import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
@@ -68,9 +64,9 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Random;
import java.util.Set;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
/**
* Implementation of service that manages APK level rollbacks.
@@ -88,6 +84,13 @@
// mLock is held when they are called.
private final Object mLock = new Object();
+ // Used for generating rollback IDs.
+ private final Random mRandom = new SecureRandom();
+
+ // Set of allocated rollback ids
+ @GuardedBy("mLock")
+ private final SparseBooleanArray mAllocatedRollbackIds = new SparseBooleanArray();
+
// Package rollback data for rollback-enabled installs that have not yet
// been committed. Maps from sessionId to rollback data.
@GuardedBy("mLock")
@@ -108,46 +111,22 @@
@GuardedBy("mLock")
private List<RollbackInfo> mRecentlyExecutedRollbacks;
- // Data for available rollbacks and recently executed rollbacks is
- // persisted in storage. Assuming the rollback data directory is
- // /data/rollback, we use the following directory structure
- // to store this data:
- // /data/rollback/
- // available/
- // XXX/
- // com.package.A/
- // base.apk
- // info.json
- // enabled.txt
- // YYY/
- // com.package.B/
- // base.apk
- // info.json
- // enabled.txt
- // recently_executed.json
- //
- // * XXX, YYY are random strings from Files.createTempDirectory
- // * info.json contains the package version to roll back from/to.
- // * enabled.txt contains a timestamp for when the rollback was first
- // made available. This file is not written until the rollback is made
- // available.
- //
- // TODO: Use AtomicFile for all the .json files?
- private final File mRollbackDataDir;
- private final File mAvailableRollbacksDir;
- private final File mRecentlyExecutedRollbacksFile;
+ private final RollbackStore mRollbackStore;
private final Context mContext;
private final HandlerThread mHandlerThread;
+ private final Installer mInstaller;
RollbackManagerServiceImpl(Context context) {
mContext = context;
+ // Note that we're calling onStart here because this object is only constructed on
+ // SystemService#onStart.
+ mInstaller = new Installer(mContext);
+ mInstaller.onStart();
mHandlerThread = new HandlerThread("RollbackManagerServiceHandler");
mHandlerThread.start();
- mRollbackDataDir = new File(Environment.getDataDirectory(), "rollback");
- mAvailableRollbacksDir = new File(mRollbackDataDir, "available");
- mRecentlyExecutedRollbacksFile = new File(mRollbackDataDir, "recently_executed.json");
+ mRollbackStore = new RollbackStore(new File(Environment.getDataDirectory(), "rollback"));
// Kick off loading of the rollback data from strorage in a background
// thread.
@@ -158,8 +137,8 @@
// expiration.
getHandler().post(() -> ensureRollbackDataLoaded());
- PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
- installer.registerSessionCallback(new SessionCallback(), getHandler());
+ PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller();
+ packageInstaller.registerSessionCallback(new SessionCallback(), getHandler());
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
@@ -196,10 +175,13 @@
PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_TOKEN, -1);
int installFlags = intent.getIntExtra(
PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALL_FLAGS, 0);
+ int[] installedUsers = intent.getIntArrayExtra(
+ PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS);
File newPackageCodePath = new File(intent.getData().getPath());
getHandler().post(() -> {
- boolean success = enableRollback(installFlags, newPackageCodePath);
+ boolean success = enableRollback(installFlags, newPackageCodePath,
+ installedUsers);
int ret = PackageManagerInternal.ENABLE_ROLLBACK_SUCCEEDED;
if (!success) {
ret = PackageManagerInternal.ENABLE_ROLLBACK_FAILED;
@@ -236,10 +218,10 @@
// it's out of date or not, so no need to check package versions here.
for (PackageRollbackInfo info : data.packages) {
- if (info.packageName.equals(packageName)) {
+ if (info.getPackageName().equals(packageName)) {
// TODO: Once the RollbackInfo API supports info about
// dependant packages, add that info here.
- return new RollbackInfo(info);
+ return new RollbackInfo(data.rollbackId, info);
}
}
return null;
@@ -257,7 +239,7 @@
for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
RollbackData data = mAvailableRollbacks.get(i);
for (PackageRollbackInfo info : data.packages) {
- packageNames.add(info.packageName);
+ packageNames.add(info.getPackageName());
}
}
}
@@ -299,7 +281,7 @@
*/
private void executeRollbackInternal(RollbackInfo rollback,
String callerPackageName, IntentSender statusReceiver) {
- String targetPackageName = rollback.targetPackage.packageName;
+ String targetPackageName = rollback.targetPackage.getPackageName();
Log.i(TAG, "Initiating rollback of " + targetPackageName);
// Get the latest RollbackData for the target package.
@@ -309,15 +291,9 @@
return;
}
- // Verify the latest rollback matches the version requested.
- // TODO: Check dependant packages too once RollbackInfo includes that
- // information.
- for (PackageRollbackInfo info : data.packages) {
- if (info.packageName.equals(targetPackageName)
- && !rollback.targetPackage.higherVersion.equals(info.higherVersion)) {
- sendFailure(statusReceiver, "Rollback is out of date.");
- return;
- }
+ if (data.rollbackId != rollback.getRollbackId()) {
+ sendFailure(statusReceiver, "Rollback for package is out of date");
+ return;
}
// Verify the RollbackData is up to date with what's installed on
@@ -329,15 +305,14 @@
// Figure out how to ensure we don't commit the rollback if
// roll forward happens at the same time.
for (PackageRollbackInfo info : data.packages) {
- PackageRollbackInfo.PackageVersion installedVersion =
- getInstalledPackageVersion(info.packageName);
+ VersionedPackage installedVersion = getInstalledPackageVersion(info.getPackageName());
if (installedVersion == null) {
// TODO: Test this case
sendFailure(statusReceiver, "Package to roll back is not installed");
return;
}
- if (!info.higherVersion.equals(installedVersion)) {
+ if (!packageVersionsEqual(info.getVersionRolledBackFrom(), installedVersion)) {
// TODO: Test this case
sendFailure(statusReceiver, "Package version to roll back not installed.");
return;
@@ -357,8 +332,14 @@
PackageManager pm = context.getPackageManager();
try {
PackageInstaller packageInstaller = pm.getPackageInstaller();
+ String installerPackageName = pm.getInstallerPackageName(targetPackageName);
+ if (installerPackageName == null) {
+ sendFailure(statusReceiver, "Cannot find installer package");
+ return;
+ }
PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ parentParams.setInstallerPackageName(installerPackageName);
parentParams.setAllowDowngrade(true);
parentParams.setMultiPackage();
int parentSessionId = packageInstaller.createSession(parentParams);
@@ -367,13 +348,14 @@
for (PackageRollbackInfo info : data.packages) {
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ params.setInstallerPackageName(installerPackageName);
params.setAllowDowngrade(true);
int sessionId = packageInstaller.createSession(params);
PackageInstaller.Session session = packageInstaller.openSession(sessionId);
// TODO: Will it always be called "base.apk"? What about splits?
// What about apex?
- File packageDir = new File(data.backupDir, info.packageName);
+ File packageDir = new File(data.backupDir, info.getPackageName());
File baseApk = new File(packageDir, "base.apk");
try (ParcelFileDescriptor fd = ParcelFileDescriptor.open(baseApk,
ParcelFileDescriptor.MODE_READ_ONLY)) {
@@ -387,28 +369,30 @@
parentSession.addChildSessionId(sessionId);
}
- final LocalIntentReceiver receiver = new LocalIntentReceiver();
+ final LocalIntentReceiver receiver = new LocalIntentReceiver(
+ (Intent result) -> {
+ int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+ PackageInstaller.STATUS_FAILURE);
+ if (status != PackageInstaller.STATUS_SUCCESS) {
+ sendFailure(statusReceiver, "Rollback downgrade install failed: "
+ + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE));
+ return;
+ }
+
+ addRecentlyExecutedRollback(rollback);
+ sendSuccess(statusReceiver);
+
+ Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
+
+ // TODO: This call emits the warning "Calling a method in the
+ // system process without a qualified user". Fix that.
+ // TODO: Limit this to receivers holding the
+ // MANAGE_ROLLBACKS permission?
+ mContext.sendBroadcast(broadcast);
+ }
+ );
+
parentSession.commit(receiver.getIntentSender());
-
- Intent result = receiver.getResult();
- int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
- PackageInstaller.STATUS_FAILURE);
- if (status != PackageInstaller.STATUS_SUCCESS) {
- sendFailure(statusReceiver, "Rollback downgrade install failed: "
- + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE));
- return;
- }
-
- addRecentlyExecutedRollback(rollback);
- sendSuccess(statusReceiver);
-
- Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED,
- Uri.fromParts("package", targetPackageName,
- Manifest.permission.MANAGE_ROLLBACKS));
-
- // TODO: This call emits the warning "Calling a method in the
- // system process without a qualified user". Fix that.
- mContext.sendBroadcast(broadcast);
} catch (IOException e) {
Log.e(TAG, "Unable to roll back " + targetPackageName, e);
sendFailure(statusReceiver, "IOException: " + e.toString());
@@ -445,9 +429,9 @@
while (iter.hasNext()) {
RollbackData data = iter.next();
for (PackageRollbackInfo info : data.packages) {
- if (info.packageName.equals(packageName)) {
+ if (info.getPackageName().equals(packageName)) {
iter.remove();
- removeFile(data.backupDir);
+ mRollbackStore.deleteAvailableRollback(data);
break;
}
}
@@ -474,85 +458,26 @@
@GuardedBy("mLock")
private void ensureRollbackDataLoadedLocked() {
if (mAvailableRollbacks == null) {
- loadRollbackDataLocked();
+ loadAllRollbackDataLocked();
}
}
/**
- * Load rollback data from storage.
+ * Load all rollback data from storage.
* Note: We do potentially heavy IO here while holding mLock, because we
* have to have the rollback data loaded before we can do anything else
* meaningful.
*/
@GuardedBy("mLock")
- private void loadRollbackDataLocked() {
- mAvailableRollbacksDir.mkdirs();
- mAvailableRollbacks = new ArrayList<>();
- for (File rollbackDir : mAvailableRollbacksDir.listFiles()) {
- File enabledFile = new File(rollbackDir, "enabled.txt");
- // TODO: Delete any directories without an enabled.txt? That could
- // potentially delete pending rollback data if reloadPersistedData
- // is called, though there's no reason besides testing for that to
- // be called.
- if (rollbackDir.isDirectory() && enabledFile.isFile()) {
- RollbackData data = new RollbackData(rollbackDir);
- try {
- PackageRollbackInfo info = null;
- for (File packageDir : rollbackDir.listFiles()) {
- if (packageDir.isDirectory()) {
- File jsonFile = new File(packageDir, "info.json");
- String jsonString = IoUtils.readFileAsString(
- jsonFile.getAbsolutePath());
- JSONObject jsonObject = new JSONObject(jsonString);
- String packageName = jsonObject.getString("packageName");
- long higherVersionCode = jsonObject.getLong("higherVersionCode");
- long lowerVersionCode = jsonObject.getLong("lowerVersionCode");
-
- data.packages.add(new PackageRollbackInfo(packageName,
- new PackageRollbackInfo.PackageVersion(higherVersionCode),
- new PackageRollbackInfo.PackageVersion(lowerVersionCode)));
- }
- }
-
- if (data.packages.isEmpty()) {
- throw new IOException("No package rollback info found");
- }
-
- String enabledString = IoUtils.readFileAsString(enabledFile.getAbsolutePath());
- data.timestamp = Instant.parse(enabledString.trim());
- mAvailableRollbacks.add(data);
- } catch (IOException | JSONException | DateTimeParseException e) {
- Log.e(TAG, "Unable to read rollback data at " + rollbackDir, e);
- removeFile(rollbackDir);
- }
- }
+ private void loadAllRollbackDataLocked() {
+ mAvailableRollbacks = mRollbackStore.loadAvailableRollbacks();
+ for (RollbackData data : mAvailableRollbacks) {
+ mAllocatedRollbackIds.put(data.rollbackId, true);
}
- mRecentlyExecutedRollbacks = new ArrayList<>();
- if (mRecentlyExecutedRollbacksFile.exists()) {
- try {
- // TODO: How to cope with changes to the format of this file from
- // when RollbackStore is updated in the future?
- String jsonString = IoUtils.readFileAsString(
- mRecentlyExecutedRollbacksFile.getAbsolutePath());
- JSONObject object = new JSONObject(jsonString);
- JSONArray array = object.getJSONArray("recentlyExecuted");
- for (int i = 0; i < array.length(); ++i) {
- JSONObject element = array.getJSONObject(i);
- String packageName = element.getString("packageName");
- long higherVersionCode = element.getLong("higherVersionCode");
- long lowerVersionCode = element.getLong("lowerVersionCode");
- PackageRollbackInfo target = new PackageRollbackInfo(packageName,
- new PackageRollbackInfo.PackageVersion(higherVersionCode),
- new PackageRollbackInfo.PackageVersion(lowerVersionCode));
- RollbackInfo rollback = new RollbackInfo(target);
- mRecentlyExecutedRollbacks.add(rollback);
- }
- } catch (IOException | JSONException e) {
- // TODO: What to do here? Surely we shouldn't just forget about
- // everything after the point of exception?
- Log.e(TAG, "Failed to read recently executed rollbacks", e);
- }
+ mRecentlyExecutedRollbacks = mRollbackStore.loadRecentlyExecutedRollbacks();
+ for (RollbackInfo info : mRecentlyExecutedRollbacks) {
+ mAllocatedRollbackIds.put(info.getRollbackId(), true);
}
scheduleExpiration(0);
@@ -566,8 +491,7 @@
private void onPackageReplaced(String packageName) {
// TODO: Could this end up incorrectly deleting a rollback for a
// package that is about to be installed?
- PackageRollbackInfo.PackageVersion installedVersion =
- getInstalledPackageVersion(packageName);
+ VersionedPackage installedVersion = getInstalledPackageVersion(packageName);
synchronized (mLock) {
ensureRollbackDataLoadedLocked();
@@ -575,10 +499,12 @@
while (iter.hasNext()) {
RollbackData data = iter.next();
for (PackageRollbackInfo info : data.packages) {
- if (info.packageName.equals(packageName)
- && !info.higherVersion.equals(installedVersion)) {
+ if (info.getPackageName().equals(packageName)
+ && !packageVersionsEqual(
+ info.getVersionRolledBackFrom(),
+ installedVersion)) {
iter.remove();
- removeFile(data.backupDir);
+ mRollbackStore.deleteAvailableRollback(data);
break;
}
}
@@ -599,49 +525,19 @@
boolean changed = false;
while (iter.hasNext()) {
RollbackInfo rollback = iter.next();
- if (packageName.equals(rollback.targetPackage.packageName)) {
+ if (packageName.equals(rollback.targetPackage.getPackageName())) {
iter.remove();
changed = true;
}
}
if (changed) {
- saveRecentlyExecutedRollbacksLocked();
+ mRollbackStore.saveRecentlyExecutedRollbacks(mRecentlyExecutedRollbacks);
}
}
}
/**
- * Write the list of recently executed rollbacks to storage.
- * Note: This happens while mLock is held, which should be okay because we
- * expect executed rollbacks to be modified only in exceptional cases.
- */
- @GuardedBy("mLock")
- private void saveRecentlyExecutedRollbacksLocked() {
- try {
- JSONObject json = new JSONObject();
- JSONArray array = new JSONArray();
- json.put("recentlyExecuted", array);
-
- for (int i = 0; i < mRecentlyExecutedRollbacks.size(); ++i) {
- RollbackInfo rollback = mRecentlyExecutedRollbacks.get(i);
- JSONObject element = new JSONObject();
- element.put("packageName", rollback.targetPackage.packageName);
- element.put("higherVersionCode", rollback.targetPackage.higherVersion.versionCode);
- element.put("lowerVersionCode", rollback.targetPackage.lowerVersion.versionCode);
- array.put(element);
- }
-
- PrintWriter pw = new PrintWriter(mRecentlyExecutedRollbacksFile);
- pw.println(json.toString());
- pw.close();
- } catch (IOException | JSONException e) {
- // TODO: What to do here?
- Log.e(TAG, "Failed to save recently executed rollbacks", e);
- }
- }
-
- /**
* Records that the given package has been recently rolled back.
*/
private void addRecentlyExecutedRollback(RollbackInfo rollback) {
@@ -650,7 +546,7 @@
synchronized (mLock) {
ensureRollbackDataLoadedLocked();
mRecentlyExecutedRollbacks.add(rollback);
- saveRecentlyExecutedRollbacksLocked();
+ mRollbackStore.saveRecentlyExecutedRollbacks(mRecentlyExecutedRollbacks);
}
}
@@ -701,7 +597,7 @@
RollbackData data = iter.next();
if (!now.isBefore(data.timestamp.plusMillis(ROLLBACK_LIFETIME_DURATION_MILLIS))) {
iter.remove();
- removeFile(data.backupDir);
+ mRollbackStore.deleteAvailableRollback(data);
} else if (oldest == null || oldest.isAfter(data.timestamp)) {
oldest = data.timestamp;
}
@@ -748,12 +644,13 @@
* staged for install with rollback enabled. Called before the package has
* been installed.
*
- * @param id the id of the enable rollback request
* @param installFlags information about what is being installed.
* @param newPackageCodePath path to the package about to be installed.
+ * @param installedUsers the set of users for which a given package is installed.
* @return true if enabling the rollback succeeds, false otherwise.
*/
- private boolean enableRollback(int installFlags, File newPackageCodePath) {
+ private boolean enableRollback(int installFlags, File newPackageCodePath,
+ int[] installedUsers) {
if ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
Log.e(TAG, "Rollbacks not supported for instant app install");
return false;
@@ -803,8 +700,7 @@
return false;
}
- PackageRollbackInfo.PackageVersion newVersion =
- new PackageRollbackInfo.PackageVersion(newPackage.versionCode);
+ VersionedPackage newVersion = new VersionedPackage(packageName, newPackage.versionCode);
// Get information about the currently installed package.
PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
@@ -815,11 +711,29 @@
Log.e(TAG, packageName + " is not installed");
return false;
}
- PackageRollbackInfo.PackageVersion installedVersion =
- new PackageRollbackInfo.PackageVersion(installedPackage.getLongVersionCode());
+ VersionedPackage installedVersion = new VersionedPackage(packageName,
+ installedPackage.getLongVersionCode());
- PackageRollbackInfo info = new PackageRollbackInfo(
- packageName, newVersion, installedVersion);
+ for (int user : installedUsers) {
+ final int storageFlags;
+ if (StorageManager.isFileEncryptedNativeOrEmulated()
+ && !StorageManager.isUserKeyUnlocked(user)) {
+ // We've encountered a user that hasn't unlocked on a FBE device, so we can't copy
+ // across app user data until the user unlocks their device.
+ Log.e(TAG, "User: " + user + " isn't unlocked, skipping CE userdata backup.");
+ storageFlags = Installer.FLAG_STORAGE_DE;
+ } else {
+ storageFlags = Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE;
+ }
+
+ try {
+ mInstaller.snapshotAppData(packageName, user, storageFlags);
+ } catch (InstallerException ie) {
+ Log.e(TAG, "Unable to create app data snapshot for: " + packageName, ie);
+ }
+ }
+
+ PackageRollbackInfo info = new PackageRollbackInfo(newVersion, installedVersion);
RollbackData data;
try {
@@ -827,9 +741,8 @@
mChildSessions.put(childSessionId, parentSessionId);
data = mPendingRollbacks.get(parentSessionId);
if (data == null) {
- File backupDir = Files.createTempDirectory(
- mAvailableRollbacksDir.toPath(), null).toFile();
- data = new RollbackData(backupDir);
+ int rollbackId = allocateRollbackIdLocked();
+ data = mRollbackStore.createAvailableRollback(rollbackId);
mPendingRollbacks.put(parentSessionId, data);
}
data.packages.add(info);
@@ -839,85 +752,84 @@
return false;
}
- File packageDir = new File(data.backupDir, packageName);
+ File packageDir = mRollbackStore.packageCodePathForAvailableRollback(data, packageName);
packageDir.mkdirs();
- try {
- JSONObject json = new JSONObject();
- json.put("packageName", packageName);
- json.put("higherVersionCode", newVersion.versionCode);
- json.put("lowerVersionCode", installedVersion.versionCode);
-
- File jsonFile = new File(packageDir, "info.json");
- PrintWriter pw = new PrintWriter(jsonFile);
- pw.println(json.toString());
- pw.close();
- } catch (IOException | JSONException e) {
- Log.e(TAG, "Unable to create rollback for " + packageName, e);
- removeFile(packageDir);
- return false;
- }
// TODO: Copy by hard link instead to save on cpu and storage space?
int status = PackageManagerServiceUtils.copyPackage(installedPackage.codePath, packageDir);
if (status != PackageManager.INSTALL_SUCCEEDED) {
Log.e(TAG, "Unable to copy package for rollback for " + packageName);
- removeFile(packageDir);
return false;
}
return true;
}
- // TODO: Don't copy this from PackageManagerShellCommand like this?
- private static class LocalIntentReceiver {
- private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
+ @Override
+ public void restoreUserData(String packageName, int userId, int appId, long ceDataInode,
+ String seInfo, int token) {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("restoureUserData may only be called by the system.");
+ }
+
+ 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) {
+ pmi.finishPackageInstall(token, false);
+ return;
+ }
+
+ final int storageFlags;
+ if (StorageManager.isFileEncryptedNativeOrEmulated()
+ && !StorageManager.isUserKeyUnlocked(userId)) {
+ // We've encountered a user that hasn't unlocked on a FBE device, so we can't copy
+ // across app user data until the user unlocks their device.
+ Log.e(TAG, "User: " + userId + " isn't unlocked, skipping CE userdata restore.");
+
+ storageFlags = Installer.FLAG_STORAGE_DE;
+ } else {
+ storageFlags = Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE;
+ }
+
+ try {
+ mInstaller.restoreAppDataSnapshot(packageName, appId, ceDataInode,
+ seInfo, userId, storageFlags);
+ } catch (InstallerException ie) {
+ Log.e(TAG, "Unable to restore app data snapshot: " + packageName, ie);
+ }
+
+ pmi.finishPackageInstall(token, false);
+ });
+ }
+
+ private class LocalIntentReceiver {
+ final Consumer<Intent> mConsumer;
+
+ LocalIntentReceiver(Consumer<Intent> consumer) {
+ mConsumer = consumer;
+ }
private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
@Override
public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
- try {
- mResult.offer(intent, 5, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
+ getHandler().post(() -> mConsumer.accept(intent));
}
};
public IntentSender getIntentSender() {
return new IntentSender((IIntentSender) mLocalSender);
}
-
- public Intent getResult() {
- try {
- return mResult.take();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
- }
-
- /**
- * Deletes a file completely.
- * If the file is a directory, its contents are deleted as well.
- * Has no effect if the directory does not exist.
- */
- private void removeFile(File file) {
- if (file.isDirectory()) {
- for (File child : file.listFiles()) {
- removeFile(child);
- }
- }
- if (file.exists()) {
- file.delete();
- }
}
/**
* Gets the version of the package currently installed.
* Returns null if the package is not currently installed.
*/
- private PackageRollbackInfo.PackageVersion getInstalledPackageVersion(String packageName) {
+ private VersionedPackage getInstalledPackageVersion(String packageName) {
PackageManager pm = mContext.getPackageManager();
PackageInfo pkgInfo = null;
try {
@@ -926,7 +838,12 @@
return null;
}
- return new PackageRollbackInfo.PackageVersion(pkgInfo.getLongVersionCode());
+ return new VersionedPackage(packageName, pkgInfo.getLongVersionCode());
+ }
+
+ private boolean packageVersionsEqual(VersionedPackage a, VersionedPackage b) {
+ return a.getPackageName().equals(b.getPackageName())
+ && a.getLongVersionCode() == b.getLongVersionCode();
}
private class SessionCallback extends PackageInstaller.SessionCallback {
@@ -958,11 +875,8 @@
if (success) {
try {
data.timestamp = Instant.now();
- File enabledFile = new File(data.backupDir, "enabled.txt");
- PrintWriter pw = new PrintWriter(enabledFile);
- pw.println(data.timestamp.toString());
- pw.close();
+ mRollbackStore.saveAvailableRollback(data);
synchronized (mLock) {
// Note: There is a small window of time between when
// the session has been committed by the package
@@ -981,12 +895,12 @@
scheduleExpiration(ROLLBACK_LIFETIME_DURATION_MILLIS);
} catch (IOException e) {
Log.e(TAG, "Unable to enable rollback", e);
- removeFile(data.backupDir);
+ mRollbackStore.deleteAvailableRollback(data);
}
} else {
// The install session was aborted, clean up the pending
// install.
- removeFile(data.backupDir);
+ mRollbackStore.deleteAvailableRollback(data);
}
}
}
@@ -1005,7 +919,7 @@
for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
RollbackData data = mAvailableRollbacks.get(i);
for (PackageRollbackInfo info : data.packages) {
- if (info.packageName.equals(packageName)) {
+ if (info.getPackageName().equals(packageName)) {
return data;
}
}
@@ -1013,4 +927,19 @@
}
return null;
}
+
+ @GuardedBy("mLock")
+ private int allocateRollbackIdLocked() throws IOException {
+ int n = 0;
+ int rollbackId;
+ do {
+ rollbackId = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
+ if (!mAllocatedRollbackIds.get(rollbackId, false)) {
+ mAllocatedRollbackIds.put(rollbackId, true);
+ return rollbackId;
+ }
+ } while (n++ < 32);
+
+ throw new IOException("Failed to allocate rollback ID");
+ }
}
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
new file mode 100644
index 0000000..7738be9
--- /dev/null
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -0,0 +1,266 @@
+/*
+ * 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.rollback;
+
+import android.content.pm.VersionedPackage;
+import android.content.rollback.PackageRollbackInfo;
+import android.content.rollback.RollbackInfo;
+import android.util.Log;
+
+import libcore.io.IoUtils;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class for loading and saving rollback data to persistent storage.
+ */
+class RollbackStore {
+ private static final String TAG = "RollbackManager";
+
+ // Assuming the rollback data directory is /data/rollback, we use the
+ // following directory structure to store persisted data for available and
+ // recently executed rollbacks:
+ // /data/rollback/
+ // available/
+ // XXX/
+ // rollback.json
+ // com.package.A/
+ // base.apk
+ // com.package.B/
+ // base.apk
+ // YYY/
+ // rollback.json
+ // com.package.C/
+ // base.apk
+ // recently_executed.json
+ //
+ // * XXX, YYY are the rollbackIds for the corresponding rollbacks.
+ // * rollback.json contains all relevant metadata for the rollback. This
+ // file is not written until the rollback is made available.
+ //
+ // TODO: Use AtomicFile for all the .json files?
+ private final File mRollbackDataDir;
+ private final File mAvailableRollbacksDir;
+ private final File mRecentlyExecutedRollbacksFile;
+
+ RollbackStore(File rollbackDataDir) {
+ mRollbackDataDir = rollbackDataDir;
+ mAvailableRollbacksDir = new File(mRollbackDataDir, "available");
+ mRecentlyExecutedRollbacksFile = new File(mRollbackDataDir, "recently_executed.json");
+ }
+
+ /**
+ * Reads the list of available rollbacks from persistent storage.
+ */
+ List<RollbackData> loadAvailableRollbacks() {
+ List<RollbackData> availableRollbacks = new ArrayList<>();
+ mAvailableRollbacksDir.mkdirs();
+ for (File rollbackDir : mAvailableRollbacksDir.listFiles()) {
+ if (rollbackDir.isDirectory()) {
+ try {
+ RollbackData data = loadRollbackData(rollbackDir);
+ availableRollbacks.add(data);
+ } catch (IOException e) {
+ // Note: Deleting the rollbackDir here will cause pending
+ // rollbacks to be deleted. This should only ever happen
+ // if reloadPersistedData is called while there are
+ // pending rollbacks. The reloadPersistedData method is
+ // currently only for testing, so that should be okay.
+ Log.e(TAG, "Unable to read rollback data at " + rollbackDir, e);
+ removeFile(rollbackDir);
+ }
+ }
+ }
+ return availableRollbacks;
+ }
+
+ /**
+ * Reads the list of recently executed rollbacks from persistent storage.
+ */
+ List<RollbackInfo> loadRecentlyExecutedRollbacks() {
+ List<RollbackInfo> recentlyExecutedRollbacks = new ArrayList<>();
+ if (mRecentlyExecutedRollbacksFile.exists()) {
+ try {
+ // TODO: How to cope with changes to the format of this file from
+ // when RollbackStore is updated in the future?
+ String jsonString = IoUtils.readFileAsString(
+ mRecentlyExecutedRollbacksFile.getAbsolutePath());
+ JSONObject object = new JSONObject(jsonString);
+ JSONArray array = object.getJSONArray("recentlyExecuted");
+ for (int i = 0; i < array.length(); ++i) {
+ JSONObject element = array.getJSONObject(i);
+ int rollbackId = element.getInt("rollbackId");
+ String packageName = element.getString("packageName");
+ long higherVersionCode = element.getLong("higherVersionCode");
+ long lowerVersionCode = element.getLong("lowerVersionCode");
+ PackageRollbackInfo target = new PackageRollbackInfo(
+ new VersionedPackage(packageName, higherVersionCode),
+ new VersionedPackage(packageName, lowerVersionCode));
+ RollbackInfo rollback = new RollbackInfo(rollbackId, target);
+ recentlyExecutedRollbacks.add(rollback);
+ }
+ } catch (IOException | JSONException e) {
+ // TODO: What to do here? Surely we shouldn't just forget about
+ // everything after the point of exception?
+ Log.e(TAG, "Failed to read recently executed rollbacks", e);
+ }
+ }
+
+ return recentlyExecutedRollbacks;
+ }
+
+ /**
+ * Creates a new RollbackData instance with backupDir assigned.
+ */
+ RollbackData createAvailableRollback(int rollbackId) throws IOException {
+ File backupDir = new File(mAvailableRollbacksDir, Integer.toString(rollbackId));
+ return new RollbackData(rollbackId, backupDir);
+ }
+
+ /**
+ * Returns the directory where the code for a package should be stored for
+ * given rollback <code>data</code> and <code>packageName</code>.
+ */
+ File packageCodePathForAvailableRollback(RollbackData data, String packageName) {
+ return new File(data.backupDir, packageName);
+ }
+
+ /**
+ * Writes the metadata for an available rollback to persistent storage.
+ */
+ void saveAvailableRollback(RollbackData data) throws IOException {
+ try {
+ JSONObject dataJson = new JSONObject();
+ JSONArray packagesJson = new JSONArray();
+ for (PackageRollbackInfo info : data.packages) {
+ JSONObject infoJson = new JSONObject();
+ infoJson.put("packageName", info.getPackageName());
+ infoJson.put("higherVersionCode",
+ info.getVersionRolledBackFrom().getLongVersionCode());
+ infoJson.put("lowerVersionCode",
+ info.getVersionRolledBackTo().getVersionCode());
+ packagesJson.put(infoJson);
+ }
+ dataJson.put("rollbackId", data.rollbackId);
+ dataJson.put("packages", packagesJson);
+ dataJson.put("timestamp", data.timestamp.toString());
+
+ PrintWriter pw = new PrintWriter(new File(data.backupDir, "rollback.json"));
+ pw.println(dataJson.toString());
+ pw.close();
+ } catch (JSONException e) {
+ throw new IOException(e);
+ }
+ }
+
+ /**
+ * Removes all persistant storage associated with the given available
+ * rollback.
+ */
+ void deleteAvailableRollback(RollbackData data) {
+ // TODO(narayan): Make sure we delete the userdata snapshot along with the backup of the
+ // actual app.
+ removeFile(data.backupDir);
+ }
+
+ /**
+ * Writes the list of recently executed rollbacks to storage.
+ */
+ void saveRecentlyExecutedRollbacks(List<RollbackInfo> recentlyExecutedRollbacks) {
+ try {
+ JSONObject json = new JSONObject();
+ JSONArray array = new JSONArray();
+ json.put("recentlyExecuted", array);
+
+ for (int i = 0; i < recentlyExecutedRollbacks.size(); ++i) {
+ RollbackInfo rollback = recentlyExecutedRollbacks.get(i);
+ JSONObject element = new JSONObject();
+ element.put("rollbackId", rollback.getRollbackId());
+ element.put("packageName", rollback.targetPackage.getPackageName());
+ element.put("higherVersionCode",
+ rollback.targetPackage.getVersionRolledBackFrom().getLongVersionCode());
+ element.put("lowerVersionCode",
+ rollback.targetPackage.getVersionRolledBackTo().getLongVersionCode());
+ array.put(element);
+ }
+
+ PrintWriter pw = new PrintWriter(mRecentlyExecutedRollbacksFile);
+ pw.println(json.toString());
+ pw.close();
+ } catch (IOException | JSONException e) {
+ // TODO: What to do here?
+ Log.e(TAG, "Failed to save recently executed rollbacks", e);
+ }
+ }
+
+ /**
+ * Reads the metadata for a rollback from the given directory.
+ * @throws IOException in case of error reading the data.
+ */
+ private RollbackData loadRollbackData(File backupDir) throws IOException {
+ try {
+ File rollbackJsonFile = new File(backupDir, "rollback.json");
+ JSONObject dataJson = new JSONObject(
+ IoUtils.readFileAsString(rollbackJsonFile.getAbsolutePath()));
+
+ int rollbackId = dataJson.getInt("rollbackId");
+ RollbackData data = new RollbackData(rollbackId, backupDir);
+
+ JSONArray packagesJson = dataJson.getJSONArray("packages");
+ for (int i = 0; i < packagesJson.length(); ++i) {
+ JSONObject infoJson = packagesJson.getJSONObject(i);
+ String packageName = infoJson.getString("packageName");
+ long higherVersionCode = infoJson.getLong("higherVersionCode");
+ long lowerVersionCode = infoJson.getLong("lowerVersionCode");
+ data.packages.add(new PackageRollbackInfo(
+ new VersionedPackage(packageName, higherVersionCode),
+ new VersionedPackage(packageName, lowerVersionCode)));
+ }
+
+ data.timestamp = Instant.parse(dataJson.getString("timestamp"));
+ return data;
+ } catch (JSONException | DateTimeParseException e) {
+ throw new IOException(e);
+ }
+ }
+
+ /**
+ * Deletes a file completely.
+ * If the file is a directory, its contents are deleted as well.
+ * Has no effect if the directory does not exist.
+ */
+ private void removeFile(File file) {
+ if (file.isDirectory()) {
+ for (File child : file.listFiles()) {
+ removeFile(child);
+ }
+ }
+ if (file.exists()) {
+ file.delete();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 4e71a05..40664fe 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -96,10 +96,10 @@
import com.android.internal.os.BinderCallsStats.ExportedCallStat;
import com.android.internal.os.KernelCpuSpeedReader;
import com.android.internal.os.KernelCpuThreadReader;
-import com.android.internal.os.KernelUidCpuActiveTimeReader;
-import com.android.internal.os.KernelUidCpuClusterTimeReader;
-import com.android.internal.os.KernelUidCpuFreqTimeReader;
-import com.android.internal.os.KernelUidCpuTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
import com.android.internal.os.KernelWakelockReader;
import com.android.internal.os.KernelWakelockStats;
import com.android.internal.os.LooperStats;
@@ -231,14 +231,16 @@
private final HashMap<Long, String> mDeletedFiles = new HashMap<>();
private final CompanionHandler mHandler;
- private KernelUidCpuTimeReader mKernelUidCpuTimeReader = new KernelUidCpuTimeReader();
+ // Disables throttler on CPU time readers.
+ private KernelCpuUidUserSysTimeReader mCpuUidUserSysTimeReader =
+ new KernelCpuUidUserSysTimeReader(false);
private KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
- private KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader =
- new KernelUidCpuFreqTimeReader();
- private KernelUidCpuActiveTimeReader mKernelUidCpuActiveTimeReader =
- new KernelUidCpuActiveTimeReader();
- private KernelUidCpuClusterTimeReader mKernelUidCpuClusterTimeReader =
- new KernelUidCpuClusterTimeReader();
+ private KernelCpuUidFreqTimeReader mCpuUidFreqTimeReader =
+ new KernelCpuUidFreqTimeReader(false);
+ private KernelCpuUidActiveTimeReader mCpuUidActiveTimeReader =
+ new KernelCpuUidActiveTimeReader(false);
+ private KernelCpuUidClusterTimeReader mCpuUidClusterTimeReader =
+ new KernelCpuUidClusterTimeReader(false);
private StoragedUidIoStatsReader mStoragedUidIoStatsReader =
new StoragedUidIoStatsReader();
@Nullable
@@ -294,12 +296,6 @@
numSpeedSteps);
firstCpuOfCluster += powerProfile.getNumCoresInCpuCluster(i);
}
- // use default throttling in
- // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
- mKernelUidCpuFreqTimeReader.setThrottleInterval(0);
- long[] freqs = mKernelUidCpuFreqTimeReader.readFreqs(powerProfile);
- mKernelUidCpuClusterTimeReader.setThrottleInterval(0);
- mKernelUidCpuActiveTimeReader.setThrottleInterval(0);
// Enable push notifications of throttling from vendor thermal
// management subsystem via thermalservice.
@@ -914,7 +910,8 @@
private void pullKernelUidCpuTime(
int tagId, long elapsedNanos, long wallClockNanos,
List<StatsLogEventWrapper> pulledData) {
- mKernelUidCpuTimeReader.readAbsolute((uid, userTimeUs, systemTimeUs) -> {
+ mCpuUidUserSysTimeReader.readAbsolute((uid, timesUs) -> {
+ long userTimeUs = timesUs[0], systemTimeUs = timesUs[1];
StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
e.writeInt(uid);
e.writeLong(userTimeUs);
@@ -926,7 +923,7 @@
private void pullKernelUidCpuFreqTime(
int tagId, long elapsedNanos, long wallClockNanos,
List<StatsLogEventWrapper> pulledData) {
- mKernelUidCpuFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> {
+ mCpuUidFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> {
for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
if (cpuFreqTimeMs[freqIndex] != 0) {
StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
@@ -943,7 +940,7 @@
private void pullKernelUidCpuClusterTime(
int tagId, long elapsedNanos, long wallClockNanos,
List<StatsLogEventWrapper> pulledData) {
- mKernelUidCpuClusterTimeReader.readAbsolute((uid, cpuClusterTimesMs) -> {
+ mCpuUidClusterTimeReader.readAbsolute((uid, cpuClusterTimesMs) -> {
for (int i = 0; i < cpuClusterTimesMs.length; i++) {
StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
wallClockNanos);
@@ -958,7 +955,7 @@
private void pullKernelUidCpuActiveTime(
int tagId, long elapsedNanos, long wallClockNanos,
List<StatsLogEventWrapper> pulledData) {
- mKernelUidCpuActiveTimeReader.readAbsolute((uid, cpuActiveTimesMs) -> {
+ mCpuUidActiveTimeReader.readAbsolute((uid, cpuActiveTimesMs) -> {
StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
e.writeInt(uid);
e.writeLong((long) cpuActiveTimesMs);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 7c1e619..8d2bab4 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -1219,13 +1219,13 @@
}
@Override
- public void onNotificationExpansionChanged(String key, boolean userAction,
- boolean expanded) throws RemoteException {
+ public void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded,
+ int location) throws RemoteException {
enforceStatusBarService();
long identity = Binder.clearCallingIdentity();
try {
mNotificationDelegate.onNotificationExpansionChanged(
- key, userAction, expanded);
+ key, userAction, expanded, location);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
new file mode 100644
index 0000000..23c042a
--- /dev/null
+++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
@@ -0,0 +1,328 @@
+/*
+ * 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 com.android.server.testharness;
+
+import android.annotation.Nullable;
+import android.app.KeyguardManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.os.BatteryManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.PersistentDataBlockManagerInternal;
+import com.android.server.SystemService;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.Set;
+
+/**
+ * Manages the Test Harness Mode service for setting up test harness mode on the device.
+ *
+ * <p>Test Harness Mode is a feature that allows the user to clean their device, retain ADB keys,
+ * and provision the device for Instrumentation testing. This means that all parts of the device
+ * that would otherwise interfere with testing (auto-syncing accounts, package verification,
+ * automatic updates, etc.) are all disabled by default but may be re-enabled by the user.
+ */
+public class TestHarnessModeService extends SystemService {
+ private static final String TAG = TestHarnessModeService.class.getSimpleName();
+ private static final String TEST_HARNESS_MODE_PROPERTY = "persist.sys.test_harness";
+
+ private PersistentDataBlockManagerInternal mPersistentDataBlockManagerInternal;
+
+ public TestHarnessModeService(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService("testharness", mService);
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ switch (phase) {
+ case PHASE_SYSTEM_SERVICES_READY:
+ setUpTestHarnessMode();
+ break;
+ case PHASE_BOOT_COMPLETED:
+ disableAutoSync();
+ break;
+ }
+ super.onBootPhase(phase);
+ }
+
+ private void setUpTestHarnessMode() {
+ Slog.d(TAG, "Setting up test harness mode");
+ byte[] testHarnessModeData = getPersistentDataBlock().getTestHarnessModeData();
+ if (testHarnessModeData == null || testHarnessModeData.length == 0) {
+ // There's no data to apply, so leave it as-is.
+ return;
+ }
+ PersistentData persistentData = PersistentData.fromBytes(testHarnessModeData);
+
+ SystemProperties.set(TEST_HARNESS_MODE_PROPERTY, persistentData.mEnabled ? "1" : "0");
+ writeAdbKeysFile(persistentData);
+ // Clear out the data block so that we don't revert the ADB keys on every boot.
+ getPersistentDataBlock().clearTestHarnessModeData();
+
+ ContentResolver cr = getContext().getContentResolver();
+ if (Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, 0) == 0) {
+ // Enable ADB
+ Settings.Global.putInt(cr, Settings.Global.ADB_ENABLED, 1);
+ } else {
+ // ADB is already enabled, we should restart the service so it picks up the new keys
+ android.os.SystemService.restart("adbd");
+ }
+
+ Settings.Global.putInt(cr, Settings.Global.PACKAGE_VERIFIER_ENABLE, 0);
+ Settings.Global.putInt(
+ cr,
+ Settings.Global.STAY_ON_WHILE_PLUGGED_IN,
+ BatteryManager.BATTERY_PLUGGED_ANY);
+ Settings.Global.putInt(cr, Settings.Global.OTA_DISABLE_AUTOMATIC_UPDATE, 1);
+ Settings.Global.putInt(cr, Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1);
+
+ setDeviceProvisioned();
+ }
+
+ private void disableAutoSync() {
+ UserInfo primaryUser = UserManager.get(getContext()).getPrimaryUser();
+ ContentResolver
+ .setMasterSyncAutomaticallyAsUser(false, primaryUser.getUserHandle().getIdentifier());
+ }
+
+ private void writeAdbKeysFile(PersistentData persistentData) {
+ Path adbKeys = Paths.get("/data/misc/adb/adb_keys");
+ try {
+ OutputStream fileOutputStream = Files.newOutputStream(adbKeys);
+ fileOutputStream.write(persistentData.mAdbKeys);
+ fileOutputStream.close();
+
+ Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(adbKeys);
+ permissions.add(PosixFilePermission.GROUP_READ);
+ Files.setPosixFilePermissions(adbKeys, permissions);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to set up adb keys", e);
+ // Note: if a device enters this block, it will remain UNAUTHORIZED in ADB, but all
+ // other settings will be set up.
+ }
+ }
+
+ // Setting the device as provisioned skips the setup wizard.
+ private void setDeviceProvisioned() {
+ ContentResolver cr = getContext().getContentResolver();
+ Settings.Global.putInt(cr, Settings.Global.DEVICE_PROVISIONED, 1);
+ Settings.Secure.putIntForUser(
+ cr,
+ Settings.Secure.USER_SETUP_COMPLETE,
+ 1,
+ UserHandle.USER_CURRENT);
+ }
+
+ @Nullable
+ private PersistentDataBlockManagerInternal getPersistentDataBlock() {
+ if (mPersistentDataBlockManagerInternal == null) {
+ Slog.d(TAG, "Getting PersistentDataBlockManagerInternal from LocalServices");
+ mPersistentDataBlockManagerInternal =
+ LocalServices.getService(PersistentDataBlockManagerInternal.class);
+ }
+ return mPersistentDataBlockManagerInternal;
+ }
+
+ private final IBinder mService = new Binder() {
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+ (new TestHarnessModeShellCommand())
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
+ };
+
+ private class TestHarnessModeShellCommand extends ShellCommand {
+ @Override
+ public int onCommand(String cmd) {
+ switch (cmd) {
+ case "enable":
+ case "restore":
+ checkPermissions();
+ final long originalId = Binder.clearCallingIdentity();
+ try {
+ if (isDeviceSecure()) {
+ getErrPrintWriter().println(
+ "Test Harness Mode cannot be enabled if there is a lock "
+ + "screen");
+ return 2;
+ }
+ return handleEnable();
+ } finally {
+ Binder.restoreCallingIdentity(originalId);
+ }
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ }
+
+ private void checkPermissions() {
+ getContext().enforceCallingPermission(
+ android.Manifest.permission.ENABLE_TEST_HARNESS_MODE,
+ "You must hold android.permission.ENABLE_TEST_HARNESS_MODE "
+ + "to enable Test Harness Mode");
+ }
+
+ private boolean isDeviceSecure() {
+ UserInfo primaryUser = UserManager.get(getContext()).getPrimaryUser();
+ KeyguardManager keyguardManager = getContext().getSystemService(KeyguardManager.class);
+ return keyguardManager.isDeviceSecure(primaryUser.id);
+ }
+
+ private int handleEnable() {
+ Path adbKeys = Paths.get("/data/misc/adb/adb_keys");
+ if (!Files.exists(adbKeys)) {
+ // This should only be accessible on eng builds that haven't yet set up ADB keys
+ getErrPrintWriter()
+ .println("No ADB keys stored; not enabling test harness mode");
+ return 1;
+ }
+
+ try (InputStream inputStream = Files.newInputStream(adbKeys)) {
+ long size = Files.size(adbKeys);
+ byte[] adbKeysBytes = new byte[(int) size];
+ int numBytes = inputStream.read(adbKeysBytes);
+ if (numBytes != size) {
+ getErrPrintWriter().println("Failed to read all bytes of adb_keys");
+ return 1;
+ }
+ PersistentData persistentData = new PersistentData(true, adbKeysBytes);
+ getPersistentDataBlock().setTestHarnessModeData(persistentData.toBytes());
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to store ADB keys.", e);
+ getErrPrintWriter().println("Failed to enable Test Harness Mode");
+ return 1;
+ }
+
+ Intent i = new Intent(Intent.ACTION_FACTORY_RESET);
+ i.setPackage("android");
+ i.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ i.putExtra(Intent.EXTRA_REASON, TAG);
+ i.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, true);
+ getContext().sendBroadcastAsUser(i, UserHandle.SYSTEM);
+ return 0;
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("About:");
+ pw.println(" Test Harness Mode is a mode that the device can be placed in to prepare");
+ pw.println(" the device for running UI tests. The device is placed into this mode by");
+ pw.println(" first wiping all data from the device, preserving ADB keys.");
+ pw.println();
+ pw.println(" By default, the following settings are configured:");
+ pw.println(" * Package Verifier is disabled");
+ pw.println(" * Stay Awake While Charging is enabled");
+ pw.println(" * OTA Updates are disabled");
+ pw.println(" * Auto-Sync for accounts is disabled");
+ pw.println();
+ pw.println(" Other apps may configure themselves differently in Test Harness Mode by");
+ pw.println(" checking ActivityManager.isRunningInUserTestHarness()");
+ pw.println();
+ pw.println("Test Harness Mode commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println();
+ pw.println(" enable|restore");
+ pw.println(" Erase all data from this device and enable Test Harness Mode,");
+ pw.println(" preserving the stored ADB keys currently on the device and toggling");
+ pw.println(" settings in a way that are conducive to Instrumentation testing.");
+ }
+ }
+
+ /**
+ * The object that will serialize/deserialize the Test Harness Mode data to and from the
+ * persistent data block.
+ */
+ public static class PersistentData {
+ static final byte VERSION_1 = 1;
+
+ final int mVersion;
+ final boolean mEnabled;
+ final byte[] mAdbKeys;
+
+ PersistentData(boolean enabled, byte[] adbKeys) {
+ this(VERSION_1, enabled, adbKeys);
+ }
+
+ PersistentData(int version, boolean enabled, byte[] adbKeys) {
+ this.mVersion = version;
+ this.mEnabled = enabled;
+ this.mAdbKeys = adbKeys;
+ }
+
+ static PersistentData fromBytes(byte[] bytes) {
+ try {
+ DataInputStream is = new DataInputStream(new ByteArrayInputStream(bytes));
+ int version = is.readInt();
+ boolean enabled = is.readBoolean();
+ int adbKeysLength = is.readInt();
+ byte[] adbKeys = new byte[adbKeysLength];
+ is.readFully(adbKeys);
+ return new PersistentData(version, enabled, adbKeys);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ byte[] toBytes() {
+ try {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(os);
+ dos.writeInt(VERSION_1);
+ dos.writeBoolean(mEnabled);
+ dos.writeInt(mAdbKeys.length);
+ dos.write(mAdbKeys);
+ dos.close();
+ return os.toByteArray();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index ced5935..423ec4c 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -22,10 +22,8 @@
import android.app.AlarmManager;
import android.app.AlarmManager.OnAlarmListener;
import android.app.admin.DevicePolicyManager;
-import android.hardware.biometrics.BiometricSourceType;
import android.app.trust.ITrustListener;
import android.app.trust.ITrustManager;
-import android.app.UserSwitchObserver;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -41,6 +39,7 @@
import android.content.res.XmlResourceParser;
import android.database.ContentObserver;
import android.graphics.drawable.Drawable;
+import android.hardware.biometrics.BiometricSourceType;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -72,13 +71,15 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.server.SystemService;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
+
/**
* Manages trust agents and trust listeners.
@@ -119,7 +120,7 @@
private static final int TRUST_USUALLY_MANAGED_FLUSH_DELAY = 2 * 60 * 1000;
private static final String TRUST_TIMEOUT_ALARM_TAG = "TrustManagerService.trustTimeoutForUser";
- private static final long TRUST_TIMEOUT_IN_MILLIS = 20 * 1000; //4 * 60 * 60 * 1000;
+ private static final long TRUST_TIMEOUT_IN_MILLIS = 4 * 60 * 60 * 1000;
private final ArraySet<AgentInfo> mActiveAgents = new ArraySet<>();
private final ArrayList<ITrustListener> mTrustListeners = new ArrayList<>();
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index 9a7e75e..744efab 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -1129,7 +1129,8 @@
* In this case, we grant a uri permission, even if the ContentProvider does not normally
* grant uri permissions.
*/
- boolean specialCrossUserGrant = UserHandle.getUserId(targetUid) != grantUri.sourceUserId
+ boolean specialCrossUserGrant = targetUid >= 0
+ && UserHandle.getUserId(targetUid) != grantUri.sourceUserId
&& checkHoldingPermissionsInternal(pm, pi, grantUri, callingUid,
modeFlags, false /*without considering the uid permissions*/);
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index b3eafa4..45689ce 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -65,7 +65,6 @@
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
import com.android.server.SystemService;
-import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.utils.ManagedApplicationService;
import com.android.server.utils.ManagedApplicationService.BinderChecker;
import com.android.server.utils.ManagedApplicationService.LogEvent;
@@ -623,14 +622,6 @@
}
@Override
- public void setVrInputMethod(ComponentName componentName) {
- enforceCallerPermissionAnyOf(Manifest.permission.RESTRICTED_VR_ACCESS);
- InputMethodManagerInternal imm =
- LocalServices.getService(InputMethodManagerInternal.class);
- imm.startVrInputMethodNoCheck(componentName);
- }
-
- @Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index caebf15..545b69b 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -49,6 +49,7 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.TypedValue;
+import android.view.Display;
import android.view.MagnificationSpec;
import android.view.Surface;
import android.view.Surface.OutOfResourcesException;
@@ -83,23 +84,36 @@
mService = service;
}
- private DisplayMagnifier mDisplayMagnifier;
+ private SparseArray<DisplayMagnifier> mDisplayMagnifiers = new SparseArray<>();
private WindowsForAccessibilityObserver mWindowsForAccessibilityObserver;
- public void setMagnificationCallbacksLocked(MagnificationCallbacks callbacks) {
+ public boolean setMagnificationCallbacksLocked(int displayId,
+ MagnificationCallbacks callbacks) {
+ boolean result = false;
if (callbacks != null) {
- if (mDisplayMagnifier != null) {
+ if (mDisplayMagnifiers.get(displayId) != null) {
throw new IllegalStateException("Magnification callbacks already set!");
}
- mDisplayMagnifier = new DisplayMagnifier(mService, callbacks);
+ final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
+ if (dc != null) {
+ final Display display = dc.getDisplay();
+ if (display != null && display.getType() != Display.TYPE_OVERLAY) {
+ mDisplayMagnifiers.put(displayId, new DisplayMagnifier(
+ mService, dc, display, callbacks));
+ result = true;
+ }
+ }
} else {
- if (mDisplayMagnifier == null) {
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier == null) {
throw new IllegalStateException("Magnification callbacks already cleared!");
}
- mDisplayMagnifier.destroyLocked();
- mDisplayMagnifier = null;
+ displayMagnifier.destroyLocked();
+ mDisplayMagnifiers.remove(displayId);
+ result = true;
}
+ return result;
}
public void setWindowsForAccessibilityCallback(WindowsForAccessibilityCallback callback) {
@@ -129,58 +143,72 @@
}
}
- public void setMagnificationSpecLocked(MagnificationSpec spec) {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.setMagnificationSpecLocked(spec);
+ public void setMagnificationSpecLocked(int displayId, MagnificationSpec spec) {
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.setMagnificationSpecLocked(spec);
}
- if (mWindowsForAccessibilityObserver != null) {
+ // TODO: support multi-display for windows observer
+ if (mWindowsForAccessibilityObserver != null && displayId == Display.DEFAULT_DISPLAY) {
mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
}
}
- public void getMagnificationRegionLocked(Region outMagnificationRegion) {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.getMagnificationRegionLocked(outMagnificationRegion);
+ public void getMagnificationRegionLocked(int displayId, Region outMagnificationRegion) {
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.getMagnificationRegionLocked(outMagnificationRegion);
}
}
- public void onRectangleOnScreenRequestedLocked(Rect rectangle) {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.onRectangleOnScreenRequestedLocked(rectangle);
+ public void onRectangleOnScreenRequestedLocked(int displayId, Rect rectangle) {
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.onRectangleOnScreenRequestedLocked(rectangle);
}
// Not relevant for the window observer.
}
- public void onWindowLayersChangedLocked() {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.onWindowLayersChangedLocked();
+ public void onWindowLayersChangedLocked(int displayId) {
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.onWindowLayersChangedLocked();
}
- if (mWindowsForAccessibilityObserver != null) {
+ // TODO: support multi-display for windows observer
+ if (mWindowsForAccessibilityObserver != null && displayId == Display.DEFAULT_DISPLAY) {
mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
}
}
public void onRotationChangedLocked(DisplayContent displayContent) {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.onRotationChangedLocked(displayContent);
+ final int displayId = displayContent.getDisplayId();
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.onRotationChangedLocked(displayContent);
}
- if (mWindowsForAccessibilityObserver != null) {
+ // TODO: support multi-display for windows observer
+ if (mWindowsForAccessibilityObserver != null && displayId == Display.DEFAULT_DISPLAY) {
mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
}
}
public void onAppWindowTransitionLocked(WindowState windowState, int transition) {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.onAppWindowTransitionLocked(windowState, transition);
+ final int displayId = windowState.getDisplayId();
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.onAppWindowTransitionLocked(windowState, transition);
}
// Not relevant for the window observer.
}
public void onWindowTransitionLocked(WindowState windowState, int transition) {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.onWindowTransitionLocked(windowState, transition);
+ final int displayId = windowState.getDisplayId();
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.onWindowTransitionLocked(windowState, transition);
}
- if (mWindowsForAccessibilityObserver != null) {
+ // TODO: support multi-display for windows observer
+ if (mWindowsForAccessibilityObserver != null && displayId == Display.DEFAULT_DISPLAY) {
mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
}
}
@@ -197,7 +225,6 @@
}
}
-
public void onSomeWindowResizedOrMovedLocked() {
// Not relevant for the display magnifier.
@@ -207,29 +234,34 @@
}
/** NOTE: This has to be called within a surface transaction. */
- public void drawMagnifiedRegionBorderIfNeededLocked() {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.drawMagnifiedRegionBorderIfNeededLocked();
+ public void drawMagnifiedRegionBorderIfNeededLocked(int displayId) {
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.drawMagnifiedRegionBorderIfNeededLocked();
}
// Not relevant for the window observer.
}
public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
- if (mDisplayMagnifier != null) {
- return mDisplayMagnifier.getMagnificationSpecForWindowLocked(windowState);
+ final int displayId = windowState.getDisplayId();
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ return displayMagnifier.getMagnificationSpecForWindowLocked(windowState);
}
return null;
}
public boolean hasCallbacksLocked() {
- return (mDisplayMagnifier != null
+ // TODO: support multi-display for windows observer
+ return (mDisplayMagnifiers.size() > 0
|| mWindowsForAccessibilityObserver != null);
}
- public void setForceShowMagnifiableBoundsLocked(boolean show) {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.setForceShowMagnifiableBoundsLocked(show);
- mDisplayMagnifier.showMagnificationBoundsIfNeeded();
+ public void setForceShowMagnifiableBoundsLocked(int displayId, boolean show) {
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.setForceShowMagnifiableBoundsLocked(show);
+ displayMagnifier.showMagnificationBoundsIfNeeded();
}
}
@@ -263,6 +295,8 @@
private final WindowManagerService mService;
private final MagnifiedViewport mMagnifedViewport;
private final Handler mHandler;
+ private final DisplayContent mDisplayContent;
+ private final Display mDisplay;
private final MagnificationCallbacks mCallbacks;
@@ -271,10 +305,14 @@
private boolean mForceShowMagnifiableBounds = false;
public DisplayMagnifier(WindowManagerService windowManagerService,
+ DisplayContent displayContent,
+ Display display,
MagnificationCallbacks callbacks) {
mContext = windowManagerService.mContext;
mService = windowManagerService;
mCallbacks = callbacks;
+ mDisplayContent = displayContent;
+ mDisplay = display;
mHandler = new MyHandler(mService.mH.getLooper());
mMagnifedViewport = new MagnifiedViewport();
mLongAnimationDuration = mContext.getResources().getInteger(
@@ -285,7 +323,7 @@
mMagnifedViewport.updateMagnificationSpecLocked(spec);
mMagnifedViewport.recomputeBoundsLocked();
- mService.applyMagnificationSpec(spec);
+ mService.applyMagnificationSpecLocked(mDisplay.getDisplayId(), spec);
mService.scheduleAnimationLocked();
}
@@ -482,7 +520,7 @@
if (mContext.getResources().getConfiguration().isScreenRound()) {
mCircularPath = new Path();
- mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
+ mDisplay.getRealSize(mTempPoint);
final int centerXY = mTempPoint.x / 2;
mCircularPath.addCircle(centerXY, centerXY, centerXY, Path.Direction.CW);
} else {
@@ -512,7 +550,7 @@
}
public void recomputeBoundsLocked() {
- mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
+ mDisplay.getRealSize(mTempPoint);
final int screenWidth = mTempPoint.x;
final int screenHeight = mTempPoint.y;
@@ -671,9 +709,8 @@
}
private void populateWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
- final DisplayContent dc = mService.getDefaultDisplayContentLocked();
mTempLayer = 0;
- dc.forAllWindows((w) -> {
+ mDisplayContent.forAllWindows((w) -> {
if (w.isOnScreen() && w.isVisibleLw()
&& (w.mAttrs.alpha != 0)
&& !w.mWinAnimator.mEnterAnimationPending) {
@@ -703,8 +740,9 @@
public ViewportWindow(Context context) {
SurfaceControl surfaceControl = null;
try {
- mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
- surfaceControl = mService.getDefaultDisplayContentLocked().makeOverlay()
+ mDisplay.getRealSize(mTempPoint);
+ surfaceControl = mDisplayContent
+ .makeOverlay()
.setName(SURFACE_TITLE)
.setBufferSize(mTempPoint.x, mTempPoint.y) // not a typo
.setFormat(PixelFormat.TRANSLUCENT)
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index e817dd4..65d66f4 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -558,26 +558,22 @@
}
/**
- * Pause all activities in either all of the stacks or just the back stacks. This is done before
- * resuming a new activity and to make sure that previously active activities are
- * paused in stacks that are no longer visible or in pinned windowing mode. This does not
- * pause activities in visible stacks, so if an activity is launched within the same stack/task,
- * then we should explicitly pause that stack's top activity.
+ * Pause all activities in either all of the stacks or just the back stacks.
* @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving().
* @param resuming The resuming activity.
* @param dontWait The resuming activity isn't going to wait for all activities to be paused
* before resuming.
- * @return {@code true} if any activity was paused as a result of this call.
+ * @return true if any activity was paused as a result of this call.
*/
boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming, boolean dontWait) {
boolean someActivityPaused = false;
for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = mStacks.get(stackNdx);
- final ActivityRecord resumedActivity = stack.getResumedActivity();
- if (resumedActivity != null
- && (!stack.shouldBeVisible(resuming) || !stack.isFocusable())) {
+ // TODO(b/111541062): Check if resumed activity on this display instead
+ if (!mRootActivityContainer.isTopDisplayFocusedStack(stack)
+ && stack.getResumedActivity() != null) {
if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack +
- " mResumedActivity=" + resumedActivity);
+ " mResumedActivity=" + stack.getResumedActivity());
someActivityPaused |= stack.startPausingLocked(userLeaving, false, resuming,
dontWait);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 6213fa0..b8634d8 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1946,84 +1946,30 @@
try {
mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
WindowVisibilityItem.obtain(true /* showWindow */));
- makeActiveIfNeeded(null /* activeActivity*/);
+ if (shouldPauseWhenBecomingVisible()) {
+ // An activity must be in the {@link PAUSING} state for the system to validate
+ // the move to {@link PAUSED}.
+ setState(PAUSING, "makeVisibleIfNeeded");
+ mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+ PauseActivityItem.obtain(finishing, false /* userLeaving */,
+ configChangeFlags, false /* dontReport */));
+ }
} catch (Exception e) {
Slog.w(TAG, "Exception thrown sending visibility update: " + intent.getComponent(), e);
}
}
- /**
- * Make activity resumed or paused if needed.
- * @param activeActivity an activity that is resumed or just completed pause action.
- * We won't change the state of this activity.
- */
- boolean makeActiveIfNeeded(ActivityRecord activeActivity) {
- if (shouldResumeActivity(activeActivity)) {
- if (DEBUG_VISIBILITY) {
- Slog.v("TAG_VISIBILITY", "Resume visible activity, " + this);
- }
- return getActivityStack().resumeTopActivityUncheckedLocked(activeActivity /* prev */,
- null /* options */);
- } else if (shouldPauseActivity(activeActivity)) {
- if (DEBUG_VISIBILITY) {
- Slog.v("TAG_VISIBILITY", "Pause visible activity, " + this);
- }
- // An activity must be in the {@link PAUSING} state for the system to validate
- // the move to {@link PAUSED}.
- setState(PAUSING, "makeVisibleIfNeeded");
- try {
- mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
- PauseActivityItem.obtain(finishing, false /* userLeaving */,
- configChangeFlags, false /* dontReport */));
- } catch (Exception e) {
- Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e);
- }
- }
- return false;
- }
-
- /**
- * Check if activity should be moved to PAUSED state. The activity:
- * - should be eligible to be made active (see {@link #shouldMakeActive(ActivityRecord)})
- * - should be non-focusable
- * - should not be currently pausing or paused
- * @param activeActivity the activity that is active or just completed pause action. We won't
- * resume if this activity is active.
- */
- private boolean shouldPauseActivity(ActivityRecord activeActivity) {
- return shouldMakeActive(activeActivity) && !isFocusable() && !isState(PAUSING, PAUSED);
- }
-
- /**
- * Check if activity should be moved to RESUMED state. The activity:
- * - should be eligible to be made active (see {@link #shouldMakeActive(ActivityRecord)})
- * - should be focusable
- * @param activeActivity the activity that is active or just completed pause action. We won't
- * resume if this activity is active.
- */
- private boolean shouldResumeActivity(ActivityRecord activeActivity) {
- return shouldMakeActive(activeActivity) && isFocusable() && !isState(RESUMED);
- }
-
- /**
- * Check if activity is eligible to be made active (resumed of paused). The activity:
- * - should be paused, stopped or stopping
- * - should not be the currently active one
- * - should be either the topmost in task, or right below the top activity that is finishing
- * If all of these conditions are not met at the same time, the activity cannot be made active.
- */
- private boolean shouldMakeActive(ActivityRecord activeActivity) {
- // If the activity is stopped, stopping, cycle to an active state. We avoid doing
+ /** Check if activity should be moved to PAUSED state when it becomes visible. */
+ private boolean shouldPauseWhenBecomingVisible() {
+ // If the activity is stopped or stopping, cycle to the paused state. We avoid doing
// this when there is an activity waiting to become translucent as the extra binder
// calls will lead to noticeable jank. A later call to
- // ActivityStack#ensureActivitiesVisibleLocked will bring the activity to a proper
- // active state.
- if (!isState(RESUMED, PAUSED, STOPPED, STOPPING)
- || getActivityStack().mTranslucentActivityWaiting != null) {
- return false;
- }
-
- if (this == activeActivity) {
+ // ActivityStack#ensureActivitiesVisibleLocked will bring the activity to the proper
+ // paused state. We also avoid doing this for the activity the stack supervisor
+ // considers the resumed activity, as normal means will bring the activity from STOPPED
+ // to RESUMED. Adding PAUSING in this scenario will lead to double lifecycles.
+ if (!isState(STOPPED, STOPPING) || getActivityStack().mTranslucentActivityWaiting != null
+ || isResumedActivityOnDisplay()) {
return false;
}
@@ -2033,14 +1979,14 @@
throw new IllegalStateException("Activity not found in its task");
}
if (positionInTask == task.mActivities.size() - 1) {
- // It's the topmost activity in the task - should become resumed now
+ // It's the topmost activity in the task - should become paused now
return true;
}
// Check if activity above is finishing now and this one becomes the topmost in task.
final ActivityRecord activityAbove = task.mActivities.get(positionInTask + 1);
if (activityAbove.finishing && results == null) {
- // We will only allow making active if activity above wasn't launched for result.
- // Otherwise it will cause this activity to resume before getting result.
+ // We will only allow pausing if activity above wasn't launched for result. Otherwise it
+ // will cause this activity to resume before getting result.
return true;
}
return false;
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 3aef8e1f..891c3da 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -357,11 +357,6 @@
*/
boolean mForceHidden = false;
- /**
- * Used to keep resumeTopActivityUncheckedLocked() from being entered recursively
- */
- boolean mInResumeTopActivity = false;
-
private boolean mUpdateBoundsDeferred;
private boolean mUpdateBoundsDeferredCalled;
private boolean mUpdateDisplayedBoundsDeferredCalled;
@@ -1737,7 +1732,6 @@
"Activity paused: token=" + token + ", timeout=" + timeout);
final ActivityRecord r = isInStackLocked(token);
-
if (r != null) {
mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
if (mPausingActivity == r) {
@@ -2094,7 +2088,8 @@
boolean aboveTop = top != null;
final boolean stackShouldBeVisible = shouldBeVisible(starting);
boolean behindFullscreenActivity = !stackShouldBeVisible;
- boolean resumeNextActivity = isFocusable() && isInStackLocked(starting) == null;
+ boolean resumeNextActivity = mRootActivityContainer.isTopDisplayFocusedStack(this)
+ && (isInStackLocked(starting) == null);
final boolean isTopNotPinnedStack =
isAttached() && getDisplay().isTopNotPinnedStack(this);
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
@@ -2155,10 +2150,6 @@
if (r.handleAlreadyVisible()) {
resumeNextActivity = false;
}
-
- if (notifyClients) {
- r.makeActiveIfNeeded(starting);
- }
} else {
r.makeVisibleIfNeeded(starting, notifyClients);
}
@@ -2336,7 +2327,7 @@
r.setVisible(true);
}
if (r != starting) {
- mStackSupervisor.startSpecificActivityLocked(r, andResume, true /* checkConfig */);
+ mStackSupervisor.startSpecificActivityLocked(r, andResume, false);
return true;
}
}
@@ -2514,7 +2505,7 @@
*/
@GuardedBy("mService")
boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {
- if (mInResumeTopActivity) {
+ if (mStackSupervisor.inResumeTopActivity) {
// Don't even start recursing.
return false;
}
@@ -2522,7 +2513,7 @@
boolean result = false;
try {
// Protect against recursion.
- mInResumeTopActivity = true;
+ mStackSupervisor.inResumeTopActivity = true;
result = resumeTopActivityInnerLocked(prev, options);
// When resuming the top activity, it may be necessary to pause the top activity (for
@@ -2537,7 +2528,7 @@
checkReadyForSleep();
}
} finally {
- mInResumeTopActivity = false;
+ mStackSupervisor.inResumeTopActivity = false;
}
return result;
@@ -2570,7 +2561,7 @@
// Find the next top-most activity to resume in this stack that is not finishing and is
// focusable. If it is not focusable, we will fall into the case below to resume the
// top activity in the next focusable task.
- ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
+ final ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
final boolean hasRunningActivity = next != null;
@@ -2658,12 +2649,6 @@
if (!mRootActivityContainer.allPausedActivitiesComplete()) {
if (DEBUG_SWITCH || DEBUG_PAUSE || DEBUG_STATES) Slog.v(TAG_PAUSE,
"resumeTopActivityLocked: Skip resume: some activity pausing.");
-
- // Adding previous activity to the waiting visible list, or it would be stopped
- // before top activity being visible.
- if (prev != null && !next.nowVisible) {
- mStackSupervisor.mActivitiesWaitingForVisibleActivity.add(prev);
- }
return false;
}
@@ -2873,9 +2858,7 @@
// the screen based on the new activity order.
boolean notUpdated = true;
- // Activity should also be visible if set mLaunchTaskBehind to true (see
- // ActivityRecord#shouldBeVisibleIgnoringKeyguard()).
- if (shouldBeVisible(next)) {
+ if (isFocusedStackOnDisplay()) {
// We have special rotation behavior when here is some active activity that
// requests specific orientation or Keyguard is locked. Make sure all activity
// visibilities are set correctly as well as the transition is updated if needed
@@ -4104,12 +4087,6 @@
mStackSupervisor.mFinishingActivities.add(r);
r.resumeKeyDispatchingLocked();
mRootActivityContainer.resumeFocusedStacksTopActivities();
- // If activity was not paused at this point - explicitly pause it to start finishing
- // process. Finishing will be completed once it reports pause back.
- if (r.isState(RESUMED) && mPausingActivity != null) {
- startPausingLocked(false /* userLeaving */, false /* uiSleeping */, next /* resuming */,
- false /* dontWait */);
- }
return r;
}
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index a83ef34..3a288ca 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -327,6 +327,9 @@
*/
PowerManager.WakeLock mGoingToSleep;
+ /** Used to keep resumeTopActivityUncheckedLocked() from being entered recursively */
+ boolean inResumeTopActivity;
+
/**
* Temporary rect used during docked stack resize calculation so we don't need to create a new
* object each time.
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 43c1206..2807094 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -179,10 +179,7 @@
.setActivityOptions(options.toBundle())
.execute();
mLastHomeActivityStartRecord = tmpOutRecord[0];
- final ActivityDisplay display =
- mService.mRootActivityContainer.getActivityDisplay(displayId);
- final ActivityStack homeStack = display != null ? display.getHomeStack() : null;
- if (homeStack != null && homeStack.mInResumeTopActivity) {
+ if (mSupervisor.inResumeTopActivity) {
// If we are in resume section already, home activity will be initialized, but not
// resumed (to avoid recursive resume) and will stay that way until something pokes it
// again. We need to schedule another resume.
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 4e2dffc..d36e545 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1629,7 +1629,7 @@
// Also, we don't want to resume activities in a task that currently has an overlay
// as the starting activity just needs to be in the visible paused state until the
// over is removed.
- mTargetStack.ensureActivitiesVisibleLocked(mStartActivity, 0, !PRESERVE_WINDOWS);
+ mTargetStack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
// Go ahead and tell window manager to execute app transition for this activity
// since the app transition will not be triggered through the resume channel.
mTargetStack.getDisplay().mDisplayContent.executeAppTransition();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 61c4863..2affa97 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5313,9 +5313,18 @@
}
void updateActivityUsageStats(ActivityRecord activity, int event) {
+ ComponentName taskRoot = null;
+ final TaskRecord task = activity.getTaskRecord();
+ if (task != null) {
+ final ActivityRecord rootActivity = task.getRootActivity();
+ if (rootActivity != null) {
+ taskRoot = rootActivity.mActivityComponent;
+ }
+ }
+
final Message m = PooledLambda.obtainMessage(
ActivityManagerInternal::updateActivityUsageStats, mAmInternal,
- activity.mActivityComponent, activity.mUserId, event, activity.appToken);
+ activity.mActivityComponent, activity.mUserId, event, activity.appToken, taskRoot);
mH.sendMessage(m);
}
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 780eda49..750c5ca 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -25,7 +25,6 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
@@ -591,9 +590,7 @@
delayed = runningAppAnimation = true;
}
final WindowState window = findMainWindow();
- //TODO (multidisplay): Magnification is supported only for the default display.
- if (window != null && accessibilityController != null
- && getDisplayContent().getDisplayId() == DEFAULT_DISPLAY) {
+ if (window != null && accessibilityController != null) {
accessibilityController.onAppWindowTransitionLocked(window, transit);
}
changed = true;
@@ -1975,7 +1972,7 @@
} else if (newTask || !processRunning || (taskSwitch && !activityCreated)) {
return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
} else if (taskSwitch && allowTaskSnapshot) {
- if (mWmService.mLowRamTaskSnapshots) {
+ if (mWmService.mLowRamTaskSnapshotsAndRecents) {
// For low RAM devices, we use the splash screen starting window instead of the
// task snapshot starting window.
return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
@@ -2407,7 +2404,7 @@
@Override
protected void reparentSurfaceControl(Transaction t, SurfaceControl newParent) {
if (!mSurfaceAnimator.hasLeash()) {
- t.reparent(mSurfaceControl, newParent.getHandle());
+ t.reparent(mSurfaceControl, newParent);
}
}
@@ -2453,7 +2450,7 @@
t.setWindowCrop(mAnimationBoundsLayer, mTmpRect);
// Reparent leash to animation bounds layer.
- t.reparent(leash, mAnimationBoundsLayer.getHandle());
+ t.reparent(leash, mAnimationBoundsLayer);
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 6527ca0..8fefd35 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -30,11 +30,14 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.FLAG_PRIVATE;
import static android.view.InsetsState.TYPE_IME;
+import static android.view.InsetsState.TYPE_NAVIGATION_BAR;
+import static android.view.InsetsState.TYPE_TOP_BAR;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static android.view.View.GONE;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
import static android.view.WindowManager.DOCKED_BOTTOM;
import static android.view.WindowManager.DOCKED_INVALID;
import static android.view.WindowManager.DOCKED_TOP;
@@ -164,6 +167,7 @@
import android.view.SurfaceControl.Transaction;
import android.view.SurfaceSession;
import android.view.View;
+import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.view.WindowManagerPolicyConstants.PointerEventListener;
@@ -1051,6 +1055,11 @@
*/
void setInsetProvider(@InternalInsetType int type, WindowState win,
@Nullable TriConsumer<DisplayFrames, WindowState, Rect> frameProvider) {
+ if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL) {
+ if (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR) {
+ return;
+ }
+ }
mInsetsStateController.getSourceProvider(type).setWindow(win, frameProvider);
}
@@ -1457,11 +1466,9 @@
}
}
- // TODO (multi-display): Magnification is supported only for the default display.
// Announce rotation only if we will not animate as we already have the
// windows in final state. Otherwise, we make this call at the rotation end.
- if (screenRotationAnimation == null && mWmService.mAccessibilityController != null
- && isDefaultDisplay) {
+ if (screenRotationAnimation == null && mWmService.mAccessibilityController != null) {
mWmService.mAccessibilityController.onRotationChangedLocked(this);
}
}
@@ -4581,7 +4588,7 @@
* Reparents the given surface to mOverlayLayer.
*/
void reparentToOverlay(Transaction transaction, SurfaceControl surface) {
- transaction.reparent(surface, mOverlayLayer.getHandle());
+ transaction.reparent(surface, mOverlayLayer);
}
void applyMagnificationSpec(MagnificationSpec spec) {
@@ -4824,11 +4831,11 @@
* Re-parent the DisplayContent's top surfaces, {@link #mWindowingLayer} and
* {@link #mOverlayLayer} to the specified surfaceControl.
*
- * @param surfaceControlHandle The handle for the new SurfaceControl, where the DisplayContent's
+ * @param surfaceControlHandle The new SurfaceControl, where the DisplayContent's
* surfaces will be re-parented to.
*/
- void reparentDisplayContent(IBinder surfaceControlHandle) {
- mPendingTransaction.reparent(mWindowingLayer, surfaceControlHandle)
- .reparent(mOverlayLayer, surfaceControlHandle);
+ void reparentDisplayContent(SurfaceControl sc) {
+ mPendingTransaction.reparent(mWindowingLayer, sc)
+ .reparent(mOverlayLayer, sc);
}
}
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 786a306..3f77e1c 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -120,6 +120,8 @@
// A surface used to catch input events for the drag-and-drop operation.
SurfaceControl mInputSurface;
+ private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+
private final Rect mTmpClipRect = new Rect();
/**
@@ -240,7 +242,7 @@
// Clear the internal variables.
if (mSurfaceControl != null) {
- mSurfaceControl.destroy();
+ mTransaction.reparent(mSurfaceControl, null).apply();
mSurfaceControl = null;
}
if (mAnimator != null && !mAnimationCompleted) {
@@ -500,18 +502,13 @@
mCurrentY = y;
// Move the surface to the given touch
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
- TAG_WM, ">>> OPEN TRANSACTION notifyMoveLocked");
- mService.openSurfaceTransaction();
- try {
- mSurfaceControl.setPosition(x - mThumbOffsetX, y - mThumbOffsetY);
- if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, " DRAG "
- + mSurfaceControl + ": pos=(" +
- (int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")");
- } finally {
- mService.closeSurfaceTransaction("notifyMoveLw");
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
- TAG_WM, "<<< CLOSE TRANSACTION notifyMoveLocked");
+ if (SHOW_LIGHT_TRANSACTIONS) {
+ Slog.i(TAG_WM, ">>> OPEN TRANSACTION notifyMoveLocked");
+ }
+ mTransaction.setPosition(mSurfaceControl, x - mThumbOffsetX, y - mThumbOffsetY).apply();
+ if (SHOW_TRANSACTIONS) {
+ Slog.i(TAG_WM, " DRAG " + mSurfaceControl + ": pos=(" + (int) (x - mThumbOffsetX) + ","
+ + (int) (y - mThumbOffsetY) + ")");
}
notifyLocationLocked(x, y);
}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index d6f1616..e49e4c0 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -16,6 +16,8 @@
package com.android.server.wm;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Rect;
@@ -159,7 +161,7 @@
}
boolean isClientVisible() {
- return !ViewRootImpl.USE_NEW_INSETS || mClientVisible;
+ return ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE || mClientVisible;
}
private class ControlAdapter implements AnimationAdapter {
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index bc01f7c..32dbe96 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -19,6 +19,9 @@
import static android.view.InsetsState.TYPE_IME;
import static android.view.InsetsState.TYPE_NAVIGATION_BAR;
import static android.view.InsetsState.TYPE_TOP_BAR;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
+import static android.view.ViewRootImpl.sNewInsetsMode;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -160,7 +163,7 @@
}
private void onControlChanged(int type, @Nullable WindowState win) {
- if (!ViewRootImpl.USE_NEW_INSETS) {
+ if (sNewInsetsMode == NEW_INSETS_MODE_NONE) {
return;
}
final WindowState previous = mTypeWinControlMap.get(type);
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 33ff194..434084c 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -64,15 +64,11 @@
public void layout(Rect outer, Rect inner, Point surfaceOrigin) {
mOuter.set(outer);
mInner.set(inner);
- mOuter.offset(-surfaceOrigin.x, -surfaceOrigin.y);
- mInner.offset(-surfaceOrigin.x, -surfaceOrigin.y);
- outer = mOuter;
- inner = mInner;
- mTop.layout(outer.left, outer.top, inner.right, inner.top);
- mLeft.layout(outer.left, inner.top, inner.left, outer.bottom);
- mBottom.layout(inner.left, inner.bottom, outer.right, outer.bottom);
- mRight.layout(inner.right, outer.top, outer.right, inner.bottom);
+ mTop.layout(outer.left, outer.top, inner.right, inner.top, surfaceOrigin);
+ mLeft.layout(outer.left, inner.top, inner.left, outer.bottom, surfaceOrigin);
+ mBottom.layout(inner.left, inner.bottom, outer.right, outer.bottom, surfaceOrigin);
+ mRight.layout(inner.right, outer.top, outer.right, inner.bottom, surfaceOrigin);
}
@@ -137,20 +133,18 @@
private final String mType;
private SurfaceControl mSurface;
- private final Rect mSurfaceFrame = new Rect();
- private final Rect mLayoutFrame = new Rect();
+ private final Rect mSurfaceFrameRelative = new Rect();
+ private final Rect mLayoutFrameGlobal = new Rect();
+ private final Rect mLayoutFrameRelative = new Rect();
public LetterboxSurface(String type) {
mType = type;
}
- public void layout(int left, int top, int right, int bottom) {
- if (mLayoutFrame.left == left && mLayoutFrame.top == top
- && mLayoutFrame.right == right && mLayoutFrame.bottom == bottom) {
- // Nothing changed.
- return;
- }
- mLayoutFrame.set(left, top, right, bottom);
+ public void layout(int left, int top, int right, int bottom, Point surfaceOrigin) {
+ mLayoutFrameGlobal.set(left, top, right, bottom);
+ mLayoutFrameRelative.set(mLayoutFrameGlobal);
+ mLayoutFrameRelative.offset(-surfaceOrigin.x, -surfaceOrigin.y);
}
private void createSurface() {
@@ -168,32 +162,37 @@
}
public int getWidth() {
- return Math.max(0, mLayoutFrame.width());
+ return Math.max(0, mLayoutFrameGlobal.width());
}
public int getHeight() {
- return Math.max(0, mLayoutFrame.height());
+ return Math.max(0, mLayoutFrameGlobal.height());
}
+ /**
+ * Returns if the given {@code rect} overlaps with this letterbox piece.
+ * @param rect the area to check for overlap in global coordinates
+ */
public boolean isOverlappingWith(Rect rect) {
- if (getWidth() <= 0 || getHeight() <= 0) {
+ if (mLayoutFrameGlobal.isEmpty()) {
return false;
}
- return Rect.intersects(rect, mLayoutFrame);
+ return Rect.intersects(rect, mLayoutFrameGlobal);
}
public void applySurfaceChanges(SurfaceControl.Transaction t) {
- if (mSurfaceFrame.equals(mLayoutFrame)) {
+ if (mSurfaceFrameRelative.equals(mLayoutFrameRelative)) {
// Nothing changed.
return;
}
- mSurfaceFrame.set(mLayoutFrame);
- if (!mSurfaceFrame.isEmpty()) {
+ mSurfaceFrameRelative.set(mLayoutFrameRelative);
+ if (!mSurfaceFrameRelative.isEmpty()) {
if (mSurface == null) {
createSurface();
}
- t.setPosition(mSurface, mSurfaceFrame.left, mSurfaceFrame.top);
- t.setWindowCrop(mSurface, mSurfaceFrame.width(), mSurfaceFrame.height());
+ t.setPosition(mSurface, mSurfaceFrameRelative.left, mSurfaceFrameRelative.top);
+ t.setWindowCrop(mSurface, mSurfaceFrameRelative.width(),
+ mSurfaceFrameRelative.height());
t.show(mSurface);
} else if (mSurface != null) {
t.hide(mSurface);
@@ -201,7 +200,7 @@
}
public boolean needsApplySurfaceChanges() {
- return !mSurfaceFrame.equals(mLayoutFrame);
+ return !mSurfaceFrameRelative.equals(mLayoutFrameRelative);
}
}
}
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index c4a853d..9b72141 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -1108,41 +1108,28 @@
return false;
}
- boolean result = false;
if (targetStack != null && (targetStack.isTopStackOnDisplay()
|| getTopDisplayFocusedStack() == targetStack)) {
- result = targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
+ return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
}
+ // Resume all top activities in focused stacks on all displays.
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- boolean resumedOnDisplay = false;
final ActivityDisplay display = mActivityDisplays.get(displayNdx);
- for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = display.getChildAt(stackNdx);
- final ActivityRecord topRunningActivity = stack.topRunningActivityLocked();
- if (!stack.isFocusableAndVisible() || topRunningActivity == null) {
- continue;
- }
- if (topRunningActivity.isState(RESUMED)) {
- // Kick off any lingering app transitions form the MoveTaskToFront operation.
- stack.executeAppTransition(targetOptions);
- } else {
- resumedOnDisplay |= topRunningActivity.makeActiveIfNeeded(target);
- }
+ final ActivityStack focusedStack = display.getFocusedStack();
+ if (focusedStack == null) {
+ continue;
}
- if (!resumedOnDisplay) {
- // In cases when there are no valid activities (e.g. device just booted or launcher
- // crashed) it's possible that nothing was resumed on a display. Requesting resume
- // of top activity in focused stack explicitly will make sure that at least home
- // activity is started and resumed, and no recursion occurs.
- final ActivityStack focusedStack = display.getFocusedStack();
- if (focusedStack != null) {
- focusedStack.resumeTopActivityUncheckedLocked(target, targetOptions);
- }
+ final ActivityRecord r = focusedStack.topRunningActivityLocked();
+ if (r == null || !r.isState(RESUMED)) {
+ focusedStack.resumeTopActivityUncheckedLocked(null, null);
+ } else if (r.isState(RESUMED)) {
+ // Kick off any lingering app transitions form the MoveTaskToFront operation.
+ focusedStack.executeAppTransition(targetOptions);
}
}
- return result;
+ return false;
}
void applySleepTokens(boolean applyToStacks) {
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 9d9b48a..1a8a911 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -199,7 +199,7 @@
* @see #setLayer
*/
void reparent(Transaction t, SurfaceControl newParent) {
- t.reparent(mLeash != null ? mLeash : mAnimatable.getSurfaceControl(), newParent.getHandle());
+ t.reparent(mLeash != null ? mLeash : mAnimatable.getSurfaceControl(), newParent);
}
/**
@@ -228,8 +228,8 @@
// Cancel source animation, but don't let animation runner cancel the animation.
from.cancelAnimation(t, false /* restarting */, false /* forwardCancel */);
- t.reparent(surface, mLeash.getHandle());
- t.reparent(mLeash, parent.getHandle());
+ t.reparent(surface, mLeash);
+ t.reparent(mLeash, parent);
mAnimatable.onAnimationLeashCreated(t, mLeash);
mService.mAnimationTransferMap.put(mAnimation, this);
}
@@ -275,7 +275,7 @@
final boolean destroy = mLeash != null && surface != null && parent != null;
if (destroy) {
if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to original parent");
- t.reparent(surface, parent.getHandle());
+ t.reparent(surface, parent);
scheduleAnim = true;
}
mService.mAnimationTransferMap.remove(mAnimation);
@@ -308,7 +308,7 @@
if (!hidden) {
t.show(leash);
}
- t.reparent(surface, leash.getHandle());
+ t.reparent(surface, leash);
return leash;
}
diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java
index 0529ed1..69dcaf4 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -698,14 +698,6 @@
return false;
}
- final boolean toTopOfStack = position == MAX_VALUE;
- if (toTopOfStack && toStack.getResumedActivity() != null
- && toStack.topRunningActivityLocked() != null) {
- // Pause the resumed activity on the target stack while re-parenting task on top of it.
- toStack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */,
- null /* resuming */, false /* pauseImmediately */);
- }
-
final int toStackWindowingMode = toStack.getWindowingMode();
final ActivityRecord topActivity = getTopActivity();
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 01a5622..beb3d82 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -17,7 +17,6 @@
package com.android.server.wm;
import static com.android.server.wm.TaskSnapshotPersister.DISABLE_FULL_SIZED_BITMAPS;
-import static com.android.server.wm.TaskSnapshotPersister.REDUCED_SCALE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -90,9 +89,8 @@
private final WindowManagerService mService;
private final TaskSnapshotCache mCache;
- private final TaskSnapshotPersister mPersister = new TaskSnapshotPersister(
- Environment::getDataSystemCeDirectory);
- private final TaskSnapshotLoader mLoader = new TaskSnapshotLoader(mPersister);
+ private final TaskSnapshotPersister mPersister;
+ private final TaskSnapshotLoader mLoader;
private final ArraySet<Task> mSkipClosingAppSnapshotTasks = new ArraySet<>();
private final ArraySet<Task> mTmpTasks = new ArraySet<>();
private final Handler mHandler = new Handler();
@@ -116,6 +114,8 @@
TaskSnapshotController(WindowManagerService service) {
mService = service;
+ mPersister = new TaskSnapshotPersister(mService, Environment::getDataSystemCeDirectory);
+ mLoader = new TaskSnapshotLoader(mPersister);
mCache = new TaskSnapshotCache(mService, mLoader);
mIsRunningOnTv = mService.mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_LEANBACK);
@@ -270,7 +270,7 @@
}
final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
- final float scaleFraction = isLowRamDevice ? REDUCED_SCALE : 1f;
+ final float scaleFraction = isLowRamDevice ? mPersister.getReducedScale() : 1f;
task.getBounds(mTmpRect);
mTmpRect.offsetTo(0, 0);
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
index 0e1570b..d30843b 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static com.android.server.wm.TaskSnapshotPersister.*;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -92,7 +91,7 @@
proto.topActivityComponent);
return new TaskSnapshot(topActivityComponent, buffer, proto.orientation,
new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom),
- reducedResolution, reducedResolution ? REDUCED_SCALE : 1f,
+ reducedResolution, reducedResolution ? mPersister.getReducedScale() : 1f,
proto.isRealSnapshot, proto.windowingMode, proto.systemUiVisibility,
proto.isTranslucent);
} catch (IOException e) {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 24b5b61..e6d646c 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -52,7 +52,9 @@
private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM;
private static final String SNAPSHOTS_DIRNAME = "snapshots";
private static final String REDUCED_POSTFIX = "_reduced";
- static final float REDUCED_SCALE = ActivityManager.isLowRamDeviceStatic() ? 0.6f : 0.5f;
+ private static final float REDUCED_SCALE = .5f;
+ private static final float LOW_RAM_REDUCED_SCALE = .6f;
+ private static final float LOW_RAM_RECENTS_REDUCED_SCALE = .1f;
static final boolean DISABLE_FULL_SIZED_BITMAPS = ActivityManager.isLowRamDeviceStatic();
private static final long DELAY_MS = 100;
private static final int QUALITY = 95;
@@ -71,6 +73,7 @@
private boolean mStarted;
private final Object mLock = new Object();
private final DirectoryResolver mDirectoryResolver;
+ private final float mReducedScale;
/**
* The list of ids of the tasks that have been persisted since {@link #removeObsoleteFiles} was
@@ -79,8 +82,16 @@
@GuardedBy("mLock")
private final ArraySet<Integer> mPersistedTaskIdsSinceLastRemoveObsolete = new ArraySet<>();
- TaskSnapshotPersister(DirectoryResolver resolver) {
+ TaskSnapshotPersister(WindowManagerService service, DirectoryResolver resolver) {
mDirectoryResolver = resolver;
+ if (service.mLowRamTaskSnapshotsAndRecents) {
+ // Use very low res snapshots if we are using Go version of recents.
+ mReducedScale = LOW_RAM_RECENTS_REDUCED_SCALE;
+ } else {
+ // TODO(122671846) Replace the low RAM value scale with the above when it is fully built
+ mReducedScale = ActivityManager.isLowRamDeviceStatic()
+ ? LOW_RAM_REDUCED_SCALE : REDUCED_SCALE;
+ }
}
/**
@@ -144,6 +155,15 @@
}
}
+ /**
+ * Gets the scaling the persister uses for low resolution task snapshots.
+ *
+ * @return the reduced scale of task snapshots when they are set to be low res
+ */
+ float getReducedScale() {
+ return mReducedScale;
+ }
+
@TestApi
void waitForQueueEmpty() {
while (true) {
@@ -350,8 +370,8 @@
final Bitmap reduced = mSnapshot.isReducedResolution()
? swBitmap
: Bitmap.createScaledBitmap(swBitmap,
- (int) (bitmap.getWidth() * REDUCED_SCALE),
- (int) (bitmap.getHeight() * REDUCED_SCALE), true /* filter */);
+ (int) (bitmap.getWidth() * mReducedScale),
+ (int) (bitmap.getHeight() * mReducedScale), true /* filter */);
try {
FileOutputStream reducedFos = new FileOutputStream(reducedFile);
reduced.compress(JPEG, QUALITY, reducedFos);
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index b8a0739..b8db98b 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -167,13 +167,11 @@
screenRotationAnimation.kill();
displayAnimator.mScreenRotationAnimation = null;
- //TODO (multidisplay): Accessibility supported only for the default
// display.
- if (accessibilityController != null && dc.isDefaultDisplay) {
+ if (accessibilityController != null) {
// We just finished rotation animation which means we did not
// announce the rotation and waited for it to end, announce now.
- accessibilityController.onRotationChangedLocked(
- mService.getDefaultDisplayContentLocked());
+ accessibilityController.onRotationChangedLocked(dc);
}
}
}
@@ -197,9 +195,8 @@
screenRotationAnimation.updateSurfaces(mTransaction);
}
orAnimating(dc.getDockedDividerController().animate(mCurrentTime));
- //TODO (multidisplay): Magnification is supported only for the default display.
- if (accessibilityController != null && dc.isDefaultDisplay) {
- accessibilityController.drawMagnifiedRegionBorderIfNeededLocked();
+ if (accessibilityController != null) {
+ accessibilityController.drawMagnifiedRegionBorderIfNeededLocked(displayId);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 1691dc0..5267e7e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -212,34 +212,40 @@
* and has access to the raw window data while the accessibility layer serves
* as a controller.
*
+ * @param displayId The logical display id.
* @param callbacks The callbacks to invoke.
+ * @return {@code false} if display id is not valid.
*/
- public abstract void setMagnificationCallbacks(@Nullable MagnificationCallbacks callbacks);
+ public abstract boolean setMagnificationCallbacks(int displayId,
+ @Nullable MagnificationCallbacks callbacks);
/**
* Set by the accessibility layer to specify the magnification and panning to
* be applied to all windows that should be magnified.
*
+ * @param displayId The logical display id.
* @param spec The MagnficationSpec to set.
*
- * @see #setMagnificationCallbacks(MagnificationCallbacks)
+ * @see #setMagnificationCallbacks(int, MagnificationCallbacks)
*/
- public abstract void setMagnificationSpec(MagnificationSpec spec);
+ public abstract void setMagnificationSpec(int displayId, MagnificationSpec spec);
/**
* Set by the accessibility framework to indicate whether the magnifiable regions of the display
* should be shown.
*
+ * @param displayId The logical display id.
* @param show {@code true} to show magnifiable region bounds, {@code false} to hide
*/
- public abstract void setForceShowMagnifiableBounds(boolean show);
+ public abstract void setForceShowMagnifiableBounds(int displayId, boolean show);
/**
* Obtains the magnification regions.
*
+ * @param displayId The logical display id.
* @param magnificationRegion the current magnification region
*/
- public abstract void getMagnificationRegion(@NonNull Region magnificationRegion);
+ public abstract void getMagnificationRegion(int displayId, @NonNull Region magnificationRegion);
/**
* Gets the magnification and translation applied to a window given its token.
@@ -251,7 +257,7 @@
*
* @return The magnification spec for the window.
*
- * @see #setMagnificationCallbacks(MagnificationCallbacks)
+ * @see #setMagnificationCallbacks(int, MagnificationCallbacks)
*/
public abstract MagnificationSpec getCompatibleMagnificationSpecForWindow(
IBinder windowToken);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index fda7a85..c6679a9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -433,11 +433,12 @@
final long mDrawLockTimeoutMillis;
final boolean mAllowAnimationsInLowPowerMode;
+ // TODO(b/122671846) Remove the flag below in favor of isLowRam once feature is stable
/**
* Use very low resolution task snapshots. Replaces task snapshot starting windows with
* splashscreen starting windows. Used on low RAM devices to save memory.
*/
- final boolean mLowRamTaskSnapshots;
+ final boolean mLowRamTaskSnapshotsAndRecents;
final boolean mAllowBootMessages;
@@ -955,7 +956,7 @@
com.android.internal.R.bool.config_disableTransitionAnimation);
mPerDisplayFocusEnabled = context.getResources().getBoolean(
com.android.internal.R.bool.config_perDisplayFocusEnabled);
- mLowRamTaskSnapshots = context.getResources().getBoolean(
+ mLowRamTaskSnapshotsAndRecents = context.getResources().getBoolean(
com.android.internal.R.bool.config_lowRamTaskSnapshotsAndRecents);
mInputManager = inputManager; // Must be before createDisplayContentLocked.
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
@@ -1845,9 +1846,9 @@
synchronized (mGlobalLock) {
if (mAccessibilityController != null) {
WindowState window = mWindowMap.get(token);
- //TODO (multidisplay): Magnification is supported only for the default display.
- if (window != null && window.getDisplayId() == DEFAULT_DISPLAY) {
- mAccessibilityController.onRectangleOnScreenRequestedLocked(rectangle);
+ if (window != null) {
+ mAccessibilityController.onRectangleOnScreenRequestedLocked(
+ window.getDisplayId(), rectangle);
}
}
}
@@ -2236,8 +2237,7 @@
win.mDestroying = true;
win.destroySurface(false, stopped);
}
- // TODO(multidisplay): Magnification is supported only for the default display.
- if (mAccessibilityController != null && win.getDisplayId() == DEFAULT_DISPLAY) {
+ if (mAccessibilityController != null) {
mAccessibilityController.onWindowTransitionLocked(win, transit);
}
@@ -6822,10 +6822,10 @@
}
@Override
- public void setMagnificationSpec(MagnificationSpec spec) {
+ public void setMagnificationSpec(int displayId, MagnificationSpec spec) {
synchronized (mGlobalLock) {
if (mAccessibilityController != null) {
- mAccessibilityController.setMagnificationSpecLocked(spec);
+ mAccessibilityController.setMagnificationSpecLocked(displayId, spec);
} else {
throw new IllegalStateException("Magnification callbacks not set!");
}
@@ -6836,10 +6836,10 @@
}
@Override
- public void setForceShowMagnifiableBounds(boolean show) {
+ public void setForceShowMagnifiableBounds(int displayId, boolean show) {
synchronized (mGlobalLock) {
if (mAccessibilityController != null) {
- mAccessibilityController.setForceShowMagnifiableBoundsLocked(show);
+ mAccessibilityController.setForceShowMagnifiableBoundsLocked(displayId, show);
} else {
throw new IllegalStateException("Magnification callbacks not set!");
}
@@ -6847,10 +6847,11 @@
}
@Override
- public void getMagnificationRegion(@NonNull Region magnificationRegion) {
+ public void getMagnificationRegion(int displayId, @NonNull Region magnificationRegion) {
synchronized (mGlobalLock) {
if (mAccessibilityController != null) {
- mAccessibilityController.getMagnificationRegionLocked(magnificationRegion);
+ mAccessibilityController.getMagnificationRegionLocked(displayId,
+ magnificationRegion);
} else {
throw new IllegalStateException("Magnification callbacks not set!");
}
@@ -6878,16 +6879,19 @@
}
@Override
- public void setMagnificationCallbacks(@Nullable MagnificationCallbacks callbacks) {
+ public boolean setMagnificationCallbacks(int displayId,
+ @Nullable MagnificationCallbacks callbacks) {
synchronized (mGlobalLock) {
if (mAccessibilityController == null) {
mAccessibilityController = new AccessibilityController(
WindowManagerService.this);
}
- mAccessibilityController.setMagnificationCallbacksLocked(callbacks);
+ boolean result = mAccessibilityController.setMagnificationCallbacksLocked(
+ displayId, callbacks);
if (!mAccessibilityController.hasCallbacksLocked()) {
mAccessibilityController = null;
}
+ return result;
}
}
@@ -7266,8 +7270,12 @@
}, false /* traverseTopToBottom */);
}
- public void applyMagnificationSpec(MagnificationSpec spec) {
- getDefaultDisplayContentLocked().applyMagnificationSpec(spec);
+ /** Called from Accessibility Controller to apply magnification spec */
+ public void applyMagnificationSpecLocked(int displayId, MagnificationSpec spec) {
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+ if (displayContent != null) {
+ displayContent.applyMagnificationSpec(spec);
+ }
}
SurfaceControl.Builder makeSurfaceBuilder(SurfaceSession s) {
@@ -7326,7 +7334,7 @@
}
@Override
- public void reparentDisplayContent(int displayId, IBinder surfaceControlHandle) {
+ public void reparentDisplayContent(int displayId, SurfaceControl sc) {
final Display display = mDisplayManager.getDisplay(displayId);
if (display == null) {
throw new IllegalArgumentException(
@@ -7343,7 +7351,7 @@
long token = Binder.clearCallingIdentity();
try {
DisplayContent displayContent = getDisplayContentOrCreate(displayId, null);
- displayContent.reparentDisplayContent(surfaceControlHandle);
+ displayContent.reparentDisplayContent(sc);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 8f86c00..ce5eb84 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1607,8 +1607,7 @@
mWmService.mAccessibilityController;
final int winTransit = TRANSIT_EXIT;
mWinAnimator.applyAnimationLocked(winTransit, false /* isEntrance */);
- //TODO (multidisplay): Magnification is supported only for the default
- if (accessibilityController != null && getDisplayId() == DEFAULT_DISPLAY) {
+ if (accessibilityController != null) {
accessibilityController.onWindowTransitionLocked(this, winTransit);
}
}
@@ -1625,8 +1624,7 @@
if (isVisibleNow()) {
mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false);
- //TODO (multidisplay): Magnification is supported only for the default
- if (mWmService.mAccessibilityController != null && isDefaultDisplay()) {
+ if (mWmService.mAccessibilityController != null) {
mWmService.mAccessibilityController.onWindowTransitionLocked(this, TRANSIT_EXIT);
}
changed = true;
@@ -1915,9 +1913,7 @@
setDisplayLayoutNeeded();
mWmService.requestTraversal();
}
- //TODO (multidisplay): Magnification is supported only for the default display.
- if (mWmService.mAccessibilityController != null
- && displayId == DEFAULT_DISPLAY) {
+ if (mWmService.mAccessibilityController != null) {
mWmService.mAccessibilityController.onWindowTransitionLocked(this, transit);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index fb5c556..6b4d6d2 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
@@ -1308,9 +1307,7 @@
transit = WindowManagerPolicy.TRANSIT_SHOW;
}
applyAnimationLocked(transit, true);
- //TODO (multidisplay): Magnification is supported only for the default display.
- if (mService.mAccessibilityController != null
- && mWin.getDisplayId() == DEFAULT_DISPLAY) {
+ if (mService.mAccessibilityController != null) {
mService.mAccessibilityController.onWindowTransitionLocked(mWin, transit);
}
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index fb00aeb..3729eaf 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -53,6 +53,7 @@
],
include_dirs: [
+ "bionic/libc/private",
"frameworks/base/libs",
"frameworks/native/services",
"system/gatekeeper/include",
diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp
index dc0d53b..159a496 100644
--- a/services/core/jni/com_android_server_SystemServer.cpp
+++ b/services/core/jni/com_android_server_SystemServer.cpp
@@ -24,6 +24,8 @@
#include <sensorservice/SensorService.h>
#include <sensorservicehidl/SensorManager.h>
+#include <bionic_malloc.h>
+
#include <cutils/properties.h>
#include <utils/Log.h>
#include <utils/misc.h>
@@ -64,6 +66,11 @@
ALOGE_IF(err != OK, "Cannot register %s: %d", ISchedulingPolicyService::descriptor, err);
}
+static void android_server_SystemServer_initZygoteChildHeapProfiling(JNIEnv* /* env */,
+ jobject /* clazz */) {
+ android_mallopt(M_INIT_ZYGOTE_CHILD_PROFILING, nullptr, 0);
+}
+
/*
* JNI registration.
*/
@@ -71,6 +78,8 @@
/* name, signature, funcPtr */
{ "startSensorService", "()V", (void*) android_server_SystemServer_startSensorService },
{ "startHidlServices", "()V", (void*) android_server_SystemServer_startHidlServices },
+ { "initZygoteChildHeapProfiling", "()V",
+ (void*) android_server_SystemServer_initZygoteChildHeapProfiling },
};
int register_android_server_SystemServer(JNIEnv* env)
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 76ae5cc..54053a8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -492,7 +492,6 @@
final UsageStatsManagerInternal mUsageStatsManagerInternal;
final TelephonyManager mTelephonyManager;
private final LockPatternUtils mLockPatternUtils;
- private final DevicePolicyConstants mConstants;
private final DeviceAdminServiceController mDeviceAdminServiceController;
private final OverlayPackagesProvider mOverlayPackagesProvider;
@@ -539,6 +538,9 @@
private final AtomicBoolean mRemoteBugreportSharingAccepted = new AtomicBoolean();
private final SetupContentObserver mSetupContentObserver;
+ private final DevicePolicyConstantsObserver mConstantsObserver;
+
+ private DevicePolicyConstants mConstants;
private static boolean ENABLE_LOCK_GUARD = Build.IS_ENG
|| true // STOPSHIP Remove it.
@@ -2169,8 +2171,10 @@
mInjector = injector;
mContext = Preconditions.checkNotNull(injector.mContext);
mHandler = new Handler(Preconditions.checkNotNull(injector.getMyLooper()));
- mConstants = DevicePolicyConstants.loadFromString(
- mInjector.settingsGlobalGetString(Global.DEVICE_POLICY_CONSTANTS));
+
+ mConstantsObserver = new DevicePolicyConstantsObserver(mHandler);
+ mConstantsObserver.register();
+ mConstants = loadConstants();
mOwners = Preconditions.checkNotNull(injector.newOwners());
@@ -4219,7 +4223,7 @@
@Override
public void setPasswordHistoryLength(ComponentName who, int length, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return;
}
Preconditions.checkNotNull(who, "ComponentName is null");
@@ -4242,13 +4246,16 @@
@Override
public int getPasswordHistoryLength(ComponentName who, int userHandle, boolean parent) {
+ if (!mLockPatternUtils.hasSecureLockScreen()) {
+ return 0;
+ }
return getStrictestPasswordRequirement(who, userHandle, parent,
admin -> admin.passwordHistoryLength, PASSWORD_QUALITY_UNSPECIFIED);
}
@Override
public void setPasswordExpirationTimeout(ComponentName who, long timeout, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return;
}
Preconditions.checkNotNull(who, "ComponentName is null");
@@ -4284,7 +4291,7 @@
*/
@Override
public long getPasswordExpirationTimeout(ComponentName who, int userHandle, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return 0L;
}
enforceFullCrossUsersPermission(userHandle);
@@ -4419,7 +4426,7 @@
@Override
public long getPasswordExpiration(ComponentName who, int userHandle, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return 0L;
}
enforceFullCrossUsersPermission(userHandle);
@@ -4766,6 +4773,9 @@
@Override
public int getCurrentFailedPasswordAttempts(int userHandle, boolean parent) {
+ if (!mLockPatternUtils.hasSecureLockScreen()) {
+ return 0;
+ }
enforceFullCrossUsersPermission(userHandle);
synchronized (getLockObject()) {
if (!isCallerWithSystemUid()) {
@@ -4785,7 +4795,7 @@
@Override
public void setMaximumFailedPasswordsForWipe(ComponentName who, int num, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return;
}
Preconditions.checkNotNull(who, "ComponentName is null");
@@ -4811,7 +4821,7 @@
@Override
public int getMaximumFailedPasswordsForWipe(ComponentName who, int userHandle, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return 0;
}
enforceFullCrossUsersPermission(userHandle);
@@ -4825,7 +4835,7 @@
@Override
public int getProfileWithMinimumFailedPasswordsForWipe(int userHandle, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return UserHandle.USER_NULL;
}
enforceFullCrossUsersPermission(userHandle);
@@ -4906,6 +4916,11 @@
}
@Override
public boolean resetPassword(String passwordOrNull, int flags) throws RemoteException {
+ if (!mLockPatternUtils.hasSecureLockScreen()) {
+ Slog.w(LOG_TAG, "Cannot reset password when the device has no lock screen");
+ return false;
+ }
+
final int callingUid = mInjector.binderGetCallingUid();
final int userHandle = mInjector.userHandleGetCallingUserId();
@@ -5248,7 +5263,7 @@
@Override
public void setRequiredStrongAuthTimeout(ComponentName who, long timeoutMs,
boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return;
}
Preconditions.checkNotNull(who, "ComponentName is null");
@@ -5281,7 +5296,7 @@
*/
@Override
public long getRequiredStrongAuthTimeout(ComponentName who, int userId, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS;
}
enforceFullCrossUsersPermission(userId);
@@ -6490,7 +6505,7 @@
*/
@Override
public void setActivePasswordState(PasswordMetrics metrics, int userHandle) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return;
}
enforceFullCrossUsersPermission(userHandle);
@@ -6510,7 +6525,7 @@
@Override
public void reportPasswordChanged(@UserIdInt int userId) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return;
}
enforceFullCrossUsersPermission(userId);
@@ -8786,7 +8801,7 @@
@Override
public void setTrustAgentConfiguration(ComponentName admin, ComponentName agent,
PersistableBundle args, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return;
}
Preconditions.checkNotNull(admin, "admin is null");
@@ -8803,7 +8818,7 @@
@Override
public List<PersistableBundle> getTrustAgentConfiguration(ComponentName admin,
ComponentName agent, int userHandle, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return null;
}
Preconditions.checkNotNull(agent, "agent null");
@@ -10954,6 +10969,25 @@
}
}
+ private class DevicePolicyConstantsObserver extends ContentObserver {
+ final Uri mConstantsUri =
+ Settings.Global.getUriFor(Settings.Global.DEVICE_POLICY_CONSTANTS);
+
+ DevicePolicyConstantsObserver(Handler handler) {
+ super(handler);
+ }
+
+ void register() {
+ mInjector.registerContentObserver(
+ mConstantsUri, /* notifyForDescendents= */ false, this, UserHandle.USER_ALL);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri, int userId) {
+ mConstants = loadConstants();
+ }
+ }
+
@VisibleForTesting
final class LocalService extends DevicePolicyManagerInternal {
private List<OnCrossProfileWidgetProvidersChangeListener> mWidgetProviderListeners;
@@ -12986,7 +13020,12 @@
throw new IllegalStateException("logging is not available");
}
if (mNetworkLogger != null) {
- return mNetworkLogger.forceBatchFinalization();
+ final long ident = mInjector.binderClearCallingIdentity();
+ try {
+ return mNetworkLogger.forceBatchFinalization();
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
}
return 0;
}
@@ -13188,7 +13227,7 @@
@Override
public boolean setResetPasswordToken(ComponentName admin, byte[] token) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return false;
}
if (token == null || token.length < 32) {
@@ -13216,7 +13255,7 @@
@Override
public boolean clearResetPasswordToken(ComponentName admin) {
- if (!mHasFeature) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return false;
}
synchronized (getLockObject()) {
@@ -13242,6 +13281,9 @@
@Override
public boolean isResetPasswordTokenActive(ComponentName admin) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
+ return false;
+ }
synchronized (getLockObject()) {
final int userHandle = mInjector.userHandleGetCallingUserId();
getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
@@ -13263,6 +13305,9 @@
@Override
public boolean resetPasswordWithToken(ComponentName admin, String passwordOrNull, byte[] token,
int flags) {
+ if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
+ return false;
+ }
Preconditions.checkNotNull(token);
synchronized (getLockObject()) {
final int userHandle = mInjector.userHandleGetCallingUserId();
@@ -14167,4 +14212,9 @@
return false;
}
}
+
+ private DevicePolicyConstants loadConstants() {
+ return DevicePolicyConstants.loadFromString(
+ mInjector.settingsGlobalGetString(Global.DEVICE_POLICY_CONSTANTS));
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java b/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java
index d8a875d..cf68ccf 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java
@@ -16,10 +16,13 @@
package com.android.server.devicepolicy;
+import android.annotation.Nullable;
import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManager;
import android.app.admin.StartInstallingUpdateCallback;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Environment;
import android.os.FileUtils;
@@ -41,7 +44,8 @@
private ParcelFileDescriptor mUpdateFileDescriptor;
private DevicePolicyConstants mConstants;
protected Context mContext;
- protected File mCopiedUpdateFile;
+
+ @Nullable protected File mCopiedUpdateFile;
static final String TAG = "UpdateInstaller";
private DevicePolicyManagerService.Injector mInjector;
@@ -59,7 +63,8 @@
public abstract void installUpdateInThread();
public void startInstallUpdate() {
- if (!checkIfBatteryIsSufficient()) {
+ mCopiedUpdateFile = null;
+ if (!isBatteryLevelSufficient()) {
notifyCallbackOnError(
DevicePolicyManager.InstallUpdateCallback.UPDATE_ERROR_BATTERY_LOW,
"The battery level must be above "
@@ -81,17 +86,21 @@
thread.start();
}
- private boolean checkIfBatteryIsSufficient() {
- BatteryManager batteryManager =
- (BatteryManager) mContext.getSystemService(Context.BATTERY_SERVICE);
- if (batteryManager != null) {
- int chargePercentage = batteryManager
- .getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
- return batteryManager.isCharging()
- ? chargePercentage >= mConstants.BATTERY_THRESHOLD_CHARGING
- : chargePercentage >= mConstants.BATTERY_THRESHOLD_NOT_CHARGING;
- }
- return false;
+ private boolean isBatteryLevelSufficient() {
+ Intent batteryStatus = mContext.registerReceiver(
+ /* receiver= */ null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+ float batteryPercentage = calculateBatteryPercentage(batteryStatus);
+ boolean isBatteryPluggedIn =
+ batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, /* defaultValue= */ -1) > 0;
+ return isBatteryPluggedIn
+ ? batteryPercentage >= mConstants.BATTERY_THRESHOLD_CHARGING
+ : batteryPercentage >= mConstants.BATTERY_THRESHOLD_NOT_CHARGING;
+ }
+
+ private float calculateBatteryPercentage(Intent batteryStatus) {
+ int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, /* defaultValue= */ -1);
+ int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, /* defaultValue= */ -1);
+ return 100 * level / (float) scale;
}
private File copyUpdateFileToDataOtaPackageDir() {
@@ -127,7 +136,7 @@
}
void cleanupUpdateFile() {
- if (mCopiedUpdateFile.exists()) {
+ if (mCopiedUpdateFile != null && mCopiedUpdateFile.exists()) {
mCopiedUpdateFile.delete();
}
}
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
index eaab650..e99dd4f 100644
--- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -17,9 +17,28 @@
package com.android.server.net.ipmemorystore;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentValues;
import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteCursor;
+import android.database.sqlite.SQLiteCursorDriver;
import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQuery;
+import android.net.NetworkUtils;
+import android.net.ipmemorystore.NetworkAttributes;
+import android.net.ipmemorystore.Status;
+import android.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringJoiner;
/**
* Encapsulating class for using the SQLite database backing the memory store.
@@ -30,6 +49,11 @@
* @hide
*/
public class IpMemoryStoreDatabase {
+ private static final String TAG = IpMemoryStoreDatabase.class.getSimpleName();
+ // A pair of NetworkAttributes objects is group-close if the confidence that they are
+ // the same is above this cutoff. See NetworkAttributes and SameL3NetworkResponse.
+ private static final float GROUPCLOSE_CONFIDENCE = 0.5f;
+
/**
* Contract class for the Network Attributes table.
*/
@@ -57,7 +81,7 @@
public static final String COLTYPE_DNSADDRESSES = "BLOB";
public static final String COLNAME_MTU = "mtu";
- public static final String COLTYPE_MTU = "INTEGER";
+ public static final String COLTYPE_MTU = "INTEGER DEFAULT -1";
public static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS "
+ TABLENAME + " ("
@@ -108,7 +132,7 @@
/** The SQLite DB helper */
public static class DbHelper extends SQLiteOpenHelper {
// Update this whenever changing the schema.
- private static final int SCHEMA_VERSION = 1;
+ private static final int SCHEMA_VERSION = 2;
private static final String DATABASE_FILENAME = "IpMemoryStore.db";
public DbHelper(@NonNull final Context context) {
@@ -117,12 +141,14 @@
}
/** Called when the database is created */
+ @Override
public void onCreate(@NonNull final SQLiteDatabase db) {
db.execSQL(NetworkAttributesContract.CREATE_TABLE);
db.execSQL(PrivateDataContract.CREATE_TABLE);
}
/** Called when the database is upgraded */
+ @Override
public void onUpgrade(@NonNull final SQLiteDatabase db, final int oldVersion,
final int newVersion) {
// No upgrade supported yet.
@@ -132,6 +158,7 @@
}
/** Called when the database is downgraded */
+ @Override
public void onDowngrade(@NonNull final SQLiteDatabase db, final int oldVersion,
final int newVersion) {
// Downgrades always nuke all data and recreate an empty table.
@@ -140,4 +167,347 @@
onCreate(db);
}
}
+
+ @NonNull
+ private static byte[] encodeAddressList(@NonNull final List<InetAddress> addresses) {
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ for (final InetAddress address : addresses) {
+ final byte[] b = address.getAddress();
+ os.write(b.length);
+ os.write(b, 0, b.length);
+ }
+ return os.toByteArray();
+ }
+
+ @NonNull
+ private static ArrayList<InetAddress> decodeAddressList(@NonNull final byte[] encoded) {
+ final ByteArrayInputStream is = new ByteArrayInputStream(encoded);
+ final ArrayList<InetAddress> addresses = new ArrayList<>();
+ int d = -1;
+ while ((d = is.read()) != -1) {
+ final byte[] bytes = new byte[d];
+ is.read(bytes, 0, d);
+ try {
+ addresses.add(InetAddress.getByAddress(bytes));
+ } catch (UnknownHostException e) { /* Hopefully impossible */ }
+ }
+ return addresses;
+ }
+
+ @NonNull
+ private static ContentValues toContentValues(@Nullable final NetworkAttributes attributes) {
+ final ContentValues values = new ContentValues();
+ if (null == attributes) return values;
+ if (null != attributes.assignedV4Address) {
+ values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS,
+ NetworkUtils.inet4AddressToIntHTH(attributes.assignedV4Address));
+ }
+ if (null != attributes.groupHint) {
+ values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint);
+ }
+ if (null != attributes.dnsAddresses) {
+ values.put(NetworkAttributesContract.COLNAME_DNSADDRESSES,
+ encodeAddressList(attributes.dnsAddresses));
+ }
+ if (null != attributes.mtu) {
+ values.put(NetworkAttributesContract.COLNAME_MTU, attributes.mtu);
+ }
+ return values;
+ }
+
+ // Convert a NetworkAttributes object to content values to store them in a table compliant
+ // with the contract defined in NetworkAttributesContract.
+ @NonNull
+ private static ContentValues toContentValues(@NonNull final String key,
+ @Nullable final NetworkAttributes attributes, final long expiry) {
+ final ContentValues values = toContentValues(attributes);
+ values.put(NetworkAttributesContract.COLNAME_L2KEY, key);
+ values.put(NetworkAttributesContract.COLNAME_EXPIRYDATE, expiry);
+ return values;
+ }
+
+ // Convert a byte array into content values to store it in a table compliant with the
+ // contract defined in PrivateDataContract.
+ @NonNull
+ private static ContentValues toContentValues(@NonNull final String key,
+ @NonNull final String clientId, @NonNull final String name,
+ @NonNull final byte[] data) {
+ final ContentValues values = new ContentValues();
+ values.put(PrivateDataContract.COLNAME_L2KEY, key);
+ values.put(PrivateDataContract.COLNAME_CLIENT, clientId);
+ values.put(PrivateDataContract.COLNAME_DATANAME, name);
+ values.put(PrivateDataContract.COLNAME_DATA, data);
+ return values;
+ }
+
+ @Nullable
+ private static NetworkAttributes readNetworkAttributesLine(@NonNull final Cursor cursor) {
+ // Make sure the data hasn't expired
+ final long expiry = getLong(cursor, NetworkAttributesContract.COLNAME_EXPIRYDATE, -1L);
+ if (expiry < System.currentTimeMillis()) return null;
+
+ final NetworkAttributes.Builder builder = new NetworkAttributes.Builder();
+ final int assignedV4AddressInt = getInt(cursor,
+ NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, 0);
+ final String groupHint = getString(cursor, NetworkAttributesContract.COLNAME_GROUPHINT);
+ final byte[] dnsAddressesBlob =
+ getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES);
+ final int mtu = getInt(cursor, NetworkAttributesContract.COLNAME_MTU, -1);
+ if (0 != assignedV4AddressInt) {
+ builder.setAssignedV4Address(NetworkUtils.intToInet4AddressHTH(assignedV4AddressInt));
+ }
+ builder.setGroupHint(groupHint);
+ if (null != dnsAddressesBlob) {
+ builder.setDnsAddresses(decodeAddressList(dnsAddressesBlob));
+ }
+ if (mtu >= 0) {
+ builder.setMtu(mtu);
+ }
+ return builder.build();
+ }
+
+ private static final String[] EXPIRY_COLUMN = new String[] {
+ NetworkAttributesContract.COLNAME_EXPIRYDATE
+ };
+ static final int EXPIRY_ERROR = -1; // Legal values for expiry are positive
+
+ static final String SELECT_L2KEY = NetworkAttributesContract.COLNAME_L2KEY + " = ?";
+
+ // Returns the expiry date of the specified row, or one of the error codes above if the
+ // row is not found or some other error
+ static long getExpiry(@NonNull final SQLiteDatabase db, @NonNull final String key) {
+ final Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
+ EXPIRY_COLUMN, // columns
+ SELECT_L2KEY, // selection
+ new String[] { key }, // selectionArgs
+ null, // groupBy
+ null, // having
+ null // orderBy
+ );
+ // L2KEY is the primary key ; it should not be possible to get more than one
+ // result here. 0 results means the key was not found.
+ if (cursor.getCount() != 1) return EXPIRY_ERROR;
+ cursor.moveToFirst();
+ final long result = cursor.getLong(0); // index in the EXPIRY_COLUMN array
+ cursor.close();
+ return result;
+ }
+
+ static final int RELEVANCE_ERROR = -1; // Legal values for relevance are positive
+
+ // Returns the relevance of the specified row, or one of the error codes above if the
+ // row is not found or some other error
+ static int getRelevance(@NonNull final SQLiteDatabase db, @NonNull final String key) {
+ final long expiry = getExpiry(db, key);
+ return expiry < 0 ? (int) expiry : RelevanceUtils.computeRelevanceForNow(expiry);
+ }
+
+ // If the attributes are null, this will only write the expiry.
+ // Returns an int out of Status.{SUCCESS,ERROR_*}
+ static int storeNetworkAttributes(@NonNull final SQLiteDatabase db, @NonNull final String key,
+ final long expiry, @Nullable final NetworkAttributes attributes) {
+ final ContentValues cv = toContentValues(key, attributes, expiry);
+ db.beginTransaction();
+ try {
+ // Unfortunately SQLite does not have any way to do INSERT OR UPDATE. Options are
+ // to either insert with on conflict ignore then update (like done here), or to
+ // construct a custom SQL INSERT statement with nested select.
+ final long resultId = db.insertWithOnConflict(NetworkAttributesContract.TABLENAME,
+ null, cv, SQLiteDatabase.CONFLICT_IGNORE);
+ if (resultId < 0) {
+ db.update(NetworkAttributesContract.TABLENAME, cv, SELECT_L2KEY, new String[]{key});
+ }
+ db.setTransactionSuccessful();
+ return Status.SUCCESS;
+ } catch (SQLiteException e) {
+ // No space left on disk or something
+ Log.e(TAG, "Could not write to the memory store", e);
+ } finally {
+ db.endTransaction();
+ }
+ return Status.ERROR_STORAGE;
+ }
+
+ // Returns an int out of Status.{SUCCESS,ERROR_*}
+ static int storeBlob(@NonNull final SQLiteDatabase db, @NonNull final String key,
+ @NonNull final String clientId, @NonNull final String name,
+ @NonNull final byte[] data) {
+ final long res = db.insertWithOnConflict(PrivateDataContract.TABLENAME, null,
+ toContentValues(key, clientId, name, data), SQLiteDatabase.CONFLICT_REPLACE);
+ return (res == -1) ? Status.ERROR_STORAGE : Status.SUCCESS;
+ }
+
+ @Nullable
+ static NetworkAttributes retrieveNetworkAttributes(@NonNull final SQLiteDatabase db,
+ @NonNull final String key) {
+ final Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
+ null, // columns, null means everything
+ NetworkAttributesContract.COLNAME_L2KEY + " = ?", // selection
+ new String[] { key }, // selectionArgs
+ null, // groupBy
+ null, // having
+ null); // orderBy
+ // L2KEY is the primary key ; it should not be possible to get more than one
+ // result here. 0 results means the key was not found.
+ if (cursor.getCount() != 1) return null;
+ cursor.moveToFirst();
+ final NetworkAttributes attributes = readNetworkAttributesLine(cursor);
+ cursor.close();
+ return attributes;
+ }
+
+ private static final String[] DATA_COLUMN = new String[] {
+ PrivateDataContract.COLNAME_DATA
+ };
+ @Nullable
+ static byte[] retrieveBlob(@NonNull final SQLiteDatabase db, @NonNull final String key,
+ @NonNull final String clientId, @NonNull final String name) {
+ final Cursor cursor = db.query(PrivateDataContract.TABLENAME,
+ DATA_COLUMN, // columns
+ PrivateDataContract.COLNAME_L2KEY + " = ? AND " // selection
+ + PrivateDataContract.COLNAME_CLIENT + " = ? AND "
+ + PrivateDataContract.COLNAME_DATANAME + " = ?",
+ new String[] { key, clientId, name }, // selectionArgs
+ null, // groupBy
+ null, // having
+ null); // orderBy
+ // The query above is querying by (composite) primary key, so it should not be possible to
+ // get more than one result here. 0 results means the key was not found.
+ if (cursor.getCount() != 1) return null;
+ cursor.moveToFirst();
+ final byte[] result = cursor.getBlob(0); // index in the DATA_COLUMN array
+ cursor.close();
+ return result;
+ }
+
+ /**
+ * The following is a horrible hack that is necessary because the Android SQLite API does not
+ * have a way to query a binary blob. This, almost certainly, is an overlook.
+ *
+ * The Android SQLite API has two family of methods : one for query that returns data, and
+ * one for more general SQL statements that can execute any statement but may not return
+ * anything. All the query methods, however, take only String[] for the arguments.
+ *
+ * In principle it is simple to write a function that will encode the binary blob in the
+ * way SQLite expects it. However, because the API forces the argument to be coerced into a
+ * String, the SQLiteQuery object generated by the default query methods will bind all
+ * arguments as Strings and SQL will *sanitize* them. This works okay for numeric types,
+ * but the format for blobs is x'<hex string>'. Note the presence of quotes, which will
+ * be sanitized, changing the contents of the field, and the query will fail to match the
+ * blob.
+ *
+ * As far as I can tell, there are two possible ways around this problem. The first one
+ * is to put the data in the query string and eschew it being an argument. This would
+ * require doing the sanitizing by hand. The other is to call bindBlob directly on the
+ * generated SQLiteQuery object, which not only is a lot less dangerous than rolling out
+ * sanitizing, but also will do the right thing if the underlying format ever changes.
+ *
+ * But none of the methods that take an SQLiteQuery object can return data ; this *must*
+ * be called with SQLiteDatabase#query. This object is not accessible from outside.
+ * However, there is a #query version that accepts a CursorFactory and this is pretty
+ * straightforward to implement as all the arguments are coming in and the SQLiteCursor
+ * class is public API.
+ * With this, it's possible to intercept the SQLiteQuery object, and assuming the args
+ * are available, to bind them directly and work around the API's oblivious coercion into
+ * Strings.
+ *
+ * This is really sad, but I don't see another way of having this work than this or the
+ * hand-rolled sanitizing, and this is the lesser evil.
+ */
+ private static class CustomCursorFactory implements SQLiteDatabase.CursorFactory {
+ @NonNull
+ private final ArrayList<Object> mArgs;
+ CustomCursorFactory(@NonNull final ArrayList<Object> args) {
+ mArgs = args;
+ }
+ @Override
+ public Cursor newCursor(final SQLiteDatabase db, final SQLiteCursorDriver masterQuery,
+ final String editTable,
+ final SQLiteQuery query) {
+ int index = 1; // bind is 1-indexed
+ for (final Object arg : mArgs) {
+ if (arg instanceof String) {
+ query.bindString(index++, (String) arg);
+ } else if (arg instanceof Long) {
+ query.bindLong(index++, (Long) arg);
+ } else if (arg instanceof Integer) {
+ query.bindLong(index++, Long.valueOf((Integer) arg));
+ } else if (arg instanceof byte[]) {
+ query.bindBlob(index++, (byte[]) arg);
+ } else {
+ throw new IllegalStateException("Unsupported type CustomCursorFactory "
+ + arg.getClass().toString());
+ }
+ }
+ return new SQLiteCursor(masterQuery, editTable, query);
+ }
+ }
+
+ // Returns the l2key of the closest match, if and only if it matches
+ // closely enough (as determined by group-closeness).
+ @Nullable
+ static String findClosestAttributes(@NonNull final SQLiteDatabase db,
+ @NonNull final NetworkAttributes attr) {
+ if (attr.isEmpty()) return null;
+ final ContentValues values = toContentValues(attr);
+
+ // Build the selection and args. To cut down on the number of lines to search, limit
+ // the search to those with at least one argument equals to the requested attributes.
+ // This works only because null attributes match only will not result in group-closeness.
+ final StringJoiner sj = new StringJoiner(" OR ");
+ final ArrayList<Object> args = new ArrayList<>();
+ args.add(System.currentTimeMillis());
+ for (final String field : values.keySet()) {
+ sj.add(field + " = ?");
+ args.add(values.get(field));
+ }
+
+ final String selection = NetworkAttributesContract.COLNAME_EXPIRYDATE + " > ? AND ("
+ + sj.toString() + ")";
+ final Cursor cursor = db.queryWithFactory(new CustomCursorFactory(args),
+ false, // distinct
+ NetworkAttributesContract.TABLENAME,
+ null, // columns, null means everything
+ selection, // selection
+ null, // selectionArgs, horrendously passed to the cursor factory instead
+ null, // groupBy
+ null, // having
+ null, // orderBy
+ null); // limit
+ if (cursor.getCount() <= 0) return null;
+ cursor.moveToFirst();
+ String bestKey = null;
+ float bestMatchConfidence = GROUPCLOSE_CONFIDENCE; // Never return a match worse than this.
+ while (!cursor.isAfterLast()) {
+ final NetworkAttributes read = readNetworkAttributesLine(cursor);
+ final float confidence = read.getNetworkGroupSamenessConfidence(attr);
+ if (confidence > bestMatchConfidence) {
+ bestKey = getString(cursor, NetworkAttributesContract.COLNAME_L2KEY);
+ bestMatchConfidence = confidence;
+ }
+ cursor.moveToNext();
+ }
+ cursor.close();
+ return bestKey;
+ }
+
+ // Helper methods
+ private static String getString(final Cursor cursor, final String columnName) {
+ final int columnIndex = cursor.getColumnIndex(columnName);
+ return (columnIndex >= 0) ? cursor.getString(columnIndex) : null;
+ }
+ private static byte[] getBlob(final Cursor cursor, final String columnName) {
+ final int columnIndex = cursor.getColumnIndex(columnName);
+ return (columnIndex >= 0) ? cursor.getBlob(columnIndex) : null;
+ }
+ private static int getInt(final Cursor cursor, final String columnName,
+ final int defaultValue) {
+ final int columnIndex = cursor.getColumnIndex(columnName);
+ return (columnIndex >= 0) ? cursor.getInt(columnIndex) : defaultValue;
+ }
+ private static long getLong(final Cursor cursor, final String columnName,
+ final long defaultValue) {
+ final int columnIndex = cursor.getColumnIndex(columnName);
+ return (columnIndex >= 0) ? cursor.getLong(columnIndex) : defaultValue;
+ }
}
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
index 55a72190..d43dc6a 100644
--- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
@@ -16,6 +16,13 @@
package com.android.server.net.ipmemorystore;
+import static android.net.ipmemorystore.Status.ERROR_DATABASE_CANNOT_BE_OPENED;
+import static android.net.ipmemorystore.Status.ERROR_GENERIC;
+import static android.net.ipmemorystore.Status.ERROR_ILLEGAL_ARGUMENT;
+import static android.net.ipmemorystore.Status.SUCCESS;
+
+import static com.android.server.net.ipmemorystore.IpMemoryStoreDatabase.EXPIRY_ERROR;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -28,7 +35,13 @@
import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
import android.net.ipmemorystore.IOnSameNetworkResponseListener;
import android.net.ipmemorystore.IOnStatusListener;
+import android.net.ipmemorystore.NetworkAttributes;
import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.net.ipmemorystore.SameL3NetworkResponse;
+import android.net.ipmemorystore.Status;
+import android.net.ipmemorystore.StatusParcelable;
+import android.net.ipmemorystore.Utils;
+import android.os.RemoteException;
import android.util.Log;
import java.util.concurrent.ExecutorService;
@@ -45,6 +58,7 @@
public class IpMemoryStoreService extends IIpMemoryStore.Stub {
private static final String TAG = IpMemoryStoreService.class.getSimpleName();
private static final int MAX_CONCURRENT_THREADS = 4;
+ private static final boolean DBG = true;
@NonNull
final Context mContext;
@@ -114,6 +128,11 @@
if (mDb != null) mDb.close();
}
+ /** Helper function to make a status object */
+ private StatusParcelable makeStatus(final int code) {
+ return new Status(code).toParcelable();
+ }
+
/**
* Store network attributes for a given L2 key.
*
@@ -128,11 +147,27 @@
* Through the listener, returns the L2 key. This is useful if the L2 key was not specified.
* If the call failed, the L2 key will be null.
*/
+ // Note that while l2Key and attributes are non-null in spirit, they are received from
+ // another process. If the remote process decides to ignore everything and send null, this
+ // process should still not crash.
@Override
- public void storeNetworkAttributes(@NonNull final String l2Key,
- @NonNull final NetworkAttributesParcelable attributes,
+ public void storeNetworkAttributes(@Nullable final String l2Key,
+ @Nullable final NetworkAttributesParcelable attributes,
@Nullable final IOnStatusListener listener) {
- // TODO : implement this.
+ // Because the parcelable is 100% mutable, the thread may not see its members initialized.
+ // Therefore either an immutable object is created on this same thread before it's passed
+ // to the executor, or there need to be a write barrier here and a read barrier in the
+ // remote thread.
+ final NetworkAttributes na = null == attributes ? null : new NetworkAttributes(attributes);
+ mExecutor.execute(() -> {
+ try {
+ final int code = storeNetworkAttributesAndBlobSync(l2Key, na,
+ null /* clientId */, null /* name */, null /* data */);
+ if (null != listener) listener.onComplete(makeStatus(code));
+ } catch (final RemoteException e) {
+ // Client at the other end died
+ }
+ });
}
/**
@@ -141,16 +176,63 @@
* @param l2Key The L2 key for this network.
* @param clientId The ID of the client.
* @param name The name of this data.
- * @param data The data to store.
+ * @param blob The data to store.
* @param listener The listener that will be invoked to return the answer, or null if the
* is not interested in learning about success/failure.
* Through the listener, returns a status to indicate success or failure.
*/
@Override
- public void storeBlob(@NonNull final String l2Key, @NonNull final String clientId,
- @NonNull final String name, @NonNull final Blob data,
+ public void storeBlob(@Nullable final String l2Key, @Nullable final String clientId,
+ @Nullable final String name, @Nullable final Blob blob,
@Nullable final IOnStatusListener listener) {
- // TODO : implement this.
+ final byte[] data = null == blob ? null : blob.data;
+ mExecutor.execute(() -> {
+ try {
+ final int code = storeNetworkAttributesAndBlobSync(l2Key,
+ null /* NetworkAttributes */, clientId, name, data);
+ if (null != listener) listener.onComplete(makeStatus(code));
+ } catch (final RemoteException e) {
+ // Client at the other end died
+ }
+ });
+ }
+
+ /**
+ * Helper method for storeNetworkAttributes and storeBlob.
+ *
+ * Either attributes or none of clientId, name and data may be null. This will write the
+ * passed data if non-null, and will write attributes if non-null, but in any case it will
+ * bump the relevance up.
+ * Returns a success code from Status.
+ */
+ private int storeNetworkAttributesAndBlobSync(@Nullable final String l2Key,
+ @Nullable final NetworkAttributes attributes,
+ @Nullable final String clientId,
+ @Nullable final String name, @Nullable final byte[] data) {
+ if (null == l2Key) return ERROR_ILLEGAL_ARGUMENT;
+ if (null == attributes && null == data) return ERROR_ILLEGAL_ARGUMENT;
+ if (null != data && (null == clientId || null == name)) return ERROR_ILLEGAL_ARGUMENT;
+ if (null == mDb) return ERROR_DATABASE_CANNOT_BE_OPENED;
+ try {
+ final long oldExpiry = IpMemoryStoreDatabase.getExpiry(mDb, l2Key);
+ final long newExpiry = RelevanceUtils.bumpExpiryDate(
+ oldExpiry == EXPIRY_ERROR ? System.currentTimeMillis() : oldExpiry);
+ final int errorCode =
+ IpMemoryStoreDatabase.storeNetworkAttributes(mDb, l2Key, newExpiry, attributes);
+ // If no blob to store, the client is interested in the result of storing the attributes
+ if (null == data) return errorCode;
+ // Otherwise it's interested in the result of storing the blob
+ return IpMemoryStoreDatabase.storeBlob(mDb, l2Key, clientId, name, data);
+ } catch (Exception e) {
+ if (DBG) {
+ Log.e(TAG, "Exception while storing for key {" + l2Key
+ + "} ; NetworkAttributes {" + (null == attributes ? "null" : attributes)
+ + "} ; clientId {" + (null == clientId ? "null" : clientId)
+ + "} ; name {" + (null == name ? "null" : name)
+ + "} ; data {" + Utils.byteArrayToString(data) + "}", e);
+ }
+ }
+ return ERROR_GENERIC;
}
/**
@@ -168,9 +250,26 @@
* Through the listener, returns the L2 key if one matched, or null.
*/
@Override
- public void findL2Key(@NonNull final NetworkAttributesParcelable attributes,
- @NonNull final IOnL2KeyResponseListener listener) {
- // TODO : implement this.
+ public void findL2Key(@Nullable final NetworkAttributesParcelable attributes,
+ @Nullable final IOnL2KeyResponseListener listener) {
+ if (null == listener) return;
+ mExecutor.execute(() -> {
+ try {
+ if (null == attributes) {
+ listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
+ return;
+ }
+ if (null == mDb) {
+ listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
+ return;
+ }
+ final String key = IpMemoryStoreDatabase.findClosestAttributes(mDb,
+ new NetworkAttributes(attributes));
+ listener.onL2KeyResponse(makeStatus(SUCCESS), key);
+ } catch (final RemoteException e) {
+ // Client at the other end died
+ }
+ });
}
/**
@@ -183,9 +282,40 @@
* Through the listener, a SameL3NetworkResponse containing the answer and confidence.
*/
@Override
- public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2,
- @NonNull final IOnSameNetworkResponseListener listener) {
- // TODO : implement this.
+ public void isSameNetwork(@Nullable final String l2Key1, @Nullable final String l2Key2,
+ @Nullable final IOnSameNetworkResponseListener listener) {
+ if (null == listener) return;
+ mExecutor.execute(() -> {
+ try {
+ if (null == l2Key1 || null == l2Key2) {
+ listener.onSameNetworkResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
+ return;
+ }
+ if (null == mDb) {
+ listener.onSameNetworkResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
+ return;
+ }
+ try {
+ final NetworkAttributes attr1 =
+ IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key1);
+ final NetworkAttributes attr2 =
+ IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key2);
+ if (null == attr1 || null == attr2) {
+ listener.onSameNetworkResponse(makeStatus(SUCCESS),
+ new SameL3NetworkResponse(l2Key1, l2Key2,
+ -1f /* never connected */).toParcelable());
+ return;
+ }
+ final float confidence = attr1.getNetworkGroupSamenessConfidence(attr2);
+ listener.onSameNetworkResponse(makeStatus(SUCCESS),
+ new SameL3NetworkResponse(l2Key1, l2Key2, confidence).toParcelable());
+ } catch (Exception e) {
+ listener.onSameNetworkResponse(makeStatus(ERROR_GENERIC), null);
+ }
+ } catch (final RemoteException e) {
+ // Client at the other end died
+ }
+ });
}
/**
@@ -198,9 +328,33 @@
* the query.
*/
@Override
- public void retrieveNetworkAttributes(@NonNull final String l2Key,
- @NonNull final IOnNetworkAttributesRetrieved listener) {
- // TODO : implement this.
+ public void retrieveNetworkAttributes(@Nullable final String l2Key,
+ @Nullable final IOnNetworkAttributesRetrieved listener) {
+ if (null == listener) return;
+ mExecutor.execute(() -> {
+ try {
+ if (null == l2Key) {
+ listener.onNetworkAttributesRetrieved(
+ makeStatus(ERROR_ILLEGAL_ARGUMENT), l2Key, null);
+ return;
+ }
+ if (null == mDb) {
+ listener.onNetworkAttributesRetrieved(
+ makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED), l2Key, null);
+ return;
+ }
+ try {
+ final NetworkAttributes attributes =
+ IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key);
+ listener.onNetworkAttributesRetrieved(makeStatus(SUCCESS), l2Key,
+ null == attributes ? null : attributes.toParcelable());
+ } catch (final Exception e) {
+ listener.onNetworkAttributesRetrieved(makeStatus(ERROR_GENERIC), l2Key, null);
+ }
+ } catch (final RemoteException e) {
+ // Client at the other end died
+ }
+ });
}
/**
@@ -217,6 +371,28 @@
@Override
public void retrieveBlob(@NonNull final String l2Key, @NonNull final String clientId,
@NonNull final String name, @NonNull final IOnBlobRetrievedListener listener) {
- // TODO : implement this.
+ if (null == listener) return;
+ mExecutor.execute(() -> {
+ try {
+ if (null == l2Key) {
+ listener.onBlobRetrieved(makeStatus(ERROR_ILLEGAL_ARGUMENT), l2Key, name, null);
+ return;
+ }
+ if (null == mDb) {
+ listener.onBlobRetrieved(makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED), l2Key,
+ name, null);
+ return;
+ }
+ try {
+ final Blob b = new Blob();
+ b.data = IpMemoryStoreDatabase.retrieveBlob(mDb, l2Key, clientId, name);
+ listener.onBlobRetrieved(makeStatus(SUCCESS), l2Key, name, b);
+ } catch (final Exception e) {
+ listener.onBlobRetrieved(makeStatus(ERROR_GENERIC), l2Key, name, null);
+ }
+ } catch (final RemoteException e) {
+ // Client at the other end died
+ }
+ });
}
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index cef47ca..65eaf554 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -136,6 +136,7 @@
import com.android.server.statusbar.StatusBarManagerService;
import com.android.server.storage.DeviceStorageMonitorService;
import com.android.server.telecom.TelecomLoaderService;
+import com.android.server.testharness.TestHarnessModeService;
import com.android.server.textclassifier.TextClassificationManagerService;
import com.android.server.textservices.TextServicesManagerService;
import com.android.server.trust.TrustManagerService;
@@ -327,6 +328,11 @@
private static native void startHidlServices();
/**
+ * Mark this process' heap as profileable. Only for debug builds.
+ */
+ private static native void initZygoteChildHeapProfiling();
+
+ /**
* The main entry point from zygote.
*/
public static void main(String[] args) {
@@ -448,6 +454,11 @@
// Initialize native services.
System.loadLibrary("android_servers");
+ // Debug builds - allow heap profiling.
+ if (Build.IS_DEBUGGABLE) {
+ initZygoteChildHeapProfiling();
+ }
+
// Check whether we failed to shut down last time we tried.
// This call may not return.
performPendingShutdown();
@@ -862,7 +873,7 @@
TimingsTraceLog traceLog = new TimingsTraceLog(
SYSTEM_SERVER_TIMING_ASYNC_TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
traceLog.traceBegin(SECONDARY_ZYGOTE_PRELOAD);
- if (!Process.zygoteProcess.preloadDefault(Build.SUPPORTED_32_BIT_ABIS[0])) {
+ if (!Process.ZYGOTE_PROCESS.preloadDefault(Build.SUPPORTED_32_BIT_ABIS[0])) {
Slog.e(TAG, "Unable to preload default resources");
}
traceLog.traceEnd();
@@ -1147,6 +1158,10 @@
traceBeginAndSlog("StartPersistentDataBlock");
mSystemServiceManager.startService(PersistentDataBlockService.class);
traceEnd();
+
+ traceBeginAndSlog("StartTestHarnessMode");
+ mSystemServiceManager.startService(TestHarnessModeService.class);
+ traceEnd();
}
if (hasPdb || OemLockService.isHalPresent()) {
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 3b4d6a7..30c7de5 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -3,18 +3,18 @@
srcs: ["java/**/*.java"],
}
-// TODO: move to networking module with DhcpClient and remove lib
-java_library {
- name: "dhcp-packet-lib",
- srcs: [
- "java/android/net/dhcp/*Packet.java",
- ]
-}
-
filegroup {
name: "services-networkstack-shared-srcs",
srcs: [
- "java/android/net/util/FdEventsReader.java", // TODO: move to NetworkStack with IpClient
+ "java/android/net/ip/InterfaceController.java", // TODO: move to NetworkStack with tethering
+ "java/android/net/util/InterfaceParams.java", // TODO: move to NetworkStack with IpServer
"java/android/net/shared/*.java",
+ ],
+}
+
+java_library {
+ name: "services-netlink-lib",
+ srcs: [
+ "java/android/net/netlink/*.java",
]
}
diff --git a/services/net/java/android/net/apf/ApfCapabilities.java b/services/net/java/android/net/apf/ApfCapabilities.java
deleted file mode 100644
index dec8ca2..0000000
--- a/services/net/java/android/net/apf/ApfCapabilities.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2016 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.apf;
-
-/**
- * APF program support capabilities.
- *
- * @hide
- */
-public class ApfCapabilities {
- /**
- * Version of APF instruction set supported for packet filtering. 0 indicates no support for
- * packet filtering using APF programs.
- */
- public final int apfVersionSupported;
-
- /**
- * Maximum size of APF program allowed.
- */
- public final int maximumApfProgramSize;
-
- /**
- * Format of packets passed to APF filter. Should be one of ARPHRD_*
- */
- public final int apfPacketFormat;
-
- public ApfCapabilities(int apfVersionSupported, int maximumApfProgramSize, int apfPacketFormat)
- {
- this.apfVersionSupported = apfVersionSupported;
- this.maximumApfProgramSize = maximumApfProgramSize;
- this.apfPacketFormat = apfPacketFormat;
- }
-
- public String toString() {
- return String.format("%s{version: %d, maxSize: %d, format: %d}", getClass().getSimpleName(),
- apfVersionSupported, maximumApfProgramSize, apfPacketFormat);
- }
-
- /**
- * Returns true if the APF interpreter advertises support for the data buffer access opcodes
- * LDDW and STDW.
- *
- * Full LDDW and STDW support is present from APFv4 on.
- */
- public boolean hasDataAccess() {
- return apfVersionSupported >= 4;
- }
-}
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
deleted file mode 100644
index f037905..0000000
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ /dev/null
@@ -1,1561 +0,0 @@
-/*
- * Copyright (C) 2016 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.apf;
-
-import static android.net.util.NetworkConstants.*;
-import static android.system.OsConstants.*;
-import static com.android.internal.util.BitUtils.bytesToBEInt;
-import static com.android.internal.util.BitUtils.getUint16;
-import static com.android.internal.util.BitUtils.getUint32;
-import static com.android.internal.util.BitUtils.getUint8;
-import static com.android.internal.util.BitUtils.uint32;
-
-import android.annotation.Nullable;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.LinkAddress;
-import android.net.LinkProperties;
-import android.net.NetworkUtils;
-import android.net.apf.ApfGenerator.IllegalInstructionException;
-import android.net.apf.ApfGenerator.Register;
-import android.net.ip.IpClient;
-import android.net.metrics.ApfProgramEvent;
-import android.net.metrics.ApfStats;
-import android.net.metrics.IpConnectivityLog;
-import android.net.metrics.RaEvent;
-import android.net.util.InterfaceParams;
-import android.os.PowerManager;
-import android.os.SystemClock;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.PacketSocketAddress;
-import android.text.format.DateUtils;
-import android.util.Log;
-import android.util.Pair;
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.HexDump;
-import com.android.internal.util.IndentingPrintWriter;
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.SocketException;
-import java.net.UnknownHostException;
-import java.nio.BufferUnderflowException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import libcore.io.IoBridge;
-
-/**
- * For networks that support packet filtering via APF programs, {@code ApfFilter}
- * listens for IPv6 ICMPv6 router advertisements (RAs) and generates APF programs to
- * filter out redundant duplicate ones.
- *
- * Threading model:
- * A collection of RAs we've received is kept in mRas. Generating APF programs uses mRas to
- * know what RAs to filter for, thus generating APF programs is dependent on mRas.
- * mRas can be accessed by multiple threads:
- * - ReceiveThread, which listens for RAs and adds them to mRas, and generates APF programs.
- * - callers of:
- * - setMulticastFilter(), which can cause an APF program to be generated.
- * - dump(), which dumps mRas among other things.
- * - shutdown(), which clears mRas.
- * So access to mRas is synchronized.
- *
- * @hide
- */
-public class ApfFilter {
-
- // Helper class for specifying functional filter parameters.
- public static class ApfConfiguration {
- public ApfCapabilities apfCapabilities;
- public boolean multicastFilter;
- public boolean ieee802_3Filter;
- public int[] ethTypeBlackList;
- }
-
- // Enums describing the outcome of receiving an RA packet.
- private static enum ProcessRaResult {
- MATCH, // Received RA matched a known RA
- DROPPED, // Received RA ignored due to MAX_RAS
- PARSE_ERROR, // Received RA could not be parsed
- ZERO_LIFETIME, // Received RA had 0 lifetime
- UPDATE_NEW_RA, // APF program updated for new RA
- UPDATE_EXPIRY // APF program updated for expiry
- }
-
- /**
- * APF packet counters.
- *
- * Packet counters are 32bit big-endian values, and allocated near the end of the APF data
- * buffer, using negative byte offsets, where -4 is equivalent to maximumApfProgramSize - 4,
- * the last writable 32bit word.
- */
- @VisibleForTesting
- public static enum Counter {
- RESERVED_OOB, // Points to offset 0 from the end of the buffer (out-of-bounds)
- TOTAL_PACKETS,
- PASSED_ARP,
- PASSED_DHCP,
- PASSED_IPV4,
- PASSED_IPV6_NON_ICMP,
- PASSED_IPV4_UNICAST,
- PASSED_IPV6_ICMP,
- PASSED_IPV6_UNICAST_NON_ICMP,
- PASSED_ARP_NON_IPV4,
- PASSED_ARP_UNKNOWN,
- PASSED_ARP_UNICAST_REPLY,
- PASSED_NON_IP_UNICAST,
- DROPPED_ETH_BROADCAST,
- DROPPED_RA,
- DROPPED_GARP_REPLY,
- DROPPED_ARP_OTHER_HOST,
- DROPPED_IPV4_L2_BROADCAST,
- DROPPED_IPV4_BROADCAST_ADDR,
- DROPPED_IPV4_BROADCAST_NET,
- DROPPED_IPV4_MULTICAST,
- DROPPED_IPV6_ROUTER_SOLICITATION,
- DROPPED_IPV6_MULTICAST_NA,
- DROPPED_IPV6_MULTICAST,
- DROPPED_IPV6_MULTICAST_PING,
- DROPPED_IPV6_NON_ICMP_MULTICAST,
- DROPPED_802_3_FRAME,
- DROPPED_ETHERTYPE_BLACKLISTED,
- DROPPED_ARP_REPLY_SPA_NO_HOST;
-
- // Returns the negative byte offset from the end of the APF data segment for
- // a given counter.
- public int offset() {
- return - this.ordinal() * 4; // Currently, all counters are 32bit long.
- }
-
- // Returns the total size of the data segment in bytes.
- public static int totalSize() {
- return (Counter.class.getEnumConstants().length - 1) * 4;
- }
- }
-
- /**
- * When APFv4 is supported, loads R1 with the offset of the specified counter.
- */
- private void maybeSetupCounter(ApfGenerator gen, Counter c) {
- if (mApfCapabilities.hasDataAccess()) {
- gen.addLoadImmediate(Register.R1, c.offset());
- }
- }
-
- // When APFv4 is supported, these point to the trampolines generated by emitEpilogue().
- // Otherwise, they're just aliases for PASS_LABEL and DROP_LABEL.
- private final String mCountAndPassLabel;
- private final String mCountAndDropLabel;
-
- // Thread to listen for RAs.
- @VisibleForTesting
- class ReceiveThread extends Thread {
- private final byte[] mPacket = new byte[1514];
- private final FileDescriptor mSocket;
- private final long mStart = SystemClock.elapsedRealtime();
- private final ApfStats mStats = new ApfStats();
-
- private volatile boolean mStopped;
-
- public ReceiveThread(FileDescriptor socket) {
- mSocket = socket;
- }
-
- public void halt() {
- mStopped = true;
- try {
- // Interrupts the read() call the thread is blocked in.
- IoBridge.closeAndSignalBlockedThreads(mSocket);
- } catch (IOException ignored) {}
- }
-
- @Override
- public void run() {
- log("begin monitoring");
- while (!mStopped) {
- try {
- int length = Os.read(mSocket, mPacket, 0, mPacket.length);
- updateStats(processRa(mPacket, length));
- } catch (IOException|ErrnoException e) {
- if (!mStopped) {
- Log.e(TAG, "Read error", e);
- }
- }
- }
- logStats();
- }
-
- private void updateStats(ProcessRaResult result) {
- mStats.receivedRas++;
- switch(result) {
- case MATCH:
- mStats.matchingRas++;
- return;
- case DROPPED:
- mStats.droppedRas++;
- return;
- case PARSE_ERROR:
- mStats.parseErrors++;
- return;
- case ZERO_LIFETIME:
- mStats.zeroLifetimeRas++;
- return;
- case UPDATE_EXPIRY:
- mStats.matchingRas++;
- mStats.programUpdates++;
- return;
- case UPDATE_NEW_RA:
- mStats.programUpdates++;
- return;
- }
- }
-
- private void logStats() {
- final long nowMs = SystemClock.elapsedRealtime();
- synchronized (this) {
- mStats.durationMs = nowMs - mStart;
- mStats.maxProgramSize = mApfCapabilities.maximumApfProgramSize;
- mStats.programUpdatesAll = mNumProgramUpdates;
- mStats.programUpdatesAllowingMulticast = mNumProgramUpdatesAllowingMulticast;
- mMetricsLog.log(mStats);
- logApfProgramEventLocked(nowMs / DateUtils.SECOND_IN_MILLIS);
- }
- }
- }
-
- private static final String TAG = "ApfFilter";
- private static final boolean DBG = true;
- private static final boolean VDBG = false;
-
- private static final int ETH_HEADER_LEN = 14;
- private static final int ETH_DEST_ADDR_OFFSET = 0;
- private static final int ETH_ETHERTYPE_OFFSET = 12;
- private static final int ETH_TYPE_MIN = 0x0600;
- private static final int ETH_TYPE_MAX = 0xFFFF;
- private static final byte[] ETH_BROADCAST_MAC_ADDRESS =
- {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
- // TODO: Make these offsets relative to end of link-layer header; don't include ETH_HEADER_LEN.
- private static final int IPV4_FRAGMENT_OFFSET_OFFSET = ETH_HEADER_LEN + 6;
- // Endianness is not an issue for this constant because the APF interpreter always operates in
- // network byte order.
- private static final int IPV4_FRAGMENT_OFFSET_MASK = 0x1fff;
- private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9;
- private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16;
- private static final int IPV4_ANY_HOST_ADDRESS = 0;
- private static final int IPV4_BROADCAST_ADDRESS = -1; // 255.255.255.255
-
- // Traffic class and Flow label are not byte aligned. Luckily we
- // don't care about either value so we'll consider bytes 1-3 of the
- // IPv6 header as don't care.
- private static final int IPV6_FLOW_LABEL_OFFSET = ETH_HEADER_LEN + 1;
- private static final int IPV6_FLOW_LABEL_LEN = 3;
- private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6;
- private static final int IPV6_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 8;
- private static final int IPV6_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 24;
- private static final int IPV6_HEADER_LEN = 40;
- // The IPv6 all nodes address ff02::1
- private static final byte[] IPV6_ALL_NODES_ADDRESS =
- { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
-
- private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN;
-
- // NOTE: this must be added to the IPv4 header length in IPV4_HEADER_SIZE_MEMORY_SLOT
- private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 2;
- private static final int UDP_HEADER_LEN = 8;
-
- private static final int DHCP_CLIENT_PORT = 68;
- // NOTE: this must be added to the IPv4 header length in IPV4_HEADER_SIZE_MEMORY_SLOT
- private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 28;
-
- private static final int ARP_HEADER_OFFSET = ETH_HEADER_LEN;
- private static final byte[] ARP_IPV4_HEADER = {
- 0, 1, // Hardware type: Ethernet (1)
- 8, 0, // Protocol type: IP (0x0800)
- 6, // Hardware size: 6
- 4, // Protocol size: 4
- };
- private static final int ARP_OPCODE_OFFSET = ARP_HEADER_OFFSET + 6;
- // Opcode: ARP request (0x0001), ARP reply (0x0002)
- private static final short ARP_OPCODE_REQUEST = 1;
- private static final short ARP_OPCODE_REPLY = 2;
- private static final int ARP_SOURCE_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 14;
- private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 24;
- // Do not log ApfProgramEvents whose actual lifetimes was less than this.
- private static final int APF_PROGRAM_EVENT_LIFETIME_THRESHOLD = 2;
- // Limit on the Black List size to cap on program usage for this
- // TODO: Select a proper max length
- private static final int APF_MAX_ETH_TYPE_BLACK_LIST_LEN = 20;
-
- private final ApfCapabilities mApfCapabilities;
- private final IpClient.Callback mIpClientCallback;
- private final InterfaceParams mInterfaceParams;
- private final IpConnectivityLog mMetricsLog;
-
- @VisibleForTesting
- byte[] mHardwareAddress;
- @VisibleForTesting
- ReceiveThread mReceiveThread;
- @GuardedBy("this")
- private long mUniqueCounter;
- @GuardedBy("this")
- private boolean mMulticastFilter;
- @GuardedBy("this")
- private boolean mInDozeMode;
- private final boolean mDrop802_3Frames;
- private final int[] mEthTypeBlackList;
-
- // Detects doze mode state transitions.
- private final BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (action.equals(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)) {
- PowerManager powerManager =
- (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- final boolean deviceIdle = powerManager.isDeviceIdleMode();
- setDozeMode(deviceIdle);
- }
- }
- };
- private final Context mContext;
-
- // Our IPv4 address, if we have just one, otherwise null.
- @GuardedBy("this")
- private byte[] mIPv4Address;
- // The subnet prefix length of our IPv4 network. Only valid if mIPv4Address is not null.
- @GuardedBy("this")
- private int mIPv4PrefixLength;
-
- @VisibleForTesting
- ApfFilter(Context context, ApfConfiguration config, InterfaceParams ifParams,
- IpClient.Callback ipClientCallback, IpConnectivityLog log) {
- mApfCapabilities = config.apfCapabilities;
- mIpClientCallback = ipClientCallback;
- mInterfaceParams = ifParams;
- mMulticastFilter = config.multicastFilter;
- mDrop802_3Frames = config.ieee802_3Filter;
- mContext = context;
-
- if (mApfCapabilities.hasDataAccess()) {
- mCountAndPassLabel = "countAndPass";
- mCountAndDropLabel = "countAndDrop";
- } else {
- // APFv4 unsupported: turn jumps to the counter trampolines to immediately PASS or DROP,
- // preserving the original pre-APFv4 behavior.
- mCountAndPassLabel = ApfGenerator.PASS_LABEL;
- mCountAndDropLabel = ApfGenerator.DROP_LABEL;
- }
-
- // Now fill the black list from the passed array
- mEthTypeBlackList = filterEthTypeBlackList(config.ethTypeBlackList);
-
- mMetricsLog = log;
-
- // TODO: ApfFilter should not generate programs until IpClient sends provisioning success.
- maybeStartFilter();
-
- // Listen for doze-mode transition changes to enable/disable the IPv6 multicast filter.
- mContext.registerReceiver(mDeviceIdleReceiver,
- new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED));
- }
-
- public synchronized void setDataSnapshot(byte[] data) {
- mDataSnapshot = data;
- }
-
- private void log(String s) {
- Log.d(TAG, "(" + mInterfaceParams.name + "): " + s);
- }
-
- @GuardedBy("this")
- private long getUniqueNumberLocked() {
- return mUniqueCounter++;
- }
-
- @GuardedBy("this")
- private static int[] filterEthTypeBlackList(int[] ethTypeBlackList) {
- ArrayList<Integer> bl = new ArrayList<Integer>();
-
- for (int p : ethTypeBlackList) {
- // Check if the protocol is a valid ether type
- if ((p < ETH_TYPE_MIN) || (p > ETH_TYPE_MAX)) {
- continue;
- }
-
- // Check if the protocol is not repeated in the passed array
- if (bl.contains(p)) {
- continue;
- }
-
- // Check if list reach its max size
- if (bl.size() == APF_MAX_ETH_TYPE_BLACK_LIST_LEN) {
- Log.w(TAG, "Passed EthType Black List size too large (" + bl.size() +
- ") using top " + APF_MAX_ETH_TYPE_BLACK_LIST_LEN + " protocols");
- break;
- }
-
- // Now add the protocol to the list
- bl.add(p);
- }
-
- return bl.stream().mapToInt(Integer::intValue).toArray();
- }
-
- /**
- * Attempt to start listening for RAs and, if RAs are received, generating and installing
- * filters to ignore useless RAs.
- */
- @VisibleForTesting
- void maybeStartFilter() {
- FileDescriptor socket;
- try {
- mHardwareAddress = mInterfaceParams.macAddr.toByteArray();
- synchronized(this) {
- // Clear the APF memory to reset all counters upon connecting to the first AP
- // in an SSID. This is limited to APFv4 devices because this large write triggers
- // a crash on some older devices (b/78905546).
- if (mApfCapabilities.hasDataAccess()) {
- byte[] zeroes = new byte[mApfCapabilities.maximumApfProgramSize];
- mIpClientCallback.installPacketFilter(zeroes);
- }
-
- // Install basic filters
- installNewProgramLocked();
- }
- socket = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IPV6);
- PacketSocketAddress addr = new PacketSocketAddress(
- (short) ETH_P_IPV6, mInterfaceParams.index);
- Os.bind(socket, addr);
- NetworkUtils.attachRaFilter(socket, mApfCapabilities.apfPacketFormat);
- } catch(SocketException|ErrnoException e) {
- Log.e(TAG, "Error starting filter", e);
- return;
- }
- mReceiveThread = new ReceiveThread(socket);
- mReceiveThread.start();
- }
-
- // Returns seconds since device boot.
- @VisibleForTesting
- protected long currentTimeSeconds() {
- return SystemClock.elapsedRealtime() / DateUtils.SECOND_IN_MILLIS;
- }
-
- public static class InvalidRaException extends Exception {
- public InvalidRaException(String m) {
- super(m);
- }
- }
-
- // A class to hold information about an RA.
- @VisibleForTesting
- class Ra {
- // From RFC4861:
- private static final int ICMP6_RA_HEADER_LEN = 16;
- private static final int ICMP6_RA_CHECKSUM_OFFSET =
- ETH_HEADER_LEN + IPV6_HEADER_LEN + 2;
- private static final int ICMP6_RA_CHECKSUM_LEN = 2;
- private static final int ICMP6_RA_OPTION_OFFSET =
- ETH_HEADER_LEN + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN;
- private static final int ICMP6_RA_ROUTER_LIFETIME_OFFSET =
- ETH_HEADER_LEN + IPV6_HEADER_LEN + 6;
- private static final int ICMP6_RA_ROUTER_LIFETIME_LEN = 2;
- // Prefix information option.
- private static final int ICMP6_PREFIX_OPTION_TYPE = 3;
- private static final int ICMP6_PREFIX_OPTION_LEN = 32;
- private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET = 4;
- private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN = 4;
- private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET = 8;
- private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN = 4;
-
- // From RFC6106: Recursive DNS Server option
- private static final int ICMP6_RDNSS_OPTION_TYPE = 25;
- // From RFC6106: DNS Search List option
- private static final int ICMP6_DNSSL_OPTION_TYPE = 31;
-
- // From RFC4191: Route Information option
- private static final int ICMP6_ROUTE_INFO_OPTION_TYPE = 24;
- // Above three options all have the same format:
- private static final int ICMP6_4_BYTE_LIFETIME_OFFSET = 4;
- private static final int ICMP6_4_BYTE_LIFETIME_LEN = 4;
-
- // Note: mPacket's position() cannot be assumed to be reset.
- private final ByteBuffer mPacket;
- // List of binary ranges that include the whole packet except the lifetimes.
- // Pairs consist of offset and length.
- private final ArrayList<Pair<Integer, Integer>> mNonLifetimes =
- new ArrayList<Pair<Integer, Integer>>();
- // Minimum lifetime in packet
- long mMinLifetime;
- // When the packet was last captured, in seconds since Unix Epoch
- long mLastSeen;
-
- // For debugging only. Offsets into the packet where PIOs are.
- private final ArrayList<Integer> mPrefixOptionOffsets = new ArrayList<>();
-
- // For debugging only. Offsets into the packet where RDNSS options are.
- private final ArrayList<Integer> mRdnssOptionOffsets = new ArrayList<>();
-
- // For debugging only. How many times this RA was seen.
- int seenCount = 0;
-
- // For debugging only. Returns the hex representation of the last matching packet.
- String getLastMatchingPacket() {
- return HexDump.toHexString(mPacket.array(), 0, mPacket.capacity(),
- false /* lowercase */);
- }
-
- // For debugging only. Returns the string representation of the IPv6 address starting at
- // position pos in the packet.
- private String IPv6AddresstoString(int pos) {
- try {
- byte[] array = mPacket.array();
- // Can't just call copyOfRange() and see if it throws, because if it reads past the
- // end it pads with zeros instead of throwing.
- if (pos < 0 || pos + 16 > array.length || pos + 16 < pos) {
- return "???";
- }
- byte[] addressBytes = Arrays.copyOfRange(array, pos, pos + 16);
- InetAddress address = (Inet6Address) InetAddress.getByAddress(addressBytes);
- return address.getHostAddress();
- } catch (UnsupportedOperationException e) {
- // array() failed. Cannot happen, mPacket is array-backed and read-write.
- return "???";
- } catch (ClassCastException|UnknownHostException e) {
- // Cannot happen.
- return "???";
- }
- }
-
- // Can't be static because it's in a non-static inner class.
- // TODO: Make this static once RA is its own class.
- private void prefixOptionToString(StringBuffer sb, int offset) {
- String prefix = IPv6AddresstoString(offset + 16);
- int length = getUint8(mPacket, offset + 2);
- long valid = getUint32(mPacket, offset + 4);
- long preferred = getUint32(mPacket, offset + 8);
- sb.append(String.format("%s/%d %ds/%ds ", prefix, length, valid, preferred));
- }
-
- private void rdnssOptionToString(StringBuffer sb, int offset) {
- int optLen = getUint8(mPacket, offset + 1) * 8;
- if (optLen < 24) return; // Malformed or empty.
- long lifetime = getUint32(mPacket, offset + 4);
- int numServers = (optLen - 8) / 16;
- sb.append("DNS ").append(lifetime).append("s");
- for (int server = 0; server < numServers; server++) {
- sb.append(" ").append(IPv6AddresstoString(offset + 8 + 16 * server));
- }
- }
-
- public String toString() {
- try {
- StringBuffer sb = new StringBuffer();
- sb.append(String.format("RA %s -> %s %ds ",
- IPv6AddresstoString(IPV6_SRC_ADDR_OFFSET),
- IPv6AddresstoString(IPV6_DEST_ADDR_OFFSET),
- getUint16(mPacket, ICMP6_RA_ROUTER_LIFETIME_OFFSET)));
- for (int i: mPrefixOptionOffsets) {
- prefixOptionToString(sb, i);
- }
- for (int i: mRdnssOptionOffsets) {
- rdnssOptionToString(sb, i);
- }
- return sb.toString();
- } catch (BufferUnderflowException|IndexOutOfBoundsException e) {
- return "<Malformed RA>";
- }
- }
-
- /**
- * Add a binary range of the packet that does not include a lifetime to mNonLifetimes.
- * Assumes mPacket.position() is as far as we've parsed the packet.
- * @param lastNonLifetimeStart offset within packet of where the last binary range of
- * data not including a lifetime.
- * @param lifetimeOffset offset from mPacket.position() to the next lifetime data.
- * @param lifetimeLength length of the next lifetime data.
- * @return offset within packet of where the next binary range of data not including
- * a lifetime. This can be passed into the next invocation of this function
- * via {@code lastNonLifetimeStart}.
- */
- private int addNonLifetime(int lastNonLifetimeStart, int lifetimeOffset,
- int lifetimeLength) {
- lifetimeOffset += mPacket.position();
- mNonLifetimes.add(new Pair<Integer, Integer>(lastNonLifetimeStart,
- lifetimeOffset - lastNonLifetimeStart));
- return lifetimeOffset + lifetimeLength;
- }
-
- private int addNonLifetimeU32(int lastNonLifetimeStart) {
- return addNonLifetime(lastNonLifetimeStart,
- ICMP6_4_BYTE_LIFETIME_OFFSET, ICMP6_4_BYTE_LIFETIME_LEN);
- }
-
- // Note that this parses RA and may throw InvalidRaException (from
- // Buffer.position(int) or due to an invalid-length option) or IndexOutOfBoundsException
- // (from ByteBuffer.get(int) ) if parsing encounters something non-compliant with
- // specifications.
- Ra(byte[] packet, int length) throws InvalidRaException {
- if (length < ICMP6_RA_OPTION_OFFSET) {
- throw new InvalidRaException("Not an ICMP6 router advertisement");
- }
-
- mPacket = ByteBuffer.wrap(Arrays.copyOf(packet, length));
- mLastSeen = currentTimeSeconds();
-
- // Sanity check packet in case a packet arrives before we attach RA filter
- // to our packet socket. b/29586253
- if (getUint16(mPacket, ETH_ETHERTYPE_OFFSET) != ETH_P_IPV6 ||
- getUint8(mPacket, IPV6_NEXT_HEADER_OFFSET) != IPPROTO_ICMPV6 ||
- getUint8(mPacket, ICMP6_TYPE_OFFSET) != ICMPV6_ROUTER_ADVERTISEMENT) {
- throw new InvalidRaException("Not an ICMP6 router advertisement");
- }
-
-
- RaEvent.Builder builder = new RaEvent.Builder();
-
- // Ignore the flow label and low 4 bits of traffic class.
- int lastNonLifetimeStart = addNonLifetime(0,
- IPV6_FLOW_LABEL_OFFSET,
- IPV6_FLOW_LABEL_LEN);
-
- // Ignore the checksum.
- lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart,
- ICMP6_RA_CHECKSUM_OFFSET,
- ICMP6_RA_CHECKSUM_LEN);
-
- // Parse router lifetime
- lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart,
- ICMP6_RA_ROUTER_LIFETIME_OFFSET,
- ICMP6_RA_ROUTER_LIFETIME_LEN);
- builder.updateRouterLifetime(getUint16(mPacket, ICMP6_RA_ROUTER_LIFETIME_OFFSET));
-
- // Ensures that the RA is not truncated.
- mPacket.position(ICMP6_RA_OPTION_OFFSET);
- while (mPacket.hasRemaining()) {
- final int position = mPacket.position();
- final int optionType = getUint8(mPacket, position);
- final int optionLength = getUint8(mPacket, position + 1) * 8;
- long lifetime;
- switch (optionType) {
- case ICMP6_PREFIX_OPTION_TYPE:
- // Parse valid lifetime
- lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart,
- ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET,
- ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN);
- lifetime = getUint32(mPacket,
- position + ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET);
- builder.updatePrefixValidLifetime(lifetime);
- // Parse preferred lifetime
- lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart,
- ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET,
- ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN);
- lifetime = getUint32(mPacket,
- position + ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET);
- builder.updatePrefixPreferredLifetime(lifetime);
- mPrefixOptionOffsets.add(position);
- break;
- // These three options have the same lifetime offset and size, and
- // are processed with the same specialized addNonLifetimeU32:
- case ICMP6_RDNSS_OPTION_TYPE:
- mRdnssOptionOffsets.add(position);
- lastNonLifetimeStart = addNonLifetimeU32(lastNonLifetimeStart);
- lifetime = getUint32(mPacket, position + ICMP6_4_BYTE_LIFETIME_OFFSET);
- builder.updateRdnssLifetime(lifetime);
- break;
- case ICMP6_ROUTE_INFO_OPTION_TYPE:
- lastNonLifetimeStart = addNonLifetimeU32(lastNonLifetimeStart);
- lifetime = getUint32(mPacket, position + ICMP6_4_BYTE_LIFETIME_OFFSET);
- builder.updateRouteInfoLifetime(lifetime);
- break;
- case ICMP6_DNSSL_OPTION_TYPE:
- lastNonLifetimeStart = addNonLifetimeU32(lastNonLifetimeStart);
- lifetime = getUint32(mPacket, position + ICMP6_4_BYTE_LIFETIME_OFFSET);
- builder.updateDnsslLifetime(lifetime);
- break;
- default:
- // RFC4861 section 4.2 dictates we ignore unknown options for fowards
- // compatibility.
- break;
- }
- if (optionLength <= 0) {
- throw new InvalidRaException(String.format(
- "Invalid option length opt=%d len=%d", optionType, optionLength));
- }
- mPacket.position(position + optionLength);
- }
- // Mark non-lifetime bytes since last lifetime.
- addNonLifetime(lastNonLifetimeStart, 0, 0);
- mMinLifetime = minLifetime(packet, length);
- mMetricsLog.log(builder.build());
- }
-
- // Ignoring lifetimes (which may change) does {@code packet} match this RA?
- boolean matches(byte[] packet, int length) {
- if (length != mPacket.capacity()) return false;
- byte[] referencePacket = mPacket.array();
- for (Pair<Integer, Integer> nonLifetime : mNonLifetimes) {
- for (int i = nonLifetime.first; i < (nonLifetime.first + nonLifetime.second); i++) {
- if (packet[i] != referencePacket[i]) return false;
- }
- }
- return true;
- }
-
- // What is the minimum of all lifetimes within {@code packet} in seconds?
- // Precondition: matches(packet, length) already returned true.
- long minLifetime(byte[] packet, int length) {
- long minLifetime = Long.MAX_VALUE;
- // Wrap packet in ByteBuffer so we can read big-endian values easily
- ByteBuffer byteBuffer = ByteBuffer.wrap(packet);
- for (int i = 0; (i + 1) < mNonLifetimes.size(); i++) {
- int offset = mNonLifetimes.get(i).first + mNonLifetimes.get(i).second;
-
- // The flow label is in mNonLifetimes, but it's not a lifetime.
- if (offset == IPV6_FLOW_LABEL_OFFSET) {
- continue;
- }
-
- // The checksum is in mNonLifetimes, but it's not a lifetime.
- if (offset == ICMP6_RA_CHECKSUM_OFFSET) {
- continue;
- }
-
- final int lifetimeLength = mNonLifetimes.get(i+1).first - offset;
- final long optionLifetime;
- switch (lifetimeLength) {
- case 2:
- optionLifetime = getUint16(byteBuffer, offset);
- break;
- case 4:
- optionLifetime = getUint32(byteBuffer, offset);
- break;
- default:
- throw new IllegalStateException("bogus lifetime size " + lifetimeLength);
- }
- minLifetime = Math.min(minLifetime, optionLifetime);
- }
- return minLifetime;
- }
-
- // How many seconds does this RA's have to live, taking into account the fact
- // that we might have seen it a while ago.
- long currentLifetime() {
- return mMinLifetime - (currentTimeSeconds() - mLastSeen);
- }
-
- boolean isExpired() {
- // TODO: We may want to handle 0 lifetime RAs differently, if they are common. We'll
- // have to calculte the filter lifetime specially as a fraction of 0 is still 0.
- return currentLifetime() <= 0;
- }
-
- // Append a filter for this RA to {@code gen}. Jump to DROP_LABEL if it should be dropped.
- // Jump to the next filter if packet doesn't match this RA.
- @GuardedBy("ApfFilter.this")
- long generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException {
- String nextFilterLabel = "Ra" + getUniqueNumberLocked();
- // Skip if packet is not the right size
- gen.addLoadFromMemory(Register.R0, gen.PACKET_SIZE_MEMORY_SLOT);
- gen.addJumpIfR0NotEquals(mPacket.capacity(), nextFilterLabel);
- int filterLifetime = (int)(currentLifetime() / FRACTION_OF_LIFETIME_TO_FILTER);
- // Skip filter if expired
- gen.addLoadFromMemory(Register.R0, gen.FILTER_AGE_MEMORY_SLOT);
- gen.addJumpIfR0GreaterThan(filterLifetime, nextFilterLabel);
- for (int i = 0; i < mNonLifetimes.size(); i++) {
- // Generate code to match the packet bytes
- Pair<Integer, Integer> nonLifetime = mNonLifetimes.get(i);
- // Don't generate JNEBS instruction for 0 bytes as it always fails the
- // ASSERT_FORWARD_IN_PROGRAM(pc + cmp_imm - 1) check where cmp_imm is
- // the number of bytes to compare. nonLifetime is zero between the
- // valid and preferred lifetimes in the prefix option.
- if (nonLifetime.second != 0) {
- gen.addLoadImmediate(Register.R0, nonLifetime.first);
- gen.addJumpIfBytesNotEqual(Register.R0,
- Arrays.copyOfRange(mPacket.array(), nonLifetime.first,
- nonLifetime.first + nonLifetime.second),
- nextFilterLabel);
- }
- // Generate code to test the lifetimes haven't gone down too far
- if ((i + 1) < mNonLifetimes.size()) {
- Pair<Integer, Integer> nextNonLifetime = mNonLifetimes.get(i + 1);
- int offset = nonLifetime.first + nonLifetime.second;
-
- // Skip the Flow label.
- if (offset == IPV6_FLOW_LABEL_OFFSET) {
- continue;
- }
- // Skip the checksum.
- if (offset == ICMP6_RA_CHECKSUM_OFFSET) {
- continue;
- }
- int length = nextNonLifetime.first - offset;
- switch (length) {
- case 4: gen.addLoad32(Register.R0, offset); break;
- case 2: gen.addLoad16(Register.R0, offset); break;
- default: throw new IllegalStateException("bogus lifetime size " + length);
- }
- gen.addJumpIfR0LessThan(filterLifetime, nextFilterLabel);
- }
- }
- maybeSetupCounter(gen, Counter.DROPPED_RA);
- gen.addJump(mCountAndDropLabel);
- gen.defineLabel(nextFilterLabel);
- return filterLifetime;
- }
- }
-
- // Maximum number of RAs to filter for.
- private static final int MAX_RAS = 10;
-
- @GuardedBy("this")
- private ArrayList<Ra> mRas = new ArrayList<Ra>();
-
- // There is always some marginal benefit to updating the installed APF program when an RA is
- // seen because we can extend the program's lifetime slightly, but there is some cost to
- // updating the program, so don't bother unless the program is going to expire soon. This
- // constant defines "soon" in seconds.
- private static final long MAX_PROGRAM_LIFETIME_WORTH_REFRESHING = 30;
- // We don't want to filter an RA for it's whole lifetime as it'll be expired by the time we ever
- // see a refresh. Using half the lifetime might be a good idea except for the fact that
- // packets may be dropped, so let's use 6.
- private static final int FRACTION_OF_LIFETIME_TO_FILTER = 6;
-
- // When did we last install a filter program? In seconds since Unix Epoch.
- @GuardedBy("this")
- private long mLastTimeInstalledProgram;
- // How long should the last installed filter program live for? In seconds.
- @GuardedBy("this")
- private long mLastInstalledProgramMinLifetime;
- @GuardedBy("this")
- private ApfProgramEvent mLastInstallEvent;
-
- // For debugging only. The last program installed.
- @GuardedBy("this")
- private byte[] mLastInstalledProgram;
-
- /**
- * For debugging only. Contains the latest APF buffer snapshot captured from the firmware.
- *
- * A typical size for this buffer is 4KB. It is present only if the WiFi HAL supports
- * IWifiStaIface#readApfPacketFilterData(), and the APF interpreter advertised support for
- * the opcodes to access the data buffer (LDDW and STDW).
- */
- @GuardedBy("this") @Nullable
- private byte[] mDataSnapshot;
-
- // How many times the program was updated since we started.
- @GuardedBy("this")
- private int mNumProgramUpdates = 0;
- // How many times the program was updated since we started for allowing multicast traffic.
- @GuardedBy("this")
- private int mNumProgramUpdatesAllowingMulticast = 0;
-
- /**
- * Generate filter code to process ARP packets. Execution of this code ends in either the
- * DROP_LABEL or PASS_LABEL and does not fall off the end.
- * Preconditions:
- * - Packet being filtered is ARP
- */
- @GuardedBy("this")
- private void generateArpFilterLocked(ApfGenerator gen) throws IllegalInstructionException {
- // Here's a basic summary of what the ARP filter program does:
- //
- // if not ARP IPv4
- // pass
- // if not ARP IPv4 reply or request
- // pass
- // if ARP reply source ip is 0.0.0.0
- // drop
- // if unicast ARP reply
- // pass
- // if interface has no IPv4 address
- // if target ip is 0.0.0.0
- // drop
- // else
- // if target ip is not the interface ip
- // drop
- // pass
-
- final String checkTargetIPv4 = "checkTargetIPv4";
-
- // Pass if not ARP IPv4.
- gen.addLoadImmediate(Register.R0, ARP_HEADER_OFFSET);
- maybeSetupCounter(gen, Counter.PASSED_ARP_NON_IPV4);
- gen.addJumpIfBytesNotEqual(Register.R0, ARP_IPV4_HEADER, mCountAndPassLabel);
-
- // Pass if unknown ARP opcode.
- gen.addLoad16(Register.R0, ARP_OPCODE_OFFSET);
- gen.addJumpIfR0Equals(ARP_OPCODE_REQUEST, checkTargetIPv4); // Skip to unicast check
- maybeSetupCounter(gen, Counter.PASSED_ARP_UNKNOWN);
- gen.addJumpIfR0NotEquals(ARP_OPCODE_REPLY, mCountAndPassLabel);
-
- // Drop if ARP reply source IP is 0.0.0.0
- gen.addLoad32(Register.R0, ARP_SOURCE_IP_ADDRESS_OFFSET);
- maybeSetupCounter(gen, Counter.DROPPED_ARP_REPLY_SPA_NO_HOST);
- gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, mCountAndDropLabel);
-
- // Pass if unicast reply.
- gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
- maybeSetupCounter(gen, Counter.PASSED_ARP_UNICAST_REPLY);
- gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, mCountAndPassLabel);
-
- // Either a unicast request, a unicast reply, or a broadcast reply.
- gen.defineLabel(checkTargetIPv4);
- if (mIPv4Address == null) {
- // When there is no IPv4 address, drop GARP replies (b/29404209).
- gen.addLoad32(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET);
- maybeSetupCounter(gen, Counter.DROPPED_GARP_REPLY);
- gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, mCountAndDropLabel);
- } else {
- // When there is an IPv4 address, drop unicast/broadcast requests
- // and broadcast replies with a different target IPv4 address.
- gen.addLoadImmediate(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET);
- maybeSetupCounter(gen, Counter.DROPPED_ARP_OTHER_HOST);
- gen.addJumpIfBytesNotEqual(Register.R0, mIPv4Address, mCountAndDropLabel);
- }
-
- maybeSetupCounter(gen, Counter.PASSED_ARP);
- gen.addJump(mCountAndPassLabel);
- }
-
- /**
- * Generate filter code to process IPv4 packets. Execution of this code ends in either the
- * DROP_LABEL or PASS_LABEL and does not fall off the end.
- * Preconditions:
- * - Packet being filtered is IPv4
- */
- @GuardedBy("this")
- private void generateIPv4FilterLocked(ApfGenerator gen) throws IllegalInstructionException {
- // Here's a basic summary of what the IPv4 filter program does:
- //
- // if filtering multicast (i.e. multicast lock not held):
- // if it's DHCP destined to our MAC:
- // pass
- // if it's L2 broadcast:
- // drop
- // if it's IPv4 multicast:
- // drop
- // if it's IPv4 broadcast:
- // drop
- // pass
-
- if (mMulticastFilter) {
- final String skipDhcpv4Filter = "skip_dhcp_v4_filter";
-
- // Pass DHCP addressed to us.
- // Check it's UDP.
- gen.addLoad8(Register.R0, IPV4_PROTOCOL_OFFSET);
- gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipDhcpv4Filter);
- // Check it's not a fragment. This matches the BPF filter installed by the DHCP client.
- gen.addLoad16(Register.R0, IPV4_FRAGMENT_OFFSET_OFFSET);
- gen.addJumpIfR0AnyBitsSet(IPV4_FRAGMENT_OFFSET_MASK, skipDhcpv4Filter);
- // Check it's addressed to DHCP client port.
- gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
- gen.addLoad16Indexed(Register.R0, UDP_DESTINATION_PORT_OFFSET);
- gen.addJumpIfR0NotEquals(DHCP_CLIENT_PORT, skipDhcpv4Filter);
- // Check it's DHCP to our MAC address.
- gen.addLoadImmediate(Register.R0, DHCP_CLIENT_MAC_OFFSET);
- // NOTE: Relies on R1 containing IPv4 header offset.
- gen.addAddR1();
- gen.addJumpIfBytesNotEqual(Register.R0, mHardwareAddress, skipDhcpv4Filter);
- maybeSetupCounter(gen, Counter.PASSED_DHCP);
- gen.addJump(mCountAndPassLabel);
-
- // Drop all multicasts/broadcasts.
- gen.defineLabel(skipDhcpv4Filter);
-
- // If IPv4 destination address is in multicast range, drop.
- gen.addLoad8(Register.R0, IPV4_DEST_ADDR_OFFSET);
- gen.addAnd(0xf0);
- maybeSetupCounter(gen, Counter.DROPPED_IPV4_MULTICAST);
- gen.addJumpIfR0Equals(0xe0, mCountAndDropLabel);
-
- // If IPv4 broadcast packet, drop regardless of L2 (b/30231088).
- maybeSetupCounter(gen, Counter.DROPPED_IPV4_BROADCAST_ADDR);
- gen.addLoad32(Register.R0, IPV4_DEST_ADDR_OFFSET);
- gen.addJumpIfR0Equals(IPV4_BROADCAST_ADDRESS, mCountAndDropLabel);
- if (mIPv4Address != null && mIPv4PrefixLength < 31) {
- maybeSetupCounter(gen, Counter.DROPPED_IPV4_BROADCAST_NET);
- int broadcastAddr = ipv4BroadcastAddress(mIPv4Address, mIPv4PrefixLength);
- gen.addJumpIfR0Equals(broadcastAddr, mCountAndDropLabel);
- }
-
- // If L2 broadcast packet, drop.
- // TODO: can we invert this condition to fall through to the common pass case below?
- maybeSetupCounter(gen, Counter.PASSED_IPV4_UNICAST);
- gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
- gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, mCountAndPassLabel);
- maybeSetupCounter(gen, Counter.DROPPED_IPV4_L2_BROADCAST);
- gen.addJump(mCountAndDropLabel);
- }
-
- // Otherwise, pass
- maybeSetupCounter(gen, Counter.PASSED_IPV4);
- gen.addJump(mCountAndPassLabel);
- }
-
-
- /**
- * Generate filter code to process IPv6 packets. Execution of this code ends in either the
- * DROP_LABEL or PASS_LABEL, or falls off the end for ICMPv6 packets.
- * Preconditions:
- * - Packet being filtered is IPv6
- */
- @GuardedBy("this")
- private void generateIPv6FilterLocked(ApfGenerator gen) throws IllegalInstructionException {
- // Here's a basic summary of what the IPv6 filter program does:
- //
- // if we're dropping multicast
- // if it's not IPCMv6 or it's ICMPv6 but we're in doze mode:
- // if it's multicast:
- // drop
- // pass
- // if it's ICMPv6 RS to any:
- // drop
- // if it's ICMPv6 NA to ff02::1:
- // drop
-
- gen.addLoad8(Register.R0, IPV6_NEXT_HEADER_OFFSET);
-
- // Drop multicast if the multicast filter is enabled.
- if (mMulticastFilter) {
- final String skipIPv6MulticastFilterLabel = "skipIPv6MulticastFilter";
- final String dropAllIPv6MulticastsLabel = "dropAllIPv6Multicast";
-
- // While in doze mode, drop ICMPv6 multicast pings, let the others pass.
- // While awake, let all ICMPv6 multicasts through.
- if (mInDozeMode) {
- // Not ICMPv6? -> Proceed to multicast filtering
- gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6, dropAllIPv6MulticastsLabel);
-
- // ICMPv6 but not ECHO? -> Skip the multicast filter.
- // (ICMPv6 ECHO requests will go through the multicast filter below).
- gen.addLoad8(Register.R0, ICMP6_TYPE_OFFSET);
- gen.addJumpIfR0NotEquals(ICMPV6_ECHO_REQUEST_TYPE, skipIPv6MulticastFilterLabel);
- } else {
- gen.addJumpIfR0Equals(IPPROTO_ICMPV6, skipIPv6MulticastFilterLabel);
- }
-
- // Drop all other packets sent to ff00::/8 (multicast prefix).
- gen.defineLabel(dropAllIPv6MulticastsLabel);
- maybeSetupCounter(gen, Counter.DROPPED_IPV6_NON_ICMP_MULTICAST);
- gen.addLoad8(Register.R0, IPV6_DEST_ADDR_OFFSET);
- gen.addJumpIfR0Equals(0xff, mCountAndDropLabel);
- // Not multicast. Pass.
- maybeSetupCounter(gen, Counter.PASSED_IPV6_UNICAST_NON_ICMP);
- gen.addJump(mCountAndPassLabel);
- gen.defineLabel(skipIPv6MulticastFilterLabel);
- } else {
- // If not ICMPv6, pass.
- maybeSetupCounter(gen, Counter.PASSED_IPV6_NON_ICMP);
- gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6, mCountAndPassLabel);
- }
-
- // If we got this far, the packet is ICMPv6. Drop some specific types.
-
- // Add unsolicited multicast neighbor announcements filter
- String skipUnsolicitedMulticastNALabel = "skipUnsolicitedMulticastNA";
- gen.addLoad8(Register.R0, ICMP6_TYPE_OFFSET);
- // Drop all router solicitations (b/32833400)
- maybeSetupCounter(gen, Counter.DROPPED_IPV6_ROUTER_SOLICITATION);
- gen.addJumpIfR0Equals(ICMPV6_ROUTER_SOLICITATION, mCountAndDropLabel);
- // If not neighbor announcements, skip filter.
- gen.addJumpIfR0NotEquals(ICMPV6_NEIGHBOR_ADVERTISEMENT, skipUnsolicitedMulticastNALabel);
- // If to ff02::1, drop.
- // TODO: Drop only if they don't contain the address of on-link neighbours.
- gen.addLoadImmediate(Register.R0, IPV6_DEST_ADDR_OFFSET);
- gen.addJumpIfBytesNotEqual(Register.R0, IPV6_ALL_NODES_ADDRESS,
- skipUnsolicitedMulticastNALabel);
- maybeSetupCounter(gen, Counter.DROPPED_IPV6_MULTICAST_NA);
- gen.addJump(mCountAndDropLabel);
- gen.defineLabel(skipUnsolicitedMulticastNALabel);
- }
-
- /**
- * Begin generating an APF program to:
- * <ul>
- * <li>Drop/Pass 802.3 frames (based on policy)
- * <li>Drop packets with EtherType within the Black List
- * <li>Drop ARP requests not for us, if mIPv4Address is set,
- * <li>Drop IPv4 broadcast packets, except DHCP destined to our MAC,
- * <li>Drop IPv4 multicast packets, if mMulticastFilter,
- * <li>Pass all other IPv4 packets,
- * <li>Drop all broadcast non-IP non-ARP packets.
- * <li>Pass all non-ICMPv6 IPv6 packets,
- * <li>Pass all non-IPv4 and non-IPv6 packets,
- * <li>Drop IPv6 ICMPv6 NAs to ff02::1.
- * <li>Drop IPv6 ICMPv6 RSs.
- * <li>Let execution continue off the end of the program for IPv6 ICMPv6 packets. This allows
- * insertion of RA filters here, or if there aren't any, just passes the packets.
- * </ul>
- */
- @GuardedBy("this")
- private ApfGenerator emitPrologueLocked() throws IllegalInstructionException {
- // This is guaranteed to succeed because of the check in maybeCreate.
- ApfGenerator gen = new ApfGenerator(mApfCapabilities.apfVersionSupported);
-
- if (mApfCapabilities.hasDataAccess()) {
- // Increment TOTAL_PACKETS
- maybeSetupCounter(gen, Counter.TOTAL_PACKETS);
- gen.addLoadData(Register.R0, 0); // load counter
- gen.addAdd(1);
- gen.addStoreData(Register.R0, 0); // write-back counter
- }
-
- // Here's a basic summary of what the initial program does:
- //
- // if it's a 802.3 Frame (ethtype < 0x0600):
- // drop or pass based on configurations
- // if it has a ether-type that belongs to the black list
- // drop
- // if it's ARP:
- // insert ARP filter to drop or pass these appropriately
- // if it's IPv4:
- // insert IPv4 filter to drop or pass these appropriately
- // if it's not IPv6:
- // if it's broadcast:
- // drop
- // pass
- // insert IPv6 filter to drop, pass, or fall off the end for ICMPv6 packets
-
- gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET);
-
- if (mDrop802_3Frames) {
- // drop 802.3 frames (ethtype < 0x0600)
- maybeSetupCounter(gen, Counter.DROPPED_802_3_FRAME);
- gen.addJumpIfR0LessThan(ETH_TYPE_MIN, mCountAndDropLabel);
- }
-
- // Handle ether-type black list
- maybeSetupCounter(gen, Counter.DROPPED_ETHERTYPE_BLACKLISTED);
- for (int p : mEthTypeBlackList) {
- gen.addJumpIfR0Equals(p, mCountAndDropLabel);
- }
-
- // Add ARP filters:
- String skipArpFiltersLabel = "skipArpFilters";
- gen.addJumpIfR0NotEquals(ETH_P_ARP, skipArpFiltersLabel);
- generateArpFilterLocked(gen);
- gen.defineLabel(skipArpFiltersLabel);
-
- // Add IPv4 filters:
- String skipIPv4FiltersLabel = "skipIPv4Filters";
- // NOTE: Relies on R0 containing ethertype. This is safe because if we got here, we did not
- // execute the ARP filter, since that filter does not fall through, but either drops or
- // passes.
- gen.addJumpIfR0NotEquals(ETH_P_IP, skipIPv4FiltersLabel);
- generateIPv4FilterLocked(gen);
- gen.defineLabel(skipIPv4FiltersLabel);
-
- // Check for IPv6:
- // NOTE: Relies on R0 containing ethertype. This is safe because if we got here, we did not
- // execute the ARP or IPv4 filters, since those filters do not fall through, but either
- // drop or pass.
- String ipv6FilterLabel = "IPv6Filters";
- gen.addJumpIfR0Equals(ETH_P_IPV6, ipv6FilterLabel);
-
- // Drop non-IP non-ARP broadcasts, pass the rest
- gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
- maybeSetupCounter(gen, Counter.PASSED_NON_IP_UNICAST);
- gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, mCountAndPassLabel);
- maybeSetupCounter(gen, Counter.DROPPED_ETH_BROADCAST);
- gen.addJump(mCountAndDropLabel);
-
- // Add IPv6 filters:
- gen.defineLabel(ipv6FilterLabel);
- generateIPv6FilterLocked(gen);
- return gen;
- }
-
- /**
- * Append packet counting epilogue to the APF program.
- *
- * Currently, the epilogue consists of two trampolines which count passed and dropped packets
- * before jumping to the actual PASS and DROP labels.
- */
- @GuardedBy("this")
- private void emitEpilogue(ApfGenerator gen) throws IllegalInstructionException {
- // If APFv4 is unsupported, no epilogue is necessary: if execution reached this far, it
- // will just fall-through to the PASS label.
- if (!mApfCapabilities.hasDataAccess()) return;
-
- // Execution will reach the bottom of the program if none of the filters match,
- // which will pass the packet to the application processor.
- maybeSetupCounter(gen, Counter.PASSED_IPV6_ICMP);
-
- // Append the count & pass trampoline, which increments the counter at the data address
- // pointed to by R1, then jumps to the pass label. This saves a few bytes over inserting
- // the entire sequence inline for every counter.
- gen.defineLabel(mCountAndPassLabel);
- gen.addLoadData(Register.R0, 0); // R0 = *(R1 + 0)
- gen.addAdd(1); // R0++
- gen.addStoreData(Register.R0, 0); // *(R1 + 0) = R0
- gen.addJump(gen.PASS_LABEL);
-
- // Same as above for the count & drop trampoline.
- gen.defineLabel(mCountAndDropLabel);
- gen.addLoadData(Register.R0, 0); // R0 = *(R1 + 0)
- gen.addAdd(1); // R0++
- gen.addStoreData(Register.R0, 0); // *(R1 + 0) = R0
- gen.addJump(gen.DROP_LABEL);
- }
-
- /**
- * Generate and install a new filter program.
- */
- @GuardedBy("this")
- @VisibleForTesting
- void installNewProgramLocked() {
- purgeExpiredRasLocked();
- ArrayList<Ra> rasToFilter = new ArrayList<>();
- final byte[] program;
- long programMinLifetime = Long.MAX_VALUE;
- long maximumApfProgramSize = mApfCapabilities.maximumApfProgramSize;
- if (mApfCapabilities.hasDataAccess()) {
- // Reserve space for the counters.
- maximumApfProgramSize -= Counter.totalSize();
- }
-
- try {
- // Step 1: Determine how many RA filters we can fit in the program.
- ApfGenerator gen = emitPrologueLocked();
-
- // The epilogue normally goes after the RA filters, but add it early to include its
- // length when estimating the total.
- emitEpilogue(gen);
-
- // Can't fit the program even without any RA filters?
- if (gen.programLengthOverEstimate() > maximumApfProgramSize) {
- Log.e(TAG, "Program exceeds maximum size " + maximumApfProgramSize);
- return;
- }
-
- for (Ra ra : mRas) {
- ra.generateFilterLocked(gen);
- // Stop if we get too big.
- if (gen.programLengthOverEstimate() > maximumApfProgramSize) break;
- rasToFilter.add(ra);
- }
-
- // Step 2: Actually generate the program
- gen = emitPrologueLocked();
- for (Ra ra : rasToFilter) {
- programMinLifetime = Math.min(programMinLifetime, ra.generateFilterLocked(gen));
- }
- emitEpilogue(gen);
- program = gen.generate();
- } catch (IllegalInstructionException|IllegalStateException e) {
- Log.e(TAG, "Failed to generate APF program.", e);
- return;
- }
- final long now = currentTimeSeconds();
- mLastTimeInstalledProgram = now;
- mLastInstalledProgramMinLifetime = programMinLifetime;
- mLastInstalledProgram = program;
- mNumProgramUpdates++;
-
- if (VDBG) {
- hexDump("Installing filter: ", program, program.length);
- }
- mIpClientCallback.installPacketFilter(program);
- logApfProgramEventLocked(now);
- mLastInstallEvent = new ApfProgramEvent();
- mLastInstallEvent.lifetime = programMinLifetime;
- mLastInstallEvent.filteredRas = rasToFilter.size();
- mLastInstallEvent.currentRas = mRas.size();
- mLastInstallEvent.programLength = program.length;
- mLastInstallEvent.flags = ApfProgramEvent.flagsFor(mIPv4Address != null, mMulticastFilter);
- }
-
- @GuardedBy("this")
- private void logApfProgramEventLocked(long now) {
- if (mLastInstallEvent == null) {
- return;
- }
- ApfProgramEvent ev = mLastInstallEvent;
- mLastInstallEvent = null;
- ev.actualLifetime = now - mLastTimeInstalledProgram;
- if (ev.actualLifetime < APF_PROGRAM_EVENT_LIFETIME_THRESHOLD) {
- return;
- }
- mMetricsLog.log(ev);
- }
-
- /**
- * Returns {@code true} if a new program should be installed because the current one dies soon.
- */
- private boolean shouldInstallnewProgram() {
- long expiry = mLastTimeInstalledProgram + mLastInstalledProgramMinLifetime;
- return expiry < currentTimeSeconds() + MAX_PROGRAM_LIFETIME_WORTH_REFRESHING;
- }
-
- private void hexDump(String msg, byte[] packet, int length) {
- log(msg + HexDump.toHexString(packet, 0, length, false /* lowercase */));
- }
-
- @GuardedBy("this")
- private void purgeExpiredRasLocked() {
- for (int i = 0; i < mRas.size();) {
- if (mRas.get(i).isExpired()) {
- log("Expiring " + mRas.get(i));
- mRas.remove(i);
- } else {
- i++;
- }
- }
- }
-
- /**
- * Process an RA packet, updating the list of known RAs and installing a new APF program
- * if the current APF program should be updated.
- * @return a ProcessRaResult enum describing what action was performed.
- */
- @VisibleForTesting
- synchronized ProcessRaResult processRa(byte[] packet, int length) {
- if (VDBG) hexDump("Read packet = ", packet, length);
-
- // Have we seen this RA before?
- for (int i = 0; i < mRas.size(); i++) {
- Ra ra = mRas.get(i);
- if (ra.matches(packet, length)) {
- if (VDBG) log("matched RA " + ra);
- // Update lifetimes.
- ra.mLastSeen = currentTimeSeconds();
- ra.mMinLifetime = ra.minLifetime(packet, length);
- ra.seenCount++;
-
- // Keep mRas in LRU order so as to prioritize generating filters for recently seen
- // RAs. LRU prioritizes this because RA filters are generated in order from mRas
- // until the filter program exceeds the maximum filter program size allowed by the
- // chipset, so RAs appearing earlier in mRas are more likely to make it into the
- // filter program.
- // TODO: consider sorting the RAs in order of increasing expiry time as well.
- // Swap to front of array.
- mRas.add(0, mRas.remove(i));
-
- // If the current program doesn't expire for a while, don't update.
- if (shouldInstallnewProgram()) {
- installNewProgramLocked();
- return ProcessRaResult.UPDATE_EXPIRY;
- }
- return ProcessRaResult.MATCH;
- }
- }
- purgeExpiredRasLocked();
- // TODO: figure out how to proceed when we've received more then MAX_RAS RAs.
- if (mRas.size() >= MAX_RAS) {
- return ProcessRaResult.DROPPED;
- }
- final Ra ra;
- try {
- ra = new Ra(packet, length);
- } catch (Exception e) {
- Log.e(TAG, "Error parsing RA", e);
- return ProcessRaResult.PARSE_ERROR;
- }
- // Ignore 0 lifetime RAs.
- if (ra.isExpired()) {
- return ProcessRaResult.ZERO_LIFETIME;
- }
- log("Adding " + ra);
- mRas.add(ra);
- installNewProgramLocked();
- return ProcessRaResult.UPDATE_NEW_RA;
- }
-
- /**
- * Create an {@link ApfFilter} if {@code apfCapabilities} indicates support for packet
- * filtering using APF programs.
- */
- public static ApfFilter maybeCreate(Context context, ApfConfiguration config,
- InterfaceParams ifParams, IpClient.Callback ipClientCallback) {
- if (context == null || config == null || ifParams == null) return null;
- ApfCapabilities apfCapabilities = config.apfCapabilities;
- if (apfCapabilities == null) return null;
- if (apfCapabilities.apfVersionSupported == 0) return null;
- if (apfCapabilities.maximumApfProgramSize < 512) {
- Log.e(TAG, "Unacceptably small APF limit: " + apfCapabilities.maximumApfProgramSize);
- return null;
- }
- // For now only support generating programs for Ethernet frames. If this restriction is
- // lifted:
- // 1. the program generator will need its offsets adjusted.
- // 2. the packet filter attached to our packet socket will need its offset adjusted.
- if (apfCapabilities.apfPacketFormat != ARPHRD_ETHER) return null;
- if (!ApfGenerator.supportsVersion(apfCapabilities.apfVersionSupported)) {
- Log.e(TAG, "Unsupported APF version: " + apfCapabilities.apfVersionSupported);
- return null;
- }
-
- return new ApfFilter(context, config, ifParams, ipClientCallback, new IpConnectivityLog());
- }
-
- public synchronized void shutdown() {
- if (mReceiveThread != null) {
- log("shutting down");
- mReceiveThread.halt(); // Also closes socket.
- mReceiveThread = null;
- }
- mRas.clear();
- mContext.unregisterReceiver(mDeviceIdleReceiver);
- }
-
- public synchronized void setMulticastFilter(boolean isEnabled) {
- if (mMulticastFilter == isEnabled) return;
- mMulticastFilter = isEnabled;
- if (!isEnabled) {
- mNumProgramUpdatesAllowingMulticast++;
- }
- installNewProgramLocked();
- }
-
- @VisibleForTesting
- public synchronized void setDozeMode(boolean isEnabled) {
- if (mInDozeMode == isEnabled) return;
- mInDozeMode = isEnabled;
- installNewProgramLocked();
- }
-
- /** Find the single IPv4 LinkAddress if there is one, otherwise return null. */
- private static LinkAddress findIPv4LinkAddress(LinkProperties lp) {
- LinkAddress ipv4Address = null;
- for (LinkAddress address : lp.getLinkAddresses()) {
- if (!(address.getAddress() instanceof Inet4Address)) {
- continue;
- }
- if (ipv4Address != null && !ipv4Address.isSameAddressAs(address)) {
- // More than one IPv4 address, abort.
- return null;
- }
- ipv4Address = address;
- }
- return ipv4Address;
- }
-
- public synchronized void setLinkProperties(LinkProperties lp) {
- // NOTE: Do not keep a copy of LinkProperties as it would further duplicate state.
- final LinkAddress ipv4Address = findIPv4LinkAddress(lp);
- final byte[] addr = (ipv4Address != null) ? ipv4Address.getAddress().getAddress() : null;
- final int prefix = (ipv4Address != null) ? ipv4Address.getPrefixLength() : 0;
- if ((prefix == mIPv4PrefixLength) && Arrays.equals(addr, mIPv4Address)) {
- return;
- }
- mIPv4Address = addr;
- mIPv4PrefixLength = prefix;
- installNewProgramLocked();
- }
-
- static public long counterValue(byte[] data, Counter counter)
- throws ArrayIndexOutOfBoundsException {
- // Follow the same wrap-around addressing scheme of the interpreter.
- int offset = counter.offset();
- if (offset < 0) {
- offset = data.length + offset;
- }
-
- // Decode 32bit big-endian integer into a long so we can count up beyond 2^31.
- long value = 0;
- for (int i = 0; i < 4; i++) {
- value = value << 8 | (data[offset] & 0xFF);
- offset++;
- }
- return value;
- }
-
- public synchronized void dump(IndentingPrintWriter pw) {
- pw.println("Capabilities: " + mApfCapabilities);
- pw.println("Receive thread: " + (mReceiveThread != null ? "RUNNING" : "STOPPED"));
- pw.println("Multicast: " + (mMulticastFilter ? "DROP" : "ALLOW"));
- try {
- pw.println("IPv4 address: " + InetAddress.getByAddress(mIPv4Address).getHostAddress());
- } catch (UnknownHostException|NullPointerException e) {}
-
- if (mLastTimeInstalledProgram == 0) {
- pw.println("No program installed.");
- return;
- }
- pw.println("Program updates: " + mNumProgramUpdates);
- pw.println(String.format(
- "Last program length %d, installed %ds ago, lifetime %ds",
- mLastInstalledProgram.length, currentTimeSeconds() - mLastTimeInstalledProgram,
- mLastInstalledProgramMinLifetime));
-
- pw.println("RA filters:");
- pw.increaseIndent();
- for (Ra ra: mRas) {
- pw.println(ra);
- pw.increaseIndent();
- pw.println(String.format(
- "Seen: %d, last %ds ago", ra.seenCount, currentTimeSeconds() - ra.mLastSeen));
- if (DBG) {
- pw.println("Last match:");
- pw.increaseIndent();
- pw.println(ra.getLastMatchingPacket());
- pw.decreaseIndent();
- }
- pw.decreaseIndent();
- }
- pw.decreaseIndent();
-
- if (DBG) {
- pw.println("Last program:");
- pw.increaseIndent();
- pw.println(HexDump.toHexString(mLastInstalledProgram, false /* lowercase */));
- pw.decreaseIndent();
- }
-
- pw.println("APF packet counters: ");
- pw.increaseIndent();
- if (!mApfCapabilities.hasDataAccess()) {
- pw.println("APF counters not supported");
- } else if (mDataSnapshot == null) {
- pw.println("No last snapshot.");
- } else {
- try {
- Counter[] counters = Counter.class.getEnumConstants();
- for (Counter c : Arrays.asList(counters).subList(1, counters.length)) {
- long value = counterValue(mDataSnapshot, c);
- // Only print non-zero counters
- if (value != 0) {
- pw.println(c.toString() + ": " + value);
- }
- }
- } catch (ArrayIndexOutOfBoundsException e) {
- pw.println("Uh-oh: " + e);
- }
- if (VDBG) {
- pw.println("Raw data dump: ");
- pw.println(HexDump.dumpHexString(mDataSnapshot));
- }
- }
- pw.decreaseIndent();
- }
-
- // TODO: move to android.net.NetworkUtils
- @VisibleForTesting
- public static int ipv4BroadcastAddress(byte[] addrBytes, int prefixLength) {
- return bytesToBEInt(addrBytes) | (int) (uint32(-1) >>> prefixLength);
- }
-}
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index a956cef..cddb91f 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -16,1019 +16,14 @@
package android.net.dhcp;
-import com.android.internal.util.HexDump;
-import com.android.internal.util.Protocol;
-import com.android.internal.util.State;
-import com.android.internal.util.MessageUtils;
-import com.android.internal.util.StateMachine;
-import com.android.internal.util.WakeupMessage;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.DhcpResults;
-import android.net.InterfaceConfiguration;
-import android.net.LinkAddress;
-import android.net.NetworkUtils;
-import android.net.TrafficStats;
-import android.net.metrics.IpConnectivityLog;
-import android.net.metrics.DhcpClientEvent;
-import android.net.metrics.DhcpErrorEvent;
-import android.net.util.InterfaceParams;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.PacketSocketAddress;
-import android.util.EventLog;
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.TimeUtils;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.lang.Thread;
-import java.net.Inet4Address;
-import java.net.SocketException;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.Random;
-
-import libcore.io.IoBridge;
-
-import static android.system.OsConstants.*;
-import static android.net.dhcp.DhcpPacket.*;
-
/**
- * A DHCPv4 client.
- *
- * Written to behave similarly to the DhcpStateMachine + dhcpcd 5.5.6 combination used in Android
- * 5.1 and below, as configured on Nexus 6. The interface is the same as DhcpStateMachine.
- *
- * TODO:
- *
- * - Exponential backoff when receiving NAKs (not specified by the RFC, but current behaviour).
- * - Support persisting lease state and support INIT-REBOOT. Android 5.1 does this, but it does not
- * do so correctly: instead of requesting the lease last obtained on a particular network (e.g., a
- * given SSID), it requests the last-leased IP address on the same interface, causing a delay if
- * the server NAKs or a timeout if it doesn't.
- *
- * Known differences from current behaviour:
- *
- * - Does not request the "static routes" option.
- * - Does not support BOOTP servers. DHCP has been around since 1993, should be everywhere now.
- * - Requests the "broadcast" option, but does nothing with it.
- * - Rejects invalid subnet masks such as 255.255.255.1 (current code treats that as 255.255.255.0).
- *
- * @hide
+ * TODO: remove this class after migrating clients.
*/
-public class DhcpClient extends StateMachine {
+public class DhcpClient {
+ public static final int CMD_PRE_DHCP_ACTION = 1003;
+ public static final int CMD_POST_DHCP_ACTION = 1004;
+ public static final int CMD_PRE_DHCP_ACTION_COMPLETE = 1006;
- private static final String TAG = "DhcpClient";
- private static final boolean DBG = true;
- private static final boolean STATE_DBG = false;
- private static final boolean MSG_DBG = false;
- private static final boolean PACKET_DBG = false;
-
- // Timers and timeouts.
- private static final int SECONDS = 1000;
- private static final int FIRST_TIMEOUT_MS = 2 * SECONDS;
- private static final int MAX_TIMEOUT_MS = 128 * SECONDS;
-
- // This is not strictly needed, since the client is asynchronous and implements exponential
- // backoff. It's maintained for backwards compatibility with the previous DHCP code, which was
- // a blocking operation with a 30-second timeout. We pick 36 seconds so we can send packets at
- // t=0, t=2, t=6, t=14, t=30, allowing for 10% jitter.
- private static final int DHCP_TIMEOUT_MS = 36 * SECONDS;
-
- private static final int PUBLIC_BASE = Protocol.BASE_DHCP;
-
- /* Commands from controller to start/stop DHCP */
- public static final int CMD_START_DHCP = PUBLIC_BASE + 1;
- public static final int CMD_STOP_DHCP = PUBLIC_BASE + 2;
-
- /* Notification from DHCP state machine prior to DHCP discovery/renewal */
- public static final int CMD_PRE_DHCP_ACTION = PUBLIC_BASE + 3;
- /* Notification from DHCP state machine post DHCP discovery/renewal. Indicates
- * success/failure */
- public static final int CMD_POST_DHCP_ACTION = PUBLIC_BASE + 4;
- /* Notification from DHCP state machine before quitting */
- public static final int CMD_ON_QUIT = PUBLIC_BASE + 5;
-
- /* Command from controller to indicate DHCP discovery/renewal can continue
- * after pre DHCP action is complete */
- public static final int CMD_PRE_DHCP_ACTION_COMPLETE = PUBLIC_BASE + 6;
-
- /* Command and event notification to/from IpManager requesting the setting
- * (or clearing) of an IPv4 LinkAddress.
- */
- public static final int CMD_CLEAR_LINKADDRESS = PUBLIC_BASE + 7;
- public static final int CMD_CONFIGURE_LINKADDRESS = PUBLIC_BASE + 8;
- public static final int EVENT_LINKADDRESS_CONFIGURED = PUBLIC_BASE + 9;
-
- /* Message.arg1 arguments to CMD_POST_DHCP_ACTION notification */
public static final int DHCP_SUCCESS = 1;
public static final int DHCP_FAILURE = 2;
-
- // Internal messages.
- private static final int PRIVATE_BASE = Protocol.BASE_DHCP + 100;
- private static final int CMD_KICK = PRIVATE_BASE + 1;
- private static final int CMD_RECEIVED_PACKET = PRIVATE_BASE + 2;
- private static final int CMD_TIMEOUT = PRIVATE_BASE + 3;
- private static final int CMD_RENEW_DHCP = PRIVATE_BASE + 4;
- private static final int CMD_REBIND_DHCP = PRIVATE_BASE + 5;
- private static final int CMD_EXPIRE_DHCP = PRIVATE_BASE + 6;
-
- // For message logging.
- private static final Class[] sMessageClasses = { DhcpClient.class };
- private static final SparseArray<String> sMessageNames =
- MessageUtils.findMessageNames(sMessageClasses);
-
- // DHCP parameters that we request.
- /* package */ static final byte[] REQUESTED_PARAMS = new byte[] {
- DHCP_SUBNET_MASK,
- DHCP_ROUTER,
- DHCP_DNS_SERVER,
- DHCP_DOMAIN_NAME,
- DHCP_MTU,
- DHCP_BROADCAST_ADDRESS, // TODO: currently ignored.
- DHCP_LEASE_TIME,
- DHCP_RENEWAL_TIME,
- DHCP_REBINDING_TIME,
- DHCP_VENDOR_INFO,
- };
-
- // DHCP flag that means "yes, we support unicast."
- private static final boolean DO_UNICAST = false;
-
- // System services / libraries we use.
- private final Context mContext;
- private final Random mRandom;
- private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
-
- // Sockets.
- // - We use a packet socket to receive, because servers send us packets bound for IP addresses
- // which we have not yet configured, and the kernel protocol stack drops these.
- // - We use a UDP socket to send, so the kernel handles ARP and routing for us (DHCP servers can
- // be off-link as well as on-link).
- private FileDescriptor mPacketSock;
- private FileDescriptor mUdpSock;
- private ReceiveThread mReceiveThread;
-
- // State variables.
- private final StateMachine mController;
- private final WakeupMessage mKickAlarm;
- private final WakeupMessage mTimeoutAlarm;
- private final WakeupMessage mRenewAlarm;
- private final WakeupMessage mRebindAlarm;
- private final WakeupMessage mExpiryAlarm;
- private final String mIfaceName;
-
- private boolean mRegisteredForPreDhcpNotification;
- private InterfaceParams mIface;
- // TODO: MacAddress-ify more of this class hierarchy.
- private byte[] mHwAddr;
- private PacketSocketAddress mInterfaceBroadcastAddr;
- private int mTransactionId;
- private long mTransactionStartMillis;
- private DhcpResults mDhcpLease;
- private long mDhcpLeaseExpiry;
- private DhcpResults mOffer;
-
- // Milliseconds SystemClock timestamps used to record transition times to DhcpBoundState.
- private long mLastInitEnterTime;
- private long mLastBoundExitTime;
-
- // States.
- private State mStoppedState = new StoppedState();
- private State mDhcpState = new DhcpState();
- private State mDhcpInitState = new DhcpInitState();
- private State mDhcpSelectingState = new DhcpSelectingState();
- private State mDhcpRequestingState = new DhcpRequestingState();
- private State mDhcpHaveLeaseState = new DhcpHaveLeaseState();
- private State mConfiguringInterfaceState = new ConfiguringInterfaceState();
- private State mDhcpBoundState = new DhcpBoundState();
- private State mDhcpRenewingState = new DhcpRenewingState();
- private State mDhcpRebindingState = new DhcpRebindingState();
- private State mDhcpInitRebootState = new DhcpInitRebootState();
- private State mDhcpRebootingState = new DhcpRebootingState();
- private State mWaitBeforeStartState = new WaitBeforeStartState(mDhcpInitState);
- private State mWaitBeforeRenewalState = new WaitBeforeRenewalState(mDhcpRenewingState);
-
- private WakeupMessage makeWakeupMessage(String cmdName, int cmd) {
- cmdName = DhcpClient.class.getSimpleName() + "." + mIfaceName + "." + cmdName;
- return new WakeupMessage(mContext, getHandler(), cmdName, cmd);
- }
-
- // TODO: Take an InterfaceParams instance instead of an interface name String.
- private DhcpClient(Context context, StateMachine controller, String iface) {
- super(TAG, controller.getHandler());
-
- mContext = context;
- mController = controller;
- mIfaceName = iface;
-
- addState(mStoppedState);
- addState(mDhcpState);
- addState(mDhcpInitState, mDhcpState);
- addState(mWaitBeforeStartState, mDhcpState);
- addState(mDhcpSelectingState, mDhcpState);
- addState(mDhcpRequestingState, mDhcpState);
- addState(mDhcpHaveLeaseState, mDhcpState);
- addState(mConfiguringInterfaceState, mDhcpHaveLeaseState);
- addState(mDhcpBoundState, mDhcpHaveLeaseState);
- addState(mWaitBeforeRenewalState, mDhcpHaveLeaseState);
- addState(mDhcpRenewingState, mDhcpHaveLeaseState);
- addState(mDhcpRebindingState, mDhcpHaveLeaseState);
- addState(mDhcpInitRebootState, mDhcpState);
- addState(mDhcpRebootingState, mDhcpState);
-
- setInitialState(mStoppedState);
-
- mRandom = new Random();
-
- // Used to schedule packet retransmissions.
- mKickAlarm = makeWakeupMessage("KICK", CMD_KICK);
- // Used to time out PacketRetransmittingStates.
- mTimeoutAlarm = makeWakeupMessage("TIMEOUT", CMD_TIMEOUT);
- // Used to schedule DHCP reacquisition.
- mRenewAlarm = makeWakeupMessage("RENEW", CMD_RENEW_DHCP);
- mRebindAlarm = makeWakeupMessage("REBIND", CMD_REBIND_DHCP);
- mExpiryAlarm = makeWakeupMessage("EXPIRY", CMD_EXPIRE_DHCP);
- }
-
- public void registerForPreDhcpNotification() {
- mRegisteredForPreDhcpNotification = true;
- }
-
- public static DhcpClient makeDhcpClient(
- Context context, StateMachine controller, InterfaceParams ifParams) {
- DhcpClient client = new DhcpClient(context, controller, ifParams.name);
- client.mIface = ifParams;
- client.start();
- return client;
- }
-
- private boolean initInterface() {
- if (mIface == null) mIface = InterfaceParams.getByName(mIfaceName);
- if (mIface == null) {
- Log.e(TAG, "Can't determine InterfaceParams for " + mIfaceName);
- return false;
- }
-
- mHwAddr = mIface.macAddr.toByteArray();
- mInterfaceBroadcastAddr = new PacketSocketAddress(mIface.index, DhcpPacket.ETHER_BROADCAST);
- return true;
- }
-
- private void startNewTransaction() {
- mTransactionId = mRandom.nextInt();
- mTransactionStartMillis = SystemClock.elapsedRealtime();
- }
-
- private boolean initSockets() {
- return initPacketSocket() && initUdpSocket();
- }
-
- private boolean initPacketSocket() {
- try {
- mPacketSock = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IP);
- PacketSocketAddress addr = new PacketSocketAddress((short) ETH_P_IP, mIface.index);
- Os.bind(mPacketSock, addr);
- NetworkUtils.attachDhcpFilter(mPacketSock);
- } catch(SocketException|ErrnoException e) {
- Log.e(TAG, "Error creating packet socket", e);
- return false;
- }
- return true;
- }
-
- private boolean initUdpSocket() {
- final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_DHCP);
- try {
- mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
- Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1);
- Os.setsockoptIfreq(mUdpSock, SOL_SOCKET, SO_BINDTODEVICE, mIfaceName);
- Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1);
- Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_RCVBUF, 0);
- Os.bind(mUdpSock, Inet4Address.ANY, DhcpPacket.DHCP_CLIENT);
- NetworkUtils.protectFromVpn(mUdpSock);
- } catch(SocketException|ErrnoException e) {
- Log.e(TAG, "Error creating UDP socket", e);
- return false;
- } finally {
- TrafficStats.setThreadStatsTag(oldTag);
- }
- return true;
- }
-
- private boolean connectUdpSock(Inet4Address to) {
- try {
- Os.connect(mUdpSock, to, DhcpPacket.DHCP_SERVER);
- return true;
- } catch (SocketException|ErrnoException e) {
- Log.e(TAG, "Error connecting UDP socket", e);
- return false;
- }
- }
-
- private static void closeQuietly(FileDescriptor fd) {
- try {
- IoBridge.closeAndSignalBlockedThreads(fd);
- } catch (IOException ignored) {}
- }
-
- private void closeSockets() {
- closeQuietly(mUdpSock);
- closeQuietly(mPacketSock);
- }
-
- class ReceiveThread extends Thread {
-
- private final byte[] mPacket = new byte[DhcpPacket.MAX_LENGTH];
- private volatile boolean mStopped = false;
-
- public void halt() {
- mStopped = true;
- closeSockets(); // Interrupts the read() call the thread is blocked in.
- }
-
- @Override
- public void run() {
- if (DBG) Log.d(TAG, "Receive thread started");
- while (!mStopped) {
- int length = 0; // Or compiler can't tell it's initialized if a parse error occurs.
- try {
- length = Os.read(mPacketSock, mPacket, 0, mPacket.length);
- DhcpPacket packet = null;
- packet = DhcpPacket.decodeFullPacket(mPacket, length, DhcpPacket.ENCAP_L2);
- if (DBG) Log.d(TAG, "Received packet: " + packet);
- sendMessage(CMD_RECEIVED_PACKET, packet);
- } catch (IOException|ErrnoException e) {
- if (!mStopped) {
- Log.e(TAG, "Read error", e);
- logError(DhcpErrorEvent.RECEIVE_ERROR);
- }
- } catch (DhcpPacket.ParseException e) {
- Log.e(TAG, "Can't parse packet: " + e.getMessage());
- if (PACKET_DBG) {
- Log.d(TAG, HexDump.dumpHexString(mPacket, 0, length));
- }
- if (e.errorCode == DhcpErrorEvent.DHCP_NO_COOKIE) {
- int snetTagId = 0x534e4554;
- String bugId = "31850211";
- int uid = -1;
- String data = DhcpPacket.ParseException.class.getName();
- EventLog.writeEvent(snetTagId, bugId, uid, data);
- }
- logError(e.errorCode);
- }
- }
- if (DBG) Log.d(TAG, "Receive thread stopped");
- }
- }
-
- private short getSecs() {
- return (short) ((SystemClock.elapsedRealtime() - mTransactionStartMillis) / 1000);
- }
-
- private boolean transmitPacket(ByteBuffer buf, String description, int encap, Inet4Address to) {
- try {
- if (encap == DhcpPacket.ENCAP_L2) {
- if (DBG) Log.d(TAG, "Broadcasting " + description);
- Os.sendto(mPacketSock, buf.array(), 0, buf.limit(), 0, mInterfaceBroadcastAddr);
- } else if (encap == DhcpPacket.ENCAP_BOOTP && to.equals(INADDR_BROADCAST)) {
- if (DBG) Log.d(TAG, "Broadcasting " + description);
- // We only send L3-encapped broadcasts in DhcpRebindingState,
- // where we have an IP address and an unconnected UDP socket.
- //
- // N.B.: We only need this codepath because DhcpRequestPacket
- // hardcodes the source IP address to 0.0.0.0. We could reuse
- // the packet socket if this ever changes.
- Os.sendto(mUdpSock, buf, 0, to, DhcpPacket.DHCP_SERVER);
- } else {
- // It's safe to call getpeername here, because we only send unicast packets if we
- // have an IP address, and we connect the UDP socket in DhcpBoundState#enter.
- if (DBG) Log.d(TAG, String.format("Unicasting %s to %s",
- description, Os.getpeername(mUdpSock)));
- Os.write(mUdpSock, buf);
- }
- } catch(ErrnoException|IOException e) {
- Log.e(TAG, "Can't send packet: ", e);
- return false;
- }
- return true;
- }
-
- private boolean sendDiscoverPacket() {
- ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
- DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr,
- DO_UNICAST, REQUESTED_PARAMS);
- return transmitPacket(packet, "DHCPDISCOVER", DhcpPacket.ENCAP_L2, INADDR_BROADCAST);
- }
-
- private boolean sendRequestPacket(
- Inet4Address clientAddress, Inet4Address requestedAddress,
- Inet4Address serverAddress, Inet4Address to) {
- // TODO: should we use the transaction ID from the server?
- final int encap = INADDR_ANY.equals(clientAddress)
- ? DhcpPacket.ENCAP_L2 : DhcpPacket.ENCAP_BOOTP;
-
- ByteBuffer packet = DhcpPacket.buildRequestPacket(
- encap, mTransactionId, getSecs(), clientAddress,
- DO_UNICAST, mHwAddr, requestedAddress,
- serverAddress, REQUESTED_PARAMS, null);
- String serverStr = (serverAddress != null) ? serverAddress.getHostAddress() : null;
- String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() +
- " request=" + requestedAddress.getHostAddress() +
- " serverid=" + serverStr;
- return transmitPacket(packet, description, encap, to);
- }
-
- private void scheduleLeaseTimers() {
- if (mDhcpLeaseExpiry == 0) {
- Log.d(TAG, "Infinite lease, no timer scheduling needed");
- return;
- }
-
- final long now = SystemClock.elapsedRealtime();
-
- // TODO: consider getting the renew and rebind timers from T1 and T2.
- // See also:
- // https://tools.ietf.org/html/rfc2131#section-4.4.5
- // https://tools.ietf.org/html/rfc1533#section-9.9
- // https://tools.ietf.org/html/rfc1533#section-9.10
- final long remainingDelay = mDhcpLeaseExpiry - now;
- final long renewDelay = remainingDelay / 2;
- final long rebindDelay = remainingDelay * 7 / 8;
- mRenewAlarm.schedule(now + renewDelay);
- mRebindAlarm.schedule(now + rebindDelay);
- mExpiryAlarm.schedule(now + remainingDelay);
- Log.d(TAG, "Scheduling renewal in " + (renewDelay / 1000) + "s");
- Log.d(TAG, "Scheduling rebind in " + (rebindDelay / 1000) + "s");
- Log.d(TAG, "Scheduling expiry in " + (remainingDelay / 1000) + "s");
- }
-
- private void notifySuccess() {
- mController.sendMessage(
- CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, new DhcpResults(mDhcpLease));
- }
-
- private void notifyFailure() {
- mController.sendMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0, null);
- }
-
- private void acceptDhcpResults(DhcpResults results, String msg) {
- mDhcpLease = results;
- mOffer = null;
- Log.d(TAG, msg + " lease: " + mDhcpLease);
- notifySuccess();
- }
-
- private void clearDhcpState() {
- mDhcpLease = null;
- mDhcpLeaseExpiry = 0;
- mOffer = null;
- }
-
- /**
- * Quit the DhcpStateMachine.
- *
- * @hide
- */
- public void doQuit() {
- Log.d(TAG, "doQuit");
- quit();
- }
-
- @Override
- protected void onQuitting() {
- Log.d(TAG, "onQuitting");
- mController.sendMessage(CMD_ON_QUIT);
- }
-
- abstract class LoggingState extends State {
- private long mEnterTimeMs;
-
- @Override
- public void enter() {
- if (STATE_DBG) Log.d(TAG, "Entering state " + getName());
- mEnterTimeMs = SystemClock.elapsedRealtime();
- }
-
- @Override
- public void exit() {
- long durationMs = SystemClock.elapsedRealtime() - mEnterTimeMs;
- logState(getName(), (int) durationMs);
- }
-
- private String messageName(int what) {
- return sMessageNames.get(what, Integer.toString(what));
- }
-
- private String messageToString(Message message) {
- long now = SystemClock.uptimeMillis();
- StringBuilder b = new StringBuilder(" ");
- TimeUtils.formatDuration(message.getWhen() - now, b);
- b.append(" ").append(messageName(message.what))
- .append(" ").append(message.arg1)
- .append(" ").append(message.arg2)
- .append(" ").append(message.obj);
- return b.toString();
- }
-
- @Override
- public boolean processMessage(Message message) {
- if (MSG_DBG) {
- Log.d(TAG, getName() + messageToString(message));
- }
- return NOT_HANDLED;
- }
-
- @Override
- public String getName() {
- // All DhcpClient's states are inner classes with a well defined name.
- // Use getSimpleName() and avoid super's getName() creating new String instances.
- return getClass().getSimpleName();
- }
- }
-
- // Sends CMD_PRE_DHCP_ACTION to the controller, waits for the controller to respond with
- // CMD_PRE_DHCP_ACTION_COMPLETE, and then transitions to mOtherState.
- abstract class WaitBeforeOtherState extends LoggingState {
- protected State mOtherState;
-
- @Override
- public void enter() {
- super.enter();
- mController.sendMessage(CMD_PRE_DHCP_ACTION);
- }
-
- @Override
- public boolean processMessage(Message message) {
- super.processMessage(message);
- switch (message.what) {
- case CMD_PRE_DHCP_ACTION_COMPLETE:
- transitionTo(mOtherState);
- return HANDLED;
- default:
- return NOT_HANDLED;
- }
- }
- }
-
- class StoppedState extends State {
- @Override
- public boolean processMessage(Message message) {
- switch (message.what) {
- case CMD_START_DHCP:
- if (mRegisteredForPreDhcpNotification) {
- transitionTo(mWaitBeforeStartState);
- } else {
- transitionTo(mDhcpInitState);
- }
- return HANDLED;
- default:
- return NOT_HANDLED;
- }
- }
- }
-
- class WaitBeforeStartState extends WaitBeforeOtherState {
- public WaitBeforeStartState(State otherState) {
- super();
- mOtherState = otherState;
- }
- }
-
- class WaitBeforeRenewalState extends WaitBeforeOtherState {
- public WaitBeforeRenewalState(State otherState) {
- super();
- mOtherState = otherState;
- }
- }
-
- class DhcpState extends State {
- @Override
- public void enter() {
- clearDhcpState();
- if (initInterface() && initSockets()) {
- mReceiveThread = new ReceiveThread();
- mReceiveThread.start();
- } else {
- notifyFailure();
- transitionTo(mStoppedState);
- }
- }
-
- @Override
- public void exit() {
- if (mReceiveThread != null) {
- mReceiveThread.halt(); // Also closes sockets.
- mReceiveThread = null;
- }
- clearDhcpState();
- }
-
- @Override
- public boolean processMessage(Message message) {
- super.processMessage(message);
- switch (message.what) {
- case CMD_STOP_DHCP:
- transitionTo(mStoppedState);
- return HANDLED;
- default:
- return NOT_HANDLED;
- }
- }
- }
-
- public boolean isValidPacket(DhcpPacket packet) {
- // TODO: check checksum.
- int xid = packet.getTransactionId();
- if (xid != mTransactionId) {
- Log.d(TAG, "Unexpected transaction ID " + xid + ", expected " + mTransactionId);
- return false;
- }
- if (!Arrays.equals(packet.getClientMac(), mHwAddr)) {
- Log.d(TAG, "MAC addr mismatch: got " +
- HexDump.toHexString(packet.getClientMac()) + ", expected " +
- HexDump.toHexString(packet.getClientMac()));
- return false;
- }
- return true;
- }
-
- public void setDhcpLeaseExpiry(DhcpPacket packet) {
- long leaseTimeMillis = packet.getLeaseTimeMillis();
- mDhcpLeaseExpiry =
- (leaseTimeMillis > 0) ? SystemClock.elapsedRealtime() + leaseTimeMillis : 0;
- }
-
- /**
- * Retransmits packets using jittered exponential backoff with an optional timeout. Packet
- * transmission is triggered by CMD_KICK, which is sent by an AlarmManager alarm. If a subclass
- * sets mTimeout to a positive value, then timeout() is called by an AlarmManager alarm mTimeout
- * milliseconds after entering the state. Kicks and timeouts are cancelled when leaving the
- * state.
- *
- * Concrete subclasses must implement sendPacket, which is called when the alarm fires and a
- * packet needs to be transmitted, and receivePacket, which is triggered by CMD_RECEIVED_PACKET
- * sent by the receive thread. They may also set mTimeout and implement timeout.
- */
- abstract class PacketRetransmittingState extends LoggingState {
-
- private int mTimer;
- protected int mTimeout = 0;
-
- @Override
- public void enter() {
- super.enter();
- initTimer();
- maybeInitTimeout();
- sendMessage(CMD_KICK);
- }
-
- @Override
- public boolean processMessage(Message message) {
- super.processMessage(message);
- switch (message.what) {
- case CMD_KICK:
- sendPacket();
- scheduleKick();
- return HANDLED;
- case CMD_RECEIVED_PACKET:
- receivePacket((DhcpPacket) message.obj);
- return HANDLED;
- case CMD_TIMEOUT:
- timeout();
- return HANDLED;
- default:
- return NOT_HANDLED;
- }
- }
-
- @Override
- public void exit() {
- super.exit();
- mKickAlarm.cancel();
- mTimeoutAlarm.cancel();
- }
-
- abstract protected boolean sendPacket();
- abstract protected void receivePacket(DhcpPacket packet);
- protected void timeout() {}
-
- protected void initTimer() {
- mTimer = FIRST_TIMEOUT_MS;
- }
-
- protected int jitterTimer(int baseTimer) {
- int maxJitter = baseTimer / 10;
- int jitter = mRandom.nextInt(2 * maxJitter) - maxJitter;
- return baseTimer + jitter;
- }
-
- protected void scheduleKick() {
- long now = SystemClock.elapsedRealtime();
- long timeout = jitterTimer(mTimer);
- long alarmTime = now + timeout;
- mKickAlarm.schedule(alarmTime);
- mTimer *= 2;
- if (mTimer > MAX_TIMEOUT_MS) {
- mTimer = MAX_TIMEOUT_MS;
- }
- }
-
- protected void maybeInitTimeout() {
- if (mTimeout > 0) {
- long alarmTime = SystemClock.elapsedRealtime() + mTimeout;
- mTimeoutAlarm.schedule(alarmTime);
- }
- }
- }
-
- class DhcpInitState extends PacketRetransmittingState {
- public DhcpInitState() {
- super();
- }
-
- @Override
- public void enter() {
- super.enter();
- startNewTransaction();
- mLastInitEnterTime = SystemClock.elapsedRealtime();
- }
-
- protected boolean sendPacket() {
- return sendDiscoverPacket();
- }
-
- protected void receivePacket(DhcpPacket packet) {
- if (!isValidPacket(packet)) return;
- if (!(packet instanceof DhcpOfferPacket)) return;
- mOffer = packet.toDhcpResults();
- if (mOffer != null) {
- Log.d(TAG, "Got pending lease: " + mOffer);
- transitionTo(mDhcpRequestingState);
- }
- }
- }
-
- // Not implemented. We request the first offer we receive.
- class DhcpSelectingState extends LoggingState {
- }
-
- class DhcpRequestingState extends PacketRetransmittingState {
- public DhcpRequestingState() {
- mTimeout = DHCP_TIMEOUT_MS / 2;
- }
-
- protected boolean sendPacket() {
- return sendRequestPacket(
- INADDR_ANY, // ciaddr
- (Inet4Address) mOffer.ipAddress.getAddress(), // DHCP_REQUESTED_IP
- (Inet4Address) mOffer.serverAddress, // DHCP_SERVER_IDENTIFIER
- INADDR_BROADCAST); // packet destination address
- }
-
- protected void receivePacket(DhcpPacket packet) {
- if (!isValidPacket(packet)) return;
- if ((packet instanceof DhcpAckPacket)) {
- DhcpResults results = packet.toDhcpResults();
- if (results != null) {
- setDhcpLeaseExpiry(packet);
- acceptDhcpResults(results, "Confirmed");
- transitionTo(mConfiguringInterfaceState);
- }
- } else if (packet instanceof DhcpNakPacket) {
- // TODO: Wait a while before returning into INIT state.
- Log.d(TAG, "Received NAK, returning to INIT");
- mOffer = null;
- transitionTo(mDhcpInitState);
- }
- }
-
- @Override
- protected void timeout() {
- // After sending REQUESTs unsuccessfully for a while, go back to init.
- transitionTo(mDhcpInitState);
- }
- }
-
- class DhcpHaveLeaseState extends State {
- @Override
- public boolean processMessage(Message message) {
- switch (message.what) {
- case CMD_EXPIRE_DHCP:
- Log.d(TAG, "Lease expired!");
- notifyFailure();
- transitionTo(mDhcpInitState);
- return HANDLED;
- default:
- return NOT_HANDLED;
- }
- }
-
- @Override
- public void exit() {
- // Clear any extant alarms.
- mRenewAlarm.cancel();
- mRebindAlarm.cancel();
- mExpiryAlarm.cancel();
- clearDhcpState();
- // Tell IpManager to clear the IPv4 address. There is no need to
- // wait for confirmation since any subsequent packets are sent from
- // INADDR_ANY anyway (DISCOVER, REQUEST).
- mController.sendMessage(CMD_CLEAR_LINKADDRESS);
- }
- }
-
- class ConfiguringInterfaceState extends LoggingState {
- @Override
- public void enter() {
- super.enter();
- mController.sendMessage(CMD_CONFIGURE_LINKADDRESS, mDhcpLease.ipAddress);
- }
-
- @Override
- public boolean processMessage(Message message) {
- super.processMessage(message);
- switch (message.what) {
- case EVENT_LINKADDRESS_CONFIGURED:
- transitionTo(mDhcpBoundState);
- return HANDLED;
- default:
- return NOT_HANDLED;
- }
- }
- }
-
- class DhcpBoundState extends LoggingState {
- @Override
- public void enter() {
- super.enter();
- if (mDhcpLease.serverAddress != null && !connectUdpSock(mDhcpLease.serverAddress)) {
- // There's likely no point in going into DhcpInitState here, we'll probably
- // just repeat the transaction, get the same IP address as before, and fail.
- //
- // NOTE: It is observed that connectUdpSock() basically never fails, due to
- // SO_BINDTODEVICE. Examining the local socket address shows it will happily
- // return an IPv4 address from another interface, or even return "0.0.0.0".
- //
- // TODO: Consider deleting this check, following testing on several kernels.
- notifyFailure();
- transitionTo(mStoppedState);
- }
-
- scheduleLeaseTimers();
- logTimeToBoundState();
- }
-
- @Override
- public void exit() {
- super.exit();
- mLastBoundExitTime = SystemClock.elapsedRealtime();
- }
-
- @Override
- public boolean processMessage(Message message) {
- super.processMessage(message);
- switch (message.what) {
- case CMD_RENEW_DHCP:
- if (mRegisteredForPreDhcpNotification) {
- transitionTo(mWaitBeforeRenewalState);
- } else {
- transitionTo(mDhcpRenewingState);
- }
- return HANDLED;
- default:
- return NOT_HANDLED;
- }
- }
-
- private void logTimeToBoundState() {
- long now = SystemClock.elapsedRealtime();
- if (mLastBoundExitTime > mLastInitEnterTime) {
- logState(DhcpClientEvent.RENEWING_BOUND, (int)(now - mLastBoundExitTime));
- } else {
- logState(DhcpClientEvent.INITIAL_BOUND, (int)(now - mLastInitEnterTime));
- }
- }
- }
-
- abstract class DhcpReacquiringState extends PacketRetransmittingState {
- protected String mLeaseMsg;
-
- @Override
- public void enter() {
- super.enter();
- startNewTransaction();
- }
-
- abstract protected Inet4Address packetDestination();
-
- protected boolean sendPacket() {
- return sendRequestPacket(
- (Inet4Address) mDhcpLease.ipAddress.getAddress(), // ciaddr
- INADDR_ANY, // DHCP_REQUESTED_IP
- null, // DHCP_SERVER_IDENTIFIER
- packetDestination()); // packet destination address
- }
-
- protected void receivePacket(DhcpPacket packet) {
- if (!isValidPacket(packet)) return;
- if ((packet instanceof DhcpAckPacket)) {
- final DhcpResults results = packet.toDhcpResults();
- if (results != null) {
- if (!mDhcpLease.ipAddress.equals(results.ipAddress)) {
- Log.d(TAG, "Renewed lease not for our current IP address!");
- notifyFailure();
- transitionTo(mDhcpInitState);
- }
- setDhcpLeaseExpiry(packet);
- // Updating our notion of DhcpResults here only causes the
- // DNS servers and routes to be updated in LinkProperties
- // in IpManager and by any overridden relevant handlers of
- // the registered IpManager.Callback. IP address changes
- // are not supported here.
- acceptDhcpResults(results, mLeaseMsg);
- transitionTo(mDhcpBoundState);
- }
- } else if (packet instanceof DhcpNakPacket) {
- Log.d(TAG, "Received NAK, returning to INIT");
- notifyFailure();
- transitionTo(mDhcpInitState);
- }
- }
- }
-
- class DhcpRenewingState extends DhcpReacquiringState {
- public DhcpRenewingState() {
- mLeaseMsg = "Renewed";
- }
-
- @Override
- public boolean processMessage(Message message) {
- if (super.processMessage(message) == HANDLED) {
- return HANDLED;
- }
-
- switch (message.what) {
- case CMD_REBIND_DHCP:
- transitionTo(mDhcpRebindingState);
- return HANDLED;
- default:
- return NOT_HANDLED;
- }
- }
-
- @Override
- protected Inet4Address packetDestination() {
- // Not specifying a SERVER_IDENTIFIER option is a violation of RFC 2131, but...
- // http://b/25343517 . Try to make things work anyway by using broadcast renews.
- return (mDhcpLease.serverAddress != null) ?
- mDhcpLease.serverAddress : INADDR_BROADCAST;
- }
- }
-
- class DhcpRebindingState extends DhcpReacquiringState {
- public DhcpRebindingState() {
- mLeaseMsg = "Rebound";
- }
-
- @Override
- public void enter() {
- super.enter();
-
- // We need to broadcast and possibly reconnect the socket to a
- // completely different server.
- closeQuietly(mUdpSock);
- if (!initUdpSocket()) {
- Log.e(TAG, "Failed to recreate UDP socket");
- transitionTo(mDhcpInitState);
- }
- }
-
- @Override
- protected Inet4Address packetDestination() {
- return INADDR_BROADCAST;
- }
- }
-
- class DhcpInitRebootState extends LoggingState {
- }
-
- class DhcpRebootingState extends LoggingState {
- }
-
- private void logError(int errorCode) {
- mMetricsLog.log(mIfaceName, new DhcpErrorEvent(errorCode));
- }
-
- private void logState(String name, int durationMs) {
- mMetricsLog.log(mIfaceName, new DhcpClientEvent(name, durationMs));
- }
}
diff --git a/services/net/java/android/net/ip/ConnectivityPacketTracker.java b/services/net/java/android/net/ip/ConnectivityPacketTracker.java
deleted file mode 100644
index e6ddbbc..0000000
--- a/services/net/java/android/net/ip/ConnectivityPacketTracker.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2016 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.ip;
-
-import static android.system.OsConstants.*;
-
-import android.net.NetworkUtils;
-import android.net.util.PacketReader;
-import android.net.util.ConnectivityPacketSummary;
-import android.net.util.InterfaceParams;
-import android.os.Handler;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.PacketSocketAddress;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.LocalLog;
-
-import libcore.io.IoBridge;
-import libcore.util.HexEncoding;
-
-import java.io.FileDescriptor;
-import java.io.InterruptedIOException;
-import java.io.IOException;
-import java.net.SocketException;
-
-
-/**
- * Critical connectivity packet tracking daemon.
- *
- * Tracks ARP, DHCPv4, and IPv6 RS/RA/NS/NA packets.
- *
- * This class's constructor, start() and stop() methods must only be called
- * from the same thread on which the passed in |log| is accessed.
- *
- * Log lines include a hexdump of the packet, which can be decoded via:
- *
- * echo -n H3XSTR1NG | sed -e 's/\([0-9A-F][0-9A-F]\)/\1 /g' -e 's/^/000000 /'
- * | text2pcap - -
- * | tcpdump -n -vv -e -r -
- *
- * @hide
- */
-public class ConnectivityPacketTracker {
- private static final String TAG = ConnectivityPacketTracker.class.getSimpleName();
- private static final boolean DBG = false;
- private static final String MARK_START = "--- START ---";
- private static final String MARK_STOP = "--- STOP ---";
- private static final String MARK_NAMED_START = "--- START (%s) ---";
- private static final String MARK_NAMED_STOP = "--- STOP (%s) ---";
-
- private final String mTag;
- private final LocalLog mLog;
- private final PacketReader mPacketListener;
- private boolean mRunning;
- private String mDisplayName;
-
- public ConnectivityPacketTracker(Handler h, InterfaceParams ifParams, LocalLog log) {
- if (ifParams == null) throw new IllegalArgumentException("null InterfaceParams");
-
- mTag = TAG + "." + ifParams.name;
- mLog = log;
- mPacketListener = new PacketListener(h, ifParams);
- }
-
- public void start(String displayName) {
- mRunning = true;
- mDisplayName = displayName;
- mPacketListener.start();
- }
-
- public void stop() {
- mPacketListener.stop();
- mRunning = false;
- mDisplayName = null;
- }
-
- private final class PacketListener extends PacketReader {
- private final InterfaceParams mInterface;
-
- PacketListener(Handler h, InterfaceParams ifParams) {
- super(h, ifParams.defaultMtu);
- mInterface = ifParams;
- }
-
- @Override
- protected FileDescriptor createFd() {
- FileDescriptor s = null;
- try {
- s = Os.socket(AF_PACKET, SOCK_RAW, 0);
- NetworkUtils.attachControlPacketFilter(s, ARPHRD_ETHER);
- Os.bind(s, new PacketSocketAddress((short) ETH_P_ALL, mInterface.index));
- } catch (ErrnoException | IOException e) {
- logError("Failed to create packet tracking socket: ", e);
- closeFd(s);
- return null;
- }
- return s;
- }
-
- @Override
- protected void handlePacket(byte[] recvbuf, int length) {
- final String summary = ConnectivityPacketSummary.summarize(
- mInterface.macAddr, recvbuf, length);
- if (summary == null) return;
-
- if (DBG) Log.d(mTag, summary);
- addLogEntry(summary +
- "\n[" + new String(HexEncoding.encode(recvbuf, 0, length)) + "]");
- }
-
- @Override
- protected void onStart() {
- final String msg = TextUtils.isEmpty(mDisplayName)
- ? MARK_START
- : String.format(MARK_NAMED_START, mDisplayName);
- mLog.log(msg);
- }
-
- @Override
- protected void onStop() {
- String msg = TextUtils.isEmpty(mDisplayName)
- ? MARK_STOP
- : String.format(MARK_NAMED_STOP, mDisplayName);
- if (!mRunning) msg += " (packet listener stopped unexpectedly)";
- mLog.log(msg);
- }
-
- @Override
- protected void logError(String msg, Exception e) {
- Log.e(mTag, msg, e);
- addLogEntry(msg + e);
- }
-
- private void addLogEntry(String entry) {
- mLog.log(entry);
- }
- }
-}
diff --git a/services/net/java/android/net/ip/InterfaceController.java b/services/net/java/android/net/ip/InterfaceController.java
index 02e4f87..b3af67c 100644
--- a/services/net/java/android/net/ip/InterfaceController.java
+++ b/services/net/java/android/net/ip/InterfaceController.java
@@ -18,14 +18,14 @@
import android.net.INetd;
import android.net.InterfaceConfiguration;
+import android.net.InterfaceConfigurationParcel;
import android.net.LinkAddress;
-import android.net.util.NetdService;
import android.net.util.SharedLog;
-import android.os.INetworkManagementService;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.system.OsConstants;
+import java.net.Inet4Address;
import java.net.InetAddress;
@@ -40,76 +40,96 @@
private final static boolean DBG = false;
private final String mIfName;
- private final INetworkManagementService mNMS;
private final INetd mNetd;
private final SharedLog mLog;
- public InterfaceController(String ifname, INetworkManagementService nms, INetd netd,
- SharedLog log) {
+ public InterfaceController(String ifname, INetd netd, SharedLog log) {
mIfName = ifname;
- mNMS = nms;
mNetd = netd;
mLog = log;
}
+ private boolean setInterfaceConfig(InterfaceConfiguration config) {
+ final InterfaceConfigurationParcel cfgParcel = config.toParcel(mIfName);
+
+ try {
+ mNetd.interfaceSetCfg(cfgParcel);
+ } catch (RemoteException | ServiceSpecificException e) {
+ logError("Setting IPv4 address to %s/%d failed: %s",
+ cfgParcel.ipv4Addr, cfgParcel.prefixLength, e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Set the IPv4 address of the interface.
+ */
public boolean setIPv4Address(LinkAddress address) {
- final InterfaceConfiguration ifcg = new InterfaceConfiguration();
- ifcg.setLinkAddress(address);
- try {
- mNMS.setInterfaceConfig(mIfName, ifcg);
- if (DBG) mLog.log("IPv4 configuration succeeded");
- } catch (IllegalStateException | RemoteException e) {
- logError("IPv4 configuration failed: %s", e);
+ if (!(address.getAddress() instanceof Inet4Address)) {
return false;
}
- return true;
+ final InterfaceConfiguration ifConfig = new InterfaceConfiguration();
+ ifConfig.setLinkAddress(address);
+ return setInterfaceConfig(ifConfig);
}
+ /**
+ * Clear the IPv4Address of the interface.
+ */
public boolean clearIPv4Address() {
+ final InterfaceConfiguration ifConfig = new InterfaceConfiguration();
+ ifConfig.setLinkAddress(new LinkAddress("0.0.0.0/0"));
+ return setInterfaceConfig(ifConfig);
+ }
+
+ private boolean setEnableIPv6(boolean enabled) {
try {
- final InterfaceConfiguration ifcg = new InterfaceConfiguration();
- ifcg.setLinkAddress(new LinkAddress("0.0.0.0/0"));
- mNMS.setInterfaceConfig(mIfName, ifcg);
- } catch (IllegalStateException | RemoteException e) {
- logError("Failed to clear IPv4 address on interface %s: %s", mIfName, e);
+ mNetd.interfaceSetEnableIPv6(mIfName, enabled);
+ } catch (RemoteException | ServiceSpecificException e) {
+ logError("%s IPv6 failed: %s", (enabled ? "enabling" : "disabling"), e);
return false;
}
return true;
}
+ /**
+ * Enable IPv6 on the interface.
+ */
public boolean enableIPv6() {
- try {
- mNMS.enableIpv6(mIfName);
- } catch (IllegalStateException | RemoteException e) {
- logError("enabling IPv6 failed: %s", e);
- return false;
- }
- return true;
+ return setEnableIPv6(true);
}
+ /**
+ * Disable IPv6 on the interface.
+ */
public boolean disableIPv6() {
- try {
- mNMS.disableIpv6(mIfName);
- } catch (IllegalStateException | RemoteException e) {
- logError("disabling IPv6 failed: %s", e);
- return false;
- }
- return true;
+ return setEnableIPv6(false);
}
+ /**
+ * Enable or disable IPv6 privacy extensions on the interface.
+ * @param enabled Whether the extensions should be enabled.
+ */
public boolean setIPv6PrivacyExtensions(boolean enabled) {
try {
- mNMS.setInterfaceIpv6PrivacyExtensions(mIfName, enabled);
- } catch (IllegalStateException | RemoteException e) {
- logError("error setting IPv6 privacy extensions: %s", e);
+ mNetd.interfaceSetIPv6PrivacyExtensions(mIfName, enabled);
+ } catch (RemoteException | ServiceSpecificException e) {
+ logError("error %s IPv6 privacy extensions: %s",
+ (enabled ? "enabling" : "disabling"), e);
return false;
}
return true;
}
+ /**
+ * Set IPv6 address generation mode on the interface.
+ *
+ * <p>IPv6 should be disabled before changing the mode.
+ */
public boolean setIPv6AddrGenModeIfSupported(int mode) {
try {
- mNMS.setIPv6AddrGenMode(mIfName, mode);
+ mNetd.setIPv6AddrGenMode(mIfName, mode);
} catch (RemoteException e) {
logError("Unable to set IPv6 addrgen mode: %s", e);
return false;
@@ -122,10 +142,16 @@
return true;
}
+ /**
+ * Add an address to the interface.
+ */
public boolean addAddress(LinkAddress addr) {
return addAddress(addr.getAddress(), addr.getPrefixLength());
}
+ /**
+ * Add an address to the interface.
+ */
public boolean addAddress(InetAddress ip, int prefixLen) {
try {
mNetd.interfaceAddAddress(mIfName, ip.getHostAddress(), prefixLen);
@@ -136,6 +162,9 @@
return true;
}
+ /**
+ * Remove an address from the interface.
+ */
public boolean removeAddress(InetAddress ip, int prefixLen) {
try {
mNetd.interfaceDelAddress(mIfName, ip.getHostAddress(), prefixLen);
@@ -146,9 +175,12 @@
return true;
}
+ /**
+ * Remove all addresses from the interface.
+ */
public boolean clearAllAddresses() {
try {
- mNMS.clearInterfaceAddresses(mIfName);
+ mNetd.interfaceClearAddrs(mIfName);
} catch (Exception e) {
logError("Failed to clear addresses: %s", e);
return false;
diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java
index 0176dd4..a61c2ef 100644
--- a/services/net/java/android/net/ip/IpClient.java
+++ b/services/net/java/android/net/ip/IpClient.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -16,184 +16,53 @@
package android.net.ip;
-import com.android.internal.util.HexDump;
-import com.android.internal.util.MessageUtils;
-import com.android.internal.util.WakeupMessage;
+import static android.net.shared.LinkPropertiesParcelableUtil.toStableParcelable;
import android.content.Context;
-import android.net.DhcpResults;
-import android.net.INetd;
-import android.net.IpPrefix;
-import android.net.LinkAddress;
-import android.net.LinkProperties.ProvisioningChange;
import android.net.LinkProperties;
import android.net.Network;
import android.net.ProxyInfo;
-import android.net.RouteInfo;
import android.net.StaticIpConfiguration;
import android.net.apf.ApfCapabilities;
-import android.net.apf.ApfFilter;
-import android.net.dhcp.DhcpClient;
-import android.net.metrics.IpConnectivityLog;
-import android.net.metrics.IpManagerEvent;
-import android.net.util.InterfaceParams;
-import android.net.util.MultinetworkPolicyTracker;
-import android.net.util.NetdService;
-import android.net.util.NetworkConstants;
-import android.net.util.SharedLog;
import android.os.ConditionVariable;
import android.os.INetworkManagementService;
-import android.os.Message;
import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.LocalLog;
import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.R;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.IState;
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
-import com.android.server.net.NetlinkTracker;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.SocketException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.List;
-import java.util.Set;
-import java.util.StringJoiner;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CountDownLatch;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-
/**
- * IpClient
- *
- * This class provides the interface to IP-layer provisioning and maintenance
- * functionality that can be used by transport layers like Wi-Fi, Ethernet,
- * et cetera.
- *
- * [ Lifetime ]
- * IpClient is designed to be instantiated as soon as the interface name is
- * known and can be as long-lived as the class containing it (i.e. declaring
- * it "private final" is okay).
- *
+ * Proxy for the IpClient in the NetworkStack. To be removed once clients are migrated.
* @hide
*/
-public class IpClient extends StateMachine {
- private static final boolean DBG = false;
+public class IpClient {
+ private static final String TAG = IpClient.class.getSimpleName();
+ private static final int IPCLIENT_BLOCK_TIMEOUT_MS = 10_000;
- // For message logging.
- private static final Class[] sMessageClasses = { IpClient.class, DhcpClient.class };
- private static final SparseArray<String> sWhatToString =
- MessageUtils.findMessageNames(sMessageClasses);
- // Two static concurrent hashmaps of interface name to logging classes.
- // One holds StateMachine logs and the other connectivity packet logs.
- private static final ConcurrentHashMap<String, SharedLog> sSmLogs = new ConcurrentHashMap<>();
- private static final ConcurrentHashMap<String, LocalLog> sPktLogs = new ConcurrentHashMap<>();
+ public static final String DUMP_ARG = "ipclient";
- // If |args| is non-empty, assume it's a list of interface names for which
- // we should print IpClient logs (filter out all others).
- public static void dumpAllLogs(PrintWriter writer, String[] args) {
- for (String ifname : sSmLogs.keySet()) {
- if (!ArrayUtils.isEmpty(args) && !ArrayUtils.contains(args, ifname)) continue;
+ private final ConditionVariable mIpClientCv;
+ private final ConditionVariable mShutdownCv;
- writer.println(String.format("--- BEGIN %s ---", ifname));
-
- final SharedLog smLog = sSmLogs.get(ifname);
- if (smLog != null) {
- writer.println("State machine log:");
- smLog.dump(null, writer, null);
- }
-
- writer.println("");
-
- final LocalLog pktLog = sPktLogs.get(ifname);
- if (pktLog != null) {
- writer.println("Connectivity packet log:");
- pktLog.readOnlyLocalLog().dump(null, writer, null);
- }
-
- writer.println(String.format("--- END %s ---", ifname));
- }
- }
+ private volatile IIpClient mIpClient;
/**
- * Callbacks for handling IpClient events.
- *
- * These methods are called by IpClient on its own thread. Implementations
- * of this class MUST NOT carry out long-running computations or hold locks
- * for which there might be contention with other code calling public
- * methods of the same IpClient instance.
+ * @see IpClientCallbacks
*/
- public static class Callback {
- // In order to receive onPreDhcpAction(), call #withPreDhcpAction()
- // when constructing a ProvisioningConfiguration.
- //
- // Implementations of onPreDhcpAction() must call
- // IpClient#completedPreDhcpAction() to indicate that DHCP is clear
- // to proceed.
- public void onPreDhcpAction() {}
- public void onPostDhcpAction() {}
+ public static class Callback extends IpClientCallbacks {}
- // This is purely advisory and not an indication of provisioning
- // success or failure. This is only here for callers that want to
- // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
- // DHCPv4 or static IPv4 configuration failure or success can be
- // determined by whether or not the passed-in DhcpResults object is
- // null or not.
- public void onNewDhcpResults(DhcpResults dhcpResults) {}
-
- public void onProvisioningSuccess(LinkProperties newLp) {}
- public void onProvisioningFailure(LinkProperties newLp) {}
-
- // Invoked on LinkProperties changes.
- public void onLinkPropertiesChange(LinkProperties newLp) {}
-
- // Called when the internal IpReachabilityMonitor (if enabled) has
- // detected the loss of a critical number of required neighbors.
- public void onReachabilityLost(String logMsg) {}
-
- // Called when the IpClient state machine terminates.
- public void onQuit() {}
-
- // Install an APF program to filter incoming packets.
- public void installPacketFilter(byte[] filter) {}
-
- // Asynchronously read back the APF program & data buffer from the wifi driver.
- // Due to Wifi HAL limitations, the current implementation only supports dumping the entire
- // buffer. In response to this request, the driver returns the data buffer asynchronously
- // by sending an IpClient#EVENT_READ_PACKET_FILTER_COMPLETE message.
- public void startReadPacketFilter() {}
-
- // If multicast filtering cannot be accomplished with APF, this function will be called to
- // actuate multicast filtering using another means.
- public void setFallbackMulticastFilter(boolean enabled) {}
-
- // Enabled/disable Neighbor Discover offload functionality. This is
- // called, for example, whenever 464xlat is being started or stopped.
- public void setNeighborDiscoveryOffload(boolean enable) {}
- }
-
+ /**
+ * IpClient callback that allows clients to block until provisioning is complete.
+ */
public static class WaitForProvisioningCallback extends Callback {
private final ConditionVariable mCV = new ConditionVariable();
private LinkProperties mCallbackLinkProperties;
+ /**
+ * Block until either {@link #onProvisioningSuccess(LinkProperties)} or
+ * {@link #onProvisioningFailure(LinkProperties)} is called.
+ */
public LinkProperties waitForProvisioning() {
mCV.block();
return mCallbackLinkProperties;
@@ -212,1720 +81,240 @@
}
}
- // Use a wrapper class to log in order to ensure complete and detailed
- // logging. This method is lighter weight than annotations/reflection
- // and has the following benefits:
- //
- // - No invoked method can be forgotten.
- // Any new method added to IpClient.Callback must be overridden
- // here or it will never be called.
- //
- // - No invoking call site can be forgotten.
- // Centralized logging in this way means call sites don't need to
- // remember to log, and therefore no call site can be forgotten.
- //
- // - No variation in log format among call sites.
- // Encourages logging of any available arguments, and all call sites
- // are necessarily logged identically.
- //
- // NOTE: Log first because passed objects may or may not be thread-safe and
- // once passed on to the callback they may be modified by another thread.
- //
- // TODO: Find an lighter weight approach.
- private class LoggingCallbackWrapper extends Callback {
- private static final String PREFIX = "INVOKE ";
- private final Callback mCallback;
-
- public LoggingCallbackWrapper(Callback callback) {
- mCallback = (callback != null) ? callback : new Callback();
- }
-
- private void log(String msg) {
- mLog.log(PREFIX + msg);
+ private class CallbackImpl extends IpClientUtil.IpClientCallbacksProxy {
+ /**
+ * Create a new IpClientCallbacksProxy.
+ */
+ CallbackImpl(IpClientCallbacks cb) {
+ super(cb);
}
@Override
- public void onPreDhcpAction() {
- log("onPreDhcpAction()");
- mCallback.onPreDhcpAction();
+ public void onIpClientCreated(IIpClient ipClient) {
+ mIpClient = ipClient;
+ mIpClientCv.open();
+ super.onIpClientCreated(ipClient);
}
- @Override
- public void onPostDhcpAction() {
- log("onPostDhcpAction()");
- mCallback.onPostDhcpAction();
- }
- @Override
- public void onNewDhcpResults(DhcpResults dhcpResults) {
- log("onNewDhcpResults({" + dhcpResults + "})");
- mCallback.onNewDhcpResults(dhcpResults);
- }
- @Override
- public void onProvisioningSuccess(LinkProperties newLp) {
- log("onProvisioningSuccess({" + newLp + "})");
- mCallback.onProvisioningSuccess(newLp);
- }
- @Override
- public void onProvisioningFailure(LinkProperties newLp) {
- log("onProvisioningFailure({" + newLp + "})");
- mCallback.onProvisioningFailure(newLp);
- }
- @Override
- public void onLinkPropertiesChange(LinkProperties newLp) {
- log("onLinkPropertiesChange({" + newLp + "})");
- mCallback.onLinkPropertiesChange(newLp);
- }
- @Override
- public void onReachabilityLost(String logMsg) {
- log("onReachabilityLost(" + logMsg + ")");
- mCallback.onReachabilityLost(logMsg);
- }
+
@Override
public void onQuit() {
- log("onQuit()");
- mCallback.onQuit();
- }
- @Override
- public void installPacketFilter(byte[] filter) {
- log("installPacketFilter(byte[" + filter.length + "])");
- mCallback.installPacketFilter(filter);
- }
- @Override
- public void startReadPacketFilter() {
- log("startReadPacketFilter()");
- mCallback.startReadPacketFilter();
- }
- @Override
- public void setFallbackMulticastFilter(boolean enabled) {
- log("setFallbackMulticastFilter(" + enabled + ")");
- mCallback.setFallbackMulticastFilter(enabled);
- }
- @Override
- public void setNeighborDiscoveryOffload(boolean enable) {
- log("setNeighborDiscoveryOffload(" + enable + ")");
- mCallback.setNeighborDiscoveryOffload(enable);
+ mShutdownCv.open();
+ super.onQuit();
}
}
/**
- * This class encapsulates parameters to be passed to
- * IpClient#startProvisioning(). A defensive copy is made by IpClient
- * and the values specified herein are in force until IpClient#stop()
- * is called.
- *
- * Example use:
- *
- * final ProvisioningConfiguration config =
- * mIpClient.buildProvisioningConfiguration()
- * .withPreDhcpAction()
- * .withProvisioningTimeoutMs(36 * 1000)
- * .build();
- * mIpClient.startProvisioning(config);
- * ...
- * mIpClient.stop();
- *
- * The specified provisioning configuration will only be active until
- * IpClient#stop() is called. Future calls to IpClient#startProvisioning()
- * must specify the configuration again.
+ * Create a new IpClient.
*/
- public static class ProvisioningConfiguration {
- // TODO: Delete this default timeout once those callers that care are
- // fixed to pass in their preferred timeout.
- //
- // We pick 36 seconds so we can send DHCP requests at
- //
- // t=0, t=2, t=6, t=14, t=30
- //
- // allowing for 10% jitter.
- private static final int DEFAULT_TIMEOUT_MS = 36 * 1000;
+ public IpClient(Context context, String iface, Callback callback) {
+ mIpClientCv = new ConditionVariable(false);
+ mShutdownCv = new ConditionVariable(false);
- public static class Builder {
- private ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
-
- public Builder withoutIPv4() {
- mConfig.mEnableIPv4 = false;
- return this;
- }
-
- public Builder withoutIPv6() {
- mConfig.mEnableIPv6 = false;
- return this;
- }
-
- public Builder withoutMultinetworkPolicyTracker() {
- mConfig.mUsingMultinetworkPolicyTracker = false;
- return this;
- }
-
- public Builder withoutIpReachabilityMonitor() {
- mConfig.mUsingIpReachabilityMonitor = false;
- return this;
- }
-
- public Builder withPreDhcpAction() {
- mConfig.mRequestedPreDhcpActionMs = DEFAULT_TIMEOUT_MS;
- return this;
- }
-
- public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
- mConfig.mRequestedPreDhcpActionMs = dhcpActionTimeoutMs;
- return this;
- }
-
- public Builder withInitialConfiguration(InitialConfiguration initialConfig) {
- mConfig.mInitialConfig = initialConfig;
- return this;
- }
-
- public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
- mConfig.mStaticIpConfig = staticConfig;
- return this;
- }
-
- public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
- mConfig.mApfCapabilities = apfCapabilities;
- return this;
- }
-
- public Builder withProvisioningTimeoutMs(int timeoutMs) {
- mConfig.mProvisioningTimeoutMs = timeoutMs;
- return this;
- }
-
- public Builder withRandomMacAddress() {
- mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64;
- return this;
- }
-
- public Builder withStableMacAddress() {
- mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
- return this;
- }
-
- public Builder withNetwork(Network network) {
- mConfig.mNetwork = network;
- return this;
- }
-
- public Builder withDisplayName(String displayName) {
- mConfig.mDisplayName = displayName;
- return this;
- }
-
- public ProvisioningConfiguration build() {
- return new ProvisioningConfiguration(mConfig);
- }
- }
-
- /* package */ boolean mEnableIPv4 = true;
- /* package */ boolean mEnableIPv6 = true;
- /* package */ boolean mUsingMultinetworkPolicyTracker = true;
- /* package */ boolean mUsingIpReachabilityMonitor = true;
- /* package */ int mRequestedPreDhcpActionMs;
- /* package */ InitialConfiguration mInitialConfig;
- /* package */ StaticIpConfiguration mStaticIpConfig;
- /* package */ ApfCapabilities mApfCapabilities;
- /* package */ int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
- /* package */ int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
- /* package */ Network mNetwork = null;
- /* package */ String mDisplayName = null;
-
- public ProvisioningConfiguration() {} // used by Builder
-
- public ProvisioningConfiguration(ProvisioningConfiguration other) {
- mEnableIPv4 = other.mEnableIPv4;
- mEnableIPv6 = other.mEnableIPv6;
- mUsingMultinetworkPolicyTracker = other.mUsingMultinetworkPolicyTracker;
- mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
- mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs;
- mInitialConfig = InitialConfiguration.copy(other.mInitialConfig);
- mStaticIpConfig = other.mStaticIpConfig;
- mApfCapabilities = other.mApfCapabilities;
- mProvisioningTimeoutMs = other.mProvisioningTimeoutMs;
- mIPv6AddrGenMode = other.mIPv6AddrGenMode;
- mNetwork = other.mNetwork;
- mDisplayName = other.mDisplayName;
- }
-
- @Override
- public String toString() {
- return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
- .add("mEnableIPv4: " + mEnableIPv4)
- .add("mEnableIPv6: " + mEnableIPv6)
- .add("mUsingMultinetworkPolicyTracker: " + mUsingMultinetworkPolicyTracker)
- .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
- .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs)
- .add("mInitialConfig: " + mInitialConfig)
- .add("mStaticIpConfig: " + mStaticIpConfig)
- .add("mApfCapabilities: " + mApfCapabilities)
- .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs)
- .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode)
- .add("mNetwork: " + mNetwork)
- .add("mDisplayName: " + mDisplayName)
- .toString();
- }
-
- public boolean isValid() {
- return (mInitialConfig == null) || mInitialConfig.isValid();
- }
- }
-
- public static class InitialConfiguration {
- public final Set<LinkAddress> ipAddresses = new HashSet<>();
- public final Set<IpPrefix> directlyConnectedRoutes = new HashSet<>();
- public final Set<InetAddress> dnsServers = new HashSet<>();
- public Inet4Address gateway; // WiFi legacy behavior with static ipv4 config
-
- public static InitialConfiguration copy(InitialConfiguration config) {
- if (config == null) {
- return null;
- }
- InitialConfiguration configCopy = new InitialConfiguration();
- configCopy.ipAddresses.addAll(config.ipAddresses);
- configCopy.directlyConnectedRoutes.addAll(config.directlyConnectedRoutes);
- configCopy.dnsServers.addAll(config.dnsServers);
- return configCopy;
- }
-
- @Override
- public String toString() {
- return String.format(
- "InitialConfiguration(IPs: {%s}, prefixes: {%s}, DNS: {%s}, v4 gateway: %s)",
- join(", ", ipAddresses), join(", ", directlyConnectedRoutes),
- join(", ", dnsServers), gateway);
- }
-
- public boolean isValid() {
- if (ipAddresses.isEmpty()) {
- return false;
- }
-
- // For every IP address, there must be at least one prefix containing that address.
- for (LinkAddress addr : ipAddresses) {
- if (!any(directlyConnectedRoutes, (p) -> p.contains(addr.getAddress()))) {
- return false;
- }
- }
- // For every dns server, there must be at least one prefix containing that address.
- for (InetAddress addr : dnsServers) {
- if (!any(directlyConnectedRoutes, (p) -> p.contains(addr))) {
- return false;
- }
- }
- // All IPv6 LinkAddresses have an RFC7421-suitable prefix length
- // (read: compliant with RFC4291#section2.5.4).
- if (any(ipAddresses, not(InitialConfiguration::isPrefixLengthCompliant))) {
- return false;
- }
- // If directlyConnectedRoutes contains an IPv6 default route
- // then ipAddresses MUST contain at least one non-ULA GUA.
- if (any(directlyConnectedRoutes, InitialConfiguration::isIPv6DefaultRoute)
- && all(ipAddresses, not(InitialConfiguration::isIPv6GUA))) {
- return false;
- }
- // The prefix length of routes in directlyConnectedRoutes be within reasonable
- // bounds for IPv6: /48-/64 just as we’d accept in RIOs.
- if (any(directlyConnectedRoutes, not(InitialConfiguration::isPrefixLengthCompliant))) {
- return false;
- }
- // There no more than one IPv4 address
- if (ipAddresses.stream().filter(LinkAddress::isIPv4).count() > 1) {
- return false;
- }
-
- return true;
- }
-
- /**
- * @return true if the given list of addressess and routes satisfies provisioning for this
- * InitialConfiguration. LinkAddresses and RouteInfo objects are not compared with equality
- * because addresses and routes seen by Netlink will contain additional fields like flags,
- * interfaces, and so on. If this InitialConfiguration has no IP address specified, the
- * provisioning check always fails.
- *
- * If the given list of routes is null, only addresses are taken into considerations.
- */
- public boolean isProvisionedBy(List<LinkAddress> addresses, List<RouteInfo> routes) {
- if (ipAddresses.isEmpty()) {
- return false;
- }
-
- for (LinkAddress addr : ipAddresses) {
- if (!any(addresses, (addrSeen) -> addr.isSameAddressAs(addrSeen))) {
- return false;
- }
- }
-
- if (routes != null) {
- for (IpPrefix prefix : directlyConnectedRoutes) {
- if (!any(routes, (routeSeen) -> isDirectlyConnectedRoute(routeSeen, prefix))) {
- return false;
- }
- }
- }
-
- return true;
- }
-
- private static boolean isDirectlyConnectedRoute(RouteInfo route, IpPrefix prefix) {
- return !route.hasGateway() && prefix.equals(route.getDestination());
- }
-
- private static boolean isPrefixLengthCompliant(LinkAddress addr) {
- return addr.isIPv4() || isCompliantIPv6PrefixLength(addr.getPrefixLength());
- }
-
- private static boolean isPrefixLengthCompliant(IpPrefix prefix) {
- return prefix.isIPv4() || isCompliantIPv6PrefixLength(prefix.getPrefixLength());
- }
-
- private static boolean isCompliantIPv6PrefixLength(int prefixLength) {
- return (NetworkConstants.RFC6177_MIN_PREFIX_LENGTH <= prefixLength)
- && (prefixLength <= NetworkConstants.RFC7421_PREFIX_LENGTH);
- }
-
- private static boolean isIPv6DefaultRoute(IpPrefix prefix) {
- return prefix.getAddress().equals(Inet6Address.ANY);
- }
-
- private static boolean isIPv6GUA(LinkAddress addr) {
- return addr.isIPv6() && addr.isGlobalPreferred();
- }
- }
-
- public static final String DUMP_ARG = "ipclient";
- public static final String DUMP_ARG_CONFIRM = "confirm";
-
- private static final int CMD_TERMINATE_AFTER_STOP = 1;
- private static final int CMD_STOP = 2;
- private static final int CMD_START = 3;
- private static final int CMD_CONFIRM = 4;
- private static final int EVENT_PRE_DHCP_ACTION_COMPLETE = 5;
- // Sent by NetlinkTracker to communicate netlink events.
- private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6;
- private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 7;
- private static final int CMD_UPDATE_HTTP_PROXY = 8;
- private static final int CMD_SET_MULTICAST_FILTER = 9;
- private static final int EVENT_PROVISIONING_TIMEOUT = 10;
- private static final int EVENT_DHCPACTION_TIMEOUT = 11;
- private static final int EVENT_READ_PACKET_FILTER_COMPLETE = 12;
-
- // Internal commands to use instead of trying to call transitionTo() inside
- // a given State's enter() method. Calling transitionTo() from enter/exit
- // encounters a Log.wtf() that can cause trouble on eng builds.
- private static final int CMD_JUMP_STARTED_TO_RUNNING = 100;
- private static final int CMD_JUMP_RUNNING_TO_STOPPING = 101;
- private static final int CMD_JUMP_STOPPING_TO_STOPPED = 102;
-
- private static final int MAX_LOG_RECORDS = 500;
- private static final int MAX_PACKET_RECORDS = 100;
-
- private static final boolean NO_CALLBACKS = false;
- private static final boolean SEND_CALLBACKS = true;
-
- // This must match the interface prefix in clatd.c.
- // TODO: Revert this hack once IpClient and Nat464Xlat work in concert.
- private static final String CLAT_PREFIX = "v4-";
-
- private static final int IMMEDIATE_FAILURE_DURATION = 0;
-
- private final State mStoppedState = new StoppedState();
- private final State mStoppingState = new StoppingState();
- private final State mStartedState = new StartedState();
- private final State mRunningState = new RunningState();
-
- private final String mTag;
- private final Context mContext;
- private final String mInterfaceName;
- private final String mClatInterfaceName;
- @VisibleForTesting
- protected final Callback mCallback;
- private final Dependencies mDependencies;
- private final CountDownLatch mShutdownLatch;
- private final INetworkManagementService mNwService;
- private final NetlinkTracker mNetlinkTracker;
- private final WakeupMessage mProvisioningTimeoutAlarm;
- private final WakeupMessage mDhcpActionTimeoutAlarm;
- private final SharedLog mLog;
- private final LocalLog mConnectivityPacketLog;
- private final MessageHandlingLogger mMsgStateLogger;
- private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
- private final InterfaceController mInterfaceCtrl;
-
- private InterfaceParams mInterfaceParams;
-
- /**
- * Non-final member variables accessed only from within our StateMachine.
- */
- private LinkProperties mLinkProperties;
- private ProvisioningConfiguration mConfiguration;
- private MultinetworkPolicyTracker mMultinetworkPolicyTracker;
- private IpReachabilityMonitor mIpReachabilityMonitor;
- private DhcpClient mDhcpClient;
- private DhcpResults mDhcpResults;
- private String mTcpBufferSizes;
- private ProxyInfo mHttpProxy;
- private ApfFilter mApfFilter;
- private boolean mMulticastFiltering;
- private long mStartTimeMillis;
-
- /**
- * Reading the snapshot is an asynchronous operation initiated by invoking
- * Callback.startReadPacketFilter() and completed when the WiFi Service responds with an
- * EVENT_READ_PACKET_FILTER_COMPLETE message. The mApfDataSnapshotComplete condition variable
- * signals when a new snapshot is ready.
- */
- private final ConditionVariable mApfDataSnapshotComplete = new ConditionVariable();
-
- public static class Dependencies {
- public INetworkManagementService getNMS() {
- return INetworkManagementService.Stub.asInterface(
- ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
- }
-
- public INetd getNetd() {
- return NetdService.getInstance();
- }
-
- public InterfaceParams getInterfaceParams(String ifname) {
- return InterfaceParams.getByName(ifname);
- }
- }
-
- public IpClient(Context context, String ifName, Callback callback) {
- this(context, ifName, callback, new Dependencies());
+ IpClientUtil.makeIpClient(context, iface, new CallbackImpl(callback));
}
/**
- * An expanded constructor, useful for dependency injection.
- * TODO: migrate all test users to mock IpClient directly and remove this ctor.
+ * @see IpClient#IpClient(Context, String, IpClient.Callback)
*/
- public IpClient(Context context, String ifName, Callback callback,
- INetworkManagementService nwService) {
- this(context, ifName, callback, new Dependencies() {
- @Override
- public INetworkManagementService getNMS() { return nwService; }
- });
+ public IpClient(Context context, String iface, Callback callback,
+ INetworkManagementService nms) {
+ this(context, iface, callback);
}
- @VisibleForTesting
- IpClient(Context context, String ifName, Callback callback, Dependencies deps) {
- super(IpClient.class.getSimpleName() + "." + ifName);
- Preconditions.checkNotNull(ifName);
- Preconditions.checkNotNull(callback);
-
- mTag = getName();
-
- mContext = context;
- mInterfaceName = ifName;
- mClatInterfaceName = CLAT_PREFIX + ifName;
- mCallback = new LoggingCallbackWrapper(callback);
- mDependencies = deps;
- mShutdownLatch = new CountDownLatch(1);
- mNwService = deps.getNMS();
-
- sSmLogs.putIfAbsent(mInterfaceName, new SharedLog(MAX_LOG_RECORDS, mTag));
- mLog = sSmLogs.get(mInterfaceName);
- sPktLogs.putIfAbsent(mInterfaceName, new LocalLog(MAX_PACKET_RECORDS));
- mConnectivityPacketLog = sPktLogs.get(mInterfaceName);
- mMsgStateLogger = new MessageHandlingLogger();
-
- // TODO: Consider creating, constructing, and passing in some kind of
- // InterfaceController.Dependencies class.
- mInterfaceCtrl = new InterfaceController(mInterfaceName, mNwService, deps.getNetd(), mLog);
-
- mNetlinkTracker = new NetlinkTracker(
- mInterfaceName,
- new NetlinkTracker.Callback() {
- @Override
- public void update() {
- sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED);
- }
- }) {
- @Override
- public void interfaceAdded(String iface) {
- super.interfaceAdded(iface);
- if (mClatInterfaceName.equals(iface)) {
- mCallback.setNeighborDiscoveryOffload(false);
- } else if (!mInterfaceName.equals(iface)) {
- return;
- }
-
- final String msg = "interfaceAdded(" + iface +")";
- logMsg(msg);
- }
-
- @Override
- public void interfaceRemoved(String iface) {
- super.interfaceRemoved(iface);
- // TODO: Also observe mInterfaceName going down and take some
- // kind of appropriate action.
- if (mClatInterfaceName.equals(iface)) {
- // TODO: consider sending a message to the IpClient main
- // StateMachine thread, in case "NDO enabled" state becomes
- // tied to more things that 464xlat operation.
- mCallback.setNeighborDiscoveryOffload(true);
- } else if (!mInterfaceName.equals(iface)) {
- return;
- }
-
- final String msg = "interfaceRemoved(" + iface +")";
- logMsg(msg);
- }
-
- private void logMsg(String msg) {
- Log.d(mTag, msg);
- getHandler().post(() -> { mLog.log("OBSERVED " + msg); });
- }
- };
-
- mLinkProperties = new LinkProperties();
- mLinkProperties.setInterfaceName(mInterfaceName);
-
- mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
- mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT);
- mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
- mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT);
-
- // Anything the StateMachine may access must have been instantiated
- // before this point.
- configureAndStartStateMachine();
-
- // Anything that may send messages to the StateMachine must only be
- // configured to do so after the StateMachine has started (above).
- startStateMachineUpdaters();
+ private interface IpClientAction {
+ void useIpClient(IIpClient ipClient) throws RemoteException;
}
- private void configureAndStartStateMachine() {
- addState(mStoppedState);
- addState(mStartedState);
- addState(mRunningState, mStartedState);
- addState(mStoppingState);
-
- setInitialState(mStoppedState);
-
- super.start();
- }
-
- private void startStateMachineUpdaters() {
+ private void doWithIpClient(IpClientAction action) {
+ mIpClientCv.block(IPCLIENT_BLOCK_TIMEOUT_MS);
try {
- mNwService.registerObserver(mNetlinkTracker);
+ action.useIpClient(mIpClient);
} catch (RemoteException e) {
- logError("Couldn't register NetlinkTracker: %s", e);
+ Log.e(TAG, "Error communicating with IpClient", e);
}
}
- private void stopStateMachineUpdaters() {
- try {
- mNwService.unregisterObserver(mNetlinkTracker);
- } catch (RemoteException e) {
- logError("Couldn't unregister NetlinkTracker: %s", e);
- }
+ /**
+ * Notify IpClient that PreDhcpAction is completed.
+ */
+ public void completedPreDhcpAction() {
+ doWithIpClient(c -> c.completedPreDhcpAction());
}
- @Override
- protected void onQuitting() {
- mCallback.onQuit();
- mShutdownLatch.countDown();
+ /**
+ * Confirm the provisioning configuration.
+ */
+ public void confirmConfiguration() {
+ doWithIpClient(c -> c.confirmConfiguration());
}
- // Shut down this IpClient instance altogether.
+ /**
+ * Notify IpClient that packet filter read is complete.
+ */
+ public void readPacketFilterComplete(byte[] data) {
+ doWithIpClient(c -> c.readPacketFilterComplete(data));
+ }
+
+ /**
+ * Shutdown the IpClient altogether.
+ */
public void shutdown() {
- stop();
- sendMessage(CMD_TERMINATE_AFTER_STOP);
+ doWithIpClient(c -> c.shutdown());
}
- // In order to avoid deadlock, this method MUST NOT be called on the
- // IpClient instance's thread. This prohibition includes code executed by
- // when methods on the passed-in IpClient.Callback instance are called.
+ /**
+ * Start the IpClient provisioning.
+ */
+ public void startProvisioning(ProvisioningConfiguration config) {
+ doWithIpClient(c -> c.startProvisioning(config.toStableParcelable()));
+ }
+
+ /**
+ * Stop the IpClient.
+ */
+ public void stop() {
+ doWithIpClient(c -> c.stop());
+ }
+
+ /**
+ * Set the IpClient TCP buffer sizes.
+ */
+ public void setTcpBufferSizes(String tcpBufferSizes) {
+ doWithIpClient(c -> c.setTcpBufferSizes(tcpBufferSizes));
+ }
+
+ /**
+ * Set the IpClient HTTP proxy.
+ */
+ public void setHttpProxy(ProxyInfo proxyInfo) {
+ doWithIpClient(c -> c.setHttpProxy(toStableParcelable(proxyInfo)));
+ }
+
+ /**
+ * Set the IpClient multicast filter.
+ */
+ public void setMulticastFilter(boolean enabled) {
+ doWithIpClient(c -> c.setMulticastFilter(enabled));
+ }
+
+ /**
+ * Dump IpClient logs.
+ */
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ doWithIpClient(c -> IpClientUtil.dumpIpClient(c, fd, pw, args));
+ }
+
+ /**
+ * Block until IpClient shutdown.
+ */
public void awaitShutdown() {
- try {
- mShutdownLatch.await();
- } catch (InterruptedException e) {
- mLog.e("Interrupted while awaiting shutdown: " + e);
- }
+ mShutdownCv.block(IPCLIENT_BLOCK_TIMEOUT_MS);
}
+ /**
+ * Create a new ProvisioningConfiguration.
+ */
public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
return new ProvisioningConfiguration.Builder();
}
- public void startProvisioning(ProvisioningConfiguration req) {
- if (!req.isValid()) {
- doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
- return;
- }
-
- mInterfaceParams = mDependencies.getInterfaceParams(mInterfaceName);
- if (mInterfaceParams == null) {
- logError("Failed to find InterfaceParams for " + mInterfaceName);
- doImmediateProvisioningFailure(IpManagerEvent.ERROR_INTERFACE_NOT_FOUND);
- return;
- }
-
- mCallback.setNeighborDiscoveryOffload(true);
- sendMessage(CMD_START, new ProvisioningConfiguration(req));
- }
-
- // TODO: Delete this.
- public void startProvisioning(StaticIpConfiguration staticIpConfig) {
- startProvisioning(buildProvisioningConfiguration()
- .withStaticConfiguration(staticIpConfig)
- .build());
- }
-
- public void startProvisioning() {
- startProvisioning(new ProvisioningConfiguration());
- }
-
- public void stop() {
- sendMessage(CMD_STOP);
- }
-
- public void confirmConfiguration() {
- sendMessage(CMD_CONFIRM);
- }
-
- public void completedPreDhcpAction() {
- sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
- }
-
- public void readPacketFilterComplete(byte[] data) {
- sendMessage(EVENT_READ_PACKET_FILTER_COMPLETE, data);
- }
-
/**
- * Set the TCP buffer sizes to use.
- *
- * This may be called, repeatedly, at any time before or after a call to
- * #startProvisioning(). The setting is cleared upon calling #stop().
+ * TODO: remove after migrating clients to use the shared configuration class directly.
+ * @see android.net.shared.ProvisioningConfiguration
*/
- public void setTcpBufferSizes(String tcpBufferSizes) {
- sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes);
- }
-
- /**
- * Set the HTTP Proxy configuration to use.
- *
- * This may be called, repeatedly, at any time before or after a call to
- * #startProvisioning(). The setting is cleared upon calling #stop().
- */
- public void setHttpProxy(ProxyInfo proxyInfo) {
- sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo);
- }
-
- /**
- * Enable or disable the multicast filter. Attempts to use APF to accomplish the filtering,
- * if not, Callback.setFallbackMulticastFilter() is called.
- */
- public void setMulticastFilter(boolean enabled) {
- sendMessage(CMD_SET_MULTICAST_FILTER, enabled);
- }
-
- public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
- if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) {
- // Execute confirmConfiguration() and take no further action.
- confirmConfiguration();
- return;
+ public static class ProvisioningConfiguration
+ extends android.net.shared.ProvisioningConfiguration {
+ public ProvisioningConfiguration(android.net.shared.ProvisioningConfiguration other) {
+ super(other);
}
- // Thread-unsafe access to mApfFilter but just used for debugging.
- final ApfFilter apfFilter = mApfFilter;
- final ProvisioningConfiguration provisioningConfig = mConfiguration;
- final ApfCapabilities apfCapabilities = (provisioningConfig != null)
- ? provisioningConfig.mApfCapabilities : null;
-
- IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
- pw.println(mTag + " APF dump:");
- pw.increaseIndent();
- if (apfFilter != null) {
- if (apfCapabilities.hasDataAccess()) {
- // Request a new snapshot, then wait for it.
- mApfDataSnapshotComplete.close();
- mCallback.startReadPacketFilter();
- if (!mApfDataSnapshotComplete.block(1000)) {
- pw.print("TIMEOUT: DUMPING STALE APF SNAPSHOT");
- }
- }
- apfFilter.dump(pw);
-
- } else {
- pw.print("No active ApfFilter; ");
- if (provisioningConfig == null) {
- pw.println("IpClient not yet started.");
- } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) {
- pw.println("Hardware does not support APF.");
- } else {
- pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities);
- }
- }
- pw.decreaseIndent();
- pw.println();
- pw.println(mTag + " current ProvisioningConfiguration:");
- pw.increaseIndent();
- pw.println(Objects.toString(provisioningConfig, "N/A"));
- pw.decreaseIndent();
-
- final IpReachabilityMonitor iprm = mIpReachabilityMonitor;
- if (iprm != null) {
- pw.println();
- pw.println(mTag + " current IpReachabilityMonitor state:");
- pw.increaseIndent();
- iprm.dump(pw);
- pw.decreaseIndent();
- }
-
- pw.println();
- pw.println(mTag + " StateMachine dump:");
- pw.increaseIndent();
- mLog.dump(fd, pw, args);
- pw.decreaseIndent();
-
- pw.println();
- pw.println(mTag + " connectivity packet log:");
- pw.println();
- pw.println("Debug with python and scapy via:");
- pw.println("shell$ python");
- pw.println(">>> from scapy import all as scapy");
- pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()");
- pw.println();
-
- pw.increaseIndent();
- mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args);
- pw.decreaseIndent();
- }
-
-
- /**
- * Internals.
- */
-
- @Override
- protected String getWhatToString(int what) {
- return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what));
- }
-
- @Override
- protected String getLogRecString(Message msg) {
- final String logLine = String.format(
- "%s/%d %d %d %s [%s]",
- mInterfaceName, (mInterfaceParams == null) ? -1 : mInterfaceParams.index,
- msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger);
-
- final String richerLogLine = getWhatToString(msg.what) + " " + logLine;
- mLog.log(richerLogLine);
- if (DBG) {
- Log.d(mTag, richerLogLine);
- }
-
- mMsgStateLogger.reset();
- return logLine;
- }
-
- @Override
- protected boolean recordLogRec(Message msg) {
- // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy,
- // and we already log any LinkProperties change that results in an
- // invocation of IpClient.Callback#onLinkPropertiesChange().
- final boolean shouldLog = (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED);
- if (!shouldLog) {
- mMsgStateLogger.reset();
- }
- return shouldLog;
- }
-
- private void logError(String fmt, Object... args) {
- final String msg = "ERROR " + String.format(fmt, args);
- Log.e(mTag, msg);
- mLog.log(msg);
- }
-
- // This needs to be called with care to ensure that our LinkProperties
- // are in sync with the actual LinkProperties of the interface. For example,
- // we should only call this if we know for sure that there are no IP addresses
- // assigned to the interface, etc.
- private void resetLinkProperties() {
- mNetlinkTracker.clearLinkProperties();
- mConfiguration = null;
- mDhcpResults = null;
- mTcpBufferSizes = "";
- mHttpProxy = null;
-
- mLinkProperties = new LinkProperties();
- mLinkProperties.setInterfaceName(mInterfaceName);
- }
-
- private void recordMetric(final int type) {
- // We may record error metrics prior to starting.
- // Map this to IMMEDIATE_FAILURE_DURATION.
- final long duration = (mStartTimeMillis > 0)
- ? (SystemClock.elapsedRealtime() - mStartTimeMillis)
- : IMMEDIATE_FAILURE_DURATION;
- mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration));
- }
-
- // For now: use WifiStateMachine's historical notion of provisioned.
- @VisibleForTesting
- static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) {
- // For historical reasons, we should connect even if all we have is
- // an IPv4 address and nothing else.
- if (lp.hasIPv4Address() || lp.isProvisioned()) {
- return true;
- }
- if (config == null) {
- return false;
- }
-
- // When an InitialConfiguration is specified, ignore any difference with previous
- // properties and instead check if properties observed match the desired properties.
- return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes());
- }
-
- // TODO: Investigate folding all this into the existing static function
- // LinkProperties.compareProvisioning() or some other single function that
- // takes two LinkProperties objects and returns a ProvisioningChange
- // object that is a correct and complete assessment of what changed, taking
- // account of the asymmetries described in the comments in this function.
- // Then switch to using it everywhere (IpReachabilityMonitor, etc.).
- private ProvisioningChange compareProvisioning(LinkProperties oldLp, LinkProperties newLp) {
- ProvisioningChange delta;
- InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null;
- final boolean wasProvisioned = isProvisioned(oldLp, config);
- final boolean isProvisioned = isProvisioned(newLp, config);
-
- if (!wasProvisioned && isProvisioned) {
- delta = ProvisioningChange.GAINED_PROVISIONING;
- } else if (wasProvisioned && isProvisioned) {
- delta = ProvisioningChange.STILL_PROVISIONED;
- } else if (!wasProvisioned && !isProvisioned) {
- delta = ProvisioningChange.STILL_NOT_PROVISIONED;
- } else {
- // (wasProvisioned && !isProvisioned)
- //
- // Note that this is true even if we lose a configuration element
- // (e.g., a default gateway) that would not be required to advance
- // into provisioned state. This is intended: if we have a default
- // router and we lose it, that's a sure sign of a problem, but if
- // we connect to a network with no IPv4 DNS servers, we consider
- // that to be a network without DNS servers and connect anyway.
- //
- // See the comment below.
- delta = ProvisioningChange.LOST_PROVISIONING;
- }
-
- final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned();
- final boolean lostIPv4Address = oldLp.hasIPv4Address() && !newLp.hasIPv4Address();
- final boolean lostIPv6Router = oldLp.hasIPv6DefaultRoute() && !newLp.hasIPv6DefaultRoute();
-
- // If bad wifi avoidance is disabled, then ignore IPv6 loss of
- // provisioning. Otherwise, when a hotspot that loses Internet
- // access sends out a 0-lifetime RA to its clients, the clients
- // will disconnect and then reconnect, avoiding the bad hotspot,
- // instead of getting stuck on the bad hotspot. http://b/31827713 .
- //
- // This is incorrect because if the hotspot then regains Internet
- // access with a different prefix, TCP connections on the
- // deprecated addresses will remain stuck.
- //
- // Note that we can still be disconnected by IpReachabilityMonitor
- // if the IPv6 default gateway (but not the IPv6 DNS servers; see
- // accompanying code in IpReachabilityMonitor) is unreachable.
- final boolean ignoreIPv6ProvisioningLoss = (mMultinetworkPolicyTracker != null)
- && !mMultinetworkPolicyTracker.getAvoidBadWifi();
-
- // Additionally:
- //
- // Partial configurations (e.g., only an IPv4 address with no DNS
- // servers and no default route) are accepted as long as DHCPv4
- // succeeds. On such a network, isProvisioned() will always return
- // false, because the configuration is not complete, but we want to
- // connect anyway. It might be a disconnected network such as a
- // Chromecast or a wireless printer, for example.
- //
- // Because on such a network isProvisioned() will always return false,
- // delta will never be LOST_PROVISIONING. So check for loss of
- // provisioning here too.
- if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) {
- delta = ProvisioningChange.LOST_PROVISIONING;
- }
-
- // Additionally:
- //
- // If the previous link properties had a global IPv6 address and an
- // IPv6 default route then also consider the loss of that default route
- // to be a loss of provisioning. See b/27962810.
- if (oldLp.hasGlobalIPv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) {
- delta = ProvisioningChange.LOST_PROVISIONING;
- }
-
- return delta;
- }
-
- private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) {
- switch (delta) {
- case GAINED_PROVISIONING:
- if (DBG) { Log.d(mTag, "onProvisioningSuccess()"); }
- recordMetric(IpManagerEvent.PROVISIONING_OK);
- mCallback.onProvisioningSuccess(newLp);
- break;
-
- case LOST_PROVISIONING:
- if (DBG) { Log.d(mTag, "onProvisioningFailure()"); }
- recordMetric(IpManagerEvent.PROVISIONING_FAIL);
- mCallback.onProvisioningFailure(newLp);
- break;
-
- default:
- if (DBG) { Log.d(mTag, "onLinkPropertiesChange()"); }
- mCallback.onLinkPropertiesChange(newLp);
- break;
- }
- }
-
- // Updates all IpClient-related state concerned with LinkProperties.
- // Returns a ProvisioningChange for possibly notifying other interested
- // parties that are not fronted by IpClient.
- private ProvisioningChange setLinkProperties(LinkProperties newLp) {
- if (mApfFilter != null) {
- mApfFilter.setLinkProperties(newLp);
- }
- if (mIpReachabilityMonitor != null) {
- mIpReachabilityMonitor.updateLinkProperties(newLp);
- }
-
- ProvisioningChange delta = compareProvisioning(mLinkProperties, newLp);
- mLinkProperties = new LinkProperties(newLp);
-
- if (delta == ProvisioningChange.GAINED_PROVISIONING) {
- // TODO: Add a proper ProvisionedState and cancel the alarm in
- // its enter() method.
- mProvisioningTimeoutAlarm.cancel();
- }
-
- return delta;
- }
-
- private LinkProperties assembleLinkProperties() {
- // [1] Create a new LinkProperties object to populate.
- LinkProperties newLp = new LinkProperties();
- newLp.setInterfaceName(mInterfaceName);
-
- // [2] Pull in data from netlink:
- // - IPv4 addresses
- // - IPv6 addresses
- // - IPv6 routes
- // - IPv6 DNS servers
- //
- // N.B.: this is fundamentally race-prone and should be fixed by
- // changing NetlinkTracker from a hybrid edge/level model to an
- // edge-only model, or by giving IpClient its own netlink socket(s)
- // so as to track all required information directly.
- LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties();
- newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses());
- for (RouteInfo route : netlinkLinkProperties.getRoutes()) {
- newLp.addRoute(route);
- }
- addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers());
-
- // [3] Add in data from DHCPv4, if available.
- //
- // mDhcpResults is never shared with any other owner so we don't have
- // to worry about concurrent modification.
- if (mDhcpResults != null) {
- for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) {
- newLp.addRoute(route);
- }
- addAllReachableDnsServers(newLp, mDhcpResults.dnsServers);
- newLp.setDomains(mDhcpResults.domains);
-
- if (mDhcpResults.mtu != 0) {
- newLp.setMtu(mDhcpResults.mtu);
- }
- }
-
- // [4] Add in TCP buffer sizes and HTTP Proxy config, if available.
- if (!TextUtils.isEmpty(mTcpBufferSizes)) {
- newLp.setTcpBufferSizes(mTcpBufferSizes);
- }
- if (mHttpProxy != null) {
- newLp.setHttpProxy(mHttpProxy);
- }
-
- // [5] Add data from InitialConfiguration
- if (mConfiguration != null && mConfiguration.mInitialConfig != null) {
- InitialConfiguration config = mConfiguration.mInitialConfig;
- // Add InitialConfiguration routes and dns server addresses once all addresses
- // specified in the InitialConfiguration have been observed with Netlink.
- if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) {
- for (IpPrefix prefix : config.directlyConnectedRoutes) {
- newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName));
- }
- }
- addAllReachableDnsServers(newLp, config.dnsServers);
- }
- final LinkProperties oldLp = mLinkProperties;
- if (DBG) {
- Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s",
- netlinkLinkProperties, newLp, oldLp));
- }
-
- // TODO: also learn via netlink routes specified by an InitialConfiguration and specified
- // from a static IP v4 config instead of manually patching them in in steps [3] and [5].
- return newLp;
- }
-
- private static void addAllReachableDnsServers(
- LinkProperties lp, Iterable<InetAddress> dnses) {
- // TODO: Investigate deleting this reachability check. We should be
- // able to pass everything down to netd and let netd do evaluation
- // and RFC6724-style sorting.
- for (InetAddress dns : dnses) {
- if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) {
- lp.addDnsServer(dns);
- }
- }
- }
-
- // Returns false if we have lost provisioning, true otherwise.
- private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
- final LinkProperties newLp = assembleLinkProperties();
- if (Objects.equals(newLp, mLinkProperties)) {
- return true;
- }
- final ProvisioningChange delta = setLinkProperties(newLp);
- if (sendCallbacks) {
- dispatchCallback(delta, newLp);
- }
- return (delta != ProvisioningChange.LOST_PROVISIONING);
- }
-
- private void handleIPv4Success(DhcpResults dhcpResults) {
- mDhcpResults = new DhcpResults(dhcpResults);
- final LinkProperties newLp = assembleLinkProperties();
- final ProvisioningChange delta = setLinkProperties(newLp);
-
- if (DBG) {
- Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
- }
- mCallback.onNewDhcpResults(dhcpResults);
- dispatchCallback(delta, newLp);
- }
-
- private void handleIPv4Failure() {
- // TODO: Investigate deleting this clearIPv4Address() call.
- //
- // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances
- // that could trigger a call to this function. If we missed handling
- // that message in StartedState for some reason we would still clear
- // any addresses upon entry to StoppedState.
- mInterfaceCtrl.clearIPv4Address();
- mDhcpResults = null;
- if (DBG) { Log.d(mTag, "onNewDhcpResults(null)"); }
- mCallback.onNewDhcpResults(null);
-
- handleProvisioningFailure();
- }
-
- private void handleProvisioningFailure() {
- final LinkProperties newLp = assembleLinkProperties();
- ProvisioningChange delta = setLinkProperties(newLp);
- // If we've gotten here and we're still not provisioned treat that as
- // a total loss of provisioning.
- //
- // Either (a) static IP configuration failed or (b) DHCPv4 failed AND
- // there was no usable IPv6 obtained before a non-zero provisioning
- // timeout expired.
- //
- // Regardless: GAME OVER.
- if (delta == ProvisioningChange.STILL_NOT_PROVISIONED) {
- delta = ProvisioningChange.LOST_PROVISIONING;
- }
-
- dispatchCallback(delta, newLp);
- if (delta == ProvisioningChange.LOST_PROVISIONING) {
- transitionTo(mStoppingState);
- }
- }
-
- private void doImmediateProvisioningFailure(int failureType) {
- logError("onProvisioningFailure(): %s", failureType);
- recordMetric(failureType);
- mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
- }
-
- private boolean startIPv4() {
- // If we have a StaticIpConfiguration attempt to apply it and
- // handle the result accordingly.
- if (mConfiguration.mStaticIpConfig != null) {
- if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) {
- handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
- } else {
- return false;
- }
- } else {
- // Start DHCPv4.
- mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpClient.this, mInterfaceParams);
- mDhcpClient.registerForPreDhcpNotification();
- mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
- }
-
- return true;
- }
-
- private boolean startIPv6() {
- return mInterfaceCtrl.setIPv6PrivacyExtensions(true) &&
- mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode) &&
- mInterfaceCtrl.enableIPv6();
- }
-
- private boolean applyInitialConfig(InitialConfiguration config) {
- // TODO: also support specifying a static IPv4 configuration in InitialConfiguration.
- for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIPv6)) {
- if (!mInterfaceCtrl.addAddress(addr)) return false;
- }
-
- return true;
- }
-
- private boolean startIpReachabilityMonitor() {
- try {
- // TODO: Fetch these parameters from settings, and install a
- // settings observer to watch for update and re-program these
- // parameters (Q: is this level of dynamic updatability really
- // necessary or does reading from settings at startup suffice?).
- final int NUM_SOLICITS = 5;
- final int INTER_SOLICIT_INTERVAL_MS = 750;
- setNeighborParameters(mDependencies.getNetd(), mInterfaceName,
- NUM_SOLICITS, INTER_SOLICIT_INTERVAL_MS);
- } catch (Exception e) {
- mLog.e("Failed to adjust neighbor parameters", e);
- // Carry on using the system defaults (currently: 3, 1000);
- }
-
- try {
- mIpReachabilityMonitor = new IpReachabilityMonitor(
- mContext,
- mInterfaceParams,
- getHandler(),
- mLog,
- new IpReachabilityMonitor.Callback() {
- @Override
- public void notifyLost(InetAddress ip, String logMsg) {
- mCallback.onReachabilityLost(logMsg);
- }
- },
- mMultinetworkPolicyTracker);
- } catch (IllegalArgumentException iae) {
- // Failed to start IpReachabilityMonitor. Log it and call
- // onProvisioningFailure() immediately.
- //
- // See http://b/31038971.
- logError("IpReachabilityMonitor failure: %s", iae);
- mIpReachabilityMonitor = null;
- }
-
- return (mIpReachabilityMonitor != null);
- }
-
- private void stopAllIP() {
- // We don't need to worry about routes, just addresses, because:
- // - disableIpv6() will clear autoconf IPv6 routes as well, and
- // - we don't get IPv4 routes from netlink
- // so we neither react to nor need to wait for changes in either.
-
- mInterfaceCtrl.disableIPv6();
- mInterfaceCtrl.clearAllAddresses();
- }
-
- class StoppedState extends State {
- @Override
- public void enter() {
- stopAllIP();
-
- resetLinkProperties();
- if (mStartTimeMillis > 0) {
- // Completed a life-cycle; send a final empty LinkProperties
- // (cleared in resetLinkProperties() above) and record an event.
- mCallback.onLinkPropertiesChange(new LinkProperties(mLinkProperties));
- recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE);
- mStartTimeMillis = 0;
- }
- }
-
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case CMD_TERMINATE_AFTER_STOP:
- stopStateMachineUpdaters();
- quit();
- break;
-
- case CMD_STOP:
- break;
-
- case CMD_START:
- mConfiguration = (ProvisioningConfiguration) msg.obj;
- transitionTo(mStartedState);
- break;
-
- case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
- handleLinkPropertiesUpdate(NO_CALLBACKS);
- break;
-
- case CMD_UPDATE_TCP_BUFFER_SIZES:
- mTcpBufferSizes = (String) msg.obj;
- handleLinkPropertiesUpdate(NO_CALLBACKS);
- break;
-
- case CMD_UPDATE_HTTP_PROXY:
- mHttpProxy = (ProxyInfo) msg.obj;
- handleLinkPropertiesUpdate(NO_CALLBACKS);
- break;
-
- case CMD_SET_MULTICAST_FILTER:
- mMulticastFiltering = (boolean) msg.obj;
- break;
-
- case DhcpClient.CMD_ON_QUIT:
- // Everything is already stopped.
- logError("Unexpected CMD_ON_QUIT (already stopped).");
- break;
-
- default:
- return NOT_HANDLED;
+ /**
+ * @see android.net.shared.ProvisioningConfiguration.Builder
+ */
+ public static class Builder extends android.net.shared.ProvisioningConfiguration.Builder {
+ // Override all methods to have a return type matching this Builder
+ @Override
+ public Builder withoutIPv4() {
+ super.withoutIPv4();
+ return this;
}
- mMsgStateLogger.handled(this, getCurrentState());
- return HANDLED;
- }
- }
+ @Override
+ public Builder withoutIPv6() {
+ super.withoutIPv6();
+ return this;
+ }
- class StoppingState extends State {
- @Override
- public void enter() {
- if (mDhcpClient == null) {
- // There's no DHCPv4 for which to wait; proceed to stopped.
- deferMessage(obtainMessage(CMD_JUMP_STOPPING_TO_STOPPED));
+ @Override
+ public Builder withoutMultinetworkPolicyTracker() {
+ super.withoutMultinetworkPolicyTracker();
+ return this;
+ }
+
+ @Override
+ public Builder withoutIpReachabilityMonitor() {
+ super.withoutIpReachabilityMonitor();
+ return this;
+ }
+
+ @Override
+ public Builder withPreDhcpAction() {
+ super.withPreDhcpAction();
+ return this;
+ }
+
+ @Override
+ public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
+ super.withPreDhcpAction(dhcpActionTimeoutMs);
+ return this;
+ }
+
+ @Override
+ public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
+ super.withStaticConfiguration(staticConfig);
+ return this;
+ }
+
+ @Override
+ public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
+ super.withApfCapabilities(apfCapabilities);
+ return this;
+ }
+
+ @Override
+ public Builder withProvisioningTimeoutMs(int timeoutMs) {
+ super.withProvisioningTimeoutMs(timeoutMs);
+ return this;
+ }
+
+ @Override
+ public Builder withRandomMacAddress() {
+ super.withRandomMacAddress();
+ return this;
+ }
+
+ @Override
+ public Builder withStableMacAddress() {
+ super.withStableMacAddress();
+ return this;
+ }
+
+ @Override
+ public Builder withNetwork(Network network) {
+ super.withNetwork(network);
+ return this;
+ }
+
+ @Override
+ public Builder withDisplayName(String displayName) {
+ super.withDisplayName(displayName);
+ return this;
+ }
+
+ @Override
+ public ProvisioningConfiguration build() {
+ return new ProvisioningConfiguration(mConfig);
}
}
-
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case CMD_JUMP_STOPPING_TO_STOPPED:
- transitionTo(mStoppedState);
- break;
-
- case CMD_STOP:
- break;
-
- case DhcpClient.CMD_CLEAR_LINKADDRESS:
- mInterfaceCtrl.clearIPv4Address();
- break;
-
- case DhcpClient.CMD_ON_QUIT:
- mDhcpClient = null;
- transitionTo(mStoppedState);
- break;
-
- default:
- deferMessage(msg);
- }
-
- mMsgStateLogger.handled(this, getCurrentState());
- return HANDLED;
- }
- }
-
- class StartedState extends State {
- @Override
- public void enter() {
- mStartTimeMillis = SystemClock.elapsedRealtime();
-
- if (mConfiguration.mProvisioningTimeoutMs > 0) {
- final long alarmTime = SystemClock.elapsedRealtime() +
- mConfiguration.mProvisioningTimeoutMs;
- mProvisioningTimeoutAlarm.schedule(alarmTime);
- }
-
- if (readyToProceed()) {
- deferMessage(obtainMessage(CMD_JUMP_STARTED_TO_RUNNING));
- } else {
- // Clear all IPv4 and IPv6 before proceeding to RunningState.
- // Clean up any leftover state from an abnormal exit from
- // tethering or during an IpClient restart.
- stopAllIP();
- }
- }
-
- @Override
- public void exit() {
- mProvisioningTimeoutAlarm.cancel();
- }
-
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case CMD_JUMP_STARTED_TO_RUNNING:
- transitionTo(mRunningState);
- break;
-
- case CMD_STOP:
- transitionTo(mStoppingState);
- break;
-
- case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
- handleLinkPropertiesUpdate(NO_CALLBACKS);
- if (readyToProceed()) {
- transitionTo(mRunningState);
- }
- break;
-
- case EVENT_PROVISIONING_TIMEOUT:
- handleProvisioningFailure();
- break;
-
- default:
- // It's safe to process messages out of order because the
- // only message that can both
- // a) be received at this time and
- // b) affect provisioning state
- // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above).
- deferMessage(msg);
- }
-
- mMsgStateLogger.handled(this, getCurrentState());
- return HANDLED;
- }
-
- private boolean readyToProceed() {
- return (!mLinkProperties.hasIPv4Address() &&
- !mLinkProperties.hasGlobalIPv6Address());
- }
- }
-
- class RunningState extends State {
- private ConnectivityPacketTracker mPacketTracker;
- private boolean mDhcpActionInFlight;
-
- @Override
- public void enter() {
- ApfFilter.ApfConfiguration apfConfig = new ApfFilter.ApfConfiguration();
- apfConfig.apfCapabilities = mConfiguration.mApfCapabilities;
- apfConfig.multicastFilter = mMulticastFiltering;
- // Get the Configuration for ApfFilter from Context
- apfConfig.ieee802_3Filter =
- mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
- apfConfig.ethTypeBlackList =
- mContext.getResources().getIntArray(R.array.config_apfEthTypeBlackList);
- mApfFilter = ApfFilter.maybeCreate(mContext, apfConfig, mInterfaceParams, mCallback);
- // TODO: investigate the effects of any multicast filtering racing/interfering with the
- // rest of this IP configuration startup.
- if (mApfFilter == null) {
- mCallback.setFallbackMulticastFilter(mMulticastFiltering);
- }
-
- mPacketTracker = createPacketTracker();
- if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName);
-
- if (mConfiguration.mEnableIPv6 && !startIPv6()) {
- doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
- enqueueJumpToStoppingState();
- return;
- }
-
- if (mConfiguration.mEnableIPv4 && !startIPv4()) {
- doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4);
- enqueueJumpToStoppingState();
- return;
- }
-
- final InitialConfiguration config = mConfiguration.mInitialConfig;
- if ((config != null) && !applyInitialConfig(config)) {
- // TODO introduce a new IpManagerEvent constant to distinguish this error case.
- doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
- enqueueJumpToStoppingState();
- return;
- }
-
- if (mConfiguration.mUsingMultinetworkPolicyTracker) {
- mMultinetworkPolicyTracker = new MultinetworkPolicyTracker(
- mContext, getHandler(),
- () -> { mLog.log("OBSERVED AvoidBadWifi changed"); });
- mMultinetworkPolicyTracker.start();
- }
-
- if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) {
- doImmediateProvisioningFailure(
- IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR);
- enqueueJumpToStoppingState();
- return;
- }
- }
-
- @Override
- public void exit() {
- stopDhcpAction();
-
- if (mIpReachabilityMonitor != null) {
- mIpReachabilityMonitor.stop();
- mIpReachabilityMonitor = null;
- }
-
- if (mMultinetworkPolicyTracker != null) {
- mMultinetworkPolicyTracker.shutdown();
- mMultinetworkPolicyTracker = null;
- }
-
- if (mDhcpClient != null) {
- mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP);
- mDhcpClient.doQuit();
- }
-
- if (mPacketTracker != null) {
- mPacketTracker.stop();
- mPacketTracker = null;
- }
-
- if (mApfFilter != null) {
- mApfFilter.shutdown();
- mApfFilter = null;
- }
-
- resetLinkProperties();
- }
-
- private void enqueueJumpToStoppingState() {
- deferMessage(obtainMessage(CMD_JUMP_RUNNING_TO_STOPPING));
- }
-
- private ConnectivityPacketTracker createPacketTracker() {
- try {
- return new ConnectivityPacketTracker(
- getHandler(), mInterfaceParams, mConnectivityPacketLog);
- } catch (IllegalArgumentException e) {
- return null;
- }
- }
-
- private void ensureDhcpAction() {
- if (!mDhcpActionInFlight) {
- mCallback.onPreDhcpAction();
- mDhcpActionInFlight = true;
- final long alarmTime = SystemClock.elapsedRealtime() +
- mConfiguration.mRequestedPreDhcpActionMs;
- mDhcpActionTimeoutAlarm.schedule(alarmTime);
- }
- }
-
- private void stopDhcpAction() {
- mDhcpActionTimeoutAlarm.cancel();
- if (mDhcpActionInFlight) {
- mCallback.onPostDhcpAction();
- mDhcpActionInFlight = false;
- }
- }
-
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case CMD_JUMP_RUNNING_TO_STOPPING:
- case CMD_STOP:
- transitionTo(mStoppingState);
- break;
-
- case CMD_START:
- logError("ALERT: START received in StartedState. Please fix caller.");
- break;
-
- case CMD_CONFIRM:
- // TODO: Possibly introduce a second type of confirmation
- // that both probes (a) on-link neighbors and (b) does
- // a DHCPv4 RENEW. We used to do this on Wi-Fi framework
- // roams.
- if (mIpReachabilityMonitor != null) {
- mIpReachabilityMonitor.probeAll();
- }
- break;
-
- case EVENT_PRE_DHCP_ACTION_COMPLETE:
- // It's possible to reach here if, for example, someone
- // calls completedPreDhcpAction() after provisioning with
- // a static IP configuration.
- if (mDhcpClient != null) {
- mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE);
- }
- break;
-
- case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
- if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
- transitionTo(mStoppingState);
- }
- break;
-
- case CMD_UPDATE_TCP_BUFFER_SIZES:
- mTcpBufferSizes = (String) msg.obj;
- // This cannot possibly change provisioning state.
- handleLinkPropertiesUpdate(SEND_CALLBACKS);
- break;
-
- case CMD_UPDATE_HTTP_PROXY:
- mHttpProxy = (ProxyInfo) msg.obj;
- // This cannot possibly change provisioning state.
- handleLinkPropertiesUpdate(SEND_CALLBACKS);
- break;
-
- case CMD_SET_MULTICAST_FILTER: {
- mMulticastFiltering = (boolean) msg.obj;
- if (mApfFilter != null) {
- mApfFilter.setMulticastFilter(mMulticastFiltering);
- } else {
- mCallback.setFallbackMulticastFilter(mMulticastFiltering);
- }
- break;
- }
-
- case EVENT_READ_PACKET_FILTER_COMPLETE: {
- if (mApfFilter != null) {
- mApfFilter.setDataSnapshot((byte[]) msg.obj);
- }
- mApfDataSnapshotComplete.open();
- break;
- }
-
- case EVENT_DHCPACTION_TIMEOUT:
- stopDhcpAction();
- break;
-
- case DhcpClient.CMD_PRE_DHCP_ACTION:
- if (mConfiguration.mRequestedPreDhcpActionMs > 0) {
- ensureDhcpAction();
- } else {
- sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
- }
- break;
-
- case DhcpClient.CMD_CLEAR_LINKADDRESS:
- mInterfaceCtrl.clearIPv4Address();
- break;
-
- case DhcpClient.CMD_CONFIGURE_LINKADDRESS: {
- final LinkAddress ipAddress = (LinkAddress) msg.obj;
- if (mInterfaceCtrl.setIPv4Address(ipAddress)) {
- mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED);
- } else {
- logError("Failed to set IPv4 address.");
- dispatchCallback(ProvisioningChange.LOST_PROVISIONING,
- new LinkProperties(mLinkProperties));
- transitionTo(mStoppingState);
- }
- break;
- }
-
- // This message is only received when:
- //
- // a) initial address acquisition succeeds,
- // b) renew succeeds or is NAK'd,
- // c) rebind succeeds or is NAK'd, or
- // c) the lease expires,
- //
- // but never when initial address acquisition fails. The latter
- // condition is now governed by the provisioning timeout.
- case DhcpClient.CMD_POST_DHCP_ACTION:
- stopDhcpAction();
-
- switch (msg.arg1) {
- case DhcpClient.DHCP_SUCCESS:
- handleIPv4Success((DhcpResults) msg.obj);
- break;
- case DhcpClient.DHCP_FAILURE:
- handleIPv4Failure();
- break;
- default:
- logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1);
- }
- break;
-
- case DhcpClient.CMD_ON_QUIT:
- // DHCPv4 quit early for some reason.
- logError("Unexpected CMD_ON_QUIT.");
- mDhcpClient = null;
- break;
-
- default:
- return NOT_HANDLED;
- }
-
- mMsgStateLogger.handled(this, getCurrentState());
- return HANDLED;
- }
- }
-
- private static class MessageHandlingLogger {
- public String processedInState;
- public String receivedInState;
-
- public void reset() {
- processedInState = null;
- receivedInState = null;
- }
-
- public void handled(State processedIn, IState receivedIn) {
- processedInState = processedIn.getClass().getSimpleName();
- receivedInState = receivedIn.getName();
- }
-
- public String toString() {
- return String.format("rcvd_in=%s, proc_in=%s",
- receivedInState, processedInState);
- }
- }
-
- private static void setNeighborParameters(
- INetd netd, String ifName, int num_solicits, int inter_solicit_interval_ms)
- throws RemoteException, IllegalArgumentException {
- Preconditions.checkNotNull(netd);
- Preconditions.checkArgument(!TextUtils.isEmpty(ifName));
- Preconditions.checkArgument(num_solicits > 0);
- Preconditions.checkArgument(inter_solicit_interval_ms > 0);
-
- for (int family : new Integer[]{INetd.IPV4, INetd.IPV6}) {
- netd.setProcSysNet(family, INetd.NEIGH, ifName, "retrans_time_ms", Integer.toString(inter_solicit_interval_ms));
- netd.setProcSysNet(family, INetd.NEIGH, ifName, "ucast_solicit", Integer.toString(num_solicits));
- }
- }
-
- // TODO: extract out into CollectionUtils.
- static <T> boolean any(Iterable<T> coll, Predicate<T> fn) {
- for (T t : coll) {
- if (fn.test(t)) {
- return true;
- }
- }
- return false;
- }
-
- static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
- return !any(coll, not(fn));
- }
-
- static <T> Predicate<T> not(Predicate<T> fn) {
- return (t) -> !fn.test(t);
- }
-
- static <T> String join(String delimiter, Collection<T> coll) {
- return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter));
- }
-
- static <T> T find(Iterable<T> coll, Predicate<T> fn) {
- for (T t: coll) {
- if (fn.test(t)) {
- return t;
- }
- }
- return null;
- }
-
- static <T> List<T> findAll(Collection<T> coll, Predicate<T> fn) {
- return coll.stream().filter(fn).collect(Collectors.toList());
}
}
diff --git a/services/net/java/android/net/ip/IpClientUtil.java b/services/net/java/android/net/ip/IpClientUtil.java
new file mode 100644
index 0000000..2a2a67a
--- /dev/null
+++ b/services/net/java/android/net/ip/IpClientUtil.java
@@ -0,0 +1,204 @@
+/*
+ * 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.ip;
+
+import static android.net.shared.IpConfigurationParcelableUtil.fromStableParcelable;
+import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable;
+
+import android.content.Context;
+import android.net.DhcpResultsParcelable;
+import android.net.LinkProperties;
+import android.net.LinkPropertiesParcelable;
+import android.net.NetworkStack;
+import android.net.ip.IIpClientCallbacks;
+import android.os.ConditionVariable;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+
+/**
+ * Utilities and wrappers to simplify communication with IpClient, which lives in the NetworkStack
+ * process.
+ *
+ * @hide
+ */
+public class IpClientUtil {
+ // TODO: remove with its callers
+ public static final String DUMP_ARG = "ipclient";
+
+ /**
+ * Subclass of {@link IpClientCallbacks} allowing clients to block until provisioning is
+ * complete with {@link WaitForProvisioningCallbacks#waitForProvisioning()}.
+ */
+ public static class WaitForProvisioningCallbacks extends IpClientCallbacks {
+ private final ConditionVariable mCV = new ConditionVariable();
+ private LinkProperties mCallbackLinkProperties;
+
+ /**
+ * Block until either {@link #onProvisioningSuccess(LinkProperties)} or
+ * {@link #onProvisioningFailure(LinkProperties)} is called.
+ */
+ public LinkProperties waitForProvisioning() {
+ mCV.block();
+ return mCallbackLinkProperties;
+ }
+
+ @Override
+ public void onProvisioningSuccess(LinkProperties newLp) {
+ mCallbackLinkProperties = newLp;
+ mCV.open();
+ }
+
+ @Override
+ public void onProvisioningFailure(LinkProperties newLp) {
+ mCallbackLinkProperties = null;
+ mCV.open();
+ }
+ }
+
+ /**
+ * Create a new IpClient.
+ *
+ * <p>This is a convenience method to allow clients to use {@link IpClientCallbacks} instead of
+ * {@link IIpClientCallbacks}.
+ * @see {@link NetworkStack#makeIpClient(String, IIpClientCallbacks)}
+ */
+ public static void makeIpClient(Context context, String ifName, IpClientCallbacks callback) {
+ context.getSystemService(NetworkStack.class)
+ .makeIpClient(ifName, new IpClientCallbacksProxy(callback));
+ }
+
+ /**
+ * Create a new IpClient.
+ *
+ * <p>This is a convenience method to allow clients to use {@link IpClientCallbacksProxy}
+ * instead of {@link IIpClientCallbacks}.
+ * @see {@link NetworkStack#makeIpClient(String, IIpClientCallbacks)}
+ */
+ public static void makeIpClient(
+ Context context, String ifName, IpClientCallbacksProxy callback) {
+ context.getSystemService(NetworkStack.class)
+ .makeIpClient(ifName, callback);
+ }
+
+ /**
+ * Wrapper to relay calls from {@link IIpClientCallbacks} to {@link IpClientCallbacks}.
+ */
+ public static class IpClientCallbacksProxy extends IIpClientCallbacks.Stub {
+ protected final IpClientCallbacks mCb;
+
+ /**
+ * Create a new IpClientCallbacksProxy.
+ */
+ public IpClientCallbacksProxy(IpClientCallbacks cb) {
+ mCb = cb;
+ }
+
+ @Override
+ public void onIpClientCreated(IIpClient ipClient) {
+ mCb.onIpClientCreated(ipClient);
+ }
+
+ @Override
+ public void onPreDhcpAction() {
+ mCb.onPreDhcpAction();
+ }
+
+ @Override
+ public void onPostDhcpAction() {
+ mCb.onPostDhcpAction();
+ }
+
+ // This is purely advisory and not an indication of provisioning
+ // success or failure. This is only here for callers that want to
+ // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
+ // DHCPv4 or static IPv4 configuration failure or success can be
+ // determined by whether or not the passed-in DhcpResults object is
+ // null or not.
+ @Override
+ public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) {
+ mCb.onNewDhcpResults(fromStableParcelable(dhcpResults));
+ }
+
+ @Override
+ public void onProvisioningSuccess(LinkPropertiesParcelable newLp) {
+ mCb.onProvisioningSuccess(fromStableParcelable(newLp));
+ }
+ @Override
+ public void onProvisioningFailure(LinkPropertiesParcelable newLp) {
+ mCb.onProvisioningFailure(fromStableParcelable(newLp));
+ }
+
+ // Invoked on LinkProperties changes.
+ @Override
+ public void onLinkPropertiesChange(LinkPropertiesParcelable newLp) {
+ mCb.onLinkPropertiesChange(fromStableParcelable(newLp));
+ }
+
+ // Called when the internal IpReachabilityMonitor (if enabled) has
+ // detected the loss of a critical number of required neighbors.
+ @Override
+ public void onReachabilityLost(String logMsg) {
+ mCb.onReachabilityLost(logMsg);
+ }
+
+ // Called when the IpClient state machine terminates.
+ @Override
+ public void onQuit() {
+ mCb.onQuit();
+ }
+
+ // Install an APF program to filter incoming packets.
+ @Override
+ public void installPacketFilter(byte[] filter) {
+ mCb.installPacketFilter(filter);
+ }
+
+ // Asynchronously read back the APF program & data buffer from the wifi driver.
+ // Due to Wifi HAL limitations, the current implementation only supports dumping the entire
+ // buffer. In response to this request, the driver returns the data buffer asynchronously
+ // by sending an IpClient#EVENT_READ_PACKET_FILTER_COMPLETE message.
+ @Override
+ public void startReadPacketFilter() {
+ mCb.startReadPacketFilter();
+ }
+
+ // If multicast filtering cannot be accomplished with APF, this function will be called to
+ // actuate multicast filtering using another means.
+ @Override
+ public void setFallbackMulticastFilter(boolean enabled) {
+ mCb.setFallbackMulticastFilter(enabled);
+ }
+
+ // Enabled/disable Neighbor Discover offload functionality. This is
+ // called, for example, whenever 464xlat is being started or stopped.
+ @Override
+ public void setNeighborDiscoveryOffload(boolean enable) {
+ mCb.setNeighborDiscoveryOffload(enable);
+ }
+ }
+
+ /**
+ * Dump logs for the specified IpClient.
+ * TODO: remove callers and delete
+ */
+ public static void dumpIpClient(
+ IIpClient connector, FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("IpClient logs have moved to dumpsys network_stack");
+ }
+}
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
deleted file mode 100644
index 2eb36a2..0000000
--- a/services/net/java/android/net/ip/IpManager.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.ip;
-
-import android.content.Context;
-import android.net.INetd;
-import android.net.LinkProperties;
-import android.net.Network;
-import android.net.StaticIpConfiguration;
-import android.net.apf.ApfCapabilities;
-import android.net.util.NetdService;
-import android.os.INetworkManagementService;
-import android.os.ServiceManager;
-import android.net.apf.ApfCapabilities;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-
-/*
- * TODO: Delete this altogether in favor of its renamed successor: IpClient.
- *
- * @hide
- */
-public class IpManager extends IpClient {
- public static class ProvisioningConfiguration extends IpClient.ProvisioningConfiguration {
- public ProvisioningConfiguration(IpClient.ProvisioningConfiguration ipcConfig) {
- super(ipcConfig);
- }
-
- public static class Builder extends IpClient.ProvisioningConfiguration.Builder {
- @Override
- public Builder withoutIPv4() {
- super.withoutIPv4();
- return this;
- }
- @Override
- public Builder withoutIPv6() {
- super.withoutIPv6();
- return this;
- }
- @Override
- public Builder withoutIpReachabilityMonitor() {
- super.withoutIpReachabilityMonitor();
- return this;
- }
- @Override
- public Builder withPreDhcpAction() {
- super.withPreDhcpAction();
- return this;
- }
- @Override
- public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
- super.withPreDhcpAction(dhcpActionTimeoutMs);
- return this;
- }
- // No Override; locally defined type.
- public Builder withInitialConfiguration(InitialConfiguration initialConfig) {
- super.withInitialConfiguration((IpClient.InitialConfiguration) initialConfig);
- return this;
- }
- @Override
- public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
- super.withStaticConfiguration(staticConfig);
- return this;
- }
- @Override
- public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
- super.withApfCapabilities(apfCapabilities);
- return this;
- }
- @Override
- public Builder withProvisioningTimeoutMs(int timeoutMs) {
- super.withProvisioningTimeoutMs(timeoutMs);
- return this;
- }
- @Override
- public Builder withNetwork(Network network) {
- super.withNetwork(network);
- return this;
- }
- @Override
- public Builder withDisplayName(String displayName) {
- super.withDisplayName(displayName);
- return this;
- }
- @Override
- public ProvisioningConfiguration build() {
- return new ProvisioningConfiguration(super.build());
- }
- }
- }
-
- public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
- return new ProvisioningConfiguration.Builder();
- }
-
- public static class InitialConfiguration extends IpClient.InitialConfiguration {
- }
-
- public static class Callback extends IpClient.Callback {
- }
-
- public IpManager(Context context, String ifName, Callback callback) {
- super(context, ifName, callback);
- }
-
- public void startProvisioning(ProvisioningConfiguration req) {
- super.startProvisioning((IpClient.ProvisioningConfiguration) req);
- }
-}
diff --git a/services/net/java/android/net/ip/IpNeighborMonitor.java b/services/net/java/android/net/ip/IpNeighborMonitor.java
deleted file mode 100644
index 9512f1b..0000000
--- a/services/net/java/android/net/ip/IpNeighborMonitor.java
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.ip;
-
-import static android.net.netlink.NetlinkConstants.hexify;
-import static android.net.netlink.NetlinkConstants.RTM_DELNEIGH;
-import static android.net.netlink.NetlinkConstants.stringForNlMsgType;
-
-import android.net.MacAddress;
-import android.net.netlink.NetlinkErrorMessage;
-import android.net.netlink.NetlinkMessage;
-import android.net.netlink.NetlinkSocket;
-import android.net.netlink.RtNetlinkNeighborMessage;
-import android.net.netlink.StructNdMsg;
-import android.net.netlink.StructNlMsgHdr;
-import android.net.util.PacketReader;
-import android.net.util.SharedLog;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.system.ErrnoException;
-import android.system.NetlinkSocketAddress;
-import android.system.Os;
-import android.system.OsConstants;
-import android.util.Log;
-
-import com.android.internal.util.BitUtils;
-
-import libcore.io.IoUtils;
-
-import java.io.FileDescriptor;
-import java.net.InetAddress;
-import java.net.SocketAddress;
-import java.net.SocketException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.StringJoiner;
-
-
-/**
- * IpNeighborMonitor.
- *
- * Monitors the kernel rtnetlink neighbor notifications and presents to callers
- * NeighborEvents describing each event. Callers can provide a consumer instance
- * to both filter (e.g. by interface index and IP address) and handle the
- * generated NeighborEvents.
- *
- * @hide
- */
-public class IpNeighborMonitor extends PacketReader {
- private static final String TAG = IpNeighborMonitor.class.getSimpleName();
- private static final boolean DBG = false;
- private static final boolean VDBG = false;
-
- /**
- * Make the kernel perform neighbor reachability detection (IPv4 ARP or IPv6 ND)
- * for the given IP address on the specified interface index.
- *
- * @return 0 if the request was successfully passed to the kernel; otherwise return
- * a non-zero error code.
- */
- public static int startKernelNeighborProbe(int ifIndex, InetAddress ip) {
- final String msgSnippet = "probing ip=" + ip.getHostAddress() + "%" + ifIndex;
- if (DBG) { Log.d(TAG, msgSnippet); }
-
- final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage(
- 1, ip, StructNdMsg.NUD_PROBE, ifIndex, null);
-
- try {
- NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_ROUTE, msg);
- } catch (ErrnoException e) {
- Log.e(TAG, "Error " + msgSnippet + ": " + e);
- return -e.errno;
- }
-
- return 0;
- }
-
- public static class NeighborEvent {
- final long elapsedMs;
- final short msgType;
- final int ifindex;
- final InetAddress ip;
- final short nudState;
- final MacAddress macAddr;
-
- public NeighborEvent(long elapsedMs, short msgType, int ifindex, InetAddress ip,
- short nudState, MacAddress macAddr) {
- this.elapsedMs = elapsedMs;
- this.msgType = msgType;
- this.ifindex = ifindex;
- this.ip = ip;
- this.nudState = nudState;
- this.macAddr = macAddr;
- }
-
- boolean isConnected() {
- return (msgType != RTM_DELNEIGH) && StructNdMsg.isNudStateConnected(nudState);
- }
-
- boolean isValid() {
- return (msgType != RTM_DELNEIGH) && StructNdMsg.isNudStateValid(nudState);
- }
-
- @Override
- public String toString() {
- final StringJoiner j = new StringJoiner(",", "NeighborEvent{", "}");
- return j.add("@" + elapsedMs)
- .add(stringForNlMsgType(msgType))
- .add("if=" + ifindex)
- .add(ip.getHostAddress())
- .add(StructNdMsg.stringForNudState(nudState))
- .add("[" + macAddr + "]")
- .toString();
- }
- }
-
- public interface NeighborEventConsumer {
- // Every neighbor event received on the netlink socket is passed in
- // here. Subclasses should filter for events of interest.
- public void accept(NeighborEvent event);
- }
-
- private final SharedLog mLog;
- private final NeighborEventConsumer mConsumer;
-
- public IpNeighborMonitor(Handler h, SharedLog log, NeighborEventConsumer cb) {
- super(h, NetlinkSocket.DEFAULT_RECV_BUFSIZE);
- mLog = log.forSubComponent(TAG);
- mConsumer = (cb != null) ? cb : (event) -> { /* discard */ };
- }
-
- @Override
- protected FileDescriptor createFd() {
- FileDescriptor fd = null;
-
- try {
- fd = NetlinkSocket.forProto(OsConstants.NETLINK_ROUTE);
- Os.bind(fd, (SocketAddress)(new NetlinkSocketAddress(0, OsConstants.RTMGRP_NEIGH)));
- Os.connect(fd, (SocketAddress)(new NetlinkSocketAddress(0, 0)));
-
- if (VDBG) {
- final NetlinkSocketAddress nlAddr = (NetlinkSocketAddress) Os.getsockname(fd);
- Log.d(TAG, "bound to sockaddr_nl{"
- + BitUtils.uint32(nlAddr.getPortId()) + ", "
- + nlAddr.getGroupsMask()
- + "}");
- }
- } catch (ErrnoException|SocketException e) {
- logError("Failed to create rtnetlink socket", e);
- IoUtils.closeQuietly(fd);
- return null;
- }
-
- return fd;
- }
-
- @Override
- protected void handlePacket(byte[] recvbuf, int length) {
- final long whenMs = SystemClock.elapsedRealtime();
-
- final ByteBuffer byteBuffer = ByteBuffer.wrap(recvbuf, 0, length);
- byteBuffer.order(ByteOrder.nativeOrder());
-
- parseNetlinkMessageBuffer(byteBuffer, whenMs);
- }
-
- private void parseNetlinkMessageBuffer(ByteBuffer byteBuffer, long whenMs) {
- while (byteBuffer.remaining() > 0) {
- final int position = byteBuffer.position();
- final NetlinkMessage nlMsg = NetlinkMessage.parse(byteBuffer);
- if (nlMsg == null || nlMsg.getHeader() == null) {
- byteBuffer.position(position);
- mLog.e("unparsable netlink msg: " + hexify(byteBuffer));
- break;
- }
-
- final int srcPortId = nlMsg.getHeader().nlmsg_pid;
- if (srcPortId != 0) {
- mLog.e("non-kernel source portId: " + BitUtils.uint32(srcPortId));
- break;
- }
-
- if (nlMsg instanceof NetlinkErrorMessage) {
- mLog.e("netlink error: " + nlMsg);
- continue;
- } else if (!(nlMsg instanceof RtNetlinkNeighborMessage)) {
- mLog.i("non-rtnetlink neighbor msg: " + nlMsg);
- continue;
- }
-
- evaluateRtNetlinkNeighborMessage((RtNetlinkNeighborMessage) nlMsg, whenMs);
- }
- }
-
- private void evaluateRtNetlinkNeighborMessage(
- RtNetlinkNeighborMessage neighMsg, long whenMs) {
- final short msgType = neighMsg.getHeader().nlmsg_type;
- final StructNdMsg ndMsg = neighMsg.getNdHeader();
- if (ndMsg == null) {
- mLog.e("RtNetlinkNeighborMessage without ND message header!");
- return;
- }
-
- final int ifindex = ndMsg.ndm_ifindex;
- final InetAddress destination = neighMsg.getDestination();
- final short nudState =
- (msgType == RTM_DELNEIGH)
- ? StructNdMsg.NUD_NONE
- : ndMsg.ndm_state;
-
- final NeighborEvent event = new NeighborEvent(
- whenMs, msgType, ifindex, destination, nudState,
- getMacAddress(neighMsg.getLinkLayerAddress()));
-
- if (VDBG) {
- Log.d(TAG, neighMsg.toString());
- }
- if (DBG) {
- Log.d(TAG, event.toString());
- }
-
- mConsumer.accept(event);
- }
-
- private static MacAddress getMacAddress(byte[] linkLayerAddress) {
- if (linkLayerAddress != null) {
- try {
- return MacAddress.fromBytes(linkLayerAddress);
- } catch (IllegalArgumentException e) {
- Log.e(TAG, "Failed to parse link-layer address: " + hexify(linkLayerAddress));
- }
- }
-
- return null;
- }
-}
diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java
deleted file mode 100644
index 7e02a28..0000000
--- a/services/net/java/android/net/ip/IpReachabilityMonitor.java
+++ /dev/null
@@ -1,386 +0,0 @@
-/*
- * Copyright (C) 2015 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.ip;
-
-import android.content.Context;
-import android.net.LinkAddress;
-import android.net.LinkProperties;
-import android.net.LinkProperties.ProvisioningChange;
-import android.net.ProxyInfo;
-import android.net.RouteInfo;
-import android.net.ip.IpNeighborMonitor.NeighborEvent;
-import android.net.metrics.IpConnectivityLog;
-import android.net.metrics.IpReachabilityEvent;
-import android.net.netlink.StructNdMsg;
-import android.net.util.InterfaceParams;
-import android.net.util.MultinetworkPolicyTracker;
-import android.net.util.SharedLog;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.os.SystemClock;
-import android.system.ErrnoException;
-import android.system.OsConstants;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.DumpUtils;
-import com.android.internal.util.DumpUtils.Dump;
-
-import java.io.InterruptedIOException;
-import java.io.PrintWriter;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.SocketAddress;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-
-/**
- * IpReachabilityMonitor.
- *
- * Monitors on-link IP reachability and notifies callers whenever any on-link
- * addresses of interest appear to have become unresponsive.
- *
- * This code does not concern itself with "why" a neighbour might have become
- * unreachable. Instead, it primarily reacts to the kernel's notion of IP
- * reachability for each of the neighbours we know to be critically important
- * to normal network connectivity. As such, it is often "just the messenger":
- * the neighbours about which it warns are already deemed by the kernel to have
- * become unreachable.
- *
- *
- * How it works:
- *
- * 1. The "on-link neighbours of interest" found in a given LinkProperties
- * instance are added to a "watch list" via #updateLinkProperties().
- * This usually means all default gateways and any on-link DNS servers.
- *
- * 2. We listen continuously for netlink neighbour messages (RTM_NEWNEIGH,
- * RTM_DELNEIGH), watching only for neighbours in the watch list.
- *
- * - A neighbour going into NUD_REACHABLE, NUD_STALE, NUD_DELAY, and
- * even NUD_PROBE is perfectly normal; we merely record the new state.
- *
- * - A neighbour's entry may be deleted (RTM_DELNEIGH), for example due
- * to garbage collection. This is not necessarily of immediate
- * concern; we record the neighbour as moving to NUD_NONE.
- *
- * - A neighbour transitioning to NUD_FAILED (for any reason) is
- * critically important and is handled as described below in #4.
- *
- * 3. All on-link neighbours in the watch list can be forcibly "probed" by
- * calling #probeAll(). This should be called whenever it is important to
- * verify that critical neighbours on the link are still reachable, e.g.
- * when roaming between BSSIDs.
- *
- * - The kernel will send unicast ARP requests for IPv4 neighbours and
- * unicast NS packets for IPv6 neighbours. The expected replies will
- * likely be unicast.
- *
- * - The forced probing is done holding a wakelock. The kernel may,
- * however, initiate probing of a neighbor on its own, i.e. whenever
- * a neighbour has expired from NUD_DELAY.
- *
- * - The kernel sends:
- *
- * /proc/sys/net/ipv{4,6}/neigh/<ifname>/ucast_solicit
- *
- * number of probes (usually 3) every:
- *
- * /proc/sys/net/ipv{4,6}/neigh/<ifname>/retrans_time_ms
- *
- * number of milliseconds (usually 1000ms). This normally results in
- * 3 unicast packets, 1 per second.
- *
- * - If no response is received to any of the probe packets, the kernel
- * marks the neighbour as being in state NUD_FAILED, and the listening
- * process in #2 will learn of it.
- *
- * 4. We call the supplied Callback#notifyLost() function if the loss of a
- * neighbour in NUD_FAILED would cause IPv4 or IPv6 configuration to
- * become incomplete (a loss of provisioning).
- *
- * - For example, losing all our IPv4 on-link DNS servers (or losing
- * our only IPv6 default gateway) constitutes a loss of IPv4 (IPv6)
- * provisioning; Callback#notifyLost() would be called.
- *
- * - Since it can be non-trivial to reacquire certain IP provisioning
- * state it may be best for the link to disconnect completely and
- * reconnect afresh.
- *
- * Accessing an instance of this class from multiple threads is NOT safe.
- *
- * @hide
- */
-public class IpReachabilityMonitor {
- private static final String TAG = "IpReachabilityMonitor";
- private static final boolean DBG = false;
- private static final boolean VDBG = false;
-
- public interface Callback {
- // This callback function must execute as quickly as possible as it is
- // run on the same thread that listens to kernel neighbor updates.
- //
- // TODO: refactor to something like notifyProvisioningLost(String msg).
- public void notifyLost(InetAddress ip, String logMsg);
- }
-
- /**
- * Encapsulates IpReachabilityMonitor depencencies on systems that hinder unit testing.
- * TODO: consider also wrapping MultinetworkPolicyTracker in this interface.
- */
- interface Dependencies {
- void acquireWakeLock(long durationMs);
-
- static Dependencies makeDefault(Context context, String iface) {
- final String lockName = TAG + "." + iface;
- final PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- final WakeLock lock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, lockName);
-
- return new Dependencies() {
- public void acquireWakeLock(long durationMs) {
- lock.acquire(durationMs);
- }
- };
- }
- }
-
- private final InterfaceParams mInterfaceParams;
- private final IpNeighborMonitor mIpNeighborMonitor;
- private final SharedLog mLog;
- private final Callback mCallback;
- private final Dependencies mDependencies;
- private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
- private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
- private LinkProperties mLinkProperties = new LinkProperties();
- private Map<InetAddress, NeighborEvent> mNeighborWatchList = new HashMap<>();
- // Time in milliseconds of the last forced probe request.
- private volatile long mLastProbeTimeMs;
-
- public IpReachabilityMonitor(
- Context context, InterfaceParams ifParams, Handler h, SharedLog log, Callback callback,
- MultinetworkPolicyTracker tracker) {
- this(ifParams, h, log, callback, tracker, Dependencies.makeDefault(context, ifParams.name));
- }
-
- @VisibleForTesting
- IpReachabilityMonitor(InterfaceParams ifParams, Handler h, SharedLog log, Callback callback,
- MultinetworkPolicyTracker tracker, Dependencies dependencies) {
- if (ifParams == null) throw new IllegalArgumentException("null InterfaceParams");
-
- mInterfaceParams = ifParams;
- mLog = log.forSubComponent(TAG);
- mCallback = callback;
- mMultinetworkPolicyTracker = tracker;
- mDependencies = dependencies;
-
- mIpNeighborMonitor = new IpNeighborMonitor(h, mLog,
- (NeighborEvent event) -> {
- if (mInterfaceParams.index != event.ifindex) return;
- if (!mNeighborWatchList.containsKey(event.ip)) return;
-
- final NeighborEvent prev = mNeighborWatchList.put(event.ip, event);
-
- // TODO: Consider what to do with other states that are not within
- // NeighborEvent#isValid() (i.e. NUD_NONE, NUD_INCOMPLETE).
- if (event.nudState == StructNdMsg.NUD_FAILED) {
- mLog.w("ALERT neighbor went from: " + prev + " to: " + event);
- handleNeighborLost(event);
- }
- });
- mIpNeighborMonitor.start();
- }
-
- public void stop() {
- mIpNeighborMonitor.stop();
- clearLinkProperties();
- }
-
- public void dump(PrintWriter pw) {
- DumpUtils.dumpAsync(
- mIpNeighborMonitor.getHandler(),
- new Dump() {
- @Override
- public void dump(PrintWriter pw, String prefix) {
- pw.println(describeWatchList("\n"));
- }
- },
- pw, "", 1000);
- }
-
- private String describeWatchList() { return describeWatchList(" "); }
-
- private String describeWatchList(String sep) {
- final StringBuilder sb = new StringBuilder();
- sb.append("iface{" + mInterfaceParams + "}," + sep);
- sb.append("ntable=[" + sep);
- String delimiter = "";
- for (Map.Entry<InetAddress, NeighborEvent> entry : mNeighborWatchList.entrySet()) {
- sb.append(delimiter).append(entry.getKey().getHostAddress() + "/" + entry.getValue());
- delimiter = "," + sep;
- }
- sb.append("]");
- return sb.toString();
- }
-
- private static boolean isOnLink(List<RouteInfo> routes, InetAddress ip) {
- for (RouteInfo route : routes) {
- if (!route.hasGateway() && route.matches(ip)) {
- return true;
- }
- }
- return false;
- }
-
- public void updateLinkProperties(LinkProperties lp) {
- if (!mInterfaceParams.name.equals(lp.getInterfaceName())) {
- // TODO: figure out whether / how to cope with interface changes.
- Log.wtf(TAG, "requested LinkProperties interface '" + lp.getInterfaceName() +
- "' does not match: " + mInterfaceParams.name);
- return;
- }
-
- mLinkProperties = new LinkProperties(lp);
- Map<InetAddress, NeighborEvent> newNeighborWatchList = new HashMap<>();
-
- final List<RouteInfo> routes = mLinkProperties.getRoutes();
- for (RouteInfo route : routes) {
- if (route.hasGateway()) {
- InetAddress gw = route.getGateway();
- if (isOnLink(routes, gw)) {
- newNeighborWatchList.put(gw, mNeighborWatchList.getOrDefault(gw, null));
- }
- }
- }
-
- for (InetAddress dns : lp.getDnsServers()) {
- if (isOnLink(routes, dns)) {
- newNeighborWatchList.put(dns, mNeighborWatchList.getOrDefault(dns, null));
- }
- }
-
- mNeighborWatchList = newNeighborWatchList;
- if (DBG) { Log.d(TAG, "watch: " + describeWatchList()); }
- }
-
- public void clearLinkProperties() {
- mLinkProperties.clear();
- mNeighborWatchList.clear();
- if (DBG) { Log.d(TAG, "clear: " + describeWatchList()); }
- }
-
- private void handleNeighborLost(NeighborEvent event) {
- final LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
-
- InetAddress ip = null;
- for (Map.Entry<InetAddress, NeighborEvent> entry : mNeighborWatchList.entrySet()) {
- // TODO: Consider using NeighborEvent#isValid() here; it's more
- // strict but may interact badly if other entries are somehow in
- // NUD_INCOMPLETE (say, during network attach).
- if (entry.getValue().nudState != StructNdMsg.NUD_FAILED) continue;
-
- ip = entry.getKey();
- for (RouteInfo route : mLinkProperties.getRoutes()) {
- if (ip.equals(route.getGateway())) {
- whatIfLp.removeRoute(route);
- }
- }
-
- if (avoidingBadLinks() || !(ip instanceof Inet6Address)) {
- // We should do this unconditionally, but alas we cannot: b/31827713.
- whatIfLp.removeDnsServer(ip);
- }
- }
-
- final ProvisioningChange delta = LinkProperties.compareProvisioning(
- mLinkProperties, whatIfLp);
-
- if (delta == ProvisioningChange.LOST_PROVISIONING) {
- final String logMsg = "FAILURE: LOST_PROVISIONING, " + event;
- Log.w(TAG, logMsg);
- if (mCallback != null) {
- // TODO: remove |ip| when the callback signature no longer has
- // an InetAddress argument.
- mCallback.notifyLost(ip, logMsg);
- }
- }
- logNudFailed(delta);
- }
-
- private boolean avoidingBadLinks() {
- return (mMultinetworkPolicyTracker == null) || mMultinetworkPolicyTracker.getAvoidBadWifi();
- }
-
- public void probeAll() {
- final List<InetAddress> ipProbeList = new ArrayList<>(mNeighborWatchList.keySet());
-
- if (!ipProbeList.isEmpty()) {
- // Keep the CPU awake long enough to allow all ARP/ND
- // probes a reasonable chance at success. See b/23197666.
- //
- // The wakelock we use is (by default) refcounted, and this version
- // of acquire(timeout) queues a release message to keep acquisitions
- // and releases balanced.
- mDependencies.acquireWakeLock(getProbeWakeLockDuration());
- }
-
- for (InetAddress ip : ipProbeList) {
- final int rval = IpNeighborMonitor.startKernelNeighborProbe(mInterfaceParams.index, ip);
- mLog.log(String.format("put neighbor %s into NUD_PROBE state (rval=%d)",
- ip.getHostAddress(), rval));
- logEvent(IpReachabilityEvent.PROBE, rval);
- }
- mLastProbeTimeMs = SystemClock.elapsedRealtime();
- }
-
- private static long getProbeWakeLockDuration() {
- // Ideally, this would be computed by examining the values of:
- //
- // /proc/sys/net/ipv[46]/neigh/<ifname>/ucast_solicit
- //
- // and:
- //
- // /proc/sys/net/ipv[46]/neigh/<ifname>/retrans_time_ms
- //
- // For now, just make some assumptions.
- final long numUnicastProbes = 3;
- final long retransTimeMs = 1000;
- final long gracePeriodMs = 500;
- return (numUnicastProbes * retransTimeMs) + gracePeriodMs;
- }
-
- private void logEvent(int probeType, int errorCode) {
- int eventType = probeType | (errorCode & 0xff);
- mMetricsLog.log(mInterfaceParams.name, new IpReachabilityEvent(eventType));
- }
-
- private void logNudFailed(ProvisioningChange delta) {
- long duration = SystemClock.elapsedRealtime() - mLastProbeTimeMs;
- boolean isFromProbe = (duration < getProbeWakeLockDuration());
- boolean isProvisioningLost = (delta == ProvisioningChange.LOST_PROVISIONING);
- int eventType = IpReachabilityEvent.nudFailureEventType(isFromProbe, isProvisioningLost);
- mMetricsLog.log(mInterfaceParams.name, new IpReachabilityEvent(eventType));
- }
-}
diff --git a/services/net/java/android/net/ip/IpServer.java b/services/net/java/android/net/ip/IpServer.java
index 8b22f68..f7360f5 100644
--- a/services/net/java/android/net/ip/IpServer.java
+++ b/services/net/java/android/net/ip/IpServer.java
@@ -40,7 +40,7 @@
import android.net.ip.RouterAdvertisementDaemon.RaParams;
import android.net.util.InterfaceParams;
import android.net.util.InterfaceSet;
-import android.net.util.NetdService;
+import android.net.shared.NetdService;
import android.net.util.SharedLog;
import android.os.INetworkManagementService;
import android.os.Looper;
@@ -226,7 +226,7 @@
mNetd = deps.getNetdService();
mStatsService = statsService;
mCallback = callback;
- mInterfaceCtrl = new InterfaceController(ifaceName, nMService, mNetd, mLog);
+ mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog);
mIfaceName = ifaceName;
mInterfaceType = interfaceType;
mLinkProperties = new LinkProperties();
diff --git a/services/net/java/android/net/ip/RouterAdvertisementDaemon.java b/services/net/java/android/net/ip/RouterAdvertisementDaemon.java
index d197d01..8e3023b 100644
--- a/services/net/java/android/net/ip/RouterAdvertisementDaemon.java
+++ b/services/net/java/android/net/ip/RouterAdvertisementDaemon.java
@@ -18,11 +18,15 @@
import static android.net.util.NetworkConstants.IPV6_MIN_MTU;
import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
-import static android.system.OsConstants.*;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+import static android.system.OsConstants.SOCK_RAW;
+import static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_BINDTODEVICE;
+import static android.system.OsConstants.SO_SNDTIMEO;
import android.net.IpPrefix;
import android.net.LinkAddress;
-import android.net.LinkProperties;
import android.net.NetworkUtils;
import android.net.TrafficStats;
import android.net.util.InterfaceParams;
@@ -34,10 +38,8 @@
import com.android.internal.annotations.GuardedBy;
import libcore.io.IoBridge;
-import libcore.util.HexEncoding;
import java.io.FileDescriptor;
-import java.io.InterruptedIOException;
import java.io.IOException;
import java.net.Inet6Address;
import java.net.InetAddress;
@@ -47,7 +49,6 @@
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
diff --git a/services/net/java/android/net/netlink/ConntrackMessage.java b/services/net/java/android/net/netlink/ConntrackMessage.java
index 605c46b..6978739 100644
--- a/services/net/java/android/net/netlink/ConntrackMessage.java
+++ b/services/net/java/android/net/netlink/ConntrackMessage.java
@@ -16,23 +16,15 @@
package android.net.netlink;
-import static android.net.netlink.NetlinkConstants.alignedLengthOf;
-import static android.net.netlink.StructNlAttr.makeNestedType;
-import static android.net.netlink.StructNlAttr.NLA_HEADERLEN;
import static android.net.netlink.StructNlMsgHdr.NLM_F_ACK;
-import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP;
import static android.net.netlink.StructNlMsgHdr.NLM_F_REPLACE;
import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
-import static android.net.util.NetworkConstants.IPV4_ADDR_LEN;
+
import static java.nio.ByteOrder.BIG_ENDIAN;
import android.system.OsConstants;
-import android.util.Log;
-import libcore.io.SizeOf;
import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
diff --git a/services/net/java/android/net/netlink/NetlinkSocket.java b/services/net/java/android/net/netlink/NetlinkSocket.java
index 40098c1..2a98d90 100644
--- a/services/net/java/android/net/netlink/NetlinkSocket.java
+++ b/services/net/java/android/net/netlink/NetlinkSocket.java
@@ -16,26 +16,26 @@
package android.net.netlink;
+import static android.net.util.SocketUtils.makeNetlinkSocketAddress;
import static android.system.OsConstants.AF_NETLINK;
import static android.system.OsConstants.EIO;
import static android.system.OsConstants.EPROTO;
import static android.system.OsConstants.ETIMEDOUT;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOL_SOCKET;
import static android.system.OsConstants.SO_RCVBUF;
import static android.system.OsConstants.SO_RCVTIMEO;
import static android.system.OsConstants.SO_SNDTIMEO;
-import static android.system.OsConstants.SOCK_DGRAM;
-import static android.system.OsConstants.SOL_SOCKET;
import android.system.ErrnoException;
-import android.system.NetlinkSocketAddress;
import android.system.Os;
import android.system.StructTimeval;
import android.util.Log;
+
import libcore.io.IoUtils;
import java.io.FileDescriptor;
import java.io.InterruptedIOException;
-import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -106,7 +106,7 @@
}
public static void connectToKernel(FileDescriptor fd) throws ErrnoException, SocketException {
- Os.connect(fd, (SocketAddress) (new NetlinkSocketAddress(0, 0)));
+ Os.connect(fd, makeNetlinkSocketAddress(0, 0));
}
private static void checkTimeout(long timeoutMs) {
diff --git a/services/net/java/android/net/netlink/StructNfGenMsg.java b/services/net/java/android/net/netlink/StructNfGenMsg.java
index 99695e2..8155977 100644
--- a/services/net/java/android/net/netlink/StructNfGenMsg.java
+++ b/services/net/java/android/net/netlink/StructNfGenMsg.java
@@ -16,8 +16,6 @@
package android.net.netlink;
-import libcore.io.SizeOf;
-
import java.nio.ByteBuffer;
@@ -29,7 +27,7 @@
* @hide
*/
public class StructNfGenMsg {
- public static final int STRUCT_SIZE = 2 + SizeOf.SHORT;
+ public static final int STRUCT_SIZE = 2 + Short.BYTES;
public static final int NFNETLINK_V0 = 0;
diff --git a/services/net/java/android/net/netlink/StructNlAttr.java b/services/net/java/android/net/netlink/StructNlAttr.java
index 811bdbb..28a4e88 100644
--- a/services/net/java/android/net/netlink/StructNlAttr.java
+++ b/services/net/java/android/net/netlink/StructNlAttr.java
@@ -17,7 +17,6 @@
package android.net.netlink;
import android.net.netlink.NetlinkConstants;
-import libcore.io.SizeOf;
import java.net.InetAddress;
import java.net.UnknownHostException;
@@ -117,7 +116,7 @@
public StructNlAttr(short type, short value, ByteOrder order) {
this(order);
nla_type = type;
- setValue(new byte[SizeOf.SHORT]);
+ setValue(new byte[Short.BYTES]);
getValueAsByteBuffer().putShort(value);
}
@@ -128,7 +127,7 @@
public StructNlAttr(short type, int value, ByteOrder order) {
this(order);
nla_type = type;
- setValue(new byte[SizeOf.INT]);
+ setValue(new byte[Integer.BYTES]);
getValueAsByteBuffer().putInt(value);
}
@@ -164,7 +163,7 @@
public int getValueAsInt(int defaultValue) {
final ByteBuffer byteBuffer = getValueAsByteBuffer();
- if (byteBuffer == null || byteBuffer.remaining() != SizeOf.INT) {
+ if (byteBuffer == null || byteBuffer.remaining() != Integer.BYTES) {
return defaultValue;
}
return getValueAsByteBuffer().getInt();
diff --git a/services/net/java/android/net/netlink/StructNlMsgErr.java b/services/net/java/android/net/netlink/StructNlMsgErr.java
index f095af4..6fcc6e6 100644
--- a/services/net/java/android/net/netlink/StructNlMsgErr.java
+++ b/services/net/java/android/net/netlink/StructNlMsgErr.java
@@ -18,7 +18,6 @@
import android.net.netlink.NetlinkConstants;
import android.net.netlink.StructNlMsgHdr;
-import libcore.io.SizeOf;
import java.nio.ByteBuffer;
@@ -31,7 +30,7 @@
* @hide
*/
public class StructNlMsgErr {
- public static final int STRUCT_SIZE = SizeOf.INT + StructNlMsgHdr.STRUCT_SIZE;
+ public static final int STRUCT_SIZE = Integer.BYTES + StructNlMsgHdr.STRUCT_SIZE;
public static boolean hasAvailableSpace(ByteBuffer byteBuffer) {
return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE;
diff --git a/services/net/java/android/net/shared/InitialConfiguration.java b/services/net/java/android/net/shared/InitialConfiguration.java
new file mode 100644
index 0000000..bc2373f
--- /dev/null
+++ b/services/net/java/android/net/shared/InitialConfiguration.java
@@ -0,0 +1,234 @@
+/*
+ * 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.shared;
+
+import static android.net.shared.ParcelableUtil.fromParcelableArray;
+import static android.net.shared.ParcelableUtil.toParcelableArray;
+import static android.text.TextUtils.join;
+
+import android.net.InitialConfigurationParcelable;
+import android.net.IpPrefix;
+import android.net.IpPrefixParcelable;
+import android.net.LinkAddress;
+import android.net.LinkAddressParcelable;
+import android.net.RouteInfo;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Predicate;
+
+/** @hide */
+public class InitialConfiguration {
+ public final Set<LinkAddress> ipAddresses = new HashSet<>();
+ public final Set<IpPrefix> directlyConnectedRoutes = new HashSet<>();
+ public final Set<InetAddress> dnsServers = new HashSet<>();
+
+ private static final int RFC6177_MIN_PREFIX_LENGTH = 48;
+ private static final int RFC7421_PREFIX_LENGTH = 64;
+
+ /**
+ * Create a InitialConfiguration that is a copy of the specified configuration.
+ */
+ public static InitialConfiguration copy(InitialConfiguration config) {
+ if (config == null) {
+ return null;
+ }
+ InitialConfiguration configCopy = new InitialConfiguration();
+ configCopy.ipAddresses.addAll(config.ipAddresses);
+ configCopy.directlyConnectedRoutes.addAll(config.directlyConnectedRoutes);
+ configCopy.dnsServers.addAll(config.dnsServers);
+ return configCopy;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "InitialConfiguration(IPs: {%s}, prefixes: {%s}, DNS: {%s})",
+ join(", ", ipAddresses), join(", ", directlyConnectedRoutes),
+ join(", ", dnsServers));
+ }
+
+ /**
+ * Tests whether the contents of this IpConfiguration represent a valid configuration.
+ */
+ public boolean isValid() {
+ if (ipAddresses.isEmpty()) {
+ return false;
+ }
+
+ // For every IP address, there must be at least one prefix containing that address.
+ for (LinkAddress addr : ipAddresses) {
+ if (!any(directlyConnectedRoutes, (p) -> p.contains(addr.getAddress()))) {
+ return false;
+ }
+ }
+ // For every dns server, there must be at least one prefix containing that address.
+ for (InetAddress addr : dnsServers) {
+ if (!any(directlyConnectedRoutes, (p) -> p.contains(addr))) {
+ return false;
+ }
+ }
+ // All IPv6 LinkAddresses have an RFC7421-suitable prefix length
+ // (read: compliant with RFC4291#section2.5.4).
+ if (any(ipAddresses, not(InitialConfiguration::isPrefixLengthCompliant))) {
+ return false;
+ }
+ // If directlyConnectedRoutes contains an IPv6 default route
+ // then ipAddresses MUST contain at least one non-ULA GUA.
+ if (any(directlyConnectedRoutes, InitialConfiguration::isIPv6DefaultRoute)
+ && all(ipAddresses, not(InitialConfiguration::isIPv6GUA))) {
+ return false;
+ }
+ // The prefix length of routes in directlyConnectedRoutes be within reasonable
+ // bounds for IPv6: /48-/64 just as we’d accept in RIOs.
+ if (any(directlyConnectedRoutes, not(InitialConfiguration::isPrefixLengthCompliant))) {
+ return false;
+ }
+ // There no more than one IPv4 address
+ if (ipAddresses.stream().filter(LinkAddress::isIPv4).count() > 1) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @return true if the given list of addressess and routes satisfies provisioning for this
+ * InitialConfiguration. LinkAddresses and RouteInfo objects are not compared with equality
+ * because addresses and routes seen by Netlink will contain additional fields like flags,
+ * interfaces, and so on. If this InitialConfiguration has no IP address specified, the
+ * provisioning check always fails.
+ *
+ * If the given list of routes is null, only addresses are taken into considerations.
+ */
+ public boolean isProvisionedBy(List<LinkAddress> addresses, List<RouteInfo> routes) {
+ if (ipAddresses.isEmpty()) {
+ return false;
+ }
+
+ for (LinkAddress addr : ipAddresses) {
+ if (!any(addresses, (addrSeen) -> addr.isSameAddressAs(addrSeen))) {
+ return false;
+ }
+ }
+
+ if (routes != null) {
+ for (IpPrefix prefix : directlyConnectedRoutes) {
+ if (!any(routes, (routeSeen) -> isDirectlyConnectedRoute(routeSeen, prefix))) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Convert this configuration to a {@link InitialConfigurationParcelable}.
+ */
+ public InitialConfigurationParcelable toStableParcelable() {
+ final InitialConfigurationParcelable p = new InitialConfigurationParcelable();
+ p.ipAddresses = toParcelableArray(ipAddresses,
+ LinkPropertiesParcelableUtil::toStableParcelable, LinkAddressParcelable.class);
+ p.directlyConnectedRoutes = toParcelableArray(directlyConnectedRoutes,
+ LinkPropertiesParcelableUtil::toStableParcelable, IpPrefixParcelable.class);
+ p.dnsServers = toParcelableArray(
+ dnsServers, IpConfigurationParcelableUtil::parcelAddress, String.class);
+ return p;
+ }
+
+ /**
+ * Create an instance of {@link InitialConfiguration} based on the contents of the specified
+ * {@link InitialConfigurationParcelable}.
+ */
+ public static InitialConfiguration fromStableParcelable(InitialConfigurationParcelable p) {
+ if (p == null) return null;
+ final InitialConfiguration config = new InitialConfiguration();
+ config.ipAddresses.addAll(fromParcelableArray(
+ p.ipAddresses, LinkPropertiesParcelableUtil::fromStableParcelable));
+ config.directlyConnectedRoutes.addAll(fromParcelableArray(
+ p.directlyConnectedRoutes, LinkPropertiesParcelableUtil::fromStableParcelable));
+ config.dnsServers.addAll(
+ fromParcelableArray(p.dnsServers, IpConfigurationParcelableUtil::unparcelAddress));
+ return config;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof InitialConfiguration)) return false;
+ final InitialConfiguration other = (InitialConfiguration) obj;
+ return ipAddresses.equals(other.ipAddresses)
+ && directlyConnectedRoutes.equals(other.directlyConnectedRoutes)
+ && dnsServers.equals(other.dnsServers);
+ }
+
+ private static boolean isDirectlyConnectedRoute(RouteInfo route, IpPrefix prefix) {
+ return !route.hasGateway() && prefix.equals(route.getDestination());
+ }
+
+ private static boolean isPrefixLengthCompliant(LinkAddress addr) {
+ return addr.isIPv4() || isCompliantIPv6PrefixLength(addr.getPrefixLength());
+ }
+
+ private static boolean isPrefixLengthCompliant(IpPrefix prefix) {
+ return prefix.isIPv4() || isCompliantIPv6PrefixLength(prefix.getPrefixLength());
+ }
+
+ private static boolean isCompliantIPv6PrefixLength(int prefixLength) {
+ return (RFC6177_MIN_PREFIX_LENGTH <= prefixLength)
+ && (prefixLength <= RFC7421_PREFIX_LENGTH);
+ }
+
+ private static boolean isIPv6DefaultRoute(IpPrefix prefix) {
+ return prefix.getAddress().equals(Inet6Address.ANY);
+ }
+
+ private static boolean isIPv6GUA(LinkAddress addr) {
+ return addr.isIPv6() && addr.isGlobalPreferred();
+ }
+
+ // TODO: extract out into CollectionUtils.
+
+ /**
+ * Indicate whether any element of the specified iterable verifies the specified predicate.
+ */
+ public static <T> boolean any(Iterable<T> coll, Predicate<T> fn) {
+ for (T t : coll) {
+ if (fn.test(t)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Indicate whether all elements of the specified iterable verifies the specified predicate.
+ */
+ public static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
+ return !any(coll, not(fn));
+ }
+
+ /**
+ * Create a predicate that returns the opposite value of the specified predicate.
+ */
+ public static <T> Predicate<T> not(Predicate<T> fn) {
+ return (t) -> !fn.test(t);
+ }
+}
diff --git a/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java b/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java
new file mode 100644
index 0000000..2c368c8
--- /dev/null
+++ b/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java
@@ -0,0 +1,135 @@
+/*
+ * 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.shared;
+
+import static android.net.shared.ParcelableUtil.fromParcelableArray;
+import static android.net.shared.ParcelableUtil.toParcelableArray;
+
+import android.annotation.Nullable;
+import android.net.ApfCapabilitiesParcelable;
+import android.net.DhcpResults;
+import android.net.DhcpResultsParcelable;
+import android.net.InetAddresses;
+import android.net.StaticIpConfiguration;
+import android.net.StaticIpConfigurationParcelable;
+import android.net.apf.ApfCapabilities;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+
+/**
+ * Collection of utility methods to convert to and from stable AIDL parcelables for IpClient
+ * configuration classes.
+ * @hide
+ */
+public final class IpConfigurationParcelableUtil {
+ /**
+ * Convert a StaticIpConfiguration to a StaticIpConfigurationParcelable.
+ */
+ public static StaticIpConfigurationParcelable toStableParcelable(
+ @Nullable StaticIpConfiguration config) {
+ if (config == null) return null;
+ final StaticIpConfigurationParcelable p = new StaticIpConfigurationParcelable();
+ p.ipAddress = LinkPropertiesParcelableUtil.toStableParcelable(config.ipAddress);
+ p.gateway = parcelAddress(config.gateway);
+ p.dnsServers = toParcelableArray(
+ config.dnsServers, IpConfigurationParcelableUtil::parcelAddress, String.class);
+ p.domains = config.domains;
+ return p;
+ }
+
+ /**
+ * Convert a StaticIpConfigurationParcelable to a StaticIpConfiguration.
+ */
+ public static StaticIpConfiguration fromStableParcelable(
+ @Nullable StaticIpConfigurationParcelable p) {
+ if (p == null) return null;
+ final StaticIpConfiguration config = new StaticIpConfiguration();
+ config.ipAddress = LinkPropertiesParcelableUtil.fromStableParcelable(p.ipAddress);
+ config.gateway = unparcelAddress(p.gateway);
+ config.dnsServers.addAll(fromParcelableArray(
+ p.dnsServers, IpConfigurationParcelableUtil::unparcelAddress));
+ config.domains = p.domains;
+ return config;
+ }
+
+ /**
+ * Convert DhcpResults to a DhcpResultsParcelable.
+ */
+ public static DhcpResultsParcelable toStableParcelable(@Nullable DhcpResults results) {
+ if (results == null) return null;
+ final DhcpResultsParcelable p = new DhcpResultsParcelable();
+ p.baseConfiguration = toStableParcelable((StaticIpConfiguration) results);
+ p.leaseDuration = results.leaseDuration;
+ p.mtu = results.mtu;
+ p.serverAddress = parcelAddress(results.serverAddress);
+ p.vendorInfo = results.vendorInfo;
+ return p;
+ }
+
+ /**
+ * Convert a DhcpResultsParcelable to DhcpResults.
+ */
+ public static DhcpResults fromStableParcelable(@Nullable DhcpResultsParcelable p) {
+ if (p == null) return null;
+ final DhcpResults results = new DhcpResults(fromStableParcelable(p.baseConfiguration));
+ results.leaseDuration = p.leaseDuration;
+ results.mtu = p.mtu;
+ results.serverAddress = (Inet4Address) unparcelAddress(p.serverAddress);
+ results.vendorInfo = p.vendorInfo;
+ return results;
+ }
+
+ /**
+ * Convert ApfCapabilities to ApfCapabilitiesParcelable.
+ */
+ public static ApfCapabilitiesParcelable toStableParcelable(@Nullable ApfCapabilities caps) {
+ if (caps == null) return null;
+ final ApfCapabilitiesParcelable p = new ApfCapabilitiesParcelable();
+ p.apfVersionSupported = caps.apfVersionSupported;
+ p.maximumApfProgramSize = caps.maximumApfProgramSize;
+ p.apfPacketFormat = caps.apfPacketFormat;
+ return p;
+ }
+
+ /**
+ * Convert ApfCapabilitiesParcelable toApfCapabilities.
+ */
+ public static ApfCapabilities fromStableParcelable(@Nullable ApfCapabilitiesParcelable p) {
+ if (p == null) return null;
+ return new ApfCapabilities(
+ p.apfVersionSupported, p.maximumApfProgramSize, p.apfPacketFormat);
+ }
+
+ /**
+ * Convert InetAddress to String.
+ * TODO: have an InetAddressParcelable
+ */
+ public static String parcelAddress(@Nullable InetAddress addr) {
+ if (addr == null) return null;
+ return addr.getHostAddress();
+ }
+
+ /**
+ * Convert String to InetAddress.
+ * TODO: have an InetAddressParcelable
+ */
+ public static InetAddress unparcelAddress(@Nullable String addr) {
+ if (addr == null) return null;
+ return InetAddresses.parseNumericAddress(addr);
+ }
+}
diff --git a/services/net/java/android/net/shared/LinkPropertiesParcelableUtil.java b/services/net/java/android/net/shared/LinkPropertiesParcelableUtil.java
index 5b77f54..51d955d 100644
--- a/services/net/java/android/net/shared/LinkPropertiesParcelableUtil.java
+++ b/services/net/java/android/net/shared/LinkPropertiesParcelableUtil.java
@@ -16,11 +16,12 @@
package android.net.shared;
+import static android.net.shared.IpConfigurationParcelableUtil.parcelAddress;
+import static android.net.shared.IpConfigurationParcelableUtil.unparcelAddress;
import static android.net.shared.ParcelableUtil.fromParcelableArray;
import static android.net.shared.ParcelableUtil.toParcelableArray;
import android.annotation.Nullable;
-import android.net.InetAddresses;
import android.net.IpPrefix;
import android.net.IpPrefixParcelable;
import android.net.LinkAddress;
@@ -33,7 +34,6 @@
import android.net.RouteInfoParcelable;
import android.net.Uri;
-import java.net.InetAddress;
import java.util.Arrays;
/**
@@ -81,7 +81,7 @@
return null;
}
final IpPrefixParcelable parcel = new IpPrefixParcelable();
- parcel.address = ipPrefix.getAddress().getHostAddress();
+ parcel.address = parcelAddress(ipPrefix.getAddress());
parcel.prefixLength = ipPrefix.getPrefixLength();
return parcel;
}
@@ -93,7 +93,7 @@
if (parcel == null) {
return null;
}
- return new IpPrefix(InetAddresses.parseNumericAddress(parcel.address), parcel.prefixLength);
+ return new IpPrefix(unparcelAddress(parcel.address), parcel.prefixLength);
}
/**
@@ -105,7 +105,7 @@
}
final RouteInfoParcelable parcel = new RouteInfoParcelable();
parcel.destination = toStableParcelable(routeInfo.getDestination());
- parcel.gatewayAddr = routeInfo.getGateway().getHostAddress();
+ parcel.gatewayAddr = parcelAddress(routeInfo.getGateway());
parcel.ifaceName = routeInfo.getInterface();
parcel.type = routeInfo.getType();
return parcel;
@@ -120,7 +120,7 @@
}
final IpPrefix destination = fromStableParcelable(parcel.destination);
return new RouteInfo(
- destination, InetAddresses.parseNumericAddress(parcel.gatewayAddr),
+ destination, unparcelAddress(parcel.gatewayAddr),
parcel.ifaceName, parcel.type);
}
@@ -132,7 +132,7 @@
return null;
}
final LinkAddressParcelable parcel = new LinkAddressParcelable();
- parcel.address = la.getAddress().getHostAddress();
+ parcel.address = parcelAddress(la.getAddress());
parcel.prefixLength = la.getPrefixLength();
parcel.flags = la.getFlags();
parcel.scope = la.getScope();
@@ -147,7 +147,7 @@
return null;
}
return new LinkAddress(
- InetAddresses.parseNumericAddress(parcel.address),
+ unparcelAddress(parcel.address),
parcel.prefixLength,
parcel.flags,
parcel.scope);
@@ -167,11 +167,11 @@
LinkPropertiesParcelableUtil::toStableParcelable,
LinkAddressParcelable.class);
parcel.dnses = toParcelableArray(
- lp.getDnsServers(), InetAddress::getHostAddress, String.class);
+ lp.getDnsServers(), IpConfigurationParcelableUtil::parcelAddress, String.class);
parcel.pcscfs = toParcelableArray(
- lp.getPcscfServers(), InetAddress::getHostAddress, String.class);
- parcel.validatedPrivateDnses = toParcelableArray(
- lp.getValidatedPrivateDnsServers(), InetAddress::getHostAddress, String.class);
+ lp.getPcscfServers(), IpConfigurationParcelableUtil::parcelAddress, String.class);
+ parcel.validatedPrivateDnses = toParcelableArray(lp.getValidatedPrivateDnsServers(),
+ IpConfigurationParcelableUtil::parcelAddress, String.class);
parcel.usePrivateDns = lp.isPrivateDnsActive();
parcel.privateDnsServerName = lp.getPrivateDnsServerName();
parcel.domains = lp.getDomains();
@@ -182,9 +182,6 @@
parcel.mtu = lp.getMtu();
parcel.tcpBufferSizes = lp.getTcpBufferSizes();
parcel.nat64Prefix = toStableParcelable(lp.getNat64Prefix());
- parcel.stackedLinks = toParcelableArray(
- lp.getStackedLinks(), LinkPropertiesParcelableUtil::toStableParcelable,
- LinkPropertiesParcelable.class);
return parcel;
}
@@ -199,11 +196,13 @@
lp.setInterfaceName(parcel.ifaceName);
lp.setLinkAddresses(fromParcelableArray(parcel.linkAddresses,
LinkPropertiesParcelableUtil::fromStableParcelable));
- lp.setDnsServers(fromParcelableArray(parcel.dnses, InetAddresses::parseNumericAddress));
- lp.setPcscfServers(fromParcelableArray(parcel.pcscfs, InetAddresses::parseNumericAddress));
+ lp.setDnsServers(fromParcelableArray(
+ parcel.dnses, IpConfigurationParcelableUtil::unparcelAddress));
+ lp.setPcscfServers(fromParcelableArray(
+ parcel.pcscfs, IpConfigurationParcelableUtil::unparcelAddress));
lp.setValidatedPrivateDnsServers(
fromParcelableArray(parcel.validatedPrivateDnses,
- InetAddresses::parseNumericAddress));
+ IpConfigurationParcelableUtil::unparcelAddress));
lp.setUsePrivateDns(parcel.usePrivateDns);
lp.setPrivateDnsServerName(parcel.privateDnsServerName);
lp.setDomains(parcel.domains);
@@ -214,9 +213,6 @@
lp.setMtu(parcel.mtu);
lp.setTcpBufferSizes(parcel.tcpBufferSizes);
lp.setNat64Prefix(fromStableParcelable(parcel.nat64Prefix));
- for (LinkPropertiesParcelable stackedLink : parcel.stackedLinks) {
- lp.addStackedLink(fromStableParcelable(stackedLink));
- }
return lp;
}
}
diff --git a/services/net/java/android/net/shared/NetdService.java b/services/net/java/android/net/shared/NetdService.java
new file mode 100644
index 0000000..be0f5f2
--- /dev/null
+++ b/services/net/java/android/net/shared/NetdService.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2016 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.shared;
+
+import android.net.INetd;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.util.Log;
+
+
+/**
+ * @hide
+ */
+public class NetdService {
+ private static final String TAG = NetdService.class.getSimpleName();
+ private static final String NETD_SERVICE_NAME = "netd";
+ private static final long BASE_TIMEOUT_MS = 100;
+ private static final long MAX_TIMEOUT_MS = 1000;
+
+
+ /**
+ * Return an INetd instance, or null if not available.
+ *
+ * It is the caller's responsibility to check for a null return value
+ * and to handle RemoteException errors from invocations on the returned
+ * interface if, for example, netd dies and is restarted.
+ *
+ * Returned instances of INetd should not be cached.
+ *
+ * @return an INetd instance or null.
+ */
+ public static INetd getInstance() {
+ // NOTE: ServiceManager does no caching for the netd service,
+ // because netd is not one of the defined common services.
+ final INetd netdInstance = INetd.Stub.asInterface(
+ ServiceManager.getService(NETD_SERVICE_NAME));
+ if (netdInstance == null) {
+ Log.w(TAG, "WARNING: returning null INetd instance.");
+ }
+ return netdInstance;
+ }
+
+ /**
+ * Blocks for a specified time until an INetd instance is available.
+ *
+ * It is the caller's responsibility to handle RemoteException errors
+ * from invocations on the returned interface if, for example, netd
+ * dies after this interface was returned.
+ *
+ * Returned instances of INetd should not be cached.
+ *
+ * Special values of maxTimeoutMs include: 0, meaning try to obtain an
+ * INetd instance only once, and -1 (or any value less than 0), meaning
+ * try to obtain an INetd instance indefinitely.
+ *
+ * @param maxTimeoutMs the maximum time to spend getting an INetd instance
+ * @return an INetd instance or null if no instance is available
+ * within |maxTimeoutMs| milliseconds.
+ */
+ public static INetd get(long maxTimeoutMs) {
+ if (maxTimeoutMs == 0) return getInstance();
+
+ final long stop = (maxTimeoutMs > 0)
+ ? SystemClock.elapsedRealtime() + maxTimeoutMs
+ : Long.MAX_VALUE;
+
+ long timeoutMs = 0;
+ while (true) {
+ final INetd netdInstance = getInstance();
+ if (netdInstance != null) {
+ return netdInstance;
+ }
+
+ final long remaining = stop - SystemClock.elapsedRealtime();
+ if (remaining <= 0) break;
+
+ // No netdInstance was received; sleep and retry.
+ timeoutMs = Math.min(timeoutMs + BASE_TIMEOUT_MS, MAX_TIMEOUT_MS);
+ timeoutMs = Math.min(timeoutMs, remaining);
+ try {
+ Thread.sleep(timeoutMs);
+ } catch (InterruptedException e) {}
+ }
+ return null;
+ }
+
+ /**
+ * Blocks until an INetd instance is available.
+ *
+ * It is the caller's responsibility to handle RemoteException errors
+ * from invocations on the returned interface if, for example, netd
+ * dies after this interface was returned.
+ *
+ * Returned instances of INetd should not be cached.
+ *
+ * @return an INetd instance.
+ */
+ public static INetd get() {
+ return get(-1);
+ }
+
+ public static interface NetdCommand {
+ void run(INetd netd) throws RemoteException;
+ }
+
+ /**
+ * Blocks until an INetd instance is availabe, and retries until either
+ * the command succeeds or a runtime exception is thrown.
+ */
+ public static void run(NetdCommand cmd) {
+ while (true) {
+ try {
+ cmd.run(get());
+ return;
+ } catch (RemoteException re) {
+ Log.e(TAG, "error communicating with netd: " + re);
+ }
+ }
+ }
+}
diff --git a/services/net/java/android/net/shared/NetworkMonitorUtils.java b/services/net/java/android/net/shared/NetworkMonitorUtils.java
index 463cf2a..3d2a2de 100644
--- a/services/net/java/android/net/shared/NetworkMonitorUtils.java
+++ b/services/net/java/android/net/shared/NetworkMonitorUtils.java
@@ -16,6 +16,11 @@
package android.net.shared;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
+
import android.content.Context;
import android.net.NetworkCapabilities;
import android.provider.Settings;
@@ -58,9 +63,12 @@
* @param dfltNetCap Default requested network capabilities.
* @param nc Network capabilities of the network to test.
*/
- public static boolean isValidationRequired(
- NetworkCapabilities dfltNetCap, NetworkCapabilities nc) {
+ public static boolean isValidationRequired(NetworkCapabilities nc) {
// TODO: Consider requiring validation for DUN networks.
- return dfltNetCap.satisfiedByNetworkCapabilities(nc);
+ return nc != null
+ && nc.hasCapability(NET_CAPABILITY_INTERNET)
+ && nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ && nc.hasCapability(NET_CAPABILITY_TRUSTED)
+ && nc.hasCapability(NET_CAPABILITY_NOT_VPN);
}
}
diff --git a/services/net/java/android/net/shared/NetworkParcelableUtil.java b/services/net/java/android/net/shared/NetworkParcelableUtil.java
new file mode 100644
index 0000000..d0b54b8
--- /dev/null
+++ b/services/net/java/android/net/shared/NetworkParcelableUtil.java
@@ -0,0 +1,50 @@
+/*
+ * 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.shared;
+
+import android.annotation.Nullable;
+import android.net.Network;
+import android.net.NetworkParcelable;
+
+/**
+ * Utility methods to convert to/from stable AIDL parcelables for network attribute classes.
+ * @hide
+ */
+public final class NetworkParcelableUtil {
+ /**
+ * Convert from a Network to a NetworkParcelable.
+ */
+ public static NetworkParcelable toStableParcelable(@Nullable Network network) {
+ if (network == null) {
+ return null;
+ }
+ final NetworkParcelable p = new NetworkParcelable();
+ p.networkHandle = network.getNetworkHandle();
+
+ return p;
+ }
+
+ /**
+ * Convert from a NetworkParcelable to a Network.
+ */
+ public static Network fromStableParcelable(@Nullable NetworkParcelable p) {
+ if (p == null) {
+ return null;
+ }
+ return Network.fromNetworkHandle(p.networkHandle);
+ }
+}
diff --git a/services/net/java/android/net/shared/ParcelableUtil.java b/services/net/java/android/net/shared/ParcelableUtil.java
index a18976c..3f40300 100644
--- a/services/net/java/android/net/shared/ParcelableUtil.java
+++ b/services/net/java/android/net/shared/ParcelableUtil.java
@@ -20,7 +20,7 @@
import java.lang.reflect.Array;
import java.util.ArrayList;
-import java.util.List;
+import java.util.Collection;
import java.util.function.Function;
/**
@@ -36,7 +36,7 @@
* converter function.
*/
public static <ParcelableType, BaseType> ParcelableType[] toParcelableArray(
- @NonNull List<BaseType> base,
+ @NonNull Collection<BaseType> base,
@NonNull Function<BaseType, ParcelableType> conv,
@NonNull Class<ParcelableType> parcelClass) {
final ParcelableType[] out = (ParcelableType[]) Array.newInstance(parcelClass, base.size());
diff --git a/services/net/java/android/net/shared/PrivateDnsConfig.java b/services/net/java/android/net/shared/PrivateDnsConfig.java
index 41e0bad..c7dc530 100644
--- a/services/net/java/android/net/shared/PrivateDnsConfig.java
+++ b/services/net/java/android/net/shared/PrivateDnsConfig.java
@@ -16,7 +16,9 @@
package android.net.shared;
-import android.net.InetAddresses;
+import static android.net.shared.ParcelableUtil.fromParcelableArray;
+import static android.net.shared.ParcelableUtil.toParcelableArray;
+
import android.net.PrivateDnsConfigParcel;
import android.text.TextUtils;
@@ -70,12 +72,8 @@
public PrivateDnsConfigParcel toParcel() {
final PrivateDnsConfigParcel parcel = new PrivateDnsConfigParcel();
parcel.hostname = hostname;
-
- final String[] parceledIps = new String[ips.length];
- for (int i = 0; i < ips.length; i++) {
- parceledIps[i] = ips[i].getHostAddress();
- }
- parcel.ips = parceledIps;
+ parcel.ips = toParcelableArray(
+ Arrays.asList(ips), IpConfigurationParcelableUtil::parcelAddress, String.class);
return parcel;
}
@@ -84,11 +82,9 @@
* Build a configuration from a stable AIDL-compatible parcel.
*/
public static PrivateDnsConfig fromParcel(PrivateDnsConfigParcel parcel) {
- final InetAddress[] ips = new InetAddress[parcel.ips.length];
- for (int i = 0; i < ips.length; i++) {
- ips[i] = InetAddresses.parseNumericAddress(parcel.ips[i]);
- }
-
+ InetAddress[] ips = new InetAddress[parcel.ips.length];
+ ips = fromParcelableArray(parcel.ips, IpConfigurationParcelableUtil::unparcelAddress)
+ .toArray(ips);
return new PrivateDnsConfig(parcel.hostname, ips);
}
}
diff --git a/services/net/java/android/net/shared/ProvisioningConfiguration.java b/services/net/java/android/net/shared/ProvisioningConfiguration.java
new file mode 100644
index 0000000..f937065
--- /dev/null
+++ b/services/net/java/android/net/shared/ProvisioningConfiguration.java
@@ -0,0 +1,310 @@
+/*
+ * 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.shared;
+
+import android.annotation.Nullable;
+import android.net.INetd;
+import android.net.Network;
+import android.net.ProvisioningConfigurationParcelable;
+import android.net.StaticIpConfiguration;
+import android.net.apf.ApfCapabilities;
+import android.net.ip.IIpClient;
+
+import java.util.Objects;
+import java.util.StringJoiner;
+
+/**
+ * This class encapsulates parameters to be passed to
+ * IpClient#startProvisioning(). A defensive copy is made by IpClient
+ * and the values specified herein are in force until IpClient#stop()
+ * is called.
+ *
+ * Example use:
+ *
+ * final ProvisioningConfiguration config =
+ * new ProvisioningConfiguration.Builder()
+ * .withPreDhcpAction()
+ * .withProvisioningTimeoutMs(36 * 1000)
+ * .build();
+ * mIpClient.startProvisioning(config.toStableParcelable());
+ * ...
+ * mIpClient.stop();
+ *
+ * The specified provisioning configuration will only be active until
+ * IIpClient#stop() is called. Future calls to IIpClient#startProvisioning()
+ * must specify the configuration again.
+ * @hide
+ */
+public class ProvisioningConfiguration {
+ // TODO: Delete this default timeout once those callers that care are
+ // fixed to pass in their preferred timeout.
+ //
+ // We pick 36 seconds so we can send DHCP requests at
+ //
+ // t=0, t=2, t=6, t=14, t=30
+ //
+ // allowing for 10% jitter.
+ private static final int DEFAULT_TIMEOUT_MS = 36 * 1000;
+
+ /**
+ * Builder to create a {@link ProvisioningConfiguration}.
+ */
+ public static class Builder {
+ protected ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
+
+ /**
+ * Specify that the configuration should not enable IPv4. It is enabled by default.
+ */
+ public Builder withoutIPv4() {
+ mConfig.mEnableIPv4 = false;
+ return this;
+ }
+
+ /**
+ * Specify that the configuration should not enable IPv6. It is enabled by default.
+ */
+ public Builder withoutIPv6() {
+ mConfig.mEnableIPv6 = false;
+ return this;
+ }
+
+ /**
+ * Specify that the configuration should not use a MultinetworkPolicyTracker. It is used
+ * by default.
+ */
+ public Builder withoutMultinetworkPolicyTracker() {
+ mConfig.mUsingMultinetworkPolicyTracker = false;
+ return this;
+ }
+
+ /**
+ * Specify that the configuration should not use a IpReachabilityMonitor. It is used by
+ * default.
+ */
+ public Builder withoutIpReachabilityMonitor() {
+ mConfig.mUsingIpReachabilityMonitor = false;
+ return this;
+ }
+
+ /**
+ * Identical to {@link #withPreDhcpAction(int)}, using a default timeout.
+ * @see #withPreDhcpAction(int)
+ */
+ public Builder withPreDhcpAction() {
+ mConfig.mRequestedPreDhcpActionMs = DEFAULT_TIMEOUT_MS;
+ return this;
+ }
+
+ /**
+ * Specify that {@link IpClientCallbacks#onPreDhcpAction()} should be called. Clients must
+ * call {@link IIpClient#completedPreDhcpAction()} when the callback called. This behavior
+ * is disabled by default.
+ * @param dhcpActionTimeoutMs Timeout for clients to call completedPreDhcpAction().
+ */
+ public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
+ mConfig.mRequestedPreDhcpActionMs = dhcpActionTimeoutMs;
+ return this;
+ }
+
+ /**
+ * Specify the initial provisioning configuration.
+ */
+ public Builder withInitialConfiguration(InitialConfiguration initialConfig) {
+ mConfig.mInitialConfig = initialConfig;
+ return this;
+ }
+
+ /**
+ * Specify a static configuration for provisioning.
+ */
+ public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
+ mConfig.mStaticIpConfig = staticConfig;
+ return this;
+ }
+
+ /**
+ * Specify ApfCapabilities.
+ */
+ public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
+ mConfig.mApfCapabilities = apfCapabilities;
+ return this;
+ }
+
+ /**
+ * Specify the timeout to use for provisioning.
+ */
+ public Builder withProvisioningTimeoutMs(int timeoutMs) {
+ mConfig.mProvisioningTimeoutMs = timeoutMs;
+ return this;
+ }
+
+ /**
+ * Specify that IPv6 address generation should use a random MAC address.
+ */
+ public Builder withRandomMacAddress() {
+ mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64;
+ return this;
+ }
+
+ /**
+ * Specify that IPv6 address generation should use a stable MAC address.
+ */
+ public Builder withStableMacAddress() {
+ mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
+ return this;
+ }
+
+ /**
+ * Specify the network to use for provisioning.
+ */
+ public Builder withNetwork(Network network) {
+ mConfig.mNetwork = network;
+ return this;
+ }
+
+ /**
+ * Specify the display name that the IpClient should use.
+ */
+ public Builder withDisplayName(String displayName) {
+ mConfig.mDisplayName = displayName;
+ return this;
+ }
+
+ /**
+ * Build the configuration using previously specified parameters.
+ */
+ public ProvisioningConfiguration build() {
+ return new ProvisioningConfiguration(mConfig);
+ }
+ }
+
+ public boolean mEnableIPv4 = true;
+ public boolean mEnableIPv6 = true;
+ public boolean mUsingMultinetworkPolicyTracker = true;
+ public boolean mUsingIpReachabilityMonitor = true;
+ public int mRequestedPreDhcpActionMs;
+ public InitialConfiguration mInitialConfig;
+ public StaticIpConfiguration mStaticIpConfig;
+ public ApfCapabilities mApfCapabilities;
+ public int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
+ public int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
+ public Network mNetwork = null;
+ public String mDisplayName = null;
+
+ public ProvisioningConfiguration() {} // used by Builder
+
+ public ProvisioningConfiguration(ProvisioningConfiguration other) {
+ mEnableIPv4 = other.mEnableIPv4;
+ mEnableIPv6 = other.mEnableIPv6;
+ mUsingMultinetworkPolicyTracker = other.mUsingMultinetworkPolicyTracker;
+ mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
+ mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs;
+ mInitialConfig = InitialConfiguration.copy(other.mInitialConfig);
+ mStaticIpConfig = other.mStaticIpConfig == null
+ ? null
+ : new StaticIpConfiguration(other.mStaticIpConfig);
+ mApfCapabilities = other.mApfCapabilities;
+ mProvisioningTimeoutMs = other.mProvisioningTimeoutMs;
+ mIPv6AddrGenMode = other.mIPv6AddrGenMode;
+ mNetwork = other.mNetwork;
+ mDisplayName = other.mDisplayName;
+ }
+
+ /**
+ * Create a ProvisioningConfigurationParcelable from this ProvisioningConfiguration.
+ */
+ public ProvisioningConfigurationParcelable toStableParcelable() {
+ final ProvisioningConfigurationParcelable p = new ProvisioningConfigurationParcelable();
+ p.enableIPv4 = mEnableIPv4;
+ p.enableIPv6 = mEnableIPv6;
+ p.usingMultinetworkPolicyTracker = mUsingMultinetworkPolicyTracker;
+ p.usingIpReachabilityMonitor = mUsingIpReachabilityMonitor;
+ p.requestedPreDhcpActionMs = mRequestedPreDhcpActionMs;
+ p.initialConfig = mInitialConfig == null ? null : mInitialConfig.toStableParcelable();
+ p.staticIpConfig = IpConfigurationParcelableUtil.toStableParcelable(mStaticIpConfig);
+ p.apfCapabilities = IpConfigurationParcelableUtil.toStableParcelable(mApfCapabilities);
+ p.provisioningTimeoutMs = mProvisioningTimeoutMs;
+ p.ipv6AddrGenMode = mIPv6AddrGenMode;
+ p.network = NetworkParcelableUtil.toStableParcelable(mNetwork);
+ p.displayName = mDisplayName;
+ return p;
+ }
+
+ /**
+ * Create a ProvisioningConfiguration from a ProvisioningConfigurationParcelable.
+ */
+ public static ProvisioningConfiguration fromStableParcelable(
+ @Nullable ProvisioningConfigurationParcelable p) {
+ if (p == null) return null;
+ final ProvisioningConfiguration config = new ProvisioningConfiguration();
+ config.mEnableIPv4 = p.enableIPv4;
+ config.mEnableIPv6 = p.enableIPv6;
+ config.mUsingMultinetworkPolicyTracker = p.usingMultinetworkPolicyTracker;
+ config.mUsingIpReachabilityMonitor = p.usingIpReachabilityMonitor;
+ config.mRequestedPreDhcpActionMs = p.requestedPreDhcpActionMs;
+ config.mInitialConfig = InitialConfiguration.fromStableParcelable(p.initialConfig);
+ config.mStaticIpConfig = IpConfigurationParcelableUtil.fromStableParcelable(
+ p.staticIpConfig);
+ config.mApfCapabilities = IpConfigurationParcelableUtil.fromStableParcelable(
+ p.apfCapabilities);
+ config.mProvisioningTimeoutMs = p.provisioningTimeoutMs;
+ config.mIPv6AddrGenMode = p.ipv6AddrGenMode;
+ config.mNetwork = NetworkParcelableUtil.fromStableParcelable(p.network);
+ config.mDisplayName = p.displayName;
+ return config;
+ }
+
+ @Override
+ public String toString() {
+ return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
+ .add("mEnableIPv4: " + mEnableIPv4)
+ .add("mEnableIPv6: " + mEnableIPv6)
+ .add("mUsingMultinetworkPolicyTracker: " + mUsingMultinetworkPolicyTracker)
+ .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
+ .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs)
+ .add("mInitialConfig: " + mInitialConfig)
+ .add("mStaticIpConfig: " + mStaticIpConfig)
+ .add("mApfCapabilities: " + mApfCapabilities)
+ .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs)
+ .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode)
+ .add("mNetwork: " + mNetwork)
+ .add("mDisplayName: " + mDisplayName)
+ .toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ProvisioningConfiguration)) return false;
+ final ProvisioningConfiguration other = (ProvisioningConfiguration) obj;
+ return mEnableIPv4 == other.mEnableIPv4
+ && mEnableIPv6 == other.mEnableIPv6
+ && mUsingMultinetworkPolicyTracker == other.mUsingMultinetworkPolicyTracker
+ && mUsingIpReachabilityMonitor == other.mUsingIpReachabilityMonitor
+ && mRequestedPreDhcpActionMs == other.mRequestedPreDhcpActionMs
+ && Objects.equals(mInitialConfig, other.mInitialConfig)
+ && Objects.equals(mStaticIpConfig, other.mStaticIpConfig)
+ && Objects.equals(mApfCapabilities, other.mApfCapabilities)
+ && mProvisioningTimeoutMs == other.mProvisioningTimeoutMs
+ && mIPv6AddrGenMode == other.mIPv6AddrGenMode
+ && Objects.equals(mNetwork, other.mNetwork)
+ && Objects.equals(mDisplayName, other.mDisplayName);
+ }
+
+ public boolean isValid() {
+ return (mInitialConfig == null) || mInitialConfig.isValid();
+ }
+}
diff --git a/services/net/java/android/net/util/ConnectivityPacketSummary.java b/services/net/java/android/net/util/ConnectivityPacketSummary.java
deleted file mode 100644
index 4951400..0000000
--- a/services/net/java/android/net/util/ConnectivityPacketSummary.java
+++ /dev/null
@@ -1,378 +0,0 @@
-/*
- * Copyright (C) 2016 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.util;
-
-import android.net.dhcp.DhcpPacket;
-import android.net.MacAddress;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.Arrays;
-import java.util.StringJoiner;
-
-import static android.system.OsConstants.*;
-import static android.net.util.NetworkConstants.*;
-
-
-/**
- * Critical connectivity packet summarizing class.
- *
- * Outputs short descriptions of ARP, DHCPv4, and IPv6 RS/RA/NS/NA packets.
- *
- * @hide
- */
-public class ConnectivityPacketSummary {
- private static final String TAG = ConnectivityPacketSummary.class.getSimpleName();
-
- private final byte[] mHwAddr;
- private final byte[] mBytes;
- private final int mLength;
- private final ByteBuffer mPacket;
- private final String mSummary;
-
- public static String summarize(MacAddress hwaddr, byte[] buffer) {
- return summarize(hwaddr, buffer, buffer.length);
- }
-
- // Methods called herein perform some but by no means all error checking.
- // They may throw runtime exceptions on malformed packets.
- public static String summarize(MacAddress macAddr, byte[] buffer, int length) {
- if ((macAddr == null) || (buffer == null)) return null;
- length = Math.min(length, buffer.length);
- return (new ConnectivityPacketSummary(macAddr, buffer, length)).toString();
- }
-
- private ConnectivityPacketSummary(MacAddress macAddr, byte[] buffer, int length) {
- mHwAddr = macAddr.toByteArray();
- mBytes = buffer;
- mLength = Math.min(length, mBytes.length);
- mPacket = ByteBuffer.wrap(mBytes, 0, mLength);
- mPacket.order(ByteOrder.BIG_ENDIAN);
-
- final StringJoiner sj = new StringJoiner(" ");
- // TODO: support other link-layers, or even no link-layer header.
- parseEther(sj);
- mSummary = sj.toString();
- }
-
- public String toString() {
- return mSummary;
- }
-
- private void parseEther(StringJoiner sj) {
- if (mPacket.remaining() < ETHER_HEADER_LEN) {
- sj.add("runt:").add(asString(mPacket.remaining()));
- return;
- }
-
- mPacket.position(ETHER_SRC_ADDR_OFFSET);
- final ByteBuffer srcMac = (ByteBuffer) mPacket.slice().limit(ETHER_ADDR_LEN);
- sj.add(ByteBuffer.wrap(mHwAddr).equals(srcMac) ? "TX" : "RX");
- sj.add(getMacAddressString(srcMac));
-
- mPacket.position(ETHER_DST_ADDR_OFFSET);
- final ByteBuffer dstMac = (ByteBuffer) mPacket.slice().limit(ETHER_ADDR_LEN);
- sj.add(">").add(getMacAddressString(dstMac));
-
- mPacket.position(ETHER_TYPE_OFFSET);
- final int etherType = asUint(mPacket.getShort());
- switch (etherType) {
- case ETHER_TYPE_ARP:
- sj.add("arp");
- parseARP(sj);
- break;
- case ETHER_TYPE_IPV4:
- sj.add("ipv4");
- parseIPv4(sj);
- break;
- case ETHER_TYPE_IPV6:
- sj.add("ipv6");
- parseIPv6(sj);
- break;
- default:
- // Unknown ether type.
- sj.add("ethtype").add(asString(etherType));
- break;
- }
- }
-
- private void parseARP(StringJoiner sj) {
- if (mPacket.remaining() < ARP_PAYLOAD_LEN) {
- sj.add("runt:").add(asString(mPacket.remaining()));
- return;
- }
-
- if (asUint(mPacket.getShort()) != ARP_HWTYPE_ETHER ||
- asUint(mPacket.getShort()) != ETHER_TYPE_IPV4 ||
- asUint(mPacket.get()) != ETHER_ADDR_LEN ||
- asUint(mPacket.get()) != IPV4_ADDR_LEN) {
- sj.add("unexpected header");
- return;
- }
-
- final int opCode = asUint(mPacket.getShort());
-
- final String senderHwAddr = getMacAddressString(mPacket);
- final String senderIPv4 = getIPv4AddressString(mPacket);
- getMacAddressString(mPacket); // target hardware address, unused
- final String targetIPv4 = getIPv4AddressString(mPacket);
-
- if (opCode == ARP_REQUEST) {
- sj.add("who-has").add(targetIPv4);
- } else if (opCode == ARP_REPLY) {
- sj.add("reply").add(senderIPv4).add(senderHwAddr);
- } else {
- sj.add("unknown opcode").add(asString(opCode));
- }
- }
-
- private void parseIPv4(StringJoiner sj) {
- if (!mPacket.hasRemaining()) {
- sj.add("runt");
- return;
- }
-
- final int startOfIpLayer = mPacket.position();
- final int ipv4HeaderLength = (mPacket.get(startOfIpLayer) & IPV4_IHL_MASK) * 4;
- if (mPacket.remaining() < ipv4HeaderLength ||
- mPacket.remaining() < IPV4_HEADER_MIN_LEN) {
- sj.add("runt:").add(asString(mPacket.remaining()));
- return;
- }
- final int startOfTransportLayer = startOfIpLayer + ipv4HeaderLength;
-
- mPacket.position(startOfIpLayer + IPV4_FLAGS_OFFSET);
- final int flagsAndFragment = asUint(mPacket.getShort());
- final boolean isFragment = (flagsAndFragment & IPV4_FRAGMENT_MASK) != 0;
-
- mPacket.position(startOfIpLayer + IPV4_PROTOCOL_OFFSET);
- final int protocol = asUint(mPacket.get());
-
- mPacket.position(startOfIpLayer + IPV4_SRC_ADDR_OFFSET);
- final String srcAddr = getIPv4AddressString(mPacket);
-
- mPacket.position(startOfIpLayer + IPV4_DST_ADDR_OFFSET);
- final String dstAddr = getIPv4AddressString(mPacket);
-
- sj.add(srcAddr).add(">").add(dstAddr);
-
- mPacket.position(startOfTransportLayer);
- if (protocol == IPPROTO_UDP) {
- sj.add("udp");
- if (isFragment) sj.add("fragment");
- else parseUDP(sj);
- } else {
- sj.add("proto").add(asString(protocol));
- if (isFragment) sj.add("fragment");
- }
- }
-
- private void parseIPv6(StringJoiner sj) {
- if (mPacket.remaining() < IPV6_HEADER_LEN) {
- sj.add("runt:").add(asString(mPacket.remaining()));
- return;
- }
-
- final int startOfIpLayer = mPacket.position();
-
- mPacket.position(startOfIpLayer + IPV6_PROTOCOL_OFFSET);
- final int protocol = asUint(mPacket.get());
-
- mPacket.position(startOfIpLayer + IPV6_SRC_ADDR_OFFSET);
- final String srcAddr = getIPv6AddressString(mPacket);
- final String dstAddr = getIPv6AddressString(mPacket);
-
- sj.add(srcAddr).add(">").add(dstAddr);
-
- mPacket.position(startOfIpLayer + IPV6_HEADER_LEN);
- if (protocol == IPPROTO_ICMPV6) {
- sj.add("icmp6");
- parseICMPv6(sj);
- } else {
- sj.add("proto").add(asString(protocol));
- }
- }
-
- private void parseICMPv6(StringJoiner sj) {
- if (mPacket.remaining() < ICMPV6_HEADER_MIN_LEN) {
- sj.add("runt:").add(asString(mPacket.remaining()));
- return;
- }
-
- final int icmp6Type = asUint(mPacket.get());
- final int icmp6Code = asUint(mPacket.get());
- mPacket.getShort(); // checksum, unused
-
- switch (icmp6Type) {
- case ICMPV6_ROUTER_SOLICITATION:
- sj.add("rs");
- parseICMPv6RouterSolicitation(sj);
- break;
- case ICMPV6_ROUTER_ADVERTISEMENT:
- sj.add("ra");
- parseICMPv6RouterAdvertisement(sj);
- break;
- case ICMPV6_NEIGHBOR_SOLICITATION:
- sj.add("ns");
- parseICMPv6NeighborMessage(sj);
- break;
- case ICMPV6_NEIGHBOR_ADVERTISEMENT:
- sj.add("na");
- parseICMPv6NeighborMessage(sj);
- break;
- default:
- sj.add("type").add(asString(icmp6Type));
- sj.add("code").add(asString(icmp6Code));
- break;
- }
- }
-
- private void parseICMPv6RouterSolicitation(StringJoiner sj) {
- final int RESERVED = 4;
- if (mPacket.remaining() < RESERVED) {
- sj.add("runt:").add(asString(mPacket.remaining()));
- return;
- }
-
- mPacket.position(mPacket.position() + RESERVED);
- parseICMPv6NeighborDiscoveryOptions(sj);
- }
-
- private void parseICMPv6RouterAdvertisement(StringJoiner sj) {
- final int FLAGS_AND_TIMERS = 3 * 4;
- if (mPacket.remaining() < FLAGS_AND_TIMERS) {
- sj.add("runt:").add(asString(mPacket.remaining()));
- return;
- }
-
- mPacket.position(mPacket.position() + FLAGS_AND_TIMERS);
- parseICMPv6NeighborDiscoveryOptions(sj);
- }
-
- private void parseICMPv6NeighborMessage(StringJoiner sj) {
- final int RESERVED = 4;
- final int minReq = RESERVED + IPV6_ADDR_LEN;
- if (mPacket.remaining() < minReq) {
- sj.add("runt:").add(asString(mPacket.remaining()));
- return;
- }
-
- mPacket.position(mPacket.position() + RESERVED);
- sj.add(getIPv6AddressString(mPacket));
- parseICMPv6NeighborDiscoveryOptions(sj);
- }
-
- private void parseICMPv6NeighborDiscoveryOptions(StringJoiner sj) {
- // All ND options are TLV, where T is one byte and L is one byte equal
- // to the length of T + L + V in units of 8 octets.
- while (mPacket.remaining() >= ICMPV6_ND_OPTION_MIN_LENGTH) {
- final int ndType = asUint(mPacket.get());
- final int ndLength = asUint(mPacket.get());
- final int ndBytes = ndLength * ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR - 2;
- if (ndBytes < 0 || ndBytes > mPacket.remaining()) {
- sj.add("<malformed>");
- break;
- }
- final int position = mPacket.position();
-
- switch (ndType) {
- case ICMPV6_ND_OPTION_SLLA:
- sj.add("slla");
- sj.add(getMacAddressString(mPacket));
- break;
- case ICMPV6_ND_OPTION_TLLA:
- sj.add("tlla");
- sj.add(getMacAddressString(mPacket));
- break;
- case ICMPV6_ND_OPTION_MTU:
- sj.add("mtu");
- final short reserved = mPacket.getShort();
- sj.add(asString(mPacket.getInt()));
- break;
- default:
- // Skip.
- break;
- }
-
- mPacket.position(position + ndBytes);
- }
- }
-
- private void parseUDP(StringJoiner sj) {
- if (mPacket.remaining() < UDP_HEADER_LEN) {
- sj.add("runt:").add(asString(mPacket.remaining()));
- return;
- }
-
- final int previous = mPacket.position();
- final int srcPort = asUint(mPacket.getShort());
- final int dstPort = asUint(mPacket.getShort());
- sj.add(asString(srcPort)).add(">").add(asString(dstPort));
-
- mPacket.position(previous + UDP_HEADER_LEN);
- if (srcPort == DHCP4_CLIENT_PORT || dstPort == DHCP4_CLIENT_PORT) {
- sj.add("dhcp4");
- parseDHCPv4(sj);
- }
- }
-
- private void parseDHCPv4(StringJoiner sj) {
- final DhcpPacket dhcpPacket;
- try {
- dhcpPacket = DhcpPacket.decodeFullPacket(mBytes, mLength, DhcpPacket.ENCAP_L2);
- sj.add(dhcpPacket.toString());
- } catch (DhcpPacket.ParseException e) {
- sj.add("parse error: " + e);
- }
- }
-
- private static String getIPv4AddressString(ByteBuffer ipv4) {
- return getIpAddressString(ipv4, IPV4_ADDR_LEN);
- }
-
- private static String getIPv6AddressString(ByteBuffer ipv6) {
- return getIpAddressString(ipv6, IPV6_ADDR_LEN);
- }
-
- private static String getIpAddressString(ByteBuffer ip, int byteLength) {
- if (ip == null || ip.remaining() < byteLength) return "invalid";
-
- byte[] bytes = new byte[byteLength];
- ip.get(bytes, 0, byteLength);
- try {
- InetAddress addr = InetAddress.getByAddress(bytes);
- return addr.getHostAddress();
- } catch (UnknownHostException uhe) {
- return "unknown";
- }
- }
-
- private static String getMacAddressString(ByteBuffer mac) {
- if (mac == null || mac.remaining() < ETHER_ADDR_LEN) return "invalid";
-
- byte[] bytes = new byte[ETHER_ADDR_LEN];
- mac.get(bytes, 0, bytes.length);
- Object[] printableBytes = new Object[bytes.length];
- int i = 0;
- for (byte b : bytes) printableBytes[i++] = new Byte(b);
-
- final String MAC48_FORMAT = "%02x:%02x:%02x:%02x:%02x:%02x";
- return String.format(MAC48_FORMAT, printableBytes);
- }
-}
diff --git a/services/net/java/android/net/util/InterfaceParams.java b/services/net/java/android/net/util/InterfaceParams.java
index a4b2fbb..f6bb873 100644
--- a/services/net/java/android/net/util/InterfaceParams.java
+++ b/services/net/java/android/net/util/InterfaceParams.java
@@ -16,9 +16,6 @@
package android.net.util;
-import static android.net.MacAddress.ALL_ZEROS_ADDRESS;
-import static android.net.util.NetworkConstants.ETHER_MTU;
-import static android.net.util.NetworkConstants.IPV6_MIN_MTU;
import static com.android.internal.util.Preconditions.checkArgument;
import android.net.MacAddress;
@@ -44,6 +41,11 @@
public final MacAddress macAddr;
public final int defaultMtu;
+ // TODO: move the below to NetworkStackConstants when this class is moved to the NetworkStack.
+ private static final int ETHER_MTU = 1500;
+ private static final int IPV6_MIN_MTU = 1280;
+
+
public static InterfaceParams getByName(String name) {
final NetworkInterface netif = getNetworkInterfaceByName(name);
if (netif == null) return null;
@@ -67,7 +69,8 @@
checkArgument((index > 0), "invalid interface index");
this.name = name;
this.index = index;
- this.macAddr = (macAddr != null) ? macAddr : ALL_ZEROS_ADDRESS;
+ this.macAddr = (macAddr != null) ? macAddr : MacAddress.fromBytes(new byte[] {
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 });
this.defaultMtu = (defaultMtu > IPV6_MIN_MTU) ? defaultMtu : IPV6_MIN_MTU;
}
diff --git a/services/net/java/android/net/util/NetdService.java b/services/net/java/android/net/util/NetdService.java
deleted file mode 100644
index 6e69ff5..0000000
--- a/services/net/java/android/net/util/NetdService.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2016 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.util;
-
-import android.net.INetd;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.ServiceSpecificException;
-import android.os.SystemClock;
-import android.util.Log;
-
-
-/**
- * @hide
- */
-public class NetdService {
- private static final String TAG = NetdService.class.getSimpleName();
- private static final String NETD_SERVICE_NAME = "netd";
- private static final long BASE_TIMEOUT_MS = 100;
- private static final long MAX_TIMEOUT_MS = 1000;
-
-
- /**
- * Return an INetd instance, or null if not available.
- *
- * It is the caller's responsibility to check for a null return value
- * and to handle RemoteException errors from invocations on the returned
- * interface if, for example, netd dies and is restarted.
- *
- * Returned instances of INetd should not be cached.
- *
- * @return an INetd instance or null.
- */
- public static INetd getInstance() {
- // NOTE: ServiceManager does no caching for the netd service,
- // because netd is not one of the defined common services.
- final INetd netdInstance = INetd.Stub.asInterface(
- ServiceManager.getService(NETD_SERVICE_NAME));
- if (netdInstance == null) {
- Log.w(TAG, "WARNING: returning null INetd instance.");
- }
- return netdInstance;
- }
-
- /**
- * Blocks for a specified time until an INetd instance is available.
- *
- * It is the caller's responsibility to handle RemoteException errors
- * from invocations on the returned interface if, for example, netd
- * dies after this interface was returned.
- *
- * Returned instances of INetd should not be cached.
- *
- * Special values of maxTimeoutMs include: 0, meaning try to obtain an
- * INetd instance only once, and -1 (or any value less than 0), meaning
- * try to obtain an INetd instance indefinitely.
- *
- * @param maxTimeoutMs the maximum time to spend getting an INetd instance
- * @return an INetd instance or null if no instance is available
- * within |maxTimeoutMs| milliseconds.
- */
- public static INetd get(long maxTimeoutMs) {
- if (maxTimeoutMs == 0) return getInstance();
-
- final long stop = (maxTimeoutMs > 0)
- ? SystemClock.elapsedRealtime() + maxTimeoutMs
- : Long.MAX_VALUE;
-
- long timeoutMs = 0;
- while (true) {
- final INetd netdInstance = getInstance();
- if (netdInstance != null) {
- return netdInstance;
- }
-
- final long remaining = stop - SystemClock.elapsedRealtime();
- if (remaining <= 0) break;
-
- // No netdInstance was received; sleep and retry.
- timeoutMs = Math.min(timeoutMs + BASE_TIMEOUT_MS, MAX_TIMEOUT_MS);
- timeoutMs = Math.min(timeoutMs, remaining);
- try {
- Thread.sleep(timeoutMs);
- } catch (InterruptedException e) {}
- }
- return null;
- }
-
- /**
- * Blocks until an INetd instance is available.
- *
- * It is the caller's responsibility to handle RemoteException errors
- * from invocations on the returned interface if, for example, netd
- * dies after this interface was returned.
- *
- * Returned instances of INetd should not be cached.
- *
- * @return an INetd instance.
- */
- public static INetd get() {
- return get(-1);
- }
-
- public static interface NetdCommand {
- void run(INetd netd) throws RemoteException;
- }
-
- /**
- * Blocks until an INetd instance is availabe, and retries until either
- * the command succeeds or a runtime exception is thrown.
- */
- public static void run(NetdCommand cmd) {
- while (true) {
- try {
- cmd.run(get());
- return;
- } catch (RemoteException re) {
- Log.e(TAG, "error communicating with netd: " + re);
- }
- }
- }
-}
diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java
index c183b81..ea5ce65 100644
--- a/services/net/java/android/net/util/NetworkConstants.java
+++ b/services/net/java/android/net/util/NetworkConstants.java
@@ -28,28 +28,6 @@
public final class NetworkConstants {
private NetworkConstants() { throw new RuntimeException("no instance permitted"); }
- /**
- * Ethernet constants.
- *
- * See also:
- * - https://tools.ietf.org/html/rfc894
- * - https://tools.ietf.org/html/rfc2464
- * - https://tools.ietf.org/html/rfc7042
- * - http://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml
- * - http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml
- */
- public static final int ETHER_DST_ADDR_OFFSET = 0;
- public static final int ETHER_SRC_ADDR_OFFSET = 6;
- public static final int ETHER_ADDR_LEN = 6;
-
- public static final int ETHER_TYPE_OFFSET = 12;
- public static final int ETHER_TYPE_LENGTH = 2;
- public static final int ETHER_TYPE_ARP = 0x0806;
- public static final int ETHER_TYPE_IPV4 = 0x0800;
- public static final int ETHER_TYPE_IPV6 = 0x86dd;
-
- public static final int ETHER_HEADER_LEN = 14;
-
public static final byte FF = asByte(0xff);
public static final byte[] ETHER_ADDR_BROADCAST = {
FF, FF, FF, FF, FF, FF
@@ -58,34 +36,12 @@
public static final int ETHER_MTU = 1500;
/**
- * ARP constants.
- *
- * See also:
- * - https://tools.ietf.org/html/rfc826
- * - http://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml
- */
- public static final int ARP_PAYLOAD_LEN = 28; // For Ethernet+IPv4.
- public static final int ARP_REQUEST = 1;
- public static final int ARP_REPLY = 2;
- public static final int ARP_HWTYPE_RESERVED_LO = 0;
- public static final int ARP_HWTYPE_ETHER = 1;
- public static final int ARP_HWTYPE_RESERVED_HI = 0xffff;
-
- /**
* IPv4 constants.
*
* See also:
* - https://tools.ietf.org/html/rfc791
*/
- public static final int IPV4_HEADER_MIN_LEN = 20;
- public static final int IPV4_IHL_MASK = 0xf;
- public static final int IPV4_FLAGS_OFFSET = 6;
- public static final int IPV4_FRAGMENT_MASK = 0x1fff;
- public static final int IPV4_PROTOCOL_OFFSET = 9;
- public static final int IPV4_SRC_ADDR_OFFSET = 12;
- public static final int IPV4_DST_ADDR_OFFSET = 16;
public static final int IPV4_ADDR_BITS = 32;
- public static final int IPV4_ADDR_LEN = 4;
/**
* IPv6 constants.
@@ -93,15 +49,10 @@
* See also:
* - https://tools.ietf.org/html/rfc2460
*/
- public static final int IPV6_HEADER_LEN = 40;
- public static final int IPV6_PROTOCOL_OFFSET = 6;
- public static final int IPV6_SRC_ADDR_OFFSET = 8;
- public static final int IPV6_DST_ADDR_OFFSET = 24;
public static final int IPV6_ADDR_BITS = 128;
public static final int IPV6_ADDR_LEN = 16;
public static final int IPV6_MIN_MTU = 1280;
public static final int RFC7421_PREFIX_LENGTH = 64;
- public static final int RFC6177_MIN_PREFIX_LENGTH = 48;
/**
* ICMP common (v4/v6) constants.
@@ -124,45 +75,7 @@
* - https://tools.ietf.org/html/rfc792
*/
public static final int ICMPV4_ECHO_REQUEST_TYPE = 8;
-
- /**
- * ICMPv6 constants.
- *
- * See also:
- * - https://tools.ietf.org/html/rfc4443
- * - https://tools.ietf.org/html/rfc4861
- */
- public static final int ICMPV6_HEADER_MIN_LEN = 4;
public static final int ICMPV6_ECHO_REQUEST_TYPE = 128;
- public static final int ICMPV6_ECHO_REPLY_TYPE = 129;
- public static final int ICMPV6_ROUTER_SOLICITATION = 133;
- public static final int ICMPV6_ROUTER_ADVERTISEMENT = 134;
- public static final int ICMPV6_NEIGHBOR_SOLICITATION = 135;
- public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136;
-
- public static final int ICMPV6_ND_OPTION_MIN_LENGTH = 8;
- public static final int ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR = 8;
- public static final int ICMPV6_ND_OPTION_SLLA = 1;
- public static final int ICMPV6_ND_OPTION_TLLA = 2;
- public static final int ICMPV6_ND_OPTION_MTU = 5;
-
-
- /**
- * UDP constants.
- *
- * See also:
- * - https://tools.ietf.org/html/rfc768
- */
- public static final int UDP_HEADER_LEN = 8;
-
- /**
- * DHCP(v4) constants.
- *
- * See also:
- * - https://tools.ietf.org/html/rfc2131
- */
- public static final int DHCP4_SERVER_PORT = 67;
- public static final int DHCP4_CLIENT_PORT = 68;
/**
* DNS constants.
@@ -176,9 +89,4 @@
* Utility functions.
*/
public static byte asByte(int i) { return (byte) i; }
-
- public static String asString(int i) { return Integer.toString(i); }
-
- public static int asUint(byte b) { return (b & 0xff); }
- public static int asUint(short s) { return (s & 0xffff); }
}
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index 4811523..164570a 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -116,6 +116,7 @@
import com.android.server.backup.testing.TransportTestUtils.TransportMock;
import com.android.server.testing.shadows.FrameworkShadowLooper;
import com.android.server.testing.shadows.ShadowApplicationPackageManager;
+import com.android.server.testing.shadows.ShadowBackupActivityThread;
import com.android.server.testing.shadows.ShadowBackupDataInput;
import com.android.server.testing.shadows.ShadowBackupDataOutput;
import com.android.server.testing.shadows.ShadowEventLog;
@@ -163,6 +164,7 @@
ShadowBackupDataOutput.class,
ShadowEventLog.class,
ShadowQueuedWork.class,
+ ShadowBackupActivityThread.class,
})
@Presubmit
public class KeyValueBackupTaskTest {
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java b/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java
index aefc871..33b8aa7 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java
@@ -62,7 +62,7 @@
}
@Implementation
- protected static boolean appIsEligibleForBackup(ApplicationInfo app, PackageManager pm) {
+ protected static boolean appIsEligibleForBackup(ApplicationInfo app, int userId) {
return sAppsEligibleForBackup.contains(app.packageName);
}
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowBackupActivityThread.java b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupActivityThread.java
new file mode 100644
index 0000000..ca2e3b6
--- /dev/null
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupActivityThread.java
@@ -0,0 +1,79 @@
+/*
+ * 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 com.android.server.testing.shadows;
+
+import android.app.ActivityThread;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowActivityThread;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Extends the existing {@link ShadowActivityThread} to add support for
+ * {@link PackageManager#getApplicationEnabledSetting(String)} in the shadow {@link PackageManager}
+ * returned by {@link ShadowBackupActivityThread#getPackageManager()}.
+ */
+@Implements(value = ActivityThread.class, isInAndroidSdk = false, looseSignatures = true)
+public class ShadowBackupActivityThread extends ShadowActivityThread {
+ @Implementation
+ public static Object getPackageManager() {
+ ClassLoader classLoader = ShadowActivityThread.class.getClassLoader();
+ Class<?> iPackageManagerClass;
+ try {
+ iPackageManagerClass = classLoader.loadClass("android.content.pm.IPackageManager");
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+
+ return Proxy.newProxyInstance(
+ classLoader,
+ new Class[] {iPackageManagerClass},
+ new InvocationHandler() {
+ @Override
+ public Object invoke(Object proxy, @Nonnull Method method, Object[] args)
+ throws Exception {
+ if (method.getName().equals("getApplicationInfo")) {
+ String packageName = (String) args[0];
+ int flags = (Integer) args[1];
+
+ try {
+ return RuntimeEnvironment.application
+ .getPackageManager()
+ .getApplicationInfo(packageName, flags);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RemoteException(e.getMessage());
+ }
+ } else if (method.getName().equals("getApplicationEnabledSetting")) {
+ return 0;
+ } else {
+ return null;
+ }
+ }
+ });
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index 1a16e56..53d72bb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -1376,6 +1376,45 @@
verifyLightStateConditions(LIGHT_STATE_ACTIVE);
}
+ @Test
+ public void testStepToIdleMode() {
+ float delta = mDeviceIdleController.MIN_PRE_IDLE_FACTOR_CHANGE;
+ for (int mode = PowerManager.PRE_IDLE_TIMEOUT_MODE_NORMAL;
+ mode <= PowerManager.PRE_IDLE_TIMEOUT_MODE_LONG;
+ mode++) {
+ int ret = mDeviceIdleController.setPreIdleTimeoutMode(mode);
+ if (mode == PowerManager.PRE_IDLE_TIMEOUT_MODE_NORMAL) {
+ assertEquals("setPreIdleTimeoutMode: " + mode + " failed.",
+ mDeviceIdleController.SET_IDLE_FACTOR_RESULT_IGNORED, ret);
+ } else {
+ assertEquals("setPreIdleTimeoutMode: " + mode + " failed.",
+ mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK, ret);
+ }
+ //TODO(b/123045185): Mocked Handler of DeviceIdleController to make message loop
+ //workable in this test class
+ mDeviceIdleController.updatePreIdleFactor();
+ float expectedfactor = mDeviceIdleController.getPreIdleTimeoutByMode(mode);
+ float curfactor = mDeviceIdleController.getPreIdleTimeoutFactor();
+ assertEquals("Pre idle time factor of mode [" + mode + "].",
+ expectedfactor, curfactor, delta);
+ mDeviceIdleController.resetPreIdleTimeoutMode();
+ mDeviceIdleController.updatePreIdleFactor();
+
+ checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_INACTIVE);
+ checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_IDLE_PENDING);
+
+ checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_SENSING);
+ checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_LOCATING);
+ checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_QUICK_DOZE_DELAY);
+ checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_IDLE_MAINTENANCE);
+ checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_IDLE);
+ checkMaybeDoAnImmediateMaintenance(expectedfactor);
+ }
+ float curfactor = mDeviceIdleController.getPreIdleTimeoutFactor();
+ assertEquals("Pre idle time factor of mode default.",
+ 1.0f, curfactor, delta);
+ }
+
private void enterDeepState(int state) {
switch (state) {
case STATE_ACTIVE:
@@ -1599,4 +1638,84 @@
fail("Conditions for " + lightStateToString(expectedLightState) + " unknown.");
}
}
+
+ private void checkNextAlarmTimeWithNewPreIdleFactor(float factor, int state) {
+ final long errorTolerance = 1000;
+ enterDeepState(state);
+ long now = SystemClock.elapsedRealtime();
+ long alarm = mDeviceIdleController.getNextAlarmTime();
+ if (state == STATE_INACTIVE || state == STATE_IDLE_PENDING) {
+ int ret = mDeviceIdleController.setPreIdleTimeoutFactor(factor);
+ if (Float.compare(factor, 1.0f) == 0) {
+ assertEquals("setPreIdleTimeoutMode: " + factor + " failed.",
+ mDeviceIdleController.SET_IDLE_FACTOR_RESULT_IGNORED, ret);
+ } else {
+ assertEquals("setPreIdleTimeoutMode: " + factor + " failed.",
+ mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK, ret);
+ }
+ if (ret == mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK) {
+ mDeviceIdleController.updatePreIdleFactor();
+ long newAlarm = mDeviceIdleController.getNextAlarmTime();
+ long newDelay = (long) ((alarm - now) * factor);
+ assertTrue("setPreIdleTimeoutFactor: " + factor,
+ Math.abs(newDelay - (newAlarm - now)) < errorTolerance);
+ mDeviceIdleController.resetPreIdleTimeoutMode();
+ mDeviceIdleController.updatePreIdleFactor();
+ mDeviceIdleController.maybeDoImmediateMaintenance();
+ newAlarm = mDeviceIdleController.getNextAlarmTime();
+ assertTrue("resetPreIdleTimeoutMode from: " + factor,
+ Math.abs(newAlarm - alarm) < errorTolerance);
+ mDeviceIdleController.setPreIdleTimeoutFactor(factor);
+ now = SystemClock.elapsedRealtime();
+ enterDeepState(state);
+ newAlarm = mDeviceIdleController.getNextAlarmTime();
+ assertTrue("setPreIdleTimeoutFactor: " + factor + " before step to idle",
+ Math.abs(newDelay - (newAlarm - now)) < errorTolerance);
+ mDeviceIdleController.resetPreIdleTimeoutMode();
+ mDeviceIdleController.updatePreIdleFactor();
+ mDeviceIdleController.maybeDoImmediateMaintenance();
+ }
+ } else {
+ mDeviceIdleController.setPreIdleTimeoutFactor(factor);
+ mDeviceIdleController.updatePreIdleFactor();
+ long newAlarm = mDeviceIdleController.getNextAlarmTime();
+ assertTrue("setPreIdleTimeoutFactor: " + factor
+ + " shounld not change next alarm" ,
+ (newAlarm == alarm));
+ mDeviceIdleController.resetPreIdleTimeoutMode();
+ mDeviceIdleController.updatePreIdleFactor();
+ mDeviceIdleController.maybeDoImmediateMaintenance();
+ }
+ }
+
+ private void checkMaybeDoAnImmediateMaintenance(float factor) {
+ int ret = mDeviceIdleController.setPreIdleTimeoutFactor(factor);
+ final long minuteInMillis = 60 * 1000;
+ if (Float.compare(factor, 1.0f) == 0) {
+ assertEquals("setPreIdleTimeoutMode: " + factor + " failed.",
+ mDeviceIdleController.SET_IDLE_FACTOR_RESULT_IGNORED, ret);
+ } else {
+ assertEquals("setPreIdleTimeoutMode: " + factor + " failed.",
+ mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK, ret);
+ }
+ if (ret == mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK) {
+ enterDeepState(STATE_IDLE);
+ long now = SystemClock.elapsedRealtime();
+ long alarm = mDeviceIdleController.getNextAlarmTime();
+ mDeviceIdleController.setIdleStartTimeForTest(
+ now - (long) (mConstants.IDLE_TIMEOUT * 0.6));
+ mDeviceIdleController.maybeDoImmediateMaintenance();
+ long newAlarm = mDeviceIdleController.getNextAlarmTime();
+ assertTrue("maintenance not reschedule IDLE_TIMEOUT * 0.6",
+ newAlarm == alarm);
+ mDeviceIdleController.setIdleStartTimeForTest(
+ now - (long) (mConstants.IDLE_TIMEOUT * 1.2));
+ mDeviceIdleController.maybeDoImmediateMaintenance();
+ newAlarm = mDeviceIdleController.getNextAlarmTime();
+ assertTrue("maintenance not reschedule IDLE_TIMEOUT * 1.2",
+ (newAlarm - now) < minuteInMillis);
+ mDeviceIdleController.resetPreIdleTimeoutMode();
+ mDeviceIdleController.updatePreIdleFactor();
+ }
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 57ee6dc..cad71a2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -25,6 +25,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
+import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
import static com.android.server.job.JobSchedulerService.RARE_INDEX;
import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
@@ -370,16 +371,19 @@
mQuotaController.saveTimingSession(0, "com.android.test.stay", one);
ExecutionStats expectedStats = new ExecutionStats();
- expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
- mQuotaController.onAppRemovedLocked("com.android.test.remove", 10001);
+ final int uid = 10001;
+ mQuotaController.onAppRemovedLocked("com.android.test.remove", uid);
assertNull(mQuotaController.getTimingSessions(0, "com.android.test.remove"));
assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test.stay"));
assertEquals(expectedStats,
mQuotaController.getExecutionStatsLocked(0, "com.android.test.remove", RARE_INDEX));
assertNotEquals(expectedStats,
mQuotaController.getExecutionStatsLocked(0, "com.android.test.stay", RARE_INDEX));
+
+ assertFalse(mQuotaController.getForegroundUids().get(uid));
}
@Test
@@ -405,7 +409,7 @@
mQuotaController.saveTimingSession(10, "com.android.test", one);
ExecutionStats expectedStats = new ExecutionStats();
- expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
mQuotaController.onUserRemovedLocked(0);
@@ -440,14 +444,14 @@
inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS;
// Invalid time is now +24 hours since there are no sessions at all for the app.
- expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats);
assertEquals(expectedStats, inputStats);
inputStats.windowSizeMs = expectedStats.windowSizeMs = MINUTE_IN_MILLIS;
// Invalid time is now +18 hours since there are no sessions in the window but the earliest
// session is 6 hours ago.
- expectedStats.invalidTimeElapsed = now + 18 * HOUR_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + 18 * HOUR_IN_MILLIS;
expectedStats.executionTimeInWindowMs = 0;
expectedStats.bgJobCountInWindow = 0;
expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -457,7 +461,7 @@
inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS;
// Invalid time is now since the session straddles the window cutoff time.
- expectedStats.invalidTimeElapsed = now;
+ expectedStats.expirationTimeElapsed = now;
expectedStats.executionTimeInWindowMs = 2 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 3;
expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -468,7 +472,7 @@
inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * MINUTE_IN_MILLIS;
// Invalid time is now since the start of the session is at the very edge of the window
// cutoff time.
- expectedStats.invalidTimeElapsed = now;
+ expectedStats.expirationTimeElapsed = now;
expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 3;
expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -479,7 +483,7 @@
inputStats.windowSizeMs = expectedStats.windowSizeMs = 49 * MINUTE_IN_MILLIS;
// Invalid time is now +44 minutes since the earliest session in the window is now-5
// minutes.
- expectedStats.invalidTimeElapsed = now + 44 * MINUTE_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + 44 * MINUTE_IN_MILLIS;
expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 3;
expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -489,7 +493,7 @@
inputStats.windowSizeMs = expectedStats.windowSizeMs = 50 * MINUTE_IN_MILLIS;
// Invalid time is now since the session is at the very edge of the window cutoff time.
- expectedStats.invalidTimeElapsed = now;
+ expectedStats.expirationTimeElapsed = now;
expectedStats.executionTimeInWindowMs = 5 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 4;
expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -500,7 +504,7 @@
inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS;
// Invalid time is now since the start of the session is at the very edge of the window
// cutoff time.
- expectedStats.invalidTimeElapsed = now;
+ expectedStats.expirationTimeElapsed = now;
expectedStats.executionTimeInWindowMs = 6 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 5;
expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -510,7 +514,7 @@
inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
// Invalid time is now since the session straddles the window cutoff time.
- expectedStats.invalidTimeElapsed = now;
+ expectedStats.expirationTimeElapsed = now;
expectedStats.executionTimeInWindowMs = 11 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 10;
expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -523,7 +527,7 @@
inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * HOUR_IN_MILLIS;
// Invalid time is now +59 minutes since the earliest session in the window is now-121
// minutes.
- expectedStats.invalidTimeElapsed = now + 59 * MINUTE_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + 59 * MINUTE_IN_MILLIS;
expectedStats.executionTimeInWindowMs = 12 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 10;
expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -536,7 +540,7 @@
inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS;
// Invalid time is now since the start of the session is at the very edge of the window
// cutoff time.
- expectedStats.invalidTimeElapsed = now;
+ expectedStats.expirationTimeElapsed = now;
expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 15;
expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -546,14 +550,14 @@
mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
assertEquals(expectedStats, inputStats);
- // Make sure invalidTimeElapsed is set correctly when it's dependent on the max period.
+ // Make sure expirationTimeElapsed is set correctly when it's dependent on the max period.
mQuotaController.getTimingSessions(0, "com.android.test")
.add(0,
createTimingSession(now - (23 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 3));
inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
// Invalid time is now +1 hour since the earliest session in the max period is 1 hour
// before the end of the max period cutoff time.
- expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 15;
expectedStats.executionTimeInMaxPeriodMs = 23 * MINUTE_IN_MILLIS;
@@ -569,7 +573,7 @@
2 * MINUTE_IN_MILLIS, 2));
inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
// Invalid time is now since the earlist session straddles the max period cutoff time.
- expectedStats.invalidTimeElapsed = now;
+ expectedStats.expirationTimeElapsed = now;
expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 15;
expectedStats.executionTimeInMaxPeriodMs = 24 * MINUTE_IN_MILLIS;
@@ -599,7 +603,7 @@
// Active
expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
- expectedStats.invalidTimeElapsed = now + 4 * MINUTE_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + 4 * MINUTE_IN_MILLIS;
expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 5;
expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
@@ -609,7 +613,7 @@
// Working
expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
- expectedStats.invalidTimeElapsed = now;
+ expectedStats.expirationTimeElapsed = now;
expectedStats.executionTimeInWindowMs = 13 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 10;
expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
@@ -621,7 +625,7 @@
// Frequent
expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
- expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
expectedStats.executionTimeInWindowMs = 23 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 15;
expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
@@ -633,7 +637,7 @@
// Rare
expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
- expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
expectedStats.executionTimeInWindowMs = 33 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 20;
expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
@@ -675,7 +679,7 @@
ExecutionStats expectedStats = new ExecutionStats();
expectedStats.windowSizeMs = originalStatsActive.windowSizeMs;
- expectedStats.invalidTimeElapsed = originalStatsActive.invalidTimeElapsed;
+ expectedStats.expirationTimeElapsed = originalStatsActive.expirationTimeElapsed;
expectedStats.executionTimeInWindowMs = originalStatsActive.executionTimeInWindowMs;
expectedStats.bgJobCountInWindow = originalStatsActive.bgJobCountInWindow;
expectedStats.executionTimeInMaxPeriodMs = originalStatsActive.executionTimeInMaxPeriodMs;
@@ -688,7 +692,7 @@
assertEquals(expectedStats, newStatsActive);
expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs;
- expectedStats.invalidTimeElapsed = originalStatsWorking.invalidTimeElapsed;
+ expectedStats.expirationTimeElapsed = originalStatsWorking.expirationTimeElapsed;
expectedStats.executionTimeInWindowMs = originalStatsWorking.executionTimeInWindowMs;
expectedStats.bgJobCountInWindow = originalStatsWorking.bgJobCountInWindow;
expectedStats.quotaCutoffTimeElapsed = originalStatsWorking.quotaCutoffTimeElapsed;
@@ -698,7 +702,7 @@
assertNotEquals(expectedStats, newStatsWorking);
expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs;
- expectedStats.invalidTimeElapsed = originalStatsFrequent.invalidTimeElapsed;
+ expectedStats.expirationTimeElapsed = originalStatsFrequent.expirationTimeElapsed;
expectedStats.executionTimeInWindowMs = originalStatsFrequent.executionTimeInWindowMs;
expectedStats.bgJobCountInWindow = originalStatsFrequent.bgJobCountInWindow;
expectedStats.quotaCutoffTimeElapsed = originalStatsFrequent.quotaCutoffTimeElapsed;
@@ -708,7 +712,7 @@
assertNotEquals(expectedStats, newStatsFrequent);
expectedStats.windowSizeMs = originalStatsRare.windowSizeMs;
- expectedStats.invalidTimeElapsed = originalStatsRare.invalidTimeElapsed;
+ expectedStats.expirationTimeElapsed = originalStatsRare.expirationTimeElapsed;
expectedStats.executionTimeInWindowMs = originalStatsRare.executionTimeInWindowMs;
expectedStats.bgJobCountInWindow = originalStatsRare.bgJobCountInWindow;
expectedStats.quotaCutoffTimeElapsed = originalStatsRare.quotaCutoffTimeElapsed;
@@ -719,6 +723,77 @@
}
@Test
+ public void testIsWithinQuotaLocked_NeverApp() {
+ assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.never", NEVER_INDEX));
+ }
+
+ @Test
+ public void testIsWithinQuotaLocked_Charging() {
+ setCharging();
+ assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX));
+ }
+
+ @Test
+ public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount() {
+ setDischarging();
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
+ mQuotaController.incrementJobCount(0, "com.android.test", 5);
+ assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
+ }
+
+ @Test
+ public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() {
+ setDischarging();
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ final int jobCount = mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME;
+ mQuotaController.saveTimingSession(0, "com.android.test.spam",
+ createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25));
+ mQuotaController.saveTimingSession(0, "com.android.test.spam",
+ createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount));
+ mQuotaController.incrementJobCount(0, "com.android.test.spam", jobCount);
+ assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.spam",
+ WORKING_INDEX));
+
+ mQuotaController.saveTimingSession(0, "com.android.test.frequent",
+ createTimingSession(now - (2 * HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 2000));
+ mQuotaController.saveTimingSession(0, "com.android.test.frequent",
+ createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 500));
+ assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.frequent",
+ FREQUENT_INDEX));
+ }
+
+ @Test
+ public void testIsWithinQuotaLocked_OverDuration_UnderJobCount() {
+ setDischarging();
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5));
+ mQuotaController.incrementJobCount(0, "com.android.test", 5);
+ assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
+ }
+
+ @Test
+ public void testIsWithinQuotaLocked_OverDuration_OverJobCount() {
+ setDischarging();
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ final int jobCount = mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME;
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25));
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount));
+ mQuotaController.incrementJobCount(0, "com.android.test", jobCount);
+ assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
+ }
+
+ @Test
public void testMaybeScheduleCleanupAlarmLocked() {
// No sessions saved yet.
mQuotaController.maybeScheduleCleanupAlarmLocked();
@@ -752,6 +827,7 @@
// Active window size is 10 minutes.
final int standbyBucket = ACTIVE_INDEX;
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
// No sessions saved yet.
mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -1016,11 +1092,37 @@
.set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
- inOrder.verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
- any());
+ inOrder.verify(mAlarmManager, never())
+ .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
inOrder.verify(mAlarmManager, times(1)).cancel(any(AlarmManager.OnAlarmListener.class));
}
+ @Test
+ public void testMaybeScheduleStartAlarmLocked_JobCount_AllowedTime() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ final int standbyBucket = WORKING_INDEX;
+ ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
+ SOURCE_PACKAGE, standbyBucket);
+ stats.jobCountInAllowedTime =
+ mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME + 2;
+
+ // Invalid time in the past, so the count shouldn't be used.
+ stats.jobCountExpirationTimeElapsed =
+ now - mQuotaController.getAllowedTimePerPeriodMs() / 2;
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
+ verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ // Invalid time in the future, so the count should be used.
+ stats.jobCountExpirationTimeElapsed =
+ now + mQuotaController.getAllowedTimePerPeriodMs() / 2;
+ final long expectedWorkingAlarmTime =
+ stats.jobCountExpirationTimeElapsed + mQuotaController.getAllowedTimePerPeriodMs();
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
+ verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+ }
/**
* Tests that the start alarm is properly rescheduled if the earliest session that contributes
@@ -1172,6 +1274,11 @@
mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 45 * MINUTE_IN_MILLIS;
mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 60 * MINUTE_IN_MILLIS;
mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 3 * HOUR_IN_MILLIS;
+ mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = 5000;
+ mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = 4000;
+ mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = 3000;
+ mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = 2000;
+ mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 500;
mQuotaController.onConstantsUpdatedLocked();
@@ -1183,11 +1290,16 @@
mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
+ assertEquals(500, mQuotaController.getMaxJobCountPerAllowedTime());
+ assertEquals(5000, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
+ assertEquals(4000, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
+ assertEquals(3000, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
+ assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
}
@Test
public void testConstantsUpdating_InvalidValues() {
- // Test negatives
+ // Test negatives/too low.
mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = -MINUTE_IN_MILLIS;
mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = -MINUTE_IN_MILLIS;
mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = -MINUTE_IN_MILLIS;
@@ -1195,6 +1307,11 @@
mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = -MINUTE_IN_MILLIS;
mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = -MINUTE_IN_MILLIS;
mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = -MINUTE_IN_MILLIS;
+ mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = -1;
+ mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = 1;
+ mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = 1;
+ mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = 1;
+ mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 0;
mQuotaController.onConstantsUpdatedLocked();
@@ -1205,6 +1322,11 @@
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
+ assertEquals(10, mQuotaController.getMaxJobCountPerAllowedTime());
+ assertEquals(100, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
+ assertEquals(100, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
+ assertEquals(100, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
+ assertEquals(100, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
// Test larger than a day. Controller should cap at one day.
mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = 25 * HOUR_IN_MILLIS;
@@ -1246,6 +1368,7 @@
@Test
public void testTimerTracking_Discharging() {
setDischarging();
+ setProcessState(ActivityManager.PROCESS_STATE_BACKUP);
JobStatus jobStatus = createJobStatus("testTimerTracking_Discharging", 1);
mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
@@ -1293,6 +1416,8 @@
*/
@Test
public void testTimerTracking_ChargingAndDischarging() {
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
JobStatus jobStatus = createJobStatus("testTimerTracking_ChargingAndDischarging", 1);
mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
JobStatus jobStatus2 = createJobStatus("testTimerTracking_ChargingAndDischarging", 2);
@@ -1363,6 +1488,7 @@
@Test
public void testTimerTracking_AllBackground() {
setDischarging();
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
JobStatus jobStatus = createJobStatus("testTimerTracking_AllBackground", 1);
mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
@@ -1503,6 +1629,64 @@
}
/**
+ * Tests that Timers don't track job counts while in the foreground.
+ */
+ @Test
+ public void testTimerTracking_JobCount_Foreground() {
+ setDischarging();
+
+ final int standbyBucket = ACTIVE_INDEX;
+ JobStatus jobFg1 = createJobStatus("testTimerTracking_JobCount_Foreground", 1);
+ JobStatus jobFg2 = createJobStatus("testTimerTracking_JobCount_Foreground", 2);
+
+ mQuotaController.maybeStartTrackingJobLocked(jobFg1, null);
+ mQuotaController.maybeStartTrackingJobLocked(jobFg2, null);
+ assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+ ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
+ SOURCE_PACKAGE, standbyBucket);
+ assertEquals(0, stats.jobCountInAllowedTime);
+
+ setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ mQuotaController.prepareForExecutionLocked(jobFg1);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.prepareForExecutionLocked(jobFg2);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(jobFg2, null, false);
+ assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ assertEquals(0, stats.jobCountInAllowedTime);
+ }
+
+ /**
+ * Tests that Timers properly track job counts while in the background.
+ */
+ @Test
+ public void testTimerTracking_JobCount_Background() {
+ final int standbyBucket = WORKING_INDEX;
+ JobStatus jobBg1 = createJobStatus("testTimerTracking_JobCount_Background", 1);
+ JobStatus jobBg2 = createJobStatus("testTimerTracking_JobCount_Background", 2);
+ mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
+ mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
+
+ ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
+ SOURCE_PACKAGE, standbyBucket);
+ assertEquals(0, stats.jobCountInAllowedTime);
+
+ setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+ mQuotaController.prepareForExecutionLocked(jobBg1);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.prepareForExecutionLocked(jobBg2);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(jobBg1, null, false);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
+
+ assertEquals(2, stats.jobCountInAllowedTime);
+ }
+
+ /**
* Tests that Timers properly track overlapping top and background jobs.
*/
@Test
@@ -1680,6 +1864,7 @@
JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1);
mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
+ setProcessState(ActivityManager.PROCESS_STATE_HOME);
// Now the package only has two seconds to run.
final long remainingTimeMs = 2 * SECOND_IN_MILLIS;
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -1707,6 +1892,7 @@
JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1);
mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
+ setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
Handler handler = mQuotaController.getHandler();
spyOn(handler);
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 1b5ba26..dc31c0f 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -156,6 +156,7 @@
</activity>
<activity android:name="com.android.server.accounts.AccountAuthenticatorDummyActivity" />
+ <activity android:name="com.android.server.adb.AdbDebuggingManagerTestActivity" />
<activity-alias android:name="a.ShortcutEnabled"
android:targetActivity="com.android.server.pm.ShortcutTestActivity"
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
index feffeef..773b877 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
@@ -20,7 +20,9 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
@@ -36,19 +38,15 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IntentFilter;
-import android.content.res.Resources;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
-import android.os.Handler;
import android.os.Looper;
-import android.os.Message;
import android.view.MagnificationSpec;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.R;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
@@ -81,48 +79,36 @@
static final Region OTHER_REGION = new Region(OTHER_MAGNIFICATION_BOUNDS);
static final int SERVICE_ID_1 = 1;
static final int SERVICE_ID_2 = 2;
+ static final int DISPLAY_0 = 0;
+ static final int DISPLAY_1 = 1;
+ static final int DISPLAY_COUNT = 2;
+ static final int INVALID_DISPLAY = 2;
+ final MagnificationController.ControllerContext mMockControllerCtx =
+ mock(MagnificationController.ControllerContext.class);
final Context mMockContext = mock(Context.class);
final AccessibilityManagerService mMockAms = mock(AccessibilityManagerService.class);
final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class);
- final MessageCapturingHandler mMessageCapturingHandler =
- new MessageCapturingHandler(null);
+ final MessageCapturingHandler mMessageCapturingHandler = new MessageCapturingHandler(null);
- final ValueAnimator mMockValueAnimator = mock(ValueAnimator.class);
- MagnificationController.SettingsBridge mMockSettingsBridge;
+ ValueAnimator mMockValueAnimator;
+ ValueAnimator.AnimatorUpdateListener mTargetAnimationListener;
MagnificationController mMagnificationController;
- ValueAnimator.AnimatorUpdateListener mTargetAnimationListener;
@Before
public void setUp() {
Looper looper = InstrumentationRegistry.getContext().getMainLooper();
// Pretending ID of the Thread associated with looper as main thread ID in controller
when(mMockContext.getMainLooper()).thenReturn(looper);
- Resources mockResources = mock(Resources.class);
- when(mMockContext.getResources()).thenReturn(mockResources);
- when(mockResources.getInteger(R.integer.config_longAnimTime))
- .thenReturn(1000);
- mMockSettingsBridge = mock(MagnificationController.SettingsBridge.class);
- mMagnificationController = new MagnificationController(mMockContext, mMockAms, new Object(),
- mMessageCapturingHandler, mMockWindowManager, mMockValueAnimator,
- mMockSettingsBridge);
+ when(mMockControllerCtx.getContext()).thenReturn(mMockContext);
+ when(mMockControllerCtx.getAms()).thenReturn(mMockAms);
+ when(mMockControllerCtx.getWindowManager()).thenReturn(mMockWindowManager);
+ when(mMockControllerCtx.getHandler()).thenReturn(mMessageCapturingHandler);
+ when(mMockControllerCtx.getAnimationDuration()).thenReturn(1000L);
+ initMockWindowManager();
- doAnswer(new Answer<Void>() {
- @Override
- public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
- Object[] args = invocationOnMock.getArguments();
- Region regionArg = (Region) args[0];
- regionArg.set(INITIAL_MAGNIFICATION_REGION);
- return null;
- }
- }).when(mMockWindowManager).getMagnificationRegion((Region) anyObject());
-
- ArgumentCaptor<ValueAnimator.AnimatorUpdateListener> listenerArgumentCaptor =
- ArgumentCaptor.forClass(ValueAnimator.AnimatorUpdateListener.class);
- verify(mMockValueAnimator).addUpdateListener(listenerArgumentCaptor.capture());
- mTargetAnimationListener = listenerArgumentCaptor.getValue();
- Mockito.reset(mMockValueAnimator); // Ignore other initialization
+ mMagnificationController = new MagnificationController(mMockControllerCtx, new Object());
}
@After
@@ -133,88 +119,132 @@
@Test
public void testRegister_WindowManagerAndContextRegisterListeners() {
- mMagnificationController.register();
+ register(DISPLAY_0);
+ register(DISPLAY_1);
+ register(INVALID_DISPLAY);
verify(mMockContext).registerReceiver(
(BroadcastReceiver) anyObject(), (IntentFilter) anyObject());
- verify(mMockWindowManager).setMagnificationCallbacks((MagnificationCallbacks) anyObject());
- assertTrue(mMagnificationController.isRegisteredLocked());
+ verify(mMockWindowManager).setMagnificationCallbacks(
+ eq(DISPLAY_0), (MagnificationCallbacks) anyObject());
+ verify(mMockWindowManager).setMagnificationCallbacks(
+ eq(DISPLAY_1), (MagnificationCallbacks) anyObject());
+ verify(mMockWindowManager).setMagnificationCallbacks(
+ eq(INVALID_DISPLAY), (MagnificationCallbacks) anyObject());
+ assertTrue(mMagnificationController.isRegistered(DISPLAY_0));
+ assertTrue(mMagnificationController.isRegistered(DISPLAY_1));
+ assertFalse(mMagnificationController.isRegistered(INVALID_DISPLAY));
}
@Test
public void testRegister_WindowManagerAndContextUnregisterListeners() {
- mMagnificationController.register();
- mMagnificationController.unregister();
-
+ register(DISPLAY_0);
+ register(DISPLAY_1);
+ mMagnificationController.unregister(DISPLAY_0);
+ verify(mMockContext, times(0)).unregisterReceiver((BroadcastReceiver) anyObject());
+ mMagnificationController.unregister(DISPLAY_1);
verify(mMockContext).unregisterReceiver((BroadcastReceiver) anyObject());
- verify(mMockWindowManager).setMagnificationCallbacks(null);
- assertFalse(mMagnificationController.isRegisteredLocked());
+ verify(mMockWindowManager).setMagnificationCallbacks(eq(DISPLAY_0), eq(null));
+ verify(mMockWindowManager).setMagnificationCallbacks(eq(DISPLAY_1), eq(null));
+ assertFalse(mMagnificationController.isRegistered(DISPLAY_0));
+ assertFalse(mMagnificationController.isRegistered(DISPLAY_1));
}
@Test
public void testInitialState_noMagnificationAndMagnificationRegionReadFromWindowManager() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ initialState_noMagnificationAndMagnificationRegionReadFromWindowManager(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void initialState_noMagnificationAndMagnificationRegionReadFromWindowManager(
+ int displayId) {
+ register(displayId);
MagnificationSpec expectedInitialSpec = getMagnificationSpec(1.0f, 0.0f, 0.0f);
Region initialMagRegion = new Region();
Rect initialBounds = new Rect();
- assertEquals(expectedInitialSpec, getCurrentMagnificationSpec());
- mMagnificationController.getMagnificationRegion(initialMagRegion);
- mMagnificationController.getMagnificationBounds(initialBounds);
+ assertEquals(expectedInitialSpec, getCurrentMagnificationSpec(displayId));
+ mMagnificationController.getMagnificationRegion(displayId, initialMagRegion);
+ mMagnificationController.getMagnificationBounds(displayId, initialBounds);
assertEquals(INITIAL_MAGNIFICATION_REGION, initialMagRegion);
assertEquals(INITIAL_MAGNIFICATION_BOUNDS, initialBounds);
assertEquals(INITIAL_MAGNIFICATION_BOUNDS.centerX(),
- mMagnificationController.getCenterX(), 0.0f);
+ mMagnificationController.getCenterX(displayId), 0.0f);
assertEquals(INITIAL_MAGNIFICATION_BOUNDS.centerY(),
- mMagnificationController.getCenterY(), 0.0f);
+ mMagnificationController.getCenterY(displayId), 0.0f);
}
@Test
public void testNotRegistered_publicMethodsShouldBeBenign() {
- assertFalse(mMagnificationController.isMagnifying());
- assertFalse(mMagnificationController.magnificationRegionContains(100, 100));
- assertFalse(mMagnificationController.reset(true));
- assertFalse(mMagnificationController.setScale(2, 100, 100, true, 0));
- assertFalse(mMagnificationController.setCenter(100, 100, false, 1));
- assertFalse(mMagnificationController.setScaleAndCenter(1.5f, 100, 100, false, 2));
- assertTrue(mMagnificationController.getIdOfLastServiceToMagnify() < 0);
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ notRegistered_publicMethodsShouldBeBenign(i);
+ resetMockWindowManager();
+ }
+ }
- mMagnificationController.getMagnificationRegion(new Region());
- mMagnificationController.getMagnificationBounds(new Rect());
- mMagnificationController.getScale();
- mMagnificationController.getOffsetX();
- mMagnificationController.getOffsetY();
- mMagnificationController.getCenterX();
- mMagnificationController.getCenterY();
- mMagnificationController.offsetMagnifiedRegion(50, 50, 1);
- mMagnificationController.unregister();
+ private void notRegistered_publicMethodsShouldBeBenign(int displayId) {
+ assertFalse(mMagnificationController.isMagnifying(displayId));
+ assertFalse(mMagnificationController.magnificationRegionContains(displayId, 100, 100));
+ assertFalse(mMagnificationController.reset(displayId, true));
+ assertFalse(mMagnificationController.setScale(displayId, 2, 100, 100, true, 0));
+ assertFalse(mMagnificationController.setCenter(displayId, 100, 100, false, 1));
+ assertFalse(mMagnificationController.setScaleAndCenter(displayId,
+ 1.5f, 100, 100, false, 2));
+ assertTrue(mMagnificationController.getIdOfLastServiceToMagnify(displayId) < 0);
+
+ mMagnificationController.getMagnificationRegion(displayId, new Region());
+ mMagnificationController.getMagnificationBounds(displayId, new Rect());
+ mMagnificationController.getScale(displayId);
+ mMagnificationController.getOffsetX(displayId);
+ mMagnificationController.getOffsetY(displayId);
+ mMagnificationController.getCenterX(displayId);
+ mMagnificationController.getCenterY(displayId);
+ mMagnificationController.offsetMagnifiedRegion(displayId, 50, 50, 1);
+ mMagnificationController.unregister(displayId);
}
@Test
public void testSetScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ setScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void setScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState(int displayId) {
+ register(displayId);
final float scale = 2.0f;
final PointF center = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
final PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, center, scale);
assertTrue(mMagnificationController
- .setScale(scale, center.x, center.y, false, SERVICE_ID_1));
+ .setScale(displayId, scale, center.x, center.y, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
final MagnificationSpec expectedSpec = getMagnificationSpec(scale, offsets);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec)));
- assertThat(getCurrentMagnificationSpec(), closeTo(expectedSpec));
- assertEquals(center.x, mMagnificationController.getCenterX(), 0.0);
- assertEquals(center.y, mMagnificationController.getCenterY(), 0.0);
+ verify(mMockWindowManager).setMagnificationSpec(
+ eq(displayId), argThat(closeTo(expectedSpec)));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedSpec));
+ assertEquals(center.x, mMagnificationController.getCenterX(displayId), 0.0);
+ assertEquals(center.y, mMagnificationController.getCenterY(displayId), 0.0);
verify(mMockValueAnimator, times(0)).start();
}
@Test
public void testSetScale_withPivotAndAnimation_stateChangesAndAnimationHappens() {
- mMagnificationController.register();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ setScale_withPivotAndAnimation_stateChangesAndAnimationHappens(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void setScale_withPivotAndAnimation_stateChangesAndAnimationHappens(int displayId) {
+ register(displayId);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
float scale = 2.0f;
PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
assertTrue(mMagnificationController
- .setScale(scale, pivotPoint.x, pivotPoint.y, true, SERVICE_ID_1));
+ .setScale(displayId, scale, pivotPoint.x, pivotPoint.y, true, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
// New center should be halfway between original center and pivot
@@ -223,467 +253,645 @@
PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
MagnificationSpec endSpec = getMagnificationSpec(scale, offsets);
- assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
- assertThat(getCurrentMagnificationSpec(), closeTo(endSpec));
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(endSpec));
verify(mMockValueAnimator).start();
// Initial value
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(startSpec);
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), eq(startSpec));
// Intermediate point
Mockito.reset(mMockWindowManager);
float fraction = 0.5f;
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
argThat(closeTo(getInterpolatedMagSpec(startSpec, endSpec, fraction))));
// Final value
Mockito.reset(mMockWindowManager);
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
}
@Test
public void testSetCenter_whileMagnifying_noAnimation_centerMoves() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ setCenter_whileMagnifying_noAnimation_centerMoves(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void setCenter_whileMagnifying_noAnimation_centerMoves(int displayId) {
+ register(displayId);
// First zoom in
float scale = 2.0f;
- assertTrue(mMagnificationController.setScale(scale,
+ assertTrue(mMagnificationController.setScale(displayId, scale,
INITIAL_MAGNIFICATION_BOUNDS.centerX(), INITIAL_MAGNIFICATION_BOUNDS.centerY(),
false, SERVICE_ID_1));
Mockito.reset(mMockWindowManager);
PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
assertTrue(mMagnificationController
- .setCenter(newCenter.x, newCenter.y, false, SERVICE_ID_1));
+ .setCenter(displayId, newCenter.x, newCenter.y, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
MagnificationSpec expectedSpec = getMagnificationSpec(scale, expectedOffsets);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec)));
- assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.0);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.0);
+ verify(mMockWindowManager).setMagnificationSpec(
+ eq(displayId), argThat(closeTo(expectedSpec)));
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.0);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.0);
verify(mMockValueAnimator, times(0)).start();
}
@Test
public void testSetScaleAndCenter_animated_stateChangesAndAnimationHappens() {
- mMagnificationController.register();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ setScaleAndCenter_animated_stateChangesAndAnimationHappens(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void setScaleAndCenter_animated_stateChangesAndAnimationHappens(int displayId) {
+ register(displayId);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
float scale = 2.5f;
PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
MagnificationSpec endSpec = getMagnificationSpec(scale, offsets);
- assertTrue(mMagnificationController.setScaleAndCenter(scale, newCenter.x, newCenter.y,
- true, SERVICE_ID_1));
+ assertTrue(mMagnificationController.setScaleAndCenter(displayId, scale, newCenter.x,
+ newCenter.y, true, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
- assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
- assertThat(getCurrentMagnificationSpec(), closeTo(endSpec));
- verify(mMockAms).notifyMagnificationChanged(
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(endSpec));
+ verify(mMockAms).notifyMagnificationChanged(displayId,
INITIAL_MAGNIFICATION_REGION, scale, newCenter.x, newCenter.y);
verify(mMockValueAnimator).start();
// Initial value
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(startSpec);
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), eq(startSpec));
// Intermediate point
Mockito.reset(mMockWindowManager);
float fraction = 0.33f;
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
argThat(closeTo(getInterpolatedMagSpec(startSpec, endSpec, fraction))));
// Final value
Mockito.reset(mMockWindowManager);
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
}
@Test
public void testSetScaleAndCenter_scaleOutOfBounds_cappedAtLimits() {
- mMagnificationController.register();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ setScaleAndCenter_scaleOutOfBounds_cappedAtLimits(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void setScaleAndCenter_scaleOutOfBounds_cappedAtLimits(int displayId) {
+ register(displayId);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter,
MagnificationController.MAX_SCALE);
MagnificationSpec endSpec = getMagnificationSpec(
MagnificationController.MAX_SCALE, offsets);
- assertTrue(mMagnificationController.setScaleAndCenter(
+ assertTrue(mMagnificationController.setScaleAndCenter(displayId,
MagnificationController.MAX_SCALE + 1.0f,
newCenter.x, newCenter.y, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
- assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
Mockito.reset(mMockWindowManager);
// Verify that we can't zoom below 1x
- assertTrue(mMagnificationController.setScaleAndCenter(0.5f,
+ assertTrue(mMagnificationController.setScaleAndCenter(displayId, 0.5f,
INITIAL_MAGNIFICATION_BOUNDS_CENTER.x, INITIAL_MAGNIFICATION_BOUNDS_CENTER.y,
false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
assertEquals(INITIAL_MAGNIFICATION_BOUNDS_CENTER.x,
- mMagnificationController.getCenterX(), 0.5);
+ mMagnificationController.getCenterX(displayId), 0.5);
assertEquals(INITIAL_MAGNIFICATION_BOUNDS_CENTER.y,
- mMagnificationController.getCenterY(), 0.5);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(startSpec)));
+ mMagnificationController.getCenterY(displayId), 0.5);
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(startSpec)));
}
@Test
public void testSetScaleAndCenter_centerOutOfBounds_cappedAtLimits() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ setScaleAndCenter_centerOutOfBounds_cappedAtLimits(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void setScaleAndCenter_centerOutOfBounds_cappedAtLimits(int displayId) {
+ register(displayId);
float scale = 2.0f;
// Off the edge to the top and left
- assertTrue(mMagnificationController.setScaleAndCenter(
+ assertTrue(mMagnificationController.setScaleAndCenter(displayId,
scale, -100f, -200f, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
PointF newCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
- assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
- verify(mMockWindowManager).setMagnificationSpec(
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
argThat(closeTo(getMagnificationSpec(scale, newOffsets))));
Mockito.reset(mMockWindowManager);
// Off the edge to the bottom and right
- assertTrue(mMagnificationController.setScaleAndCenter(scale,
+ assertTrue(mMagnificationController.setScaleAndCenter(displayId, scale,
INITIAL_MAGNIFICATION_BOUNDS.right + 1, INITIAL_MAGNIFICATION_BOUNDS.bottom + 1,
false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
- assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
- verify(mMockWindowManager).setMagnificationSpec(
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
argThat(closeTo(getMagnificationSpec(scale, newOffsets))));
}
@Test
public void testMagnificationRegionChanged_serviceNotified() {
- mMagnificationController.register();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ magnificationRegionChanged_serviceNotified(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void magnificationRegionChanged_serviceNotified(int displayId) {
+ register(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
callbacks.onMagnificationRegionChanged(OTHER_REGION);
mMessageCapturingHandler.sendAllMessages();
- verify(mMockAms).notifyMagnificationChanged(OTHER_REGION, 1.0f,
+ verify(mMockAms).notifyMagnificationChanged(displayId, OTHER_REGION, 1.0f,
OTHER_MAGNIFICATION_BOUNDS.centerX(), OTHER_MAGNIFICATION_BOUNDS.centerY());
}
@Test
public void testOffsetMagnifiedRegion_whileMagnifying_offsetsMove() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ offsetMagnifiedRegion_whileMagnifying_offsetsMove(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void offsetMagnifiedRegion_whileMagnifying_offsetsMove(int displayId) {
+ register(displayId);
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
float scale = 2.0f;
PointF startOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, startCenter, scale);
// First zoom in
assertTrue(mMagnificationController
- .setScaleAndCenter(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1));
+ .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, false,
+ SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
Mockito.reset(mMockWindowManager);
PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
- mMagnificationController.offsetMagnifiedRegion(
- startOffsets.x - newOffsets.x, startOffsets.y - newOffsets.y, SERVICE_ID_1);
+ mMagnificationController.offsetMagnifiedRegion(displayId,
+ startOffsets.x - newOffsets.x, startOffsets.y - newOffsets.y,
+ SERVICE_ID_1);
mMessageCapturingHandler.sendAllMessages();
MagnificationSpec expectedSpec = getMagnificationSpec(scale, newOffsets);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec)));
- assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.0);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.0);
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+ argThat(closeTo(expectedSpec)));
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.0);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.0);
verify(mMockValueAnimator, times(0)).start();
}
@Test
public void testOffsetMagnifiedRegion_whileNotMagnifying_hasNoEffect() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ offsetMagnifiedRegion_whileNotMagnifying_hasNoEffect(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void offsetMagnifiedRegion_whileNotMagnifying_hasNoEffect(int displayId) {
+ register(displayId);
Mockito.reset(mMockWindowManager);
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
- mMagnificationController.offsetMagnifiedRegion(10, 10, SERVICE_ID_1);
- assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
- mMagnificationController.offsetMagnifiedRegion(-10, -10, SERVICE_ID_1);
- assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+ mMagnificationController.offsetMagnifiedRegion(displayId, 10, 10, SERVICE_ID_1);
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
+ mMagnificationController.offsetMagnifiedRegion(displayId, -10, -10, SERVICE_ID_1);
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
verifyNoMoreInteractions(mMockWindowManager);
}
@Test
public void testOffsetMagnifiedRegion_whileMagnifyingButAtEdge_hasNoEffect() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ offsetMagnifiedRegion_whileMagnifyingButAtEdge_hasNoEffect(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void offsetMagnifiedRegion_whileMagnifyingButAtEdge_hasNoEffect(int displayId) {
+ register(displayId);
float scale = 2.0f;
// Upper left edges
PointF ulCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
assertTrue(mMagnificationController
- .setScaleAndCenter(scale, ulCenter.x, ulCenter.y, false, SERVICE_ID_1));
+ .setScaleAndCenter(displayId, scale, ulCenter.x, ulCenter.y, false,
+ SERVICE_ID_1));
Mockito.reset(mMockWindowManager);
- MagnificationSpec ulSpec = getCurrentMagnificationSpec();
- mMagnificationController.offsetMagnifiedRegion(-10, -10, SERVICE_ID_1);
- assertThat(getCurrentMagnificationSpec(), closeTo(ulSpec));
+ MagnificationSpec ulSpec = getCurrentMagnificationSpec(displayId);
+ mMagnificationController.offsetMagnifiedRegion(displayId, -10, -10,
+ SERVICE_ID_1);
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(ulSpec));
verifyNoMoreInteractions(mMockWindowManager);
// Lower right edges
PointF lrCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
assertTrue(mMagnificationController
- .setScaleAndCenter(scale, lrCenter.x, lrCenter.y, false, SERVICE_ID_1));
+ .setScaleAndCenter(displayId, scale, lrCenter.x, lrCenter.y, false,
+ SERVICE_ID_1));
Mockito.reset(mMockWindowManager);
- MagnificationSpec lrSpec = getCurrentMagnificationSpec();
- mMagnificationController.offsetMagnifiedRegion(10, 10, SERVICE_ID_1);
- assertThat(getCurrentMagnificationSpec(), closeTo(lrSpec));
+ MagnificationSpec lrSpec = getCurrentMagnificationSpec(displayId);
+ mMagnificationController.offsetMagnifiedRegion(displayId, 10, 10,
+ SERVICE_ID_1);
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(lrSpec));
verifyNoMoreInteractions(mMockWindowManager);
}
@Test
public void testGetIdOfLastServiceToChange_returnsCorrectValue() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ getIdOfLastServiceToChange_returnsCorrectValue(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void getIdOfLastServiceToChange_returnsCorrectValue(int displayId) {
+ register(displayId);
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
assertTrue(mMagnificationController
- .setScale(2.0f, startCenter.x, startCenter.y, false, SERVICE_ID_1));
- assertEquals(SERVICE_ID_1, mMagnificationController.getIdOfLastServiceToMagnify());
+ .setScale(displayId, 2.0f, startCenter.x, startCenter.y, false,
+ SERVICE_ID_1));
+ assertEquals(SERVICE_ID_1, mMagnificationController.getIdOfLastServiceToMagnify(displayId));
assertTrue(mMagnificationController
- .setScale(1.5f, startCenter.x, startCenter.y, false, SERVICE_ID_2));
- assertEquals(SERVICE_ID_2, mMagnificationController.getIdOfLastServiceToMagnify());
+ .setScale(displayId, 1.5f, startCenter.x, startCenter.y, false,
+ SERVICE_ID_2));
+ assertEquals(SERVICE_ID_2, mMagnificationController.getIdOfLastServiceToMagnify(displayId));
}
@Test
public void testResetIfNeeded_resetsOnlyIfLastMagnifyingServiceIsDisabled() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ resetIfNeeded_resetsOnlyIfLastMagnifyingServiceIsDisabled(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void resetIfNeeded_resetsOnlyIfLastMagnifyingServiceIsDisabled(int displayId) {
+ register(displayId);
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
mMagnificationController
- .setScale(2.0f, startCenter.x, startCenter.y, false, SERVICE_ID_1);
+ .setScale(displayId, 2.0f, startCenter.x, startCenter.y, false,
+ SERVICE_ID_1);
mMagnificationController
- .setScale(1.5f, startCenter.x, startCenter.y, false, SERVICE_ID_2);
- assertFalse(mMagnificationController.resetIfNeeded(SERVICE_ID_1));
- assertTrue(mMagnificationController.isMagnifying());
- assertTrue(mMagnificationController.resetIfNeeded(SERVICE_ID_2));
- assertFalse(mMagnificationController.isMagnifying());
+ .setScale(displayId, 1.5f, startCenter.x, startCenter.y, false,
+ SERVICE_ID_2);
+ assertFalse(mMagnificationController.resetIfNeeded(displayId, SERVICE_ID_1));
+ assertTrue(mMagnificationController.isMagnifying(displayId));
+ assertTrue(mMagnificationController.resetIfNeeded(displayId, SERVICE_ID_2));
+ assertFalse(mMagnificationController.isMagnifying(displayId));
}
@Test
public void testSetUserId_resetsOnlyIfIdChanges() {
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ testSetUserId_resetsOnlyIfIdChanges(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void testSetUserId_resetsOnlyIfIdChanges(int displayId) {
final int userId1 = 1;
final int userId2 = 2;
- mMagnificationController.register();
+ register(displayId);
mMagnificationController.setUserId(userId1);
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
float scale = 2.0f;
- mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1);
+ mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, false,
+ SERVICE_ID_1);
mMagnificationController.setUserId(userId1);
- assertTrue(mMagnificationController.isMagnifying());
+ assertTrue(mMagnificationController.isMagnifying(displayId));
mMagnificationController.setUserId(userId2);
- assertFalse(mMagnificationController.isMagnifying());
+ assertFalse(mMagnificationController.isMagnifying(displayId));
}
@Test
public void testResetIfNeeded_doesWhatItSays() {
- mMagnificationController.register();
- zoomIn2xToMiddle();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ testResetIfNeeded_doesWhatItSays(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void testResetIfNeeded_doesWhatItSays(int displayId) {
+ register(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
reset(mMockAms);
- assertTrue(mMagnificationController.resetIfNeeded(false));
- verify(mMockAms).notifyMagnificationChanged(
+ assertTrue(mMagnificationController.resetIfNeeded(displayId, false));
+ verify(mMockAms).notifyMagnificationChanged(eq(displayId),
eq(INITIAL_MAGNIFICATION_REGION), eq(1.0f), anyFloat(), anyFloat());
- assertFalse(mMagnificationController.isMagnifying());
- assertFalse(mMagnificationController.resetIfNeeded(false));
+ assertFalse(mMagnificationController.isMagnifying(displayId));
+ assertFalse(mMagnificationController.resetIfNeeded(displayId, false));
}
@Test
public void testTurnScreenOff_resetsMagnification() {
- mMagnificationController.register();
+ register(DISPLAY_0);
+ register(DISPLAY_1);
ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
ArgumentCaptor.forClass(BroadcastReceiver.class);
verify(mMockContext).registerReceiver(
broadcastReceiverCaptor.capture(), (IntentFilter) anyObject());
BroadcastReceiver br = broadcastReceiverCaptor.getValue();
- zoomIn2xToMiddle();
+ zoomIn2xToMiddle(DISPLAY_0);
+ zoomIn2xToMiddle(DISPLAY_1);
mMessageCapturingHandler.sendAllMessages();
br.onReceive(mMockContext, null);
mMessageCapturingHandler.sendAllMessages();
- assertFalse(mMagnificationController.isMagnifying());
+ assertFalse(mMagnificationController.isMagnifying(DISPLAY_0));
+ assertFalse(mMagnificationController.isMagnifying(DISPLAY_1));
}
@Test
public void testUserContextChange_resetsMagnification() {
- mMagnificationController.register();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
- zoomIn2xToMiddle();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ contextChange_resetsMagnification(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void contextChange_resetsMagnification(int displayId) {
+ register(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
callbacks.onUserContextChanged();
mMessageCapturingHandler.sendAllMessages();
- assertFalse(mMagnificationController.isMagnifying());
+ assertFalse(mMagnificationController.isMagnifying(displayId));
}
@Test
public void testRotation_resetsMagnification() {
- mMagnificationController.register();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
- zoomIn2xToMiddle();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ rotation_resetsMagnification(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void rotation_resetsMagnification(int displayId) {
+ register(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
- assertTrue(mMagnificationController.isMagnifying());
+ assertTrue(mMagnificationController.isMagnifying(displayId));
callbacks.onRotationChanged(0);
mMessageCapturingHandler.sendAllMessages();
- assertFalse(mMagnificationController.isMagnifying());
+ assertFalse(mMagnificationController.isMagnifying(displayId));
}
@Test
public void testBoundsChange_whileMagnifyingWithCompatibleSpec_noSpecChange() {
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ boundsChange_whileMagnifyingWithCompatibleSpec_noSpecChange(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void boundsChange_whileMagnifyingWithCompatibleSpec_noSpecChange(int displayId) {
// Going from a small region to a large one leads to no issues
- mMagnificationController.register();
- zoomIn2xToMiddle();
+ register(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
Mockito.reset(mMockWindowManager);
callbacks.onMagnificationRegionChanged(OTHER_REGION_COMPAT);
mMessageCapturingHandler.sendAllMessages();
- assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
verifyNoMoreInteractions(mMockWindowManager);
}
@Test
public void testBoundsChange_whileZoomingWithCompatibleSpec_noSpecChange() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ boundsChange_whileZoomingWithCompatibleSpec_noSpecChange(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void boundsChange_whileZoomingWithCompatibleSpec_noSpecChange(int displayId) {
+ register(displayId);
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
float scale = 2.0f;
// setting animate parameter to true is differ from zoomIn2xToMiddle()
- mMagnificationController.setScale(scale, startCenter.x, startCenter.y, true, SERVICE_ID_1);
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, true,
+ SERVICE_ID_1);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
Mockito.reset(mMockWindowManager);
callbacks.onMagnificationRegionChanged(OTHER_REGION_COMPAT);
mMessageCapturingHandler.sendAllMessages();
- assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
verifyNoMoreInteractions(mMockWindowManager);
}
@Test
public void testBoundsChange_whileMagnifyingWithIncompatibleSpec_offsetsConstrained() {
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ boundsChange_whileMagnifyingWithIncompatibleSpec_offsetsConstrained(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void boundsChange_whileMagnifyingWithIncompatibleSpec_offsetsConstrained(
+ int displayId) {
// In a large region, pan to the farthest point possible
- mMagnificationController.register();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ register(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
callbacks.onMagnificationRegionChanged(OTHER_REGION);
mMessageCapturingHandler.sendAllMessages();
PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER;
float scale = 2.0f;
- mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1);
+ mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, false,
+ SERVICE_ID_1);
mMessageCapturingHandler.sendAllMessages();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(startSpec)));
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(startSpec)));
Mockito.reset(mMockWindowManager);
callbacks.onMagnificationRegionChanged(INITIAL_MAGNIFICATION_REGION);
mMessageCapturingHandler.sendAllMessages();
- MagnificationSpec endSpec = getCurrentMagnificationSpec();
+ MagnificationSpec endSpec = getCurrentMagnificationSpec(displayId);
assertThat(endSpec, CoreMatchers.not(closeTo(startSpec)));
PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS,
INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale);
assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets)));
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
}
@Test
public void testBoundsChange_whileZoomingWithIncompatibleSpec_jumpsToCompatibleSpec() {
- mMagnificationController.register();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ boundsChange_whileZoomingWithIncompatibleSpec_jumpsToCompatibleSpec(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void boundsChange_whileZoomingWithIncompatibleSpec_jumpsToCompatibleSpec(
+ int displayId) {
+ register(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
callbacks.onMagnificationRegionChanged(OTHER_REGION);
mMessageCapturingHandler.sendAllMessages();
PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER;
float scale = 2.0f;
- mMagnificationController.setScale(scale, startCenter.x, startCenter.y, true, SERVICE_ID_1);
+ mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, true,
+ SERVICE_ID_1);
mMessageCapturingHandler.sendAllMessages();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
when(mMockValueAnimator.isRunning()).thenReturn(true);
callbacks.onMagnificationRegionChanged(INITIAL_MAGNIFICATION_REGION);
mMessageCapturingHandler.sendAllMessages();
verify(mMockValueAnimator).cancel();
- MagnificationSpec endSpec = getCurrentMagnificationSpec();
+ MagnificationSpec endSpec = getCurrentMagnificationSpec(displayId);
assertThat(endSpec, CoreMatchers.not(closeTo(startSpec)));
PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS,
INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale);
assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets)));
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
}
@Test
public void testRequestRectOnScreen_rectAlreadyOnScreen_doesNothing() {
- mMagnificationController.register();
- zoomIn2xToMiddle();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ requestRectOnScreen_rectAlreadyOnScreen_doesNothing(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void requestRectOnScreen_rectAlreadyOnScreen_doesNothing(int displayId) {
+ register(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
Mockito.reset(mMockWindowManager);
int centerX = (int) INITIAL_MAGNIFICATION_BOUNDS_CENTER.x;
int centerY = (int) INITIAL_MAGNIFICATION_BOUNDS_CENTER.y;
callbacks.onRectangleOnScreenRequested(centerX - 1, centerY - 1, centerX + 1, centerY - 1);
mMessageCapturingHandler.sendAllMessages();
- assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
verifyNoMoreInteractions(mMockWindowManager);
}
@Test
public void testRequestRectOnScreen_rectCanFitOnScreen_pansToGetRectOnScreen() {
- mMagnificationController.register();
- zoomIn2xToMiddle();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ requestRectOnScreen_rectCanFitOnScreen_pansToGetRectOnScreen(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void requestRectOnScreen_rectCanFitOnScreen_pansToGetRectOnScreen(int displayId) {
+ register(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
Mockito.reset(mMockWindowManager);
callbacks.onRectangleOnScreenRequested(0, 0, 1, 1);
mMessageCapturingHandler.sendAllMessages();
MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, 0, 0);
- assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedEndSpec));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+ argThat(closeTo(expectedEndSpec)));
}
@Test
public void testRequestRectOnScreen_garbageInput_doesNothing() {
- mMagnificationController.register();
- zoomIn2xToMiddle();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ requestRectOnScreen_garbageInput_doesNothing(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void requestRectOnScreen_garbageInput_doesNothing(int displayId) {
+ register(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
Mockito.reset(mMockWindowManager);
callbacks.onRectangleOnScreenRequested(0, 0, -50, -50);
mMessageCapturingHandler.sendAllMessages();
- assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
verifyNoMoreInteractions(mMockWindowManager);
}
@Test
public void testRequestRectOnScreen_rectTooWide_pansToGetStartOnScreenBasedOnLocale() {
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ requestRectOnScreen_rectTooWide_pansToGetStartOnScreenBasedOnLocale(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void requestRectOnScreen_rectTooWide_pansToGetStartOnScreenBasedOnLocale(
+ int displayId) {
Locale.setDefault(new Locale("en", "us"));
- mMagnificationController.register();
- zoomIn2xToMiddle();
+ register(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
Mockito.reset(mMockWindowManager);
Rect wideRect = new Rect(0, 50, 100, 51);
callbacks.onRectangleOnScreenRequested(
wideRect.left, wideRect.top, wideRect.right, wideRect.bottom);
mMessageCapturingHandler.sendAllMessages();
MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, 0, startSpec.offsetY);
- assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedEndSpec));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+ argThat(closeTo(expectedEndSpec)));
Mockito.reset(mMockWindowManager);
// Repeat with RTL
@@ -692,50 +900,66 @@
wideRect.left, wideRect.top, wideRect.right, wideRect.bottom);
mMessageCapturingHandler.sendAllMessages();
expectedEndSpec = getMagnificationSpec(2.0f, -100, startSpec.offsetY);
- assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedEndSpec));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+ argThat(closeTo(expectedEndSpec)));
}
@Test
public void testRequestRectOnScreen_rectTooTall_pansMinimumToGetTopOnScreen() {
- mMagnificationController.register();
- zoomIn2xToMiddle();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ requestRectOnScreen_rectTooTall_pansMinimumToGetTopOnScreen(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void requestRectOnScreen_rectTooTall_pansMinimumToGetTopOnScreen(int displayId) {
+ register(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
Mockito.reset(mMockWindowManager);
Rect tallRect = new Rect(50, 0, 51, 100);
callbacks.onRectangleOnScreenRequested(
tallRect.left, tallRect.top, tallRect.right, tallRect.bottom);
mMessageCapturingHandler.sendAllMessages();
MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, startSpec.offsetX, 0);
- assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedEndSpec));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+ argThat(closeTo(expectedEndSpec)));
}
@Test
public void testChangeMagnification_duringAnimation_animatesToNewValue() {
- mMagnificationController.register();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ changeMagnification_duringAnimation_animatesToNewValue(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void changeMagnification_duringAnimation_animatesToNewValue(int displayId) {
+ register(displayId);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
float scale = 2.5f;
PointF firstCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
MagnificationSpec firstEndSpec = getMagnificationSpec(
scale, computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, firstCenter, scale));
- assertTrue(mMagnificationController.setScaleAndCenter(scale, firstCenter.x, firstCenter.y,
- true, SERVICE_ID_1));
+ assertTrue(mMagnificationController.setScaleAndCenter(displayId,
+ scale, firstCenter.x, firstCenter.y, true, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
- assertEquals(firstCenter.x, mMagnificationController.getCenterX(), 0.5);
- assertEquals(firstCenter.y, mMagnificationController.getCenterY(), 0.5);
- assertThat(getCurrentMagnificationSpec(), closeTo(firstEndSpec));
+ assertEquals(firstCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
+ assertEquals(firstCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(firstEndSpec));
verify(mMockValueAnimator, times(1)).start();
// Initial value
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(startSpec);
- verify(mMockAms).notifyMagnificationChanged(
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), eq(startSpec));
+ verify(mMockAms).notifyMagnificationChanged(displayId,
INITIAL_MAGNIFICATION_REGION, scale, firstCenter.x, firstCenter.y);
Mockito.reset(mMockWindowManager);
@@ -745,32 +969,34 @@
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
MagnificationSpec intermediateSpec1 =
getInterpolatedMagSpec(startSpec, firstEndSpec, fraction);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(intermediateSpec1)));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+ argThat(closeTo(intermediateSpec1)));
Mockito.reset(mMockWindowManager);
PointF newCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
MagnificationSpec newEndSpec = getMagnificationSpec(
scale, computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale));
- assertTrue(mMagnificationController.setCenter(
+ assertTrue(mMagnificationController.setCenter(displayId,
newCenter.x, newCenter.y, true, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
// Animation should have been restarted
verify(mMockValueAnimator, times(2)).start();
- verify(mMockAms).notifyMagnificationChanged(
+ verify(mMockAms).notifyMagnificationChanged(displayId,
INITIAL_MAGNIFICATION_REGION, scale, newCenter.x, newCenter.y);
// New starting point should be where we left off
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(intermediateSpec1)));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+ argThat(closeTo(intermediateSpec1)));
Mockito.reset(mMockWindowManager);
// Second intermediate point
fraction = 0.5f;
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
argThat(closeTo(getInterpolatedMagSpec(intermediateSpec1, newEndSpec, fraction))));
Mockito.reset(mMockWindowManager);
@@ -778,21 +1004,54 @@
Mockito.reset(mMockWindowManager);
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(newEndSpec)));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+ argThat(closeTo(newEndSpec)));
}
- private void zoomIn2xToMiddle() {
+ private void initMockWindowManager() {
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ when(mMockWindowManager.setMagnificationCallbacks(eq(i), any())).thenReturn(true);
+ }
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
+ Object[] args = invocationOnMock.getArguments();
+ Region regionArg = (Region) args[1];
+ regionArg.set(INITIAL_MAGNIFICATION_REGION);
+ return null;
+ }
+ }).when(mMockWindowManager).getMagnificationRegion(anyInt(), (Region) anyObject());
+ }
+
+ private void resetMockWindowManager() {
+ Mockito.reset(mMockWindowManager);
+ initMockWindowManager();
+ }
+
+ private void register(int displayId) {
+ mMockValueAnimator = mock(ValueAnimator.class);
+ when(mMockControllerCtx.newValueAnimator()).thenReturn(mMockValueAnimator);
+ mMagnificationController.register(displayId);
+ ArgumentCaptor<ValueAnimator.AnimatorUpdateListener> listenerArgumentCaptor =
+ ArgumentCaptor.forClass(ValueAnimator.AnimatorUpdateListener.class);
+ verify(mMockValueAnimator).addUpdateListener(listenerArgumentCaptor.capture());
+ mTargetAnimationListener = listenerArgumentCaptor.getValue();
+ Mockito.reset(mMockValueAnimator); // Ignore other initialization
+ }
+
+ private void zoomIn2xToMiddle(int displayId) {
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
float scale = 2.0f;
- mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1);
- assertTrue(mMagnificationController.isMagnifying());
+ mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, false,
+ SERVICE_ID_1);
+ assertTrue(mMagnificationController.isMagnifying(displayId));
}
- private MagnificationCallbacks getMagnificationCallbacks() {
+ private MagnificationCallbacks getMagnificationCallbacks(int displayId) {
ArgumentCaptor<MagnificationCallbacks> magnificationCallbacksCaptor =
ArgumentCaptor.forClass(MagnificationCallbacks.class);
verify(mMockWindowManager)
- .setMagnificationCallbacks(magnificationCallbacksCaptor.capture());
+ .setMagnificationCallbacks(eq(displayId), magnificationCallbacksCaptor.capture());
return magnificationCallbacksCaptor.getValue();
}
@@ -823,9 +1082,10 @@
return spec;
}
- private MagnificationSpec getCurrentMagnificationSpec() {
- return getMagnificationSpec(mMagnificationController.getScale(),
- mMagnificationController.getOffsetX(), mMagnificationController.getOffsetY());
+ private MagnificationSpec getCurrentMagnificationSpec(int displayId) {
+ return getMagnificationSpec(mMagnificationController.getScale(displayId),
+ mMagnificationController.getOffsetX(displayId),
+ mMagnificationController.getOffsetY(displayId));
}
private MagSpecMatcher closeTo(MagnificationSpec spec) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
index 032074a..d91ce39 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
@@ -26,16 +26,19 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.Context;
+import android.os.Handler;
import android.os.Message;
import android.util.DebugUtils;
import android.view.InputDevice;
@@ -104,14 +107,9 @@
public static final float DEFAULT_X = 301;
public static final float DEFAULT_Y = 299;
+ private static final int DISPLAY_0 = 0;
+
private Context mContext;
- final AccessibilityManagerService mMockAms = mock(AccessibilityManagerService.class);
- final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class);
- final MessageCapturingHandler mMessageCapturingHandler =
- new MessageCapturingHandler(null);
- final ValueAnimator mMockValueAnimator = mock(ValueAnimator.class);
- MagnificationController.SettingsBridge mMockSettingsBridge =
- mock(MagnificationController.SettingsBridge.class);
MagnificationController mMagnificationController;
private OffsettableClock mClock;
@@ -123,18 +121,26 @@
@Before
public void setUp() {
mContext = InstrumentationRegistry.getContext();
- mMagnificationController = new MagnificationController(mContext, mMockAms, new Object(),
- mMessageCapturingHandler, mMockWindowManager, mMockValueAnimator,
- mMockSettingsBridge) {
+ final MagnificationController.ControllerContext mockController =
+ mock(MagnificationController.ControllerContext.class);
+ final WindowManagerInternal mockWindowManager = mock(WindowManagerInternal.class);
+ when(mockController.getContext()).thenReturn(mContext);
+ when(mockController.getAms()).thenReturn(mock(AccessibilityManagerService.class));
+ when(mockController.getWindowManager()).thenReturn(mockWindowManager);
+ when(mockController.getHandler()).thenReturn(new Handler(mContext.getMainLooper()));
+ when(mockController.newValueAnimator()).thenReturn(new ValueAnimator());
+ when(mockController.getAnimationDuration()).thenReturn(1000L);
+ when(mockWindowManager.setMagnificationCallbacks(eq(DISPLAY_0), any())).thenReturn(true);
+ mMagnificationController = new MagnificationController(mockController, new Object()) {
@Override
- public boolean magnificationRegionContains(float x, float y) {
+ public boolean magnificationRegionContains(int displayId, float x, float y) {
return true;
}
@Override
- void setForceShowMagnifiableBounds(boolean show) {}
+ void setForceShowMagnifiableBounds(int displayId, boolean show) {}
};
- mMagnificationController.register();
+ mMagnificationController.register(DISPLAY_0);
mClock = new OffsettableClock.Stopped();
boolean detectTripleTap = true;
@@ -144,7 +150,7 @@
@After
public void tearDown() {
- mMagnificationController.unregister();
+ mMagnificationController.unregister(DISPLAY_0);
}
@NonNull
@@ -302,6 +308,24 @@
assertZoomsImmediatelyOnSwipeFrom(STATE_SHORTCUT_TRIGGERED);
}
+ @Test
+ public void testMultiTap_outOfDistanceSlop_shouldInIdle() {
+ // All delay motion events should be sent, if multi-tap with out of distance slop.
+ // STATE_IDLE will check if tapCount() < 2.
+ allowEventDelegation();
+ assertStaysIn(STATE_IDLE, () -> {
+ tap();
+ tap(DEFAULT_X * 2, DEFAULT_Y * 2);
+ });
+ assertStaysIn(STATE_IDLE, () -> {
+ tap();
+ tap(DEFAULT_X * 2, DEFAULT_Y * 2);
+ tap();
+ tap(DEFAULT_X * 2, DEFAULT_Y * 2);
+ tap();
+ });
+ }
+
private void assertZoomsImmediatelyOnSwipeFrom(int state) {
goFromStateIdleTo(state);
swipeAndHold();
@@ -509,7 +533,7 @@
}
private boolean isZoomed() {
- return mMgh.mMagnificationController.isMagnifying();
+ return mMgh.mMagnificationController.isMagnifying(DISPLAY_0);
}
private int tapCount() {
@@ -525,6 +549,11 @@
send(upEvent());
}
+ private void tap(float x, float y) {
+ send(downEvent(x, y));
+ send(upEvent(x, y));
+ }
+
private void swipe() {
swipeAndHold();
send(upEvent());
@@ -566,18 +595,26 @@
}
private MotionEvent downEvent() {
+ return downEvent(DEFAULT_X, DEFAULT_Y);
+ }
+
+ private MotionEvent downEvent(float x, float y) {
mLastDownTime = mClock.now();
return fromTouchscreen(MotionEvent.obtain(mLastDownTime, mLastDownTime,
- ACTION_DOWN, DEFAULT_X, DEFAULT_Y, 0));
+ ACTION_DOWN, x, y, 0));
}
private MotionEvent upEvent() {
- return upEvent(mLastDownTime);
+ return upEvent(DEFAULT_X, DEFAULT_Y, mLastDownTime);
}
- private MotionEvent upEvent(long downTime) {
+ private MotionEvent upEvent(float x, float y) {
+ return upEvent(x, y, mLastDownTime);
+ }
+
+ private MotionEvent upEvent(float x, float y, long downTime) {
return fromTouchscreen(MotionEvent.obtain(downTime, mClock.now(),
- MotionEvent.ACTION_UP, DEFAULT_X, DEFAULT_Y, 0));
+ MotionEvent.ACTION_UP, x, y, 0));
}
private MotionEvent pointerEvent(int action, float x, float y) {
diff --git a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
new file mode 100644
index 0000000..65af677
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
@@ -0,0 +1,497 @@
+/*
+ * 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.adb;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.server.FgThread;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(JUnit4.class)
+public final class AdbDebuggingManagerTest {
+
+ private static final String TAG = "AdbDebuggingManagerTest";
+
+ // This component is passed to the AdbDebuggingManager to act as the activity that can confirm
+ // unknown adb keys. An overlay package was first attempted to override the
+ // config_customAdbPublicKeyConfirmationComponent config, but the value from that package was
+ // not being read.
+ private static final String ADB_CONFIRM_COMPONENT =
+ "com.android.frameworks.servicestests/"
+ + "com.android.server.adb.AdbDebuggingManagerTestActivity";
+
+ // The base64 encoding of the values 'test key 1' and 'test key 2'.
+ private static final String TEST_KEY_1 = "dGVzdCBrZXkgMQo=";
+ private static final String TEST_KEY_2 = "dGVzdCBrZXkgMgo=";
+
+ // This response is received from the AdbDebuggingHandler when the key is allowed to connect
+ private static final String RESPONSE_KEY_ALLOWED = "OK";
+ // This response is received from the AdbDebuggingHandler when the key is not allowed to connect
+ private static final String RESPONSE_KEY_DENIED = "NO";
+
+ // wait up to 5 seconds for any blocking queries
+ private static final long TIMEOUT = 5000;
+ private static final TimeUnit TIMEOUT_TIME_UNIT = TimeUnit.MILLISECONDS;
+
+ private Context mContext;
+ private AdbDebuggingManager mManager;
+ private AdbDebuggingManager.AdbDebuggingThread mThread;
+ private AdbDebuggingManager.AdbDebuggingHandler mHandler;
+ private AdbDebuggingManager.AdbKeyStore mKeyStore;
+ private BlockingQueue<TestResult> mBlockingQueue;
+ private long mOriginalAllowedConnectionTime;
+ private File mKeyFile;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getContext();
+ mManager = new AdbDebuggingManager(mContext, ADB_CONFIRM_COMPONENT);
+ mKeyFile = new File(mContext.getFilesDir(), "test_adb_keys.xml");
+ if (mKeyFile.exists()) {
+ mKeyFile.delete();
+ }
+ mThread = new AdbDebuggingThreadTest();
+ mKeyStore = mManager.new AdbKeyStore(mKeyFile);
+ mHandler = mManager.new AdbDebuggingHandler(FgThread.get().getLooper(), mThread, mKeyStore);
+ mOriginalAllowedConnectionTime = mKeyStore.getAllowedConnectionTime();
+ mBlockingQueue = new ArrayBlockingQueue<>(1);
+
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mKeyStore.deleteKeyStore();
+ setAllowedConnectionTime(mOriginalAllowedConnectionTime);
+ }
+
+ /**
+ * Sets the allowed connection time within which a subsequent connection from a key for which
+ * the user selected the 'Always allow' option will be allowed without user interaction.
+ */
+ private void setAllowedConnectionTime(long connectionTime) {
+ Settings.Global.putLong(mContext.getContentResolver(),
+ Settings.Global.ADB_ALLOWED_CONNECTION_TIME, connectionTime);
+ };
+
+ @Test
+ public void testAllowNewKeyOnce() throws Exception {
+ // Verifies the behavior when a new key first attempts to connect to a device. During the
+ // first connection the ADB confirmation activity should be launched to prompt the user to
+ // allow the connection with an option to always allow connections from this key.
+
+ // Verify if the user allows the key but does not select the option to 'always
+ // allow' that the connection is allowed but the key is not stored.
+ runAdbTest(TEST_KEY_1, true, false, false);
+ }
+
+ @Test
+ public void testDenyNewKey() throws Exception {
+ // Verifies if the user does not allow the key then the connection is not allowed and the
+ // key is not stored.
+ runAdbTest(TEST_KEY_1, false, false, false);
+ }
+
+ @Test
+ public void testDisconnectAlwaysAllowKey() throws Exception {
+ // When a key is disconnected from a device ADB should send a disconnect message; this
+ // message should trigger an update of the last connection time for the currently connected
+ // key.
+
+ // Allow a connection from a new key with the 'Always allow' option selected.
+ runAdbTest(TEST_KEY_1, true, true, false);
+
+ // Get the last connection time for the currently connected key to verify that it is updated
+ // after the disconnect.
+ long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
+
+ // Sleep for a small amount of time to ensure a difference can be observed in the last
+ // connection time after a disconnect.
+ Thread.sleep(10);
+
+ // Send the disconnect message for the currently connected key to trigger an update of the
+ // last connection time.
+ mHandler.obtainMessage(
+ AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_DISCONNECT).sendToTarget();
+
+ // Use a latch to ensure the test does not exit untill the Runnable has been processed.
+ CountDownLatch latch = new CountDownLatch(1);
+
+ // Post a new Runnable to the handler to ensure it runs after the disconnect message is
+ // processed.
+ mHandler.post(() -> {
+ assertNotEquals(
+ "The last connection time was not updated after the disconnect",
+ lastConnectionTime,
+ mKeyStore.getLastConnectionTime(TEST_KEY_1));
+ latch.countDown();
+ });
+ if (!latch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) {
+ fail("The Runnable to verify the last connection time was updated did not complete "
+ + "within the timeout period");
+ }
+ }
+
+ @Test
+ public void testDisconnectAllowedOnceKey() throws Exception {
+ // When a key is disconnected ADB should send a disconnect message; this message should
+ // essentially result in a noop for keys that the user only allows once since the last
+ // connection time is not maintained for these keys.
+
+ // Allow a connection from a new key with the 'Always allow' option set to false
+ runAdbTest(TEST_KEY_1, true, false, false);
+
+ // Send the disconnect message for the currently connected key.
+ mHandler.obtainMessage(
+ AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_DISCONNECT).sendToTarget();
+
+ // Verify that the disconnected key is not automatically allowed on a subsequent connection.
+ runAdbTest(TEST_KEY_1, true, false, false);
+ }
+
+ @Test
+ public void testAlwaysAllowConnectionFromKey() throws Exception {
+ // Verifies when the user selects the 'Always allow' option for the current key that
+ // subsequent connection attempts from that key are allowed.
+
+ // Allow a connection from a new key with the 'Always allow' option selected.
+ runAdbTest(TEST_KEY_1, true, true, false);
+
+ // Next attempt another connection with the same key and verify that the activity to prompt
+ // the user to accept the key is not launched.
+ runAdbTest(TEST_KEY_1, true, true, true);
+
+ // Verify that a different key is not automatically allowed.
+ runAdbTest(TEST_KEY_2, false, false, false);
+ }
+
+ @Test
+ public void testOriginalAlwaysAllowBehavior() throws Exception {
+ // If the Settings.Global.ADB_ALLOWED_CONNECTION_TIME setting is set to 0 then the original
+ // behavior of 'Always allow' should be restored.
+
+ // Accept the test key with the 'Always allow' option selected.
+ runAdbTest(TEST_KEY_1, true, true, false);
+
+ // Set the connection time to 0 to restore the original behavior.
+ setAllowedConnectionTime(0);
+
+ // Set the last connection time to the test key to a very small value to ensure it would
+ // fail the new test but would be allowed with the original behavior.
+ mKeyStore.setLastConnectionTime(TEST_KEY_1, 1);
+
+ // Run the test with the key and verify that the connection is automatically allowed.
+ runAdbTest(TEST_KEY_1, true, true, true);
+ }
+
+ @Test
+ public void testLastConnectionTimeUpdatedByScheduledJob() throws Exception {
+ // If a development device is left connected to a system beyond the allowed connection time
+ // a reboot of the device while connected could make it appear as though the last connection
+ // time is beyond the allowed window. A scheduled job runs daily while a key is connected
+ // to update the last connection time to the current time; this ensures if the device is
+ // rebooted while connected to a system the last connection time should be within 24 hours.
+
+ // Allow the key to connect with the 'Always allow' option selected
+ runAdbTest(TEST_KEY_1, true, true, false);
+
+ // Get the current last connection time for comparison after the scheduled job is run
+ long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
+
+ // Sleep a small amount of time to ensure that the updated connection time changes
+ Thread.sleep(10);
+
+ // Send a message to the handler to update the last connection time for the active key
+ mHandler.obtainMessage(
+ AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME)
+ .sendToTarget();
+
+ // Post a Runnable to the handler to ensure it runs after the update key connection time
+ // message is processed.
+ CountDownLatch latch = new CountDownLatch(1);
+ mHandler.post(() -> {
+ assertNotEquals(
+ "The last connection time of the key was not updated after the update key "
+ + "connection time message",
+ lastConnectionTime, mKeyStore.getLastConnectionTime(TEST_KEY_1));
+ latch.countDown();
+ });
+ if (!latch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) {
+ fail("The Runnable to verify the last connection time was updated did not complete "
+ + "within the timeout period");
+ }
+ }
+
+ @Test
+ public void testKeystorePersisted() throws Exception {
+ // After any updates are made to the key store a message should be sent to persist the
+ // key store. This test verifies that a key that is always allowed is persisted in the key
+ // store along with its last connection time.
+
+ // Allow the key to connect with the 'Always allow' option selected
+ runAdbTest(TEST_KEY_1, true, true, false);
+
+ // Send a message to the handler to persist the updated keystore.
+ mHandler.obtainMessage(
+ AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_PERSIST_KEY_STORE)
+ .sendToTarget();
+
+ // Post a Runnable to the handler to ensure the persist key store message has been processed
+ // using a new AdbKeyStore backed by the key file.
+ mHandler.post(() -> assertTrue(
+ "The key with the 'Always allow' option selected was not persisted in the keystore",
+ mManager.new AdbKeyStore(mKeyFile).isKeyAuthorized(TEST_KEY_1)));
+
+ // Get the current last connection time to ensure it is updated in the persisted keystore.
+ long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
+
+ // Sleep a small amount of time to ensure the last connection time is updated.
+ Thread.sleep(10);
+
+ // Send a message to the handler to update the last connection time for the active key.
+ mHandler.obtainMessage(
+ AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME)
+ .sendToTarget();
+
+ // Persist the updated last connection time.
+ mHandler.obtainMessage(
+ AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_PERSIST_KEY_STORE)
+ .sendToTarget();
+
+ // Post a Runnable with a new key store backed by the key file to verify that the last
+ // connection time obtained above is different from the persisted updated value.
+ CountDownLatch latch = new CountDownLatch(1);
+ mHandler.post(() -> {
+ assertNotEquals(
+ "The last connection time in the key file was not updated after the update "
+ + "connection time message", lastConnectionTime,
+ mManager.new AdbKeyStore(mKeyFile).getLastConnectionTime(TEST_KEY_1));
+ latch.countDown();
+ });
+ if (!latch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) {
+ fail("The Runnable to verify the last connection time was updated did not complete "
+ + "within the timeout period");
+ }
+ }
+
+ @Test
+ public void testAdbClearRemovesActiveKey() throws Exception {
+ // If the user selects the option to 'Revoke USB debugging authorizations' while an 'Always
+ // allow' key is connected that key should be deleted as well.
+
+ // Allow the key to connect with the 'Always allow' option selected
+ runAdbTest(TEST_KEY_1, true, true, false);
+
+ // Send a message to the handler to clear the adb authorizations.
+ mHandler.obtainMessage(
+ AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_CLEAR).sendToTarget();
+
+ // Send a message to disconnect the currently connected key
+ mHandler.obtainMessage(
+ AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_DISCONNECT).sendToTarget();
+
+ // Post a Runnable to ensure the disconnect has completed to verify the 'Always allow' key
+ // that was connected when the clear was sent requires authorization.
+ CountDownLatch latch = new CountDownLatch(1);
+ mHandler.post(() -> {
+ assertFalse(
+ "The currently connected 'always allow' key should not be authorized after an"
+ + " adb"
+ + " clear message.",
+ mKeyStore.isKeyAuthorized(TEST_KEY_1));
+ latch.countDown();
+ });
+ if (!latch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) {
+ fail("The Runnable to verify the key is not authorized did not complete within the "
+ + "timeout period");
+ }
+ }
+
+ @Test
+ public void testAdbGrantRevokedIfLastConnectionBeyondAllowedTime() throws Exception {
+ // If the user selects the 'Always allow' option then subsequent connections from the key
+ // will be allowed as long as the connection is within the allowed window. Once the last
+ // connection time is beyond this window the user should be prompted to allow the key again.
+
+ // Allow the key to connect with the 'Always allow' option selected
+ runAdbTest(TEST_KEY_1, true, true, false);
+
+ // Set the allowed window to a small value to ensure the time is beyond the allowed window.
+ setAllowedConnectionTime(1);
+
+ // Sleep for a small amount of time to exceed the allowed window.
+ Thread.sleep(10);
+
+ // A new connection from this key should prompt the user again.
+ runAdbTest(TEST_KEY_1, true, true, false);
+ }
+
+ @Test
+ public void testLastConnectionTimeCannotBeSetBack() throws Exception {
+ // When a device is first booted there is a possibility that the system time will be set to
+ // the build time of the system image. If a device is connected to a system during a reboot
+ // this could cause the connection time to be set in the past; if the device time is not
+ // corrected before the device is disconnected then a subsequent connection with the time
+ // corrected would appear as though the last connection time was beyond the allowed window,
+ // and the user would be required to authorize the connection again. This test verifies that
+ // the AdbKeyStore does not update the last connection time if it is less than the
+ // previously written connection time.
+
+ // Allow the key to connect with the 'Always allow' option selected
+ runAdbTest(TEST_KEY_1, true, true, false);
+
+ // Get the last connection time that was written to the key store.
+ long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
+
+ // Attempt to set the last connection time to 1970
+ mKeyStore.setLastConnectionTime(TEST_KEY_1, 0);
+ assertEquals(
+ "The last connection time in the adb key store should not be set to a value less "
+ + "than the previous connection time",
+ lastConnectionTime, mKeyStore.getLastConnectionTime(TEST_KEY_1));
+
+ // Attempt to set the last connection time just beyond the allowed window.
+ mKeyStore.setLastConnectionTime(TEST_KEY_1,
+ Math.max(0, lastConnectionTime - (mKeyStore.getAllowedConnectionTime() + 1)));
+ assertEquals(
+ "The last connection time in the adb key store should not be set to a value less "
+ + "than the previous connection time",
+ lastConnectionTime, mKeyStore.getLastConnectionTime(TEST_KEY_1));
+ }
+
+ /**
+ * Runs an adb test with the provided configuration.
+ *
+ * @param key The base64 encoding of the key to be used during the test.
+ * @param allowKey boolean indicating whether the key should be allowed to connect.
+ * @param alwaysAllow boolean indicating whether the 'Always allow' option should be selected.
+ * @param autoAllowExpected boolean indicating whether the key is expected to be automatically
+ * allowed without user interaction.
+ */
+ private void runAdbTest(String key, boolean allowKey, boolean alwaysAllow,
+ boolean autoAllowExpected) throws Exception {
+ // if the key should not be automatically allowed then set up the activity
+ if (!autoAllowExpected) {
+ new AdbDebuggingManagerTestActivity.Configurator()
+ .setExpectedKey(key)
+ .setAllowKey(allowKey)
+ .setAlwaysAllow(alwaysAllow)
+ .setHandler(mHandler)
+ .setBlockingQueue(mBlockingQueue);
+ }
+ // send the message indicating a new key is attempting to connect
+ mHandler.obtainMessage(AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_CONFIRM,
+ key).sendToTarget();
+ // if the key should not be automatically allowed then the ADB public key confirmation
+ // activity should be launched
+ if (!autoAllowExpected) {
+ TestResult activityResult = mBlockingQueue.poll(TIMEOUT, TIMEOUT_TIME_UNIT);
+ assertNotNull(
+ "The ADB public key confirmation activity did not complete within the timeout"
+ + " period", activityResult);
+ assertEquals("The ADB public key activity failed with result: " + activityResult,
+ TestResult.RESULT_ACTIVITY_LAUNCHED, activityResult.mReturnCode);
+ }
+ // If the activity was launched it should send a response back to the manager that would
+ // trigger a response to the thread, or if the key is a known valid key then a response
+ // should be sent back without requiring interaction with the activity.
+ TestResult threadResult = mBlockingQueue.poll(TIMEOUT, TIMEOUT_TIME_UNIT);
+ assertNotNull("A response was not sent to the thread within the timeout period",
+ threadResult);
+ // verify that the result is an expected message from the thread
+ assertEquals("An unexpected result was received: " + threadResult,
+ TestResult.RESULT_RESPONSE_RECEIVED, threadResult.mReturnCode);
+ assertEquals("The manager did not send the proper response for allowKey = " + allowKey,
+ allowKey ? RESPONSE_KEY_ALLOWED : RESPONSE_KEY_DENIED, threadResult.mMessage);
+ // if the key is not allowed or not always allowed verify it is not in the key store
+ if (!allowKey || !alwaysAllow) {
+ assertFalse(
+ "The key should not be allowed automatically on subsequent connection attempts",
+ mKeyStore.isKeyAuthorized(key));
+ }
+ }
+
+ /**
+ * Helper class that extends AdbDebuggingThread to receive the response from AdbDebuggingManager
+ * indicating whether the key should be allowed to connect.
+ */
+ class AdbDebuggingThreadTest extends AdbDebuggingManager.AdbDebuggingThread {
+ AdbDebuggingThreadTest() {
+ mManager.super();
+ }
+
+ @Override
+ public void sendResponse(String msg) {
+ TestResult result = new TestResult(TestResult.RESULT_RESPONSE_RECEIVED, msg);
+ try {
+ mBlockingQueue.put(result);
+ } catch (InterruptedException e) {
+ Log.e(TAG,
+ "Caught an InterruptedException putting the result in the queue: " + result,
+ e);
+ }
+ }
+ }
+
+ /**
+ * Contains the result for the current portion of the test along with any corresponding
+ * messages.
+ */
+ public static class TestResult {
+ public int mReturnCode;
+ public String mMessage;
+
+ public static final int RESULT_ACTIVITY_LAUNCHED = 1;
+ public static final int RESULT_UNEXPECTED_KEY = 2;
+ public static final int RESULT_RESPONSE_RECEIVED = 3;
+
+ public TestResult(int returnCode) {
+ this(returnCode, null);
+ }
+
+ public TestResult(int returnCode, String message) {
+ mReturnCode = returnCode;
+ mMessage = message;
+ }
+
+ @Override
+ public String toString() {
+ return "{mReturnCode = " + mReturnCode + ", mMessage = " + mMessage + "}";
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTestActivity.java b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTestActivity.java
new file mode 100644
index 0000000..1a9c180
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTestActivity.java
@@ -0,0 +1,142 @@
+/*
+ * 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.adb;
+
+import static com.android.server.adb.AdbDebuggingManagerTest.TestResult;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Message;
+import android.util.Log;
+
+import java.util.concurrent.BlockingQueue;
+
+/**
+ * Helper Activity used to test the AdbDebuggingManager's prompt to allow an adb key.
+ */
+public class AdbDebuggingManagerTestActivity extends Activity {
+
+ private static final String TAG = "AdbDebuggingManagerTestActivity";
+
+ /*
+ * Static values that must be set before each test to modify the behavior of the Activity.
+ */
+ private static AdbDebuggingManager.AdbDebuggingHandler sHandler;
+ private static boolean sAllowKey;
+ private static boolean sAlwaysAllow;
+ private static String sExpectedKey;
+ private static BlockingQueue sBlockingQueue;
+
+ /**
+ * Receives the Intent sent from the AdbDebuggingManager and sends the preconfigured response.
+ */
+ @Override
+ public void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ Intent intent = getIntent();
+ String key = intent.getStringExtra("key");
+ if (!key.equals(sExpectedKey)) {
+ TestResult result = new TestResult(TestResult.RESULT_UNEXPECTED_KEY, key);
+ postResult(result);
+ finish();
+ return;
+ }
+ // Post the result that the activity was successfully launched as expected and a response
+ // is being sent to let the test method know that it should move on to waiting for the next
+ // expected response from the AdbDebuggingManager.
+ TestResult result = new TestResult(
+ AdbDebuggingManagerTest.TestResult.RESULT_ACTIVITY_LAUNCHED);
+ postResult(result);
+
+ // Initialize the message based on the preconfigured values. If the key is accepted the
+ // AdbDebuggingManager expects the key to be in the obj field of the message, and if the
+ // user selects the 'Always allow' option the manager expects the arg1 field to be set to 1.
+ int messageType;
+ if (sAllowKey) {
+ messageType = AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_ALLOW;
+ } else {
+ messageType = AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_DENY;
+ }
+ Message message = sHandler.obtainMessage(messageType);
+ message.obj = key;
+ if (sAlwaysAllow) {
+ message.arg1 = 1;
+ }
+ finish();
+ sHandler.sendMessage(message);
+ }
+
+ /**
+ * Posts the result of the activity to the test method.
+ */
+ private void postResult(TestResult result) {
+ try {
+ sBlockingQueue.put(result);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Caught an InterruptedException posting the result " + result, e);
+ }
+ }
+
+ /**
+ * Allows test methods to specify the behavior of the Activity before it is invoked by the
+ * AdbDebuggingManager.
+ */
+ public static class Configurator {
+
+ /**
+ * Sets the test handler to be used by this activity to send the configured response.
+ */
+ public Configurator setHandler(AdbDebuggingManager.AdbDebuggingHandler handler) {
+ sHandler = handler;
+ return this;
+ }
+
+ /**
+ * Sets whether the key should be allowed for this test.
+ */
+ public Configurator setAllowKey(boolean allow) {
+ sAllowKey = allow;
+ return this;
+ }
+
+ /**
+ * Sets whether the 'Always allow' option should be selected for this test.
+ */
+ public Configurator setAlwaysAllow(boolean alwaysAllow) {
+ sAlwaysAllow = alwaysAllow;
+ return this;
+ }
+
+ /**
+ * Sets the key that should be expected from the AdbDebuggingManager for this test.
+ */
+ public Configurator setExpectedKey(String expectedKey) {
+ sExpectedKey = expectedKey;
+ return this;
+ }
+
+ /**
+ * Sets the BlockingQueue that should be used to post the result of the Activity back to the
+ * test method.
+ */
+ public Configurator setBlockingQueue(BlockingQueue blockingQueue) {
+ sBlockingQueue = blockingQueue;
+ return this;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
index 6a9a121..ac4a5fe 100644
--- a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
@@ -25,7 +25,6 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@@ -48,6 +47,7 @@
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
+import android.util.SparseArray;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -90,17 +90,30 @@
};
private static final int NON_USER_SYSTEM = UserHandle.USER_SYSTEM + 1;
- @UserIdInt private int mUserId;
- @Mock private BackupManagerService mBackupManagerServiceMock;
- @Mock private Context mContextMock;
- @Mock private File mSuppressFileMock;
- @Mock private File mSuppressFileParentMock;
- @Mock private IBinder mAgentMock;
- @Mock private ParcelFileDescriptor mParcelFileDescriptorMock;
- @Mock private IFullBackupRestoreObserver mFullBackupRestoreObserverMock;
- @Mock private IBackupObserver mBackupObserverMock;
- @Mock private IBackupManagerMonitor mBackupManagerMonitorMock;
- @Mock private PrintWriter mPrintWriterMock;
+ @UserIdInt
+ private int mUserId;
+ @Mock
+ private BackupManagerService mBackupManagerServiceMock;
+ @Mock
+ private UserBackupManagerService mUserBackupManagerService;
+ @Mock
+ private Context mContextMock;
+ @Mock
+ private File mSuppressFileMock;
+ @Mock
+ private File mSuppressFileParentMock;
+ @Mock
+ private IBinder mAgentMock;
+ @Mock
+ private ParcelFileDescriptor mParcelFileDescriptorMock;
+ @Mock
+ private IFullBackupRestoreObserver mFullBackupRestoreObserverMock;
+ @Mock
+ private IBackupObserver mBackupObserverMock;
+ @Mock
+ private IBackupManagerMonitor mBackupManagerMonitorMock;
+ @Mock
+ private PrintWriter mPrintWriterMock;
private FileDescriptor mFileDescriptorStub = new FileDescriptor();
@@ -110,16 +123,20 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ mUserId = NON_USER_SYSTEM;
+
+ SparseArray<UserBackupManagerService> serviceUsers = new SparseArray<>();
+ serviceUsers.append(UserHandle.SYSTEM.getIdentifier(), mUserBackupManagerService);
+ serviceUsers.append(NON_USER_SYSTEM, mUserBackupManagerService);
+ when(mBackupManagerServiceMock.getServiceUsers()).thenReturn(serviceUsers);
TrampolineTestable.sBackupManagerServiceMock = mBackupManagerServiceMock;
- TrampolineTestable.sSuppressFile = mSuppressFileMock;
TrampolineTestable.sCallingUserId = UserHandle.USER_SYSTEM;
TrampolineTestable.sCallingUid = Process.SYSTEM_UID;
TrampolineTestable.sBackupDisabled = false;
when(mSuppressFileMock.getParentFile()).thenReturn(mSuppressFileParentMock);
- mUserId = NON_USER_SYSTEM;
mTrampoline = new TrampolineTestable(mContextMock);
mContentResolver = new MockContentResolver();
@@ -128,11 +145,6 @@
}
@Test
- public void constructor_createsSuppressFileDirectory() {
- verify(mSuppressFileParentMock).mkdirs();
- }
-
- @Test
public void unlockUser_whenMultiUserSettingDisabled_callsBackupManagerServiceForSystemUser() {
Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0);
mTrampoline.initializeService();
@@ -147,9 +159,9 @@
Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0);
mTrampoline.initializeService();
- mTrampoline.unlockUser(10);
+ mTrampoline.unlockUser(NON_USER_SYSTEM);
- verify(mBackupManagerServiceMock, never()).startServiceForUser(10);
+ verify(mBackupManagerServiceMock, never()).startServiceForUser(NON_USER_SYSTEM);
}
@Test
@@ -157,9 +169,9 @@
Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 1);
mTrampoline.initializeService();
- mTrampoline.unlockUser(10);
+ mTrampoline.unlockUser(NON_USER_SYSTEM);
- verify(mBackupManagerServiceMock).startServiceForUser(10);
+ verify(mBackupManagerServiceMock).startServiceForUser(NON_USER_SYSTEM);
}
@Test
@@ -177,19 +189,20 @@
Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0);
mTrampoline.initializeService();
- mTrampoline.stopUser(10);
+ mTrampoline.stopUser(NON_USER_SYSTEM);
- verify(mBackupManagerServiceMock, never()).stopServiceForUser(10);
+ verify(mBackupManagerServiceMock, never()).stopServiceForUser(NON_USER_SYSTEM);
}
@Test
public void stopUser_whenMultiUserSettingEnabled_callsBackupManagerServiceForNonSystemUser() {
Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 1);
+
mTrampoline.initializeService();
- mTrampoline.stopUser(10);
+ mTrampoline.stopUser(NON_USER_SYSTEM);
- verify(mBackupManagerServiceMock).stopServiceForUser(10);
+ verify(mBackupManagerServiceMock).stopServiceForUser(NON_USER_SYSTEM);
}
@Test
@@ -211,9 +224,10 @@
// Verify that BackupManagerService is not initialized if suppress file exists.
@Test
- public void initializeService_suppressFileExists_nonInitialized() {
- when(mSuppressFileMock.exists()).thenReturn(true);
+ public void initializeService_suppressFileExists_nonInitialized() throws Exception {
TrampolineTestable trampoline = new TrampolineTestable(mContextMock);
+ trampoline.createBackupSuppressFileForUser(UserHandle.USER_SYSTEM);
+
trampoline.initializeService();
@@ -233,6 +247,14 @@
}
@Test
+ public void isBackupServiceActive_forNonSysUser_whenSysUserIsDeactivated_returnsFalse() {
+ mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
+
+ assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
+ }
+
+ @Test
public void setBackupServiceActive_callerSystemUid_serviceCreated() {
TrampolineTestable.sCallingUid = Process.SYSTEM_UID;
@@ -292,11 +314,18 @@
}
@Test
+ public void setBackupServiceActive_makeNonActive_alreadyNonActive_ignored() {
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
+
+ assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ }
+
+ @Test
public void setBackupServiceActive_makeActive_serviceCreatedAndSuppressFileDeleted() {
mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
- verify(mSuppressFileMock).delete();
}
@Test
@@ -308,40 +337,25 @@
mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
- verify(mSuppressFileMock).createNewFile();
}
@Test
- public void
- setBackupServiceActive_makeNonActive_serviceDeletedAndSuppressFileCreated_ioExceptionHandled()
- throws IOException {
- when(mSuppressFileMock.createNewFile()).thenThrow(new IOException());
+ public void setBackupActive_nonSystemUser_disabledForSystemUser_ignored() {
mTrampoline.initializeService();
- assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
-
mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
+ mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
- assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
- verify(mSuppressFileMock).createNewFile();
+ assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
}
@Test
- public void setBackupServiceActive_makeNonActive_alreadyNonActive_ignored() throws IOException {
- reset(mSuppressFileMock);
-
- mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
-
- verifyNoMoreInteractions(mSuppressFileMock);
- }
-
- @Test
- public void dataChanged_calledBeforeInitialize_ignored() throws RemoteException {
+ public void dataChanged_calledBeforeInitialize_ignored() throws Exception {
mTrampoline.dataChanged(PACKAGE_NAME);
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void dataChangedForUser_forwarded() throws RemoteException {
+ public void dataChangedForUser_forwarded() throws Exception {
mTrampoline.initializeService();
mTrampoline.dataChangedForUser(mUserId, PACKAGE_NAME);
@@ -350,7 +364,7 @@
}
@Test
- public void dataChanged_forwarded() throws RemoteException {
+ public void dataChanged_forwarded() throws Exception {
TrampolineTestable.sCallingUserId = mUserId;
mTrampoline.initializeService();
@@ -360,13 +374,13 @@
}
@Test
- public void clearBackupData_calledBeforeInitialize_ignored() throws RemoteException {
+ public void clearBackupData_calledBeforeInitialize_ignored() throws Exception {
mTrampoline.clearBackupData(TRANSPORT_NAME, PACKAGE_NAME);
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void clearBackupDataForUser_forwarded() throws RemoteException {
+ public void clearBackupDataForUser_forwarded() throws Exception {
mTrampoline.initializeService();
mTrampoline.clearBackupDataForUser(mUserId, TRANSPORT_NAME, PACKAGE_NAME);
@@ -375,7 +389,7 @@
}
@Test
- public void clearBackupData_forwarded() throws RemoteException {
+ public void clearBackupData_forwarded() throws Exception {
TrampolineTestable.sCallingUserId = mUserId;
mTrampoline.initializeService();
@@ -385,13 +399,13 @@
}
@Test
- public void agentConnected_calledBeforeInitialize_ignored() throws RemoteException {
+ public void agentConnected_calledBeforeInitialize_ignored() throws Exception {
mTrampoline.agentConnected(PACKAGE_NAME, mAgentMock);
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void agentConnectedForUser_forwarded() throws RemoteException {
+ public void agentConnectedForUser_forwarded() throws Exception {
mTrampoline.initializeService();
mTrampoline.agentConnectedForUser(mUserId, PACKAGE_NAME, mAgentMock);
@@ -400,7 +414,7 @@
}
@Test
- public void agentConnected_forwarded() throws RemoteException {
+ public void agentConnected_forwarded() throws Exception {
TrampolineTestable.sCallingUserId = mUserId;
mTrampoline.initializeService();
@@ -410,13 +424,13 @@
}
@Test
- public void agentDisconnected_calledBeforeInitialize_ignored() throws RemoteException {
+ public void agentDisconnected_calledBeforeInitialize_ignored() throws Exception {
mTrampoline.agentDisconnected(PACKAGE_NAME);
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void agentDisconnectedForUser_forwarded() throws RemoteException {
+ public void agentDisconnectedForUser_forwarded() throws Exception {
mTrampoline.initializeService();
mTrampoline.agentDisconnectedForUser(mUserId, PACKAGE_NAME);
@@ -425,7 +439,7 @@
}
@Test
- public void agentDisconnected_forwarded() throws RemoteException {
+ public void agentDisconnected_forwarded() throws Exception {
TrampolineTestable.sCallingUserId = mUserId;
mTrampoline.initializeService();
@@ -435,13 +449,13 @@
}
@Test
- public void restoreAtInstall_calledBeforeInitialize_ignored() throws RemoteException {
+ public void restoreAtInstall_calledBeforeInitialize_ignored() throws Exception {
mTrampoline.restoreAtInstall(PACKAGE_NAME, 123);
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void restoreAtInstallForUser_forwarded() throws RemoteException {
+ public void restoreAtInstallForUser_forwarded() throws Exception {
mTrampoline.initializeService();
mTrampoline.restoreAtInstallForUser(mUserId, PACKAGE_NAME, 123);
@@ -450,7 +464,7 @@
}
@Test
- public void restoreAtInstall_forwarded() throws RemoteException {
+ public void restoreAtInstall_forwarded() throws Exception {
TrampolineTestable.sCallingUserId = mUserId;
mTrampoline.initializeService();
@@ -460,13 +474,13 @@
}
@Test
- public void setBackupEnabled_calledBeforeInitialize_ignored() throws RemoteException {
+ public void setBackupEnabled_calledBeforeInitialize_ignored() throws Exception {
mTrampoline.setBackupEnabled(true);
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void setBackupEnabledForUser_forwarded() throws RemoteException {
+ public void setBackupEnabledForUser_forwarded() throws Exception {
mTrampoline.initializeService();
mTrampoline.setBackupEnabledForUser(mUserId, true);
@@ -475,7 +489,7 @@
}
@Test
- public void setBackupEnabled_forwardedToCallingUserId() throws RemoteException {
+ public void setBackupEnabled_forwardedToCallingUserId() throws Exception {
TrampolineTestable.sCallingUserId = mUserId;
mTrampoline.initializeService();
@@ -485,13 +499,13 @@
}
@Test
- public void setAutoRestore_calledBeforeInitialize_ignored() throws RemoteException {
+ public void setAutoRestore_calledBeforeInitialize_ignored() throws Exception {
mTrampoline.setAutoRestore(true);
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void setAutoRestoreForUser_forwarded() throws RemoteException {
+ public void setAutoRestoreForUser_forwarded() throws Exception {
mTrampoline.initializeService();
mTrampoline.setAutoRestoreForUser(mUserId, true);
@@ -500,7 +514,7 @@
}
@Test
- public void setAutoRestore_forwarded() throws RemoteException {
+ public void setAutoRestore_forwarded() throws Exception {
TrampolineTestable.sCallingUserId = mUserId;
mTrampoline.initializeService();
@@ -510,13 +524,13 @@
}
@Test
- public void isBackupEnabled_calledBeforeInitialize_ignored() throws RemoteException {
+ public void isBackupEnabled_calledBeforeInitialize_ignored() throws Exception {
assertFalse(mTrampoline.isBackupEnabled());
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void isBackupEnabledForUser_forwarded() throws RemoteException {
+ public void isBackupEnabledForUser_forwarded() throws Exception {
mTrampoline.initializeService();
mTrampoline.isBackupEnabledForUser(mUserId);
@@ -525,7 +539,7 @@
}
@Test
- public void isBackupEnabled_forwardedToCallingUserId() throws RemoteException {
+ public void isBackupEnabled_forwardedToCallingUserId() throws Exception {
TrampolineTestable.sCallingUserId = mUserId;
mTrampoline.initializeService();
@@ -535,39 +549,39 @@
}
@Test
- public void setBackupPassword_calledBeforeInitialize_ignored() throws RemoteException {
+ public void setBackupPassword_calledBeforeInitialize_ignored() throws Exception {
mTrampoline.setBackupPassword(CURRENT_PASSWORD, NEW_PASSWORD);
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void setBackupPassword_forwarded() throws RemoteException {
+ public void setBackupPassword_forwarded() throws Exception {
mTrampoline.initializeService();
mTrampoline.setBackupPassword(CURRENT_PASSWORD, NEW_PASSWORD);
verify(mBackupManagerServiceMock).setBackupPassword(CURRENT_PASSWORD, NEW_PASSWORD);
}
@Test
- public void hasBackupPassword_calledBeforeInitialize_ignored() throws RemoteException {
+ public void hasBackupPassword_calledBeforeInitialize_ignored() throws Exception {
assertFalse(mTrampoline.hasBackupPassword());
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void hasBackupPassword_forwarded() throws RemoteException {
+ public void hasBackupPassword_forwarded() throws Exception {
mTrampoline.initializeService();
mTrampoline.hasBackupPassword();
verify(mBackupManagerServiceMock).hasBackupPassword();
}
@Test
- public void backupNow_calledBeforeInitialize_ignored() throws RemoteException {
+ public void backupNow_calledBeforeInitialize_ignored() throws Exception {
mTrampoline.backupNow();
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void backupNowForUser_forwarded() throws RemoteException {
+ public void backupNowForUser_forwarded() throws Exception {
mTrampoline.initializeService();
mTrampoline.backupNowForUser(mUserId);
@@ -576,7 +590,7 @@
}
@Test
- public void backupNow_forwardedToCallingUserId() throws RemoteException {
+ public void backupNow_forwardedToCallingUserId() throws Exception {
TrampolineTestable.sCallingUserId = mUserId;
mTrampoline.initializeService();
@@ -586,7 +600,7 @@
}
@Test
- public void adbBackup_calledBeforeInitialize_ignored() throws RemoteException {
+ public void adbBackup_calledBeforeInitialize_ignored() throws Exception {
mTrampoline.adbBackup(mUserId, mParcelFileDescriptorMock, true, true,
true, true, true, true, true, true,
PACKAGE_NAMES);
@@ -594,7 +608,7 @@
}
@Test
- public void adbBackup_forwarded() throws RemoteException {
+ public void adbBackup_forwarded() throws Exception {
mTrampoline.initializeService();
mTrampoline.adbBackup(mUserId, mParcelFileDescriptorMock, true, true,
true, true, true, true, true, true,
@@ -604,13 +618,13 @@
}
@Test
- public void fullTransportBackup_calledBeforeInitialize_ignored() throws RemoteException {
+ public void fullTransportBackup_calledBeforeInitialize_ignored() throws Exception {
mTrampoline.fullTransportBackupForUser(mUserId, PACKAGE_NAMES);
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void fullTransportBackupForUser_forwarded() throws RemoteException {
+ public void fullTransportBackupForUser_forwarded() throws Exception {
mTrampoline.initializeService();
mTrampoline.fullTransportBackupForUser(mUserId, PACKAGE_NAMES);
@@ -619,13 +633,13 @@
}
@Test
- public void adbRestore_calledBeforeInitialize_ignored() throws RemoteException {
+ public void adbRestore_calledBeforeInitialize_ignored() throws Exception {
mTrampoline.adbRestore(mUserId, mParcelFileDescriptorMock);
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void adbRestore_forwarded() throws RemoteException {
+ public void adbRestore_forwarded() throws Exception {
mTrampoline.initializeService();
mTrampoline.adbRestore(mUserId, mParcelFileDescriptorMock);
verify(mBackupManagerServiceMock).adbRestore(mUserId, mParcelFileDescriptorMock);
@@ -633,14 +647,14 @@
@Test
public void acknowledgeFullBackupOrRestore_calledBeforeInitialize_ignored()
- throws RemoteException {
+ throws Exception {
mTrampoline.acknowledgeFullBackupOrRestore(123, true, CURRENT_PASSWORD, ENCRYPTION_PASSWORD,
mFullBackupRestoreObserverMock);
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void acknowledgeFullBackupOrRestoreForUser_forwarded() throws RemoteException {
+ public void acknowledgeFullBackupOrRestoreForUser_forwarded() throws Exception {
mTrampoline.initializeService();
mTrampoline.acknowledgeFullBackupOrRestoreForUser(
@@ -662,7 +676,7 @@
}
@Test
- public void acknowledgeFullBackupOrRestore_forwarded() throws RemoteException {
+ public void acknowledgeFullBackupOrRestore_forwarded() throws Exception {
TrampolineTestable.sCallingUserId = mUserId;
mTrampoline.initializeService();
@@ -680,13 +694,13 @@
}
@Test
- public void getCurrentTransport_calledBeforeInitialize_ignored() throws RemoteException {
+ public void getCurrentTransport_calledBeforeInitialize_ignored() throws Exception {
assertNull(mTrampoline.getCurrentTransport());
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void getCurrentTransportForUser_forwarded() throws RemoteException {
+ public void getCurrentTransportForUser_forwarded() throws Exception {
when(mBackupManagerServiceMock.getCurrentTransport(mUserId)).thenReturn(TRANSPORT_NAME);
mTrampoline.initializeService();
@@ -695,7 +709,7 @@
}
@Test
- public void getCurrentTransport_forwarded() throws RemoteException {
+ public void getCurrentTransport_forwarded() throws Exception {
TrampolineTestable.sCallingUserId = mUserId;
when(mBackupManagerServiceMock.getCurrentTransport(mUserId)).thenReturn(TRANSPORT_NAME);
mTrampoline.initializeService();
@@ -705,13 +719,13 @@
}
@Test
- public void listAllTransports_calledBeforeInitialize_ignored() throws RemoteException {
+ public void listAllTransports_calledBeforeInitialize_ignored() throws Exception {
assertNull(mTrampoline.listAllTransports());
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void listAllTransportsForUser_forwarded() throws RemoteException {
+ public void listAllTransportsForUser_forwarded() throws Exception {
when(mBackupManagerServiceMock.listAllTransports(mUserId)).thenReturn(TRANSPORTS);
mTrampoline.initializeService();
@@ -721,7 +735,7 @@
@Test
- public void listAllTransports_forwarded() throws RemoteException {
+ public void listAllTransports_forwarded() throws Exception {
TrampolineTestable.sCallingUserId = mUserId;
when(mBackupManagerServiceMock.listAllTransports(mUserId)).thenReturn(TRANSPORTS);
mTrampoline.initializeService();
@@ -732,13 +746,13 @@
@Test
public void listAllTransportComponentsForUser_calledBeforeInitialize_ignored()
- throws RemoteException {
+ throws Exception {
assertNull(mTrampoline.listAllTransportComponentsForUser(mUserId));
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void listAllTransportComponentsForUser_forwarded() throws RemoteException {
+ public void listAllTransportComponentsForUser_forwarded() throws Exception {
when(mBackupManagerServiceMock.listAllTransportComponents(mUserId)).thenReturn(
TRANSPORT_COMPONENTS);
mTrampoline.initializeService();
@@ -748,13 +762,13 @@
}
@Test
- public void getTransportWhitelist_calledBeforeInitialize_ignored() throws RemoteException {
+ public void getTransportWhitelist_calledBeforeInitialize_ignored() throws Exception {
assertNull(mTrampoline.getTransportWhitelist());
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void getTransportWhitelist_forwarded() throws RemoteException {
+ public void getTransportWhitelist_forwarded() {
when(mBackupManagerServiceMock.getTransportWhitelist()).thenReturn(TRANSPORTS);
mTrampoline.initializeService();
@@ -763,8 +777,7 @@
}
@Test
- public void updateTransportAttributesForUser_calledBeforeInitialize_ignored()
- throws RemoteException {
+ public void updateTransportAttributesForUser_calledBeforeInitialize_ignored() {
mTrampoline.updateTransportAttributesForUser(
mUserId,
TRANSPORT_COMPONENT_NAME,
@@ -778,7 +791,7 @@
}
@Test
- public void updateTransportAttributesForUser_forwarded() throws RemoteException {
+ public void updateTransportAttributesForUser_forwarded() {
when(mBackupManagerServiceMock.getTransportWhitelist()).thenReturn(TRANSPORTS);
mTrampoline.initializeService();
@@ -809,7 +822,7 @@
}
@Test
- public void selectBackupTransportForUser_forwarded() throws RemoteException {
+ public void selectBackupTransportForUser_forwarded() throws Exception {
mTrampoline.initializeService();
mTrampoline.selectBackupTransportForUser(mUserId, TRANSPORT_NAME);
@@ -818,7 +831,7 @@
}
@Test
- public void selectBackupTransport_forwarded() throws RemoteException {
+ public void selectBackupTransport_forwarded() throws Exception {
TrampolineTestable.sCallingUserId = mUserId;
mTrampoline.initializeService();
@@ -895,7 +908,7 @@
}
@Test
- public void selectBackupTransportAsyncForUser_forwarded() throws RemoteException {
+ public void selectBackupTransportAsyncForUser_forwarded() throws Exception {
mTrampoline.initializeService();
mTrampoline.selectBackupTransportAsyncForUser(mUserId, TRANSPORT_COMPONENT_NAME, null);
@@ -905,13 +918,13 @@
}
@Test
- public void getConfigurationIntent_calledBeforeInitialize_ignored() throws RemoteException {
+ public void getConfigurationIntent_calledBeforeInitialize_ignored() throws Exception {
mTrampoline.getConfigurationIntent(TRANSPORT_NAME);
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void getConfigurationIntentForUser_forwarded() throws RemoteException {
+ public void getConfigurationIntentForUser_forwarded() throws Exception {
Intent configurationIntentStub = new Intent();
when(mBackupManagerServiceMock.getConfigurationIntent(mUserId, TRANSPORT_NAME)).thenReturn(
configurationIntentStub);
@@ -924,7 +937,7 @@
}
@Test
- public void getConfigurationIntent_forwarded() throws RemoteException {
+ public void getConfigurationIntent_forwarded() throws Exception {
TrampolineTestable.sCallingUserId = mUserId;
Intent configurationIntentStub = new Intent();
when(mBackupManagerServiceMock.getConfigurationIntent(mUserId, TRANSPORT_NAME)).thenReturn(
@@ -936,13 +949,13 @@
}
@Test
- public void getDestinationString_calledBeforeInitialize_ignored() throws RemoteException {
+ public void getDestinationString_calledBeforeInitialize_ignored() throws Exception {
assertNull(mTrampoline.getDestinationString(TRANSPORT_NAME));
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void getDestinationStringForUser_forwarded() throws RemoteException {
+ public void getDestinationStringForUser_forwarded() throws Exception {
when(mBackupManagerServiceMock.getDestinationString(mUserId, TRANSPORT_NAME)).thenReturn(
DESTINATION_STRING);
mTrampoline.initializeService();
@@ -954,7 +967,7 @@
}
@Test
- public void getDestinationString_forwarded() throws RemoteException {
+ public void getDestinationString_forwarded() throws Exception {
TrampolineTestable.sCallingUserId = mUserId;
when(mBackupManagerServiceMock.getDestinationString(mUserId, TRANSPORT_NAME)).thenReturn(
DESTINATION_STRING);
@@ -965,13 +978,13 @@
}
@Test
- public void getDataManagementIntent_calledBeforeInitialize_ignored() throws RemoteException {
+ public void getDataManagementIntent_calledBeforeInitialize_ignored() throws Exception {
assertNull(mTrampoline.getDataManagementIntent(TRANSPORT_NAME));
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void getDataManagementIntentForUser_forwarded() throws RemoteException {
+ public void getDataManagementIntentForUser_forwarded() throws Exception {
Intent dataManagementIntent = new Intent();
when(mBackupManagerServiceMock.getDataManagementIntent(mUserId, TRANSPORT_NAME)).thenReturn(
dataManagementIntent);
@@ -984,7 +997,7 @@
}
@Test
- public void getDataManagementIntent_forwarded() throws RemoteException {
+ public void getDataManagementIntent_forwarded() throws Exception {
TrampolineTestable.sCallingUserId = mUserId;
Intent dataManagementIntent = new Intent();
when(mBackupManagerServiceMock.getDataManagementIntent(mUserId, TRANSPORT_NAME)).thenReturn(
@@ -996,13 +1009,13 @@
}
@Test
- public void getDataManagementLabel_calledBeforeInitialize_ignored() throws RemoteException {
+ public void getDataManagementLabel_calledBeforeInitialize_ignored() throws Exception {
assertNull(mTrampoline.getDataManagementLabel(TRANSPORT_NAME));
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void getDataManagementLabelForUser_forwarded() throws RemoteException {
+ public void getDataManagementLabelForUser_forwarded() throws Exception {
when(mBackupManagerServiceMock.getDataManagementLabel(mUserId, TRANSPORT_NAME)).thenReturn(
DATA_MANAGEMENT_LABEL);
mTrampoline.initializeService();
@@ -1014,7 +1027,7 @@
}
@Test
- public void getDataManagementLabel_forwarded() throws RemoteException {
+ public void getDataManagementLabel_forwarded() throws Exception {
TrampolineTestable.sCallingUserId = mUserId;
when(mBackupManagerServiceMock.getDataManagementLabel(mUserId, TRANSPORT_NAME)).thenReturn(
DATA_MANAGEMENT_LABEL);
@@ -1025,13 +1038,13 @@
}
@Test
- public void beginRestoreSession_calledBeforeInitialize_ignored() throws RemoteException {
+ public void beginRestoreSession_calledBeforeInitialize_ignored() throws Exception {
mTrampoline.beginRestoreSessionForUser(mUserId, PACKAGE_NAME, TRANSPORT_NAME);
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void beginRestoreSessionForUser_forwarded() throws RemoteException {
+ public void beginRestoreSessionForUser_forwarded() throws Exception {
mTrampoline.initializeService();
mTrampoline.beginRestoreSessionForUser(mUserId, PACKAGE_NAME, TRANSPORT_NAME);
@@ -1041,13 +1054,13 @@
}
@Test
- public void opComplete_calledBeforeInitialize_ignored() throws RemoteException {
+ public void opComplete_calledBeforeInitialize_ignored() throws Exception {
mTrampoline.opComplete(1, 2);
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void opComplete_forwarded() throws RemoteException {
+ public void opComplete_forwarded() throws Exception {
TrampolineTestable.sCallingUserId = mUserId;
mTrampoline.initializeService();
@@ -1057,14 +1070,13 @@
}
@Test
- public void getAvailableRestoreTokenForUser_calledBeforeInitialize_ignored()
- throws RemoteException {
+ public void getAvailableRestoreTokenForUser_calledBeforeInitialize_ignored() {
assertEquals(0, mTrampoline.getAvailableRestoreTokenForUser(mUserId, PACKAGE_NAME));
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void getAvailableRestoreTokenForUser_forwarded() throws RemoteException {
+ public void getAvailableRestoreTokenForUser_forwarded() {
when(mBackupManagerServiceMock.getAvailableRestoreToken(mUserId, PACKAGE_NAME))
.thenReturn(123L);
mTrampoline.initializeService();
@@ -1074,14 +1086,13 @@
}
@Test
- public void isAppEligibleForBackupForUser_calledBeforeInitialize_ignored()
- throws RemoteException {
+ public void isAppEligibleForBackupForUser_calledBeforeInitialize_ignored() {
assertFalse(mTrampoline.isAppEligibleForBackupForUser(mUserId, PACKAGE_NAME));
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void isAppEligibleForBackupForUser_forwarded() throws RemoteException {
+ public void isAppEligibleForBackupForUser_forwarded() {
when(mBackupManagerServiceMock.isAppEligibleForBackup(mUserId, PACKAGE_NAME))
.thenReturn(true);
mTrampoline.initializeService();
@@ -1098,7 +1109,7 @@
}
@Test
- public void requestBackupForUser_forwarded() throws RemoteException {
+ public void requestBackupForUser_forwarded() throws Exception {
when(mBackupManagerServiceMock.requestBackup(mUserId, PACKAGE_NAMES,
mBackupObserverMock, mBackupManagerMonitorMock, 123)).thenReturn(456);
mTrampoline.initializeService();
@@ -1110,7 +1121,7 @@
}
@Test
- public void requestBackup_forwardedToCallingUserId() throws RemoteException {
+ public void requestBackup_forwardedToCallingUserId() throws Exception {
TrampolineTestable.sCallingUserId = mUserId;
when(mBackupManagerServiceMock.requestBackup(NON_USER_SYSTEM, PACKAGE_NAMES,
mBackupObserverMock, mBackupManagerMonitorMock, 123)).thenReturn(456);
@@ -1123,13 +1134,13 @@
}
@Test
- public void cancelBackups_calledBeforeInitialize_ignored() throws RemoteException {
+ public void cancelBackups_calledBeforeInitialize_ignored() throws Exception {
mTrampoline.cancelBackups();
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void cancelBackupsForUser_forwarded() throws RemoteException {
+ public void cancelBackupsForUser_forwarded() throws Exception {
mTrampoline.initializeService();
mTrampoline.cancelBackupsForUser(mUserId);
@@ -1138,7 +1149,7 @@
}
@Test
- public void cancelBackups_forwardedToCallingUserId() throws RemoteException {
+ public void cancelBackups_forwardedToCallingUserId() throws Exception {
TrampolineTestable.sCallingUserId = mUserId;
mTrampoline.initializeService();
@@ -1148,13 +1159,13 @@
}
@Test
- public void beginFullBackup_calledBeforeInitialize_ignored() throws RemoteException {
+ public void beginFullBackup_calledBeforeInitialize_ignored() throws Exception {
mTrampoline.beginFullBackup(mUserId, new FullBackupJob());
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void beginFullBackup_forwarded() throws RemoteException {
+ public void beginFullBackup_forwarded() throws Exception {
FullBackupJob fullBackupJob = new FullBackupJob();
when(mBackupManagerServiceMock.beginFullBackup(mUserId, fullBackupJob)).thenReturn(true);
@@ -1164,20 +1175,20 @@
}
@Test
- public void endFullBackup_calledBeforeInitialize_ignored() throws RemoteException {
+ public void endFullBackup_calledBeforeInitialize_ignored() {
mTrampoline.endFullBackup(mUserId);
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
- public void endFullBackup_forwarded() throws RemoteException {
+ public void endFullBackup_forwarded() {
mTrampoline.initializeService();
mTrampoline.endFullBackup(mUserId);
verify(mBackupManagerServiceMock).endFullBackup(mUserId);
}
@Test
- public void dump_callerDoesNotHavePermission_ignored() throws RemoteException {
+ public void dump_callerDoesNotHavePermission_ignored() {
when(mContextMock.checkCallingOrSelfPermission(
android.Manifest.permission.DUMP)).thenReturn(
PackageManager.PERMISSION_DENIED);
@@ -1189,7 +1200,7 @@
}
@Test
- public void dump_calledBeforeInitialize_ignored() throws RemoteException {
+ public void dump_calledBeforeInitialize_ignored() {
when(mContextMock.checkCallingOrSelfPermission(
android.Manifest.permission.DUMP)).thenReturn(
PackageManager.PERMISSION_GRANTED);
@@ -1200,7 +1211,7 @@
}
@Test
- public void dump_callerHasPermission_forwarded() throws RemoteException {
+ public void dump_callerHasPermission_forwarded() {
when(mContextMock.checkCallingOrSelfPermission(
android.Manifest.permission.DUMP)).thenReturn(
PackageManager.PERMISSION_GRANTED);
@@ -1213,11 +1224,36 @@
private static class TrampolineTestable extends Trampoline {
static boolean sBackupDisabled = false;
- static File sSuppressFile = null;
static int sCallingUserId = -1;
static int sCallingUid = -1;
static BackupManagerService sBackupManagerServiceMock = null;
private int mCreateServiceCallsCount = 0;
+ private SparseArray<FakeFile> mSuppressFiles = new SparseArray<>();
+
+ private static class FakeFile extends File {
+ private boolean mExists;
+
+ FakeFile(String pathname) {
+ super(pathname);
+ }
+
+ @Override
+ public boolean exists() {
+ return mExists;
+ }
+
+ @Override
+ public boolean delete() {
+ mExists = false;
+ return true;
+ }
+
+ @Override
+ public boolean createNewFile() throws IOException {
+ mExists = true;
+ return true;
+ }
+ }
TrampolineTestable(Context context) {
super(context);
@@ -1229,8 +1265,12 @@
}
@Override
- public File getSuppressFile() {
- return sSuppressFile;
+ public File getSuppressFileForUser(int userId) {
+ if (mSuppressFiles.get(userId) == null) {
+ FakeFile file = new FakeFile(Integer.toString(userId));
+ mSuppressFiles.append(userId, file);
+ }
+ return mSuppressFiles.get(userId);
}
protected int binderGetCallingUserId() {
@@ -1249,6 +1289,11 @@
}
@Override
+ protected void createBackupSuppressFileForUser(int userId) throws IOException {
+ getSuppressFileForUser(userId).createNewFile();
+ }
+
+ @Override
protected void postToHandler(Runnable runnable) {
runnable.run();
}
diff --git a/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java b/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java
new file mode 100644
index 0000000..095a1de
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java
@@ -0,0 +1,1172 @@
+/*
+ * 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 com.android.server.backup.testutils;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ChangedPackages;
+import android.content.pm.IDexModuleRegisterCallback;
+import android.content.pm.IOnPermissionsChangeListener;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.IPackageDeleteObserver2;
+import android.content.pm.IPackageInstaller;
+import android.content.pm.IPackageManager;
+import android.content.pm.IPackageMoveObserver;
+import android.content.pm.IPackageStatsObserver;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.KeySet;
+import android.content.pm.ModuleInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.SuspendDialogInfo;
+import android.content.pm.VerifierDeviceIdentity;
+import android.content.pm.VersionedPackage;
+import android.content.pm.dex.IArtManager;
+import android.graphics.Bitmap;
+import android.os.IBinder;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import java.util.List;
+
+/**
+ * Stub for IPackageManager to use in tests.
+ */
+public class IPackageManagerStub implements IPackageManager {
+ public static PackageInfo sPackageInfo;
+ public static int sApplicationEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+
+ @Override
+ public PackageInfo getPackageInfo(String packageName, int flags, int userId)
+ throws RemoteException {
+ return sPackageInfo;
+ }
+
+ @Override
+ public int getApplicationEnabledSetting(String packageName, int userId) throws RemoteException {
+ return sApplicationEnabledSetting;
+ }
+
+ @Override
+ public void checkPackageStartable(String packageName, int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public boolean isPackageAvailable(String packageName, int userId) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public PackageInfo getPackageInfoVersioned(VersionedPackage versionedPackage, int flags,
+ int userId) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public int getPackageUid(String packageName, int flags, int userId) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public int[] getPackageGids(String packageName, int flags, int userId) throws RemoteException {
+ return new int[0];
+ }
+
+ @Override
+ public String[] currentToCanonicalPackageNames(String[] names) throws RemoteException {
+ return new String[0];
+ }
+
+ @Override
+ public String[] canonicalToCurrentPackageNames(String[] names) throws RemoteException {
+ return new String[0];
+ }
+
+ @Override
+ public PermissionInfo getPermissionInfo(String name, String packageName, int flags)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice queryPermissionsByGroup(String group, int flags)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public PermissionGroupInfo getPermissionGroupInfo(String name, int flags)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice getAllPermissionGroups(int flags) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ActivityInfo getActivityInfo(ComponentName className, int flags, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean activitySupportsIntent(ComponentName className, Intent intent,
+ String resolvedType)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public ActivityInfo getReceiverInfo(ComponentName className, int flags, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ServiceInfo getServiceInfo(ComponentName className, int flags, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ProviderInfo getProviderInfo(ComponentName className, int flags, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public int checkPermission(String permName, String pkgName, int userId) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public int checkUidPermission(String permName, int uid) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public boolean addPermission(PermissionInfo info) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void removePermission(String name) throws RemoteException {
+
+ }
+
+ @Override
+ public void grantRuntimePermission(String packageName, String permissionName, int userId)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void revokeRuntimePermission(String packageName, String permissionName, int userId)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void resetRuntimePermissions() throws RemoteException {
+
+ }
+
+ @Override
+ public int getPermissionFlags(String permissionName, String packageName, int userId)
+ throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public void updatePermissionFlags(String permissionName, String packageName, int flagMask,
+ int flagValues, int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public void updatePermissionFlagsForAllApps(int flagMask, int flagValues, int userId)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public boolean shouldShowRequestPermissionRationale(String permissionName, String packageName,
+ int userId) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean isProtectedBroadcast(String actionName) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public int checkSignatures(String pkg1, String pkg2) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public int checkUidSignatures(int uid1, int uid2) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public List<String> getAllPackages() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public String[] getPackagesForUid(int uid) throws RemoteException {
+ return new String[0];
+ }
+
+ @Override
+ public String getNameForUid(int uid) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public String[] getNamesForUids(int[] uids) throws RemoteException {
+ return new String[0];
+ }
+
+ @Override
+ public int getUidForSharedUser(String sharedUserName) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public int getFlagsForUid(int uid) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public int getPrivateFlagsForUid(int uid) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public boolean isUidPrivileged(int uid) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public String[] getAppOpPermissionPackages(String permissionName) throws RemoteException {
+ return new String[0];
+ }
+
+ @Override
+ public ResolveInfo resolveIntent(Intent intent, String resolvedType, int flags, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ResolveInfo findPersistentPreferredActivity(Intent intent, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean canForwardTo(Intent intent, String resolvedType, int sourceUserId,
+ int targetUserId) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public ParceledListSlice queryIntentActivities(Intent intent, String resolvedType, int flags,
+ int userId) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice queryIntentActivityOptions(ComponentName caller, Intent[] specifics,
+ String[] specificTypes, Intent intent, String resolvedType, int flags, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice queryIntentReceivers(Intent intent, String resolvedType, int flags,
+ int userId) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ResolveInfo resolveService(Intent intent, String resolvedType, int flags, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice queryIntentServices(Intent intent, String resolvedType, int flags,
+ int userId) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice queryIntentContentProviders(Intent intent, String resolvedType,
+ int flags, int userId) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice getInstalledPackages(int flags, int userId) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice getPackagesHoldingPermissions(String[] permissions, int flags,
+ int userId) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice getInstalledApplications(int flags, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice getPersistentApplications(int flags) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ProviderInfo resolveContentProvider(String name, int flags, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public void querySyncProviders(List<String> outNames, List<ProviderInfo> outInfo)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public ParceledListSlice queryContentProviders(String processName, int uid, int flags,
+ String metaDataKey) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public InstrumentationInfo getInstrumentationInfo(ComponentName className, int flags)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice queryInstrumentation(String targetPackage, int flags)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public void finishPackageInstall(int token, boolean didLaunch) throws RemoteException {
+
+ }
+
+ @Override
+ public void setInstallerPackageName(String targetPackage, String installerPackageName)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void setApplicationCategoryHint(String packageName, int categoryHint,
+ String callerPackageName) throws RemoteException {
+
+ }
+
+ @Override
+ public void deletePackageAsUser(String packageName, int versionCode,
+ IPackageDeleteObserver observer, int userId, int flags) throws RemoteException {
+
+ }
+
+ @Override
+ public void deletePackageVersioned(VersionedPackage versionedPackage,
+ IPackageDeleteObserver2 observer, int userId, int flags) throws RemoteException {
+
+ }
+
+ @Override
+ public String getInstallerPackageName(String packageName) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public void resetApplicationPreferences(int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public ResolveInfo getLastChosenActivity(Intent intent, String resolvedType, int flags)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public void setLastChosenActivity(Intent intent, String resolvedType, int flags,
+ IntentFilter filter, int match, ComponentName activity) throws RemoteException {
+
+ }
+
+ @Override
+ public void addPreferredActivity(IntentFilter filter, int match, ComponentName[] set,
+ ComponentName activity, int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public void replacePreferredActivity(IntentFilter filter, int match, ComponentName[] set,
+ ComponentName activity, int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public void clearPackagePreferredActivities(String packageName) throws RemoteException {
+
+ }
+
+ @Override
+ public int getPreferredActivities(List<IntentFilter> outFilters,
+ List<ComponentName> outActivities, String packageName) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public void addPersistentPreferredActivity(IntentFilter filter, ComponentName activity,
+ int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public void clearPackagePersistentPreferredActivities(String packageName, int userId)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void addCrossProfileIntentFilter(IntentFilter intentFilter, String ownerPackage,
+ int sourceUserId, int targetUserId, int flags) throws RemoteException {
+
+ }
+
+ @Override
+ public void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public String[] setDistractingPackageRestrictionsAsUser(String[] packageNames,
+ int restrictionFlags, int userId) throws RemoteException {
+ return new String[0];
+ }
+
+ @Override
+ public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended,
+ PersistableBundle appExtras, PersistableBundle launcherExtras, SuspendDialogInfo dialogInfo,
+ String callingPackage, int userId) throws RemoteException {
+ return new String[0];
+ }
+
+ @Override
+ public String[] getUnsuspendablePackagesForUser(String[] packageNames, int userId)
+ throws RemoteException {
+ return new String[0];
+ }
+
+ @Override
+ public boolean isPackageSuspendedForUser(String packageName, int userId)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public PersistableBundle getSuspendedPackageAppExtras(String packageName, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public byte[] getPreferredActivityBackup(int userId) throws RemoteException {
+ return new byte[0];
+ }
+
+ @Override
+ public void restorePreferredActivities(byte[] backup, int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public byte[] getDefaultAppsBackup(int userId) throws RemoteException {
+ return new byte[0];
+ }
+
+ @Override
+ public void restoreDefaultApps(byte[] backup, int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public byte[] getIntentFilterVerificationBackup(int userId) throws RemoteException {
+ return new byte[0];
+ }
+
+ @Override
+ public void restoreIntentFilterVerification(byte[] backup, int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public byte[] getPermissionGrantBackup(int userId) throws RemoteException {
+ return new byte[0];
+ }
+
+ @Override
+ public void restorePermissionGrants(byte[] backup, int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public ComponentName getHomeActivities(List<ResolveInfo> outHomeCandidates)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public void setHomeActivity(ComponentName className, int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags,
+ int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public int getComponentEnabledSetting(ComponentName componentName, int userId)
+ throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public void setApplicationEnabledSetting(String packageName, int newState, int flags,
+ int userId,
+ String callingPackage) throws RemoteException {
+
+ }
+
+ @Override
+ public void logAppProcessStartIfNeeded(String processName, int uid, String seinfo,
+ String apkFile,
+ int pid) throws RemoteException {
+
+ }
+
+ @Override
+ public void flushPackageRestrictionsAsUser(int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public void setPackageStoppedState(String packageName, boolean stopped, int userId)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void freeStorageAndNotify(String volumeUuid, long freeStorageSize, int storageFlags,
+ IPackageDataObserver observer) throws RemoteException {
+
+ }
+
+ @Override
+ public void freeStorage(String volumeUuid, long freeStorageSize, int storageFlags,
+ IntentSender pi) throws RemoteException {
+
+ }
+
+ @Override
+ public void deleteApplicationCacheFiles(String packageName, IPackageDataObserver observer)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void deleteApplicationCacheFilesAsUser(String packageName, int userId,
+ IPackageDataObserver observer) throws RemoteException {
+
+ }
+
+ @Override
+ public void clearApplicationUserData(String packageName, IPackageDataObserver observer,
+ int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public void clearApplicationProfileData(String packageName) throws RemoteException {
+
+ }
+
+ @Override
+ public void getPackageSizeInfo(String packageName, int userHandle,
+ IPackageStatsObserver observer)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public String[] getSystemSharedLibraryNames() throws RemoteException {
+ return new String[0];
+ }
+
+ @Override
+ public ParceledListSlice getSystemAvailableFeatures() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean hasSystemFeature(String name, int version) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void enterSafeMode() throws RemoteException {
+
+ }
+
+ @Override
+ public boolean isSafeMode() throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void systemReady() throws RemoteException {
+
+ }
+
+ @Override
+ public boolean hasSystemUidErrors() throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void performFstrimIfNeeded() throws RemoteException {
+
+ }
+
+ @Override
+ public void updatePackagesIfNeeded() throws RemoteException {
+
+ }
+
+ @Override
+ public void notifyPackageUse(String packageName, int reason) throws RemoteException {
+
+ }
+
+ @Override
+ public void notifyDexLoad(String loadingPackageName, List<String> classLoadersNames,
+ List<String> classPaths, String loaderIsa) throws RemoteException {
+
+ }
+
+ @Override
+ public void registerDexModule(String packageName, String dexModulePath, boolean isSharedModule,
+ IDexModuleRegisterCallback callback) throws RemoteException {
+
+ }
+
+ @Override
+ public boolean performDexOptMode(String packageName, boolean checkProfiles,
+ String targetCompilerFilter, boolean force, boolean bootComplete, String splitName)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean performDexOptSecondary(String packageName, String targetCompilerFilter,
+ boolean force) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean compileLayouts(String packageName) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void dumpProfiles(String packageName) throws RemoteException {
+
+ }
+
+ @Override
+ public void forceDexOpt(String packageName) throws RemoteException {
+
+ }
+
+ @Override
+ public boolean runBackgroundDexoptJob(List<String> packageNames) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void reconcileSecondaryDexFiles(String packageName) throws RemoteException {
+
+ }
+
+ @Override
+ public int getMoveStatus(int moveId) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public void registerMoveCallback(IPackageMoveObserver callback) throws RemoteException {
+
+ }
+
+ @Override
+ public void unregisterMoveCallback(IPackageMoveObserver callback) throws RemoteException {
+
+ }
+
+ @Override
+ public int movePackage(String packageName, String volumeUuid) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public int movePrimaryStorage(String volumeUuid) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public boolean addPermissionAsync(PermissionInfo info) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean setInstallLocation(int loc) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public int getInstallLocation() throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public int installExistingPackageAsUser(String packageName, int userId, int installFlags,
+ int installReason) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public void verifyPendingInstall(int id, int verificationCode) throws RemoteException {
+
+ }
+
+ @Override
+ public void extendVerificationTimeout(int id, int verificationCodeAtTimeout,
+ long millisecondsToDelay) throws RemoteException {
+
+ }
+
+ @Override
+ public void verifyIntentFilter(int id, int verificationCode, List<String> failedDomains)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public int getIntentVerificationStatus(String packageName, int userId) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public boolean updateIntentVerificationStatus(String packageName, int status, int userId)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public ParceledListSlice getIntentFilterVerifications(String packageName)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice getAllIntentFilters(String packageName) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean setDefaultBrowserPackageName(String packageName, int userId)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public String getDefaultBrowserPackageName(int userId) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public VerifierDeviceIdentity getVerifierDeviceIdentity() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean isFirstBoot() throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean isOnlyCoreApps() throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean isUpgrade() throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void setPermissionEnforced(String permission, boolean enforced) throws RemoteException {
+
+ }
+
+ @Override
+ public boolean isPermissionEnforced(String permission) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean isStorageLow() throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden, int userId)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean getApplicationHiddenSettingAsUser(String packageName, int userId)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void setSystemAppHiddenUntilInstalled(String packageName, boolean hidden)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public boolean setSystemAppInstallState(String packageName, boolean installed, int userId)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public IPackageInstaller getPackageInstaller() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean setBlockUninstallForUser(String packageName, boolean blockUninstall, int userId)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean getBlockUninstallForUser(String packageName, int userId) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public KeySet getKeySetByAlias(String packageName, String alias) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public KeySet getSigningKeySet(String packageName) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean isPackageSignedByKeySet(String packageName, KeySet ks) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean isPackageSignedByKeySetExactly(String packageName, KeySet ks)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void addOnPermissionsChangeListener(IOnPermissionsChangeListener listener)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void removeOnPermissionsChangeListener(IOnPermissionsChangeListener listener)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void grantDefaultPermissionsToEnabledCarrierApps(String[] packageNames, int userId)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void grantDefaultPermissionsToEnabledImsServices(String[] packageNames, int userId)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void grantDefaultPermissionsToEnabledTelephonyDataServices(String[] packageNames,
+ int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public void revokeDefaultPermissionsFromDisabledTelephonyDataServices(String[] packageNames,
+ int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public void grantDefaultPermissionsToActiveLuiApp(String packageName, int userId)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void revokeDefaultPermissionsFromLuiApps(String[] packageNames, int userId)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public boolean isPermissionRevokedByPolicy(String permission, String packageName, int userId)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public String getPermissionControllerPackageName() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ParceledListSlice getInstantApps(int userId) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public byte[] getInstantAppCookie(String packageName, int userId) throws RemoteException {
+ return new byte[0];
+ }
+
+ @Override
+ public boolean setInstantAppCookie(String packageName, byte[] cookie, int userId)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public Bitmap getInstantAppIcon(String packageName, int userId) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean isInstantApp(String packageName, int userId) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean setRequiredForSystemUser(String packageName, boolean systemUserApp)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void setUpdateAvailable(String packageName, boolean updateAvaialble)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public String getServicesSystemSharedLibraryPackageName() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public String getSharedSystemSharedLibraryPackageName() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ChangedPackages getChangedPackages(int sequenceNumber, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean isPackageDeviceAdminOnAnyUser(String packageName) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public int getInstallReason(String packageName, int userId) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public ParceledListSlice getSharedLibraries(String packageName, int flags, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean canRequestPackageInstalls(String packageName, int userId)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void deletePreloadsFileCache() throws RemoteException {
+
+ }
+
+ @Override
+ public ComponentName getInstantAppResolverComponent() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ComponentName getInstantAppResolverSettingsComponent() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ComponentName getInstantAppInstallerComponent() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public String getInstantAppAndroidId(String packageName, int userId) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public IArtManager getArtManager() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public void setHarmfulAppWarning(String packageName, CharSequence warning, int userId)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public CharSequence getHarmfulAppWarning(String packageName, int userId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean hasSigningCertificate(String packageName, byte[] signingCertificate, int flags)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean hasUidSigningCertificate(int uid, byte[] signingCertificate, int flags)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public String getSystemTextClassifierPackageName() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public String getWellbeingPackageName() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean isPackageStateProtected(String packageName, int userId) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void sendDeviceCustomizationReadyBroadcast() throws RemoteException {
+
+ }
+
+ @Override
+ public List<ModuleInfo> getInstalledModules(int flags) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public ModuleInfo getModuleInfo(String packageName, int flags) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java b/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java
index 525135c..7172752 100644
--- a/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java
+++ b/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java
@@ -48,11 +48,7 @@
@Override
public PackageInfo getPackageInfo(String packageName, int flags)
throws NameNotFoundException {
- if (sPackageInfo == null) {
- throw new NameNotFoundException();
- }
-
- return sPackageInfo;
+ return getPackageInfoAsUser(packageName, flags, UserHandle.USER_SYSTEM);
}
@Override
@@ -64,7 +60,11 @@
@Override
public PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
throws NameNotFoundException {
- return null;
+ if (sPackageInfo == null) {
+ throw new NameNotFoundException();
+ }
+
+ return sPackageInfo;
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java
index 479a19b..a92b576 100644
--- a/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java
@@ -29,13 +29,14 @@
import android.content.pm.Signature;
import android.content.pm.SigningInfo;
import android.os.Process;
+import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.backup.UserBackupManagerService;
-import com.android.server.backup.testutils.PackageManagerStub;
+import com.android.server.backup.testutils.IPackageManagerStub;
import org.junit.Before;
import org.junit.Test;
@@ -53,13 +54,17 @@
private static final Signature SIGNATURE_3 = generateSignature((byte) 3);
private static final Signature SIGNATURE_4 = generateSignature((byte) 4);
- private PackageManagerStub mPackageManagerStub;
+ private IPackageManagerStub mPackageManagerStub;
private PackageManagerInternal mMockPackageManagerInternal;
+ private int mUserId;
+
@Before
public void setUp() throws Exception {
- mPackageManagerStub = new PackageManagerStub();
+ mPackageManagerStub = new IPackageManagerStub();
mMockPackageManagerInternal = mock(PackageManagerInternal.class);
+
+ mUserId = UserHandle.USER_SYSTEM;
}
@Test
@@ -71,7 +76,7 @@
applicationInfo.packageName = TEST_PACKAGE_NAME;
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mPackageManagerStub);
+ mPackageManagerStub, mUserId);
assertThat(isEligible).isFalse();
}
@@ -86,7 +91,7 @@
applicationInfo.packageName = TEST_PACKAGE_NAME;
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mPackageManagerStub);
+ mPackageManagerStub, mUserId);
assertThat(isEligible).isFalse();
}
@@ -100,7 +105,7 @@
applicationInfo.packageName = UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mPackageManagerStub);
+ mPackageManagerStub, mUserId);
assertThat(isEligible).isFalse();
}
@@ -114,11 +119,11 @@
applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
applicationInfo.packageName = TEST_PACKAGE_NAME;
- PackageManagerStub.sApplicationEnabledSetting =
+ IPackageManagerStub.sApplicationEnabledSetting =
PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mPackageManagerStub);
+ mPackageManagerStub, mUserId);
assertThat(isEligible).isTrue();
}
@@ -132,11 +137,11 @@
applicationInfo.backupAgentName = null;
applicationInfo.packageName = TEST_PACKAGE_NAME;
- PackageManagerStub.sApplicationEnabledSetting =
+ IPackageManagerStub.sApplicationEnabledSetting =
PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mPackageManagerStub);
+ mPackageManagerStub, mUserId);
assertThat(isEligible).isTrue();
}
@@ -150,11 +155,11 @@
applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
applicationInfo.packageName = TEST_PACKAGE_NAME;
- PackageManagerStub.sApplicationEnabledSetting =
+ IPackageManagerStub.sApplicationEnabledSetting =
PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mPackageManagerStub);
+ mPackageManagerStub, mUserId);
assertThat(isEligible).isTrue();
}
@@ -168,11 +173,11 @@
applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
applicationInfo.packageName = TEST_PACKAGE_NAME;
- PackageManagerStub.sApplicationEnabledSetting =
+ IPackageManagerStub.sApplicationEnabledSetting =
PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mPackageManagerStub);
+ mPackageManagerStub, mUserId);
assertThat(isEligible).isFalse();
}
@@ -186,11 +191,11 @@
applicationInfo.backupAgentName = null;
applicationInfo.packageName = TEST_PACKAGE_NAME;
- PackageManagerStub.sApplicationEnabledSetting =
+ IPackageManagerStub.sApplicationEnabledSetting =
PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mPackageManagerStub);
+ mPackageManagerStub, mUserId);
assertThat(isEligible).isFalse();
}
@@ -204,11 +209,11 @@
applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
applicationInfo.packageName = TEST_PACKAGE_NAME;
- PackageManagerStub.sApplicationEnabledSetting =
+ IPackageManagerStub.sApplicationEnabledSetting =
PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mPackageManagerStub);
+ mPackageManagerStub, mUserId);
assertThat(isEligible).isFalse();
}
@@ -222,10 +227,11 @@
applicationInfo.packageName = TEST_PACKAGE_NAME;
applicationInfo.enabled = true;
- PackageManagerStub.sApplicationEnabledSetting =
+ IPackageManagerStub.sApplicationEnabledSetting =
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
- boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub);
+ boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub,
+ mUserId);
assertThat(isDisabled).isFalse();
}
@@ -239,10 +245,11 @@
applicationInfo.packageName = TEST_PACKAGE_NAME;
applicationInfo.enabled = false;
- PackageManagerStub.sApplicationEnabledSetting =
+ IPackageManagerStub.sApplicationEnabledSetting =
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
- boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub);
+ boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub,
+ mUserId);
assertThat(isDisabled).isTrue();
}
@@ -255,10 +262,11 @@
applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
applicationInfo.packageName = TEST_PACKAGE_NAME;
- PackageManagerStub.sApplicationEnabledSetting =
+ IPackageManagerStub.sApplicationEnabledSetting =
PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
- boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub);
+ boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub,
+ mUserId);
assertThat(isDisabled).isFalse();
}
@@ -271,10 +279,11 @@
applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
applicationInfo.packageName = TEST_PACKAGE_NAME;
- PackageManagerStub.sApplicationEnabledSetting =
+ IPackageManagerStub.sApplicationEnabledSetting =
PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
- boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub);
+ boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub,
+ mUserId);
assertThat(isDisabled).isTrue();
}
@@ -287,10 +296,11 @@
applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
applicationInfo.packageName = TEST_PACKAGE_NAME;
- PackageManagerStub.sApplicationEnabledSetting =
+ IPackageManagerStub.sApplicationEnabledSetting =
PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
- boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub);
+ boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub,
+ mUserId);
assertThat(isDisabled).isTrue();
}
@@ -303,10 +313,11 @@
applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
applicationInfo.packageName = TEST_PACKAGE_NAME;
- PackageManagerStub.sApplicationEnabledSetting =
+ IPackageManagerStub.sApplicationEnabledSetting =
PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
- boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub);
+ boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub,
+ mUserId);
assertThat(isDisabled).isTrue();
}
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
index d43b677..5fcce67 100644
--- a/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
@@ -44,6 +44,7 @@
import android.content.pm.SigningInfo;
import android.os.Bundle;
import android.os.Process;
+import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
@@ -88,12 +89,14 @@
private final PackageManagerStub mPackageManagerStub = new PackageManagerStub();
private Context mContext;
+ private int mUserId;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mContext = InstrumentationRegistry.getContext();
+ mUserId = UserHandle.USER_SYSTEM;
}
@Test
@@ -146,7 +149,7 @@
fileMetadata);
RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy(
mPackageManagerStub, false /* allowApks */, fileMetadata, signatures,
- mMockPackageManagerInternal);
+ mMockPackageManagerInternal, mUserId);
assertThat(restorePolicy).isEqualTo(RestorePolicy.IGNORE);
assertThat(fileMetadata.packageName).isEqualTo(TEST_PACKAGE_NAME);
@@ -160,7 +163,7 @@
fileMetadata);
restorePolicy = tarBackupReader.chooseRestorePolicy(
mPackageManagerStub, false /* allowApks */, fileMetadata, signatures,
- mMockPackageManagerInternal);
+ mMockPackageManagerInternal, mUserId);
assertThat(restorePolicy).isEqualTo(RestorePolicy.IGNORE);
assertThat(fileMetadata.packageName).isEqualTo(TEST_PACKAGE_NAME);
@@ -223,7 +226,7 @@
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
true /* allowApks */, new FileMetadata(), null /* signatures */,
- mMockPackageManagerInternal);
+ mMockPackageManagerInternal, mUserId);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
verifyZeroInteractions(mBackupManagerMonitorMock);
@@ -244,7 +247,7 @@
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
true /* allowApks */, info, new Signature[0] /* signatures */,
- mMockPackageManagerInternal);
+ mMockPackageManagerInternal, mUserId);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -269,7 +272,7 @@
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
true /* allowApks */, info, new Signature[0] /* signatures */,
- mMockPackageManagerInternal);
+ mMockPackageManagerInternal, mUserId);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -295,7 +298,7 @@
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */,
- mMockPackageManagerInternal);
+ mMockPackageManagerInternal, mUserId);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -320,7 +323,7 @@
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */,
- mMockPackageManagerInternal);
+ mMockPackageManagerInternal, mUserId);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -347,7 +350,7 @@
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */,
- mMockPackageManagerInternal);
+ mMockPackageManagerInternal, mUserId);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -381,7 +384,8 @@
PackageManagerStub.sPackageInfo = packageInfo;
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
- false /* allowApks */, new FileMetadata(), signatures, mMockPackageManagerInternal);
+ false /* allowApks */, new FileMetadata(), signatures,
+ mMockPackageManagerInternal, mUserId);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -419,7 +423,8 @@
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
packageInfo.packageName);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
- false /* allowApks */, new FileMetadata(), signatures, mMockPackageManagerInternal);
+ false /* allowApks */, new FileMetadata(), signatures,
+ mMockPackageManagerInternal, mUserId);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -456,7 +461,8 @@
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
packageInfo.packageName);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
- false /* allowApks */, new FileMetadata(), signatures, mMockPackageManagerInternal);
+ false /* allowApks */, new FileMetadata(), signatures,
+ mMockPackageManagerInternal, mUserId);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -497,7 +503,8 @@
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
packageInfo.packageName);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
- false /* allowApks */, info, signatures, mMockPackageManagerInternal);
+ false /* allowApks */, info, signatures, mMockPackageManagerInternal,
+ mUserId);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -540,7 +547,8 @@
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
packageInfo.packageName);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
- true /* allowApks */, info, signatures, mMockPackageManagerInternal);
+ true /* allowApks */, info, signatures, mMockPackageManagerInternal,
+ mUserId);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK);
verifyNoMoreInteractions(mBackupManagerMonitorMock);
@@ -579,7 +587,8 @@
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
packageInfo.packageName);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
- false /* allowApks */, info, signatures, mMockPackageManagerInternal);
+ false /* allowApks */, info, signatures, mMockPackageManagerInternal,
+ mUserId);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
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 38e8ac2..0813e6fa 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -206,6 +206,8 @@
mAdmin1Context.binder.callingUid = DpmMockContext.CALLER_UID;
setUpUserManager();
+
+ when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true);
}
private TransferOwnershipMetadataManager getMockTransferMetadataManager() {
@@ -836,6 +838,7 @@
MockUtils.checkIntent(intent),
MockUtils.checkUserHandle(MANAGED_PROFILE_USER_ID));
}
+
/**
* Test for: {@link DevicePolicyManager#setDeviceOwner} DO on system user installs successfully.
*/
@@ -2618,6 +2621,7 @@
when(getServices().lockPatternUtils
.isSeparateProfileChallengeEnabled(eq(PROFILE_USER))).thenReturn(false);
dpmi.reportSeparateProfileChallengeChanged(PROFILE_USER);
+ when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true);
verifyScreenTimeoutCall(Long.MAX_VALUE, PROFILE_USER);
verifyScreenTimeoutCall(10L , UserHandle.USER_SYSTEM);
@@ -4233,6 +4237,41 @@
assertTrue(dpm.isActivePasswordSufficient());
}
+ public void testIsActivePasswordSufficient_noLockScreen() throws Exception {
+ // If there is no lock screen, the password is considered empty no matter what, because
+ // it provides no security.
+ when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(false);
+
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ mContext.packageName = admin1.getPackageName();
+ setupDeviceOwner();
+
+ // If no password requirements are set, isActivePasswordSufficient should succeed.
+ assertTrue(dpm.isActivePasswordSufficient());
+
+ // Now set some password quality requirements.
+ dpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
+
+ reset(mContext.spiedContext);
+ final int userHandle = UserHandle.getUserId(mContext.binder.callingUid);
+ PasswordMetrics passwordMetricsNoSymbols = new PasswordMetrics(
+ DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 9,
+ 8, 2,
+ 6, 1,
+ 0, 1);
+ // This should be ignored, as there is no lock screen.
+ dpm.setActivePasswordState(passwordMetricsNoSymbols, userHandle);
+ dpm.reportPasswordChanged(userHandle);
+
+ // No broadcast should be sent.
+ verify(mContext.spiedContext, times(0)).sendBroadcastAsUser(
+ MockUtils.checkIntentAction(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED),
+ MockUtils.checkUserHandle(userHandle));
+
+ // The active (nonexistent) password doesn't comply with the requirements.
+ assertFalse(dpm.isActivePasswordSufficient());
+ }
+
private void setActivePasswordState(PasswordMetrics passwordMetrics)
throws Exception {
final int userHandle = UserHandle.getUserId(mContext.binder.callingUid);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 3a6cdc2..a89198a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -147,12 +147,12 @@
}
@Override
- void writeStringSetting(String key, String value) {
+ void writeStringSystemProperty(String key, String value) {
// do nothing
}
@Override
- boolean readBooleanSetting(String key, boolean defVal) {
+ boolean readBooleanSystemProperty(String key, boolean defVal) {
switch (key) {
case Constants.PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE:
return mMutingEnabled;
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
index 0328621..8afc3d3 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
@@ -22,7 +22,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -58,8 +57,7 @@
// Make sure that there is a short-circuit for DEFAULT_DISPLAY.
assertEquals(DEFAULT_DISPLAY,
InputMethodManagerService.computeImeDisplayIdForTarget(
- DEFAULT_DISPLAY, false /* isVrImeStarted */,
- sMustNotBeCalledChecker));
+ DEFAULT_DISPLAY, sMustNotBeCalledChecker));
}
@Test
@@ -67,17 +65,7 @@
// Make sure that there is a short-circuit for INVALID_DISPLAY.
assertEquals(DEFAULT_DISPLAY,
InputMethodManagerService.computeImeDisplayIdForTarget(
- INVALID_DISPLAY, false /* isVrImeStarted */,
- sMustNotBeCalledChecker));
- }
-
- @Test
- public void testComputeImeDisplayId_VrIme() {
- // Make sure that there is a short-circuit for VR IME.
- assertEquals(DEFAULT_DISPLAY,
- InputMethodManagerService.computeImeDisplayIdForTarget(
- SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, true /* isVrImeStarted */,
- sMustNotBeCalledChecker));
+ INVALID_DISPLAY, sMustNotBeCalledChecker));
}
@Test
@@ -86,8 +74,7 @@
// Make sure IME displayId is DEFAULT_DISPLAY.
assertEquals(DEFAULT_DISPLAY,
InputMethodManagerService.computeImeDisplayIdForTarget(
- NO_SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, false /* isVrImeStarted */,
- sChecker));
+ NO_SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, sChecker));
}
@Test
@@ -96,7 +83,6 @@
// Make sure IME displayId is the same display.
assertEquals(SYSTEM_DECORATION_SUPPORT_DISPLAY_ID,
InputMethodManagerService.computeImeDisplayIdForTarget(
- SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, false /* isVrImeStarted */,
- sChecker));
+ SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, sChecker));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 2dc3510..cf89cb8 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -37,8 +37,8 @@
import android.os.IProgressListener;
import android.os.RemoteException;
import android.os.UserManager;
-import android.os.storage.StorageManager;
import android.os.storage.IStorageManager;
+import android.os.storage.StorageManager;
import android.security.KeyStore;
import android.test.AndroidTestCase;
@@ -46,6 +46,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
import com.android.server.LocalServices;
+import com.android.server.wm.WindowManagerInternal;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@@ -85,6 +86,8 @@
KeyStore mKeyStore;
MockSyntheticPasswordManager mSpManager;
IAuthSecret mAuthSecretService;
+ WindowManagerInternal mMockWindowManager;
+ protected boolean mHasSecureLockScreen;
@Override
protected void setUp() throws Exception {
@@ -97,10 +100,13 @@
mActivityManager = mock(IActivityManager.class);
mDevicePolicyManager = mock(DevicePolicyManager.class);
mDevicePolicyManagerInternal = mock(DevicePolicyManagerInternal.class);
+ mMockWindowManager = mock(WindowManagerInternal.class);
LocalServices.removeServiceForTest(LockSettingsInternal.class);
LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+ LocalServices.removeServiceForTest(WindowManagerInternal.class);
LocalServices.addService(DevicePolicyManagerInternal.class, mDevicePolicyManagerInternal);
+ LocalServices.addService(WindowManagerInternal.class, mMockWindowManager);
mContext = new MockLockSettingsContext(getContext(), mUserManager, mNotificationManager,
mDevicePolicyManager, mock(StorageManager.class), mock(TrustManager.class),
@@ -114,11 +120,17 @@
storageDir.mkdirs();
}
+ mHasSecureLockScreen = true;
mLockPatternUtils = new LockPatternUtils(mContext) {
@Override
public ILockSettings getLockSettings() {
return mService;
}
+
+ @Override
+ public boolean hasSecureLockScreen() {
+ return mHasSecureLockScreen;
+ }
};
mSpManager = new MockSyntheticPasswordManager(mContext, mStorage, mGateKeeperService,
mUserManager);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index e12f6d3..5124803 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -26,13 +26,12 @@
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.service.gatekeeper.GateKeeperResponse;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.VerifyCredentialResponse;
-import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
import com.android.server.locksettings.FakeGateKeeperService.VerifyHandle;
+import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
/**
* runtest frameworks-services -c com.android.server.locksettings.LockSettingsServiceTests
@@ -54,11 +53,21 @@
PASSWORD_QUALITY_ALPHABETIC);
}
+ public void testCreatePasswordFailsWithoutLockScreen() throws RemoteException {
+ testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, "password",
+ CREDENTIAL_TYPE_PASSWORD, PASSWORD_QUALITY_ALPHABETIC);
+ }
+
public void testCreatePatternPrimaryUser() throws RemoteException {
testCreateCredential(PRIMARY_USER_ID, "123456789", CREDENTIAL_TYPE_PATTERN,
PASSWORD_QUALITY_SOMETHING);
}
+ public void testCreatePatternFailsWithoutLockScreen() throws RemoteException {
+ testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, "123456789",
+ CREDENTIAL_TYPE_PATTERN, PASSWORD_QUALITY_SOMETHING);
+ }
+
public void testChangePasswordPrimaryUser() throws RemoteException {
testChangeCredentials(PRIMARY_USER_ID, "78963214", CREDENTIAL_TYPE_PATTERN,
"asdfghjk", CREDENTIAL_TYPE_PASSWORD, PASSWORD_QUALITY_ALPHABETIC);
@@ -198,6 +207,21 @@
assertVerifyCredentials(userId, credential, type, -1);
}
+ private void testCreateCredentialFailsWithoutLockScreen(
+ int userId, String credential, int type, int quality) throws RemoteException {
+ mHasSecureLockScreen = false;
+
+ try {
+ mService.setLockCredential(credential, type, null, quality, userId);
+ fail("An exception should have been thrown.");
+ } catch (UnsupportedOperationException e) {
+ // Success - the exception was expected.
+ }
+
+ assertFalse(mService.havePassword(userId));
+ assertFalse(mService.havePattern(userId));
+ }
+
private void testChangeCredentials(int userId, String newCredential, int newType,
String oldCredential, int oldType, int quality) throws RemoteException {
final long sid = 1234;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
index a28a5a1..929c3b5 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
@@ -77,6 +78,7 @@
final Context context = InstrumentationRegistry.getTargetContext();
mUserId = ActivityManager.getCurrentUser();
mCommand = new LockSettingsShellCommand(mLockPatternUtils);
+ when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(true);
}
@Test
@@ -103,6 +105,16 @@
}
@Test
+ public void testChangePin_noLockScreen() throws Exception {
+ when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false);
+ assertEquals(-1, mCommand.exec(new Binder(), in, out, err,
+ new String[] { "set-pin", "--old", "1234", "4321" },
+ mShellCallback, mResultReceiver));
+ verify(mLockPatternUtils).hasSecureLockScreen();
+ verifyNoMoreInteractions(mLockPatternUtils);
+ }
+
+ @Test
public void testChangePassword() throws Exception {
when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
@@ -115,6 +127,16 @@
}
@Test
+ public void testChangePassword_noLockScreen() throws Exception {
+ when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false);
+ assertEquals(-1, mCommand.exec(new Binder(), in, out, err,
+ new String[] { "set-password", "--old", "1234", "4321" },
+ mShellCallback, mResultReceiver));
+ verify(mLockPatternUtils).hasSecureLockScreen();
+ verifyNoMoreInteractions(mLockPatternUtils);
+ }
+
+ @Test
public void testChangePattern() throws Exception {
when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
@@ -126,6 +148,16 @@
}
@Test
+ public void testChangePattern_noLockScreen() throws Exception {
+ when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false);
+ assertEquals(-1, mCommand.exec(new Binder(), in, out, err,
+ new String[] { "set-pattern", "--old", "1234", "4321" },
+ mShellCallback, mResultReceiver));
+ verify(mLockPatternUtils).hasSecureLockScreen();
+ verifyNoMoreInteractions(mLockPatternUtils);
+ }
+
+ @Test
public void testClear() throws Exception {
when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 94e02bc..0595a5b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -40,10 +40,10 @@
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
import com.android.server.locksettings.SyntheticPasswordManager.PasswordData;
-import java.util.ArrayList;
-
import org.mockito.ArgumentCaptor;
+import java.util.ArrayList;
+
/**
* runtest frameworks-services -c com.android.server.locksettings.SyntheticPasswordTests
@@ -448,6 +448,37 @@
assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
}
+ public void testSetLockCredentialWithTokenFailsWithoutLockScreen() throws Exception {
+ final String password = "password";
+ final String pattern = "123654";
+ final String token = "some-high-entropy-secure-token";
+
+ mHasSecureLockScreen = false;
+ enableSyntheticPassword();
+ long handle = mLocalService.addEscrowToken(token.getBytes(), PRIMARY_USER_ID);
+ assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+
+ try {
+ mLocalService.setLockCredentialWithToken(password,
+ LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, token.getBytes(),
+ PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+ fail("An exception should have been thrown.");
+ } catch (UnsupportedOperationException e) {
+ // Success - the exception was expected.
+ }
+ assertFalse(mService.havePassword(PRIMARY_USER_ID));
+
+ try {
+ mLocalService.setLockCredentialWithToken(pattern,
+ LockPatternUtils.CREDENTIAL_TYPE_PATTERN, handle, token.getBytes(),
+ PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+ fail("An exception should have been thrown.");
+ } catch (UnsupportedOperationException e) {
+ // Success - the exception was expected.
+ }
+ assertFalse(mService.havePattern(PRIMARY_USER_ID));
+ }
+
public void testgetHashFactorPrimaryUser() throws RemoteException {
final String password = "password";
mService.setLockCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
diff --git a/services/tests/servicestests/src/com/android/server/pm/LauncherAppsServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/LauncherAppsServiceTest.java
new file mode 100644
index 0000000..d7dc58d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/LauncherAppsServiceTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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 com.android.server.pm;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.pm.PackageParser;
+import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class LauncherAppsServiceTest {
+
+ private static final Signature SIGNATURE_1 = new Signature(new byte[]{0x00, 0x01, 0x02, 0x03});
+ private static final Signature SIGNATURE_2 = new Signature(new byte[]{0x04, 0x05, 0x06, 0x07});
+ private static final Signature SIGNATURE_3 = new Signature(new byte[]{0x08, 0x09, 0x10, 0x11});
+
+ @Test
+ public void testComputePackageCertDigest() {
+ String digest = LauncherAppsService.LauncherAppsImpl.computePackageCertDigest(SIGNATURE_1);
+ assertEquals("A02A05B025B928C039CF1AE7E8EE04E7C190C0DB", digest);
+ }
+
+ @Test
+ public void testGetLatestSignaturesWithSingleCert() {
+ SigningInfo signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[]{SIGNATURE_1},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null));
+ Signature[] signatures = LauncherAppsService.LauncherAppsImpl.getLatestSignatures(
+ signingInfo);
+ assertEquals(1, signatures.length);
+ assertEquals(SIGNATURE_1, signatures[0]);
+ }
+
+ @Test
+ public void testGetLatestSignaturesWithMultiCert() {
+ SigningInfo signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[]{SIGNATURE_1, SIGNATURE_2},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null));
+ Signature[] signatures = LauncherAppsService.LauncherAppsImpl.getLatestSignatures(
+ signingInfo);
+ assertEquals(2, signatures.length);
+ assertEquals(SIGNATURE_1, signatures[0]);
+ assertEquals(SIGNATURE_2, signatures[1]);
+ }
+
+ @Test
+ public void testGetLatestSignaturesWithCertHistory() {
+ SigningInfo signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[]{SIGNATURE_1},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ new Signature[]{SIGNATURE_2, SIGNATURE_3}));
+ Signature[] signatures = LauncherAppsService.LauncherAppsImpl.getLatestSignatures(
+ signingInfo);
+ assertEquals(1, signatures.length);
+ assertEquals(SIGNATURE_2, signatures[0]);
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
new file mode 100644
index 0000000..73e9613
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
@@ -0,0 +1,305 @@
+/*
+ * 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 com.android.server.pm;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
+import android.content.pm.PackageInstaller;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.Xml;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.FastXmlSerializer;
+
+import libcore.io.IoUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class PackageInstallerSessionTest {
+ private File mTmpDir;
+ private AtomicFile mSessionsFile;
+ private static final String TAG_SESSIONS = "sessions";
+
+ @Mock
+ PackageManagerService mMockPackageManagerInternal;
+
+ @Before
+ public void setUp() throws Exception {
+ mTmpDir = IoUtils.createTemporaryDirectory("PackageInstallerSessionTest");
+ mSessionsFile = new AtomicFile(
+ new File(mTmpDir.getAbsolutePath() + "/sessions.xml"), "package-session");
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testWriteAndRestoreSessionXmlSimpleSession() {
+ PackageInstallerSession session = createSimpleSession();
+ dumpSession(session);
+ List<PackageInstallerSession> restored = restoreSessions();
+ assertEquals(1, restored.size());
+ assertSessionsEquivalent(session, restored.get(0));
+ }
+
+ @Test
+ public void testWriteAndRestoreSessionXmlStagedSession() {
+ PackageInstallerSession session = createStagedSession();
+ dumpSession(session);
+ List<PackageInstallerSession> restored = restoreSessions();
+ assertEquals(1, restored.size());
+ assertSessionsEquivalent(session, restored.get(0));
+ }
+
+ @Test
+ public void testWriteAndRestoreSessionXmlGrantedPermission() {
+ PackageInstallerSession session = createSessionWithGrantedPermissions();
+ dumpSession(session);
+ List<PackageInstallerSession> restored = restoreSessions();
+ assertEquals(1, restored.size());
+ assertSessionsEquivalent(session, restored.get(0));
+ }
+
+ @Test
+ public void testWriteAndRestoreSessionXmlMultiPackageSessions() {
+ PackageInstallerSession session = createMultiPackageParentSession(123, new int[]{234, 345});
+ PackageInstallerSession childSession1 = createMultiPackageChildSession(234, 123);
+ PackageInstallerSession childSession2 = createMultiPackageChildSession(345, 123);
+ List<PackageInstallerSession> sessionGroup =
+ Arrays.asList(session, childSession1, childSession2);
+ dumpSessions(sessionGroup);
+ List<PackageInstallerSession> restored = restoreSessions();
+ assertEquals(3, restored.size());
+ assertSessionsEquivalent(sessionGroup, restored);
+ }
+
+ private PackageInstallerSession createSimpleSession() {
+ return createSession(false, false, 123, false, PackageInstaller.SessionInfo.INVALID_ID,
+ null);
+ }
+
+ private PackageInstallerSession createStagedSession() {
+ return createSession(true, false, 123, false, PackageInstaller.SessionInfo.INVALID_ID,
+ null);
+ }
+
+ private PackageInstallerSession createSessionWithGrantedPermissions() {
+ return createSession(false, true, 123, false, PackageInstaller.SessionInfo.INVALID_ID,
+ null);
+ }
+
+ private PackageInstallerSession createMultiPackageParentSession(int sessionId,
+ int[] childSessionIds) {
+ return createSession(false, false, sessionId, true,
+ PackageInstaller.SessionInfo.INVALID_ID, childSessionIds);
+ }
+
+ private PackageInstallerSession createMultiPackageChildSession(int sessionId,
+ int parentSessionId) {
+ return createSession(false, false, sessionId, false, parentSessionId, null);
+ }
+
+ private PackageInstallerSession createSession(boolean staged, boolean withGrantedPermissions,
+ int sessionId, boolean isMultiPackage,
+ int parentSessionId, int[] childSessionIds) {
+ PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ if (staged) {
+ params.isStaged = true;
+ }
+ if (withGrantedPermissions) {
+ params.grantedRuntimePermissions = new String[]{"permission1", "permission2"};
+ }
+ if (isMultiPackage) {
+ params.isMultiPackage = true;
+ }
+ return new PackageInstallerSession(
+ /* callback */ null,
+ /* context */null,
+ /* pm */ mMockPackageManagerInternal,
+ /* sessionProvider */ null,
+ /* looper */ BackgroundThread.getHandler().getLooper(),
+ /* stagingManager */ null,
+ /* sessionId */ sessionId,
+ /* userId */ 456,
+ /* installerPackageName */ "testInstaller",
+ /* installerUid */ -1,
+ /* sessionParams */ params,
+ /* createdMillis */ 0L,
+ /* stageDir */ mTmpDir,
+ /* stageCid */ null,
+ /* prepared */ true,
+ /* sealed */ false, // Setting to true would trigger some PM logic.
+ /* childSessionIds */ childSessionIds != null ? childSessionIds : new int[0],
+ /* parentSessionId */ parentSessionId,
+ /* isReady */ staged ? true : false,
+ /* isFailed */ false,
+ /* isApplied */false,
+ /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.VERIFICATION_FAILED);
+ }
+
+ private void dumpSession(PackageInstallerSession session) {
+ dumpSessions(Arrays.asList(session));
+ }
+
+ private void dumpSessions(List<PackageInstallerSession> sessions) {
+ FileOutputStream fos = null;
+ try {
+ fos = mSessionsFile.startWrite();
+
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(fos, StandardCharsets.UTF_8.name());
+ out.startDocument(null, true);
+ out.startTag(null, TAG_SESSIONS);
+ for (PackageInstallerSession session : sessions) {
+ session.write(out, mTmpDir);
+ }
+ out.endTag(null, TAG_SESSIONS);
+ out.endDocument();
+
+ mSessionsFile.finishWrite(fos);
+ Slog.d("PackageInstallerSessionTest", new String(mSessionsFile.readFully()));
+ } catch (IOException e) {
+ if (fos != null) {
+ mSessionsFile.failWrite(fos);
+ }
+ }
+ }
+
+ // This is roughly the logic used in PackageInstallerService to read the session. Note that
+ // this test stresses readFromXml method from PackageInstallerSession, and doesn't cover the
+ // PackageInstallerService portion of the parsing.
+ private List<PackageInstallerSession> restoreSessions() {
+ List<PackageInstallerSession> ret = new ArrayList<>();
+ FileInputStream fis = null;
+ try {
+ fis = mSessionsFile.openRead();
+ final XmlPullParser in = Xml.newPullParser();
+ in.setInput(fis, StandardCharsets.UTF_8.name());
+
+ int type;
+ while ((type = in.next()) != END_DOCUMENT) {
+ if (type == START_TAG) {
+ final String tag = in.getName();
+ if (PackageInstallerSession.TAG_SESSION.equals(tag)) {
+ final PackageInstallerSession session;
+ try {
+ session = PackageInstallerSession.readFromXml(in, null,
+ null, mMockPackageManagerInternal,
+ BackgroundThread.getHandler().getLooper(), null,
+ mTmpDir, null);
+ ret.add(session);
+ } catch (Exception e) {
+ Slog.e("PackageInstallerSessionTest", "Exception ", e);
+ continue;
+ }
+ }
+ }
+ }
+ } catch (FileNotFoundException e) {
+ // Missing sessions are okay, probably first boot
+ } catch (IOException | XmlPullParserException e) {
+
+ } finally {
+ IoUtils.closeQuietly(fis);
+ }
+ return ret;
+ }
+
+ private void assertSessionParamsEquivalent(PackageInstaller.SessionParams expected,
+ PackageInstaller.SessionParams actual) {
+ assertEquals(expected.mode, actual.mode);
+ assertEquals(expected.installFlags, actual.installFlags);
+ assertEquals(expected.installLocation, actual.installLocation);
+ assertEquals(expected.installReason, actual.installReason);
+ assertEquals(expected.sizeBytes, actual.sizeBytes);
+ assertEquals(expected.appPackageName, actual.appPackageName);
+ assertEquals(expected.appIcon, actual.appIcon);
+ assertEquals(expected.originatingUri, actual.originatingUri);
+ assertEquals(expected.originatingUid, actual.originatingUid);
+ assertEquals(expected.referrerUri, actual.referrerUri);
+ assertEquals(expected.abiOverride, actual.abiOverride);
+ assertEquals(expected.volumeUuid, actual.volumeUuid);
+ assertArrayEquals(expected.grantedRuntimePermissions, actual.grantedRuntimePermissions);
+ assertEquals(expected.installerPackageName, actual.installerPackageName);
+ assertEquals(expected.isMultiPackage, actual.isMultiPackage);
+ assertEquals(expected.isStaged, actual.isStaged);
+ }
+
+ private void assertSessionsEquivalent(List<PackageInstallerSession> expected,
+ List<PackageInstallerSession> actual) {
+ assertEquals(expected.size(), actual.size());
+ for (PackageInstallerSession expectedSession : expected) {
+ boolean foundSession = false;
+ for (PackageInstallerSession actualSession : actual) {
+ if (expectedSession.sessionId == actualSession.sessionId) {
+ // We should only encounter each expected session once.
+ assertFalse(foundSession);
+ foundSession = true;
+ assertSessionsEquivalent(expectedSession, actualSession);
+ }
+ }
+ assertTrue(foundSession);
+ }
+ }
+
+ private void assertSessionsEquivalent(PackageInstallerSession expected,
+ PackageInstallerSession actual) {
+ assertEquals(expected.sessionId, actual.sessionId);
+ assertEquals(expected.userId, actual.userId);
+ assertSessionParamsEquivalent(expected.params, actual.params);
+ assertEquals(expected.getInstallerUid(), actual.getInstallerUid());
+ assertEquals(expected.stageDir.getAbsolutePath(), actual.stageDir.getAbsolutePath());
+ assertEquals(expected.stageCid, actual.stageCid);
+ assertEquals(expected.isPrepared(), actual.isPrepared());
+ assertEquals(expected.isStaged(), actual.isStaged());
+ assertEquals(expected.isStagedSessionApplied(), actual.isStagedSessionApplied());
+ assertEquals(expected.isStagedSessionFailed(), actual.isStagedSessionFailed());
+ assertEquals(expected.isStagedSessionReady(), actual.isStagedSessionReady());
+ assertEquals(expected.getStagedSessionErrorCode(), actual.getStagedSessionErrorCode());
+ assertEquals(expected.isPrepared(), actual.isPrepared());
+ assertEquals(expected.isSealed(), actual.isSealed());
+ assertEquals(expected.isMultiPackage(), actual.isMultiPackage());
+ assertEquals(expected.hasParentSessionId(), actual.hasParentSessionId());
+ assertEquals(expected.getParentSessionId(), actual.getParentSessionId());
+ assertArrayEquals(expected.getChildSessionIds(), actual.getChildSessionIds());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java
index f817e8e..6da202b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java
@@ -16,8 +16,6 @@
package com.android.server.pm.dex;
-import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_DEX;
-
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.atMost;
@@ -26,10 +24,12 @@
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
+import android.os.UserHandle;
import android.os.storage.StorageManager;
import androidx.test.filters.SmallTest;
@@ -56,40 +56,44 @@
public class DexLoggerTests {
private static final String OWNING_PACKAGE_NAME = "package.name";
private static final String VOLUME_UUID = "volUuid";
- private static final String DEX_PATH = "/bar/foo.jar";
+ private static final String FILE_PATH = "/bar/foo.jar";
private static final int STORAGE_FLAGS = StorageManager.FLAG_STORAGE_DE;
private static final int OWNER_UID = 43;
private static final int OWNER_USER_ID = 44;
// Obtained via: echo -n "foo.jar" | sha256sum
- private static final String DEX_FILENAME_HASH =
+ private static final String FILENAME_HASH =
"91D7B844D7CC9673748FF057D8DC83972280FC28537D381AA42015A9CF214B9F";
- private static final byte[] CONTENT_HASH_BYTES = new byte[] {
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
- 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32
+ private static final byte[] CONTENT_HASH_BYTES = new byte[]{
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32
};
private static final String CONTENT_HASH =
"0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20";
private static final byte[] EMPTY_BYTES = {};
- @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
+ private static final String EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH =
+ "dcl:" + FILENAME_HASH;
+ private static final String EXPECTED_MESSAGE_WITH_CONTENT_HASH =
+ EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH + " " + CONTENT_HASH;
+ private static final String EXPECTED_MESSAGE_NATIVE_WITH_CONTENT_HASH =
+ "dcln:" + FILENAME_HASH + " " + CONTENT_HASH;
+
+ @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.LENIENT);
@Mock IPackageManager mPM;
@Mock Installer mInstaller;
- private PackageDynamicCodeLoading mPackageDynamicCodeLoading;
private DexLogger mDexLogger;
private final ListMultimap<Integer, String> mMessagesForUid = ArrayListMultimap.create();
private boolean mWriteTriggered = false;
- private static final String EXPECTED_MESSAGE_WITH_CONTENT_HASH =
- DEX_FILENAME_HASH + " " + CONTENT_HASH;
@Before
public void setup() throws Exception {
// Disable actually attempting to do file writes.
- mPackageDynamicCodeLoading = new PackageDynamicCodeLoading() {
+ PackageDynamicCodeLoading packageDynamicCodeLoading = new PackageDynamicCodeLoading() {
@Override
void maybeWriteAsync() {
mWriteTriggered = true;
@@ -102,13 +106,13 @@
};
// For test purposes capture log messages as well as sending to the event log.
- mDexLogger = new DexLogger(mPM, mInstaller, mPackageDynamicCodeLoading) {
+ mDexLogger = new DexLogger(mPM, mInstaller, packageDynamicCodeLoading) {
@Override
- void writeDclEvent(int uid, String message) {
- super.writeDclEvent(uid, message);
- mMessagesForUid.put(uid, message);
- }
- };
+ void writeDclEvent(String subtag, int uid, String message) {
+ super.writeDclEvent(subtag, uid, message);
+ mMessagesForUid.put(uid, subtag + ":" + message);
+ }
+ };
// Make the owning package exist in our mock PackageManager.
ApplicationInfo appInfo = new ApplicationInfo();
@@ -124,9 +128,9 @@
@Test
public void testOneLoader_ownFile_withFileHash() throws Exception {
- whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES));
+ whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES));
- recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+ recordLoad(OWNING_PACKAGE_NAME, FILE_PATH);
mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID);
@@ -139,13 +143,13 @@
@Test
public void testOneLoader_ownFile_noFileHash() throws Exception {
- whenFileIsHashed(DEX_PATH, doReturn(EMPTY_BYTES));
+ whenFileIsHashed(FILE_PATH, doReturn(EMPTY_BYTES));
- recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+ recordLoad(OWNING_PACKAGE_NAME, FILE_PATH);
mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID);
- assertThat(mMessagesForUid).containsEntry(OWNER_UID, DEX_FILENAME_HASH);
+ assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH);
// File should be removed from the DCL list, since we can't hash it.
assertThat(mWriteTriggered).isTrue();
@@ -154,13 +158,14 @@
@Test
public void testOneLoader_ownFile_hashingFails() throws Exception {
- whenFileIsHashed(DEX_PATH, doThrow(new InstallerException("Intentional failure for test")));
+ whenFileIsHashed(FILE_PATH,
+ doThrow(new InstallerException("Intentional failure for test")));
- recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+ recordLoad(OWNING_PACKAGE_NAME, FILE_PATH);
mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID);
- assertThat(mMessagesForUid).containsEntry(OWNER_UID, DEX_FILENAME_HASH);
+ assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH);
// File should be removed from the DCL list, since we can't hash it.
assertThat(mWriteTriggered).isTrue();
@@ -178,11 +183,23 @@
}
@Test
+ public void testOneLoader_pathTraversal() throws Exception {
+ String filePath = "/bar/../secret/foo.jar";
+ whenFileIsHashed(filePath, doReturn(CONTENT_HASH_BYTES));
+ setPackageUid(OWNING_PACKAGE_NAME, -1);
+
+ recordLoad(OWNING_PACKAGE_NAME, filePath);
+ mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
+
+ assertThat(mMessagesForUid).isEmpty();
+ }
+
+ @Test
public void testOneLoader_differentOwner() throws Exception {
- whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES));
+ whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES));
setPackageUid("other.package.name", 1001);
- recordLoad("other.package.name", DEX_PATH);
+ recordLoad("other.package.name", FILE_PATH);
mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
assertThat(mMessagesForUid.keys()).containsExactly(1001);
@@ -192,10 +209,10 @@
@Test
public void testOneLoader_differentOwner_uninstalled() throws Exception {
- whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES));
+ whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES));
setPackageUid("other.package.name", -1);
- recordLoad("other.package.name", DEX_PATH);
+ recordLoad("other.package.name", FILE_PATH);
mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
assertThat(mMessagesForUid).isEmpty();
@@ -203,22 +220,38 @@
}
@Test
+ public void testNativeCodeLoad() throws Exception {
+ whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES));
+
+ recordLoadNative(FILE_PATH);
+ mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
+
+ assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID);
+ assertThat(mMessagesForUid)
+ .containsEntry(OWNER_UID, EXPECTED_MESSAGE_NATIVE_WITH_CONTENT_HASH);
+
+ assertThat(mWriteTriggered).isFalse();
+ assertThat(mDexLogger.getAllPackagesWithDynamicCodeLoading())
+ .containsExactly(OWNING_PACKAGE_NAME);
+ }
+
+ @Test
public void testMultipleLoadersAndFiles() throws Exception {
String otherDexPath = "/bar/nosuchdir/foo.jar";
- whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES));
+ whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES));
whenFileIsHashed(otherDexPath, doReturn(EMPTY_BYTES));
setPackageUid("other.package.name1", 1001);
setPackageUid("other.package.name2", 1002);
- recordLoad("other.package.name1", DEX_PATH);
+ recordLoad("other.package.name1", FILE_PATH);
recordLoad("other.package.name1", otherDexPath);
- recordLoad("other.package.name2", DEX_PATH);
- recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+ recordLoad("other.package.name2", FILE_PATH);
+ recordLoad(OWNING_PACKAGE_NAME, FILE_PATH);
mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
assertThat(mMessagesForUid.keys()).containsExactly(1001, 1001, 1002, OWNER_UID);
assertThat(mMessagesForUid).containsEntry(1001, EXPECTED_MESSAGE_WITH_CONTENT_HASH);
- assertThat(mMessagesForUid).containsEntry(1001, DEX_FILENAME_HASH);
+ assertThat(mMessagesForUid).containsEntry(1001, EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH);
assertThat(mMessagesForUid).containsEntry(1002, EXPECTED_MESSAGE_WITH_CONTENT_HASH);
assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITH_CONTENT_HASH);
@@ -233,7 +266,7 @@
@Test
public void testUnknownOwner() {
reset(mPM);
- recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+ recordLoad(OWNING_PACKAGE_NAME, FILE_PATH);
mDexLogger.logDynamicCodeLoading("other.package.name");
assertThat(mMessagesForUid).isEmpty();
@@ -244,7 +277,7 @@
@Test
public void testUninstalledPackage() {
reset(mPM);
- recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+ recordLoad(OWNING_PACKAGE_NAME, FILE_PATH);
mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
assertThat(mMessagesForUid).isEmpty();
@@ -262,7 +295,16 @@
}
private void recordLoad(String loadingPackageName, String dexPath) {
- mPackageDynamicCodeLoading.record(
- OWNING_PACKAGE_NAME, dexPath, FILE_TYPE_DEX, OWNER_USER_ID, loadingPackageName);
+ mDexLogger.recordDex(OWNER_USER_ID, dexPath, OWNING_PACKAGE_NAME, loadingPackageName);
+ mWriteTriggered = false;
+ }
+
+ private void recordLoadNative(String nativePath) throws Exception {
+ int loadingUid = UserHandle.getUid(OWNER_USER_ID, OWNER_UID);
+ String[] packageNames = { OWNING_PACKAGE_NAME };
+ when(mPM.getPackagesForUid(loadingUid)).thenReturn(packageNames);
+
+ mDexLogger.recordNative(loadingUid, nativePath);
+ mWriteTriggered = false;
}
}
diff --git a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
index 860656b..8d9b3cf 100644
--- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
+++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
@@ -45,6 +45,8 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class UsageStatsDatabaseTest {
+
+ private static final int MAX_TESTED_VERSION = 4;
protected Context mContext;
private UsageStatsDatabase mUsageStatsDatabase;
private File mTestDir;
@@ -131,8 +133,8 @@
for (int i = 0; i < numberOfEvents; i++) {
Event event = new Event();
- final int packageInt = ((i / 3) % 7);
- event.mPackage = "fake.package.name" + packageInt; //clusters of 3 events from 7 "apps"
+ final int packageInt = ((i / 3) % 7); //clusters of 3 events from 7 "apps"
+ event.mPackage = "fake.package.name" + packageInt;
if (packageInt == 3) {
// Third app is an instant app
event.mFlags |= Event.FLAG_IS_PACKAGE_INSTANT_APP;
@@ -144,6 +146,13 @@
event.mEventType = i % (MAX_EVENT_TYPE + 1); //"random" event type
event.mInstanceId = instanceId;
+
+ final int rootPackageInt = (i % 5); // 5 "apps" start each task
+ event.mTaskRootPackage = "fake.package.name" + rootPackageInt;
+
+ final int rootClassInt = i % 6;
+ event.mTaskRootClass = ".fake.class.name" + rootClassInt;
+
switch (event.mEventType) {
case Event.CONFIGURATION_CHANGE:
//empty config,
@@ -163,7 +172,7 @@
break;
}
- mIntervalStats.events.insert(event);
+ mIntervalStats.addEvent(event);
mIntervalStats.update(event.mPackage, event.mClass, event.mTimeStamp, event.mEventType,
event.mInstanceId);
@@ -234,31 +243,40 @@
assertEquals(us1.mChooserCounts, us2.mChooserCounts);
}
- void compareUsageEvent(Event e1, Event e2, int debugId) {
- assertEquals(e1.mPackage, e2.mPackage, "Usage event " + debugId);
- assertEquals(e1.mClass, e2.mClass, "Usage event " + debugId);
- assertEquals(e1.mTimeStamp, e2.mTimeStamp, "Usage event " + debugId);
- assertEquals(e1.mEventType, e2.mEventType, "Usage event " + debugId);
- switch (e1.mEventType) {
- case Event.CONFIGURATION_CHANGE:
- assertEquals(e1.mConfiguration, e2.mConfiguration,
- "Usage event " + debugId + e2.mConfiguration.toString());
- break;
- case Event.SHORTCUT_INVOCATION:
- assertEquals(e1.mShortcutId, e2.mShortcutId, "Usage event " + debugId);
- break;
- case Event.STANDBY_BUCKET_CHANGED:
- assertEquals(e1.mBucketAndReason, e2.mBucketAndReason, "Usage event " + debugId);
- break;
- case Event.NOTIFICATION_INTERRUPTION:
- assertEquals(e1.mNotificationChannelId, e2.mNotificationChannelId,
- "Usage event " + debugId);
- break;
+ void compareUsageEvent(Event e1, Event e2, int debugId, int minVersion) {
+ switch (minVersion) {
+ case 4: // test fields added in version 4
+ assertEquals(e1.mInstanceId, e2.mInstanceId, "Usage event " + debugId);
+ assertEquals(e1.mTaskRootPackage, e2.mTaskRootPackage, "Usage event " + debugId);
+ assertEquals(e1.mTaskRootClass, e2.mTaskRootClass, "Usage event " + debugId);
+ // fallthrough
+ default:
+ assertEquals(e1.mPackage, e2.mPackage, "Usage event " + debugId);
+ assertEquals(e1.mClass, e2.mClass, "Usage event " + debugId);
+ assertEquals(e1.mTimeStamp, e2.mTimeStamp, "Usage event " + debugId);
+ assertEquals(e1.mEventType, e2.mEventType, "Usage event " + debugId);
+ switch (e1.mEventType) {
+ case Event.CONFIGURATION_CHANGE:
+ assertEquals(e1.mConfiguration, e2.mConfiguration,
+ "Usage event " + debugId + e2.mConfiguration.toString());
+ break;
+ case Event.SHORTCUT_INVOCATION:
+ assertEquals(e1.mShortcutId, e2.mShortcutId, "Usage event " + debugId);
+ break;
+ case Event.STANDBY_BUCKET_CHANGED:
+ assertEquals(e1.mBucketAndReason, e2.mBucketAndReason,
+ "Usage event " + debugId);
+ break;
+ case Event.NOTIFICATION_INTERRUPTION:
+ assertEquals(e1.mNotificationChannelId, e2.mNotificationChannelId,
+ "Usage event " + debugId);
+ break;
+ }
+ assertEquals(e1.mFlags, e2.mFlags);
}
- assertEquals(e1.mFlags, e2.mFlags);
}
- void compareIntervalStats(IntervalStats stats1, IntervalStats stats2) {
+ void compareIntervalStats(IntervalStats stats1, IntervalStats stats2, int minVersion) {
assertEquals(stats1.majorVersion, stats2.majorVersion);
assertEquals(stats1.minorVersion, stats2.minorVersion);
assertEquals(stats1.beginTime, stats2.beginTime);
@@ -311,7 +329,7 @@
} else {
assertEquals(stats1.events.size(), stats2.events.size());
for (int i = 0; i < stats1.events.size(); i++) {
- compareUsageEvent(stats1.events.get(i), stats2.events.get(i), i);
+ compareUsageEvent(stats1.events.get(i), stats2.events.get(i), i, minVersion);
}
}
}
@@ -326,7 +344,7 @@
mIntervalStatsVerifier);
assertEquals(1, stats.size());
- compareIntervalStats(mIntervalStats, stats.get(0));
+ compareIntervalStats(mIntervalStats, stats.get(0), MAX_TESTED_VERSION);
}
/**
@@ -359,8 +377,10 @@
mIntervalStatsVerifier);
assertEquals(1, stats.size());
+
+ final int minVersion = oldVersion < newVersion ? oldVersion : newVersion;
// The written and read IntervalStats should match
- compareIntervalStats(mIntervalStats, stats.get(0));
+ compareIntervalStats(mIntervalStats, stats.get(0), minVersion);
}
/**
@@ -401,7 +421,7 @@
if (mIntervalStats.events != null) mIntervalStats.events.clear();
// The written and read IntervalStats should match
- compareIntervalStats(mIntervalStats, stats.get(0));
+ compareIntervalStats(mIntervalStats, stats.get(0), version);
}
/**
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 94b21af..c0f9b80 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -185,6 +185,9 @@
private NotificationChannel mTestNotificationChannel = new NotificationChannel(
TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
+
+ private static final int NOTIFICATION_LOCATION_UNKNOWN = 0;
+
@Mock
private NotificationListeners mListeners;
@Mock private NotificationAssistants mAssistants;
@@ -2528,11 +2531,13 @@
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
mService.addNotification(r);
- mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, true);
+ mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, true,
+ NOTIFICATION_LOCATION_UNKNOWN);
verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.sbn), eq(true), eq((true)));
assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
- mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, false);
+ mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, false,
+ NOTIFICATION_LOCATION_UNKNOWN);
verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.sbn), eq(true), eq((false)));
assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
}
@@ -2542,11 +2547,13 @@
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
mService.addNotification(r);
- mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true);
+ mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true,
+ NOTIFICATION_LOCATION_UNKNOWN);
assertFalse(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.sbn), eq(false), eq((true)));
- mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, false);
+ mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, false,
+ NOTIFICATION_LOCATION_UNKNOWN);
assertFalse(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
verify(mAssistants).notifyAssistantExpansionChangedLocked(
eq(r.sbn), eq(false), eq((false)));
@@ -3793,7 +3800,8 @@
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
mService.addNotification(r);
- mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true);
+ mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true,
+ NOTIFICATION_LOCATION_UNKNOWN);
NotificationVisibility[] notificationVisibility = new NotificationVisibility[] {
NotificationVisibility.obtain(r.getKey(), 0, 0, true)
};
@@ -3808,7 +3816,8 @@
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
mService.addNotification(r);
- mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true);
+ mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true,
+ NOTIFICATION_LOCATION_UNKNOWN);
assertEquals(0, mService.countLogSmartSuggestionsVisible);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 319ffed..8be63fc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -31,7 +31,6 @@
import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT;
import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING;
import static com.android.server.wm.ActivityStack.ActivityState.PAUSING;
-import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
import static com.android.server.wm.ActivityStack.ActivityState.STOPPED;
import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_MOVING;
@@ -76,9 +75,6 @@
mStack = (TestActivityStack) new StackBuilder(mRootActivityContainer).build();
mTask = mStack.getChildAt(0);
mActivity = mTask.getTopActivity();
-
- doReturn(false).when(mService).isBooting();
- doReturn(true).when(mService).isBooted();
}
@Test
@@ -121,23 +117,22 @@
mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
- // The activity is in the focused stack so it should be resumed.
+ // The activity is in the focused stack so it should not move to paused.
mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
- assertTrue(mActivity.isState(RESUMED));
+ assertTrue(mActivity.isState(STOPPED));
assertFalse(pauseFound.value);
- // Make the activity non focusable
- mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
- doReturn(false).when(mActivity).isFocusable();
+ // Clear focused stack
+ final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+ when(display.getFocusedStack()).thenReturn(null);
- // If the activity is not focusable, it should move to paused.
+ // In the unfocused stack, the activity should move to paused.
mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
assertTrue(mActivity.isState(PAUSING));
assertTrue(pauseFound.value);
// Make sure that the state does not change for current non-stopping states.
mActivity.setState(INITIALIZING, "testPausingWhenVisibleFromStopped");
- doReturn(true).when(mActivity).isFocusable();
mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index ea8f33f..68df87e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -55,7 +55,6 @@
import android.hardware.display.DisplayManagerGlobal;
import android.os.Handler;
import android.os.Looper;
-import android.os.PowerManager;
import android.os.Process;
import android.os.UserHandle;
import android.service.voice.IVoiceInteractionSession;
@@ -426,7 +425,6 @@
doReturn(mock(IPackageManager.class)).when(this).getPackageManager();
// allow background activity starts by default
doReturn(true).when(this).isBackgroundActivityStartsEnabled();
- doNothing().when(this).updateCpuStats();
}
void setup(IntentFirewall intentFirewall, PendingIntentController intentController,
@@ -582,8 +580,6 @@
doNothing().when(this).acquireLaunchWakelock();
doReturn(mKeyguardController).when(this).getKeyguardController();
- mLaunchingActivity = mock(PowerManager.WakeLock.class);
-
initialize();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
index d0b9225..ea5ab7b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
@@ -70,9 +70,9 @@
mToken.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
verify(mTransaction).reparent(eq(mToken.getSurfaceControl()),
- eq(mToken.mSurfaceAnimator.mLeash.getHandle()));
+ eq(mToken.mSurfaceAnimator.mLeash));
verify(mTransaction).reparent(eq(mToken.mSurfaceAnimator.mLeash),
- eq(mToken.mAnimationBoundsLayer.getHandle()));
+ eq(mToken.mAnimationBoundsLayer));
}
@Test
@@ -111,7 +111,7 @@
mToken.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
verify(mTransaction).reparent(eq(mToken.getSurfaceControl()),
- eq(mToken.mSurfaceAnimator.mLeash.getHandle()));
+ eq(mToken.mSurfaceAnimator.mLeash));
assertThat(mToken.mAnimationBoundsLayer).isNull();
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
new file mode 100644
index 0000000..c52c8d7
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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 com.android.server.wm;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+
+import java.util.function.Supplier;
+
+@SmallTest
+@Presubmit
+public class LetterboxTest {
+
+ Letterbox mLetterbox;
+ SurfaceControlMocker mSurfaces;
+ SurfaceControl.Transaction mTransaction;
+
+ @Before
+ public void setUp() throws Exception {
+ mSurfaces = new SurfaceControlMocker();
+ mLetterbox = new Letterbox(mSurfaces);
+ mTransaction = mock(SurfaceControl.Transaction.class);
+ }
+
+ @Test
+ public void testOverlappingWith_usesGlobalCoordinates() {
+ mLetterbox.layout(new Rect(0, 0, 10, 50), new Rect(0, 2, 10, 45), new Point(1000, 2000));
+ assertTrue(mLetterbox.isOverlappingWith(new Rect(0, 0, 1, 1)));
+ }
+
+ @Test
+ public void testSurfaceOrigin_applied() {
+ mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
+ mLetterbox.applySurfaceChanges(mTransaction);
+ verify(mTransaction).setPosition(mSurfaces.top, -1000, -2000);
+ }
+
+ @Test
+ public void testSurfaceOrigin_changeCausesReapply() {
+ mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
+ mLetterbox.applySurfaceChanges(mTransaction);
+ clearInvocations(mTransaction);
+ mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(0, 0));
+ assertTrue(mLetterbox.needsApplySurfaceChanges());
+ mLetterbox.applySurfaceChanges(mTransaction);
+ verify(mTransaction).setPosition(mSurfaces.top, 0, 0);
+ }
+
+ class SurfaceControlMocker implements Supplier<SurfaceControl.Builder> {
+ private SurfaceControl.Builder mLeftBuilder;
+ public SurfaceControl left;
+ private SurfaceControl.Builder mTopBuilder;
+ public SurfaceControl top;
+ private SurfaceControl.Builder mRightBuilder;
+ public SurfaceControl right;
+ private SurfaceControl.Builder mBottomBuilder;
+ public SurfaceControl bottom;
+
+ @Override
+ public SurfaceControl.Builder get() {
+ final SurfaceControl.Builder builder = mock(SurfaceControl.Builder.class,
+ InvocationOnMock::getMock);
+ when(builder.setName(anyString())).then((i) -> {
+ if (((String) i.getArgument(0)).contains("left")) {
+ mLeftBuilder = (SurfaceControl.Builder) i.getMock();
+ } else if (((String) i.getArgument(0)).contains("top")) {
+ mTopBuilder = (SurfaceControl.Builder) i.getMock();
+ } else if (((String) i.getArgument(0)).contains("right")) {
+ mRightBuilder = (SurfaceControl.Builder) i.getMock();
+ } else if (((String) i.getArgument(0)).contains("bottom")) {
+ mBottomBuilder = (SurfaceControl.Builder) i.getMock();
+ }
+ return i.getMock();
+ });
+
+ doAnswer((i) -> {
+ final SurfaceControl control = mock(SurfaceControl.class);
+ if (i.getMock() == mLeftBuilder) {
+ left = control;
+ } else if (i.getMock() == mTopBuilder) {
+ top = control;
+ } else if (i.getMock() == mRightBuilder) {
+ right = control;
+ } else if (i.getMock() == mBottomBuilder) {
+ bottom = control;
+ }
+ return control;
+ }).when(builder).build();
+ return builder;
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
index ad80cd6..9b84215 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -90,7 +90,7 @@
final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
OnAnimationFinishedCallback.class);
assertAnimating(mAnimatable);
- verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash.getHandle()));
+ verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash));
verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture());
callbackCaptor.getValue().onAnimationFinished(mSpec);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
index b996bfb..c595868 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
@@ -19,6 +19,10 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.TaskPositioner.MIN_ASPECT;
import static com.android.server.wm.WindowManagerService.dipToPixel;
import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP;
@@ -38,13 +42,12 @@
import org.junit.Before;
import org.junit.Test;
-import org.mockito.Mockito;
/**
* Tests for the {@link TaskPositioner} class.
*
* Build/Install/Run:
- * atest FrameworksServicesTests:TaskPositionerTests
+ * atest WmTests:TaskPositionerTests
*/
@SmallTest
public class TaskPositionerTests extends WindowTestsBase {
@@ -73,18 +76,17 @@
mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, dm);
mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, dm);
- mPositioner = new TaskPositioner(mWm, Mockito.mock(IActivityTaskManager.class));
+ mPositioner = new TaskPositioner(mWm, mock(IActivityTaskManager.class));
mPositioner.register(mDisplayContent);
- mWindow = Mockito.spy(createWindow(null, TYPE_BASE_APPLICATION, "window"));
- final Task task = Mockito.spy(mWindow.getTask());
- Mockito.when(mWindow.getTask()).thenReturn(task);
-
- Mockito.doAnswer(invocation -> {
+ mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window");
+ final Task task = mWindow.getTask();
+ spyOn(task);
+ doAnswer(invocation -> {
final Rect rect = (Rect) invocation.getArguments()[0];
rect.set(mDimBounds);
- return (Void) null;
- }).when(task).getDimBounds(Mockito.any(Rect.class));
+ return null;
+ }).when(task).getDimBounds(any(Rect.class));
mWindow.getStack().setWindowingMode(WINDOWING_MODE_FREEFORM);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
index 946ffb60..d29e3fa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -53,7 +53,7 @@
public void setUp() {
final UserManager um = UserManager.get(getInstrumentation().getTargetContext());
mTestUserId = um.getUserHandle();
- mPersister = new TaskSnapshotPersister(userId -> FILES_DIR);
+ mPersister = new TaskSnapshotPersister(mWm, userId -> FILES_DIR);
mLoader = new TaskSnapshotLoader(mPersister);
mPersister.start();
}
diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java
index 9a5bd13..f1ddfe4 100644
--- a/services/usage/java/com/android/server/usage/IntervalStats.java
+++ b/services/usage/java/com/android/server/usage/IntervalStats.java
@@ -218,6 +218,14 @@
case (int) IntervalStatsProto.Event.INSTANCE_ID:
event.mInstanceId = parser.readInt(IntervalStatsProto.Event.INSTANCE_ID);
break;
+ case (int) IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX:
+ event.mTaskRootPackage = getCachedStringRef(stringPool.get(
+ parser.readInt(IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX) - 1));
+ break;
+ case (int) IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX:
+ event.mTaskRootClass = getCachedStringRef(stringPool.get(
+ parser.readInt(IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX) - 1));
+ break;
case ProtoInputStream.NO_MORE_FIELDS:
// Handle default values for certain events types
switch (event.mEventType) {
@@ -332,6 +340,12 @@
if (event.mClass != null) {
event.mClass = getCachedStringRef(event.mClass);
}
+ if (event.mTaskRootPackage != null) {
+ event.mTaskRootPackage = getCachedStringRef(event.mTaskRootPackage);
+ }
+ if (event.mTaskRootClass != null) {
+ event.mTaskRootClass = getCachedStringRef(event.mTaskRootClass);
+ }
if (event.mEventType == NOTIFICATION_INTERRUPTION) {
event.mNotificationChannelId = getCachedStringRef(event.mNotificationChannelId);
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsProto.java b/services/usage/java/com/android/server/usage/UsageStatsProto.java
index d706537..11d49eb 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsProto.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsProto.java
@@ -442,6 +442,28 @@
proto.write(IntervalStatsProto.Event.FLAGS, event.mFlags);
proto.write(IntervalStatsProto.Event.TYPE, event.mEventType);
proto.write(IntervalStatsProto.Event.INSTANCE_ID, event.mInstanceId);
+ if (event.mTaskRootPackage != null) {
+ final int taskRootPackageIndex = stats.mStringCache.indexOf(event.mTaskRootPackage);
+ if (taskRootPackageIndex >= 0) {
+ proto.write(IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX,
+ taskRootPackageIndex + 1);
+ } else {
+ // Task root package not in Stringpool for some reason.
+ Slog.w(TAG, "Usage event task root package name (" + event.mTaskRootPackage
+ + ") not found in IntervalStats string cache");
+ }
+ }
+ if (event.mTaskRootClass != null) {
+ final int taskRootClassIndex = stats.mStringCache.indexOf(event.mTaskRootClass);
+ if (taskRootClassIndex >= 0) {
+ proto.write(IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX,
+ taskRootClassIndex + 1);
+ } else {
+ // Task root class not in Stringpool for some reason.
+ Slog.w(TAG, "Usage event task root class name (" + event.mTaskRootClass
+ + ") not found in IntervalStats string cache");
+ }
+ }
switch (event.mEventType) {
case UsageEvents.Event.CONFIGURATION_CHANGE:
if (event.mConfiguration != null) {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 76a3aa8..6ad698b 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -22,6 +22,8 @@
import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK;
import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION;
import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION;
+import static android.app.usage.UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY;
+import static android.app.usage.UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY;
import android.Manifest;
import android.app.ActivityManager;
@@ -39,6 +41,7 @@
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManager.StandbyBuckets;
+import android.app.usage.UsageStatsManager.UsageSource;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -65,6 +68,7 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
@@ -132,6 +136,7 @@
private File mUsageStatsDir;
long mRealTimeSnapshot;
long mSystemTimeSnapshot;
+ int mUsageSource;
/** Manages the standby state of apps. */
AppStandbyController mAppStandby;
@@ -258,6 +263,7 @@
} else {
Slog.w(TAG, "Missing procfs interface: " + KERNEL_COUNTER_FILE);
}
+ readUsageSourceSetting();
}
}
@@ -268,6 +274,13 @@
return mDpmInternal;
}
+ private void readUsageSourceSetting() {
+ synchronized (mLock) {
+ mUsageSource = Settings.Global.getInt(getContext().getContentResolver(),
+ Settings.Global.APP_TIME_LIMIT_USAGE_SOURCE, USAGE_SOURCE_TASK_ROOT_ACTIVITY);
+ }
+ }
+
private class UserActionsReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
@@ -459,12 +472,28 @@
service.reportEvent(event);
mAppStandby.reportEvent(event, elapsedRealtime, userId);
+
+ String packageName;
+
+ switch(mUsageSource) {
+ case USAGE_SOURCE_CURRENT_ACTIVITY:
+ packageName = event.getPackageName();
+ break;
+ case USAGE_SOURCE_TASK_ROOT_ACTIVITY:
+ default:
+ packageName = event.getTaskRootPackageName();
+ if (packageName == null) {
+ packageName = event.getPackageName();
+ }
+ break;
+ }
+
switch (event.mEventType) {
case Event.ACTIVITY_RESUMED:
synchronized (mVisibleActivities) {
mVisibleActivities.put(event.mInstanceId, event.getClassName());
try {
- mAppTimeLimit.noteUsageStart(event.getPackageName(), userId);
+ mAppTimeLimit.noteUsageStart(packageName, userId);
} catch (IllegalArgumentException iae) {
Slog.e(TAG, "Failed to note usage start", iae);
}
@@ -496,7 +525,7 @@
synchronized (mVisibleActivities) {
if (mVisibleActivities.removeReturnOld(event.mInstanceId) != null) {
try {
- mAppTimeLimit.noteUsageStop(event.getPackageName(), userId);
+ mAppTimeLimit.noteUsageStop(packageName, userId);
} catch (IllegalArgumentException iae) {
Slog.w(TAG, "Failed to note usage stop", iae);
}
@@ -638,7 +667,7 @@
* Called by the Binder stub.
*/
UsageEvents queryEventsForPackage(int userId, long beginTime, long endTime,
- String packageName) {
+ String packageName, boolean includeTaskRoot) {
synchronized (mLock) {
final long timeNow = checkAndGetTimeLocked();
if (!validRange(timeNow, beginTime, endTime)) {
@@ -647,7 +676,7 @@
final UserUsageStatsService service =
getUserDataAndInitializeIfNeededLocked(userId, timeNow);
- return service.queryEventsForPackage(beginTime, endTime, packageName);
+ return service.queryEventsForPackage(beginTime, endTime, packageName, includeTaskRoot);
}
}
@@ -738,6 +767,10 @@
mAppStandby.dumpState(args, pw);
}
+ idpw.println();
+ idpw.printPair("Usage Source", UsageStatsManager.usageSourceToString(mUsageSource));
+ idpw.println();
+
mAppTimeLimit.dump(null, pw);
}
}
@@ -808,7 +841,7 @@
return mode == AppOpsManager.MODE_ALLOWED;
}
- private boolean hasObserverPermission(String callingPackage) {
+ private boolean hasObserverPermission() {
final int callingUid = Binder.getCallingUid();
DevicePolicyManagerInternal dpmInternal = getDpmInternal();
if (callingUid == Process.SYSTEM_UID
@@ -939,10 +972,12 @@
final int callingUserId = UserHandle.getUserId(callingUid);
checkCallerIsSameApp(callingPackage);
+ final boolean includeTaskRoot = hasPermission(callingPackage);
+
final long token = Binder.clearCallingIdentity();
try {
return UsageStatsService.this.queryEventsForPackage(callingUserId, beginTime,
- endTime, callingPackage);
+ endTime, callingPackage, includeTaskRoot);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -989,7 +1024,7 @@
final long token = Binder.clearCallingIdentity();
try {
return UsageStatsService.this.queryEventsForPackage(userId, beginTime,
- endTime, pkg);
+ endTime, pkg, true);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1229,7 +1264,7 @@
public void registerAppUsageObserver(int observerId,
String[] packages, long timeLimitMs, PendingIntent
callbackIntent, String callingPackage) {
- if (!hasObserverPermission(callingPackage)) {
+ if (!hasObserverPermission()) {
throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
}
@@ -1252,7 +1287,7 @@
@Override
public void unregisterAppUsageObserver(int observerId, String callingPackage) {
- if (!hasObserverPermission(callingPackage)) {
+ if (!hasObserverPermission()) {
throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
}
@@ -1271,7 +1306,7 @@
long timeLimitMs, long sessionThresholdTimeMs,
PendingIntent limitReachedCallbackIntent, PendingIntent sessionEndCallbackIntent,
String callingPackage) {
- if (!hasObserverPermission(callingPackage)) {
+ if (!hasObserverPermission()) {
throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
}
@@ -1295,7 +1330,7 @@
@Override
public void unregisterUsageSessionObserver(int sessionObserverId, String callingPackage) {
- if (!hasObserverPermission(callingPackage)) {
+ if (!hasObserverPermission()) {
throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
}
@@ -1373,6 +1408,21 @@
Binder.restoreCallingIdentity(binderToken);
}
}
+
+ @Override
+ public @UsageSource int getUsageSource() {
+ if (!hasObserverPermission()) {
+ throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
+ }
+ synchronized (mLock) {
+ return mUsageSource;
+ }
+ }
+
+ @Override
+ public void forceUsageSourceSettingRead() {
+ readUsageSourceSetting();
+ }
}
void registerAppUsageObserver(int callingUid, int observerId, String[] packages,
@@ -1406,7 +1456,7 @@
@Override
public void reportEvent(ComponentName component, int userId, int eventType,
- int instanceId) {
+ int instanceId, ComponentName taskRoot) {
if (component == null) {
Slog.w(TAG, "Event reported without a component name");
return;
@@ -1416,6 +1466,13 @@
event.mPackage = component.getPackageName();
event.mClass = component.getClassName();
event.mInstanceId = instanceId;
+ if (taskRoot == null) {
+ event.mTaskRootPackage = null;
+ event.mTaskRootClass = null;
+ } else {
+ event.mTaskRootPackage = taskRoot.getPackageName();
+ event.mTaskRootClass = taskRoot.getClassName();
+ }
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 2d1098c7..d52d32f 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -401,7 +401,7 @@
}
UsageEvents queryEvents(final long beginTime, final long endTime,
- boolean obfuscateInstantApps) {
+ boolean obfuscateInstantApps) {
final ArraySet<String> names = new ArraySet<>();
List<Event> results = queryStats(INTERVAL_DAILY,
beginTime, endTime, new StatCombiner<Event>() {
@@ -425,6 +425,12 @@
if (event.mClass != null) {
names.add(event.mClass);
}
+ if (event.mTaskRootPackage != null) {
+ names.add(event.mTaskRootPackage);
+ }
+ if (event.mTaskRootClass != null) {
+ names.add(event.mTaskRootClass);
+ }
accumulatedResult.add(event);
}
}
@@ -436,11 +442,11 @@
String[] table = names.toArray(new String[names.size()]);
Arrays.sort(table);
- return new UsageEvents(results, table);
+ return new UsageEvents(results, table, true);
}
UsageEvents queryEventsForPackage(final long beginTime, final long endTime,
- final String packageName) {
+ final String packageName, boolean includeTaskRoot) {
final ArraySet<String> names = new ArraySet<>();
names.add(packageName);
final List<Event> results = queryStats(INTERVAL_DAILY,
@@ -459,6 +465,12 @@
if (event.mClass != null) {
names.add(event.mClass);
}
+ if (includeTaskRoot && event.mTaskRootPackage != null) {
+ names.add(event.mTaskRootPackage);
+ }
+ if (includeTaskRoot && event.mTaskRootClass != null) {
+ names.add(event.mTaskRootClass);
+ }
accumulatedResult.add(event);
}
});
@@ -469,7 +481,7 @@
final String[] table = names.toArray(new String[names.size()]);
Arrays.sort(table);
- return new UsageEvents(results, table);
+ return new UsageEvents(results, table, includeTaskRoot);
}
void persistActiveStats() {
@@ -684,6 +696,14 @@
pw.printPair("instanceId", event.getInstanceId());
}
+ if (event.getTaskRootPackageName() != null) {
+ pw.printPair("taskRootPackage", event.getTaskRootPackageName());
+ }
+
+ if (event.getTaskRootClassName() != null) {
+ pw.printPair("taskRootClass", event.getTaskRootClassName());
+ }
+
if (event.mNotificationChannelId != null) {
pw.printPair("channelId", event.mNotificationChannelId);
}
diff --git a/startop/OWNERS b/startop/OWNERS
index bfe96d3..762cd8e 100644
--- a/startop/OWNERS
+++ b/startop/OWNERS
@@ -3,3 +3,4 @@
eholk@google.com
iam@google.com
sehr@google.com
+mathieuc@google.com
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index a39e885..7d1f8ce 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
import android.telecom.Connection.VideoProvider;
@@ -64,6 +65,10 @@
public void onStatusHintsChanged(Conference conference, StatusHints statusHints) {}
public void onExtrasChanged(Conference c, Bundle extras) {}
public void onExtrasRemoved(Conference c, List<String> keys) {}
+ public void onConferenceStateChanged(Conference c, boolean isConference) {}
+ public void onAddressChanged(Conference c, Uri newAddress, int presentation) {}
+ public void onCallerDisplayNameChanged(
+ Conference c, String callerDisplayName, int presentation) {}
}
private final Set<Listener> mListeners = new CopyOnWriteArraySet<>();
@@ -946,6 +951,62 @@
public void onExtrasChanged(Bundle extras) {}
/**
+ * Set whether Telecom should treat this {@link Conference} as a conference call or if it
+ * should treat it as a single-party call.
+ * This method is used as part of a workaround regarding IMS conference calls and user
+ * expectation. In IMS, once a conference is formed, the UE is connected to an IMS conference
+ * server. If all participants of the conference drop out of the conference except for one, the
+ * UE is still connected to the IMS conference server. At this point, the user logically
+ * assumes they're no longer in a conference, yet the underlying network actually is.
+ * To help provide a better user experiece, IMS conference calls can pretend to actually be a
+ * single-party call when the participant count drops to 1. Although the dialer/phone app
+ * could perform this trickery, it makes sense to do this in Telephony since a fix there will
+ * ensure that bluetooth head units, auto and wearable apps all behave consistently.
+ *
+ * @param isConference {@code true} if this {@link Conference} should be treated like a
+ * conference call, {@code false} if it should be treated like a single-party call.
+ * @hide
+ */
+ public void setConferenceState(boolean isConference) {
+ for (Listener l : mListeners) {
+ l.onConferenceStateChanged(this, isConference);
+ }
+ }
+
+ /**
+ * Sets the address of this {@link Conference}. Used when {@link #setConferenceState(boolean)}
+ * is called to mark a conference temporarily as NOT a conference.
+ *
+ * @param address The new address.
+ * @param presentation The presentation requirements for the address.
+ * See {@link TelecomManager} for valid values.
+ * @hide
+ */
+ public final void setAddress(Uri address, int presentation) {
+ Log.d(this, "setAddress %s", address);
+ for (Listener l : mListeners) {
+ l.onAddressChanged(this, address, presentation);
+ }
+ }
+
+ /**
+ * Sets the caller display name (CNAP) of this {@link Conference}. Used when
+ * {@link #setConferenceState(boolean)} is called to mark a conference temporarily as NOT a
+ * conference.
+ *
+ * @param callerDisplayName The new display name.
+ * @param presentation The presentation requirements for the handle.
+ * See {@link TelecomManager} for valid values.
+ * @hide
+ */
+ public final void setCallerDisplayName(String callerDisplayName, int presentation) {
+ Log.d(this, "setCallerDisplayName %s", callerDisplayName);
+ for (Listener l : mListeners) {
+ l.onCallerDisplayNameChanged(this, callerDisplayName, presentation);
+ }
+ }
+
+ /**
* Handles a change to extras received from Telecom.
*
* @param extras The new extras.
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 4d5f5e1..9bafbe0 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -1254,6 +1254,31 @@
mAdapter.removeExtras(id, keys);
}
}
+
+ @Override
+ public void onConferenceStateChanged(Conference c, boolean isConference) {
+ String id = mIdByConference.get(c);
+ if (id != null) {
+ mAdapter.setConferenceState(id, isConference);
+ }
+ }
+
+ @Override
+ public void onAddressChanged(Conference c, Uri newAddress, int presentation) {
+ String id = mIdByConference.get(c);
+ if (id != null) {
+ mAdapter.setAddress(id, newAddress, presentation);
+ }
+ }
+
+ @Override
+ public void onCallerDisplayNameChanged(Conference c, String callerDisplayName,
+ int presentation) {
+ String id = mIdByConference.get(c);
+ if (id != null) {
+ mAdapter.setCallerDisplayName(id, callerDisplayName, presentation);
+ }
+ }
};
private final Connection.Listener mConnectionListener = new Connection.Listener() {
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
index 520e7ed..6c3f4f3 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
@@ -653,4 +653,22 @@
}
}
}
+
+ /**
+ * Sets whether a conference is treated as a conference or a single party call.
+ * See {@link Conference#setConferenceState(boolean)} for more information.
+ *
+ * @param callId The ID of the telecom call.
+ * @param isConference {@code true} if this call should be treated as a conference,
+ * {@code false} otherwise.
+ */
+ void setConferenceState(String callId, boolean isConference) {
+ Log.v(this, "setConferenceState: %s %b", callId, isConference);
+ for (IConnectionServiceAdapter adapter : mAdapters) {
+ try {
+ adapter.setConferenceState(callId, isConference, Log.getExternalSession());
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
}
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
index 78d65e6..f99b218 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
@@ -74,6 +74,7 @@
private static final int MSG_ON_RTT_UPGRADE_REQUEST = 33;
private static final int MSG_SET_PHONE_ACCOUNT_CHANGED = 34;
private static final int MSG_CONNECTION_SERVICE_FOCUS_RELEASED = 35;
+ private static final int MSG_SET_CONFERENCE_STATE = 36;
private final IConnectionServiceAdapter mDelegate;
@@ -333,6 +334,14 @@
case MSG_CONNECTION_SERVICE_FOCUS_RELEASED:
mDelegate.onConnectionServiceFocusReleased(null /*Session.Info*/);
break;
+ case MSG_SET_CONFERENCE_STATE:
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ mDelegate.setConferenceState((String) args.arg1, (Boolean) args.arg2,
+ (Session.Info) args.arg3);
+ } finally {
+ args.recycle();
+ }
}
}
};
@@ -615,6 +624,16 @@
public void resetConnectionTime(String callId, Session.Info sessionInfo) {
// Do nothing
}
+
+ @Override
+ public void setConferenceState(String callId, boolean isConference,
+ Session.Info sessionInfo) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = isConference;
+ args.arg3 = sessionInfo;
+ mHandler.obtainMessage(MSG_SET_CONFERENCE_STATE, args).sendToTarget();
+ }
};
public ConnectionServiceAdapterServant(IConnectionServiceAdapter delegate) {
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index 9821dcb..744544e 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -471,6 +471,12 @@
public void resetConnectionTime(String callId, Session.Info sessionInfo) {
// Do nothing
}
+
+ @Override
+ public void setConferenceState(String callId, boolean isConference,
+ Session.Info sessionInfo) {
+ // Do nothing
+ }
};
private final ConnectionServiceAdapterServant mServant =
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 6c4b1af8..0fe5e08 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -793,15 +793,17 @@
* <p>
* Apps must be prepared for this method to return {@code null}, indicating that there currently
* exists no user-chosen default {@code PhoneAccount}.
+ * <p>
+ * The default dialer has access to use this method.
*
* @return The user outgoing phone account selected by the user.
- * @hide
*/
- @UnsupportedAppUsage
+ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() {
try {
if (isServiceConnected()) {
- return getTelecomService().getUserSelectedOutgoingPhoneAccount();
+ return getTelecomService().getUserSelectedOutgoingPhoneAccount(
+ mContext.getOpPackageName());
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelecomService#getUserSelectedOutgoingPhoneAccount", e);
@@ -810,10 +812,14 @@
}
/**
- * Sets the user-chosen default for making outgoing phone calls.
+ * Sets the user-chosen default {@link PhoneAccountHandle} for making outgoing phone calls.
+ *
+ * @param accountHandle The {@link PhoneAccountHandle} which will be used by default for making
+ * outgoing voice calls.
* @hide
*/
- @UnsupportedAppUsage
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @SystemApi
public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
try {
if (isServiceConnected()) {
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
index 0157a58..76ac88e 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
@@ -123,4 +123,6 @@
void onConnectionServiceFocusReleased(in Session.Info sessionInfo);
void resetConnectionTime(String callIdi, in Session.Info sessionInfo);
+
+ void setConferenceState(String callId, boolean isConference, in Session.Info sessionInfo);
}
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 954a709..e1d5c17 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -45,7 +45,7 @@
/**
* @see TelecomServiceImpl#getUserSelectedOutgoingPhoneAccount
*/
- PhoneAccountHandle getUserSelectedOutgoingPhoneAccount();
+ PhoneAccountHandle getUserSelectedOutgoingPhoneAccount(String callingPackage);
/**
* @see TelecomServiceImpl#setUserSelectedOutgoingPhoneAccount
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 312b318..a33b44c 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2508,10 +2508,18 @@
public static final String KEY_GPS_LOCK_STRING = KEY_PREFIX + "gps_lock";
/**
- * SUPL NI emergency extension time in seconds. Default to "0".
+ * Control Plane / SUPL NI emergency extension time in seconds. Default to "0".
*/
public static final String KEY_ES_EXTENSION_SEC = KEY_PREFIX + "es_extension_sec";
+ /**
+ * Space separated list of Android package names of proxy applications representing
+ * the non-framework entities requesting location directly from GNSS without involving
+ * the framework, as managed by IGnssVisibilityControl.hal. For example,
+ * "com.example.mdt com.example.ims".
+ */
+ public static final String KEY_NFW_PROXY_APPS = KEY_PREFIX + "nfw_proxy_apps";
+
private static PersistableBundle getDefaults() {
PersistableBundle defaults = new PersistableBundle();
defaults.putBoolean(KEY_PERSIST_LPP_MODE_BOOL, true);
@@ -2525,6 +2533,7 @@
defaults.putString(KEY_A_GLONASS_POS_PROTOCOL_SELECT_STRING, "0");
defaults.putString(KEY_GPS_LOCK_STRING, "3");
defaults.putString(KEY_ES_EXTENSION_SEC, "0");
+ defaults.putString(KEY_NFW_PROXY_APPS, "");
return defaults;
}
}
diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java
index 7b29f69..30e641d 100644
--- a/telephony/java/android/telephony/CellSignalStrengthGsm.java
+++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java
@@ -148,8 +148,9 @@
/**
* Return the Bit Error Rate
- * @returns the bit error rate (0-7, 99) as defined in TS 27.007 8.5 or UNAVAILABLE.
- * @hide
+ *
+ * @return the bit error rate (0-7, 99) as defined in TS 27.007 8.5 or
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE}.
*/
public int getBitErrorRate() {
return mBitErrorRate;
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
index 26ec6de..ca264f7 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -42,7 +42,7 @@
// This series of errors as specified by the standards
// specified in ril.h
- /** Operator determined barring. */
+ /** Operator determined barring. (no retry) */
public static final int OPERATOR_BARRED = 0x08;
/** NAS signalling. */
public static final int NAS_SIGNALLING = 0x0E;
@@ -91,6 +91,11 @@
public static final int FILTER_SYTAX_ERROR = 0x2D;
/** Packet Data Protocol (PDP) without active traffic flow template (TFT). */
public static final int PDP_WITHOUT_ACTIVE_TFT = 0x2E;
+ /**
+ * UE requested to modify QoS parameters or the bearer control mode, which is not compatible
+ * with the selected bearer control mode.
+ */
+ public static final int ACTIVATION_REJECTED_BCM_VIOLATION = 0x30;
/** Packet Data Protocol (PDP) type IPv4 only allowed. */
public static final int ONLY_IPV4_ALLOWED = 0x32; /* no retry */
/** Packet Data Protocol (PDP) type IPv6 only allowed. */
@@ -103,6 +108,27 @@
public static final int PDN_CONN_DOES_NOT_EXIST = 0x36;
/** Multiple connections to a same PDN is not allowed. */
public static final int MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED = 0x37;
+ /**
+ * Network has already initiated the activation, modification, or deactivation of bearer
+ * resources that was requested by the UE.
+ */
+ public static final int COLLISION_WITH_NETWORK_INITIATED_REQUEST = 0x38;
+ /**
+ * Network supports IPv4v6 PDP type only. Non-IP type is not allowed. In LTE mode of operation,
+ * this is a PDN throttling cause code, meaning the UE may throttle further requests to the
+ * same APN.
+ */
+ public static final int ONLY_IPV4V6_ALLOWED = 0x39;
+ /**
+ * Network supports non-IP PDP type only. IPv4, IPv6 and IPv4v6 is not allowed. In LTE mode of
+ * operation, this is a PDN throttling cause code, meaning the UE can throttle further requests
+ * to the same APN.
+ */
+ public static final int ONLY_NON_IP_ALLOWED = 0x3A;
+ /** QCI (QoS Class Identifier) indicated in the UE request cannot be supported. */
+ public static final int UNSUPPORTED_QCI_VALUE = 0x3B;
+ /** Procedure requested by the UE was rejected because the bearer handling is not supported. */
+ public static final int BEARER_HANDLING_NOT_SUPPORTED = 0x3C;
/** Max number of Packet Data Protocol (PDP) context reached. */
public static final int ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED = 0x41;
/** Unsupported APN in current public land mobile network (PLMN). */
@@ -146,6 +172,742 @@
public static final int EMM_ACCESS_BARRED_INFINITE_RETRY = 0x79;
/** Authentication failure on emergency call. */
public static final int AUTH_FAILURE_ON_EMERGENCY_CALL = 0x7A;
+ /** Not receiving a DNS address that was mandatory. */
+ public static final int INVALID_DNS_ADDR = 0x7B;
+ /** Not receiving either a PCSCF or a DNS address, one of them being mandatory. */
+ public static final int INVALID_PCSCF_OR_DNS_ADDRESS = 0x7C;
+ /** Emergency call bring up on a different ePDG. */
+ public static final int CALL_PREEMPT_BY_EMERGENCY_APN = 0x7F;
+ /** UE performs a detach or disconnect PDN action based on TE requirements. */
+ public static final int UE_INITIATED_DETACH_OR_DISCONNECT = 0x80;
+
+ /** Reason unspecified for foreign agent rejected MIP (Mobile IP) registration. */
+ public static final int MIP_FA_REASON_UNSPECIFIED = 0x7D0;
+ /** Foreign agent administratively prohibited MIP (Mobile IP) registration. */
+ public static final int MIP_FA_ADMIN_PROHIBITED = 0x7D1;
+ /** Foreign agent rejected MIP (Mobile IP) registration because of insufficient resources. */
+ public static final int MIP_FA_INSUFFICIENT_RESOURCES = 0x7D2;
+ /**
+ * Foreign agent rejected MIP (Mobile IP) registration because of MN-AAA authenticator was
+ * wrong.
+ */
+ public static final int MIP_FA_MOBILE_NODE_AUTHENTICATION_FAILURE = 0x7D3;
+ /**
+ * Foreign agent rejected MIP (Mobile IP) registration because of home agent authentication
+ * failure.
+ */
+ public static final int MIP_FA_HOME_AGENT_AUTHENTICATION_FAILURE = 0x7D4;
+ /**
+ * Foreign agent rejected MIP (Mobile IP) registration because of requested lifetime was too
+ * long.
+ */
+ public static final int MIP_FA_REQUESTED_LIFETIME_TOO_LONG = 0x7D5;
+ /** Foreign agent rejected MIP (Mobile IP) registration because of malformed request. */
+ public static final int MIP_FA_MALFORMED_REQUEST = 0x7D6;
+ /** Foreign agent rejected MIP (Mobile IP) registration because of malformed reply. */
+ public static final int MIP_FA_MALFORMED_REPLY = 0x7D7;
+ /**
+ * Foreign agent rejected MIP (Mobile IP) registration because of requested encapsulation was
+ * unavailable.
+ */
+ public static final int MIP_FA_ENCAPSULATION_UNAVAILABLE = 0x7D8;
+ /**
+ * Foreign agent rejected MIP (Mobile IP) registration of VJ Header Compression was
+ * unavailable.
+ */
+ public static final int MIP_FA_VJ_HEADER_COMPRESSION_UNAVAILABLE = 0x7D9;
+ /**
+ * Foreign agent rejected MIP (Mobile IP) registration because of reverse tunnel was
+ * unavailable.
+ */
+ public static final int MIP_FA_REVERSE_TUNNEL_UNAVAILABLE = 0x7DA;
+ /**
+ * Foreign agent rejected MIP (Mobile IP) registration because of reverse tunnel was mandatory
+ * but not requested by device.
+ */
+ public static final int MIP_FA_REVERSE_TUNNEL_IS_MANDATORY = 0x7DB;
+ /**
+ * Foreign agent rejected MIP (Mobile IP) registration because of delivery style was not
+ * supported.
+ */
+ public static final int MIP_FA_DELIVERY_STYLE_NOT_SUPPORTED = 0x7DC;
+ /**
+ * Foreign agent rejected MIP (Mobile IP) registration because of missing NAI (Network Access
+ * Identifier).
+ */
+ public static final int MIP_FA_MISSING_NAI = 0x7DD;
+ /** Foreign agent rejected MIP (Mobile IP) registration because of missing Home Agent. */
+ public static final int MIP_FA_MISSING_HOME_AGENT = 0x7DE;
+ /** Foreign agent rejected MIP (Mobile IP) registration because of missing Home Address. */
+ public static final int MIP_FA_MISSING_HOME_ADDRESS = 0x7DF;
+ /** Foreign agent rejected MIP (Mobile IP) registration because of unknown challenge. */
+ public static final int MIP_FA_UNKNOWN_CHALLENGE = 0x7E0;
+ /** Foreign agent rejected MIP (Mobile IP) registration because of missing challenge. */
+ public static final int MIP_FA_MISSING_CHALLENGE = 0x7E1;
+ /** Foreign agent rejected MIP (Mobile IP) registration because of stale challenge. */
+ public static final int MIP_FA_STALE_CHALLENGE = 0x7E2;
+ /** Reason unspecified for home agent rejected MIP (Mobile IP) registration. */
+ public static final int MIP_HA_REASON_UNSPECIFIED = 0x7E3;
+ /** Home agent administratively prohibited MIP (Mobile IP) registration. */
+ public static final int MIP_HA_ADMIN_PROHIBITED = 0x7E4;
+ /** Home agent rejected MIP (Mobile IP) registration because of insufficient resources. */
+ public static final int MIP_HA_INSUFFICIENT_RESOURCES = 0x7E5;
+ /**
+ * Home agent rejected MIP (Mobile IP) registration because of MN-HA authenticator was
+ * wrong.
+ */
+ public static final int MIP_HA_MOBILE_NODE_AUTHENTICATION_FAILURE = 0x7E6;
+ /**
+ * Home agent rejected MIP (Mobile IP) registration because of foreign agent authentication
+ * failure.
+ */
+ public static final int MIP_HA_FOREIGN_AGENT_AUTHENTICATION_FAILURE = 0x7E7;
+ /** Home agent rejected MIP (Mobile IP) registration because of registration id mismatch. */
+ public static final int MIP_HA_REGISTRATION_ID_MISMATCH = 0x7E8;
+ /** Home agent rejected MIP (Mobile IP) registration because of malformed request. */
+ public static final int MIP_HA_MALFORMED_REQUEST = 0x7E9;
+ /** Home agent rejected MIP (Mobile IP) registration because of unknown home agent address. */
+ public static final int MIP_HA_UNKNOWN_HOME_AGENT_ADDRESS = 0x7EA;
+ /**
+ * Home agent rejected MIP (Mobile IP) registration because of reverse tunnel was
+ * unavailable.
+ */
+ public static final int MIP_HA_REVERSE_TUNNEL_UNAVAILABLE = 0x7EB;
+ /**
+ * Home agent rejected MIP (Mobile IP) registration because of reverse tunnel is mandatory but
+ * not requested by device.
+ */
+ public static final int MIP_HA_REVERSE_TUNNEL_IS_MANDATORY = 0x7EC;
+ /** Home agent rejected MIP (Mobile IP) registration because of encapsulation unavailable. */
+ public static final int MIP_HA_ENCAPSULATION_UNAVAILABLE = 0x7ED;
+ /** Tearing down is in progress. */
+ public static final int CLOSE_IN_PROGRESS = 0x7EE;
+ /** Brought down by the network. */
+ public static final int NETWORK_INITIATED_TERMINATION = 0x7EF;
+ /** Another application in modem preempts the data call. */
+ public static final int MODEM_APP_PREEMPTED = 0x7F0;
+ /**
+ * IPV4 PDN is in throttled state due to network providing only IPV6 address during the
+ * previous VSNCP bringup (subs_limited_to_v6).
+ */
+ public static final int PDN_IPV4_CALL_DISALLOWED = 0x7F1;
+ /** IPV4 PDN is in throttled state due to previous VSNCP bringup failure(s). */
+ public static final int PDN_IPV4_CALL_THROTTLED = 0x7F2;
+ /**
+ * IPV6 PDN is in throttled state due to network providing only IPV4 address during the
+ * previous VSNCP bringup (subs_limited_to_v4).
+ */
+ public static final int PDN_IPV6_CALL_DISALLOWED = 0x7F3;
+ /** IPV6 PDN is in throttled state due to previous VSNCP bringup failure(s). */
+ public static final int PDN_IPV6_CALL_THROTTLED = 0x7F4;
+ /** Modem restart. */
+ public static final int MODEM_RESTART = 0x7F5;
+ /** PDP PPP calls are not supported. */
+ public static final int PDP_PPP_NOT_SUPPORTED = 0x7F6;
+ /** RAT on which the data call is attempted/connected is no longer the preferred RAT. */
+ public static final int UNPREFERRED_RAT = 0x7F7;
+ /** Physical link is in the process of cleanup. */
+ public static final int PHYSICAL_LINK_CLOSE_IN_PROGRESS = 0x7F8;
+ /** Interface bring up is attempted for an APN that is yet to be handed over to target RAT. */
+ public static final int APN_PENDING_HANDOVER = 0x7F9;
+ /** APN bearer type in the profile does not match preferred network mode. */
+ public static final int PROFILE_BEARER_INCOMPATIBLE = 0x7FA;
+ /** Card was refreshed or removed. */
+ public static final int SIM_CARD_CHANGED = 0x7FB;
+ /** Device is going into lower power mode or powering down. */
+ public static final int LOW_POWER_MODE_OR_POWERING_DOWN = 0x7FC;
+ /** APN has been disabled. */
+ public static final int APN_DISABLED = 0x7FD;
+ /** Maximum PPP inactivity timer expired. */
+ public static final int MAX_PPP_INACTIVITY_TIMER_EXPIRED = 0x7FE;
+ /** IPv6 address transfer failed. */
+ public static final int IPV6_ADDRESS_TRANSFER_FAILED = 0x7FF;
+ /** Target RAT swap failed. */
+ public static final int TRAT_SWAP_FAILED = 0x800;
+ /** Device falls back from eHRPD to HRPD. */
+ public static final int EHRPD_TO_HRPD_FALLBACK = 0x801;
+ /**
+ * UE is in MIP-only configuration but the MIP configuration fails on call bring up due to
+ * incorrect provisioning.
+ */
+ public static final int MIP_CONFIG_FAILURE = 0x802;
+ /**
+ * PDN inactivity timer expired due to no data transmission in a configurable duration of time.
+ */
+ public static final int PDN_INACTIVITY_TIMER_EXPIRED = 0x803;
+ /**
+ * IPv4 data call bring up is rejected because the UE already maintains the allotted maximum
+ * number of IPv4 data connections.
+ */
+ public static final int MAX_IPV4_CONNECTIONS = 0x804;
+ /**
+ * IPv6 data call bring up is rejected because the UE already maintains the allotted maximum
+ * number of IPv6 data connections.
+ */
+ public static final int MAX_IPV6_CONNECTIONS = 0x805;
+ /**
+ * New PDN bring up is rejected during interface selection because the UE has already allotted
+ * the available interfaces for other PDNs.
+ */
+ public static final int APN_MISMATCH = 0x806;
+ /**
+ * New call bring up is rejected since the existing data call IP type doesn't match the
+ * requested IP.
+ */
+ public static final int IP_VERSION_MISMATCH = 0x807;
+ /** Dial up networking (DUN) call bring up is rejected since UE is in eHRPD RAT. */
+ public static final int DUN_CALL_DISALLOWED = 0x808;
+ /*** Rejected/Brought down since UE is transition between EPC and NONEPC RAT. */
+ public static final int INTERNAL_EPC_NONEPC_TRANSITION = 0x809;
+ /** The current interface is being in use. */
+ public static final int INTERFACE_IN_USE = 0x80A;
+ /** PDN connection to the APN is disallowed on the roaming network. */
+ public static final int APN_DISALLOWED_ON_ROAMING = 0x80B;
+ /** APN-related parameters are changed. */
+ public static final int APN_PARAMETERS_CHANGED = 0x80C;
+ /** PDN is attempted to be brought up with NULL APN but NULL APN is not supported. */
+ public static final int NULL_APN_DISALLOWED = 0x80D;
+ /**
+ * Thermal level increases and causes calls to be torn down when normal mode of operation is
+ * not allowed.
+ */
+ public static final int THERMAL_MITIGATION = 0x80E;
+ /**
+ * PDN Connection to a given APN is disallowed because data is disabled from the device user
+ * interface settings.
+ */
+ public static final int DATA_SETTINGS_DISABLED = 0x80F;
+ /**
+ * PDN Connection to a given APN is disallowed because data roaming is disabled from the device
+ * user interface settings and the UE is roaming.
+ */
+ public static final int DATA_ROAMING_SETTINGS_DISABLED = 0x810;
+ /** DDS (Default data subscription) switch occurs. */
+ public static final int DDS_SWITCHED = 0x811;
+ /** PDN being brought up with an APN that is part of forbidden APN Name list. */
+ public static final int FORBIDDEN_APN_NAME = 0x812;
+ /** Default data subscription switch is in progress. */
+ public static final int DDS_SWITCH_IN_PROGRESS = 0x813;
+ /** Roaming is disallowed during call bring up. */
+ public static final int CALL_DISALLOWED_IN_ROAMING = 0x814;
+ /**
+ * UE is unable to bring up a non-IP data call because the device is not camped on a NB1 cell.
+ */
+ public static final int NON_IP_NOT_SUPPORTED = 0x815;
+ /** Non-IP PDN is in throttled state due to previous VSNCP bringup failure(s). */
+ public static final int PDN_NON_IP_CALL_THROTTLED = 0x816;
+ /** Non-IP PDN is in disallowed state due to the network providing only an IP address. */
+ public static final int PDN_NON_IP_CALL_DISALLOWED = 0x817;
+ /** Device in CDMA locked state. */
+ public static final int CDMA_LOCK = 0x818;
+ /** Received an intercept order from the base station. */
+ public static final int CDMA_INTERCEPT = 0x819;
+ /** Receiving a reorder from the base station. */
+ public static final int CDMA_REORDER = 0x81A;
+ /** Receiving a release from the base station with a SO (Service Option) Reject reason. */
+ public static final int CDMA_RELEASE_DUE_TO_SO_REJECTION = 0x81B;
+ /** Receiving an incoming call from the base station. */
+ public static final int CDMA_INCOMING_CALL = 0x81C;
+ /** Received an alert stop from the base station due to incoming only. */
+ public static final int CDMA_ALERT_STOP = 0x81D;
+ /**
+ * Channel acquisition failures. This indicates that device has failed acquiring all the
+ * channels in the PRL.
+ */
+ public static final int CHANNEL_ACQUISITION_FAILURE = 0x81E;
+ /** Maximum access probes transmitted. */
+ public static final int MAX_ACCESS_PROBE = 0x81F;
+ /** Concurrent service is not supported by base station. */
+ public static final int CONCURRENT_SERVICE_NOT_SUPPORTED_BY_BASE_STATION = 0x820;
+ /** There was no response received from the base station. */
+ public static final int NO_RESPONSE_FROM_BASE_STATION = 0x821;
+ /** The base station rejecting the call. */
+ public static final int REJECTED_BY_BASE_STATION = 0x822;
+ /** The concurrent services requested were not compatible. */
+ public static final int CONCURRENT_SERVICES_INCOMPATIBLE = 0x823;
+ /** Device does not have CDMA service. */
+ public static final int NO_CDMA_SERVICE = 0x824;
+ /** RUIM not being present. */
+ public static final int RUIM_NOT_PRESENT = 0x825;
+ /** Receiving a retry order from the base station. */
+ public static final int CDMA_RETRY_ORDER = 0x826;
+ /** Access blocked by the base station. */
+ public static final int ACCESS_BLOCK = 0x827;
+ /** Access blocked by the base station for all mobile devices. */
+ public static final int ACCESS_BLOCK_ALL = 0x828;
+ /** Maximum access probes for the IS-707B call. */
+ public static final int IS707B_MAX_ACCESS_PROBES = 0x829;
+ /** Put device in thermal emergency. */
+ public static final int THERMAL_EMERGENCY = 0x82A;
+ /** In favor of a voice call or SMS when concurrent voice and data are not supported. */
+ public static final int CONCURRENT_SERVICES_NOT_ALLOWED = 0x82B;
+ /** The other clients rejected incoming call. */
+ public static final int INCOMING_CALL_REJECTED = 0x82C;
+ /** No service on the gateway. */
+ public static final int NO_SERVICE_ON_GATEWAY = 0x82D;
+ /** GPRS context is not available. */
+ public static final int NO_GPRS_CONTEXT = 0x82E;
+ /**
+ * Network refuses service to the MS because either an identity of the MS is not acceptable to
+ * the network or the MS does not pass the authentication check.
+ */
+ public static final int ILLEGAL_MS = 0x82F;
+ /** ME could not be authenticated and the ME used is not acceptable to the network. */
+ public static final int ILLEGAL_ME = 0x830;
+ /** Not allowed to operate either GPRS or non-GPRS services. */
+ public static final int GPRS_SERVICES_AND_NON_GPRS_SERVICES_NOT_ALLOWED = 0x831;
+ /** MS is not allowed to operate GPRS services. */
+ public static final int GPRS_SERVICES_NOT_ALLOWED = 0x832;
+ /** No matching identity or context could be found in the network. */
+ public static final int MS_IDENTITY_CANNOT_BE_DERIVED_BY_THE_NETWORK = 0x833;
+ /**
+ * Mobile reachable timer has expired, or the GMM context data related to the subscription does
+ * not exist in the SGSN.
+ */
+ public static final int IMPLICITLY_DETACHED = 0x834;
+ /**
+ * UE requests GPRS service, or the network initiates a detach request in a PLMN which does not
+ * offer roaming for GPRS services to that MS.
+ */
+ public static final int PLMN_NOT_ALLOWED = 0x835;
+ /**
+ * MS requests service, or the network initiates a detach request, in a location area where the
+ * HPLMN determines that the MS, by subscription, is not allowed to operate.
+ */
+ public static final int LOCATION_AREA_NOT_ALLOWED = 0x836;
+ /**
+ * UE requests GPRS service or the network initiates a detach request in a PLMN that does not
+ * offer roaming for GPRS services.
+ */
+ public static final int GPRS_SERVICES_NOT_ALLOWED_IN_THIS_PLMN = 0x837;
+ /** PDP context already exists. */
+ public static final int PDP_DUPLICATE = 0x838;
+ /** RAT change on the UE. */
+ public static final int UE_RAT_CHANGE = 0x839;
+ /** Network cannot serve a request from the MS due to congestion. */
+ public static final int CONGESTION = 0x83A;
+ /**
+ * MS requests an establishment of the radio access bearers for all active PDP contexts by
+ * sending a service request message indicating data to the network, but the SGSN does not have
+ * any active PDP context.
+ */
+ public static final int NO_PDP_CONTEXT_ACTIVATED = 0x83B;
+ /** Access class blocking restrictions for the current camped cell. */
+ public static final int ACCESS_CLASS_DSAC_REJECTION = 0x83C;
+ /** SM attempts PDP activation for a maximum of four attempts. */
+ public static final int PDP_ACTIVATE_MAX_RETRY_FAILED = 0x83D;
+ /** Radio access bearer failure. */
+ public static final int RADIO_ACCESS_BEARER_FAILURE = 0x83E;
+ /** Invalid EPS bearer identity in the request. */
+ public static final int ESM_UNKNOWN_EPS_BEARER_CONTEXT = 0x83F;
+ /** Data radio bearer is released by the RRC. */
+ public static final int DRB_RELEASED_BY_RRC = 0x840;
+ /** Indicate the connection was released. */
+ public static final int CONNECTION_RELEASED = 0x841;
+ /** UE is detached. */
+ public static final int EMM_DETACHED = 0x842;
+ /** Attach procedure is rejected by the network. */
+ public static final int EMM_ATTACH_FAILED = 0x843;
+ /** Attach procedure is started for EMC purposes. */
+ public static final int EMM_ATTACH_STARTED = 0x844;
+ /** Service request procedure failure. */
+ public static final int LTE_NAS_SERVICE_REQUEST_FAILED = 0x845;
+ /** Active dedicated bearer was requested using the same default bearer ID. */
+ public static final int DUPLICATE_BEARER_ID = 0x846;
+ /** Collision scenarios for the UE and network-initiated procedures. */
+ public static final int ESM_COLLISION_SCENARIOS = 0x847;
+ /** Bearer must be deactivated to synchronize with the network. */
+ public static final int ESM_BEARER_DEACTIVATED_TO_SYNC_WITH_NETWORK = 0x848;
+ /** Active dedicated bearer was requested for an existing default bearer. */
+ public static final int ESM_NW_ACTIVATED_DED_BEARER_WITH_ID_OF_DEF_BEARER = 0x849;
+ /** Bad OTA message is received from the network. */
+ public static final int ESM_BAD_OTA_MESSAGE = 0x84A;
+ /** Download server rejected the call. */
+ public static final int ESM_DOWNLOAD_SERVER_REJECTED_THE_CALL = 0x84B;
+ /** PDN was disconnected by the downlaod server due to IRAT. */
+ public static final int ESM_CONTEXT_TRANSFERRED_DUE_TO_IRAT = 0x84C;
+ /** Dedicated bearer will be deactivated regardless of the network response. */
+ public static final int DS_EXPLICIT_DEACTIVATION = 0x84D;
+ /** No specific local cause is mentioned, usually a valid OTA cause. */
+ public static final int ESM_LOCAL_CAUSE_NONE = 0x84E;
+ /** Throttling is not needed for this service request failure. */
+ public static final int LTE_THROTTLING_NOT_REQUIRED = 0x84F;
+ /** Access control list check failure at the lower layer. */
+ public static final int ACCESS_CONTROL_LIST_CHECK_FAILURE = 0x850;
+ /** Service is not allowed on the requested PLMN. */
+ public static final int SERVICE_NOT_ALLOWED_ON_PLMN = 0x851;
+ /** T3417 timer expiration of the service request procedure. */
+ public static final int EMM_T3417_EXPIRED = 0x852;
+ /** Extended service request fails due to expiration of the T3417 EXT timer. */
+ public static final int EMM_T3417_EXT_EXPIRED = 0x853;
+ /** Transmission failure of radio resource control (RRC) uplink data. */
+ public static final int RRC_UPLINK_DATA_TRANSMISSION_FAILURE = 0x854;
+ /** Radio resource control (RRC) uplink data delivery failed due to a handover. */
+ public static final int RRC_UPLINK_DELIVERY_FAILED_DUE_TO_HANDOVER = 0x855;
+ /** Radio resource control (RRC) uplink data delivery failed due to a connection release. */
+ public static final int RRC_UPLINK_CONNECTION_RELEASE = 0x856;
+ /** Radio resource control (RRC) uplink data delivery failed due to a radio link failure. */
+ public static final int RRC_UPLINK_RADIO_LINK_FAILURE = 0x857;
+ /**
+ * Radio resource control (RRC) is not connected but the non-access stratum (NAS) sends an
+ * uplink data request.
+ */
+ public static final int RRC_UPLINK_ERROR_REQUEST_FROM_NAS = 0x858;
+ /** Radio resource control (RRC) connection failure at access stratum. */
+ public static final int RRC_CONNECTION_ACCESS_STRATUM_FAILURE = 0x859;
+ /**
+ * Radio resource control (RRC) connection establishment is aborted due to another procedure.
+ */
+ public static final int RRC_CONNECTION_ANOTHER_PROCEDURE_IN_PROGRESS = 0x85A;
+ /** Radio resource control (RRC) connection establishment failed due to access barrred. */
+ public static final int RRC_CONNECTION_ACCESS_BARRED = 0x85B;
+ /**
+ * Radio resource control (RRC) connection establishment failed due to cell reselection at
+ * access stratum.
+ */
+ public static final int RRC_CONNECTION_CELL_RESELECTION = 0x85C;
+ /**
+ * Connection establishment failed due to configuration failure at the radio resource control
+ * (RRC).
+ */
+ public static final int RRC_CONNECTION_CONFIG_FAILURE = 0x85D;
+ /** Radio resource control (RRC) connection could not be established in the time limit. */
+ public static final int RRC_CONNECTION_TIMER_EXPIRED = 0x85E;
+ /**
+ * Connection establishment failed due to a link failure at the radio resource control (RRC).
+ */
+ public static final int RRC_CONNECTION_LINK_FAILURE = 0x85F;
+ /**
+ * Connection establishment failed as the radio resource control (RRC) is not camped on any
+ * cell.
+ */
+ public static final int RRC_CONNECTION_CELL_NOT_CAMPED = 0x860;
+ /**
+ * Connection establishment failed due to a service interval failure at the radio resource
+ * control (RRC).
+ */
+ public static final int RRC_CONNECTION_SYSTEM_INTERVAL_FAILURE = 0x861;
+ /**
+ * Radio resource control (RRC) connection establishment failed due to the network rejecting
+ * the UE connection request.
+ */
+ public static final int RRC_CONNECTION_REJECT_BY_NETWORK = 0x862;
+ /** Normal radio resource control (RRC) connection release. */
+ public static final int RRC_CONNECTION_NORMAL_RELEASE = 0x863;
+ /**
+ * Radio resource control (RRC) connection release failed due to radio link failure conditions.
+ */
+ public static final int RRC_CONNECTION_RADIO_LINK_FAILURE = 0x864;
+ /** Radio resource control (RRC) connection re-establishment failure. */
+ public static final int RRC_CONNECTION_REESTABLISHMENT_FAILURE = 0x865;
+ /** UE is out of service during the call register. */
+ public static final int RRC_CONNECTION_OUT_OF_SERVICE_DURING_CELL_REGISTER = 0x866;
+ /**
+ * Connection has been released by the radio resource control (RRC) due to an abort request.
+ */
+ public static final int RRC_CONNECTION_ABORT_REQUEST = 0x867;
+ /**
+ * Radio resource control (RRC) connection released due to a system information block read
+ * error.
+ */
+ public static final int RRC_CONNECTION_SYSTEM_INFORMATION_BLOCK_READ_ERROR = 0x868;
+ /** Network-initiated detach with reattach. */
+ public static final int NETWORK_INITIATED_DETACH_WITH_AUTO_REATTACH = 0x869;
+ /** Network-initiated detach without reattach. */
+ public static final int NETWORK_INITIATED_DETACH_NO_AUTO_REATTACH = 0x86A;
+ /** ESM procedure maximum attempt timeout failure. */
+ public static final int ESM_PROCEDURE_TIME_OUT = 0x86B;
+ /**
+ * No PDP exists with the given connection ID while modifying or deactivating or activation for
+ * an already active PDP.
+ */
+ public static final int INVALID_CONNECTION_ID = 0x86C;
+ /** Maximum NSAPIs have been exceeded during PDP activation. */
+ public static final int MAXIMIUM_NSAPIS_EXCEEDED = 0x86D;
+ /** Primary context for NSAPI does not exist. */
+ public static final int INVALID_PRIMARY_NSAPI = 0x86E;
+ /** Unable to encode the OTA message for MT PDP or deactivate PDP. */
+ public static final int CANNOT_ENCODE_OTA_MESSAGE = 0x86F;
+ /**
+ * Radio access bearer is not established by the lower layers during activation, modification,
+ * or deactivation.
+ */
+ public static final int RADIO_ACCESS_BEARER_SETUP_FAILURE = 0x870;
+ /** Expiration of the PDP establish timer with a maximum of five retries. */
+ public static final int PDP_ESTABLISH_TIMEOUT_EXPIRED = 0x871;
+ /** Expiration of the PDP modify timer with a maximum of four retries. */
+ public static final int PDP_MODIFY_TIMEOUT_EXPIRED = 0x872;
+ /** Expiration of the PDP deactivate timer with a maximum of four retries. */
+ public static final int PDP_INACTIVE_TIMEOUT_EXPIRED = 0x873;
+ /** PDP activation failed due to RRC_ABORT or a forbidden PLMN. */
+ public static final int PDP_LOWERLAYER_ERROR = 0x874;
+ /** MO PDP modify collision when the MT PDP is already in progress. */
+ public static final int PDP_MODIFY_COLLISION = 0x875;
+ /** Maximum size of the L2 message was exceeded. */
+ public static final int MAXINUM_SIZE_OF_L2_MESSAGE_EXCEEDED = 0x876;
+ /** Non-access stratum (NAS) request was rejected by the network. */
+ public static final int NAS_REQUEST_REJECTED_BY_NETWORK = 0x877;
+ /**
+ * Radio resource control (RRC) connection establishment failure due to an error in the request
+ * message.
+ */
+ public static final int RRC_CONNECTION_INVALID_REQUEST = 0x878;
+ /**
+ * Radio resource control (RRC) connection establishment failure due to a change in the
+ * tracking area ID.
+ */
+ public static final int RRC_CONNECTION_TRACKING_AREA_ID_CHANGED = 0x879;
+ /**
+ * Radio resource control (RRC) connection establishment failure due to the RF was unavailable.
+ */
+ public static final int RRC_CONNECTION_RF_UNAVAILABLE = 0x87A;
+ /**
+ * Radio resource control (RRC) connection was aborted before deactivating the LTE stack due to
+ * a successful LTE to WCDMA/GSM/TD-SCDMA IRAT change.
+ */
+ public static final int RRC_CONNECTION_ABORTED_DUE_TO_IRAT_CHANGE = 0x87B;
+ /**
+ * If the UE has an LTE radio link failure before security is established, the radio resource
+ * control (RRC) connection must be released and the UE must return to idle.
+ */
+ public static final int RRC_CONNECTION_RELEASED_SECURITY_NOT_ACTIVE = 0x87C;
+ /**
+ * Radio resource control (RRC) connection was aborted by the non-access stratum (NAS) after an
+ * IRAT to LTE IRAT handover.
+ */
+ public static final int RRC_CONNECTION_ABORTED_AFTER_HANDOVER = 0x87D;
+ /**
+ * Radio resource control (RRC) connection was aborted before deactivating the LTE stack after
+ * a successful LTE to GSM/EDGE IRAT cell change order procedure.
+ */
+ public static final int RRC_CONNECTION_ABORTED_AFTER_IRAT_CELL_CHANGE = 0x87E;
+ /**
+ * Radio resource control (RRC) connection was aborted in the middle of a LTE to GSM IRAT cell
+ * change order procedure.
+ */
+ public static final int RRC_CONNECTION_ABORTED_DURING_IRAT_CELL_CHANGE = 0x87F;
+ /** IMSI present in the UE is unknown in the home subscriber server. */
+ public static final int IMSI_UNKNOWN_IN_HOME_SUBSCRIBER_SERVER = 0x880;
+ /** IMEI of the UE is not accepted by the network. */
+ public static final int IMEI_NOT_ACCEPTED = 0x881;
+ /** EPS and non-EPS services are not allowed by the network. */
+ public static final int EPS_SERVICES_AND_NON_EPS_SERVICES_NOT_ALLOWED = 0x882;
+ /** EPS services are not allowed in the PLMN. */
+ public static final int EPS_SERVICES_NOT_ALLOWED_IN_PLMN = 0x883;
+ /** Mobile switching center is temporarily unreachable. */
+ public static final int MSC_TEMPORARILY_NOT_REACHABLE = 0x884;
+ /** CS domain is not available. */
+ public static final int CS_DOMAIN_NOT_AVAILABLE = 0x885;
+ /** ESM level failure. */
+ public static final int ESM_FAILURE = 0x886;
+ /** MAC level failure. */
+ public static final int MAC_FAILURE = 0x887;
+ /** Synchronization failure. */
+ public static final int SYNCHRONIZATION_FAILURE = 0x888;
+ /** UE security capabilities mismatch. */
+ public static final int UE_SECURITY_CAPABILITIES_MISMATCH = 0x889;
+ /** Unspecified security mode reject. */
+ public static final int SECURITY_MODE_REJECTED = 0x88A;
+ /** Unacceptable non-EPS authentication. */
+ public static final int UNACCEPTABLE_NON_EPS_AUTHENTICATION = 0x88B;
+ /** CS fallback call establishment is not allowed. */
+ public static final int CS_FALLBACK_CALL_ESTABLISHMENT_NOT_ALLOWED = 0x88C;
+ /** No EPS bearer context was activated. */
+ public static final int NO_EPS_BEARER_CONTEXT_ACTIVATED = 0x88D;
+ /** Invalid EMM state. */
+ public static final int INVALID_EMM_STATE = 0x88E;
+ /** Non-Access Spectrum layer failure. */
+ public static final int NAS_LAYER_FAILURE = 0x88F;
+ /** Multiple PDP call feature is disabled. */
+ public static final int MULTIPLE_PDP_CALL_NOT_ALLOWED = 0x890;
+ /** Data call has been brought down because EMBMS is not enabled at the RRC layer. */
+ public static final int EMBMS_NOT_ENABLED = 0x891;
+ /** Data call was unsuccessfully transferred during the IRAT handover. */
+ public static final int IRAT_HANDOVER_FAILED = 0x892;
+ /** EMBMS data call has been successfully brought down. */
+ public static final int EMBMS_REGULAR_DEACTIVATION = 0x893;
+ /** Test loop-back data call has been successfully brought down. */
+ public static final int TEST_LOOPBACK_REGULAR_DEACTIVATION = 0x894;
+ /** Lower layer registration failure. */
+ public static final int LOWER_LAYER_REGISTRATION_FAILURE = 0x895;
+ /**
+ * Network initiates a detach on LTE with error cause ""data plan has been replenished or has
+ * expired.
+ */
+ public static final int DATA_PLAN_EXPIRED = 0x896;
+ /** UMTS interface is brought down due to handover from UMTS to iWLAN. */
+ public static final int UMTS_HANDOVER_TO_IWLAN = 0x897;
+ /** Received a connection deny due to general or network busy on EVDO network. */
+ public static final int EVDO_CONNECTION_DENY_BY_GENERAL_OR_NETWORK_BUSY = 0x898;
+ /** Received a connection deny due to billing or authentication failure on EVDO network. */
+ public static final int EVDO_CONNECTION_DENY_BY_BILLING_OR_AUTHENTICATION_FAILURE = 0x899;
+ /** HDR system has been changed due to redirection or the PRL was not preferred. */
+ public static final int EVDO_HDR_CHANGED = 0x89A;
+ /** Device exited HDR due to redirection or the PRL was not preferred. */
+ public static final int EVDO_HDR_EXITED = 0x89B;
+ /** Device does not have an HDR session. */
+ public static final int EVDO_HDR_NO_SESSION = 0x89C;
+ /** It is ending an HDR call origination in favor of a GPS fix. */
+ public static final int EVDO_USING_GPS_FIX_INSTEAD_OF_HDR_CALL = 0x89D;
+ /** Connection setup on the HDR system was time out. */
+ public static final int EVDO_HDR_CONNECTION_SETUP_TIMEOUT = 0x89E;
+ /** Device failed to acquire a co-located HDR for origination. */
+ public static final int FAILED_TO_ACQUIRE_COLOCATED_HDR = 0x89F;
+ /** OTASP commit is in progress. */
+ public static final int OTASP_COMMIT_IN_PROGRESS = 0x8A0;
+ /** Device has no hybrid HDR service. */
+ public static final int NO_HYBRID_HDR_SERVICE = 0x8A1;
+ /** HDR module could not be obtained because of the RF locked. */
+ public static final int HDR_NO_LOCK_GRANTED = 0x8A2;
+ /** DBM or SMS is in progress. */
+ public static final int DBM_OR_SMS_IN_PROGRESS = 0x8A3;
+ /** HDR module released the call due to fade. */
+ public static final int HDR_FADE = 0x8A4;
+ /** HDR system access failure. */
+ public static final int HDR_ACCESS_FAILURE = 0x8A5;
+ /**
+ * P_rev supported by 1 base station is less than 6, which is not supported for a 1X data call.
+ * The UE must be in the footprint of BS which has p_rev >= 6 to support this SO33 call.
+ */
+ public static final int UNSUPPORTED_1X_PREV = 0x8A6;
+ /** Client ended the data call. */
+ public static final int LOCAL_END = 0x8A7;
+ /** Device has no service. */
+ public static final int NO_SERVICE = 0x8A8;
+ /** Device lost the system due to fade. */
+ public static final int FADE = 0x8A9;
+ /** Receiving a release from the base station with no reason. */
+ public static final int NORMAL_RELEASE = 0x8AA;
+ /** Access attempt is already in progress. */
+ public static final int ACCESS_ATTEMPT_ALREADY_IN_PROGRESS = 0x8AB;
+ /** Device is in the process of redirecting or handing off to a different target system. */
+ public static final int REDIRECTION_OR_HANDOFF_IN_PROGRESS = 0x8AC;
+ /** Device is operating in Emergency mode. */
+ public static final int EMERGENCY_MODE = 0x8AD;
+ /** Device is in use (e.g., voice call). */
+ public static final int PHONE_IN_USE = 0x8AE;
+ /**
+ * Device operational mode is different from the mode requested in the traffic channel bring up.
+ */
+ public static final int INVALID_MODE = 0x8AF;
+ /** SIM was marked by the network as invalid for the circuit and/or packet service domain. */
+ public static final int INVALID_SIM_STATE = 0x8B0;
+ /** There is no co-located HDR. */
+ public static final int NO_COLLOCATED_HDR = 0x8B1;
+ /** UE is entering power save mode. */
+ public static final int UE_IS_ENTERING_POWERSAVE_MODE = 0x8B2;
+ /** Dual switch from single standby to dual standby is in progress. */
+ public static final int DUAL_SWITCH = 0x8B3;
+ /**
+ * Data call bring up fails in the PPP setup due to a timeout.
+ * (e.g., an LCP conf ack was not received from the network)
+ */
+ public static final int PPP_TIMEOUT = 0x8B4;
+ /**
+ * Data call bring up fails in the PPP setup due to an authorization failure.
+ * (e.g., authorization is required, but not negotiated with the network during an LCP phase)
+ */
+ public static final int PPP_AUTH_FAILURE = 0x8B5;
+ /** Data call bring up fails in the PPP setup due to an option mismatch. */
+ public static final int PPP_OPTION_MISMATCH = 0x8B6;
+ /** Data call bring up fails in the PPP setup due to a PAP failure. */
+ public static final int PPP_PAP_FAILURE = 0x8B7;
+ /** Data call bring up fails in the PPP setup due to a CHAP failure. */
+ public static final int PPP_CHAP_FAILURE = 0x8B8;
+ /**
+ * Data call bring up fails in the PPP setup because the PPP is in the process of cleaning the
+ * previous PPP session.
+ */
+ public static final int PPP_CLOSE_IN_PROGRESS = 0x8B9;
+ /**
+ * IPv6 interface bring up fails because the network provided only the IPv4 address for the
+ * upcoming PDN permanent client can reattempt a IPv6 call bring up after the IPv4 interface is
+ * also brought down. However, there is no guarantee that the network will provide a IPv6
+ * address.
+ */
+ public static final int LIMITED_TO_IPV4 = 0x8BA;
+ /**
+ * IPv4 interface bring up fails because the network provided only the IPv6 address for the
+ * upcoming PDN permanent client can reattempt a IPv4 call bring up after the IPv6 interface is
+ * also brought down. However there is no guarantee that the network will provide a IPv4
+ * address.
+ */
+ public static final int LIMITED_TO_IPV6 = 0x8BB;
+ /** Data call bring up fails in the VSNCP phase due to a VSNCP timeout error. */
+ public static final int VSNCP_TIMEOUT = 0x8BC;
+ /**
+ * Data call bring up fails in the VSNCP phase due to a general error. It's used when there is
+ * no other specific error code available to report the failure.
+ */
+ public static final int VSNCP_GEN_ERROR = 0x8BD;
+ /**
+ * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+ * configuration request because the requested APN is unauthorized.
+ */
+ public static final int VSNCP_APN_UNATHORIZED = 0x8BE;
+ /**
+ * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+ * configuration request because the PDN limit has been exceeded.
+ */
+ public static final int VSNCP_PDN_LIMIT_EXCEEDED = 0x8BF;
+ /**
+ * Data call bring up fails in the VSNCP phase due to the network rejected the VSNCP
+ * configuration request due to no PDN gateway address.
+ */
+ public static final int VSNCP_NO_PDN_GATEWAY_ADDRESS = 0x8C0;
+ /**
+ * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+ * configuration request because the PDN gateway is unreachable.
+ */
+ public static final int VSNCP_PDN_GATEWAY_UNREACHABLE = 0x8C1;
+ /**
+ * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+ * configuration request due to a PDN gateway reject.
+ */
+ public static final int VSNCP_PDN_GATEWAY_REJECT = 0x8C2;
+ /**
+ * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+ * configuration request with the reason of insufficient parameter.
+ */
+ public static final int VSNCP_INSUFFICIENT_PARAMETERS = 0x8C3;
+ /**
+ * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+ * configuration request with the reason of resource unavailable.
+ */
+ public static final int VSNCP_RESOURCE_UNAVAILABLE = 0x8C4;
+ /**
+ * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+ * configuration request with the reason of administratively prohibited at the HSGW.
+ */
+ public static final int VSNCP_ADMINISTRATIVELY_PROHIBITED = 0x8C5;
+ /**
+ * Data call bring up fails in the VSNCP phase due to a network rejection of PDN ID in use, or
+ * all existing PDNs are brought down with this end reason because one of the PDN bring up was
+ * rejected by the network with the reason of PDN ID in use.
+ */
+ public static final int VSNCP_PDN_ID_IN_USE = 0x8C6;
+ /**
+ * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+ * configuration request for the reason of subscriber limitation.
+ */
+ public static final int VSNCP_SUBSCRIBER_LIMITATION = 0x8C7;
+ /**
+ * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+ * configuration request because the PDN exists for this APN.
+ */
+ public static final int VSNCP_PDN_EXISTS_FOR_THIS_APN = 0x8C8;
+ /**
+ * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP
+ * configuration request with reconnect to this PDN not allowed, or an active data call is
+ * terminated by the network because reconnection to this PDN is not allowed. Upon receiving
+ * this error code from the network, the modem infinitely throttles the PDN until the next
+ * power cycle.
+ */
+ public static final int VSNCP_RECONNECT_NOT_ALLOWED = 0x8C9;
+ /** Device failure to obtain the prefix from the network. */
+ public static final int IPV6_PREFIX_UNAVAILABLE = 0x8CA;
+ /** System preference change back to SRAT during handoff */
+ public static final int HANDOFF_PREFERENCE_CHANGED = 0x8CB;
// OEM sepecific error codes. To be used by OEMs when they don't
// want to reveal error code which would be replaced by ERROR_UNSPECIFIED
@@ -199,6 +961,13 @@
/** @hide */
public static final int RESET_BY_FRAMEWORK = 0x10005;
+ /**
+ * Data handover failed.
+ *
+ * @hide
+ */
+ public static final int HANDOVER_FAILED = 0x10006;
+
/** @hide */
@IntDef(value = {
NONE,
@@ -226,12 +995,18 @@
FILTER_SEMANTIC_ERROR,
FILTER_SYTAX_ERROR,
PDP_WITHOUT_ACTIVE_TFT,
+ ACTIVATION_REJECTED_BCM_VIOLATION,
ONLY_IPV4_ALLOWED,
ONLY_IPV6_ALLOWED,
ONLY_SINGLE_BEARER_ALLOWED,
ESM_INFO_NOT_RECEIVED,
PDN_CONN_DOES_NOT_EXIST,
MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED,
+ COLLISION_WITH_NETWORK_INITIATED_REQUEST,
+ ONLY_IPV4V6_ALLOWED,
+ ONLY_NON_IP_ALLOWED,
+ UNSUPPORTED_QCI_VALUE,
+ BEARER_HANDLING_NOT_SUPPORTED,
ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED,
UNSUPPORTED_APN_IN_CURRENT_PLMN,
INVALID_TRANSACTION_ID,
@@ -242,7 +1017,7 @@
UNKNOWN_INFO_ELEMENT,
CONDITIONAL_IE_ERROR,
MSG_AND_PROTOCOL_STATE_UNCOMPATIBLE,
- PROTOCOL_ERRORS, /* no retry */
+ PROTOCOL_ERRORS,
APN_TYPE_CONFLICT,
INVALID_PCSCF_ADDR,
INTERNAL_CALL_PREEMPT_BY_HIGH_PRIO_APN,
@@ -254,6 +1029,262 @@
IFACE_AND_POL_FAMILY_MISMATCH,
EMM_ACCESS_BARRED_INFINITE_RETRY,
AUTH_FAILURE_ON_EMERGENCY_CALL,
+ INVALID_DNS_ADDR,
+ INVALID_PCSCF_OR_DNS_ADDRESS,
+ CALL_PREEMPT_BY_EMERGENCY_APN,
+ UE_INITIATED_DETACH_OR_DISCONNECT,
+ MIP_FA_REASON_UNSPECIFIED,
+ MIP_FA_ADMIN_PROHIBITED,
+ MIP_FA_INSUFFICIENT_RESOURCES,
+ MIP_FA_MOBILE_NODE_AUTHENTICATION_FAILURE,
+ MIP_FA_HOME_AGENT_AUTHENTICATION_FAILURE,
+ MIP_FA_REQUESTED_LIFETIME_TOO_LONG,
+ MIP_FA_MALFORMED_REQUEST,
+ MIP_FA_MALFORMED_REPLY,
+ MIP_FA_ENCAPSULATION_UNAVAILABLE,
+ MIP_FA_VJ_HEADER_COMPRESSION_UNAVAILABLE,
+ MIP_FA_REVERSE_TUNNEL_UNAVAILABLE,
+ MIP_FA_REVERSE_TUNNEL_IS_MANDATORY,
+ MIP_FA_DELIVERY_STYLE_NOT_SUPPORTED,
+ MIP_FA_MISSING_NAI,
+ MIP_FA_MISSING_HOME_AGENT,
+ MIP_FA_MISSING_HOME_ADDRESS,
+ MIP_FA_UNKNOWN_CHALLENGE,
+ MIP_FA_MISSING_CHALLENGE,
+ MIP_FA_STALE_CHALLENGE,
+ MIP_HA_REASON_UNSPECIFIED,
+ MIP_HA_ADMIN_PROHIBITED,
+ MIP_HA_INSUFFICIENT_RESOURCES,
+ MIP_HA_MOBILE_NODE_AUTHENTICATION_FAILURE,
+ MIP_HA_FOREIGN_AGENT_AUTHENTICATION_FAILURE,
+ MIP_HA_REGISTRATION_ID_MISMATCH,
+ MIP_HA_MALFORMED_REQUEST,
+ MIP_HA_UNKNOWN_HOME_AGENT_ADDRESS,
+ MIP_HA_REVERSE_TUNNEL_UNAVAILABLE,
+ MIP_HA_REVERSE_TUNNEL_IS_MANDATORY,
+ MIP_HA_ENCAPSULATION_UNAVAILABLE,
+ CLOSE_IN_PROGRESS,
+ NETWORK_INITIATED_TERMINATION,
+ MODEM_APP_PREEMPTED,
+ PDN_IPV4_CALL_DISALLOWED,
+ PDN_IPV4_CALL_THROTTLED,
+ PDN_IPV6_CALL_DISALLOWED,
+ PDN_IPV6_CALL_THROTTLED,
+ MODEM_RESTART,
+ PDP_PPP_NOT_SUPPORTED,
+ UNPREFERRED_RAT,
+ PHYSICAL_LINK_CLOSE_IN_PROGRESS,
+ APN_PENDING_HANDOVER,
+ PROFILE_BEARER_INCOMPATIBLE,
+ SIM_CARD_CHANGED,
+ LOW_POWER_MODE_OR_POWERING_DOWN,
+ APN_DISABLED,
+ MAX_PPP_INACTIVITY_TIMER_EXPIRED,
+ IPV6_ADDRESS_TRANSFER_FAILED,
+ TRAT_SWAP_FAILED,
+ EHRPD_TO_HRPD_FALLBACK,
+ MIP_CONFIG_FAILURE,
+ PDN_INACTIVITY_TIMER_EXPIRED,
+ MAX_IPV4_CONNECTIONS,
+ MAX_IPV6_CONNECTIONS,
+ APN_MISMATCH,
+ IP_VERSION_MISMATCH,
+ DUN_CALL_DISALLOWED,
+ INTERNAL_EPC_NONEPC_TRANSITION,
+ INTERFACE_IN_USE,
+ APN_DISALLOWED_ON_ROAMING,
+ APN_PARAMETERS_CHANGED,
+ NULL_APN_DISALLOWED,
+ THERMAL_MITIGATION,
+ DATA_SETTINGS_DISABLED,
+ DATA_ROAMING_SETTINGS_DISABLED,
+ DDS_SWITCHED,
+ FORBIDDEN_APN_NAME,
+ DDS_SWITCH_IN_PROGRESS,
+ CALL_DISALLOWED_IN_ROAMING,
+ NON_IP_NOT_SUPPORTED,
+ PDN_NON_IP_CALL_THROTTLED,
+ PDN_NON_IP_CALL_DISALLOWED,
+ CDMA_LOCK,
+ CDMA_INTERCEPT,
+ CDMA_REORDER,
+ CDMA_RELEASE_DUE_TO_SO_REJECTION,
+ CDMA_INCOMING_CALL,
+ CDMA_ALERT_STOP,
+ CHANNEL_ACQUISITION_FAILURE,
+ MAX_ACCESS_PROBE,
+ CONCURRENT_SERVICE_NOT_SUPPORTED_BY_BASE_STATION,
+ NO_RESPONSE_FROM_BASE_STATION,
+ REJECTED_BY_BASE_STATION,
+ CONCURRENT_SERVICES_INCOMPATIBLE,
+ NO_CDMA_SERVICE,
+ RUIM_NOT_PRESENT,
+ CDMA_RETRY_ORDER,
+ ACCESS_BLOCK,
+ ACCESS_BLOCK_ALL,
+ IS707B_MAX_ACCESS_PROBES,
+ THERMAL_EMERGENCY,
+ CONCURRENT_SERVICES_NOT_ALLOWED,
+ INCOMING_CALL_REJECTED,
+ NO_SERVICE_ON_GATEWAY,
+ NO_GPRS_CONTEXT,
+ ILLEGAL_MS,
+ ILLEGAL_ME,
+ GPRS_SERVICES_AND_NON_GPRS_SERVICES_NOT_ALLOWED,
+ GPRS_SERVICES_NOT_ALLOWED,
+ MS_IDENTITY_CANNOT_BE_DERIVED_BY_THE_NETWORK,
+ IMPLICITLY_DETACHED,
+ PLMN_NOT_ALLOWED,
+ LOCATION_AREA_NOT_ALLOWED,
+ GPRS_SERVICES_NOT_ALLOWED_IN_THIS_PLMN,
+ PDP_DUPLICATE,
+ UE_RAT_CHANGE,
+ CONGESTION,
+ NO_PDP_CONTEXT_ACTIVATED,
+ ACCESS_CLASS_DSAC_REJECTION,
+ PDP_ACTIVATE_MAX_RETRY_FAILED,
+ RADIO_ACCESS_BEARER_FAILURE,
+ ESM_UNKNOWN_EPS_BEARER_CONTEXT,
+ DRB_RELEASED_BY_RRC,
+ CONNECTION_RELEASED,
+ EMM_DETACHED,
+ EMM_ATTACH_FAILED,
+ EMM_ATTACH_STARTED,
+ LTE_NAS_SERVICE_REQUEST_FAILED,
+ DUPLICATE_BEARER_ID,
+ ESM_COLLISION_SCENARIOS,
+ ESM_BEARER_DEACTIVATED_TO_SYNC_WITH_NETWORK,
+ ESM_NW_ACTIVATED_DED_BEARER_WITH_ID_OF_DEF_BEARER,
+ ESM_BAD_OTA_MESSAGE,
+ ESM_DOWNLOAD_SERVER_REJECTED_THE_CALL,
+ ESM_CONTEXT_TRANSFERRED_DUE_TO_IRAT,
+ DS_EXPLICIT_DEACTIVATION,
+ ESM_LOCAL_CAUSE_NONE,
+ LTE_THROTTLING_NOT_REQUIRED,
+ ACCESS_CONTROL_LIST_CHECK_FAILURE,
+ SERVICE_NOT_ALLOWED_ON_PLMN,
+ EMM_T3417_EXPIRED,
+ EMM_T3417_EXT_EXPIRED,
+ RRC_UPLINK_DATA_TRANSMISSION_FAILURE,
+ RRC_UPLINK_DELIVERY_FAILED_DUE_TO_HANDOVER,
+ RRC_UPLINK_CONNECTION_RELEASE,
+ RRC_UPLINK_RADIO_LINK_FAILURE,
+ RRC_UPLINK_ERROR_REQUEST_FROM_NAS,
+ RRC_CONNECTION_ACCESS_STRATUM_FAILURE,
+ RRC_CONNECTION_ANOTHER_PROCEDURE_IN_PROGRESS,
+ RRC_CONNECTION_ACCESS_BARRED,
+ RRC_CONNECTION_CELL_RESELECTION,
+ RRC_CONNECTION_CONFIG_FAILURE,
+ RRC_CONNECTION_TIMER_EXPIRED,
+ RRC_CONNECTION_LINK_FAILURE,
+ RRC_CONNECTION_CELL_NOT_CAMPED,
+ RRC_CONNECTION_SYSTEM_INTERVAL_FAILURE,
+ RRC_CONNECTION_REJECT_BY_NETWORK,
+ RRC_CONNECTION_NORMAL_RELEASE,
+ RRC_CONNECTION_RADIO_LINK_FAILURE,
+ RRC_CONNECTION_REESTABLISHMENT_FAILURE,
+ RRC_CONNECTION_OUT_OF_SERVICE_DURING_CELL_REGISTER,
+ RRC_CONNECTION_ABORT_REQUEST,
+ RRC_CONNECTION_SYSTEM_INFORMATION_BLOCK_READ_ERROR,
+ NETWORK_INITIATED_DETACH_WITH_AUTO_REATTACH,
+ NETWORK_INITIATED_DETACH_NO_AUTO_REATTACH,
+ ESM_PROCEDURE_TIME_OUT,
+ INVALID_CONNECTION_ID,
+ MAXIMIUM_NSAPIS_EXCEEDED,
+ INVALID_PRIMARY_NSAPI,
+ CANNOT_ENCODE_OTA_MESSAGE,
+ RADIO_ACCESS_BEARER_SETUP_FAILURE,
+ PDP_ESTABLISH_TIMEOUT_EXPIRED,
+ PDP_MODIFY_TIMEOUT_EXPIRED,
+ PDP_INACTIVE_TIMEOUT_EXPIRED,
+ PDP_LOWERLAYER_ERROR,
+ PDP_MODIFY_COLLISION,
+ MAXINUM_SIZE_OF_L2_MESSAGE_EXCEEDED,
+ NAS_REQUEST_REJECTED_BY_NETWORK,
+ RRC_CONNECTION_INVALID_REQUEST,
+ RRC_CONNECTION_TRACKING_AREA_ID_CHANGED,
+ RRC_CONNECTION_RF_UNAVAILABLE,
+ RRC_CONNECTION_ABORTED_DUE_TO_IRAT_CHANGE,
+ RRC_CONNECTION_RELEASED_SECURITY_NOT_ACTIVE,
+ RRC_CONNECTION_ABORTED_AFTER_HANDOVER,
+ RRC_CONNECTION_ABORTED_AFTER_IRAT_CELL_CHANGE,
+ RRC_CONNECTION_ABORTED_DURING_IRAT_CELL_CHANGE,
+ IMSI_UNKNOWN_IN_HOME_SUBSCRIBER_SERVER,
+ IMEI_NOT_ACCEPTED,
+ EPS_SERVICES_AND_NON_EPS_SERVICES_NOT_ALLOWED,
+ EPS_SERVICES_NOT_ALLOWED_IN_PLMN,
+ MSC_TEMPORARILY_NOT_REACHABLE,
+ CS_DOMAIN_NOT_AVAILABLE,
+ ESM_FAILURE,
+ MAC_FAILURE,
+ SYNCHRONIZATION_FAILURE,
+ UE_SECURITY_CAPABILITIES_MISMATCH,
+ SECURITY_MODE_REJECTED,
+ UNACCEPTABLE_NON_EPS_AUTHENTICATION,
+ CS_FALLBACK_CALL_ESTABLISHMENT_NOT_ALLOWED,
+ NO_EPS_BEARER_CONTEXT_ACTIVATED,
+ INVALID_EMM_STATE,
+ NAS_LAYER_FAILURE,
+ MULTIPLE_PDP_CALL_NOT_ALLOWED,
+ EMBMS_NOT_ENABLED,
+ IRAT_HANDOVER_FAILED,
+ EMBMS_REGULAR_DEACTIVATION,
+ TEST_LOOPBACK_REGULAR_DEACTIVATION,
+ LOWER_LAYER_REGISTRATION_FAILURE,
+ DATA_PLAN_EXPIRED,
+ UMTS_HANDOVER_TO_IWLAN,
+ EVDO_CONNECTION_DENY_BY_GENERAL_OR_NETWORK_BUSY,
+ EVDO_CONNECTION_DENY_BY_BILLING_OR_AUTHENTICATION_FAILURE,
+ EVDO_HDR_CHANGED,
+ EVDO_HDR_EXITED,
+ EVDO_HDR_NO_SESSION,
+ EVDO_USING_GPS_FIX_INSTEAD_OF_HDR_CALL,
+ EVDO_HDR_CONNECTION_SETUP_TIMEOUT,
+ FAILED_TO_ACQUIRE_COLOCATED_HDR,
+ OTASP_COMMIT_IN_PROGRESS,
+ NO_HYBRID_HDR_SERVICE,
+ HDR_NO_LOCK_GRANTED,
+ DBM_OR_SMS_IN_PROGRESS,
+ HDR_FADE,
+ HDR_ACCESS_FAILURE,
+ UNSUPPORTED_1X_PREV,
+ LOCAL_END,
+ NO_SERVICE,
+ FADE,
+ NORMAL_RELEASE,
+ ACCESS_ATTEMPT_ALREADY_IN_PROGRESS,
+ REDIRECTION_OR_HANDOFF_IN_PROGRESS,
+ EMERGENCY_MODE,
+ PHONE_IN_USE,
+ INVALID_MODE,
+ INVALID_SIM_STATE,
+ NO_COLLOCATED_HDR,
+ UE_IS_ENTERING_POWERSAVE_MODE,
+ DUAL_SWITCH,
+ PPP_TIMEOUT,
+ PPP_AUTH_FAILURE,
+ PPP_OPTION_MISMATCH,
+ PPP_PAP_FAILURE,
+ PPP_CHAP_FAILURE,
+ PPP_CLOSE_IN_PROGRESS,
+ LIMITED_TO_IPV4,
+ LIMITED_TO_IPV6,
+ VSNCP_TIMEOUT,
+ VSNCP_GEN_ERROR,
+ VSNCP_APN_UNATHORIZED,
+ VSNCP_PDN_LIMIT_EXCEEDED,
+ VSNCP_NO_PDN_GATEWAY_ADDRESS,
+ VSNCP_PDN_GATEWAY_UNREACHABLE,
+ VSNCP_PDN_GATEWAY_REJECT,
+ VSNCP_INSUFFICIENT_PARAMETERS,
+ VSNCP_RESOURCE_UNAVAILABLE,
+ VSNCP_ADMINISTRATIVELY_PROHIBITED,
+ VSNCP_PDN_ID_IN_USE,
+ VSNCP_SUBSCRIBER_LIMITATION,
+ VSNCP_PDN_EXISTS_FOR_THIS_APN,
+ VSNCP_RECONNECT_NOT_ALLOWED,
+ IPV6_PREFIX_UNAVAILABLE,
+ HANDOFF_PREFERENCE_CHANGED,
OEM_DCFAILCAUSE_1,
OEM_DCFAILCAUSE_2,
OEM_DCFAILCAUSE_3,
@@ -317,6 +1348,7 @@
sFailCauseMap.put(FILTER_SEMANTIC_ERROR, "FILTER_SEMANTIC_ERROR");
sFailCauseMap.put(FILTER_SYTAX_ERROR, "FILTER_SYTAX_ERROR");
sFailCauseMap.put(PDP_WITHOUT_ACTIVE_TFT, "PDP_WITHOUT_ACTIVE_TFT");
+ sFailCauseMap.put(ACTIVATION_REJECTED_BCM_VIOLATION, "ACTIVATION_REJECTED_BCM_VIOLATION");
sFailCauseMap.put(ONLY_IPV4_ALLOWED, "ONLY_IPV4_ALLOWED");
sFailCauseMap.put(ONLY_IPV6_ALLOWED, "ONLY_IPV6_ALLOWED");
sFailCauseMap.put(ONLY_SINGLE_BEARER_ALLOWED, "ONLY_SINGLE_BEARER_ALLOWED");
@@ -324,6 +1356,12 @@
sFailCauseMap.put(PDN_CONN_DOES_NOT_EXIST, "PDN_CONN_DOES_NOT_EXIST");
sFailCauseMap.put(MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED,
"MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED");
+ sFailCauseMap.put(COLLISION_WITH_NETWORK_INITIATED_REQUEST,
+ "COLLISION_WITH_NETWORK_INITIATED_REQUEST");
+ sFailCauseMap.put(ONLY_IPV4V6_ALLOWED, "ONLY_IPV4V6_ALLOWED");
+ sFailCauseMap.put(ONLY_NON_IP_ALLOWED, "ONLY_NON_IP_ALLOWED");
+ sFailCauseMap.put(UNSUPPORTED_QCI_VALUE, "UNSUPPORTED_QCI_VALUE");
+ sFailCauseMap.put(BEARER_HANDLING_NOT_SUPPORTED, "BEARER_HANDLING_NOT_SUPPORTED");
sFailCauseMap.put(ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED,
"ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED");
sFailCauseMap.put(UNSUPPORTED_APN_IN_CURRENT_PLMN,
@@ -353,6 +1391,301 @@
"EMM_ACCESS_BARRED_INFINITE_RETRY");
sFailCauseMap.put(AUTH_FAILURE_ON_EMERGENCY_CALL,
"AUTH_FAILURE_ON_EMERGENCY_CALL");
+ sFailCauseMap.put(INVALID_DNS_ADDR, "INVALID_DNS_ADDR");
+ sFailCauseMap.put(INVALID_PCSCF_OR_DNS_ADDRESS, "INVALID_PCSCF_OR_DNS_ADDRESS");
+ sFailCauseMap.put(CALL_PREEMPT_BY_EMERGENCY_APN, "CALL_PREEMPT_BY_EMERGENCY_APN");
+ sFailCauseMap.put(UE_INITIATED_DETACH_OR_DISCONNECT, "UE_INITIATED_DETACH_OR_DISCONNECT");
+ sFailCauseMap.put(MIP_FA_REASON_UNSPECIFIED, "MIP_FA_REASON_UNSPECIFIED");
+ sFailCauseMap.put(MIP_FA_ADMIN_PROHIBITED, "MIP_FA_ADMIN_PROHIBITED");
+ sFailCauseMap.put(MIP_FA_INSUFFICIENT_RESOURCES, "MIP_FA_INSUFFICIENT_RESOURCES");
+ sFailCauseMap.put(MIP_FA_MOBILE_NODE_AUTHENTICATION_FAILURE,
+ "MIP_FA_MOBILE_NODE_AUTHENTICATION_FAILURE");
+ sFailCauseMap.put(MIP_FA_HOME_AGENT_AUTHENTICATION_FAILURE,
+ "MIP_FA_HOME_AGENT_AUTHENTICATION_FAILURE");
+ sFailCauseMap.put(MIP_FA_REQUESTED_LIFETIME_TOO_LONG, "MIP_FA_REQUESTED_LIFETIME_TOO_LONG");
+ sFailCauseMap.put(MIP_FA_MALFORMED_REQUEST, "MIP_FA_MALFORMED_REQUEST");
+ sFailCauseMap.put(MIP_FA_MALFORMED_REPLY, "MIP_FA_MALFORMED_REPLY");
+ sFailCauseMap.put(MIP_FA_ENCAPSULATION_UNAVAILABLE, "MIP_FA_ENCAPSULATION_UNAVAILABLE");
+ sFailCauseMap.put(MIP_FA_VJ_HEADER_COMPRESSION_UNAVAILABLE,
+ "MIP_FA_VJ_HEADER_COMPRESSION_UNAVAILABLE");
+ sFailCauseMap.put(MIP_FA_REVERSE_TUNNEL_UNAVAILABLE, "MIP_FA_REVERSE_TUNNEL_UNAVAILABLE");
+ sFailCauseMap.put(MIP_FA_REVERSE_TUNNEL_IS_MANDATORY, "MIP_FA_REVERSE_TUNNEL_IS_MANDATORY");
+ sFailCauseMap.put(MIP_FA_DELIVERY_STYLE_NOT_SUPPORTED,
+ "MIP_FA_DELIVERY_STYLE_NOT_SUPPORTED");
+ sFailCauseMap.put(MIP_FA_MISSING_NAI, "MIP_FA_MISSING_NAI");
+ sFailCauseMap.put(MIP_FA_MISSING_HOME_AGENT, "MIP_FA_MISSING_HOME_AGENT");
+ sFailCauseMap.put(MIP_FA_MISSING_HOME_ADDRESS, "MIP_FA_MISSING_HOME_ADDRESS");
+ sFailCauseMap.put(MIP_FA_UNKNOWN_CHALLENGE, "MIP_FA_UNKNOWN_CHALLENGE");
+ sFailCauseMap.put(MIP_FA_MISSING_CHALLENGE, "MIP_FA_MISSING_CHALLENGE");
+ sFailCauseMap.put(MIP_FA_STALE_CHALLENGE, "MIP_FA_STALE_CHALLENGE");
+ sFailCauseMap.put(MIP_HA_REASON_UNSPECIFIED, "MIP_HA_REASON_UNSPECIFIED");
+ sFailCauseMap.put(MIP_HA_ADMIN_PROHIBITED, "MIP_HA_ADMIN_PROHIBITED");
+ sFailCauseMap.put(MIP_HA_INSUFFICIENT_RESOURCES, "MIP_HA_INSUFFICIENT_RESOURCES");
+ sFailCauseMap.put(MIP_HA_MOBILE_NODE_AUTHENTICATION_FAILURE,
+ "MIP_HA_MOBILE_NODE_AUTHENTICATION_FAILURE");
+ sFailCauseMap.put(MIP_HA_FOREIGN_AGENT_AUTHENTICATION_FAILURE,
+ "MIP_HA_FOREIGN_AGENT_AUTHENTICATION_FAILURE");
+ sFailCauseMap.put(MIP_HA_REGISTRATION_ID_MISMATCH, "MIP_HA_REGISTRATION_ID_MISMATCH");
+ sFailCauseMap.put(MIP_HA_MALFORMED_REQUEST, "MIP_HA_MALFORMED_REQUEST");
+ sFailCauseMap.put(MIP_HA_UNKNOWN_HOME_AGENT_ADDRESS, "MIP_HA_UNKNOWN_HOME_AGENT_ADDRESS");
+ sFailCauseMap.put(MIP_HA_REVERSE_TUNNEL_UNAVAILABLE, "MIP_HA_REVERSE_TUNNEL_UNAVAILABLE");
+ sFailCauseMap.put(MIP_HA_REVERSE_TUNNEL_IS_MANDATORY, "MIP_HA_REVERSE_TUNNEL_IS_MANDATORY");
+ sFailCauseMap.put(MIP_HA_ENCAPSULATION_UNAVAILABLE, "MIP_HA_ENCAPSULATION_UNAVAILABLE");
+ sFailCauseMap.put(CLOSE_IN_PROGRESS, "CLOSE_IN_PROGRESS");
+ sFailCauseMap.put(NETWORK_INITIATED_TERMINATION, "NETWORK_INITIATED_TERMINATION");
+ sFailCauseMap.put(MODEM_APP_PREEMPTED, "MODEM_APP_PREEMPTED");
+ sFailCauseMap.put(PDN_IPV4_CALL_DISALLOWED, "PDN_IPV4_CALL_DISALLOWED");
+ sFailCauseMap.put(PDN_IPV4_CALL_THROTTLED, "PDN_IPV4_CALL_THROTTLED");
+ sFailCauseMap.put(PDN_IPV6_CALL_DISALLOWED, "PDN_IPV6_CALL_DISALLOWED");
+ sFailCauseMap.put(PDN_IPV6_CALL_THROTTLED, "PDN_IPV6_CALL_THROTTLED");
+ sFailCauseMap.put(MODEM_RESTART, "MODEM_RESTART");
+ sFailCauseMap.put(PDP_PPP_NOT_SUPPORTED, "PDP_PPP_NOT_SUPPORTED");
+ sFailCauseMap.put(UNPREFERRED_RAT, "UNPREFERRED_RAT");
+ sFailCauseMap.put(PHYSICAL_LINK_CLOSE_IN_PROGRESS, "PHYSICAL_LINK_CLOSE_IN_PROGRESS");
+ sFailCauseMap.put(APN_PENDING_HANDOVER, "APN_PENDING_HANDOVER");
+ sFailCauseMap.put(PROFILE_BEARER_INCOMPATIBLE, "PROFILE_BEARER_INCOMPATIBLE");
+ sFailCauseMap.put(SIM_CARD_CHANGED, "SIM_CARD_CHANGED");
+ sFailCauseMap.put(LOW_POWER_MODE_OR_POWERING_DOWN, "LOW_POWER_MODE_OR_POWERING_DOWN");
+ sFailCauseMap.put(APN_DISABLED, "APN_DISABLED");
+ sFailCauseMap.put(MAX_PPP_INACTIVITY_TIMER_EXPIRED, "MAX_PPP_INACTIVITY_TIMER_EXPIRED");
+ sFailCauseMap.put(IPV6_ADDRESS_TRANSFER_FAILED, "IPV6_ADDRESS_TRANSFER_FAILED");
+ sFailCauseMap.put(TRAT_SWAP_FAILED, "TRAT_SWAP_FAILED");
+ sFailCauseMap.put(EHRPD_TO_HRPD_FALLBACK, "EHRPD_TO_HRPD_FALLBACK");
+ sFailCauseMap.put(MIP_CONFIG_FAILURE, "MIP_CONFIG_FAILURE");
+ sFailCauseMap.put(PDN_INACTIVITY_TIMER_EXPIRED, "PDN_INACTIVITY_TIMER_EXPIRED");
+ sFailCauseMap.put(MAX_IPV4_CONNECTIONS, "MAX_IPV4_CONNECTIONS");
+ sFailCauseMap.put(MAX_IPV6_CONNECTIONS, "MAX_IPV6_CONNECTIONS");
+ sFailCauseMap.put(APN_MISMATCH, "APN_MISMATCH");
+ sFailCauseMap.put(IP_VERSION_MISMATCH, "IP_VERSION_MISMATCH");
+ sFailCauseMap.put(DUN_CALL_DISALLOWED, "DUN_CALL_DISALLOWED");
+ sFailCauseMap.put(INTERNAL_EPC_NONEPC_TRANSITION, "INTERNAL_EPC_NONEPC_TRANSITION");
+ sFailCauseMap.put(INTERFACE_IN_USE, "INTERFACE_IN_USE");
+ sFailCauseMap.put(APN_DISALLOWED_ON_ROAMING, "APN_DISALLOWED_ON_ROAMING");
+ sFailCauseMap.put(APN_PARAMETERS_CHANGED, "APN_PARAMETERS_CHANGED");
+ sFailCauseMap.put(NULL_APN_DISALLOWED, "NULL_APN_DISALLOWED");
+ sFailCauseMap.put(THERMAL_MITIGATION, "THERMAL_MITIGATION");
+ sFailCauseMap.put(DATA_SETTINGS_DISABLED, "DATA_SETTINGS_DISABLED");
+ sFailCauseMap.put(DATA_ROAMING_SETTINGS_DISABLED, "DATA_ROAMING_SETTINGS_DISABLED");
+ sFailCauseMap.put(DDS_SWITCHED, "DDS_SWITCHED");
+ sFailCauseMap.put(FORBIDDEN_APN_NAME, "FORBIDDEN_APN_NAME");
+ sFailCauseMap.put(DDS_SWITCH_IN_PROGRESS, "DDS_SWITCH_IN_PROGRESS");
+ sFailCauseMap.put(CALL_DISALLOWED_IN_ROAMING, "CALL_DISALLOWED_IN_ROAMING");
+ sFailCauseMap.put(NON_IP_NOT_SUPPORTED, "NON_IP_NOT_SUPPORTED");
+ sFailCauseMap.put(PDN_NON_IP_CALL_THROTTLED, "PDN_NON_IP_CALL_THROTTLED");
+ sFailCauseMap.put(PDN_NON_IP_CALL_DISALLOWED, "PDN_NON_IP_CALL_DISALLOWED");
+ sFailCauseMap.put(CDMA_LOCK, "CDMA_LOCK");
+ sFailCauseMap.put(CDMA_INTERCEPT, "CDMA_INTERCEPT");
+ sFailCauseMap.put(CDMA_REORDER, "CDMA_REORDER");
+ sFailCauseMap.put(CDMA_RELEASE_DUE_TO_SO_REJECTION, "CDMA_RELEASE_DUE_TO_SO_REJECTION");
+ sFailCauseMap.put(CDMA_INCOMING_CALL, "CDMA_INCOMING_CALL");
+ sFailCauseMap.put(CDMA_ALERT_STOP, "CDMA_ALERT_STOP");
+ sFailCauseMap.put(CHANNEL_ACQUISITION_FAILURE, "CHANNEL_ACQUISITION_FAILURE");
+ sFailCauseMap.put(MAX_ACCESS_PROBE, "MAX_ACCESS_PROBE");
+ sFailCauseMap.put(CONCURRENT_SERVICE_NOT_SUPPORTED_BY_BASE_STATION,
+ "CONCURRENT_SERVICE_NOT_SUPPORTED_BY_BASE_STATION");
+ sFailCauseMap.put(NO_RESPONSE_FROM_BASE_STATION, "NO_RESPONSE_FROM_BASE_STATION");
+ sFailCauseMap.put(REJECTED_BY_BASE_STATION, "REJECTED_BY_BASE_STATION");
+ sFailCauseMap.put(CONCURRENT_SERVICES_INCOMPATIBLE, "CONCURRENT_SERVICES_INCOMPATIBLE");
+ sFailCauseMap.put(NO_CDMA_SERVICE, "NO_CDMA_SERVICE");
+ sFailCauseMap.put(RUIM_NOT_PRESENT, "RUIM_NOT_PRESENT");
+ sFailCauseMap.put(CDMA_RETRY_ORDER, "CDMA_RETRY_ORDER");
+ sFailCauseMap.put(ACCESS_BLOCK, "ACCESS_BLOCK");
+ sFailCauseMap.put(ACCESS_BLOCK_ALL, "ACCESS_BLOCK_ALL");
+ sFailCauseMap.put(IS707B_MAX_ACCESS_PROBES, "IS707B_MAX_ACCESS_PROBES");
+ sFailCauseMap.put(THERMAL_EMERGENCY, "THERMAL_EMERGENCY");
+ sFailCauseMap.put(CONCURRENT_SERVICES_NOT_ALLOWED, "CONCURRENT_SERVICES_NOT_ALLOWED");
+ sFailCauseMap.put(INCOMING_CALL_REJECTED, "INCOMING_CALL_REJECTED");
+ sFailCauseMap.put(NO_SERVICE_ON_GATEWAY, "NO_SERVICE_ON_GATEWAY");
+ sFailCauseMap.put(NO_GPRS_CONTEXT, "NO_GPRS_CONTEXT");
+ sFailCauseMap.put(ILLEGAL_MS, "ILLEGAL_MS");
+ sFailCauseMap.put(ILLEGAL_ME, "ILLEGAL_ME");
+ sFailCauseMap.put(GPRS_SERVICES_AND_NON_GPRS_SERVICES_NOT_ALLOWED,
+ "GPRS_SERVICES_AND_NON_GPRS_SERVICES_NOT_ALLOWED");
+ sFailCauseMap.put(GPRS_SERVICES_NOT_ALLOWED, "GPRS_SERVICES_NOT_ALLOWED");
+ sFailCauseMap.put(MS_IDENTITY_CANNOT_BE_DERIVED_BY_THE_NETWORK,
+ "MS_IDENTITY_CANNOT_BE_DERIVED_BY_THE_NETWORK");
+ sFailCauseMap.put(IMPLICITLY_DETACHED, "IMPLICITLY_DETACHED");
+ sFailCauseMap.put(PLMN_NOT_ALLOWED, "PLMN_NOT_ALLOWED");
+ sFailCauseMap.put(LOCATION_AREA_NOT_ALLOWED, "LOCATION_AREA_NOT_ALLOWED");
+ sFailCauseMap.put(GPRS_SERVICES_NOT_ALLOWED_IN_THIS_PLMN,
+ "GPRS_SERVICES_NOT_ALLOWED_IN_THIS_PLMN");
+ sFailCauseMap.put(PDP_DUPLICATE, "PDP_DUPLICATE");
+ sFailCauseMap.put(UE_RAT_CHANGE, "UE_RAT_CHANGE");
+ sFailCauseMap.put(CONGESTION, "CONGESTION");
+ sFailCauseMap.put(NO_PDP_CONTEXT_ACTIVATED, "NO_PDP_CONTEXT_ACTIVATED");
+ sFailCauseMap.put(ACCESS_CLASS_DSAC_REJECTION, "ACCESS_CLASS_DSAC_REJECTION");
+ sFailCauseMap.put(PDP_ACTIVATE_MAX_RETRY_FAILED, "PDP_ACTIVATE_MAX_RETRY_FAILED");
+ sFailCauseMap.put(RADIO_ACCESS_BEARER_FAILURE, "RADIO_ACCESS_BEARER_FAILURE");
+ sFailCauseMap.put(ESM_UNKNOWN_EPS_BEARER_CONTEXT, "ESM_UNKNOWN_EPS_BEARER_CONTEXT");
+ sFailCauseMap.put(DRB_RELEASED_BY_RRC, "DRB_RELEASED_BY_RRC");
+ sFailCauseMap.put(CONNECTION_RELEASED, "CONNECTION_RELEASED");
+ sFailCauseMap.put(EMM_DETACHED, "EMM_DETACHED");
+ sFailCauseMap.put(EMM_ATTACH_FAILED, "EMM_ATTACH_FAILED");
+ sFailCauseMap.put(EMM_ATTACH_STARTED, "EMM_ATTACH_STARTED");
+ sFailCauseMap.put(LTE_NAS_SERVICE_REQUEST_FAILED, "LTE_NAS_SERVICE_REQUEST_FAILED");
+ sFailCauseMap.put(DUPLICATE_BEARER_ID, "DUPLICATE_BEARER_ID");
+ sFailCauseMap.put(ESM_COLLISION_SCENARIOS, "ESM_COLLISION_SCENARIOS");
+ sFailCauseMap.put(ESM_BEARER_DEACTIVATED_TO_SYNC_WITH_NETWORK,
+ "ESM_BEARER_DEACTIVATED_TO_SYNC_WITH_NETWORK");
+ sFailCauseMap.put(ESM_NW_ACTIVATED_DED_BEARER_WITH_ID_OF_DEF_BEARER,
+ "ESM_NW_ACTIVATED_DED_BEARER_WITH_ID_OF_DEF_BEARER");
+ sFailCauseMap.put(ESM_BAD_OTA_MESSAGE, "ESM_BAD_OTA_MESSAGE");
+ sFailCauseMap.put(ESM_DOWNLOAD_SERVER_REJECTED_THE_CALL,
+ "ESM_DOWNLOAD_SERVER_REJECTED_THE_CALL");
+ sFailCauseMap.put(ESM_CONTEXT_TRANSFERRED_DUE_TO_IRAT,
+ "ESM_CONTEXT_TRANSFERRED_DUE_TO_IRAT");
+ sFailCauseMap.put(DS_EXPLICIT_DEACTIVATION, "DS_EXPLICIT_DEACTIVATION");
+ sFailCauseMap.put(ESM_LOCAL_CAUSE_NONE, "ESM_LOCAL_CAUSE_NONE");
+ sFailCauseMap.put(LTE_THROTTLING_NOT_REQUIRED, "LTE_THROTTLING_NOT_REQUIRED");
+ sFailCauseMap.put(ACCESS_CONTROL_LIST_CHECK_FAILURE,
+ "ACCESS_CONTROL_LIST_CHECK_FAILURE");
+ sFailCauseMap.put(SERVICE_NOT_ALLOWED_ON_PLMN, "SERVICE_NOT_ALLOWED_ON_PLMN");
+ sFailCauseMap.put(EMM_T3417_EXPIRED, "EMM_T3417_EXPIRED");
+ sFailCauseMap.put(EMM_T3417_EXT_EXPIRED, "EMM_T3417_EXT_EXPIRED");
+ sFailCauseMap.put(RRC_UPLINK_DATA_TRANSMISSION_FAILURE,
+ "RRC_UPLINK_DATA_TRANSMISSION_FAILURE");
+ sFailCauseMap.put(RRC_UPLINK_DELIVERY_FAILED_DUE_TO_HANDOVER,
+ "RRC_UPLINK_DELIVERY_FAILED_DUE_TO_HANDOVER");
+ sFailCauseMap.put(RRC_UPLINK_CONNECTION_RELEASE, "RRC_UPLINK_CONNECTION_RELEASE");
+ sFailCauseMap.put(RRC_UPLINK_RADIO_LINK_FAILURE, "RRC_UPLINK_RADIO_LINK_FAILURE");
+ sFailCauseMap.put(RRC_UPLINK_ERROR_REQUEST_FROM_NAS, "RRC_UPLINK_ERROR_REQUEST_FROM_NAS");
+ sFailCauseMap.put(RRC_CONNECTION_ACCESS_STRATUM_FAILURE,
+ "RRC_CONNECTION_ACCESS_STRATUM_FAILURE");
+ sFailCauseMap.put(RRC_CONNECTION_ANOTHER_PROCEDURE_IN_PROGRESS,
+ "RRC_CONNECTION_ANOTHER_PROCEDURE_IN_PROGRESS");
+ sFailCauseMap.put(RRC_CONNECTION_ACCESS_BARRED, "RRC_CONNECTION_ACCESS_BARRED");
+ sFailCauseMap.put(RRC_CONNECTION_CELL_RESELECTION, "RRC_CONNECTION_CELL_RESELECTION");
+ sFailCauseMap.put(RRC_CONNECTION_CONFIG_FAILURE, "RRC_CONNECTION_CONFIG_FAILURE");
+ sFailCauseMap.put(RRC_CONNECTION_TIMER_EXPIRED, "RRC_CONNECTION_TIMER_EXPIRED");
+ sFailCauseMap.put(RRC_CONNECTION_LINK_FAILURE, "RRC_CONNECTION_LINK_FAILURE");
+ sFailCauseMap.put(RRC_CONNECTION_CELL_NOT_CAMPED, "RRC_CONNECTION_CELL_NOT_CAMPED");
+ sFailCauseMap.put(RRC_CONNECTION_SYSTEM_INTERVAL_FAILURE,
+ "RRC_CONNECTION_SYSTEM_INTERVAL_FAILURE");
+ sFailCauseMap.put(RRC_CONNECTION_REJECT_BY_NETWORK, "RRC_CONNECTION_REJECT_BY_NETWORK");
+ sFailCauseMap.put(RRC_CONNECTION_NORMAL_RELEASE, "RRC_CONNECTION_NORMAL_RELEASE");
+ sFailCauseMap.put(RRC_CONNECTION_RADIO_LINK_FAILURE, "RRC_CONNECTION_RADIO_LINK_FAILURE");
+ sFailCauseMap.put(RRC_CONNECTION_REESTABLISHMENT_FAILURE,
+ "RRC_CONNECTION_REESTABLISHMENT_FAILURE");
+ sFailCauseMap.put(RRC_CONNECTION_OUT_OF_SERVICE_DURING_CELL_REGISTER,
+ "RRC_CONNECTION_OUT_OF_SERVICE_DURING_CELL_REGISTER");
+ sFailCauseMap.put(RRC_CONNECTION_ABORT_REQUEST, "RRC_CONNECTION_ABORT_REQUEST");
+ sFailCauseMap.put(RRC_CONNECTION_SYSTEM_INFORMATION_BLOCK_READ_ERROR,
+ "RRC_CONNECTION_SYSTEM_INFORMATION_BLOCK_READ_ERROR");
+ sFailCauseMap.put(NETWORK_INITIATED_DETACH_WITH_AUTO_REATTACH,
+ "NETWORK_INITIATED_DETACH_WITH_AUTO_REATTACH");
+ sFailCauseMap.put(NETWORK_INITIATED_DETACH_NO_AUTO_REATTACH,
+ "NETWORK_INITIATED_DETACH_NO_AUTO_REATTACH");
+ sFailCauseMap.put(ESM_PROCEDURE_TIME_OUT, "ESM_PROCEDURE_TIME_OUT");
+ sFailCauseMap.put(INVALID_CONNECTION_ID, "INVALID_CONNECTION_ID");
+ sFailCauseMap.put(MAXIMIUM_NSAPIS_EXCEEDED, "MAXIMIUM_NSAPIS_EXCEEDED");
+ sFailCauseMap.put(INVALID_PRIMARY_NSAPI, "INVALID_PRIMARY_NSAPI");
+ sFailCauseMap.put(CANNOT_ENCODE_OTA_MESSAGE, "CANNOT_ENCODE_OTA_MESSAGE");
+ sFailCauseMap.put(RADIO_ACCESS_BEARER_SETUP_FAILURE, "RADIO_ACCESS_BEARER_SETUP_FAILURE");
+ sFailCauseMap.put(PDP_ESTABLISH_TIMEOUT_EXPIRED, "PDP_ESTABLISH_TIMEOUT_EXPIRED");
+ sFailCauseMap.put(PDP_MODIFY_TIMEOUT_EXPIRED, "PDP_MODIFY_TIMEOUT_EXPIRED");
+ sFailCauseMap.put(PDP_INACTIVE_TIMEOUT_EXPIRED, "PDP_INACTIVE_TIMEOUT_EXPIRED");
+ sFailCauseMap.put(PDP_LOWERLAYER_ERROR, "PDP_LOWERLAYER_ERROR");
+ sFailCauseMap.put(PDP_MODIFY_COLLISION, "PDP_MODIFY_COLLISION");
+ sFailCauseMap.put(MAXINUM_SIZE_OF_L2_MESSAGE_EXCEEDED,
+ "MAXINUM_SIZE_OF_L2_MESSAGE_EXCEEDED");
+ sFailCauseMap.put(NAS_REQUEST_REJECTED_BY_NETWORK, "NAS_REQUEST_REJECTED_BY_NETWORK");
+ sFailCauseMap.put(RRC_CONNECTION_INVALID_REQUEST, "RRC_CONNECTION_INVALID_REQUEST");
+ sFailCauseMap.put(RRC_CONNECTION_TRACKING_AREA_ID_CHANGED,
+ "RRC_CONNECTION_TRACKING_AREA_ID_CHANGED");
+ sFailCauseMap.put(RRC_CONNECTION_RF_UNAVAILABLE, "RRC_CONNECTION_RF_UNAVAILABLE");
+ sFailCauseMap.put(RRC_CONNECTION_ABORTED_DUE_TO_IRAT_CHANGE,
+ "RRC_CONNECTION_ABORTED_DUE_TO_IRAT_CHANGE");
+ sFailCauseMap.put(RRC_CONNECTION_RELEASED_SECURITY_NOT_ACTIVE,
+ "RRC_CONNECTION_RELEASED_SECURITY_NOT_ACTIVE");
+ sFailCauseMap.put(RRC_CONNECTION_ABORTED_AFTER_HANDOVER,
+ "RRC_CONNECTION_ABORTED_AFTER_HANDOVER");
+ sFailCauseMap.put(RRC_CONNECTION_ABORTED_AFTER_IRAT_CELL_CHANGE,
+ "RRC_CONNECTION_ABORTED_AFTER_IRAT_CELL_CHANGE");
+ sFailCauseMap.put(RRC_CONNECTION_ABORTED_DURING_IRAT_CELL_CHANGE,
+ "RRC_CONNECTION_ABORTED_DURING_IRAT_CELL_CHANGE");
+ sFailCauseMap.put(IMSI_UNKNOWN_IN_HOME_SUBSCRIBER_SERVER,
+ "IMSI_UNKNOWN_IN_HOME_SUBSCRIBER_SERVER");
+ sFailCauseMap.put(IMEI_NOT_ACCEPTED, "IMEI_NOT_ACCEPTED");
+ sFailCauseMap.put(EPS_SERVICES_AND_NON_EPS_SERVICES_NOT_ALLOWED,
+ "EPS_SERVICES_AND_NON_EPS_SERVICES_NOT_ALLOWED");
+ sFailCauseMap.put(EPS_SERVICES_NOT_ALLOWED_IN_PLMN, "EPS_SERVICES_NOT_ALLOWED_IN_PLMN");
+ sFailCauseMap.put(MSC_TEMPORARILY_NOT_REACHABLE, "MSC_TEMPORARILY_NOT_REACHABLE");
+ sFailCauseMap.put(CS_DOMAIN_NOT_AVAILABLE, "CS_DOMAIN_NOT_AVAILABLE");
+ sFailCauseMap.put(ESM_FAILURE, "ESM_FAILURE");
+ sFailCauseMap.put(MAC_FAILURE, "MAC_FAILURE");
+ sFailCauseMap.put(SYNCHRONIZATION_FAILURE, "SYNCHRONIZATION_FAILURE");
+ sFailCauseMap.put(UE_SECURITY_CAPABILITIES_MISMATCH, "UE_SECURITY_CAPABILITIES_MISMATCH");
+ sFailCauseMap.put(SECURITY_MODE_REJECTED, "SECURITY_MODE_REJECTED");
+ sFailCauseMap.put(UNACCEPTABLE_NON_EPS_AUTHENTICATION,
+ "UNACCEPTABLE_NON_EPS_AUTHENTICATION");
+ sFailCauseMap.put(CS_FALLBACK_CALL_ESTABLISHMENT_NOT_ALLOWED,
+ "CS_FALLBACK_CALL_ESTABLISHMENT_NOT_ALLOWED");
+ sFailCauseMap.put(NO_EPS_BEARER_CONTEXT_ACTIVATED, "NO_EPS_BEARER_CONTEXT_ACTIVATED");
+ sFailCauseMap.put(INVALID_EMM_STATE, "INVALID_EMM_STATE");
+ sFailCauseMap.put(NAS_LAYER_FAILURE, "NAS_LAYER_FAILURE");
+ sFailCauseMap.put(MULTIPLE_PDP_CALL_NOT_ALLOWED, "MULTIPLE_PDP_CALL_NOT_ALLOWED");
+ sFailCauseMap.put(EMBMS_NOT_ENABLED, "EMBMS_NOT_ENABLED");
+ sFailCauseMap.put(IRAT_HANDOVER_FAILED, "IRAT_HANDOVER_FAILED");
+ sFailCauseMap.put(EMBMS_REGULAR_DEACTIVATION, "EMBMS_REGULAR_DEACTIVATION");
+ sFailCauseMap.put(TEST_LOOPBACK_REGULAR_DEACTIVATION, "TEST_LOOPBACK_REGULAR_DEACTIVATION");
+ sFailCauseMap.put(LOWER_LAYER_REGISTRATION_FAILURE, "LOWER_LAYER_REGISTRATION_FAILURE");
+ sFailCauseMap.put(DATA_PLAN_EXPIRED, "DATA_PLAN_EXPIRED");
+ sFailCauseMap.put(UMTS_HANDOVER_TO_IWLAN, "UMTS_HANDOVER_TO_IWLAN");
+ sFailCauseMap.put(EVDO_CONNECTION_DENY_BY_GENERAL_OR_NETWORK_BUSY,
+ "EVDO_CONNECTION_DENY_BY_GENERAL_OR_NETWORK_BUSY");
+ sFailCauseMap.put(EVDO_CONNECTION_DENY_BY_BILLING_OR_AUTHENTICATION_FAILURE,
+ "EVDO_CONNECTION_DENY_BY_BILLING_OR_AUTHENTICATION_FAILURE");
+ sFailCauseMap.put(EVDO_HDR_CHANGED, "EVDO_HDR_CHANGED");
+ sFailCauseMap.put(EVDO_HDR_EXITED, "EVDO_HDR_EXITED");
+ sFailCauseMap.put(EVDO_HDR_NO_SESSION, "EVDO_HDR_NO_SESSION");
+ sFailCauseMap.put(EVDO_USING_GPS_FIX_INSTEAD_OF_HDR_CALL,
+ "EVDO_USING_GPS_FIX_INSTEAD_OF_HDR_CALL");
+ sFailCauseMap.put(EVDO_HDR_CONNECTION_SETUP_TIMEOUT, "EVDO_HDR_CONNECTION_SETUP_TIMEOUT");
+ sFailCauseMap.put(FAILED_TO_ACQUIRE_COLOCATED_HDR, "FAILED_TO_ACQUIRE_COLOCATED_HDR");
+ sFailCauseMap.put(OTASP_COMMIT_IN_PROGRESS, "OTASP_COMMIT_IN_PROGRESS");
+ sFailCauseMap.put(NO_HYBRID_HDR_SERVICE, "NO_HYBRID_HDR_SERVICE");
+ sFailCauseMap.put(HDR_NO_LOCK_GRANTED, "HDR_NO_LOCK_GRANTED");
+ sFailCauseMap.put(DBM_OR_SMS_IN_PROGRESS, "DBM_OR_SMS_IN_PROGRESS");
+ sFailCauseMap.put(HDR_FADE, "HDR_FADE");
+ sFailCauseMap.put(HDR_ACCESS_FAILURE, "HDR_ACCESS_FAILURE");
+ sFailCauseMap.put(UNSUPPORTED_1X_PREV, "UNSUPPORTED_1X_PREV");
+ sFailCauseMap.put(LOCAL_END, "LOCAL_END");
+ sFailCauseMap.put(NO_SERVICE, "NO_SERVICE");
+ sFailCauseMap.put(FADE, "FADE");
+ sFailCauseMap.put(NORMAL_RELEASE, "NORMAL_RELEASE");
+ sFailCauseMap.put(ACCESS_ATTEMPT_ALREADY_IN_PROGRESS, "ACCESS_ATTEMPT_ALREADY_IN_PROGRESS");
+ sFailCauseMap.put(REDIRECTION_OR_HANDOFF_IN_PROGRESS, "REDIRECTION_OR_HANDOFF_IN_PROGRESS");
+ sFailCauseMap.put(EMERGENCY_MODE, "EMERGENCY_MODE");
+ sFailCauseMap.put(PHONE_IN_USE, "PHONE_IN_USE");
+ sFailCauseMap.put(INVALID_MODE, "INVALID_MODE");
+ sFailCauseMap.put(INVALID_SIM_STATE, "INVALID_SIM_STATE");
+ sFailCauseMap.put(NO_COLLOCATED_HDR, "NO_COLLOCATED_HDR");
+ sFailCauseMap.put(UE_IS_ENTERING_POWERSAVE_MODE, "UE_IS_ENTERING_POWERSAVE_MODE");
+ sFailCauseMap.put(DUAL_SWITCH, "DUAL_SWITCH");
+ sFailCauseMap.put(PPP_TIMEOUT, "PPP_TIMEOUT");
+ sFailCauseMap.put(PPP_AUTH_FAILURE, "PPP_AUTH_FAILURE");
+ sFailCauseMap.put(PPP_OPTION_MISMATCH, "PPP_OPTION_MISMATCH");
+ sFailCauseMap.put(PPP_PAP_FAILURE, "PPP_PAP_FAILURE");
+ sFailCauseMap.put(PPP_CHAP_FAILURE, "PPP_CHAP_FAILURE");
+ sFailCauseMap.put(PPP_CLOSE_IN_PROGRESS, "PPP_CLOSE_IN_PROGRESS");
+ sFailCauseMap.put(LIMITED_TO_IPV4, "LIMITED_TO_IPV4");
+ sFailCauseMap.put(LIMITED_TO_IPV6, "LIMITED_TO_IPV6");
+ sFailCauseMap.put(VSNCP_TIMEOUT, "VSNCP_TIMEOUT");
+ sFailCauseMap.put(VSNCP_GEN_ERROR, "VSNCP_GEN_ERROR");
+ sFailCauseMap.put(VSNCP_APN_UNATHORIZED, "VSNCP_APN_UNATHORIZED");
+ sFailCauseMap.put(VSNCP_PDN_LIMIT_EXCEEDED, "VSNCP_PDN_LIMIT_EXCEEDED");
+ sFailCauseMap.put(VSNCP_NO_PDN_GATEWAY_ADDRESS, "VSNCP_NO_PDN_GATEWAY_ADDRESS");
+ sFailCauseMap.put(VSNCP_PDN_GATEWAY_UNREACHABLE, "VSNCP_PDN_GATEWAY_UNREACHABLE");
+ sFailCauseMap.put(VSNCP_PDN_GATEWAY_REJECT, "VSNCP_PDN_GATEWAY_REJECT");
+ sFailCauseMap.put(VSNCP_INSUFFICIENT_PARAMETERS, "VSNCP_INSUFFICIENT_PARAMETERS");
+ sFailCauseMap.put(VSNCP_RESOURCE_UNAVAILABLE, "VSNCP_RESOURCE_UNAVAILABLE");
+ sFailCauseMap.put(VSNCP_ADMINISTRATIVELY_PROHIBITED, "VSNCP_ADMINISTRATIVELY_PROHIBITED");
+ sFailCauseMap.put(VSNCP_PDN_ID_IN_USE, "VSNCP_PDN_ID_IN_USE");
+ sFailCauseMap.put(VSNCP_SUBSCRIBER_LIMITATION, "VSNCP_SUBSCRIBER_LIMITATION");
+ sFailCauseMap.put(VSNCP_PDN_EXISTS_FOR_THIS_APN, "VSNCP_PDN_EXISTS_FOR_THIS_APN");
+ sFailCauseMap.put(VSNCP_RECONNECT_NOT_ALLOWED, "VSNCP_RECONNECT_NOT_ALLOWED");
+ sFailCauseMap.put(IPV6_PREFIX_UNAVAILABLE, "IPV6_PREFIX_UNAVAILABLE");
+ sFailCauseMap.put(HANDOFF_PREFERENCE_CHANGED, "HANDOFF_PREFERENCE_CHANGED");
sFailCauseMap.put(OEM_DCFAILCAUSE_1, "OEM_DCFAILCAUSE_1");
sFailCauseMap.put(OEM_DCFAILCAUSE_2, "OEM_DCFAILCAUSE_2");
sFailCauseMap.put(OEM_DCFAILCAUSE_3, "OEM_DCFAILCAUSE_3");
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index ad3ca6d..91375bc 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -292,14 +292,27 @@
* Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69
*
* @return RSSI in ASU 0..31, 99, or UNAVAILABLE
+ *
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthGsm#getAsuLevel}.
+ * @see android.telephony#CellSignalStrengthGsm
+ * @see android.telephony.SignalStrength#getCellSignalStrengths
*/
+ @Deprecated
public int getGsmSignalStrength() {
return mGsm.getAsuLevel();
}
/**
* Get the GSM bit error rate (0-7, 99) as defined in TS 27.007 8.5
+ *
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthGsm#getBitErrorRate}.
+ *
+ * @see android.telephony#CellSignalStrengthGsm
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
*/
+ @Deprecated
public int getGsmBitErrorRate() {
return mGsm.getBitErrorRate();
}
@@ -308,14 +321,28 @@
* Get the CDMA RSSI value in dBm
*
* @return the CDMA RSSI value or {@link #INVALID} if invalid
+ *
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthCdma#getCdmaDbm}.
+ *
+ * @see android.telephony#CellSignalStrengthCdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
*/
+ @Deprecated
public int getCdmaDbm() {
return mCdma.getCdmaDbm();
}
/**
* Get the CDMA Ec/Io value in dB*10
+ *
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthCdma#getCdmaEcio}.
+ *
+ * @see android.telephony#CellSignalStrengthCdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
*/
+ @Deprecated
public int getCdmaEcio() {
return mCdma.getCdmaEcio();
}
@@ -324,51 +351,112 @@
* Get the EVDO RSSI value in dBm
*
* @return the EVDO RSSI value or {@link #INVALID} if invalid
+ *
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthCdma#getEvdoDbm}.
+ *
+ * @see android.telephony#CellSignalStrengthCdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
*/
+ @Deprecated
public int getEvdoDbm() {
return mCdma.getEvdoDbm();
}
/**
* Get the EVDO Ec/Io value in dB*10
+ *
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthCdma#getEvdoEcio}.
+ *
+ * @see android.telephony#CellSignalStrengthCdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
*/
+ @Deprecated
public int getEvdoEcio() {
return mCdma.getEvdoEcio();
}
/**
* Get the signal to noise ratio. Valid values are 0-8. 8 is the highest.
+ *
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthCdma#getEvdoSnr}.
+ *
+ * @see android.telephony#CellSignalStrengthCdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
*/
+ @Deprecated
public int getEvdoSnr() {
return mCdma.getEvdoSnr();
}
- /** @hide */
- @UnsupportedAppUsage
+ /**
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthLte#getRssi}.
+ *
+ * @see android.telephony#CellSignalStrengthLte
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getLteSignalStrength() {
return mLte.getRssi();
}
- /** @hide */
- @UnsupportedAppUsage
+ /**
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthLte#getRsrp}.
+ *
+ * @see android.telephony#CellSignalStrengthLte
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getLteRsrp() {
return mLte.getRsrp();
}
- /** @hide */
- @UnsupportedAppUsage
+ /**
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthLte#getRsrq}.
+ *
+ * @see android.telephony#CellSignalStrengthLte
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getLteRsrq() {
return mLte.getRsrq();
}
- /** @hide */
- @UnsupportedAppUsage
+ /**
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthLte#getRssnr}.
+ *
+ * @see android.telephony#CellSignalStrengthLte
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getLteRssnr() {
return mLte.getRssnr();
}
- /** @hide */
- @UnsupportedAppUsage
+ /**
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthLte#getCqi}.
+ *
+ * @see android.telephony#CellSignalStrengthLte
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getLteCqi() {
return mLte.getCqi();
}
@@ -391,11 +479,17 @@
}
/**
- * Get the signal level as an asu value between 0..31, 99 is unknown
+ * Get the signal level as an asu value with a range dependent on the underlying technology.
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrength#getAsuLevel}. Because the levels vary by technology,
+ * this method is misleading and should not be used.
+ * @see android.telephony#CellSignalStrength
+ * @see android.telephony.SignalStrength#getCellSignalStrengths
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getAsuLevel() {
return getPrimary().getAsuLevel();
}
@@ -403,9 +497,15 @@
/**
* Get the signal strength as dBm
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrength#getDbm()}. Because the levels vary by technology,
+ * this method is misleading and should not be used.
+ * @see android.telephony#CellSignalStrength
+ * @see android.telephony.SignalStrength#getCellSignalStrengths
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getDbm() {
return getPrimary().getDbm();
}
@@ -413,9 +513,15 @@
/**
* Get Gsm signal strength as dBm
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthGsm#getDbm}.
+ *
+ * @see android.telephony#CellSignalStrengthGsm
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getGsmDbm() {
return mGsm.getDbm();
}
@@ -423,9 +529,15 @@
/**
* Get gsm as level 0..4
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthGsm#getLevel}.
+ *
+ * @see android.telephony#CellSignalStrengthGsm
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getGsmLevel() {
return mGsm.getLevel();
}
@@ -433,9 +545,15 @@
/**
* Get the gsm signal level as an asu value between 0..31, 99 is unknown
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthGsm#getAsuLevel}.
+ *
+ * @see android.telephony#CellSignalStrengthGsm
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getGsmAsuLevel() {
return mGsm.getAsuLevel();
}
@@ -443,9 +561,15 @@
/**
* Get cdma as level 0..4
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthCdma#getLevel}.
+ *
+ * @see android.telephony#CellSignalStrengthCdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getCdmaLevel() {
return mCdma.getLevel();
}
@@ -453,9 +577,17 @@
/**
* Get the cdma signal level as an asu value between 0..31, 99 is unknown
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthCdma#getAsuLevel}. Since there is no definition of
+ * ASU for CDMA, the resultant value is Android-specific and is not recommended
+ * for use.
+ *
+ * @see android.telephony#CellSignalStrengthCdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getCdmaAsuLevel() {
return mCdma.getAsuLevel();
}
@@ -463,9 +595,15 @@
/**
* Get Evdo as level 0..4
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthCdma#getEvdoLevel}.
+ *
+ * @see android.telephony#CellSignalStrengthCdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getEvdoLevel() {
return mCdma.getEvdoLevel();
}
@@ -473,9 +611,17 @@
/**
* Get the evdo signal level as an asu value between 0..31, 99 is unknown
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthCdma#getEvdoAsuLevel}. Since there is no definition of
+ * ASU for EvDO, the resultant value is Android-specific and is not recommended
+ * for use.
+ *
+ * @see android.telephony#CellSignalStrengthCdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getEvdoAsuLevel() {
return mCdma.getEvdoAsuLevel();
}
@@ -483,9 +629,15 @@
/**
* Get LTE as dBm
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthLte#getDbm}.
+ *
+ * @see android.telephony#CellSignalStrengthLte
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getLteDbm() {
return mLte.getRsrp();
}
@@ -493,9 +645,15 @@
/**
* Get LTE as level 0..4
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthLte#getLevel}.
+ *
+ * @see android.telephony#CellSignalStrengthLte
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getLteLevel() {
return mLte.getLevel();
}
@@ -504,26 +662,46 @@
* Get the LTE signal level as an asu value between 0..97, 99 is unknown
* Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthLte#getAsuLevel}.
+ *
+ * @see android.telephony#CellSignalStrengthLte
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getLteAsuLevel() {
return mLte.getAsuLevel();
}
/**
* @return true if this is for GSM
+ *
+ * @deprecated This method returns true if there are any 3gpp type SignalStrength elements in
+ * this SignalStrength report or if the report contains no valid SignalStrength
+ * information. Instead callers should use
+ * {@link android.telephony.SignalStrength#getCellSignalStrengths
+ * getCellSignalStrengths()} to determine which types of information are contained
+ * in the SignalStrength report.
*/
+ @Deprecated
public boolean isGsm() {
return !(getPrimary() instanceof CellSignalStrengthCdma);
}
/**
- * @return get TD_SCDMA dbm
+ * @return get TD-SCDMA dBm
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthTdscdma#getDbm}.
+ *
+ * @see android.telephony#CellSignalStrengthTdscdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getTdScdmaDbm() {
return mTdscdma.getRscp();
}
@@ -534,9 +712,15 @@
* INT_MAX: 0x7FFFFFFF denotes invalid value
* Reference: 3GPP TS 25.123, section 9.1.1.1
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthTdscdma#getLevel}.
+ *
+ * @see android.telephony#CellSignalStrengthTdscdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getTdScdmaLevel() {
return mTdscdma.getLevel();
}
@@ -544,18 +728,30 @@
/**
* Get the TD-SCDMA signal level as an asu value.
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthTdscdma#getAsuLevel}.
+ *
+ * @see android.telephony#CellSignalStrengthTdscdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public int getTdScdmaAsuLevel() {
return mTdscdma.getAsuLevel();
}
/**
- * Gets WCDMA RSCP as a dbm value between -120 and -24, as defined in TS 27.007 8.69.
+ * Gets WCDMA RSCP as a dBm value between -120 and -24, as defined in TS 27.007 8.69.
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthWcdma#getRscp}.
+ *
+ * @see android.telephony#CellSignalStrengthWcdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
+ @Deprecated
public int getWcdmaRscp() {
return mWcdma.getRscp();
}
@@ -563,8 +759,14 @@
/**
* Get the WCDMA signal level as an ASU value between 0-96, 255 is unknown
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthWcdma#getAsuLevel}.
+ *
+ * @see android.telephony#CellSignalStrengthWcdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
+ @Deprecated
public int getWcdmaAsuLevel() {
/*
* 3GPP 27.007 (Ver 10.3.0) Sec 8.69
@@ -578,10 +780,16 @@
}
/**
- * Gets WCDMA signal strength as a dbm value between -120 and -24, as defined in TS 27.007 8.69.
+ * Gets WCDMA signal strength as a dBm value between -120 and -24, as defined in TS 27.007 8.69.
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthWcdma#getDbm}.
+ *
+ * @see android.telephony#CellSignalStrengthWcdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
+ @Deprecated
public int getWcdmaDbm() {
return mWcdma.getDbm();
}
@@ -589,13 +797,19 @@
/**
* Get WCDMA as level 0..4
*
+ * @deprecated this information should be retrieved from
+ * {@link CellSignalStrengthWcdma#getDbm}.
+ *
+ * @see android.telephony#CellSignalStrengthWcdma
+ * @see android.telephony.SignalStrength#getCellSignalStrengths()
* @hide
*/
+ @Deprecated
public int getWcdmaLevel() {
return mWcdma.getLevel();
}
- /**
+ /**
* @return hash code
*/
@Override
@@ -639,9 +853,13 @@
* Set SignalStrength based on intent notifier map
*
* @param m intent notifier map
+ *
+ * @deprecated this method relies on non-stable implementation details, and full access to
+ * internal storage is available via {@link getCellSignalStrengths()}.
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
private void setFromNotifierBundle(Bundle m) {
mCdma = m.getParcelable("Cdma");
mGsm = m.getParcelable("Gsm");
@@ -654,9 +872,13 @@
* Set intent notifier Bundle based on SignalStrength
*
* @param m intent notifier Bundle
+ *
+ * @deprecated this method relies on non-stable implementation details, and full access to
+ * internal storage is available via {@link getCellSignalStrengths()}.
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public void fillInNotifierBundle(Bundle m) {
m.putParcelable("Cdma", mCdma);
m.putParcelable("Gsm", mGsm);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 8053353..e710e0e 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -93,6 +93,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.regex.Matcher;
@@ -8585,12 +8586,25 @@
}
- /** @hide */
- public String getLocaleFromDefaultSim() {
+ /**
+ * Returns a well-formed IETF BCP 47 language tag representing the locale from the SIM, e.g,
+ * en-US. Returns {@code null} if no locale could be derived from subscriptions.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+ *
+ * @see Locale#toLanguageTag()
+ * @see Locale#forLanguageTag(String)
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @Nullable public String getSimLocale() {
try {
final ITelephony telephony = getITelephony();
if (telephony != null) {
- return telephony.getLocaleFromDefaultSim();
+ return telephony.getSimLocaleForSubscriber(getSubId());
}
} catch (RemoteException ex) {
}
@@ -8598,6 +8612,22 @@
}
/**
+ * TODO delete after SuW migrates to new API.
+ * @hide
+ */
+ public String getLocaleFromDefaultSim() {
+ try {
+ final ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.getSimLocaleForSubscriber(getSubId());
+ }
+ } catch (RemoteException ex) {
+ }
+ return null;
+ }
+
+
+ /**
* Requests the modem activity info. The recipient will place the result
* in `result`.
* @param result The object on which the recipient will send the resulting
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 8d148c3..0e69530 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -140,15 +140,19 @@
@Retention(RetentionPolicy.SOURCE)
public @interface AuthType {}
- // Possible values for protocol.
- /** Protocol type for IP. */
+ // Possible values for protocol which is defined in TS 27.007 section 10.1.1.
+ /** Internet protocol. */
public static final int PROTOCOL_IP = 0;
- /** Protocol type for IPV6. */
+ /** Internet protocol, version 6. */
public static final int PROTOCOL_IPV6 = 1;
- /** Protocol type for IPV4V6. */
+ /** Virtual PDP type introduced to handle dual IP stack UE capability. */
public static final int PROTOCOL_IPV4V6 = 2;
- /** Protocol type for PPP. */
+ /** Point to point protocol. */
public static final int PROTOCOL_PPP = 3;
+ /** Transfer of Non-IP data to external packet data network. */
+ public static final int PROTOCOL_NON_IP = 4;
+ /** Transfer of Unstructured data to the Data Network via N6. */
+ public static final int PROTOCOL_UNSTRUCTURED = 5;
/** @hide */
@IntDef(prefix = { "PROTOCOL_" }, value = {
@@ -156,6 +160,8 @@
PROTOCOL_IPV6,
PROTOCOL_IPV4V6,
PROTOCOL_PPP,
+ PROTOCOL_NON_IP,
+ PROTOCOL_UNSTRUCTURED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ProtocolType {}
@@ -217,11 +223,15 @@
PROTOCOL_STRING_MAP.put("IPV6", PROTOCOL_IPV6);
PROTOCOL_STRING_MAP.put("IPV4V6", PROTOCOL_IPV4V6);
PROTOCOL_STRING_MAP.put("PPP", PROTOCOL_PPP);
+ PROTOCOL_STRING_MAP.put("NON-IP", PROTOCOL_NON_IP);
+ PROTOCOL_STRING_MAP.put("UNSTRUCTURED", PROTOCOL_UNSTRUCTURED);
PROTOCOL_INT_MAP = new ArrayMap<Integer, String>();
PROTOCOL_INT_MAP.put(PROTOCOL_IP, "IP");
PROTOCOL_INT_MAP.put(PROTOCOL_IPV6, "IPV6");
PROTOCOL_INT_MAP.put(PROTOCOL_IPV4V6, "IPV4V6");
PROTOCOL_INT_MAP.put(PROTOCOL_PPP, "PPP");
+ PROTOCOL_INT_MAP.put(PROTOCOL_NON_IP, "NON-IP");
+ PROTOCOL_INT_MAP.put(PROTOCOL_UNSTRUCTURED, "UNSTRUCTURED");
MVNO_TYPE_STRING_MAP = new ArrayMap<String, Integer>();
MVNO_TYPE_STRING_MAP.put("spn", MVNO_TYPE_SPN);
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index 25f5133..294c79b 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -52,8 +52,7 @@
* @param status Data call fail cause. 0 indicates no error.
* @param suggestedRetryTime The suggested data retry time in milliseconds.
* @param cid The unique id of the data connection.
- * @param active Data connection active status. 0 = inactive, 1 = active/physical link down,
- * 2 = active/physical link up.
+ * @param active Data connection active status. 0 = inactive, 1 = dormant, 2 = active.
* @param type The connection protocol, should be one of the PDP_type values in TS 27.007
* section 10.1.1. For example, "IP", "IPV6", "IPV4V6", or "PPP".
* @param ifname The network interface name.
@@ -124,7 +123,7 @@
public int getCallId() { return mCid; }
/**
- * @return 0 = inactive, 1 = active/physical link down, 2 = active/physical link up.
+ * @return 0 = inactive, 1 = dormant, 2 = active.
*/
public int getActive() { return mActive; }
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 0e5c71d..0fa1b41 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -129,6 +129,66 @@
"android.telephony.euicc.action.RESOLVE_ERROR";
/**
+ * Intent action sent by system apps (such as the Settings app) to the Telephony framework to
+ * enable or disable a subscription. Must be accompanied with {@link #EXTRA_SUBSCRIPTION_ID} and
+ * {@link #EXTRA_ENABLE_SUBSCRIPTION}.
+ *
+ * <p>Unlike {@link #switchToSubscription(int, PendingIntent)}, using this action allows the
+ * underlying eUICC service (i.e. the LPA app) to control the UI experience during this
+ * operation. The action is received by the Telephony framework, which in turn selects and
+ * launches an appropriate LPA activity to present UI to the user. For example, the activity may
+ * show a confirmation dialog, a progress dialog, or an error dialog when necessary.
+ *
+ * <p>The launched activity will immediately finish with
+ * {@link android.app.Activity#RESULT_CANCELED} if {@link #isEnabled} is false.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED =
+ "android.telephony.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED";
+
+ /**
+ * Intent action sent by system apps (such as the Settings app) to the Telephony framework to
+ * delete a subscription. Must be accompanied with {@link #EXTRA_SUBSCRIPTION_ID}.
+ *
+ * <p>Unlike {@link #deleteSubscription(int, PendingIntent)}, using this action allows the
+ * underlying eUICC service (i.e. the LPA app) to control the UI experience during this
+ * operation. The action is received by the Telephony framework, which in turn selects and
+ * launches an appropriate LPA activity to present UI to the user. For example, the activity may
+ * show a confirmation dialog, a progress dialog, or an error dialog when necessary.
+ *
+ * <p>The launched activity will immediately finish with
+ * {@link android.app.Activity#RESULT_CANCELED} if {@link #isEnabled} is false.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED =
+ "android.telephony.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
+
+ /**
+ * Intent action sent by system apps (such as the Settings app) to the Telephony framework to
+ * rename a subscription. Must be accompanied with {@link #EXTRA_SUBSCRIPTION_ID} and
+ * {@link #EXTRA_SUBSCRIPTION_NICKNAME}.
+ *
+ * <p>Unlike {@link #updateSubscriptionNickname(int, String, PendingIntent)}, using this action
+ * allows the the underlying eUICC service (i.e. the LPA app) to control the UI experience
+ * during this operation. The action is received by the Telephony framework, which in turn
+ * selects and launches an appropriate LPA activity to present UI to the user. For example, the
+ * activity may show a confirmation dialog, a progress dialog, or an error dialog when
+ * necessary.
+ *
+ * <p>The launched activity will immediately finish with
+ * {@link android.app.Activity#RESULT_CANCELED} if {@link #isEnabled} is false.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED =
+ "android.telephony.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED";
+
+ /**
* Result code for an operation indicating that the operation succeeded.
*/
public static final int EMBEDDED_SUBSCRIPTION_RESULT_OK = 0;
@@ -219,6 +279,37 @@
"android.telephony.euicc.extra.FORCE_PROVISION";
/**
+ * Key for an extra set on privileged actions {@link #ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED},
+ * {@link #ACTION_DELETE_SUBSCRIPTION_PRIVILEGED}, and
+ * {@link #ACTION_RENAME_SUBSCRIPTION_PRIVILEGED} providing the ID of the targeted subscription.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_SUBSCRIPTION_ID =
+ "android.telephony.euicc.extra.SUBSCRIPTION_ID";
+
+ /**
+ * Key for an extra set on {@link #ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED} providing a boolean
+ * value of whether to enable or disable the targeted subscription.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_ENABLE_SUBSCRIPTION =
+ "android.telephony.euicc.extra.ENABLE_SUBSCRIPTION";
+
+ /**
+ * Key for an extra set on {@link #ACTION_RENAME_SUBSCRIPTION_PRIVILEGED} providing a new
+ * nickname for the targeted subscription.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_SUBSCRIPTION_NICKNAME =
+ "android.telephony.euicc.extra.SUBSCRIPTION_NICKNAME";
+
+ /**
* Optional meta-data attribute for a carrier app providing an icon to use to represent the
* carrier. If not provided, the app's launcher icon will be used as a fallback.
*/
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 5736a46..9cc173c 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1178,12 +1178,12 @@
void factoryReset(int subId);
/**
- * An estimate of the users's current locale based on the default SIM.
+ * Returns users's current locale based on the SIM.
*
* The returned string will be a well formed BCP-47 language tag, or {@code null}
* if no locale could be derived.
*/
- String getLocaleFromDefaultSim();
+ String getSimLocaleForSubscriber(int subId);
/**
* Requests the modem activity info asynchronously.
diff --git a/test-mock/api/current.txt b/test-mock/api/current.txt
index a181bc38..1110790 100644
--- a/test-mock/api/current.txt
+++ b/test-mock/api/current.txt
@@ -32,7 +32,6 @@
public class MockContext extends android.content.Context {
ctor public MockContext();
- method public boolean bindIsolatedService(android.content.Intent, android.content.ServiceConnection, int, String);
method public boolean bindService(android.content.Intent, android.content.ServiceConnection, int);
method public int checkCallingOrSelfPermission(String);
method public int checkCallingOrSelfUriPermission(android.net.Uri, int);
@@ -81,7 +80,6 @@
method public java.io.File getNoBackupFilesDir();
method public java.io.File getObbDir();
method public java.io.File[] getObbDirs();
- method public String getOpPackageName();
method public String getPackageCodePath();
method public android.content.pm.PackageManager getPackageManager();
method public String getPackageName();
@@ -137,7 +135,6 @@
method public boolean stopService(android.content.Intent);
method public void unbindService(android.content.ServiceConnection);
method public void unregisterReceiver(android.content.BroadcastReceiver);
- method public void updateServiceGroup(android.content.ServiceConnection, int, int);
}
@Deprecated public class MockCursor implements android.database.Cursor {
diff --git a/tests/DexLoggerIntegrationTests/Android.mk b/tests/DexLoggerIntegrationTests/Android.mk
index ee2ec0a..979d13a 100644
--- a/tests/DexLoggerIntegrationTests/Android.mk
+++ b/tests/DexLoggerIntegrationTests/Android.mk
@@ -29,6 +29,35 @@
dexloggertest_jar := $(LOCAL_BUILT_MODULE)
+# Also build a native library that the test app can dynamically load
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE := DexLoggerNativeTestLibrary
+LOCAL_MULTILIB := first
+LOCAL_SRC_FILES := src/cpp/com_android_dcl_Jni.cpp
+LOCAL_C_INCLUDES += \
+ $(JNI_H_INCLUDE)
+LOCAL_SDK_VERSION := 28
+LOCAL_NDK_STL_VARIANT := c++_static
+
+include $(BUILD_SHARED_LIBRARY)
+
+dexloggertest_so := $(LOCAL_BUILT_MODULE)
+
+# And a standalone native executable that we can exec.
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE := DexLoggerNativeExecutable
+LOCAL_SRC_FILES := src/cpp/test_executable.cpp
+
+include $(BUILD_EXECUTABLE)
+
+dexloggertest_executable := $(LOCAL_BUILT_MODULE)
+
# Build the test app itself
include $(CLEAR_VARS)
@@ -37,14 +66,18 @@
LOCAL_PACKAGE_NAME := DexLoggerIntegrationTests
LOCAL_SDK_VERSION := current
LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
+LOCAL_CERTIFICATE := shared
LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/server/pm)
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
truth-prebuilt \
-# This gets us the javalib.jar built by DexLoggerTestLibrary above.
-LOCAL_JAVA_RESOURCE_FILES := $(dexloggertest_jar)
+# This gets us the javalib.jar built by DexLoggerTestLibrary above as well as the various
+# native binaries.
+LOCAL_JAVA_RESOURCE_FILES := \
+ $(dexloggertest_jar) \
+ $(dexloggertest_so) \
+ $(dexloggertest_executable)
include $(BUILD_PACKAGE)
diff --git a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
index 75ee089..d68769b 100644
--- a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
+++ b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
@@ -17,6 +17,7 @@
package com.android.server.pm.dex;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import android.app.UiAutomation;
import android.content.Context;
@@ -25,6 +26,7 @@
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.LargeTest;
import android.util.EventLog;
+import android.util.EventLog.Event;
import dalvik.system.DexClassLoader;
@@ -65,14 +67,13 @@
// Event log tag used for SNET related events
private static final int SNET_TAG = 0x534e4554;
- // Subtag used to distinguish dynamic code loading events
- private static final String DCL_SUBTAG = "dcl";
+ // Subtags used to distinguish dynamic code loading events
+ private static final String DCL_DEX_SUBTAG = "dcl";
+ private static final String DCL_NATIVE_SUBTAG = "dcln";
- // All the tags we care about
- private static final int[] TAG_LIST = new int[] { SNET_TAG };
-
- // This is {@code DynamicCodeLoggingService#JOB_ID}
- private static final int DYNAMIC_CODE_LOGGING_JOB_ID = 2030028;
+ // These are job IDs from DynamicCodeLoggingService
+ private static final int IDLE_LOGGING_JOB_ID = 2030028;
+ private static final int AUDIT_WATCHING_JOB_ID = 203142925;
private static Context sContext;
private static int sMyUid;
@@ -89,15 +90,20 @@
// Without this the first test passes and others don't - we don't see new events in the
// log. The exact reason is unclear.
EventLog.writeEvent(SNET_TAG, "Dummy event");
+
+ // Audit log messages are throttled by the kernel (at the request of logd) to 5 per
+ // second, so running the tests too quickly in sequence means we lose some and get
+ // spurious failures. Sigh.
+ SystemClock.sleep(1000);
}
@Test
- public void testDexLoggerGeneratesEvents() throws Exception {
- File privateCopyFile = fileForJar("copied.jar");
+ public void testDexLoggerGeneratesEvents_standardClassLoader() throws Exception {
+ File privateCopyFile = privateFile("copied.jar");
// Obtained via "echo -n copied.jar | sha256sum"
String expectedNameHash =
"1B6C71DB26F36582867432CCA12FB6A517470C9F9AABE9198DD4C5C030D6DC0C";
- String expectedContentHash = copyAndHashJar(privateCopyFile);
+ String expectedContentHash = copyAndHashResource("/javalib.jar", privateCopyFile);
// Feed the jar to a class loader and make sure it contains what we expect.
ClassLoader parentClassLoader = sContext.getClass().getClassLoader();
@@ -107,18 +113,18 @@
// And make sure we log events about it
long previousEventNanos = mostRecentEventTimeNanos();
- runDexLogger();
+ runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
- assertDclLoggedSince(previousEventNanos, expectedNameHash, expectedContentHash);
+ assertDclLoggedSince(previousEventNanos, DCL_DEX_SUBTAG,
+ expectedNameHash, expectedContentHash);
}
@Test
-
public void testDexLoggerGeneratesEvents_unknownClassLoader() throws Exception {
- File privateCopyFile = fileForJar("copied2.jar");
+ File privateCopyFile = privateFile("copied2.jar");
String expectedNameHash =
"202158B6A3169D78F1722487205A6B036B3F2F5653FDCFB4E74710611AC7EB93";
- String expectedContentHash = copyAndHashJar(privateCopyFile);
+ String expectedContentHash = copyAndHashResource("/javalib.jar", privateCopyFile);
// This time make sure an unknown class loader is an ancestor of the class loader we use.
ClassLoader knownClassLoader = sContext.getClass().getClassLoader();
@@ -129,22 +135,185 @@
// And make sure we log events about it
long previousEventNanos = mostRecentEventTimeNanos();
- runDexLogger();
+ runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
- assertDclLoggedSince(previousEventNanos, expectedNameHash, expectedContentHash);
+ assertDclLoggedSince(previousEventNanos, DCL_DEX_SUBTAG,
+ expectedNameHash, expectedContentHash);
}
- private static File fileForJar(String name) {
- return new File(sContext.getDir("jars", Context.MODE_PRIVATE), name);
+ @Test
+ public void testDexLoggerGeneratesEvents_nativeLibrary() throws Exception {
+ File privateCopyFile = privateFile("copied.so");
+ String expectedNameHash =
+ "996223BAD4B4FE75C57A3DEC61DB9C0B38E0A7AD479FC95F33494F4BC55A0F0E";
+ String expectedContentHash =
+ copyAndHashResource("/DexLoggerNativeTestLibrary.so", privateCopyFile);
+
+ System.load(privateCopyFile.toString());
+
+ // Run the job to scan generated audit log entries
+ runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
+
+ // And then make sure we log events about it
+ long previousEventNanos = mostRecentEventTimeNanos();
+ runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
+
+ assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG,
+ expectedNameHash, expectedContentHash);
}
- private static String copyAndHashJar(File copyTo) throws Exception {
+ @Test
+ public void testDexLoggerGeneratesEvents_nativeLibrary_escapedName() throws Exception {
+ // A file name with a space will be escaped in the audit log; verify we un-escape it
+ // correctly.
+ File privateCopyFile = privateFile("second copy.so");
+ String expectedNameHash =
+ "8C39990C560B4F36F83E208E279F678746FE23A790E4C50F92686584EA2041CA";
+ String expectedContentHash =
+ copyAndHashResource("/DexLoggerNativeTestLibrary.so", privateCopyFile);
+
+ System.load(privateCopyFile.toString());
+
+ // Run the job to scan generated audit log entries
+ runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
+
+ // And then make sure we log events about it
+ long previousEventNanos = mostRecentEventTimeNanos();
+ runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
+
+ assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG,
+ expectedNameHash, expectedContentHash);
+ }
+
+ @Test
+ public void testDexLoggerGeneratesEvents_nativeExecutable() throws Exception {
+ File privateCopyFile = privateFile("test_executable");
+ String expectedNameHash =
+ "3FBEC3F925A132D18F347F11AE9A5BB8DE1238828F8B4E064AA86EB68BD46DCF";
+ String expectedContentHash =
+ copyAndHashResource("/DexLoggerNativeExecutable", privateCopyFile);
+ assertThat(privateCopyFile.setExecutable(true)).isTrue();
+
+ Process process = Runtime.getRuntime().exec(privateCopyFile.toString());
+ int exitCode = process.waitFor();
+ assertThat(exitCode).isEqualTo(0);
+
+ // Run the job to scan generated audit log entries
+ runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
+
+ // And then make sure we log events about it
+ long previousEventNanos = mostRecentEventTimeNanos();
+ runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
+
+ assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG,
+ expectedNameHash, expectedContentHash);
+ }
+
+ @Test
+ public void testDexLoggerGeneratesEvents_spoofed_validFile() throws Exception {
+ File privateCopyFile = privateFile("spoofed");
+
+ String expectedContentHash =
+ copyAndHashResource("/DexLoggerNativeExecutable", privateCopyFile);
+
+ EventLog.writeEvent(EventLog.getTagCode("auditd"),
+ "type=1400 avc: granted { execute_no_trans } "
+ + "path=\"" + privateCopyFile + "\" "
+ + "scontext=u:r:untrusted_app_27: "
+ + "tcontext=u:object_r:app_data_file: "
+ + "tclass=file ");
+
+ String expectedNameHash =
+ "1CF36F503A02877BB775DC23C1C5A47A95F2684B6A1A83B11795B856D88861E3";
+
+ // Run the job to scan generated audit log entries
+ runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
+
+ // And then make sure we log events about it
+ long previousEventNanos = mostRecentEventTimeNanos();
+ runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
+
+ assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG,
+ expectedNameHash, expectedContentHash);
+ }
+
+ @Test
+ public void testDexLoggerGeneratesEvents_spoofed_pathTraversal() throws Exception {
+ File privateDir = privateFile("x").getParentFile();
+
+ // Transform /a/b/c -> /a/b/c/../../.. so we get back to the root
+ File pathTraversalToRoot = privateDir;
+ File root = new File("/");
+ while (!privateDir.equals(root)) {
+ pathTraversalToRoot = new File(pathTraversalToRoot, "..");
+ privateDir = privateDir.getParentFile();
+ }
+
+ File spoofedFile = new File(pathTraversalToRoot, "dev/urandom");
+
+ assertWithMessage("Expected " + spoofedFile + " to be readable")
+ .that(spoofedFile.canRead()).isTrue();
+
+ EventLog.writeEvent(EventLog.getTagCode("auditd"),
+ "type=1400 avc: granted { execute_no_trans } "
+ + "path=\"" + spoofedFile + "\" "
+ + "scontext=u:r:untrusted_app_27: "
+ + "tcontext=u:object_r:app_data_file: "
+ + "tclass=file ");
+
+ String expectedNameHash =
+ "65528FE876BD676B0DFCC9A8ACA8988E026766F99EEC1E1FB48F46B2F635E225";
+
+ // Run the job to scan generated audit log entries
+ runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
+
+ // And then trigger generating DCL events
+ long previousEventNanos = mostRecentEventTimeNanos();
+ runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
+
+ assertNoDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, expectedNameHash);
+ }
+
+ @Test
+ public void testDexLoggerGeneratesEvents_spoofed_otherAppFile() throws Exception {
+ File ourPath = sContext.getDatabasePath("android_pay");
+ File targetPath = new File(ourPath.toString()
+ .replace("com.android.frameworks.dexloggertest", "com.google.android.gms"));
+
+ assertWithMessage("Expected " + targetPath + " to not be readable")
+ .that(targetPath.canRead()).isFalse();
+
+ EventLog.writeEvent(EventLog.getTagCode("auditd"),
+ "type=1400 avc: granted { execute_no_trans } "
+ + "path=\"" + targetPath + "\" "
+ + "scontext=u:r:untrusted_app_27: "
+ + "tcontext=u:object_r:app_data_file: "
+ + "tclass=file ");
+
+ String expectedNameHash =
+ "CBE04E8AB9E7199FC19CBAAF9C774B88E56B3B19E823F2251693380AD6F515E6";
+
+ // Run the job to scan generated audit log entries
+ runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
+
+ // And then trigger generating DCL events
+ long previousEventNanos = mostRecentEventTimeNanos();
+ runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
+
+ assertNoDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, expectedNameHash);
+ }
+
+ private static File privateFile(String name) {
+ return new File(sContext.getDir("dcl", Context.MODE_PRIVATE), name);
+ }
+
+ private static String copyAndHashResource(String resourcePath, File copyTo) throws Exception {
MessageDigest hasher = MessageDigest.getInstance("SHA-256");
// Copy the jar from our Java resources to a private data directory
Class<?> thisClass = DexLoggerIntegrationTests.class;
- try (InputStream input = thisClass.getResourceAsStream("/javalib.jar");
- OutputStream output = new FileOutputStream(copyTo)) {
+ try (InputStream input = thisClass.getResourceAsStream(resourcePath);
+ OutputStream output = new FileOutputStream(copyTo)) {
byte[] buffer = new byte[1024];
while (true) {
int numRead = input.read(buffer);
@@ -166,24 +335,18 @@
return formatter.toString();
}
- private static long mostRecentEventTimeNanos() throws Exception {
- List<EventLog.Event> events = new ArrayList<>();
-
- EventLog.readEvents(TAG_LIST, events);
- return events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos();
- }
-
- private static void runDexLogger() throws Exception {
- // This forces {@code DynamicCodeLoggingService} to start now.
- runCommand("cmd jobscheduler run -f android " + DYNAMIC_CODE_LOGGING_JOB_ID);
+ private static void runDynamicCodeLoggingJob(int jobId) throws Exception {
+ // This forces the DynamicCodeLoggingService job to start now.
+ runCommand("cmd jobscheduler run -f android " + jobId);
// Wait for the job to have run.
long startTime = SystemClock.elapsedRealtime();
while (true) {
String response = runCommand(
- "cmd jobscheduler get-job-state android " + DYNAMIC_CODE_LOGGING_JOB_ID);
+ "cmd jobscheduler get-job-state android " + jobId);
if (!response.contains("pending") && !response.contains("active")) {
break;
}
+ // Don't wait forever - if it's taken > 10s then something is very wrong.
if (SystemClock.elapsedRealtime() - startTime > TimeUnit.SECONDS.toMillis(10)) {
throw new AssertionError("Job has not completed: " + response);
}
@@ -208,37 +371,68 @@
return response.toString("UTF-8");
}
- private static void assertDclLoggedSince(long previousEventNanos, String expectedNameHash,
- String expectedContentHash) throws Exception {
- List<EventLog.Event> events = new ArrayList<>();
- EventLog.readEvents(TAG_LIST, events);
- int found = 0;
- for (EventLog.Event event : events) {
+ private static long mostRecentEventTimeNanos() throws Exception {
+ List<Event> events = readSnetEvents();
+ return events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos();
+ }
+
+ private static void assertDclLoggedSince(long previousEventNanos, String expectedSubTag,
+ String expectedNameHash, String expectedContentHash) throws Exception {
+ List<String> messages =
+ findMatchingEvents(previousEventNanos, expectedSubTag, expectedNameHash);
+
+ assertWithMessage("Expected exactly one matching log entry").that(messages).hasSize(1);
+ assertThat(messages.get(0)).endsWith(expectedContentHash);
+ }
+
+ private static void assertNoDclLoggedSince(long previousEventNanos, String expectedSubTag,
+ String expectedNameHash) throws Exception {
+ List<String> messages =
+ findMatchingEvents(previousEventNanos, expectedSubTag, expectedNameHash);
+
+ assertWithMessage("Expected no matching log entries").that(messages).isEmpty();
+ }
+
+ private static List<String> findMatchingEvents(long previousEventNanos, String expectedSubTag,
+ String expectedNameHash) throws Exception {
+ List<String> messages = new ArrayList<>();
+
+ for (Event event : readSnetEvents()) {
if (event.getTimeNanos() <= previousEventNanos) {
continue;
}
- Object[] data = (Object[]) event.getData();
- // We only care about DCL events that we generated.
- String subTag = (String) data[0];
- if (!DCL_SUBTAG.equals(subTag)) {
+ Object data = event.getData();
+ if (!(data instanceof Object[])) {
continue;
}
- int uid = (int) data[1];
+ Object[] fields = (Object[]) data;
+
+ // We only care about DCL events that we generated.
+ String subTag = (String) fields[0];
+ if (!expectedSubTag.equals(subTag)) {
+ continue;
+ }
+ int uid = (int) fields[1];
if (uid != sMyUid) {
continue;
}
- String message = (String) data[2];
+ String message = (String) fields[2];
if (!message.startsWith(expectedNameHash)) {
continue;
}
- assertThat(message).endsWith(expectedContentHash);
- ++found;
+ messages.add(message);
+ //assertThat(message).endsWith(expectedContentHash);
}
+ return messages;
+ }
- assertThat(found).isEqualTo(1);
+ private static List<Event> readSnetEvents() throws Exception {
+ List<Event> events = new ArrayList<>();
+ EventLog.readEvents(new int[] { SNET_TAG }, events);
+ return events;
}
/**
diff --git a/tests/DexLoggerIntegrationTests/src/cpp/com_android_dcl_Jni.cpp b/tests/DexLoggerIntegrationTests/src/cpp/com_android_dcl_Jni.cpp
new file mode 100644
index 0000000..0608883
--- /dev/null
+++ b/tests/DexLoggerIntegrationTests/src/cpp/com_android_dcl_Jni.cpp
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+#include "jni.h"
+
+extern "C" jint JNI_OnLoad(JavaVM* /* vm */, void* /* reserved */)
+{
+ return JNI_VERSION_1_6;
+}
diff --git a/tests/DexLoggerIntegrationTests/src/cpp/test_executable.cpp b/tests/DexLoggerIntegrationTests/src/cpp/test_executable.cpp
new file mode 100644
index 0000000..ad025e6
--- /dev/null
+++ b/tests/DexLoggerIntegrationTests/src/cpp/test_executable.cpp
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+int main() {
+ // This program just has to run, it doesn't need to do anything. So we don't.
+ return 0;
+}
diff --git a/tests/HwAccelerationTest/Android.mk b/tests/HwAccelerationTest/Android.mk
index 11ea954..79072fa 100644
--- a/tests/HwAccelerationTest/Android.mk
+++ b/tests/HwAccelerationTest/Android.mk
@@ -21,6 +21,7 @@
LOCAL_PACKAGE_NAME := HwAccelerationTest
LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_CERTIFICATE := platform
LOCAL_MODULE_TAGS := tests
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index c8f96c9..f330b83 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -310,6 +310,15 @@
<category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
+
+ <activity
+ android:name="PictureCaptureDemo"
+ android:label="Debug/Picture Capture">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="com.android.test.hwui.TEST" />
+ </intent-filter>
+ </activity>
<activity
android:name="SmallCircleActivity"
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/PictureCaptureDemo.java b/tests/HwAccelerationTest/src/com/android/test/hwui/PictureCaptureDemo.java
new file mode 100644
index 0000000..029e302
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/PictureCaptureDemo.java
@@ -0,0 +1,153 @@
+/*
+ * 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 com.android.test.hwui;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Picture;
+import android.os.Bundle;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewDebug;
+import android.webkit.WebChromeClient;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+import android.widget.ProgressBar;
+
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.Random;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+public class PictureCaptureDemo extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final LinearLayout layout = new LinearLayout(this);
+ layout.setOrientation(LinearLayout.VERTICAL);
+
+ final LinearLayout inner = new LinearLayout(this);
+ inner.setOrientation(LinearLayout.HORIZONTAL);
+ ProgressBar spinner = new ProgressBar(this, null, android.R.attr.progressBarStyleLarge);
+ inner.addView(spinner,
+ new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+
+ inner.addView(new View(this), new LayoutParams(50, 1));
+
+ Picture picture = new Picture();
+ Canvas canvas = picture.beginRecording(100, 100);
+ canvas.drawColor(Color.RED);
+ Paint paint = new Paint();
+ paint.setTextSize(32);
+ paint.setColor(Color.BLACK);
+ canvas.drawText("Hello", 0, 50, paint);
+ picture.endRecording();
+
+ ImageView iv1 = new ImageView(this);
+ iv1.setImageBitmap(Bitmap.createBitmap(picture, 100, 100, Bitmap.Config.ARGB_8888));
+ inner.addView(iv1, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+
+ inner.addView(new View(this), new LayoutParams(50, 1));
+
+ ImageView iv2 = new ImageView(this);
+ iv2.setImageBitmap(Bitmap.createBitmap(picture, 100, 100, Bitmap.Config.HARDWARE));
+ inner.addView(iv2, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+
+ layout.addView(inner,
+ new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+ // For testing with a functor in the tree
+ WebView wv = new WebView(this);
+ wv.setWebViewClient(new WebViewClient());
+ wv.setWebChromeClient(new WebChromeClient());
+ wv.loadUrl("https://google.com");
+ layout.addView(wv, new LayoutParams(LayoutParams.MATCH_PARENT, 400));
+
+ SurfaceView mySurfaceView = new SurfaceView(this);
+ layout.addView(mySurfaceView,
+ new LayoutParams(LayoutParams.MATCH_PARENT, 600));
+
+ setContentView(layout);
+
+ mySurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
+ private AutoCloseable mStopCapture;
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ final Random rand = new Random();
+ mStopCapture = ViewDebug.startRenderingCommandsCapture(mySurfaceView,
+ mCaptureThread, (picture) -> {
+ if (rand.nextInt(20) == 0) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ }
+ }
+ Canvas canvas = holder.lockCanvas();
+ if (canvas == null) {
+ return false;
+ }
+ canvas.drawPicture(picture);
+ holder.unlockCanvasAndPost(canvas);
+ picture.close();
+ return true;
+ });
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ if (mStopCapture != null) {
+ try {
+ mStopCapture.close();
+ } catch (Exception e) {
+ }
+ mStopCapture = null;
+ }
+ }
+ });
+ }
+
+ ExecutorService mCaptureThread = Executors.newSingleThreadExecutor();
+ ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+
+ Picture deepCopy(Picture src) {
+ try {
+ PipedInputStream inputStream = new PipedInputStream();
+ PipedOutputStream outputStream = new PipedOutputStream(inputStream);
+ Future<Picture> future = mExecutor.submit(() -> Picture.createFromStream(inputStream));
+ src.writeToStream(outputStream);
+ outputStream.close();
+ return future.get();
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+}
diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java
index d3c39f0..030641b 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java
@@ -44,7 +44,6 @@
RollbackBroadcastReceiver() {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
- filter.addDataScheme("package");
InstrumentationRegistry.getContext().registerReceiver(this, filter);
}
diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index c2e735e..9d67cea 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -22,9 +22,9 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
-import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.support.test.InstrumentationRegistry;
@@ -38,7 +38,6 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -99,7 +98,7 @@
// so that's not the case!
for (int i = 0; i < 5; ++i) {
for (RollbackInfo info : rm.getRecentlyExecutedRollbacks()) {
- if (TEST_APP_A.equals(info.targetPackage.packageName)) {
+ if (TEST_APP_A.equals(info.targetPackage.getPackageName())) {
Log.i(TAG, "Sleeping 1 second to wait for uninstall to take effect.");
Thread.sleep(1000);
break;
@@ -117,7 +116,7 @@
// There should be no recently executed rollbacks for this package.
for (RollbackInfo info : rm.getRecentlyExecutedRollbacks()) {
- assertNotEquals(TEST_APP_A, info.targetPackage.packageName);
+ assertNotEquals(TEST_APP_A, info.targetPackage.getPackageName());
}
// Install v1 of the app (without rollbacks enabled).
@@ -136,9 +135,7 @@
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollback);
- assertEquals(TEST_APP_A, rollback.targetPackage.packageName);
- assertEquals(2, rollback.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollback.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
// We should not have received any rollback requests yet.
// TODO: Possibly flaky if, by chance, some other app on device
@@ -154,21 +151,18 @@
// received could lead to test flakiness.
Intent broadcast = broadcastReceiver.poll(5, TimeUnit.SECONDS);
assertNotNull(broadcast);
- assertEquals(TEST_APP_A, broadcast.getData().getSchemeSpecificPart());
assertNull(broadcastReceiver.poll(0, TimeUnit.SECONDS));
// Verify the recent rollback has been recorded.
rollback = null;
for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
- if (TEST_APP_A.equals(r.targetPackage.packageName)) {
+ if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
assertNull(rollback);
rollback = r;
}
}
assertNotNull(rollback);
- assertEquals(TEST_APP_A, rollback.targetPackage.packageName);
- assertEquals(2, rollback.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollback.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
broadcastReceiver.unregister();
context.unregisterReceiver(enableRollbackReceiver);
@@ -209,16 +203,12 @@
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollbackA);
- assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName);
- assertEquals(2, rollbackA.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollbackA.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
assertNotNull(rollbackB);
- assertEquals(TEST_APP_B, rollbackB.targetPackage.packageName);
- assertEquals(2, rollbackB.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollbackB.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
// Reload the persisted data.
rm.reloadPersistedData();
@@ -226,16 +216,12 @@
// The apps should still be available for rollback.
rollbackA = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollbackA);
- assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName);
- assertEquals(2, rollbackA.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollbackA.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
rollbackB = rm.getAvailableRollback(TEST_APP_B);
assertNotNull(rollbackB);
- assertEquals(TEST_APP_B, rollbackB.targetPackage.packageName);
- assertEquals(2, rollbackB.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollbackB.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
// Rollback of B should not rollback A
RollbackTestUtils.rollback(rollbackB);
@@ -279,16 +265,12 @@
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollbackA);
- assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName);
- assertEquals(2, rollbackA.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollbackA.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
assertNotNull(rollbackB);
- assertEquals(TEST_APP_B, rollbackB.targetPackage.packageName);
- assertEquals(2, rollbackB.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollbackB.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
// Reload the persisted data.
rm.reloadPersistedData();
@@ -296,16 +278,12 @@
// The apps should still be available for rollback.
rollbackA = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollbackA);
- assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName);
- assertEquals(2, rollbackA.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollbackA.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
rollbackB = rm.getAvailableRollback(TEST_APP_B);
assertNotNull(rollbackB);
- assertEquals(TEST_APP_B, rollbackB.targetPackage.packageName);
- assertEquals(2, rollbackB.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollbackB.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
// Rollback of B should rollback A as well
RollbackTestUtils.rollback(rollbackB);
@@ -349,15 +327,13 @@
// Verify the recent rollback has been recorded.
rollback = null;
for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
- if (TEST_APP_A.equals(r.targetPackage.packageName)) {
+ if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
assertNull(rollback);
rollback = r;
}
}
assertNotNull(rollback);
- assertEquals(TEST_APP_A, rollback.targetPackage.packageName);
- assertEquals(2, rollback.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollback.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
// Reload the persisted data.
rm.reloadPersistedData();
@@ -365,15 +341,13 @@
// Verify the recent rollback is still recorded.
rollback = null;
for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
- if (TEST_APP_A.equals(r.targetPackage.packageName)) {
+ if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
assertNull(rollback);
rollback = r;
}
}
assertNotNull(rollback);
- assertEquals(TEST_APP_A, rollback.targetPackage.packageName);
- assertEquals(2, rollback.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollback.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
} finally {
RollbackTestUtils.dropShellPermissionIdentity();
}
@@ -405,9 +379,7 @@
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollback);
- assertEquals(TEST_APP_A, rollback.targetPackage.packageName);
- assertEquals(2, rollback.targetPackage.higherVersion.versionCode);
- assertEquals(1, rollback.targetPackage.lowerVersion.versionCode);
+ assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
// Expire the rollback.
rm.expireRollbackForPackage(TEST_APP_A);
@@ -470,7 +442,7 @@
* Test that app user data is rolled back.
* TODO: Stop ignoring this test once user data rollback is supported.
*/
- @Ignore @Test
+ @Test
public void testUserDataRollback() throws Exception {
try {
RollbackTestUtils.adoptShellPermissionIdentity(
@@ -479,9 +451,9 @@
Manifest.permission.MANAGE_ROLLBACKS);
RollbackTestUtils.uninstall(TEST_APP_A);
- RollbackTestUtils.install("RollbackTestAppV1.apk", false);
+ RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
processUserData(TEST_APP_A);
- RollbackTestUtils.install("RollbackTestAppV2.apk", true);
+ RollbackTestUtils.install("RollbackTestAppAv2.apk", true);
processUserData(TEST_APP_A);
RollbackManager rm = RollbackTestUtils.getRollbackManager();
@@ -500,8 +472,7 @@
@Test
public void testRollbackBroadcastRestrictions() throws Exception {
RollbackBroadcastReceiver broadcastReceiver = new RollbackBroadcastReceiver();
- Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED,
- Uri.fromParts("package", "com.android.tests.rollback.bogus", null));
+ Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
try {
InstrumentationRegistry.getContext().sendBroadcast(broadcast);
fail("Succeeded in sending restricted broadcast from app context.");
@@ -550,11 +521,11 @@
Thread.sleep(1000);
RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollbackA);
- assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName);
+ assertEquals(TEST_APP_A, rollbackA.targetPackage.getPackageName());
RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
assertNotNull(rollbackB);
- assertEquals(TEST_APP_B, rollbackB.targetPackage.packageName);
+ assertEquals(TEST_APP_B, rollbackB.targetPackage.getPackageName());
// Executing rollback should roll back the correct package.
RollbackTestUtils.rollback(rollbackA);
@@ -671,7 +642,7 @@
// We should not see a recent rollback listed for TEST_APP_B
for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
- assertNotEquals(TEST_APP_B, r.targetPackage.packageName);
+ assertNotEquals(TEST_APP_B, r.targetPackage.getPackageName());
}
// TODO: Test the listed dependent apps for the recently executed
@@ -681,4 +652,15 @@
RollbackTestUtils.dropShellPermissionIdentity();
}
}
+
+ // Helper function to test the value of a PackageRollbackInfo
+ private void assertPackageRollbackInfoEquals(String packageName,
+ long versionRolledBackFrom, long versionRolledBackTo,
+ PackageRollbackInfo info) {
+ assertEquals(packageName, info.getPackageName());
+ assertEquals(packageName, info.getVersionRolledBackFrom().getPackageName());
+ assertEquals(versionRolledBackFrom, info.getVersionRolledBackFrom().getLongVersionCode());
+ assertEquals(packageName, info.getVersionRolledBackTo().getPackageName());
+ assertEquals(versionRolledBackTo, info.getVersionRolledBackTo().getLongVersionCode());
+ }
}
diff --git a/tests/net/Android.mk b/tests/net/Android.mk
index 6850673..7e1b400 100644
--- a/tests/net/Android.mk
+++ b/tests/net/Android.mk
@@ -32,74 +32,6 @@
LOCAL_CERTIFICATE := platform
-# These are not normally accessible from apps so they must be explicitly included.
-LOCAL_JNI_SHARED_LIBRARIES := \
- android.hidl.token@1.0 \
- libartbase \
- libbacktrace \
- libbase \
- libbinder \
- libbinderthreadstate \
- libc++ \
- libcrypto \
- libcutils \
- libdexfile \
- libframeworksnettestsjni \
- libhidl-gen-utils \
- libhidlbase \
- libhidltransport \
- libhwbinder \
- liblog \
- liblzma \
- libnativehelper \
- libpackagelistparser \
- libpcre2 \
- libprocessgroup \
- libselinux \
- libui \
- libutils \
- libvintf \
- libvndksupport \
- libtinyxml2 \
- libunwindstack \
- libutilscallstack \
- libziparchive \
- libz \
- netd_aidl_interface-cpp
-
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
include $(BUILD_PACKAGE)
-
-#########################################################################
-# Build JNI Shared Library
-#########################################################################
-
-LOCAL_PATH:= $(LOCAL_PATH)/jni
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_CFLAGS := -Wall -Wextra -Werror
-
-LOCAL_C_INCLUDES := \
- libpcap \
- hardware/google/apf
-
-LOCAL_SRC_FILES := $(call all-cpp-files-under)
-
-LOCAL_SHARED_LIBRARIES := \
- libbinder \
- liblog \
- libcutils \
- libnativehelper \
- netd_aidl_interface-cpp
-
-LOCAL_STATIC_LIBRARIES := \
- libpcap \
- libapf
-
-LOCAL_MODULE := libframeworksnettestsjni
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/net/java/android/net/DnsPacketTest.java b/tests/net/java/android/net/DnsPacketTest.java
new file mode 100644
index 0000000..032e526
--- /dev/null
+++ b/tests/net/java/android/net/DnsPacketTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DnsPacketTest {
+ private void assertHeaderParses(DnsPacket.DnsHeader header, int id, int flag,
+ int qCount, int aCount, int nsCount, int arCount) {
+ assertEquals(header.id, id);
+ assertEquals(header.flags, flag);
+ assertEquals(header.getSectionCount(DnsPacket.QDSECTION), qCount);
+ assertEquals(header.getSectionCount(DnsPacket.ANSECTION), aCount);
+ assertEquals(header.getSectionCount(DnsPacket.NSSECTION), nsCount);
+ assertEquals(header.getSectionCount(DnsPacket.ARSECTION), arCount);
+ }
+
+ private void assertSectionParses(DnsPacket.DnsSection section, String dname,
+ int dtype, int dclass, int ttl, byte[] rr) {
+ assertEquals(section.dName, dname);
+ assertEquals(section.nsType, dtype);
+ assertEquals(section.nsClass, dclass);
+ assertEquals(section.ttl, ttl);
+ assertTrue(Arrays.equals(section.getRR(), rr));
+ }
+
+ class TestDnsPacket extends DnsPacket {
+ TestDnsPacket(byte[] data) throws ParseException {
+ super(data);
+ }
+
+ public DnsHeader getHeader() {
+ return mHeader;
+ }
+ public List<DnsSection> getSectionList(int secType) {
+ return mSections[secType];
+ }
+ }
+
+ @Test
+ public void testNullDisallowed() {
+ try {
+ new TestDnsPacket(null);
+ fail("Exception not thrown for null byte array");
+ } catch (DnsPacket.ParseException e) {
+ }
+ }
+
+ @Test
+ public void testV4Answer() throws Exception {
+ final byte[] v4blob = new byte[] {
+ /* Header */
+ 0x55, 0x66, /* Transaction ID */
+ (byte) 0x81, (byte) 0x80, /* Flags */
+ 0x00, 0x01, /* Questions */
+ 0x00, 0x01, /* Answer RRs */
+ 0x00, 0x00, /* Authority RRs */
+ 0x00, 0x00, /* Additional RRs */
+ /* Queries */
+ 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+ 0x00, 0x01, /* Type */
+ 0x00, 0x01, /* Class */
+ /* Answers */
+ (byte) 0xc0, 0x0c, /* Name */
+ 0x00, 0x01, /* Type */
+ 0x00, 0x01, /* Class */
+ 0x00, 0x00, 0x01, 0x2b, /* TTL */
+ 0x00, 0x04, /* Data length */
+ (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */
+ };
+ TestDnsPacket packet = new TestDnsPacket(v4blob);
+
+ // Header part
+ assertHeaderParses(packet.getHeader(), 0x5566, 0x8180, 1, 1, 0, 0);
+
+ // Section part
+ List<DnsPacket.DnsSection> qdSectionList =
+ packet.getSectionList(DnsPacket.QDSECTION);
+ assertEquals(qdSectionList.size(), 1);
+ assertSectionParses(qdSectionList.get(0), "www.google.com", 1, 1, 0, null);
+
+ List<DnsPacket.DnsSection> anSectionList =
+ packet.getSectionList(DnsPacket.ANSECTION);
+ assertEquals(anSectionList.size(), 1);
+ assertSectionParses(anSectionList.get(0), "www.google.com", 1, 1, 0x12b,
+ new byte[]{ (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 });
+ }
+
+ @Test
+ public void testV6Answer() throws Exception {
+ final byte[] v6blob = new byte[] {
+ /* Header */
+ 0x77, 0x22, /* Transaction ID */
+ (byte) 0x81, (byte) 0x80, /* Flags */
+ 0x00, 0x01, /* Questions */
+ 0x00, 0x01, /* Answer RRs */
+ 0x00, 0x00, /* Authority RRs */
+ 0x00, 0x00, /* Additional RRs */
+ /* Queries */
+ 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+ 0x00, 0x1c, /* Type */
+ 0x00, 0x01, /* Class */
+ /* Answers */
+ (byte) 0xc0, 0x0c, /* Name */
+ 0x00, 0x1c, /* Type */
+ 0x00, 0x01, /* Class */
+ 0x00, 0x00, 0x00, 0x37, /* TTL */
+ 0x00, 0x10, /* Data length */
+ 0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 /* Address */
+ };
+ TestDnsPacket packet = new TestDnsPacket(v6blob);
+
+ // Header part
+ assertHeaderParses(packet.getHeader(), 0x7722, 0x8180, 1, 1, 0, 0);
+
+ // Section part
+ List<DnsPacket.DnsSection> qdSectionList =
+ packet.getSectionList(DnsPacket.QDSECTION);
+ assertEquals(qdSectionList.size(), 1);
+ assertSectionParses(qdSectionList.get(0), "www.google.com", 28, 1, 0, null);
+
+ List<DnsPacket.DnsSection> anSectionList =
+ packet.getSectionList(DnsPacket.ANSECTION);
+ assertEquals(anSectionList.size(), 1);
+ assertSectionParses(anSectionList.get(0), "www.google.com", 28, 1, 0x37,
+ new byte[]{ 0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 });
+ }
+}
diff --git a/tests/net/java/android/net/apf/ApfTest.java b/tests/net/java/android/net/apf/ApfTest.java
deleted file mode 100644
index 151b559..0000000
--- a/tests/net/java/android/net/apf/ApfTest.java
+++ /dev/null
@@ -1,1779 +0,0 @@
-/*
- * Copyright (C) 2012 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.apf;
-
-import static android.net.util.NetworkConstants.*;
-import static android.system.OsConstants.*;
-import static com.android.internal.util.BitUtils.bytesToBEInt;
-import static com.android.internal.util.BitUtils.put;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.net.LinkAddress;
-import android.net.LinkProperties;
-import android.net.apf.ApfFilter.ApfConfiguration;
-import android.net.apf.ApfGenerator.IllegalInstructionException;
-import android.net.apf.ApfGenerator.Register;
-import android.net.ip.IpClient;
-import android.net.metrics.IpConnectivityLog;
-import android.net.metrics.RaEvent;
-import android.net.util.InterfaceParams;
-import android.os.ConditionVariable;
-import android.os.Parcelable;
-import android.os.SystemClock;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.text.format.DateUtils;
-import android.util.Log;
-import com.android.frameworks.tests.net.R;
-import com.android.internal.util.HexDump;
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.InetAddress;
-import java.nio.ByteBuffer;
-import java.util.List;
-import java.util.Random;
-import libcore.io.IoUtils;
-import libcore.io.Streams;
-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;
-
-/**
- * Tests for APF program generator and interpreter.
- *
- * Build, install and run with:
- * runtest frameworks-net -c android.net.apf.ApfTest
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class ApfTest {
- private static final int TIMEOUT_MS = 500;
- private static final int MIN_APF_VERSION = 2;
-
- @Mock IpConnectivityLog mLog;
- @Mock Context mContext;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- // Load up native shared library containing APF interpreter exposed via JNI.
- System.loadLibrary("frameworksnettestsjni");
- }
-
- private static final String TAG = "ApfTest";
- // Expected return codes from APF interpreter.
- private static final int PASS = 1;
- private static final int DROP = 0;
- // Interpreter will just accept packets without link layer headers, so pad fake packet to at
- // least the minimum packet size.
- private static final int MIN_PKT_SIZE = 15;
-
- private static final ApfCapabilities MOCK_APF_CAPABILITIES =
- new ApfCapabilities(2, 1700, ARPHRD_ETHER);
-
- private static final boolean DROP_MULTICAST = true;
- private static final boolean ALLOW_MULTICAST = false;
-
- private static final boolean DROP_802_3_FRAMES = true;
- private static final boolean ALLOW_802_3_FRAMES = false;
-
- // Constants for opcode encoding
- private static final byte LI_OP = (byte)(13 << 3);
- private static final byte LDDW_OP = (byte)(22 << 3);
- private static final byte STDW_OP = (byte)(23 << 3);
- private static final byte SIZE0 = (byte)(0 << 1);
- private static final byte SIZE8 = (byte)(1 << 1);
- private static final byte SIZE16 = (byte)(2 << 1);
- private static final byte SIZE32 = (byte)(3 << 1);
- private static final byte R1 = 1;
-
- private static ApfConfiguration getDefaultConfig() {
- ApfFilter.ApfConfiguration config = new ApfConfiguration();
- config.apfCapabilities = MOCK_APF_CAPABILITIES;
- config.multicastFilter = ALLOW_MULTICAST;
- config.ieee802_3Filter = ALLOW_802_3_FRAMES;
- config.ethTypeBlackList = new int[0];
- return config;
- }
-
- private static String label(int code) {
- switch (code) {
- case PASS: return "PASS";
- case DROP: return "DROP";
- default: return "UNKNOWN";
- }
- }
-
- private static void assertReturnCodesEqual(int expected, int got) {
- assertEquals(label(expected), label(got));
- }
-
- private void assertVerdict(int expected, byte[] program, byte[] packet, int filterAge) {
- assertReturnCodesEqual(expected, apfSimulate(program, packet, null, filterAge));
- }
-
- private void assertVerdict(int expected, byte[] program, byte[] packet) {
- assertReturnCodesEqual(expected, apfSimulate(program, packet, null, 0));
- }
-
- private void assertPass(byte[] program, byte[] packet, int filterAge) {
- assertVerdict(PASS, program, packet, filterAge);
- }
-
- private void assertPass(byte[] program, byte[] packet) {
- assertVerdict(PASS, program, packet);
- }
-
- private void assertDrop(byte[] program, byte[] packet, int filterAge) {
- assertVerdict(DROP, program, packet, filterAge);
- }
-
- private void assertDrop(byte[] program, byte[] packet) {
- assertVerdict(DROP, program, packet);
- }
-
- private void assertProgramEquals(byte[] expected, byte[] program) throws AssertionError {
- // assertArrayEquals() would only print one byte, making debugging difficult.
- if (!java.util.Arrays.equals(expected, program)) {
- throw new AssertionError(
- "\nexpected: " + HexDump.toHexString(expected) +
- "\nactual: " + HexDump.toHexString(program));
- }
- }
-
- private void assertDataMemoryContents(
- int expected, byte[] program, byte[] packet, byte[] data, byte[] expected_data)
- throws IllegalInstructionException, Exception {
- assertReturnCodesEqual(expected, apfSimulate(program, packet, data, 0 /* filterAge */));
-
- // assertArrayEquals() would only print one byte, making debugging difficult.
- if (!java.util.Arrays.equals(expected_data, data)) {
- throw new Exception(
- "\nprogram: " + HexDump.toHexString(program) +
- "\ndata memory: " + HexDump.toHexString(data) +
- "\nexpected: " + HexDump.toHexString(expected_data));
- }
- }
-
- private void assertVerdict(int expected, ApfGenerator gen, byte[] packet, int filterAge)
- throws IllegalInstructionException {
- assertReturnCodesEqual(expected, apfSimulate(gen.generate(), packet, null,
- filterAge));
- }
-
- private void assertPass(ApfGenerator gen, byte[] packet, int filterAge)
- throws IllegalInstructionException {
- assertVerdict(PASS, gen, packet, filterAge);
- }
-
- private void assertDrop(ApfGenerator gen, byte[] packet, int filterAge)
- throws IllegalInstructionException {
- assertVerdict(DROP, gen, packet, filterAge);
- }
-
- private void assertPass(ApfGenerator gen)
- throws IllegalInstructionException {
- assertVerdict(PASS, gen, new byte[MIN_PKT_SIZE], 0);
- }
-
- private void assertDrop(ApfGenerator gen)
- throws IllegalInstructionException {
- assertVerdict(DROP, gen, new byte[MIN_PKT_SIZE], 0);
- }
-
- /**
- * Test each instruction by generating a program containing the instruction,
- * generating bytecode for that program and running it through the
- * interpreter to verify it functions correctly.
- */
- @Test
- public void testApfInstructions() throws IllegalInstructionException {
- // Empty program should pass because having the program counter reach the
- // location immediately after the program indicates the packet should be
- // passed to the AP.
- ApfGenerator gen = new ApfGenerator(MIN_APF_VERSION);
- assertPass(gen);
-
- // Test jumping to pass label.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addJump(gen.PASS_LABEL);
- byte[] program = gen.generate();
- assertEquals(1, program.length);
- assertEquals((14 << 3) | (0 << 1) | 0, program[0]);
- assertPass(program, new byte[MIN_PKT_SIZE], 0);
-
- // Test jumping to drop label.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addJump(gen.DROP_LABEL);
- program = gen.generate();
- assertEquals(2, program.length);
- assertEquals((14 << 3) | (1 << 1) | 0, program[0]);
- assertEquals(1, program[1]);
- assertDrop(program, new byte[15], 15);
-
- // Test jumping if equal to 0.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jumping if not equal to 0.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
- assertPass(gen);
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jumping if registers equal.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addJumpIfR0EqualsR1(gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jumping if registers not equal.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
- assertPass(gen);
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test load immediate.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test add.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addAdd(1234567890);
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test subtract.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addAdd(-1234567890);
- gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test or.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addOr(1234567890);
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test and.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addAnd(123456789);
- gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test left shift.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addLeftShift(1);
- gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test right shift.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addRightShift(1);
- gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test multiply.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R0, 123456789);
- gen.addMul(2);
- gen.addJumpIfR0Equals(123456789 * 2, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test divide.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addDiv(2);
- gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test divide by zero.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addDiv(0);
- gen.addJump(gen.DROP_LABEL);
- assertPass(gen);
-
- // Test add.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R1, 1234567890);
- gen.addAddR1();
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test subtract.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R1, -1234567890);
- gen.addAddR1();
- gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test or.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R1, 1234567890);
- gen.addOrR1();
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test and.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addLoadImmediate(Register.R1, 123456789);
- gen.addAndR1();
- gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test left shift.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addLoadImmediate(Register.R1, 1);
- gen.addLeftShiftR1();
- gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test right shift.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addLoadImmediate(Register.R1, -1);
- gen.addLeftShiftR1();
- gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test multiply.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R0, 123456789);
- gen.addLoadImmediate(Register.R1, 2);
- gen.addMulR1();
- gen.addJumpIfR0Equals(123456789 * 2, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test divide.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addLoadImmediate(Register.R1, 2);
- gen.addDivR1();
- gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test divide by zero.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addDivR1();
- gen.addJump(gen.DROP_LABEL);
- assertPass(gen);
-
- // Test byte load.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoad8(Register.R0, 1);
- gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
- assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
- // Test out of bounds load.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoad8(Register.R0, 16);
- gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
- assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
- // Test half-word load.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoad16(Register.R0, 1);
- gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
- assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
- // Test word load.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoad32(Register.R0, 1);
- gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
- assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
-
- // Test byte indexed load.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R1, 1);
- gen.addLoad8Indexed(Register.R0, 0);
- gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
- assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
- // Test out of bounds indexed load.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R1, 8);
- gen.addLoad8Indexed(Register.R0, 8);
- gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
- assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
- // Test half-word indexed load.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R1, 1);
- gen.addLoad16Indexed(Register.R0, 0);
- gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
- assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
- // Test word indexed load.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R1, 1);
- gen.addLoad32Indexed(Register.R0, 0);
- gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
- assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
-
- // Test jumping if greater than.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
- assertPass(gen);
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jumping if less than.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addJumpIfR0LessThan(0, gen.DROP_LABEL);
- assertPass(gen);
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addJumpIfR0LessThan(1, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jumping if any bits set.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
- assertPass(gen);
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
- assertDrop(gen);
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R0, 3);
- gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jumping if register greater than.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
- assertPass(gen);
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R0, 2);
- gen.addLoadImmediate(Register.R1, 1);
- gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jumping if register less than.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
- assertPass(gen);
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R1, 1);
- gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jumping if any bits set in register.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R1, 3);
- gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
- assertPass(gen);
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R1, 3);
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
- assertDrop(gen);
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R1, 3);
- gen.addLoadImmediate(Register.R0, 3);
- gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test load from memory.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadFromMemory(Register.R0, 0);
- gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test store to memory.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R1, 1234567890);
- gen.addStoreToMemory(Register.R1, 12);
- gen.addLoadFromMemory(Register.R0, 12);
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test filter age pre-filled memory.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadFromMemory(Register.R0, gen.FILTER_AGE_MEMORY_SLOT);
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen, new byte[MIN_PKT_SIZE], 1234567890);
-
- // Test packet size pre-filled memory.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadFromMemory(Register.R0, gen.PACKET_SIZE_MEMORY_SLOT);
- gen.addJumpIfR0Equals(MIN_PKT_SIZE, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test IPv4 header size pre-filled memory.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
- gen.addJumpIfR0Equals(20, gen.DROP_LABEL);
- assertDrop(gen, new byte[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x45}, 0);
-
- // Test not.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addNot(Register.R0);
- gen.addJumpIfR0Equals(~1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test negate.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addNeg(Register.R0);
- gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test move.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R1, 1234567890);
- gen.addMove(Register.R0);
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addMove(Register.R1);
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test swap.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R1, 1234567890);
- gen.addSwap();
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addSwap();
- gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jump if bytes not equal.
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
- program = gen.generate();
- assertEquals(6, program.length);
- assertEquals((13 << 3) | (1 << 1) | 0, program[0]);
- assertEquals(1, program[1]);
- assertEquals(((20 << 3) | (1 << 1) | 0) - 256, program[2]);
- assertEquals(1, program[3]);
- assertEquals(1, program[4]);
- assertEquals(123, program[5]);
- assertDrop(program, new byte[MIN_PKT_SIZE], 0);
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
- byte[] packet123 = {0,123,0,0,0,0,0,0,0,0,0,0,0,0,0};
- assertPass(gen, packet123, 0);
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
- assertDrop(gen, packet123, 0);
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,30,4,5}, gen.DROP_LABEL);
- byte[] packet12345 = {0,1,2,3,4,5,0,0,0,0,0,0,0,0,0};
- assertDrop(gen, packet12345, 0);
- gen = new ApfGenerator(MIN_APF_VERSION);
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,3,4,5}, gen.DROP_LABEL);
- assertPass(gen, packet12345, 0);
- }
-
- @Test(expected = ApfGenerator.IllegalInstructionException.class)
- public void testApfGeneratorWantsV2OrGreater() throws Exception {
- // The minimum supported APF version is 2.
- new ApfGenerator(1);
- }
-
- @Test
- public void testApfDataOpcodesWantApfV3() throws IllegalInstructionException, Exception {
- ApfGenerator gen = new ApfGenerator(MIN_APF_VERSION);
- try {
- gen.addStoreData(Register.R0, 0);
- fail();
- } catch (IllegalInstructionException expected) {
- /* pass */
- }
- try {
- gen.addLoadData(Register.R0, 0);
- fail();
- } catch (IllegalInstructionException expected) {
- /* pass */
- }
- }
-
- /**
- * Test that the generator emits immediates using the shortest possible encoding.
- */
- @Test
- public void testImmediateEncoding() throws IllegalInstructionException {
- ApfGenerator gen;
-
- // 0-byte immediate: li R0, 0
- gen = new ApfGenerator(4);
- gen.addLoadImmediate(Register.R0, 0);
- assertProgramEquals(new byte[]{LI_OP | SIZE0}, gen.generate());
-
- // 1-byte immediate: li R0, 42
- gen = new ApfGenerator(4);
- gen.addLoadImmediate(Register.R0, 42);
- assertProgramEquals(new byte[]{LI_OP | SIZE8, 42}, gen.generate());
-
- // 2-byte immediate: li R1, 0x1234
- gen = new ApfGenerator(4);
- gen.addLoadImmediate(Register.R1, 0x1234);
- assertProgramEquals(new byte[]{LI_OP | SIZE16 | R1, 0x12, 0x34}, gen.generate());
-
- // 4-byte immediate: li R0, 0x12345678
- gen = new ApfGenerator(3);
- gen.addLoadImmediate(Register.R0, 0x12345678);
- assertProgramEquals(
- new byte[]{LI_OP | SIZE32, 0x12, 0x34, 0x56, 0x78},
- gen.generate());
- }
-
- /**
- * Test that the generator emits negative immediates using the shortest possible encoding.
- */
- @Test
- public void testNegativeImmediateEncoding() throws IllegalInstructionException {
- ApfGenerator gen;
-
- // 1-byte negative immediate: li R0, -42
- gen = new ApfGenerator(3);
- gen.addLoadImmediate(Register.R0, -42);
- assertProgramEquals(new byte[]{LI_OP | SIZE8, -42}, gen.generate());
-
- // 2-byte negative immediate: li R1, -0x1122
- gen = new ApfGenerator(3);
- gen.addLoadImmediate(Register.R1, -0x1122);
- assertProgramEquals(new byte[]{LI_OP | SIZE16 | R1, (byte)0xEE, (byte)0xDE},
- gen.generate());
-
- // 4-byte negative immediate: li R0, -0x11223344
- gen = new ApfGenerator(3);
- gen.addLoadImmediate(Register.R0, -0x11223344);
- assertProgramEquals(
- new byte[]{LI_OP | SIZE32, (byte)0xEE, (byte)0xDD, (byte)0xCC, (byte)0xBC},
- gen.generate());
- }
-
- /**
- * Test that the generator correctly emits positive and negative immediates for LDDW/STDW.
- */
- @Test
- public void testLoadStoreDataEncoding() throws IllegalInstructionException {
- ApfGenerator gen;
-
- // Load data with no offset: lddw R0, [0 + r1]
- gen = new ApfGenerator(3);
- gen.addLoadData(Register.R0, 0);
- assertProgramEquals(new byte[]{LDDW_OP | SIZE0}, gen.generate());
-
- // Store data with 8bit negative offset: lddw r0, [-42 + r1]
- gen = new ApfGenerator(3);
- gen.addStoreData(Register.R0, -42);
- assertProgramEquals(new byte[]{STDW_OP | SIZE8, -42}, gen.generate());
-
- // Store data to R1 with 16bit negative offset: stdw r1, [-0x1122 + r0]
- gen = new ApfGenerator(3);
- gen.addStoreData(Register.R1, -0x1122);
- assertProgramEquals(new byte[]{STDW_OP | SIZE16 | R1, (byte)0xEE, (byte)0xDE},
- gen.generate());
-
- // Load data to R1 with 32bit negative offset: lddw r1, [0xDEADBEEF + r0]
- gen = new ApfGenerator(3);
- gen.addLoadData(Register.R1, 0xDEADBEEF);
- assertProgramEquals(
- new byte[]{LDDW_OP | SIZE32 | R1, (byte)0xDE, (byte)0xAD, (byte)0xBE, (byte)0xEF},
- gen.generate());
- }
-
- /**
- * Test that the interpreter correctly executes STDW with a negative 8bit offset
- */
- @Test
- public void testApfDataWrite() throws IllegalInstructionException, Exception {
- byte[] packet = new byte[MIN_PKT_SIZE];
- byte[] data = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
- byte[] expected_data = data.clone();
-
- // No memory access instructions: should leave the data segment untouched.
- ApfGenerator gen = new ApfGenerator(3);
- assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
-
- // Expect value 0x87654321 to be stored starting from address -11 from the end of the
- // data buffer, in big-endian order.
- gen = new ApfGenerator(3);
- gen.addLoadImmediate(Register.R0, 0x87654321);
- gen.addLoadImmediate(Register.R1, -5);
- gen.addStoreData(Register.R0, -6); // -5 + -6 = -11 (offset +5 with data_len=16)
- expected_data[5] = (byte)0x87;
- expected_data[6] = (byte)0x65;
- expected_data[7] = (byte)0x43;
- expected_data[8] = (byte)0x21;
- assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
- }
-
- /**
- * Test that the interpreter correctly executes LDDW with a negative 16bit offset
- */
- @Test
- public void testApfDataRead() throws IllegalInstructionException, Exception {
- // Program that DROPs if address 10 (-6) contains 0x87654321.
- ApfGenerator gen = new ApfGenerator(3);
- gen.addLoadImmediate(Register.R1, 1000);
- gen.addLoadData(Register.R0, -1006); // 1000 + -1006 = -6 (offset +10 with data_len=16)
- gen.addJumpIfR0Equals(0x87654321, gen.DROP_LABEL);
- byte[] program = gen.generate();
- byte[] packet = new byte[MIN_PKT_SIZE];
-
- // Content is incorrect (last byte does not match) -> PASS
- byte[] data = new byte[16];
- data[10] = (byte)0x87;
- data[11] = (byte)0x65;
- data[12] = (byte)0x43;
- data[13] = (byte)0x00; // != 0x21
- byte[] expected_data = data.clone();
- assertDataMemoryContents(PASS, program, packet, data, expected_data);
-
- // Fix the last byte -> conditional jump taken -> DROP
- data[13] = (byte)0x21;
- expected_data = data;
- assertDataMemoryContents(DROP, program, packet, data, expected_data);
- }
-
- /**
- * Test that the interpreter correctly executes LDDW followed by a STDW.
- * To cover a few more edge cases, LDDW has a 0bit offset, while STDW has a positive 8bit
- * offset.
- */
- @Test
- public void testApfDataReadModifyWrite() throws IllegalInstructionException, Exception {
- ApfGenerator gen = new ApfGenerator(3);
- gen.addLoadImmediate(Register.R1, -22);
- gen.addLoadData(Register.R0, 0); // Load from address 32 -22 + 0 = 10
- gen.addAdd(0x78453412); // 87654321 + 78453412 = FFAA7733
- gen.addStoreData(Register.R0, 4); // Write back to address 32 -22 + 4 = 14
-
- byte[] packet = new byte[MIN_PKT_SIZE];
- byte[] data = new byte[32];
- data[10] = (byte)0x87;
- data[11] = (byte)0x65;
- data[12] = (byte)0x43;
- data[13] = (byte)0x21;
- byte[] expected_data = data.clone();
- expected_data[14] = (byte)0xFF;
- expected_data[15] = (byte)0xAA;
- expected_data[16] = (byte)0x77;
- expected_data[17] = (byte)0x33;
- assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
- }
-
- @Test
- public void testApfDataBoundChecking() throws IllegalInstructionException, Exception {
- byte[] packet = new byte[MIN_PKT_SIZE];
- byte[] data = new byte[32];
- byte[] expected_data = data;
-
- // Program that DROPs unconditionally. This is our the baseline.
- ApfGenerator gen = new ApfGenerator(3);
- gen.addLoadImmediate(Register.R0, 3);
- gen.addLoadData(Register.R1, 7);
- gen.addJump(gen.DROP_LABEL);
- assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data);
-
- // Same program as before, but this time we're trying to load past the end of the data.
- gen = new ApfGenerator(3);
- gen.addLoadImmediate(Register.R0, 20);
- gen.addLoadData(Register.R1, 15); // 20 + 15 > 32
- gen.addJump(gen.DROP_LABEL); // Not reached.
- assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
-
- // Subtracting an immediate should work...
- gen = new ApfGenerator(3);
- gen.addLoadImmediate(Register.R0, 20);
- gen.addLoadData(Register.R1, -4);
- gen.addJump(gen.DROP_LABEL);
- assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data);
-
- // ...and underflowing simply wraps around to the end of the buffer...
- gen = new ApfGenerator(3);
- gen.addLoadImmediate(Register.R0, 20);
- gen.addLoadData(Register.R1, -30);
- gen.addJump(gen.DROP_LABEL);
- assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data);
-
- // ...but doesn't allow accesses before the start of the buffer
- gen = new ApfGenerator(3);
- gen.addLoadImmediate(Register.R0, 20);
- gen.addLoadData(Register.R1, -1000);
- gen.addJump(gen.DROP_LABEL); // Not reached.
- assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
- }
-
- /**
- * Generate some BPF programs, translate them to APF, then run APF and BPF programs
- * over packet traces and verify both programs filter out the same packets.
- */
- @Test
- public void testApfAgainstBpf() throws Exception {
- String[] tcpdump_filters = new String[]{ "udp", "tcp", "icmp", "icmp6", "udp port 53",
- "arp", "dst 239.255.255.250", "arp or tcp or udp port 53", "net 192.168.1.0/24",
- "arp or icmp6 or portrange 53-54", "portrange 53-54 or portrange 100-50000",
- "tcp[tcpflags] & (tcp-ack|tcp-fin) != 0 and (ip[2:2] > 57 or icmp)" };
- String pcap_filename = stageFile(R.raw.apf);
- for (String tcpdump_filter : tcpdump_filters) {
- byte[] apf_program = Bpf2Apf.convert(compileToBpf(tcpdump_filter));
- assertTrue("Failed to match for filter: " + tcpdump_filter,
- compareBpfApf(tcpdump_filter, pcap_filename, apf_program));
- }
- }
-
- /**
- * Generate APF program, run pcap file though APF filter, then check all the packets in the file
- * should be dropped.
- */
- @Test
- public void testApfFilterPcapFile() throws Exception {
- final byte[] MOCK_PCAP_IPV4_ADDR = {(byte) 172, 16, 7, (byte) 151};
- String pcapFilename = stageFile(R.raw.apfPcap);
- MockIpClientCallback ipClientCallback = new MockIpClientCallback();
- LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_PCAP_IPV4_ADDR), 16);
- LinkProperties lp = new LinkProperties();
- lp.addLinkAddress(link);
-
- ApfConfiguration config = getDefaultConfig();
- ApfCapabilities MOCK_APF_PCAP_CAPABILITIES = new ApfCapabilities(4, 1700, ARPHRD_ETHER);
- config.apfCapabilities = MOCK_APF_PCAP_CAPABILITIES;
- config.multicastFilter = DROP_MULTICAST;
- config.ieee802_3Filter = DROP_802_3_FRAMES;
- TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
- apfFilter.setLinkProperties(lp);
- byte[] program = ipClientCallback.getApfProgram();
- byte[] data = new byte[ApfFilter.Counter.totalSize()];
- final boolean result;
-
- result = dropsAllPackets(program, data, pcapFilename);
- Log.i(TAG, "testApfFilterPcapFile(): Data counters: " + HexDump.toHexString(data, false));
-
- assertTrue("Failed to drop all packets by filter. \nAPF counters:" +
- HexDump.toHexString(data, false), result);
- }
-
- private class MockIpClientCallback extends IpClient.Callback {
- private final ConditionVariable mGotApfProgram = new ConditionVariable();
- private byte[] mLastApfProgram;
-
- @Override
- public void installPacketFilter(byte[] filter) {
- mLastApfProgram = filter;
- mGotApfProgram.open();
- }
-
- public void resetApfProgramWait() {
- mGotApfProgram.close();
- }
-
- public byte[] getApfProgram() {
- assertTrue(mGotApfProgram.block(TIMEOUT_MS));
- return mLastApfProgram;
- }
-
- public void assertNoProgramUpdate() {
- assertFalse(mGotApfProgram.block(TIMEOUT_MS));
- }
- }
-
- private static class TestApfFilter extends ApfFilter {
- public static final byte[] MOCK_MAC_ADDR = {1,2,3,4,5,6};
-
- private FileDescriptor mWriteSocket;
- private final long mFixedTimeMs = SystemClock.elapsedRealtime();
-
- public TestApfFilter(Context context, ApfConfiguration config,
- IpClient.Callback ipClientCallback, IpConnectivityLog log) throws Exception {
- super(context, config, InterfaceParams.getByName("lo"), ipClientCallback, log);
- }
-
- // Pretend an RA packet has been received and show it to ApfFilter.
- public void pretendPacketReceived(byte[] packet) throws IOException, ErrnoException {
- // ApfFilter's ReceiveThread will be waiting to read this.
- Os.write(mWriteSocket, packet, 0, packet.length);
- }
-
- @Override
- protected long currentTimeSeconds() {
- return mFixedTimeMs / DateUtils.SECOND_IN_MILLIS;
- }
-
- @Override
- void maybeStartFilter() {
- mHardwareAddress = MOCK_MAC_ADDR;
- installNewProgramLocked();
-
- // Create two sockets, "readSocket" and "mWriteSocket" and connect them together.
- FileDescriptor readSocket = new FileDescriptor();
- mWriteSocket = new FileDescriptor();
- try {
- Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mWriteSocket, readSocket);
- } catch (ErrnoException e) {
- fail();
- return;
- }
- // Now pass readSocket to ReceiveThread as if it was setup to read raw RAs.
- // This allows us to pretend RA packets have been recieved via pretendPacketReceived().
- mReceiveThread = new ReceiveThread(readSocket);
- mReceiveThread.start();
- }
-
- @Override
- public void shutdown() {
- super.shutdown();
- IoUtils.closeQuietly(mWriteSocket);
- }
- }
-
- private static final int ETH_HEADER_LEN = 14;
- private static final int ETH_DEST_ADDR_OFFSET = 0;
- private static final int ETH_ETHERTYPE_OFFSET = 12;
- private static final byte[] ETH_BROADCAST_MAC_ADDRESS =
- {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
-
- private static final int IPV4_VERSION_IHL_OFFSET = ETH_HEADER_LEN + 0;
- private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9;
- private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16;
- private static final byte[] IPV4_BROADCAST_ADDRESS =
- {(byte) 255, (byte) 255, (byte) 255, (byte) 255};
-
- private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6;
- private static final int IPV6_HEADER_LEN = 40;
- private static final int IPV6_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 24;
- // The IPv6 all nodes address ff02::1
- private static final byte[] IPV6_ALL_NODES_ADDRESS =
- { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
- private static final byte[] IPV6_ALL_ROUTERS_ADDRESS =
- { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 };
-
- private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN;
- private static final int ICMP6_ROUTER_SOLICITATION = 133;
- private static final int ICMP6_ROUTER_ADVERTISEMENT = 134;
- private static final int ICMP6_NEIGHBOR_SOLICITATION = 135;
- private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136;
-
- private static final int ICMP6_RA_HEADER_LEN = 16;
- private static final int ICMP6_RA_ROUTER_LIFETIME_OFFSET =
- ETH_HEADER_LEN + IPV6_HEADER_LEN + 6;
- private static final int ICMP6_RA_CHECKSUM_OFFSET =
- ETH_HEADER_LEN + IPV6_HEADER_LEN + 2;
- private static final int ICMP6_RA_OPTION_OFFSET =
- ETH_HEADER_LEN + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN;
-
- private static final int ICMP6_PREFIX_OPTION_TYPE = 3;
- private static final int ICMP6_PREFIX_OPTION_LEN = 32;
- private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET = 4;
- private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET = 8;
-
- // From RFC6106: Recursive DNS Server option
- private static final int ICMP6_RDNSS_OPTION_TYPE = 25;
- // From RFC6106: DNS Search List option
- private static final int ICMP6_DNSSL_OPTION_TYPE = 31;
-
- // From RFC4191: Route Information option
- private static final int ICMP6_ROUTE_INFO_OPTION_TYPE = 24;
- // Above three options all have the same format:
- private static final int ICMP6_4_BYTE_OPTION_LEN = 8;
- private static final int ICMP6_4_BYTE_LIFETIME_OFFSET = 4;
- private static final int ICMP6_4_BYTE_LIFETIME_LEN = 4;
-
- private static final int UDP_HEADER_LEN = 8;
- private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 22;
-
- private static final int DHCP_CLIENT_PORT = 68;
- private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 48;
-
- private static final int ARP_HEADER_OFFSET = ETH_HEADER_LEN;
- private static final byte[] ARP_IPV4_REQUEST_HEADER = {
- 0, 1, // Hardware type: Ethernet (1)
- 8, 0, // Protocol type: IP (0x0800)
- 6, // Hardware size: 6
- 4, // Protocol size: 4
- 0, 1 // Opcode: request (1)
- };
- private static final byte[] ARP_IPV4_REPLY_HEADER = {
- 0, 1, // Hardware type: Ethernet (1)
- 8, 0, // Protocol type: IP (0x0800)
- 6, // Hardware size: 6
- 4, // Protocol size: 4
- 0, 2 // Opcode: reply (2)
- };
- private static final int ARP_SOURCE_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 14;
- private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 24;
-
- private static final byte[] MOCK_IPV4_ADDR = {10, 0, 0, 1};
- private static final byte[] MOCK_BROADCAST_IPV4_ADDR = {10, 0, 31, (byte) 255}; // prefix = 19
- private static final byte[] MOCK_MULTICAST_IPV4_ADDR = {(byte) 224, 0, 0, 1};
- private static final byte[] ANOTHER_IPV4_ADDR = {10, 0, 0, 2};
- private static final byte[] IPV4_SOURCE_ADDR = {10, 0, 0, 3};
- private static final byte[] ANOTHER_IPV4_SOURCE_ADDR = {(byte) 192, 0, 2, 1};
- private static final byte[] BUG_PROBE_SOURCE_ADDR1 = {0, 0, 1, 2};
- private static final byte[] BUG_PROBE_SOURCE_ADDR2 = {3, 4, 0, 0};
- private static final byte[] IPV4_ANY_HOST_ADDR = {0, 0, 0, 0};
-
- // Helper to initialize a default apfFilter.
- private ApfFilter setupApfFilter(IpClient.Callback ipClientCallback, ApfConfiguration config)
- throws Exception {
- LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
- LinkProperties lp = new LinkProperties();
- lp.addLinkAddress(link);
- TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
- apfFilter.setLinkProperties(lp);
- return apfFilter;
- }
-
- @Test
- public void testApfFilterIPv4() throws Exception {
- MockIpClientCallback ipClientCallback = new MockIpClientCallback();
- LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
- LinkProperties lp = new LinkProperties();
- lp.addLinkAddress(link);
-
- ApfConfiguration config = getDefaultConfig();
- config.multicastFilter = DROP_MULTICAST;
- TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
- apfFilter.setLinkProperties(lp);
-
- byte[] program = ipClientCallback.getApfProgram();
-
- // Verify empty packet of 100 zero bytes is passed
- ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
- assertPass(program, packet.array());
-
- // Verify unicast IPv4 packet is passed
- put(packet, ETH_DEST_ADDR_OFFSET, TestApfFilter.MOCK_MAC_ADDR);
- packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
- put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_IPV4_ADDR);
- assertPass(program, packet.array());
-
- // Verify L2 unicast to IPv4 broadcast addresses is dropped (b/30231088)
- put(packet, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);
- assertDrop(program, packet.array());
- put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_BROADCAST_IPV4_ADDR);
- assertDrop(program, packet.array());
-
- // Verify multicast/broadcast IPv4, not DHCP to us, is dropped
- put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
- assertDrop(program, packet.array());
- packet.put(IPV4_VERSION_IHL_OFFSET, (byte)0x45);
- assertDrop(program, packet.array());
- packet.put(IPV4_PROTOCOL_OFFSET, (byte)IPPROTO_UDP);
- assertDrop(program, packet.array());
- packet.putShort(UDP_DESTINATION_PORT_OFFSET, (short)DHCP_CLIENT_PORT);
- assertDrop(program, packet.array());
- put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_MULTICAST_IPV4_ADDR);
- assertDrop(program, packet.array());
- put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_BROADCAST_IPV4_ADDR);
- assertDrop(program, packet.array());
- put(packet, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);
- assertDrop(program, packet.array());
-
- // Verify broadcast IPv4 DHCP to us is passed
- put(packet, DHCP_CLIENT_MAC_OFFSET, TestApfFilter.MOCK_MAC_ADDR);
- assertPass(program, packet.array());
-
- // Verify unicast IPv4 DHCP to us is passed
- put(packet, ETH_DEST_ADDR_OFFSET, TestApfFilter.MOCK_MAC_ADDR);
- assertPass(program, packet.array());
-
- apfFilter.shutdown();
- }
-
- @Test
- public void testApfFilterIPv6() throws Exception {
- MockIpClientCallback ipClientCallback = new MockIpClientCallback();
- ApfConfiguration config = getDefaultConfig();
- TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
- byte[] program = ipClientCallback.getApfProgram();
-
- // Verify empty IPv6 packet is passed
- ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
- packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
- assertPass(program, packet.array());
-
- // Verify empty ICMPv6 packet is passed
- packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
- assertPass(program, packet.array());
-
- // Verify empty ICMPv6 NA packet is passed
- packet.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_NEIGHBOR_ANNOUNCEMENT);
- assertPass(program, packet.array());
-
- // Verify ICMPv6 NA to ff02::1 is dropped
- put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_NODES_ADDRESS);
- assertDrop(program, packet.array());
-
- // Verify ICMPv6 RS to any is dropped
- packet.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_ROUTER_SOLICITATION);
- assertDrop(program, packet.array());
- put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_ROUTERS_ADDRESS);
- assertDrop(program, packet.array());
-
- apfFilter.shutdown();
- }
-
- @Test
- public void testApfFilterMulticast() throws Exception {
- final byte[] unicastIpv4Addr = {(byte)192,0,2,63};
- final byte[] broadcastIpv4Addr = {(byte)192,0,2,(byte)255};
- final byte[] multicastIpv4Addr = {(byte)224,0,0,1};
- final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb};
-
- MockIpClientCallback ipClientCallback = new MockIpClientCallback();
- LinkAddress link = new LinkAddress(InetAddress.getByAddress(unicastIpv4Addr), 24);
- LinkProperties lp = new LinkProperties();
- lp.addLinkAddress(link);
-
- ApfConfiguration config = getDefaultConfig();
- config.ieee802_3Filter = DROP_802_3_FRAMES;
- TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
- apfFilter.setLinkProperties(lp);
-
- byte[] program = ipClientCallback.getApfProgram();
-
- // Construct IPv4 and IPv6 multicast packets.
- ByteBuffer mcastv4packet = ByteBuffer.wrap(new byte[100]);
- mcastv4packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
- put(mcastv4packet, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr);
-
- ByteBuffer mcastv6packet = ByteBuffer.wrap(new byte[100]);
- mcastv6packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
- mcastv6packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_UDP);
- put(mcastv6packet, IPV6_DEST_ADDR_OFFSET, multicastIpv6Addr);
-
- // Construct IPv4 broadcast packet.
- ByteBuffer bcastv4packet1 = ByteBuffer.wrap(new byte[100]);
- bcastv4packet1.put(ETH_BROADCAST_MAC_ADDRESS);
- bcastv4packet1.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
- put(bcastv4packet1, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr);
-
- ByteBuffer bcastv4packet2 = ByteBuffer.wrap(new byte[100]);
- bcastv4packet2.put(ETH_BROADCAST_MAC_ADDRESS);
- bcastv4packet2.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
- put(bcastv4packet2, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);
-
- // Construct IPv4 broadcast with L2 unicast address packet (b/30231088).
- ByteBuffer bcastv4unicastl2packet = ByteBuffer.wrap(new byte[100]);
- bcastv4unicastl2packet.put(TestApfFilter.MOCK_MAC_ADDR);
- bcastv4unicastl2packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
- put(bcastv4unicastl2packet, IPV4_DEST_ADDR_OFFSET, broadcastIpv4Addr);
-
- // Verify initially disabled multicast filter is off
- assertPass(program, mcastv4packet.array());
- assertPass(program, mcastv6packet.array());
- assertPass(program, bcastv4packet1.array());
- assertPass(program, bcastv4packet2.array());
- assertPass(program, bcastv4unicastl2packet.array());
-
- // Turn on multicast filter and verify it works
- ipClientCallback.resetApfProgramWait();
- apfFilter.setMulticastFilter(true);
- program = ipClientCallback.getApfProgram();
- assertDrop(program, mcastv4packet.array());
- assertDrop(program, mcastv6packet.array());
- assertDrop(program, bcastv4packet1.array());
- assertDrop(program, bcastv4packet2.array());
- assertDrop(program, bcastv4unicastl2packet.array());
-
- // Turn off multicast filter and verify it's off
- ipClientCallback.resetApfProgramWait();
- apfFilter.setMulticastFilter(false);
- program = ipClientCallback.getApfProgram();
- assertPass(program, mcastv4packet.array());
- assertPass(program, mcastv6packet.array());
- assertPass(program, bcastv4packet1.array());
- assertPass(program, bcastv4packet2.array());
- assertPass(program, bcastv4unicastl2packet.array());
-
- // Verify it can be initialized to on
- ipClientCallback.resetApfProgramWait();
- apfFilter.shutdown();
- config.multicastFilter = DROP_MULTICAST;
- config.ieee802_3Filter = DROP_802_3_FRAMES;
- apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
- apfFilter.setLinkProperties(lp);
- program = ipClientCallback.getApfProgram();
- assertDrop(program, mcastv4packet.array());
- assertDrop(program, mcastv6packet.array());
- assertDrop(program, bcastv4packet1.array());
- assertDrop(program, bcastv4unicastl2packet.array());
-
- // Verify that ICMPv6 multicast is not dropped.
- mcastv6packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
- assertPass(program, mcastv6packet.array());
-
- apfFilter.shutdown();
- }
-
- @Test
- public void testApfFilterMulticastPingWhileDozing() throws Exception {
- MockIpClientCallback ipClientCallback = new MockIpClientCallback();
- ApfFilter apfFilter = setupApfFilter(ipClientCallback, getDefaultConfig());
-
- // Construct a multicast ICMPv6 ECHO request.
- final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb};
- ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
- packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
- packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
- packet.put(ICMP6_TYPE_OFFSET, (byte)ICMPV6_ECHO_REQUEST_TYPE);
- put(packet, IPV6_DEST_ADDR_OFFSET, multicastIpv6Addr);
-
- // Normally, we let multicast pings alone...
- assertPass(ipClientCallback.getApfProgram(), packet.array());
-
- // ...and even while dozing...
- apfFilter.setDozeMode(true);
- assertPass(ipClientCallback.getApfProgram(), packet.array());
-
- // ...but when the multicast filter is also enabled, drop the multicast pings to save power.
- apfFilter.setMulticastFilter(true);
- assertDrop(ipClientCallback.getApfProgram(), packet.array());
-
- // However, we should still let through all other ICMPv6 types.
- ByteBuffer raPacket = ByteBuffer.wrap(packet.array().clone());
- raPacket.put(ICMP6_TYPE_OFFSET, (byte)ICMPV6_ROUTER_ADVERTISEMENT);
- assertPass(ipClientCallback.getApfProgram(), raPacket.array());
-
- // Now wake up from doze mode to ensure that we no longer drop the packets.
- // (The multicast filter is still enabled at this point).
- apfFilter.setDozeMode(false);
- assertPass(ipClientCallback.getApfProgram(), packet.array());
-
- apfFilter.shutdown();
- }
-
- @Test
- public void testApfFilter802_3() throws Exception {
- MockIpClientCallback ipClientCallback = new MockIpClientCallback();
- ApfConfiguration config = getDefaultConfig();
- ApfFilter apfFilter = setupApfFilter(ipClientCallback, config);
- byte[] program = ipClientCallback.getApfProgram();
-
- // Verify empty packet of 100 zero bytes is passed
- // Note that eth-type = 0 makes it an IEEE802.3 frame
- ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
- assertPass(program, packet.array());
-
- // Verify empty packet with IPv4 is passed
- packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
- assertPass(program, packet.array());
-
- // Verify empty IPv6 packet is passed
- packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
- assertPass(program, packet.array());
-
- // Now turn on the filter
- ipClientCallback.resetApfProgramWait();
- apfFilter.shutdown();
- config.ieee802_3Filter = DROP_802_3_FRAMES;
- apfFilter = setupApfFilter(ipClientCallback, config);
- program = ipClientCallback.getApfProgram();
-
- // Verify that IEEE802.3 frame is dropped
- // In this case ethtype is used for payload length
- packet.putShort(ETH_ETHERTYPE_OFFSET, (short)(100 - 14));
- assertDrop(program, packet.array());
-
- // Verify that IPv4 (as example of Ethernet II) frame will pass
- packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
- assertPass(program, packet.array());
-
- // Verify that IPv6 (as example of Ethernet II) frame will pass
- packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
- assertPass(program, packet.array());
-
- apfFilter.shutdown();
- }
-
- @Test
- public void testApfFilterEthTypeBL() throws Exception {
- final int[] emptyBlackList = {};
- final int[] ipv4BlackList = {ETH_P_IP};
- final int[] ipv4Ipv6BlackList = {ETH_P_IP, ETH_P_IPV6};
-
- MockIpClientCallback ipClientCallback = new MockIpClientCallback();
- ApfConfiguration config = getDefaultConfig();
- ApfFilter apfFilter = setupApfFilter(ipClientCallback, config);
- byte[] program = ipClientCallback.getApfProgram();
-
- // Verify empty packet of 100 zero bytes is passed
- // Note that eth-type = 0 makes it an IEEE802.3 frame
- ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
- assertPass(program, packet.array());
-
- // Verify empty packet with IPv4 is passed
- packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
- assertPass(program, packet.array());
-
- // Verify empty IPv6 packet is passed
- packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
- assertPass(program, packet.array());
-
- // Now add IPv4 to the black list
- ipClientCallback.resetApfProgramWait();
- apfFilter.shutdown();
- config.ethTypeBlackList = ipv4BlackList;
- apfFilter = setupApfFilter(ipClientCallback, config);
- program = ipClientCallback.getApfProgram();
-
- // Verify that IPv4 frame will be dropped
- packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
- assertDrop(program, packet.array());
-
- // Verify that IPv6 frame will pass
- packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
- assertPass(program, packet.array());
-
- // Now let us have both IPv4 and IPv6 in the black list
- ipClientCallback.resetApfProgramWait();
- apfFilter.shutdown();
- config.ethTypeBlackList = ipv4Ipv6BlackList;
- apfFilter = setupApfFilter(ipClientCallback, config);
- program = ipClientCallback.getApfProgram();
-
- // Verify that IPv4 frame will be dropped
- packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
- assertDrop(program, packet.array());
-
- // Verify that IPv6 frame will be dropped
- packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
- assertDrop(program, packet.array());
-
- apfFilter.shutdown();
- }
-
- private byte[] getProgram(MockIpClientCallback cb, ApfFilter filter, LinkProperties lp) {
- cb.resetApfProgramWait();
- filter.setLinkProperties(lp);
- return cb.getApfProgram();
- }
-
- private void verifyArpFilter(byte[] program, int filterResult) {
- // Verify ARP request packet
- assertPass(program, arpRequestBroadcast(MOCK_IPV4_ADDR));
- assertVerdict(filterResult, program, arpRequestBroadcast(ANOTHER_IPV4_ADDR));
- assertDrop(program, arpRequestBroadcast(IPV4_ANY_HOST_ADDR));
-
- // Verify ARP reply packets from different source ip
- assertDrop(program, arpReply(IPV4_ANY_HOST_ADDR, IPV4_ANY_HOST_ADDR));
- assertPass(program, arpReply(ANOTHER_IPV4_SOURCE_ADDR, IPV4_ANY_HOST_ADDR));
- assertPass(program, arpReply(BUG_PROBE_SOURCE_ADDR1, IPV4_ANY_HOST_ADDR));
- assertPass(program, arpReply(BUG_PROBE_SOURCE_ADDR2, IPV4_ANY_HOST_ADDR));
-
- // Verify unicast ARP reply packet is always accepted.
- assertPass(program, arpReply(IPV4_SOURCE_ADDR, MOCK_IPV4_ADDR));
- assertPass(program, arpReply(IPV4_SOURCE_ADDR, ANOTHER_IPV4_ADDR));
- assertPass(program, arpReply(IPV4_SOURCE_ADDR, IPV4_ANY_HOST_ADDR));
-
- // Verify GARP reply packets are always filtered
- assertDrop(program, garpReply());
- }
-
- @Test
- public void testApfFilterArp() throws Exception {
- MockIpClientCallback ipClientCallback = new MockIpClientCallback();
- ApfConfiguration config = getDefaultConfig();
- config.multicastFilter = DROP_MULTICAST;
- config.ieee802_3Filter = DROP_802_3_FRAMES;
- TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
-
- // Verify initially ARP request filter is off, and GARP filter is on.
- verifyArpFilter(ipClientCallback.getApfProgram(), PASS);
-
- // Inform ApfFilter of our address and verify ARP filtering is on
- LinkAddress linkAddress = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 24);
- LinkProperties lp = new LinkProperties();
- assertTrue(lp.addLinkAddress(linkAddress));
- verifyArpFilter(getProgram(ipClientCallback, apfFilter, lp), DROP);
-
- // Inform ApfFilter of loss of IP and verify ARP filtering is off
- verifyArpFilter(getProgram(ipClientCallback, apfFilter, new LinkProperties()), PASS);
-
- apfFilter.shutdown();
- }
-
- private static byte[] arpReply(byte[] sip, byte[] tip) {
- ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
- packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
- put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REPLY_HEADER);
- put(packet, ARP_SOURCE_IP_ADDRESS_OFFSET, sip);
- put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, tip);
- return packet.array();
- }
-
- private static byte[] arpRequestBroadcast(byte[] tip) {
- ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
- packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
- put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
- put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REQUEST_HEADER);
- put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, tip);
- return packet.array();
- }
-
- private static byte[] garpReply() {
- ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
- packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
- put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
- put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REPLY_HEADER);
- put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, IPV4_ANY_HOST_ADDR);
- return packet.array();
- }
-
- // Verify that the last program pushed to the IpClient.Callback properly filters the
- // given packet for the given lifetime.
- private void verifyRaLifetime(byte[] program, ByteBuffer packet, int lifetime) {
- final int FRACTION_OF_LIFETIME = 6;
- final int ageLimit = lifetime / FRACTION_OF_LIFETIME;
-
- // Verify new program should drop RA for 1/6th its lifetime and pass afterwards.
- assertDrop(program, packet.array());
- assertDrop(program, packet.array(), ageLimit);
- assertPass(program, packet.array(), ageLimit + 1);
- assertPass(program, packet.array(), lifetime);
- // Verify RA checksum is ignored
- final short originalChecksum = packet.getShort(ICMP6_RA_CHECKSUM_OFFSET);
- packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)12345);
- assertDrop(program, packet.array());
- packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)-12345);
- assertDrop(program, packet.array());
- packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, originalChecksum);
-
- // Verify other changes to RA make it not match filter
- final byte originalFirstByte = packet.get(0);
- packet.put(0, (byte)-1);
- assertPass(program, packet.array());
- packet.put(0, (byte)0);
- assertDrop(program, packet.array());
- packet.put(0, originalFirstByte);
- }
-
- // Test that when ApfFilter is shown the given packet, it generates a program to filter it
- // for the given lifetime.
- private void verifyRaLifetime(TestApfFilter apfFilter, MockIpClientCallback ipClientCallback,
- ByteBuffer packet, int lifetime) throws IOException, ErrnoException {
- // Verify new program generated if ApfFilter witnesses RA
- ipClientCallback.resetApfProgramWait();
- apfFilter.pretendPacketReceived(packet.array());
- byte[] program = ipClientCallback.getApfProgram();
- verifyRaLifetime(program, packet, lifetime);
- }
-
- private void verifyRaEvent(RaEvent expected) {
- ArgumentCaptor<Parcelable> captor = ArgumentCaptor.forClass(Parcelable.class);
- verify(mLog, atLeastOnce()).log(captor.capture());
- RaEvent got = lastRaEvent(captor.getAllValues());
- if (!raEventEquals(expected, got)) {
- assertEquals(expected, got); // fail for printing an assertion error message.
- }
- }
-
- private RaEvent lastRaEvent(List<Parcelable> events) {
- RaEvent got = null;
- for (Parcelable ev : events) {
- if (ev instanceof RaEvent) {
- got = (RaEvent) ev;
- }
- }
- return got;
- }
-
- private boolean raEventEquals(RaEvent ev1, RaEvent ev2) {
- return (ev1 != null) && (ev2 != null)
- && (ev1.routerLifetime == ev2.routerLifetime)
- && (ev1.prefixValidLifetime == ev2.prefixValidLifetime)
- && (ev1.prefixPreferredLifetime == ev2.prefixPreferredLifetime)
- && (ev1.routeInfoLifetime == ev2.routeInfoLifetime)
- && (ev1.rdnssLifetime == ev2.rdnssLifetime)
- && (ev1.dnsslLifetime == ev2.dnsslLifetime);
- }
-
- private void assertInvalidRa(TestApfFilter apfFilter, MockIpClientCallback ipClientCallback,
- ByteBuffer packet) throws IOException, ErrnoException {
- ipClientCallback.resetApfProgramWait();
- apfFilter.pretendPacketReceived(packet.array());
- ipClientCallback.assertNoProgramUpdate();
- }
-
- @Test
- public void testApfFilterRa() throws Exception {
- MockIpClientCallback ipClientCallback = new MockIpClientCallback();
- ApfConfiguration config = getDefaultConfig();
- config.multicastFilter = DROP_MULTICAST;
- config.ieee802_3Filter = DROP_802_3_FRAMES;
- TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
- byte[] program = ipClientCallback.getApfProgram();
-
- final int ROUTER_LIFETIME = 1000;
- final int PREFIX_VALID_LIFETIME = 200;
- final int PREFIX_PREFERRED_LIFETIME = 100;
- final int RDNSS_LIFETIME = 300;
- final int ROUTE_LIFETIME = 400;
- // Note that lifetime of 2000 will be ignored in favor of shorter route lifetime of 1000.
- final int DNSSL_LIFETIME = 2000;
- final int VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET = ETH_HEADER_LEN;
- // IPv6, traffic class = 0, flow label = 0x12345
- final int VERSION_TRAFFIC_CLASS_FLOW_LABEL = 0x60012345;
-
- // Verify RA is passed the first time
- ByteBuffer basePacket = ByteBuffer.wrap(new byte[ICMP6_RA_OPTION_OFFSET]);
- basePacket.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
- basePacket.putInt(VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET,
- VERSION_TRAFFIC_CLASS_FLOW_LABEL);
- basePacket.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
- basePacket.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_ROUTER_ADVERTISEMENT);
- basePacket.putShort(ICMP6_RA_ROUTER_LIFETIME_OFFSET, (short)ROUTER_LIFETIME);
- basePacket.position(IPV6_DEST_ADDR_OFFSET);
- basePacket.put(IPV6_ALL_NODES_ADDRESS);
- assertPass(program, basePacket.array());
-
- verifyRaLifetime(apfFilter, ipClientCallback, basePacket, ROUTER_LIFETIME);
- verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, -1, -1));
-
- ByteBuffer newFlowLabelPacket = ByteBuffer.wrap(new byte[ICMP6_RA_OPTION_OFFSET]);
- basePacket.clear();
- newFlowLabelPacket.put(basePacket);
- // Check that changes are ignored in every byte of the flow label.
- newFlowLabelPacket.putInt(VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET,
- VERSION_TRAFFIC_CLASS_FLOW_LABEL + 0x11111);
-
- // Ensure zero-length options cause the packet to be silently skipped.
- // Do this before we test other packets. http://b/29586253
- ByteBuffer zeroLengthOptionPacket = ByteBuffer.wrap(
- new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
- basePacket.clear();
- zeroLengthOptionPacket.put(basePacket);
- zeroLengthOptionPacket.put((byte)ICMP6_PREFIX_OPTION_TYPE);
- zeroLengthOptionPacket.put((byte)0);
- assertInvalidRa(apfFilter, ipClientCallback, zeroLengthOptionPacket);
-
- // Generate several RAs with different options and lifetimes, and verify when
- // ApfFilter is shown these packets, it generates programs to filter them for the
- // appropriate lifetime.
- ByteBuffer prefixOptionPacket = ByteBuffer.wrap(
- new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_LEN]);
- basePacket.clear();
- prefixOptionPacket.put(basePacket);
- prefixOptionPacket.put((byte)ICMP6_PREFIX_OPTION_TYPE);
- prefixOptionPacket.put((byte)(ICMP6_PREFIX_OPTION_LEN / 8));
- prefixOptionPacket.putInt(
- ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET,
- PREFIX_PREFERRED_LIFETIME);
- prefixOptionPacket.putInt(
- ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET,
- PREFIX_VALID_LIFETIME);
- verifyRaLifetime(
- apfFilter, ipClientCallback, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME);
- verifyRaEvent(new RaEvent(
- ROUTER_LIFETIME, PREFIX_VALID_LIFETIME, PREFIX_PREFERRED_LIFETIME, -1, -1, -1));
-
- ByteBuffer rdnssOptionPacket = ByteBuffer.wrap(
- new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
- basePacket.clear();
- rdnssOptionPacket.put(basePacket);
- rdnssOptionPacket.put((byte)ICMP6_RDNSS_OPTION_TYPE);
- rdnssOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
- rdnssOptionPacket.putInt(
- ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, RDNSS_LIFETIME);
- verifyRaLifetime(apfFilter, ipClientCallback, rdnssOptionPacket, RDNSS_LIFETIME);
- verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, RDNSS_LIFETIME, -1));
-
- ByteBuffer routeInfoOptionPacket = ByteBuffer.wrap(
- new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
- basePacket.clear();
- routeInfoOptionPacket.put(basePacket);
- routeInfoOptionPacket.put((byte)ICMP6_ROUTE_INFO_OPTION_TYPE);
- routeInfoOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
- routeInfoOptionPacket.putInt(
- ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, ROUTE_LIFETIME);
- verifyRaLifetime(apfFilter, ipClientCallback, routeInfoOptionPacket, ROUTE_LIFETIME);
- verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, ROUTE_LIFETIME, -1, -1));
-
- ByteBuffer dnsslOptionPacket = ByteBuffer.wrap(
- new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
- basePacket.clear();
- dnsslOptionPacket.put(basePacket);
- dnsslOptionPacket.put((byte)ICMP6_DNSSL_OPTION_TYPE);
- dnsslOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
- dnsslOptionPacket.putInt(
- ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, DNSSL_LIFETIME);
- verifyRaLifetime(apfFilter, ipClientCallback, dnsslOptionPacket, ROUTER_LIFETIME);
- verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, -1, DNSSL_LIFETIME));
-
- // Verify that current program filters all five RAs:
- program = ipClientCallback.getApfProgram();
- verifyRaLifetime(program, basePacket, ROUTER_LIFETIME);
- verifyRaLifetime(program, newFlowLabelPacket, ROUTER_LIFETIME);
- verifyRaLifetime(program, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME);
- verifyRaLifetime(program, rdnssOptionPacket, RDNSS_LIFETIME);
- verifyRaLifetime(program, routeInfoOptionPacket, ROUTE_LIFETIME);
- verifyRaLifetime(program, dnsslOptionPacket, ROUTER_LIFETIME);
-
- apfFilter.shutdown();
- }
-
- /**
- * Stage a file for testing, i.e. make it native accessible. Given a resource ID,
- * copy that resource into the app's data directory and return the path to it.
- */
- private String stageFile(int rawId) throws Exception {
- File file = new File(InstrumentationRegistry.getContext().getFilesDir(), "staged_file");
- new File(file.getParent()).mkdirs();
- InputStream in = null;
- OutputStream out = null;
- try {
- in = InstrumentationRegistry.getContext().getResources().openRawResource(rawId);
- out = new FileOutputStream(file);
- Streams.copy(in, out);
- } finally {
- if (in != null) in.close();
- if (out != null) out.close();
- }
- return file.getAbsolutePath();
- }
-
- private static void put(ByteBuffer buffer, int position, byte[] bytes) {
- final int original = buffer.position();
- buffer.position(position);
- buffer.put(bytes);
- buffer.position(original);
- }
-
- @Test
- public void testRaParsing() throws Exception {
- final int maxRandomPacketSize = 512;
- final Random r = new Random();
- MockIpClientCallback cb = new MockIpClientCallback();
- ApfConfiguration config = getDefaultConfig();
- config.multicastFilter = DROP_MULTICAST;
- config.ieee802_3Filter = DROP_802_3_FRAMES;
- TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mLog);
- for (int i = 0; i < 1000; i++) {
- byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
- r.nextBytes(packet);
- try {
- apfFilter.new Ra(packet, packet.length);
- } catch (ApfFilter.InvalidRaException e) {
- } catch (Exception e) {
- throw new Exception("bad packet: " + HexDump.toHexString(packet), e);
- }
- }
- }
-
- @Test
- public void testRaProcessing() throws Exception {
- final int maxRandomPacketSize = 512;
- final Random r = new Random();
- MockIpClientCallback cb = new MockIpClientCallback();
- ApfConfiguration config = getDefaultConfig();
- config.multicastFilter = DROP_MULTICAST;
- config.ieee802_3Filter = DROP_802_3_FRAMES;
- TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mLog);
- for (int i = 0; i < 1000; i++) {
- byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
- r.nextBytes(packet);
- try {
- apfFilter.processRa(packet, packet.length);
- } catch (Exception e) {
- throw new Exception("bad packet: " + HexDump.toHexString(packet), e);
- }
- }
- }
-
- /**
- * Call the APF interpreter to run {@code program} on {@code packet} with persistent memory
- * segment {@data} pretending the filter was installed {@code filter_age} seconds ago.
- */
- private native static int apfSimulate(byte[] program, byte[] packet, byte[] data,
- int filter_age);
-
- /**
- * Compile a tcpdump human-readable filter (e.g. "icmp" or "tcp port 54") into a BPF
- * prorgam and return a human-readable dump of the BPF program identical to "tcpdump -d".
- */
- private native static String compileToBpf(String filter);
-
- /**
- * Open packet capture file {@code pcap_filename} and filter the packets using tcpdump
- * human-readable filter (e.g. "icmp" or "tcp port 54") compiled to a BPF program and
- * at the same time using APF program {@code apf_program}. Return {@code true} if
- * both APF and BPF programs filter out exactly the same packets.
- */
- private native static boolean compareBpfApf(String filter, String pcap_filename,
- byte[] apf_program);
-
-
- /**
- * Open packet capture file {@code pcapFilename} and run it through APF filter. Then
- * checks whether all the packets are dropped and populates data[] {@code data} with
- * the APF counters.
- */
- private native static boolean dropsAllPackets(byte[] program, byte[] data, String pcapFilename);
-
- @Test
- public void testBroadcastAddress() throws Exception {
- assertEqualsIp("255.255.255.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 0));
- assertEqualsIp("0.0.0.0", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 32));
- assertEqualsIp("0.0.3.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 22));
- assertEqualsIp("0.255.255.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 8));
-
- assertEqualsIp("255.255.255.255", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 0));
- assertEqualsIp("10.0.0.1", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 32));
- assertEqualsIp("10.0.0.255", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 24));
- assertEqualsIp("10.0.255.255", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 16));
- }
-
- public void assertEqualsIp(String expected, int got) throws Exception {
- int want = bytesToBEInt(InetAddress.getByName(expected).getAddress());
- assertEquals(want, got);
- }
-}
diff --git a/tests/net/java/android/net/ip/IpClientTest.java b/tests/net/java/android/net/ip/IpClientTest.java
deleted file mode 100644
index cba3c65..0000000
--- a/tests/net/java/android/net/ip/IpClientTest.java
+++ /dev/null
@@ -1,523 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.ip;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.AlarmManager;
-import android.content.Context;
-import android.content.res.Resources;
-import android.net.INetd;
-import android.net.IpPrefix;
-import android.net.LinkAddress;
-import android.net.LinkProperties;
-import android.net.MacAddress;
-import android.net.RouteInfo;
-import android.net.ip.IpClient.Callback;
-import android.net.ip.IpClient.InitialConfiguration;
-import android.net.ip.IpClient.ProvisioningConfiguration;
-import android.net.util.InterfaceParams;
-import android.os.INetworkManagementService;
-import android.provider.Settings;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.test.mock.MockContentResolver;
-
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.internal.R;
-import com.android.server.net.BaseNetworkObserver;
-
-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.net.InetAddress;
-import java.util.Arrays;
-import java.util.List;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Tests for IpClient.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class IpClientTest {
- private static final int DEFAULT_AVOIDBADWIFI_CONFIG_VALUE = 1;
-
- private static final String VALID = "VALID";
- private static final String INVALID = "INVALID";
- private static final String TEST_IFNAME = "test_wlan0";
- private static final int TEST_IFINDEX = 1001;
- // See RFC 7042#section-2.1.2 for EUI-48 documentation values.
- private static final MacAddress TEST_MAC = MacAddress.fromString("00:00:5E:00:53:01");
- private static final int TEST_TIMEOUT_MS = 400;
-
- @Mock private Context mContext;
- @Mock private INetworkManagementService mNMService;
- @Mock private INetd mNetd;
- @Mock private Resources mResources;
- @Mock private Callback mCb;
- @Mock private AlarmManager mAlarm;
- @Mock private IpClient.Dependencies mDependecies;
- private MockContentResolver mContentResolver;
-
- private BaseNetworkObserver mObserver;
- private InterfaceParams mIfParams;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- when(mContext.getSystemService(eq(Context.ALARM_SERVICE))).thenReturn(mAlarm);
- when(mContext.getResources()).thenReturn(mResources);
- when(mResources.getInteger(R.integer.config_networkAvoidBadWifi))
- .thenReturn(DEFAULT_AVOIDBADWIFI_CONFIG_VALUE);
-
- mContentResolver = new MockContentResolver();
- mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
- when(mContext.getContentResolver()).thenReturn(mContentResolver);
-
- mIfParams = null;
-
- when(mDependecies.getNMS()).thenReturn(mNMService);
- when(mDependecies.getNetd()).thenReturn(mNetd);
- }
-
- private void setTestInterfaceParams(String ifname) {
- mIfParams = (ifname != null)
- ? new InterfaceParams(ifname, TEST_IFINDEX, TEST_MAC)
- : null;
- when(mDependecies.getInterfaceParams(anyString())).thenReturn(mIfParams);
- }
-
- private IpClient makeIpClient(String ifname) throws Exception {
- setTestInterfaceParams(ifname);
- final IpClient ipc = new IpClient(mContext, ifname, mCb, mDependecies);
- verify(mNMService, timeout(TEST_TIMEOUT_MS).times(1)).disableIpv6(ifname);
- verify(mNMService, timeout(TEST_TIMEOUT_MS).times(1)).clearInterfaceAddresses(ifname);
- ArgumentCaptor<BaseNetworkObserver> arg =
- ArgumentCaptor.forClass(BaseNetworkObserver.class);
- verify(mNMService, times(1)).registerObserver(arg.capture());
- mObserver = arg.getValue();
- reset(mNMService);
- // Verify IpClient doesn't call onLinkPropertiesChange() when it starts.
- verify(mCb, never()).onLinkPropertiesChange(any());
- reset(mCb);
- return ipc;
- }
-
- private static LinkProperties makeEmptyLinkProperties(String iface) {
- final LinkProperties empty = new LinkProperties();
- empty.setInterfaceName(iface);
- return empty;
- }
-
- @Test
- public void testNullInterfaceNameMostDefinitelyThrows() throws Exception {
- setTestInterfaceParams(null);
- try {
- final IpClient ipc = new IpClient(mContext, null, mCb, mDependecies);
- ipc.shutdown();
- fail();
- } catch (NullPointerException npe) {
- // Phew; null interface names not allowed.
- }
- }
-
- @Test
- public void testNullCallbackMostDefinitelyThrows() throws Exception {
- final String ifname = "lo";
- setTestInterfaceParams(ifname);
- try {
- final IpClient ipc = new IpClient(mContext, ifname, null, mDependecies);
- ipc.shutdown();
- fail();
- } catch (NullPointerException npe) {
- // Phew; null callbacks not allowed.
- }
- }
-
- @Test
- public void testInvalidInterfaceDoesNotThrow() throws Exception {
- setTestInterfaceParams(TEST_IFNAME);
- final IpClient ipc = new IpClient(mContext, TEST_IFNAME, mCb, mDependecies);
- ipc.shutdown();
- }
-
- @Test
- public void testInterfaceNotFoundFailsImmediately() throws Exception {
- setTestInterfaceParams(null);
- final IpClient ipc = new IpClient(mContext, TEST_IFNAME, mCb, mDependecies);
- ipc.startProvisioning(new IpClient.ProvisioningConfiguration());
- verify(mCb, times(1)).onProvisioningFailure(any());
- ipc.shutdown();
- }
-
- @Test
- public void testDefaultProvisioningConfiguration() throws Exception {
- final String iface = TEST_IFNAME;
- final IpClient ipc = makeIpClient(iface);
-
- ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
- .withoutIPv4()
- // TODO: mock IpReachabilityMonitor's dependencies (NetworkInterface, PowerManager)
- // and enable it in this test
- .withoutIpReachabilityMonitor()
- .build();
-
- ipc.startProvisioning(config);
- verify(mCb, times(1)).setNeighborDiscoveryOffload(true);
- verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setFallbackMulticastFilter(false);
- verify(mCb, never()).onProvisioningFailure(any());
-
- ipc.shutdown();
- verify(mNMService, timeout(TEST_TIMEOUT_MS).times(1)).disableIpv6(iface);
- verify(mNMService, timeout(TEST_TIMEOUT_MS).times(1)).clearInterfaceAddresses(iface);
- verify(mCb, timeout(TEST_TIMEOUT_MS).times(1))
- .onLinkPropertiesChange(eq(makeEmptyLinkProperties(iface)));
- }
-
- @Test
- public void testProvisioningWithInitialConfiguration() throws Exception {
- final String iface = TEST_IFNAME;
- final IpClient ipc = makeIpClient(iface);
-
- String[] addresses = {
- "fe80::a4be:f92:e1f7:22d1/64",
- "fe80::f04a:8f6:6a32:d756/64",
- "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"
- };
- String[] prefixes = { "fe80::/64", "fd2c:4e57:8e3c::/64" };
-
- ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
- .withoutIPv4()
- .withoutIpReachabilityMonitor()
- .withInitialConfiguration(conf(links(addresses), prefixes(prefixes), ips()))
- .build();
-
- ipc.startProvisioning(config);
- verify(mCb, times(1)).setNeighborDiscoveryOffload(true);
- verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setFallbackMulticastFilter(false);
- verify(mCb, never()).onProvisioningFailure(any());
-
- for (String addr : addresses) {
- String[] parts = addr.split("/");
- verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1))
- .interfaceAddAddress(iface, parts[0], Integer.parseInt(parts[1]));
- }
-
- final int lastAddr = addresses.length - 1;
-
- // Add N - 1 addresses
- for (int i = 0; i < lastAddr; i++) {
- mObserver.addressUpdated(iface, new LinkAddress(addresses[i]));
- verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(any());
- reset(mCb);
- }
-
- // Add Nth address
- mObserver.addressUpdated(iface, new LinkAddress(addresses[lastAddr]));
- LinkProperties want = linkproperties(links(addresses), routes(prefixes));
- want.setInterfaceName(iface);
- verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).onProvisioningSuccess(eq(want));
-
- ipc.shutdown();
- verify(mNMService, timeout(TEST_TIMEOUT_MS).times(1)).disableIpv6(iface);
- verify(mNMService, timeout(TEST_TIMEOUT_MS).times(1)).clearInterfaceAddresses(iface);
- verify(mCb, timeout(TEST_TIMEOUT_MS).times(1))
- .onLinkPropertiesChange(eq(makeEmptyLinkProperties(iface)));
- }
-
- @Test
- public void testIsProvisioned() throws Exception {
- InitialConfiguration empty = conf(links(), prefixes());
- IsProvisionedTestCase[] testcases = {
- // nothing
- notProvisionedCase(links(), routes(), dns(), null),
- notProvisionedCase(links(), routes(), dns(), empty),
-
- // IPv4
- provisionedCase(links("192.0.2.12/24"), routes(), dns(), empty),
-
- // IPv6
- notProvisionedCase(
- links("fe80::a4be:f92:e1f7:22d1/64", "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"),
- routes(), dns(), empty),
- notProvisionedCase(
- links("fe80::a4be:f92:e1f7:22d1/64", "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"),
- routes("fe80::/64", "fd2c:4e57:8e3c::/64"), dns("fd00:1234:5678::1000"), empty),
- provisionedCase(
- links("2001:db8:dead:beef:f00::a0/64", "fe80::1/64"),
- routes("::/0"),
- dns("2001:db8:dead:beef:f00::02"), empty),
-
- // Initial configuration
- provisionedCase(
- links("fe80::e1f7:22d1/64", "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"),
- routes("fe80::/64", "fd2c:4e57:8e3c::/64"),
- dns(),
- conf(links("fe80::e1f7:22d1/64", "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"),
- prefixes( "fe80::/64", "fd2c:4e57:8e3c::/64"), ips()))
- };
-
- for (IsProvisionedTestCase testcase : testcases) {
- if (IpClient.isProvisioned(testcase.lp, testcase.config) != testcase.isProvisioned) {
- fail(testcase.errorMessage());
- }
- }
- }
-
- static class IsProvisionedTestCase {
- boolean isProvisioned;
- LinkProperties lp;
- InitialConfiguration config;
-
- String errorMessage() {
- return String.format("expected %s with config %s to be %s, but was %s",
- lp, config, provisioned(isProvisioned), provisioned(!isProvisioned));
- }
-
- static String provisioned(boolean isProvisioned) {
- return isProvisioned ? "provisioned" : "not provisioned";
- }
- }
-
- static IsProvisionedTestCase provisionedCase(Set<LinkAddress> lpAddrs, Set<RouteInfo> lpRoutes,
- Set<InetAddress> lpDns, InitialConfiguration config) {
- return provisioningTest(true, lpAddrs, lpRoutes, lpDns, config);
- }
-
- static IsProvisionedTestCase notProvisionedCase(Set<LinkAddress> lpAddrs,
- Set<RouteInfo> lpRoutes, Set<InetAddress> lpDns, InitialConfiguration config) {
- return provisioningTest(false, lpAddrs, lpRoutes, lpDns, config);
- }
-
- static IsProvisionedTestCase provisioningTest(boolean isProvisioned, Set<LinkAddress> lpAddrs,
- Set<RouteInfo> lpRoutes, Set<InetAddress> lpDns, InitialConfiguration config) {
- IsProvisionedTestCase testcase = new IsProvisionedTestCase();
- testcase.isProvisioned = isProvisioned;
- testcase.lp = new LinkProperties();
- testcase.lp.setLinkAddresses(lpAddrs);
- for (RouteInfo route : lpRoutes) {
- testcase.lp.addRoute(route);
- }
- for (InetAddress dns : lpDns) {
- testcase.lp.addDnsServer(dns);
- }
- testcase.config = config;
- return testcase;
- }
-
- @Test
- public void testInitialConfigurations() throws Exception {
- InitialConfigurationTestCase[] testcases = {
- validConf("valid IPv4 configuration",
- links("192.0.2.12/24"), prefixes("192.0.2.0/24"), dns("192.0.2.2")),
- validConf("another valid IPv4 configuration",
- links("192.0.2.12/24"), prefixes("192.0.2.0/24"), dns()),
- validConf("valid IPv6 configurations",
- links("2001:db8:dead:beef:f00::a0/64", "fe80::1/64"),
- prefixes("2001:db8:dead:beef::/64", "fe80::/64"),
- dns("2001:db8:dead:beef:f00::02")),
- validConf("valid IPv6 configurations",
- links("fe80::1/64"), prefixes("fe80::/64"), dns()),
- validConf("valid IPv6/v4 configuration",
- links("2001:db8:dead:beef:f00::a0/48", "192.0.2.12/24"),
- prefixes("2001:db8:dead:beef::/64", "192.0.2.0/24"),
- dns("192.0.2.2", "2001:db8:dead:beef:f00::02")),
- validConf("valid IPv6 configuration without any GUA.",
- links("fd00:1234:5678::1/48"),
- prefixes("fd00:1234:5678::/48"),
- dns("fd00:1234:5678::1000")),
-
- invalidConf("empty configuration", links(), prefixes(), dns()),
- invalidConf("v4 addr and dns not in any prefix",
- links("192.0.2.12/24"), prefixes("198.51.100.0/24"), dns("192.0.2.2")),
- invalidConf("v4 addr not in any prefix",
- links("198.51.2.12/24"), prefixes("198.51.100.0/24"), dns("192.0.2.2")),
- invalidConf("v4 dns addr not in any prefix",
- links("192.0.2.12/24"), prefixes("192.0.2.0/24"), dns("198.51.100.2")),
- invalidConf("v6 addr not in any prefix",
- links("2001:db8:dead:beef:f00::a0/64", "fe80::1/64"),
- prefixes("2001:db8:dead:beef::/64"),
- dns("2001:db8:dead:beef:f00::02")),
- invalidConf("v6 dns addr not in any prefix",
- links("fe80::1/64"), prefixes("fe80::/64"), dns("2001:db8:dead:beef:f00::02")),
- invalidConf("default ipv6 route and no GUA",
- links("fd01:1111:2222:3333::a0/128"), prefixes("::/0"), dns()),
- invalidConf("invalid v6 prefix length",
- links("2001:db8:dead:beef:f00::a0/128"), prefixes("2001:db8:dead:beef::/32"),
- dns()),
- invalidConf("another invalid v6 prefix length",
- links("2001:db8:dead:beef:f00::a0/128"), prefixes("2001:db8:dead:beef::/72"),
- dns())
- };
-
- for (InitialConfigurationTestCase testcase : testcases) {
- if (testcase.config.isValid() != testcase.isValid) {
- fail(testcase.errorMessage());
- }
- }
- }
-
- static class InitialConfigurationTestCase {
- String descr;
- boolean isValid;
- InitialConfiguration config;
- public String errorMessage() {
- return String.format("%s: expected configuration %s to be %s, but was %s",
- descr, config, validString(isValid), validString(!isValid));
- }
- static String validString(boolean isValid) {
- return isValid ? VALID : INVALID;
- }
- }
-
- static InitialConfigurationTestCase validConf(String descr, Set<LinkAddress> links,
- Set<IpPrefix> prefixes, Set<InetAddress> dns) {
- return confTestCase(descr, true, conf(links, prefixes, dns));
- }
-
- static InitialConfigurationTestCase invalidConf(String descr, Set<LinkAddress> links,
- Set<IpPrefix> prefixes, Set<InetAddress> dns) {
- return confTestCase(descr, false, conf(links, prefixes, dns));
- }
-
- static InitialConfigurationTestCase confTestCase(
- String descr, boolean isValid, InitialConfiguration config) {
- InitialConfigurationTestCase testcase = new InitialConfigurationTestCase();
- testcase.descr = descr;
- testcase.isValid = isValid;
- testcase.config = config;
- return testcase;
- }
-
- static LinkProperties linkproperties(Set<LinkAddress> addresses, Set<RouteInfo> routes) {
- LinkProperties lp = new LinkProperties();
- lp.setLinkAddresses(addresses);
- for (RouteInfo route : routes) {
- lp.addRoute(route);
- }
- return lp;
- }
-
- static InitialConfiguration conf(Set<LinkAddress> links, Set<IpPrefix> prefixes) {
- return conf(links, prefixes, new HashSet<>());
- }
-
- static InitialConfiguration conf(
- Set<LinkAddress> links, Set<IpPrefix> prefixes, Set<InetAddress> dns) {
- InitialConfiguration conf = new InitialConfiguration();
- conf.ipAddresses.addAll(links);
- conf.directlyConnectedRoutes.addAll(prefixes);
- conf.dnsServers.addAll(dns);
- return conf;
- }
-
- static Set<RouteInfo> routes(String... routes) {
- return mapIntoSet(routes, (r) -> new RouteInfo(new IpPrefix(r)));
- }
-
- static Set<IpPrefix> prefixes(String... prefixes) {
- return mapIntoSet(prefixes, IpPrefix::new);
- }
-
- static Set<LinkAddress> links(String... addresses) {
- return mapIntoSet(addresses, LinkAddress::new);
- }
-
- static Set<InetAddress> ips(String... addresses) {
- return mapIntoSet(addresses, InetAddress::getByName);
- }
-
- static Set<InetAddress> dns(String... addresses) {
- return ips(addresses);
- }
-
- static <A, B> Set<B> mapIntoSet(A[] in, Fn<A, B> fn) {
- Set<B> out = new HashSet<>(in.length);
- for (A item : in) {
- try {
- out.add(fn.call(item));
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
- return out;
- }
-
- interface Fn<A,B> {
- B call(A a) throws Exception;
- }
-
- @Test
- public void testAll() {
- List<String> list1 = Arrays.asList();
- List<String> list2 = Arrays.asList("foo");
- List<String> list3 = Arrays.asList("bar", "baz");
- List<String> list4 = Arrays.asList("foo", "bar", "baz");
-
- assertTrue(IpClient.all(list1, (x) -> false));
- assertFalse(IpClient.all(list2, (x) -> false));
- assertTrue(IpClient.all(list3, (x) -> true));
- assertTrue(IpClient.all(list2, (x) -> x.charAt(0) == 'f'));
- assertFalse(IpClient.all(list4, (x) -> x.charAt(0) == 'f'));
- }
-
- @Test
- public void testAny() {
- List<String> list1 = Arrays.asList();
- List<String> list2 = Arrays.asList("foo");
- List<String> list3 = Arrays.asList("bar", "baz");
- List<String> list4 = Arrays.asList("foo", "bar", "baz");
-
- assertFalse(IpClient.any(list1, (x) -> true));
- assertTrue(IpClient.any(list2, (x) -> true));
- assertTrue(IpClient.any(list2, (x) -> x.charAt(0) == 'f'));
- assertFalse(IpClient.any(list3, (x) -> x.charAt(0) == 'f'));
- assertTrue(IpClient.any(list4, (x) -> x.charAt(0) == 'f'));
- }
-
- @Test
- public void testFindAll() {
- List<String> list1 = Arrays.asList();
- List<String> list2 = Arrays.asList("foo");
- List<String> list3 = Arrays.asList("foo", "bar", "baz");
-
- assertEquals(list1, IpClient.findAll(list1, (x) -> true));
- assertEquals(list1, IpClient.findAll(list3, (x) -> false));
- assertEquals(list3, IpClient.findAll(list3, (x) -> true));
- assertEquals(list2, IpClient.findAll(list3, (x) -> x.charAt(0) == 'f'));
- }
-}
diff --git a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java b/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
deleted file mode 100644
index e65585f..0000000
--- a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.ip;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.when;
-
-import android.net.util.InterfaceParams;
-import android.net.util.SharedLog;
-import android.os.Handler;
-import android.os.Looper;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-
-/**
- * Tests for IpReachabilityMonitor.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class IpReachabilityMonitorTest {
-
- @Mock IpReachabilityMonitor.Callback mCallback;
- @Mock IpReachabilityMonitor.Dependencies mDependencies;
- @Mock SharedLog mLog;
- Handler mHandler;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- when(mLog.forSubComponent(anyString())).thenReturn(mLog);
- mHandler = new Handler(Looper.getMainLooper());
- }
-
- IpReachabilityMonitor makeMonitor() {
- final InterfaceParams ifParams = new InterfaceParams("fake0", 1, null);
- return new IpReachabilityMonitor(ifParams, mHandler, mLog, mCallback, null, mDependencies);
- }
-
- @Test
- public void testNothing() {
- IpReachabilityMonitor monitor = makeMonitor();
- }
-}
diff --git a/tests/net/java/android/net/ip/IpServerTest.java b/tests/net/java/android/net/ip/IpServerTest.java
index c3162af..80aac04 100644
--- a/tests/net/java/android/net/ip/IpServerTest.java
+++ b/tests/net/java/android/net/ip/IpServerTest.java
@@ -33,6 +33,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
@@ -47,6 +48,7 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import android.net.INetd;
import android.net.INetworkStatsService;
import android.net.InterfaceConfiguration;
import android.net.IpPrefix;
@@ -92,6 +94,7 @@
private static final int MAKE_DHCPSERVER_TIMEOUT_MS = 1000;
@Mock private INetworkManagementService mNMService;
+ @Mock private INetd mNetd;
@Mock private INetworkStatsService mStatsService;
@Mock private IpServer.Callback mCallback;
@Mock private InterfaceConfiguration mInterfaceConfiguration;
@@ -112,16 +115,6 @@
}
private void initStateMachine(int interfaceType, boolean usingLegacyDhcp) throws Exception {
- mIpServer = new IpServer(
- IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog,
- mNMService, mStatsService, mCallback, usingLegacyDhcp, mDependencies);
- mIpServer.start();
- // Starting the state machine always puts us in a consistent state and notifies
- // the rest of the world that we've changed from an unknown to available state.
- mLooper.dispatchAll();
- reset(mNMService, mStatsService, mCallback);
- when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
-
doAnswer(inv -> {
final IDhcpServerCallbacks cb = inv.getArgument(2);
new Thread(() -> {
@@ -135,6 +128,17 @@
}).when(mDependencies).makeDhcpServer(any(), mDhcpParamsCaptor.capture(), any());
when(mDependencies.getRouterAdvertisementDaemon(any())).thenReturn(mRaDaemon);
when(mDependencies.getInterfaceParams(IFACE_NAME)).thenReturn(TEST_IFACE_PARAMS);
+ when(mDependencies.getNetdService()).thenReturn(mNetd);
+
+ mIpServer = new IpServer(
+ IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog,
+ mNMService, mStatsService, mCallback, usingLegacyDhcp, mDependencies);
+ mIpServer.start();
+ // Starting the state machine always puts us in a consistent state and notifies
+ // the rest of the world that we've changed from an unknown to available state.
+ mLooper.dispatchAll();
+ reset(mNMService, mStatsService, mCallback);
+ when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
when(mRaDaemon.start()).thenReturn(true);
}
@@ -223,9 +227,9 @@
initTetheredStateMachine(TETHERING_BLUETOOTH, null);
dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
- InOrder inOrder = inOrder(mNMService, mStatsService, mCallback);
+ InOrder inOrder = inOrder(mNMService, mNetd, mStatsService, mCallback);
inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
- inOrder.verify(mNMService).setInterfaceConfig(eq(IFACE_NAME), any());
+ inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
inOrder.verify(mCallback).updateInterfaceState(
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
@@ -318,12 +322,12 @@
initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
- InOrder inOrder = inOrder(mNMService, mStatsService, mCallback);
+ InOrder inOrder = inOrder(mNMService, mNetd, mStatsService, mCallback);
inOrder.verify(mStatsService).forceUpdate();
inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
- inOrder.verify(mNMService).setInterfaceConfig(eq(IFACE_NAME), any());
+ inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
inOrder.verify(mCallback).updateInterfaceState(
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
diff --git a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java
index a9f9758..1fc67a8 100644
--- a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java
+++ b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java
@@ -27,6 +27,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.lang.reflect.Modifier;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.Arrays;
@@ -60,6 +61,12 @@
builder.setMtu(null);
in = builder.build();
assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+
+ // Verify that this test does not miss any new field added later.
+ // If any field is added to NetworkAttributes it must be tested here for parceling
+ // roundtrip.
+ assertEquals(4, Arrays.stream(NetworkAttributes.class.getDeclaredFields())
+ .filter(f -> !Modifier.isStatic(f.getModifiers())).count());
}
@Test
diff --git a/tests/net/java/android/net/shared/InitialConfigurationTest.java b/tests/net/java/android/net/shared/InitialConfigurationTest.java
new file mode 100644
index 0000000..78792bd
--- /dev/null
+++ b/tests/net/java/android/net/shared/InitialConfigurationTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.shared;
+
+import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.shared.ParcelableTestUtil.assertFieldCountEquals;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.function.Consumer;
+
+/**
+ * Tests for {@link InitialConfiguration}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class InitialConfigurationTest {
+ private InitialConfiguration mConfig;
+
+ @Before
+ public void setUp() {
+ mConfig = new InitialConfiguration();
+ mConfig.ipAddresses.addAll(Arrays.asList(
+ new LinkAddress(parseNumericAddress("192.168.45.45"), 16),
+ new LinkAddress(parseNumericAddress("2001:db8::45"), 33)));
+ mConfig.directlyConnectedRoutes.addAll(Arrays.asList(
+ new IpPrefix(parseNumericAddress("192.168.46.46"), 17),
+ new IpPrefix(parseNumericAddress("2001:db8::46"), 34)));
+ mConfig.dnsServers.addAll(Arrays.asList(
+ parseNumericAddress("192.168.47.47"),
+ parseNumericAddress("2001:db8::47")));
+ // Any added InitialConfiguration field must be included in equals() to be tested properly
+ assertFieldCountEquals(3, InitialConfiguration.class);
+ }
+
+ @Test
+ public void testParcelUnparcelInitialConfiguration() {
+ final InitialConfiguration unparceled =
+ InitialConfiguration.fromStableParcelable(mConfig.toStableParcelable());
+ assertEquals(mConfig, unparceled);
+ }
+
+ @Test
+ public void testEquals() {
+ assertEquals(mConfig, InitialConfiguration.copy(mConfig));
+
+ assertNotEqualsAfterChange(c -> c.ipAddresses.add(
+ new LinkAddress(parseNumericAddress("192.168.47.47"), 24)));
+ assertNotEqualsAfterChange(c -> c.directlyConnectedRoutes.add(
+ new IpPrefix(parseNumericAddress("192.168.46.46"), 32)));
+ assertNotEqualsAfterChange(c -> c.dnsServers.add(parseNumericAddress("2001:db8::49")));
+ assertFieldCountEquals(3, InitialConfiguration.class);
+ }
+
+ private void assertNotEqualsAfterChange(Consumer<InitialConfiguration> mutator) {
+ final InitialConfiguration newConfig = InitialConfiguration.copy(mConfig);
+ mutator.accept(newConfig);
+ assertNotEquals(mConfig, newConfig);
+ }
+}
diff --git a/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java b/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java
new file mode 100644
index 0000000..14df392
--- /dev/null
+++ b/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.shared;
+
+import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.shared.IpConfigurationParcelableUtil.fromStableParcelable;
+import static android.net.shared.IpConfigurationParcelableUtil.toStableParcelable;
+import static android.net.shared.ParcelableTestUtil.assertFieldCountEquals;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.DhcpResults;
+import android.net.LinkAddress;
+import android.net.StaticIpConfiguration;
+import android.net.apf.ApfCapabilities;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+
+/**
+ * Tests for {@link IpConfigurationParcelableUtil}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpConfigurationParcelableUtilTest {
+ private StaticIpConfiguration mStaticIpConfiguration;
+ private DhcpResults mDhcpResults;
+
+ @Before
+ public void setUp() {
+ mStaticIpConfiguration = new StaticIpConfiguration();
+ mStaticIpConfiguration.ipAddress = new LinkAddress(parseNumericAddress("2001:db8::42"), 64);
+ mStaticIpConfiguration.gateway = parseNumericAddress("192.168.42.42");
+ mStaticIpConfiguration.dnsServers.add(parseNumericAddress("2001:db8::43"));
+ mStaticIpConfiguration.dnsServers.add(parseNumericAddress("192.168.43.43"));
+ mStaticIpConfiguration.domains = "example.com";
+ // Any added StaticIpConfiguration field must be included in equals() to be tested properly
+ assertFieldCountEquals(4, StaticIpConfiguration.class);
+
+ mDhcpResults = new DhcpResults(mStaticIpConfiguration);
+ mDhcpResults.serverAddress = (Inet4Address) parseNumericAddress("192.168.44.44");
+ mDhcpResults.vendorInfo = "TEST_VENDOR_INFO";
+ mDhcpResults.leaseDuration = 3600;
+ mDhcpResults.mtu = 1450;
+ // Any added DhcpResults field must be included in equals() to be tested properly
+ assertFieldCountEquals(4, DhcpResults.class);
+ }
+
+ @Test
+ public void testParcelUnparcelStaticConfiguration() {
+ doStaticConfigurationParcelUnparcelTest();
+ }
+
+ @Test
+ public void testParcelUnparcelStaticConfiguration_NullIpAddress() {
+ mStaticIpConfiguration.ipAddress = null;
+ doStaticConfigurationParcelUnparcelTest();
+ }
+
+ @Test
+ public void testParcelUnparcelStaticConfiguration_NullGateway() {
+ mStaticIpConfiguration.gateway = null;
+ doStaticConfigurationParcelUnparcelTest();
+ }
+
+ @Test
+ public void testParcelUnparcelStaticConfiguration_NullDomains() {
+ mStaticIpConfiguration.domains = null;
+ doStaticConfigurationParcelUnparcelTest();
+ }
+
+ @Test
+ public void testParcelUnparcelStaticConfiguration_EmptyDomains() {
+ mStaticIpConfiguration.domains = "";
+ doStaticConfigurationParcelUnparcelTest();
+ }
+
+ private void doStaticConfigurationParcelUnparcelTest() {
+ final StaticIpConfiguration unparceled =
+ fromStableParcelable(toStableParcelable(mStaticIpConfiguration));
+ assertEquals(mStaticIpConfiguration, unparceled);
+ }
+
+ @Test
+ public void testParcelUnparcelDhcpResults() {
+ doDhcpResultsParcelUnparcelTest();
+ }
+
+ @Test
+ public void testParcelUnparcelDhcpResults_NullServerAddress() {
+ mDhcpResults.serverAddress = null;
+ doDhcpResultsParcelUnparcelTest();
+ }
+
+ @Test
+ public void testParcelUnparcelDhcpResults_NullVendorInfo() {
+ mDhcpResults.vendorInfo = null;
+ doDhcpResultsParcelUnparcelTest();
+ }
+
+ private void doDhcpResultsParcelUnparcelTest() {
+ final DhcpResults unparceled = fromStableParcelable(toStableParcelable(mDhcpResults));
+ assertEquals(mDhcpResults, unparceled);
+ }
+
+ @Test
+ public void testParcelUnparcelApfCapabilities() {
+ final ApfCapabilities caps = new ApfCapabilities(123, 456, 789);
+ assertEquals(caps, fromStableParcelable(toStableParcelable(caps)));
+ }
+}
diff --git a/tests/net/java/android/net/shared/LinkPropertiesParcelableUtilTest.java b/tests/net/java/android/net/shared/LinkPropertiesParcelableUtilTest.java
index 4cabfc9..b6d01db 100644
--- a/tests/net/java/android/net/shared/LinkPropertiesParcelableUtilTest.java
+++ b/tests/net/java/android/net/shared/LinkPropertiesParcelableUtilTest.java
@@ -18,6 +18,7 @@
import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable;
import static android.net.shared.LinkPropertiesParcelableUtil.toStableParcelable;
+import static android.net.shared.ParcelableTestUtil.assertFieldCountEquals;
import static org.junit.Assert.assertEquals;
@@ -35,7 +36,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collections;
@@ -48,62 +48,52 @@
private LinkProperties mLinkProperties;
private static final String TEST_LINKPROPS_IFACE = "TEST_IFACE";
- private static final String TEST_STACKED_LINK_1_IFACE = "TEST_STACKED_IFACE_1";
- private static final String TEST_STACKED_LINK_2_IFACE = "TEST_STACKED_IFACE_2";
@Before
public void setUp() {
- mLinkProperties = makeLinkProperties(TEST_LINKPROPS_IFACE);
- mLinkProperties.addStackedLink(makeLinkProperties(TEST_STACKED_LINK_1_IFACE));
- mLinkProperties.addStackedLink(makeLinkProperties(TEST_STACKED_LINK_2_IFACE));
- }
-
- private static LinkProperties makeLinkProperties(String iface) {
- final LinkProperties lp = new LinkProperties();
- lp.setInterfaceName(iface);
- lp.setLinkAddresses(Arrays.asList(
+ mLinkProperties = new LinkProperties();
+ mLinkProperties.setInterfaceName(TEST_LINKPROPS_IFACE);
+ mLinkProperties.setLinkAddresses(Arrays.asList(
new LinkAddress(InetAddresses.parseNumericAddress("192.168.0.42"), 16),
new LinkAddress(InetAddresses.parseNumericAddress("2001:db8::7"), 42)));
- lp.setDnsServers(Arrays.asList(
+ mLinkProperties.setDnsServers(Arrays.asList(
InetAddresses.parseNumericAddress("2001:db8::42"),
InetAddresses.parseNumericAddress("192.168.1.1")
));
- lp.setValidatedPrivateDnsServers(Arrays.asList(
+ mLinkProperties.setValidatedPrivateDnsServers(Arrays.asList(
InetAddresses.parseNumericAddress("2001:db8::43"),
InetAddresses.parseNumericAddress("192.168.42.43")
));
- lp.setPcscfServers(Arrays.asList(
+ mLinkProperties.setPcscfServers(Arrays.asList(
InetAddresses.parseNumericAddress("2001:db8::47"),
InetAddresses.parseNumericAddress("192.168.42.47")
));
- lp.setUsePrivateDns(true);
- lp.setPrivateDnsServerName("test.example.com");
- lp.setDomains("test1.example.com,test2.example.com");
- lp.addRoute(new RouteInfo(
+ mLinkProperties.setUsePrivateDns(true);
+ mLinkProperties.setPrivateDnsServerName("test.example.com");
+ mLinkProperties.setDomains("test1.example.com,test2.example.com");
+ mLinkProperties.addRoute(new RouteInfo(
new IpPrefix(InetAddresses.parseNumericAddress("2001:db8::44"), 45),
InetAddresses.parseNumericAddress("2001:db8::45"),
- iface,
+ TEST_LINKPROPS_IFACE,
RouteInfo.RTN_UNICAST
));
- lp.addRoute(new RouteInfo(
+ mLinkProperties.addRoute(new RouteInfo(
new IpPrefix(InetAddresses.parseNumericAddress("192.168.44.45"), 16),
InetAddresses.parseNumericAddress("192.168.45.1"),
- iface,
+ TEST_LINKPROPS_IFACE,
RouteInfo.RTN_THROW
));
- lp.setHttpProxy(new ProxyInfo("test3.example.com", 8000,
+ mLinkProperties.setHttpProxy(new ProxyInfo("test3.example.com", 8000,
"excl1.example.com,excl2.example.com"));
- lp.setMtu(5000);
- lp.setTcpBufferSizes("1,2,3,4,5,6");
- lp.setNat64Prefix(new IpPrefix(InetAddresses.parseNumericAddress("2001:db8::48"), 96));
+ mLinkProperties.setMtu(5000);
+ mLinkProperties.setTcpBufferSizes("1,2,3,4,5,6");
+ mLinkProperties.setNat64Prefix(
+ new IpPrefix(InetAddresses.parseNumericAddress("2001:db8::48"), 96));
// Verify that this test does not miss any new field added later.
// If any added field is not included in LinkProperties#equals, assertLinkPropertiesEquals
// must also be updated.
- assertEquals(14, Arrays.stream(LinkProperties.class.getDeclaredFields())
- .filter(f -> !Modifier.isStatic(f.getModifiers())).count());
-
- return lp;
+ assertFieldCountEquals(14, LinkProperties.class);
}
@Test
@@ -187,7 +177,7 @@
private static void assertLinkPropertiesEquals(LinkProperties expected, LinkProperties actual) {
assertEquals(expected, actual);
- // LinkProperties equals() does not include stacked links
- assertEquals(expected.getStackedLinks(), actual.getStackedLinks());
+ // Equality on stacked links is not tested as they should not be passed to processes using
+ // LinkPropertiesParcelable.
}
}
diff --git a/tests/net/java/android/net/shared/ParcelableTestUtil.java b/tests/net/java/android/net/shared/ParcelableTestUtil.java
new file mode 100644
index 0000000..088ea3c
--- /dev/null
+++ b/tests/net/java/android/net/shared/ParcelableTestUtil.java
@@ -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.shared;
+
+import static org.junit.Assert.assertEquals;
+
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+
+/**
+ * Utility classes to write tests for stable AIDL parceling/unparceling
+ */
+public final class ParcelableTestUtil {
+
+ /**
+ * Verifies that the number of nonstatic fields in a class equals a given count.
+ *
+ * <p>This assertion serves as a reminder to update test code around it if fields are added
+ * after the test is written.
+ * @param count Expected number of nonstatic fields in the class.
+ * @param clazz Class to test.
+ */
+ public static <T> void assertFieldCountEquals(int count, Class<T> clazz) {
+ assertEquals(count, Arrays.stream(clazz.getDeclaredFields())
+ .filter(f -> !Modifier.isStatic(f.getModifiers())).count());
+ }
+}
diff --git a/tests/net/java/android/net/shared/ProvisioningConfigurationTest.java b/tests/net/java/android/net/shared/ProvisioningConfigurationTest.java
new file mode 100644
index 0000000..6ea47d2
--- /dev/null
+++ b/tests/net/java/android/net/shared/ProvisioningConfigurationTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.shared;
+
+import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.shared.ParcelableTestUtil.assertFieldCountEquals;
+import static android.net.shared.ProvisioningConfiguration.fromStableParcelable;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.net.LinkAddress;
+import android.net.Network;
+import android.net.StaticIpConfiguration;
+import android.net.apf.ApfCapabilities;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+
+/**
+ * Tests for {@link ProvisioningConfiguration}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ProvisioningConfigurationTest {
+ private ProvisioningConfiguration mConfig;
+
+ @Before
+ public void setUp() {
+ mConfig = new ProvisioningConfiguration();
+ mConfig.mEnableIPv4 = true;
+ mConfig.mEnableIPv6 = true;
+ mConfig.mUsingMultinetworkPolicyTracker = true;
+ mConfig.mUsingIpReachabilityMonitor = true;
+ mConfig.mRequestedPreDhcpActionMs = 42;
+ mConfig.mInitialConfig = new InitialConfiguration();
+ mConfig.mInitialConfig.ipAddresses.add(
+ new LinkAddress(parseNumericAddress("192.168.42.42"), 24));
+ mConfig.mStaticIpConfig = new StaticIpConfiguration();
+ mConfig.mStaticIpConfig.ipAddress =
+ new LinkAddress(parseNumericAddress("2001:db8::42"), 90);
+ // Not testing other InitialConfig or StaticIpConfig members: they have their own unit tests
+ mConfig.mApfCapabilities = new ApfCapabilities(1, 2, 3);
+ mConfig.mProvisioningTimeoutMs = 4200;
+ mConfig.mIPv6AddrGenMode = 123;
+ mConfig.mNetwork = new Network(321);
+ mConfig.mDisplayName = "test_config";
+ // Any added field must be included in equals() to be tested properly
+ assertFieldCountEquals(12, ProvisioningConfiguration.class);
+ }
+
+ @Test
+ public void testParcelUnparcel() {
+ doParcelUnparcelTest();
+ }
+
+ @Test
+ public void testParcelUnparcel_NullInitialConfiguration() {
+ mConfig.mInitialConfig = null;
+ doParcelUnparcelTest();
+ }
+
+ @Test
+ public void testParcelUnparcel_NullStaticConfiguration() {
+ mConfig.mStaticIpConfig = null;
+ doParcelUnparcelTest();
+ }
+
+ @Test
+ public void testParcelUnparcel_NullApfCapabilities() {
+ mConfig.mApfCapabilities = null;
+ doParcelUnparcelTest();
+ }
+
+ @Test
+ public void testParcelUnparcel_NullNetwork() {
+ mConfig.mNetwork = null;
+ doParcelUnparcelTest();
+ }
+
+ private void doParcelUnparcelTest() {
+ final ProvisioningConfiguration unparceled =
+ fromStableParcelable(mConfig.toStableParcelable());
+ assertEquals(mConfig, unparceled);
+ }
+
+ @Test
+ public void testEquals() {
+ assertEquals(mConfig, new ProvisioningConfiguration(mConfig));
+
+ assertNotEqualsAfterChange(c -> c.mEnableIPv4 = false);
+ assertNotEqualsAfterChange(c -> c.mEnableIPv6 = false);
+ assertNotEqualsAfterChange(c -> c.mUsingMultinetworkPolicyTracker = false);
+ assertNotEqualsAfterChange(c -> c.mUsingIpReachabilityMonitor = false);
+ assertNotEqualsAfterChange(c -> c.mRequestedPreDhcpActionMs++);
+ assertNotEqualsAfterChange(c -> c.mInitialConfig.ipAddresses.add(
+ new LinkAddress(parseNumericAddress("192.168.47.47"), 16)));
+ assertNotEqualsAfterChange(c -> c.mInitialConfig = null);
+ assertNotEqualsAfterChange(c -> c.mStaticIpConfig.ipAddress =
+ new LinkAddress(parseNumericAddress("2001:db8::47"), 64));
+ assertNotEqualsAfterChange(c -> c.mStaticIpConfig = null);
+ assertNotEqualsAfterChange(c -> c.mApfCapabilities = new ApfCapabilities(4, 5, 6));
+ assertNotEqualsAfterChange(c -> c.mApfCapabilities = null);
+ assertNotEqualsAfterChange(c -> c.mProvisioningTimeoutMs++);
+ assertNotEqualsAfterChange(c -> c.mIPv6AddrGenMode++);
+ assertNotEqualsAfterChange(c -> c.mNetwork = new Network(123));
+ assertNotEqualsAfterChange(c -> c.mNetwork = null);
+ assertNotEqualsAfterChange(c -> c.mDisplayName = "other_test");
+ assertNotEqualsAfterChange(c -> c.mDisplayName = null);
+ assertFieldCountEquals(12, ProvisioningConfiguration.class);
+ }
+
+ private void assertNotEqualsAfterChange(Consumer<ProvisioningConfiguration> mutator) {
+ final ProvisioningConfiguration newConfig = new ProvisioningConfiguration(mConfig);
+ mutator.accept(newConfig);
+ assertNotEquals(mConfig, newConfig);
+ }
+}
diff --git a/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java b/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java
deleted file mode 100644
index f9b7ec8..0000000
--- a/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java
+++ /dev/null
@@ -1,419 +0,0 @@
-/*
- * Copyright (C) 2016 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.util;
-
-import static android.net.util.NetworkConstants.*;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.net.MacAddress;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.test.filters.SmallTest;
-
-import org.junit.runner.RunWith;
-import org.junit.Test;
-
-import libcore.util.HexEncoding;
-
-/**
- * Tests for ConnectivityPacketSummary.
- *
- * @hide
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class ConnectivityPacketSummaryTest {
- private static final MacAddress MYHWADDR = MacAddress.fromString("80:7a:bf:6f:48:f3");
-
- private String getSummary(String hexBytes) {
- hexBytes = hexBytes.replaceAll("\\s+", "");
- final byte[] bytes = HexEncoding.decode(hexBytes.toCharArray(), false);
- return ConnectivityPacketSummary.summarize(MYHWADDR, bytes);
- }
-
- @Test
- public void testParseICMPv6DADProbe() {
- final String packet =
- // Ethernet
- "3333FF6F48F3 807ABF6F48F3 86DD" +
- // IPv6
- "600000000018 3A FF" +
- "00000000000000000000000000000000" +
- "FF0200000000000000000001FF6F48F3" +
- // ICMPv6
- "87 00 A8E7" +
- "00000000" +
- "FE80000000000000827ABFFFFE6F48F3";
-
- final String expected =
- "TX 80:7a:bf:6f:48:f3 > 33:33:ff:6f:48:f3 ipv6" +
- " :: > ff02::1:ff6f:48f3 icmp6" +
- " ns fe80::827a:bfff:fe6f:48f3";
-
- assertEquals(expected, getSummary(packet));
- }
-
- @Test
- public void testParseICMPv6RS() {
- final String packet =
- // Ethernet
- "333300000002 807ABF6F48F3 86DD" +
- // IPv6
- "600000000010 3A FF" +
- "FE80000000000000827ABFFFFE6F48F3" +
- "FF020000000000000000000000000002" +
- // ICMPv6 RS
- "85 00 6973" +
- "00000000" +
- "01 01 807ABF6F48F3";
-
- final String expected =
- "TX 80:7a:bf:6f:48:f3 > 33:33:00:00:00:02 ipv6" +
- " fe80::827a:bfff:fe6f:48f3 > ff02::2 icmp6" +
- " rs slla 80:7a:bf:6f:48:f3";
-
- assertEquals(expected, getSummary(packet));
- }
-
- @Test
- public void testParseICMPv6RA() {
- final String packet =
- // Ethernet
- "807ABF6F48F3 100E7E263FC1 86DD" +
- // IPv6
- "600000000068 3A FF" +
- "FE80000000000000FA000004FD000001" +
- "FE80000000000000827ABFFFFE6F48F3" +
- // ICMPv6 RA
- "86 00 8141" +
- "40 00 0E10" +
- "00000000" +
- "00000000" +
- "01 01 00005E000265" +
- "05 01 0000000005DC" +
- "19 05 000000000E10" +
- " 20014860486000000000000000008844" +
- " 20014860486000000000000000008888" +
- "03 04 40 C0" +
- " 00278D00" +
- " 00093A80" +
- " 00000000" +
- " 2401FA000004FD000000000000000000";
-
- final String expected =
- "RX 10:0e:7e:26:3f:c1 > 80:7a:bf:6f:48:f3 ipv6" +
- " fe80::fa00:4:fd00:1 > fe80::827a:bfff:fe6f:48f3 icmp6" +
- " ra slla 00:00:5e:00:02:65 mtu 1500";
-
- assertEquals(expected, getSummary(packet));
- }
-
- @Test
- public void testParseICMPv6NS() {
- final String packet =
- // Ethernet
- "807ABF6F48F3 100E7E263FC1 86DD" +
- // IPv6
- "6C0000000020 3A FF" +
- "FE80000000000000FA000004FD000001" +
- "FF0200000000000000000001FF01C146" +
- // ICMPv6 NS
- "87 00 8AD4" +
- "00000000" +
- "2401FA000004FD0015EA6A5C7B01C146" +
- "01 01 00005E000265";
-
- final String expected =
- "RX 10:0e:7e:26:3f:c1 > 80:7a:bf:6f:48:f3 ipv6" +
- " fe80::fa00:4:fd00:1 > ff02::1:ff01:c146 icmp6" +
- " ns 2401:fa00:4:fd00:15ea:6a5c:7b01:c146 slla 00:00:5e:00:02:65";
-
- assertEquals(expected, getSummary(packet));
- }
-
- @Test
- public void testInvalidICMPv6NDLength() {
- final String packet =
- // Ethernet
- "807ABF6F48F3 100E7E263FC1 86DD" +
- // IPv6
- "600000000068 3A FF" +
- "FE80000000000000FA000004FD000001" +
- "FE80000000000000827ABFFFFE6F48F3" +
- // ICMPv6 RA
- "86 00 8141" +
- "40 00 0E10" +
- "00000000" +
- "00000000" +
- "01 01 00005E000265" +
- "00 00 0102030405D6";
-
- final String expected =
- "RX 10:0e:7e:26:3f:c1 > 80:7a:bf:6f:48:f3 ipv6" +
- " fe80::fa00:4:fd00:1 > fe80::827a:bfff:fe6f:48f3 icmp6" +
- " ra slla 00:00:5e:00:02:65 <malformed>";
-
- assertEquals(expected, getSummary(packet));
- }
-
- @Test
- public void testParseICMPv6NA() {
- final String packet =
- // Ethernet
- "00005E000265 807ABF6F48F3 86DD" +
- "600000000020 3A FF" +
- "2401FA000004FD0015EA6A5C7B01C146" +
- "FE80000000000000FA000004FD000001" +
- "88 00 E8126" +
- "0000000" +
- "2401FA000004FD0015EA6A5C7B01C146" +
- "02 01 807ABF6F48F3";
-
- final String expected =
- "TX 80:7a:bf:6f:48:f3 > 00:00:5e:00:02:65 ipv6" +
- " 2401:fa00:4:fd00:15ea:6a5c:7b01:c146 > fe80::fa00:4:fd00:1 icmp6" +
- " na 2401:fa00:4:fd00:15ea:6a5c:7b01:c146 tlla 80:7a:bf:6f:48:f3";
-
- assertEquals(expected, getSummary(packet));
- }
-
- @Test
- public void testParseARPRequest() {
- final String packet =
- // Ethernet
- "FFFFFFFFFFFF 807ABF6F48F3 0806" +
- // ARP
- "0001 0800 06 04" +
- // Request
- "0001" +
- "807ABF6F48F3 64706ADB" +
- "000000000000 64706FFD";
-
- final String expected =
- "TX 80:7a:bf:6f:48:f3 > ff:ff:ff:ff:ff:ff arp" +
- " who-has 100.112.111.253";
-
- assertEquals(expected, getSummary(packet));
- }
-
- @Test
- public void testParseARPReply() {
- final String packet =
- // Ethernet
- "807ABF6F48F3 288A1CA8DFC1 0806" +
- // ARP
- "0001 0800 06 04" +
- // Reply
- "0002" +
- "288A1CA8DFC1 64706FFD"+
- "807ABF6F48F3 64706ADB" +
- // Ethernet padding to packet min size.
- "0000000000000000000000000000";
-
- final String expected =
- "RX 28:8a:1c:a8:df:c1 > 80:7a:bf:6f:48:f3 arp" +
- " reply 100.112.111.253 28:8a:1c:a8:df:c1";
-
- assertEquals(expected, getSummary(packet));
- }
-
- @Test
- public void testParseDHCPv4Discover() {
- final String packet =
- // Ethernet
- "FFFFFFFFFFFF 807ABF6F48F3 0800" +
- // IPv4
- "451001580000400040113986" +
- "00000000" +
- "FFFFFFFF" +
- // UDP
- "0044 0043" +
- "0144 5559" +
- // DHCPv4
- "01 01 06 00" +
- "79F7ACA4" +
- "0000 0000" +
- "00000000" +
- "00000000" +
- "00000000" +
- "00000000" +
- "807ABF6F48F300000000000000000000" +
- "0000000000000000000000000000000000000000000000000000000000000000" +
- "0000000000000000000000000000000000000000000000000000000000000000" +
- "0000000000000000000000000000000000000000000000000000000000000000" +
- "0000000000000000000000000000000000000000000000000000000000000000" +
- "0000000000000000000000000000000000000000000000000000000000000000" +
- "0000000000000000000000000000000000000000000000000000000000000000" +
- "63 82 53 63" +
- "35 01 01" +
- "3D 07 01807ABF6F48F3" +
- "39 02 05DC" +
- "3C 12 616E64726F69642D646863702D372E312E32" +
- "0C 18 616E64726F69642D36623030366333313333393835343139" +
- "37 0A 01 03 06 0F 1A 1C 33 3A 3B 2B" +
- "FF" +
- "00";
-
- final String expectedPrefix =
- "TX 80:7a:bf:6f:48:f3 > ff:ff:ff:ff:ff:ff ipv4" +
- " 0.0.0.0 > 255.255.255.255 udp" +
- " 68 > 67 dhcp4" +
- " 80:7a:bf:6f:48:f3 DISCOVER";
-
- assertTrue(getSummary(packet).startsWith(expectedPrefix));
- }
-
- @Test
- public void testParseDHCPv4Offer() {
- final String packet =
- // Ethernet
- "807ABF6F48F3 288A1CA8DFC1 0800" +
- // IPv4
- "4500013D4D2C0000401188CB" +
- "64706FFD" +
- "64706ADB" +
- // UDP
- "0043 0044" +
- "0129 371D" +
- // DHCPv4
- "02 01 06 01" +
- "79F7ACA4" +
- "0000 0000" +
- "00000000" +
- "64706ADB" +
- "00000000" +
- "00000000" +
- "807ABF6F48F300000000000000000000" +
- "0000000000000000000000000000000000000000000000000000000000000000" +
- "0000000000000000000000000000000000000000000000000000000000000000" +
- "0000000000000000000000000000000000000000000000000000000000000000" +
- "0000000000000000000000000000000000000000000000000000000000000000" +
- "0000000000000000000000000000000000000000000000000000000000000000" +
- "0000000000000000000000000000000000000000000000000000000000000000" +
- "63 82 53 63" +
- "35 01 02" +
- "36 04 AC188A0B" +
- "33 04 00000708" +
- "01 04 FFFFF000" +
- "03 04 64706FFE" +
- "06 08 08080808" +
- " 08080404" +
- "FF0001076165313A363636FF";
-
- final String expectedPrefix =
- "RX 28:8a:1c:a8:df:c1 > 80:7a:bf:6f:48:f3 ipv4" +
- " 100.112.111.253 > 100.112.106.219 udp" +
- " 67 > 68 dhcp4" +
- " 80:7a:bf:6f:48:f3 OFFER";
-
- assertTrue(getSummary(packet).startsWith(expectedPrefix));
- }
-
- @Test
- public void testParseDHCPv4Request() {
- final String packet =
- // Ethernet
- "FFFFFFFFFFFF 807ABF6F48F3 0800" +
- // IPv4
- "45100164000040004011397A" +
- "00000000" +
- "FFFFFFFF" +
- // UDP
- "0044 0043" +
- "0150 E5C7" +
- // DHCPv4
- "01 01 06 00" +
- "79F7ACA4" +
- "0001 0000" +
- "00000000" +
- "00000000" +
- "00000000" +
- "00000000" +
- "807ABF6F48F300000000000000000000" +
- "0000000000000000000000000000000000000000000000000000000000000000" +
- "0000000000000000000000000000000000000000000000000000000000000000" +
- "0000000000000000000000000000000000000000000000000000000000000000" +
- "0000000000000000000000000000000000000000000000000000000000000000" +
- "0000000000000000000000000000000000000000000000000000000000000000" +
- "0000000000000000000000000000000000000000000000000000000000000000" +
- "63 82 53 63" +
- "35 01 03" +
- "3D 07 01807ABF6F48F3" +
- "32 04 64706ADB" +
- "36 04 AC188A0B" +
- "39 02 05DC" +
- "3C 12 616E64726F69642D646863702D372E312E32" +
- "0C 18 616E64726F69642D36623030366333313333393835343139" +
- "37 0A 01 03 06 0F 1A 1C 33 3A 3B 2B" +
- "FF" +
- "00";
-
- final String expectedPrefix =
- "TX 80:7a:bf:6f:48:f3 > ff:ff:ff:ff:ff:ff ipv4" +
- " 0.0.0.0 > 255.255.255.255 udp" +
- " 68 > 67 dhcp4" +
- " 80:7a:bf:6f:48:f3 REQUEST";
-
- assertTrue(getSummary(packet).startsWith(expectedPrefix));
- }
-
- @Test
- public void testParseDHCPv4Ack() {
- final String packet =
- // Ethernet
- "807ABF6F48F3 288A1CA8DFC1 0800" +
- // IPv4
- "4500013D4D3B0000401188BC" +
- "64706FFD" +
- "64706ADB" +
- // UDP
- "0043 0044" +
- "0129 341C" +
- // DHCPv4
- "02 01 06 01" +
- "79F7ACA4" +
- "0001 0000" +
- "00000000" +
- "64706ADB" +
- "00000000" +
- "00000000" +
- "807ABF6F48F300000000000000000000" +
- "0000000000000000000000000000000000000000000000000000000000000000" +
- "0000000000000000000000000000000000000000000000000000000000000000" +
- "0000000000000000000000000000000000000000000000000000000000000000" +
- "0000000000000000000000000000000000000000000000000000000000000000" +
- "0000000000000000000000000000000000000000000000000000000000000000" +
- "0000000000000000000000000000000000000000000000000000000000000000" +
- "63 82 53 63" +
- "35 01 05" +
- "36 04 AC188A0B" +
- "33 04 00000708" +
- "01 04 FFFFF000" +
- "03 04 64706FFE" +
- "06 08 08080808" +
- " 08080404" +
- "FF0001076165313A363636FF";
-
- final String expectedPrefix =
- "RX 28:8a:1c:a8:df:c1 > 80:7a:bf:6f:48:f3 ipv4" +
- " 100.112.111.253 > 100.112.106.219 udp" +
- " 67 > 68 dhcp4" +
- " 80:7a:bf:6f:48:f3 ACK";
-
- assertTrue(getSummary(packet).startsWith(expectedPrefix));
- }
-}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 882babf..dda4481 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -1508,6 +1508,12 @@
verifyActiveNetwork(TRANSPORT_WIFI);
}
+ @Test
+ public void testRequiresValidation() {
+ assertTrue(NetworkMonitorUtils.isValidationRequired(
+ mCm.getDefaultRequest().networkCapabilities));
+ }
+
enum CallbackState {
NONE,
AVAILABLE,
@@ -4404,8 +4410,7 @@
mMockVpn.setUids(ranges);
// VPN networks do not satisfy the default request and are automatically validated
// by NetworkMonitor
- assertFalse(NetworkMonitorUtils.isValidationRequired(
- mCm.getDefaultRequest().networkCapabilities, vpnNetworkAgent.mNetworkCapabilities));
+ assertFalse(NetworkMonitorUtils.isValidationRequired(vpnNetworkAgent.mNetworkCapabilities));
vpnNetworkAgent.setNetworkValid();
vpnNetworkAgent.connect(false);
@@ -4775,7 +4780,7 @@
// Clat iface up, expect stack link updated.
clat.interfaceLinkStateChanged(CLAT_PREFIX + MOBILE_IFNAME, true);
- waitForIdle();
+ networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
List<LinkProperties> stackedLps = mCm.getLinkProperties(mCellNetworkAgent.getNetwork())
.getStackedLinks();
assertEquals(makeClatLinkProperties(myIpv4), stackedLps.get(0));
@@ -4783,7 +4788,6 @@
// Change trivial linkproperties and see if stacked link is preserved.
cellLp.addDnsServer(InetAddress.getByName("8.8.8.8"));
mCellNetworkAgent.sendLinkProperties(cellLp);
- waitForIdle();
networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
List<LinkProperties> stackedLpsAfterChange =
@@ -4795,19 +4799,19 @@
cellLp.addLinkAddress(myIpv4);
cellLp.addRoute(new RouteInfo(myIpv4, null, MOBILE_IFNAME));
mCellNetworkAgent.sendLinkProperties(cellLp);
- waitForIdle();
networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME);
// Clat iface removed, expect linkproperties revert to original one
clat.interfaceRemoved(CLAT_PREFIX + MOBILE_IFNAME);
- waitForIdle();
networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
LinkProperties actualLpAfterIpv4 = mCm.getLinkProperties(mCellNetworkAgent.getNetwork());
assertEquals(cellLp, actualLpAfterIpv4);
// Clean up
mCellNetworkAgent.disconnect();
+ networkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+ networkCallback.assertNoCallback();
mCm.unregisterNetworkCallback(networkCallback);
}
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index 8359fe2..1a0cb74 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -18,14 +18,15 @@
import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.Context;
import android.net.ConnectivityManager;
@@ -34,12 +35,11 @@
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
-import android.net.RouteInfo;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.net.RouteInfo;
import android.net.metrics.ApfProgramEvent;
import android.net.metrics.ApfStats;
-import android.net.metrics.DefaultNetworkEvent;
import android.net.metrics.DhcpClientEvent;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.IpManagerEvent;
@@ -55,6 +55,13 @@
import com.android.internal.util.BitUtils;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass;
+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.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collections;
@@ -62,13 +69,6 @@
import java.util.Iterator;
import java.util.List;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
@RunWith(AndroidJUnit4.class)
@SmallTest
public class IpConnectivityMetricsTest {
@@ -154,7 +154,7 @@
@Test
public void testRateLimiting() {
final IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
- final ApfProgramEvent ev = new ApfProgramEvent();
+ final ApfProgramEvent ev = new ApfProgramEvent.Builder().build();
final long fakeTimestamp = 1;
int attempt = 100; // More than burst quota, but less than buffer size.
@@ -304,26 +304,31 @@
when(mCm.getNetworkCapabilities(new Network(100))).thenReturn(ncWifi);
when(mCm.getNetworkCapabilities(new Network(101))).thenReturn(ncCell);
- ApfStats apfStats = new ApfStats();
- apfStats.durationMs = 45000;
- apfStats.receivedRas = 10;
- apfStats.matchingRas = 2;
- apfStats.droppedRas = 2;
- apfStats.parseErrors = 2;
- apfStats.zeroLifetimeRas = 1;
- apfStats.programUpdates = 4;
- apfStats.programUpdatesAll = 7;
- apfStats.programUpdatesAllowingMulticast = 3;
- apfStats.maxProgramSize = 2048;
+ ApfStats apfStats = new ApfStats.Builder()
+ .setDurationMs(45000)
+ .setReceivedRas(10)
+ .setMatchingRas(2)
+ .setDroppedRas(2)
+ .setParseErrors(2)
+ .setZeroLifetimeRas(1)
+ .setProgramUpdates(4)
+ .setProgramUpdatesAll(7)
+ .setProgramUpdatesAllowingMulticast(3)
+ .setMaxProgramSize(2048)
+ .build();
- ValidationProbeEvent validationEv = new ValidationProbeEvent();
- validationEv.durationMs = 40730;
- validationEv.probeType = ValidationProbeEvent.PROBE_HTTP;
- validationEv.returnCode = 204;
+ final ValidationProbeEvent validationEv = new ValidationProbeEvent.Builder()
+ .setDurationMs(40730)
+ .setProbeType(ValidationProbeEvent.PROBE_HTTP, true)
+ .setReturnCode(204)
+ .build();
+ final DhcpClientEvent event = new DhcpClientEvent.Builder()
+ .setMsg("SomeState")
+ .setDurationMs(192)
+ .build();
Parcelable[] events = {
- new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED),
- new DhcpClientEvent("SomeState", 192),
+ new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED), event,
new IpManagerEvent(IpManagerEvent.PROVISIONING_OK, 5678),
validationEv,
apfStats,
@@ -424,7 +429,7 @@
" validation_probe_event <",
" latency_ms: 40730",
" probe_result: 204",
- " probe_type: 1",
+ " probe_type: 257",
" >",
">",
"events <",
diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java
index 1ea83c2..b635607 100644
--- a/tests/net/java/com/android/server/connectivity/TetheringTest.java
+++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java
@@ -803,13 +803,14 @@
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
mLooper.dispatchAll();
- // We verify get/set called thrice here: once for setup and twice during
- // teardown because all events happen over the course of the single
+ // We verify get/set called thrice here: twice for setup (on NMService) and once during
+ // teardown (on Netd) because all events happen over the course of the single
// dispatchAll() above. Note that once the IpServer IPv4 address config
// code is refactored the two calls during shutdown will revert to one.
verify(mNMService, times(2)).getInterfaceConfig(TEST_WLAN_IFNAME);
- verify(mNMService, times(3))
+ verify(mNMService, times(2))
.setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class));
+ verify(mNetd, times(1)).interfaceSetCfg(argThat(p -> TEST_WLAN_IFNAME.equals(p.ifName)));
verify(mNMService, times(1)).tetherInterface(TEST_WLAN_IFNAME);
verify(mWifiManager).updateInterfaceIpState(
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_TETHERED);
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 9bf7587..0b74d87 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -57,7 +57,6 @@
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.net.ConnectivityManager;
-import android.net.IConnectivityManager;
import android.net.IpPrefix;
import android.net.LinkProperties;
import android.net.Network;
@@ -97,7 +96,6 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
-import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
@@ -240,6 +238,30 @@
}
@Test
+ public void testGetAlwaysAndOnGetLockDown() throws Exception {
+ final Vpn vpn = createVpn(primaryUser.id);
+
+ // Default state.
+ assertFalse(vpn.getAlwaysOn());
+ assertFalse(vpn.getLockdown());
+
+ // Set always-on without lockdown.
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false));
+ assertTrue(vpn.getAlwaysOn());
+ assertFalse(vpn.getLockdown());
+
+ // Set always-on with lockdown.
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true));
+ assertTrue(vpn.getAlwaysOn());
+ assertTrue(vpn.getLockdown());
+
+ // Remove always-on configuration.
+ assertTrue(vpn.setAlwaysOnPackage(null, false));
+ assertFalse(vpn.getAlwaysOn());
+ assertFalse(vpn.getLockdown());
+ }
+
+ @Test
public void testLockdownChangingPackage() throws Exception {
final Vpn vpn = createVpn(primaryUser.id);
final UidRange user = UidRange.createForUser(primaryUser.id);
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
index e63c3b0..f2ecef9 100644
--- a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -16,13 +16,34 @@
package com.android.server.net.ipmemorystore;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import android.content.Context;
+import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.IOnBlobRetrievedListener;
+import android.net.ipmemorystore.IOnL2KeyResponseListener;
+import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
+import android.net.ipmemorystore.IOnSameNetworkResponseListener;
+import android.net.ipmemorystore.IOnStatusListener;
+import android.net.ipmemorystore.NetworkAttributes;
+import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.net.ipmemorystore.SameL3NetworkResponse;
+import android.net.ipmemorystore.SameL3NetworkResponseParcelable;
+import android.net.ipmemorystore.Status;
+import android.net.ipmemorystore.StatusParcelable;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -30,41 +51,443 @@
import org.mockito.MockitoAnnotations;
import java.io.File;
+import java.lang.reflect.Modifier;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
-/** Unit tests for {@link IpMemoryStoreServiceTest}. */
+/** Unit tests for {@link IpMemoryStoreService}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class IpMemoryStoreServiceTest {
+ private static final String TEST_CLIENT_ID = "testClientId";
+ private static final String TEST_DATA_NAME = "testData";
+
+ private static final int FAKE_KEY_COUNT = 20;
+ private static final String[] FAKE_KEYS;
+ static {
+ FAKE_KEYS = new String[FAKE_KEY_COUNT];
+ for (int i = 0; i < FAKE_KEYS.length; ++i) {
+ FAKE_KEYS[i] = "fakeKey" + i;
+ }
+ }
+
@Mock
- Context mMockContext;
+ private Context mMockContext;
+ private File mDbFile;
+
+ private IpMemoryStoreService mService;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- doReturn(new File("/tmp/test.db")).when(mMockContext).getDatabasePath(anyString());
+ final Context context = InstrumentationRegistry.getContext();
+ final File dir = context.getFilesDir();
+ mDbFile = new File(dir, "test.db");
+ doReturn(mDbFile).when(mMockContext).getDatabasePath(anyString());
+ mService = new IpMemoryStoreService(mMockContext);
+ }
+
+ @After
+ public void tearDown() {
+ mService.shutdown();
+ mDbFile.delete();
+ }
+
+ /** Helper method to make a vanilla IOnStatusListener */
+ private IOnStatusListener onStatus(Consumer<Status> functor) {
+ return new IOnStatusListener() {
+ @Override
+ public void onComplete(final StatusParcelable statusParcelable) throws RemoteException {
+ functor.accept(new Status(statusParcelable));
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+ };
+ }
+
+ /** Helper method to make an IOnBlobRetrievedListener */
+ private interface OnBlobRetrievedListener {
+ void onBlobRetrieved(Status status, String l2Key, String name, byte[] data);
+ }
+ private IOnBlobRetrievedListener onBlobRetrieved(final OnBlobRetrievedListener functor) {
+ return new IOnBlobRetrievedListener() {
+ @Override
+ public void onBlobRetrieved(final StatusParcelable statusParcelable,
+ final String l2Key, final String name, final Blob blob) throws RemoteException {
+ functor.onBlobRetrieved(new Status(statusParcelable), l2Key, name,
+ null == blob ? null : blob.data);
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+ };
+ }
+
+ /** Helper method to make an IOnNetworkAttributesRetrievedListener */
+ private interface OnNetworkAttributesRetrievedListener {
+ void onNetworkAttributesRetrieved(Status status, String l2Key, NetworkAttributes attr);
+ }
+ private IOnNetworkAttributesRetrieved onNetworkAttributesRetrieved(
+ final OnNetworkAttributesRetrievedListener functor) {
+ return new IOnNetworkAttributesRetrieved() {
+ @Override
+ public void onNetworkAttributesRetrieved(final StatusParcelable status,
+ final String l2Key, final NetworkAttributesParcelable attributes)
+ throws RemoteException {
+ functor.onNetworkAttributesRetrieved(new Status(status), l2Key,
+ null == attributes ? null : new NetworkAttributes(attributes));
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+ };
+ }
+
+ /** Helper method to make an IOnSameNetworkResponseListener */
+ private interface OnSameNetworkResponseListener {
+ void onSameNetworkResponse(Status status, SameL3NetworkResponse answer);
+ }
+ private IOnSameNetworkResponseListener onSameResponse(
+ final OnSameNetworkResponseListener functor) {
+ return new IOnSameNetworkResponseListener() {
+ @Override
+ public void onSameNetworkResponse(final StatusParcelable status,
+ final SameL3NetworkResponseParcelable sameL3Network)
+ throws RemoteException {
+ functor.onSameNetworkResponse(new Status(status),
+ null == sameL3Network ? null : new SameL3NetworkResponse(sameL3Network));
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+ };
+ }
+
+ /** Helper method to make an IOnL2KeyResponseListener */
+ private interface OnL2KeyResponseListener {
+ void onL2KeyResponse(Status status, String key);
+ }
+ private IOnL2KeyResponseListener onL2KeyResponse(final OnL2KeyResponseListener functor) {
+ return new IOnL2KeyResponseListener() {
+ @Override
+ public void onL2KeyResponse(final StatusParcelable status, final String key)
+ throws RemoteException {
+ functor.onL2KeyResponse(new Status(status), key);
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+ };
+ }
+
+ // Helper method to factorize some boilerplate
+ private void doLatched(final String timeoutMessage, final Consumer<CountDownLatch> functor) {
+ final CountDownLatch latch = new CountDownLatch(1);
+ functor.accept(latch);
+ try {
+ latch.await(5000, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ fail(timeoutMessage);
+ }
+ }
+
+ // Helper methods to factorize more boilerplate
+ private void storeAttributes(final String l2Key, final NetworkAttributes na) {
+ storeAttributes("Did not complete storing attributes", l2Key, na);
+ }
+ private void storeAttributes(final String timeoutMessage, final String l2Key,
+ final NetworkAttributes na) {
+ doLatched(timeoutMessage, latch -> mService.storeNetworkAttributes(l2Key, na.toParcelable(),
+ onStatus(status -> {
+ assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
+ latch.countDown();
+ })));
}
@Test
- public void testNetworkAttributes() {
- final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
- // TODO : implement this
+ public void testNetworkAttributes() throws UnknownHostException {
+ final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
+ na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
+ na.setGroupHint("hint1");
+ na.setMtu(219);
+ final String l2Key = FAKE_KEYS[0];
+ NetworkAttributes attributes = na.build();
+ storeAttributes(l2Key, attributes);
+
+ doLatched("Did not complete retrieving attributes", latch ->
+ mService.retrieveNetworkAttributes(l2Key, onNetworkAttributesRetrieved(
+ (status, key, attr) -> {
+ assertTrue("Retrieve network attributes not successful : "
+ + status.resultCode, status.isSuccess());
+ assertEquals(l2Key, key);
+ assertEquals(attributes, attr);
+ latch.countDown();
+ })));
+
+ final NetworkAttributes.Builder na2 = new NetworkAttributes.Builder();
+ na.setDnsAddresses(Arrays.asList(
+ new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")}));
+ final NetworkAttributes attributes2 = na2.build();
+ storeAttributes("Did not complete storing attributes 2", l2Key, attributes2);
+
+ doLatched("Did not complete retrieving attributes 2", latch ->
+ mService.retrieveNetworkAttributes(l2Key, onNetworkAttributesRetrieved(
+ (status, key, attr) -> {
+ assertTrue("Retrieve network attributes not successful : "
+ + status.resultCode, status.isSuccess());
+ assertEquals(l2Key, key);
+ assertEquals(attributes.assignedV4Address, attr.assignedV4Address);
+ assertEquals(attributes.groupHint, attr.groupHint);
+ assertEquals(attributes.mtu, attr.mtu);
+ assertEquals(attributes2.dnsAddresses, attr.dnsAddresses);
+ latch.countDown();
+ })));
+
+ doLatched("Did not complete retrieving attributes 3", latch ->
+ mService.retrieveNetworkAttributes(l2Key + "nonexistent",
+ onNetworkAttributesRetrieved(
+ (status, key, attr) -> {
+ assertTrue("Retrieve network attributes not successful : "
+ + status.resultCode, status.isSuccess());
+ assertEquals(l2Key + "nonexistent", key);
+ assertNull("Retrieved data not stored", attr);
+ latch.countDown();
+ }
+ )));
+
+ // Verify that this test does not miss any new field added later.
+ // If any field is added to NetworkAttributes it must be tested here for storing
+ // and retrieving.
+ assertEquals(4, Arrays.stream(NetworkAttributes.class.getDeclaredFields())
+ .filter(f -> !Modifier.isStatic(f.getModifiers())).count());
+ }
+
+ @Test
+ public void testInvalidAttributes() {
+ doLatched("Did not complete storing bad attributes", latch ->
+ mService.storeNetworkAttributes("key", null, onStatus(status -> {
+ assertFalse("Success storing on a null key",
+ status.isSuccess());
+ assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
+ latch.countDown();
+ })));
+
+ final NetworkAttributes na = new NetworkAttributes.Builder().setMtu(2).build();
+ doLatched("Did not complete storing bad attributes", latch ->
+ mService.storeNetworkAttributes(null, na.toParcelable(), onStatus(status -> {
+ assertFalse("Success storing null attributes on a null key",
+ status.isSuccess());
+ assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
+ latch.countDown();
+ })));
+
+ doLatched("Did not complete storing bad attributes", latch ->
+ mService.storeNetworkAttributes(null, null, onStatus(status -> {
+ assertFalse("Success storing null attributes on a null key",
+ status.isSuccess());
+ assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
+ latch.countDown();
+ })));
+
+ doLatched("Did not complete retrieving bad attributes", latch ->
+ mService.retrieveNetworkAttributes(null, onNetworkAttributesRetrieved(
+ (status, key, attr) -> {
+ assertFalse("Success retrieving attributes for a null key",
+ status.isSuccess());
+ assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
+ assertNull(key);
+ assertNull(attr);
+ })));
}
@Test
public void testPrivateData() {
- final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
- // TODO : implement this
+ final Blob b = new Blob();
+ b.data = new byte[] { -3, 6, 8, -9, 12, -128, 0, 89, 112, 91, -34 };
+ final String l2Key = FAKE_KEYS[0];
+ doLatched("Did not complete storing private data", latch ->
+ mService.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
+ onStatus(status -> {
+ assertTrue("Store status not successful : " + status.resultCode,
+ status.isSuccess());
+ latch.countDown();
+ })));
+
+ doLatched("Did not complete retrieving private data", latch ->
+ mService.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, onBlobRetrieved(
+ (status, key, name, data) -> {
+ assertTrue("Retrieve blob status not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(l2Key, key);
+ assertEquals(name, TEST_DATA_NAME);
+ Arrays.equals(b.data, data);
+ latch.countDown();
+ })));
+
+ // Most puzzling error message ever
+ doLatched("Did not complete retrieving nothing", latch ->
+ mService.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME + "2", onBlobRetrieved(
+ (status, key, name, data) -> {
+ assertTrue("Retrieve blob status not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(l2Key, key);
+ assertEquals(name, TEST_DATA_NAME + "2");
+ assertNull(data);
+ latch.countDown();
+ })));
}
@Test
- public void testFindL2Key() {
- final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
- // TODO : implement this
+ public void testFindL2Key() throws UnknownHostException {
+ final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
+ na.setGroupHint("hint0");
+ storeAttributes(FAKE_KEYS[0], na.build());
+
+ na.setDnsAddresses(Arrays.asList(
+ new InetAddress[] {Inet6Address.getByName("8D56:9AF1::08EE:20F1")}));
+ na.setMtu(219);
+ storeAttributes(FAKE_KEYS[1], na.build());
+ na.setMtu(null);
+ na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
+ na.setDnsAddresses(Arrays.asList(
+ new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")}));
+ na.setGroupHint("hint1");
+ storeAttributes(FAKE_KEYS[2], na.build());
+ na.setMtu(219);
+ storeAttributes(FAKE_KEYS[3], na.build());
+ na.setMtu(240);
+ storeAttributes(FAKE_KEYS[4], na.build());
+ na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("5.6.7.8"));
+ storeAttributes(FAKE_KEYS[5], na.build());
+
+ // Matches key 5 exactly
+ doLatched("Did not finish finding L2Key", latch ->
+ mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+ assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(FAKE_KEYS[5], key);
+ })));
+
+ // MTU matches key 4 but v4 address matches key 5. The latter is stronger.
+ na.setMtu(240);
+ doLatched("Did not finish finding L2Key", latch ->
+ mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+ assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(FAKE_KEYS[5], key);
+ })));
+
+ // Closest to key 3 (indeed, identical)
+ na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
+ na.setMtu(219);
+ doLatched("Did not finish finding L2Key", latch ->
+ mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+ assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(FAKE_KEYS[3], key);
+ })));
+
+ // Group hint alone must not be strong enough to override the rest
+ na.setGroupHint("hint0");
+ doLatched("Did not finish finding L2Key", latch ->
+ mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+ assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(FAKE_KEYS[3], key);
+ })));
+
+ // Still closest to key 3, though confidence is lower
+ na.setGroupHint("hint1");
+ na.setDnsAddresses(null);
+ doLatched("Did not finish finding L2Key", latch ->
+ mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+ assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(FAKE_KEYS[3], key);
+ })));
+
+ // But changing the MTU makes this closer to key 4
+ na.setMtu(240);
+ doLatched("Did not finish finding L2Key", latch ->
+ mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+ assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(FAKE_KEYS[4], key);
+ })));
+
+ // MTU alone not strong enough to make this group-close
+ na.setGroupHint(null);
+ na.setDnsAddresses(null);
+ na.setAssignedV4Address(null);
+ doLatched("Did not finish finding L2Key", latch ->
+ mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+ assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+ status.isSuccess());
+ assertNull(key);
+ })));
+ }
+
+ private void assertNetworksSameness(final String key1, final String key2, final int sameness) {
+ doLatched("Did not finish evaluating sameness", latch ->
+ mService.isSameNetwork(key1, key2, onSameResponse((status, answer) -> {
+ assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(sameness, answer.getNetworkSameness());
+ })));
}
@Test
- public void testIsSameNetwork() {
- final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
- // TODO : implement this
+ public void testIsSameNetwork() throws UnknownHostException {
+ final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
+ na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
+ na.setGroupHint("hint1");
+ na.setMtu(219);
+ na.setDnsAddresses(Arrays.asList(Inet6Address.getByName("0A1C:2E40:480A::1CA6")));
+
+ storeAttributes(FAKE_KEYS[0], na.build());
+ // 0 and 1 have identical attributes
+ storeAttributes(FAKE_KEYS[1], na.build());
+
+ // Hopefully only the MTU being different still means it's the same network
+ na.setMtu(200);
+ storeAttributes(FAKE_KEYS[2], na.build());
+
+ // Hopefully different MTU, assigned V4 address and grouphint make a different network,
+ // even with identical DNS addresses
+ na.setAssignedV4Address(null);
+ na.setGroupHint("hint2");
+ storeAttributes(FAKE_KEYS[3], na.build());
+
+ assertNetworksSameness(FAKE_KEYS[0], FAKE_KEYS[1], SameL3NetworkResponse.NETWORK_SAME);
+ assertNetworksSameness(FAKE_KEYS[0], FAKE_KEYS[2], SameL3NetworkResponse.NETWORK_SAME);
+ assertNetworksSameness(FAKE_KEYS[1], FAKE_KEYS[2], SameL3NetworkResponse.NETWORK_SAME);
+ assertNetworksSameness(FAKE_KEYS[0], FAKE_KEYS[3], SameL3NetworkResponse.NETWORK_DIFFERENT);
+ assertNetworksSameness(FAKE_KEYS[0], "neverInsertedKey",
+ SameL3NetworkResponse.NETWORK_NEVER_CONNECTED);
+
+ doLatched("Did not finish evaluating sameness", latch ->
+ mService.isSameNetwork(null, null, onSameResponse((status, answer) -> {
+ assertFalse("Retrieve network sameness suspiciously successful : "
+ + status.resultCode, status.isSuccess());
+ assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
+ assertNull(answer);
+ })));
}
}
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
new file mode 100644
index 0000000..fe19eee
--- /dev/null
+++ b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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 com.android.server.net.ipmemorystore;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.ipmemorystore.NetworkAttributes;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+import java.net.Inet4Address;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+
+/** Unit tests for {@link NetworkAttributes}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NetworkAttributesTest {
+ private static final String WEIGHT_FIELD_NAME_PREFIX = "WEIGHT_";
+ private static final float EPSILON = 0.0001f;
+
+ // This is running two tests to make sure the total weight is the sum of all weights. To be
+ // sure this is not fireproof, but you'd kind of need to do it on purpose to pass.
+ @Test
+ public void testTotalWeight() throws IllegalAccessException, UnknownHostException {
+ // Make sure that TOTAL_WEIGHT is equal to the sum of the fields starting with WEIGHT_
+ float sum = 0f;
+ final Field[] fieldList = NetworkAttributes.class.getDeclaredFields();
+ for (final Field field : fieldList) {
+ if (!field.getName().startsWith(WEIGHT_FIELD_NAME_PREFIX)) continue;
+ field.setAccessible(true);
+ sum += (float) field.get(null);
+ }
+ assertEquals(sum, NetworkAttributes.TOTAL_WEIGHT, EPSILON);
+
+ // Use directly the constructor with all attributes, and make sure that when compared
+ // to itself the score is a clean 1.0f.
+ final NetworkAttributes na =
+ new NetworkAttributes(
+ (Inet4Address) Inet4Address.getByAddress(new byte[] {1, 2, 3, 4}),
+ "some hint",
+ Arrays.asList(Inet4Address.getByAddress(new byte[] {5, 6, 7, 8}),
+ Inet4Address.getByAddress(new byte[] {9, 0, 1, 2})),
+ 98);
+ assertEquals(1.0f, na.getNetworkGroupSamenessConfidence(na), EPSILON);
+ }
+}
diff --git a/tests/utils/SleepUtils/AlarmService/Android.mk b/tests/utils/SleepUtils/AlarmService/Android.mk
deleted file mode 100644
index 9022f03..0000000
--- a/tests/utils/SleepUtils/AlarmService/Android.mk
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# Copyright (C) 2013 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_SRC_FILES += \
- src/com/android/testing/alarmservice/Alarm.aidl
-LOCAL_PACKAGE_NAME := SleepUtilsAlarmService
-LOCAL_SDK_VERSION := 7
-include $(BUILD_PACKAGE)
diff --git a/tests/utils/SleepUtils/AlarmService/AndroidManifest.xml b/tests/utils/SleepUtils/AlarmService/AndroidManifest.xml
deleted file mode 100644
index 1b6de39..0000000
--- a/tests/utils/SleepUtils/AlarmService/AndroidManifest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project Licensed under the
- Apache License, Version 2.0 (the "License"); you may not use this file except
- in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software distributed
- under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
- OR CONDITIONS OF ANY KIND, either express or implied. See the License for
- the specific language governing permissions and limitations under the License. -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.testing.alarmservice" >
-
- <uses-sdk android:minSdkVersion="7" />
- <uses-permission android:name="android.permission.WAKE_LOCK" />
-
- <application android:label="Sleep Utils Alarm Service">
- <service android:name=".AlarmService"
- android:label="Sleep Utils Alarm Service"
- android:exported="true"
- android:enabled="true">
- <intent-filter>
- <action android:name="com.android.testing.ALARM_SERVICE" />
- </intent-filter>
- </service>
- <receiver android:name=".WakeUpCall">
- <intent-filter>
- <action android:name="com.android.testing.alarmservice.WAKEUP" />
- </intent-filter>
- </receiver>
- </application>
-</manifest>
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
deleted file mode 100644
index 62a8c48..0000000
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2013 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.testing.alarmservice;
-
-interface Alarm {
- int prepare();
- int setAlarmAndWait(long timeoutMills);
- int done();
-}
\ No newline at end of file
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmImpl.java b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmImpl.java
deleted file mode 100644
index 122d55d..0000000
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmImpl.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2013 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.testing.alarmservice;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.util.Log;
-
-import com.android.testing.alarmservice.Alarm.Stub;
-
-public class AlarmImpl extends Stub {
-
- private static final String LOG_TAG = AlarmImpl.class.getSimpleName();
-
- private Context mContext;
-
- public AlarmImpl(Context context) {
- super();
- mContext = context;
- }
-
- @Override
- public int prepare() throws RemoteException {
- WakeUpController.getController().getWakeLock().acquire();
- Log.d(LOG_TAG, "AlarmService prepared, wake lock acquired");
- return 0;
- }
-
- @Override
- public int setAlarmAndWait(long timeoutMills) throws RemoteException {
- // calculate when device should be waken up
- long atTime = SystemClock.elapsedRealtime() + timeoutMills;
- AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- Intent wakupIntent = new Intent(WakeUpCall.WAKEUP_CALL);
- PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, wakupIntent, 0);
- // set alarm, which will be delivered in form of the wakeupIntent
- am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, atTime, pi);
- Log.d(LOG_TAG, String.format("Alarm set: %d, giving up wake lock", atTime));
- Object lock = WakeUpController.getController().getWakeSync();
- // release wakelock and wait for the lock to be poked from the broadcast receiver
- WakeUpController.getController().getWakeLock().release();
- // does not really matter if device enters suspend before we start waiting on lock
- synchronized (lock) {
- try {
- lock.wait();
- } catch (InterruptedException e) {
- }
- }
- Log.d(LOG_TAG, String.format("Alarm triggered, done waiting"));
- return 0;
- }
-
- @Override
- public int done() throws RemoteException {
- WakeUpController.getController().getWakeLock().release();
- return 0;
- }
-
-}
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmService.java b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmService.java
deleted file mode 100644
index 576a1cf..0000000
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmService.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2013 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.testing.alarmservice;
-
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.os.IBinder;
-
-public class AlarmService extends Service {
-
- private AlarmImpl mAlarmImpl = null;
- static Context sContext;
-
- @Override
- public void onCreate() {
- super.onCreate();
- sContext = this;
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return getAlarmImpl();
- }
-
- private AlarmImpl getAlarmImpl() {
- if (mAlarmImpl == null) {
- mAlarmImpl = new AlarmImpl(this);
- }
- return mAlarmImpl;
- }
-
- @Override
- public void onDestroy() {
- sContext = null;
- super.onDestroy();
- }
-}
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpCall.java b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpCall.java
deleted file mode 100644
index f4bb4db..0000000
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpCall.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2013 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.testing.alarmservice;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-/**
- * The receiver for the alarm we set
- *
- */
-public class WakeUpCall extends BroadcastReceiver {
-
- public static final String WAKEUP_CALL = "com.android.testing.alarmservice.WAKEUP";
-
- @Override
- public void onReceive(Context context, Intent intent) {
- // we acquire wakelock without release because user is supposed to manually release it
- WakeUpController.getController().getWakeLock().acquire();
- Object lock = WakeUpController.getController().getWakeSync();
- synchronized (lock) {
- // poke the lock so the service side can be woken from waiting on the lock
- lock.notifyAll();
- }
- }
-
-}
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpController.java b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpController.java
deleted file mode 100644
index 478371f..0000000
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpController.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2013 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.testing.alarmservice;
-
-import android.content.Context;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.util.Log;
-
-/**
- * A singleton used for controlling and sharing of states/wakelocks
- *
- */
-public class WakeUpController {
-
- private static final String LOG_TAG = WakeUpController.class.getName();
- private static WakeUpController mController = null;
- private WakeLock mWakeLock = null;
- private Object mWakeSync = new Object();
-
- private WakeUpController() {
- Log.i(LOG_TAG, "Created instance: 0x" + Integer.toHexString(this.hashCode()));
- }
-
- public static synchronized WakeUpController getController() {
- if (mController == null) {
- mController = new WakeUpController();
- }
- return mController;
- }
-
- public WakeLock getWakeLock() {
- if (mWakeLock == null) {
- PowerManager pm =
- (PowerManager) AlarmService.sContext.getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "testing-alarmservice");
- Log.i(LOG_TAG, "Create wakelock: 0x" + Integer.toHexString(mWakeLock.hashCode()));
- }
- return mWakeLock;
- }
-
- public Object getWakeSync() {
- return mWakeSync;
- }
-}
diff --git a/tests/utils/SleepUtils/Android.mk b/tests/utils/SleepUtils/Android.mk
deleted file mode 100644
index 0e65e22..0000000
--- a/tests/utils/SleepUtils/Android.mk
+++ /dev/null
@@ -1,2 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/tests/utils/SleepUtils/README b/tests/utils/SleepUtils/README
deleted file mode 100644
index bfe07da..0000000
--- a/tests/utils/SleepUtils/README
+++ /dev/null
@@ -1,23 +0,0 @@
-This folder contains utils to properly perform timed suspend and wakeup.
-
-AlarmService - a service that client can bind to and perform:
-1) holding wakelock (singleton to this service)
-2) setting alarm for a specified period and releasing the wakelock; service
- call will block until alarm has been triggered and the wakelock is held
-3) releasing the wakelock
-
-SleepHelper - a self instrumentation meant as a convenient way to trigger
-the service functions from command line. Corresponding to service function
-above, supported operations are:
-1) holding wakelock
-am instrument -w -e command prepare \
- com.android.testing.sleephelper/.SetAlarm
-
-2) setting alarm and wait til triggered
-am instrument -w -e command set_wait \
- -e param <time in ms> com.android.testing.sleephelper/.SetAlarm
-Note: for the function to work properly, "-w" parameter is required
-
-3) releasing wakelock
-am instrument -w -e command done \
- com.android.testing.sleephelper/.SetAlarm
diff --git a/tests/utils/SleepUtils/SleepHelper/Android.mk b/tests/utils/SleepUtils/SleepHelper/Android.mk
deleted file mode 100644
index f8267fd..0000000
--- a/tests/utils/SleepUtils/SleepHelper/Android.mk
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# Copyright (C) 2013 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-# Only compile source java files in this apk.
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_SRC_FILES += \
- ../AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
-LOCAL_SDK_VERSION := 7
-LOCAL_PACKAGE_NAME := SleepUtilsSleepHelper
-
-include $(BUILD_PACKAGE)
diff --git a/tests/utils/SleepUtils/SleepHelper/AndroidManifest.xml b/tests/utils/SleepUtils/SleepHelper/AndroidManifest.xml
deleted file mode 100644
index 0f1d491..0000000
--- a/tests/utils/SleepUtils/SleepHelper/AndroidManifest.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project Licensed under the
- Apache License, Version 2.0 (the "License"); you may not use this file except
- in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software distributed
- under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
- OR CONDITIONS OF ANY KIND, either express or implied. See the License for
- the specific language governing permissions and limitations under the License. -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.testing.sleephelper">
-
- <uses-sdk android:minSdkVersion="7" />
- <instrumentation android:label="Sleep Helper"
- android:name="com.android.testing.sleephelper.SetAlarm"
- android:targetPackage="com.android.testing.sleephelper" />
-
- <application android:label="Sleep Utils Sleep Helper">
- <uses-library android:name="android.test.runner" />
- </application>
-</manifest>
diff --git a/tests/utils/SleepUtils/SleepHelper/src/com/android/testing/sleephelper/SetAlarm.java b/tests/utils/SleepUtils/SleepHelper/src/com/android/testing/sleephelper/SetAlarm.java
deleted file mode 100644
index b558741..0000000
--- a/tests/utils/SleepUtils/SleepHelper/src/com/android/testing/sleephelper/SetAlarm.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2013 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.testing.sleephelper;
-
-import android.app.Activity;
-import android.app.Instrumentation;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Bundle;
-import android.os.Debug;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.testing.alarmservice.Alarm;
-
-public class SetAlarm extends Instrumentation {
-
- private static final String COMMAND = "command";
- private static final String PARAM = "param";
- private static final String CMD_PREPARE = "prepare";
- private static final String CMD_SET = "set_wait";
- private static final String CMD_DONE = "done";
- private static final String SERVICE_ACTION = "com.android.testing.ALARM_SERVICE";
- private static final String SERVICE_PKG = "com.android.testing.alarmservice";
- private static final String LOG_TAG = SetAlarm.class.getSimpleName();
-
- private Alarm mAlarmService = null;
- private Bundle mArgs = null;
- private String mCommand = null;
- private Intent mServceIntent = new Intent(SERVICE_ACTION).setPackage(SERVICE_PKG);
-
- private ServiceConnection mConn = new ServiceConnection() {
- @Override
- public void onServiceDisconnected(ComponentName name) {
- Log.d(LOG_TAG, "Service disconnected.");
- mAlarmService = null;
- errorFinish("service disconnected");
- }
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- Log.d(LOG_TAG, "Service connected.");
- mAlarmService = Alarm.Stub.asInterface(service);
- handleCommands();
- }
- };
-
-
- private void handleCommands() {
- if (CMD_PREPARE.equals(mCommand)) {
- callPrepare();
- } else if (CMD_SET.equals(mCommand)) {
- String paramString = mArgs.getString(PARAM);
- if (paramString == null) {
- errorFinish("argument expected for alarm time");
- }
- long timeout = -1;
- try {
- timeout = Long.parseLong(paramString);
- } catch (NumberFormatException nfe) {
- errorFinish("a number argument is expected");
- }
- callSetAndWait(timeout);
- } else if (CMD_DONE.equals(mCommand)) {
- callDone();
- } else {
- errorFinish("Unrecognized command: " + mCommand);
- }
- finish(Activity.RESULT_OK, new Bundle());
- }
-
- @Override
- public void onCreate(Bundle arguments) {
- super.onCreate(arguments);
- mCommand = arguments.getString(COMMAND);
- if ("true".equals(arguments.getString("debug"))) {
- Debug.waitForDebugger();
- }
- if (mCommand == null) {
- errorFinish("No command specified");
- }
- mArgs = arguments;
- connectToAlarmService();
- }
-
- private void errorFinish(String msg) {
- Bundle ret = new Bundle();
- ret.putString("error", msg);
- finish(Activity.RESULT_CANCELED, ret);
- }
-
- private void connectToAlarmService() {
- // start the service with an intent, this ensures the service keeps running after unbind
- ComponentName cn = getContext().startService(mServceIntent);
- if (cn == null) {
- errorFinish("failed to start service");
- }
- if (!getContext().bindService(mServceIntent, mConn, Context.BIND_AUTO_CREATE)) {
- errorFinish("failed to bind service");
- }
- }
-
- private void callPrepare() {
- try {
- mAlarmService.prepare();
- } catch (RemoteException e) {
- errorFinish("RemoteExeption in prepare()");
- } finally {
- getContext().unbindService(mConn);
- }
- }
-
- private void callDone() {
- try {
- mAlarmService.done();
- } catch (RemoteException e) {
- errorFinish("RemoteExeption in prepare()");
- } finally {
- getContext().unbindService(mConn);
- }
- // explicitly stop the service (started in prepare()) so that the service is now free
- // to be reclaimed
- getContext().stopService(mServceIntent);
- }
-
- private void callSetAndWait(long timeoutMills) {
- try {
- mAlarmService.setAlarmAndWait(timeoutMills);
- } catch (RemoteException e) {
- errorFinish("RemoteExeption in setAlarmAndWait()");
- } finally {
- getContext().unbindService(mConn);
- }
- }
-}
diff --git a/tests/utils/SleepUtils/WakeLoopService/Android.mk b/tests/utils/SleepUtils/WakeLoopService/Android.mk
deleted file mode 100644
index a8a944b..0000000
--- a/tests/utils/SleepUtils/WakeLoopService/Android.mk
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Copyright (C) 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_PACKAGE_NAME := WakeupLoopService
-LOCAL_SDK_VERSION := 7
-include $(BUILD_PACKAGE)
diff --git a/tests/utils/SleepUtils/WakeLoopService/AndroidManifest.xml b/tests/utils/SleepUtils/WakeLoopService/AndroidManifest.xml
deleted file mode 100644
index a7028c4..0000000
--- a/tests/utils/SleepUtils/WakeLoopService/AndroidManifest.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project Licensed under the
- Apache License, Version 2.0 (the "License"); you may not use this file except
- in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software distributed
- under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
- OR CONDITIONS OF ANY KIND, either express or implied. See the License for
- the specific language governing permissions and limitations under the License. -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.test.wakeuploop" >
-
- <uses-sdk android:minSdkVersion="7" />
- <uses-permission android:name="android.permission.WAKE_LOCK" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
- <application android:label="Auto Wakeup Loop">
- <service android:name=".WakeLoopService"
- android:label="Wakup Loop Service"
- android:exported="true"
- android:enabled="true">
- <intent-filter>
- <action android:name="android.test.wakeuploop.WAKEUP_SERVICE" />
- </intent-filter>
- </service>
- <receiver android:name=".WakeUpCall">
- <intent-filter>
- <action android:name="android.test.wakeuploop.WAKEUP" />
- </intent-filter>
- </receiver>
- </application>
-</manifest>
diff --git a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/FileUtil.java b/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/FileUtil.java
deleted file mode 100644
index c8b075b..0000000
--- a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/FileUtil.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2014 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.test.wakeuploop;
-
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class FileUtil {
-
- private static FileUtil sInst = null;
- private static DateFormat sDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
-
- private FileUtil() {};
-
- public static FileUtil get() {
- if (sInst == null) {
- sInst = new FileUtil();
- }
- return sInst;
- }
-
- public void writeDateToFile(File file) {
- try {
- FileOutputStream fos = new FileOutputStream(file);
- fos.write(sDateFormat.format(new Date()).getBytes());
- fos.write('\n');
- fos.flush();
- fos.close();
- } catch (IOException ioe) {
- Log.e("FileUtil", "exception writing date to file", ioe);
- }
- }
-}
diff --git a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeLoopService.java b/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeLoopService.java
deleted file mode 100644
index 4f557b8..0000000
--- a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeLoopService.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2014 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.test.wakeuploop;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.SystemClock;
-import android.util.Log;
-
-import java.io.File;
-
-public class WakeLoopService extends Service {
-
- private static final String LOG_TAG = WakeLoopService.class.getSimpleName();
- static final String WAKEUP_INTERNAL = "WAKEUP_INTERVAL";
- static final String MAX_LOOP = "MAX_LOOP";
- static final String STOP_CALLBACK = "STOP_CALLBACK";
- static final String THIS_LOOP = "THIS_LOOP";
- static final int MSG_STOP_SERVICE = 0xd1ed1e;
-
- private final Handler mHandler = new Handler() {
- public void handleMessage(Message msg) {
- if (msg.what == MSG_STOP_SERVICE) {
- stopSelf();
- } else {
- super.handleMessage(msg);
- }
- };
- };
-
- @Override
- public IBinder onBind(Intent intent) {
- // no binding, just start via intent
- return null;
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- // get wakeup interval from intent
- long wakeupInterval = intent.getLongExtra(WAKEUP_INTERNAL, 0);
- long maxLoop = intent.getLongExtra(MAX_LOOP, 0);
-
- if (wakeupInterval == 0) {
- // stop and error
- Log.e(LOG_TAG, "No wakeup interval specified, not starting the service");
- stopSelf();
- return START_NOT_STICKY;
- }
- FileUtil.get().writeDateToFile(new File(Environment.getExternalStorageDirectory(),
- "wakeup-loop-start.txt"));
- Log.d(LOG_TAG, String.format("WakeLoop: STARTED interval = %d, total loop = %d",
- wakeupInterval, maxLoop));
- // calculate when device should be waken up
- long atTime = SystemClock.elapsedRealtime() + wakeupInterval;
- AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
- Intent wakupIntent = new Intent(WakeUpCall.WAKEUP_CALL)
- .putExtra(WAKEUP_INTERNAL, wakeupInterval)
- .putExtra(MAX_LOOP, maxLoop)
- .putExtra(THIS_LOOP, 0L)
- .putExtra(STOP_CALLBACK, new Messenger(mHandler));
- PendingIntent pi = PendingIntent.getBroadcast(this, 0, wakupIntent,
- PendingIntent.FLAG_UPDATE_CURRENT);
- // set alarm, which will be delivered in form of the wakeupIntent
- am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, atTime, pi);
- return START_NOT_STICKY;
- }
-
- @Override
- public void onDestroy() {
- Log.d(LOG_TAG, "WakeLoop: STOPPED");
- // cancel alarms first
- Intent intent = new Intent(WakeUpCall.WAKEUP_CALL)
- .putExtra(WakeUpCall.CANCEL, "true");
- sendBroadcast(intent);
- }
-}
diff --git a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeUpCall.java b/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeUpCall.java
deleted file mode 100644
index 8347bbf0..0000000
--- a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeUpCall.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2014 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.test.wakeuploop;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Environment;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.util.Log;
-
-import java.io.File;
-
-/**
- * The receiver for the alarm we set
- *
- */
-public class WakeUpCall extends BroadcastReceiver {
- private static final String LOG_TAG = WakeUpCall.class.getSimpleName();
- static final String WAKEUP_CALL = "android.test.wakeuploop.WAKEUP";
- static final String CANCEL = "CANCEL";
-
- @Override
- public void onReceive(Context context, Intent intent) {
- AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- boolean cancel = intent.hasExtra(CANCEL);
- if (!cancel) {
- long maxLoop = intent.getLongExtra(WakeLoopService.MAX_LOOP, 0);
- long wakeupInterval = intent.getLongExtra(WakeLoopService.WAKEUP_INTERNAL, 0);
- long thisLoop = intent.getLongExtra(WakeLoopService.THIS_LOOP, -1);
- Log.d(LOG_TAG, String.format("incoming: interval = %d, max loop = %d, this loop = %d",
- wakeupInterval, maxLoop, thisLoop));
- if (thisLoop == -1) {
- Log.e(LOG_TAG, "no valid loop count received, trying to stop service");
- stopService(intent);
- return;
- }
- if (wakeupInterval == 0) {
- Log.e(LOG_TAG, "no valid wakeup interval received, trying to stop service");
- stopService(intent);
- return;
- }
- thisLoop++;
- Log.d(LOG_TAG, String.format("WakeLoop - iteration %d of %d", thisLoop, maxLoop));
- if (thisLoop == maxLoop) {
- // when maxLoop is 0, we loop forever, so not checking that case
- // here
- Log.d(LOG_TAG, "reached max loop count, stopping service");
- stopService(intent);
- return;
- }
- screenOn(context);
- FileUtil.get().writeDateToFile(
- new File(Environment.getExternalStorageDirectory(), "wakeup-loop.txt"));
- // calculate when device should be waken up
- long atTime = SystemClock.elapsedRealtime() + wakeupInterval;
- intent.putExtra(WakeLoopService.THIS_LOOP, thisLoop);
- PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent,
- PendingIntent.FLAG_UPDATE_CURRENT);
- // set alarm, which will be delivered in form of the wakeupIntent
- am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, atTime, pi);
- } else {
- // cancel alarms
- Log.d(LOG_TAG, "cancelling future alarms on request");
- am.cancel(PendingIntent.getBroadcast(context, 0, intent, 0));
- }
- }
-
- private void stopService(Intent i) {
- Messenger msgr = i.getParcelableExtra(WakeLoopService.STOP_CALLBACK);
- if (msgr == null) {
- Log.e(LOG_TAG, "no stop service callback found, cannot stop");
- } else {
- Message msg = new Message();
- msg.what = WakeLoopService.MSG_STOP_SERVICE;
- try {
- msgr.send(msg);
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "ignored remoted exception while attempting to stop service", e);
- }
- }
- }
-
- private void screenOn(Context context) {
- PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
- @SuppressWarnings("deprecation")
- WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK |
- PowerManager.ACQUIRE_CAUSES_WAKEUP, LOG_TAG);
- wl.acquire(500);
- }
-}
diff --git a/tests/utils/testutils/java/com/android/test/filters/SelectTest.java b/tests/utils/testutils/java/com/android/test/filters/SelectTest.java
new file mode 100644
index 0000000..d0350af
--- /dev/null
+++ b/tests/utils/testutils/java/com/android/test/filters/SelectTest.java
@@ -0,0 +1,338 @@
+/*
+ * 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 com.android.test.filters;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.junit.runner.Description;
+import org.junit.runner.manipulation.Filter;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
+
+/**
+ * JUnit filter to select tests.
+ *
+ * <p>This filter selects tests specified by package name, class name, and method name. With this
+ * filter, the package and the class options of AndroidJUnitRunner can be superseded. Also the
+ * restriction that prevents using the package and the class options can be mitigated.
+ *
+ * <p><b>Select out tests from Java packages:</b> this option supersedes {@code -e package} option.
+ * <pre>
+ * adb shell am instrument -w \
+ * -e filter com.android.test.filters.SelectTest \
+ * -e selectTest package1.,package2. \
+ * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
+ * Note that the ending {@code .} in package name is mandatory.
+ *
+ * <p><b>Select out test classes:</b> this option supersedes {@code -e class} option.
+ * <pre>
+ * adb shell am instrument -w \
+ * -e filter com.android.test.filters.SelectTest \
+ * -e selectTest package1.ClassA,package2.ClassB \
+ * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
+ *
+ * <p><b>Select out test methods from Java classes:</b>
+ * <pre>
+ * adb shell am instrument -w \
+ * -e filter com.android.test.filters.SelectTest \
+ * -e selectTest package1.ClassA#methodX,package2.ClassB#methodY \
+ * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
+ *
+ * Those options can be used simultaneously. For example
+ * <pre>
+ * adb shell am instrument -w \
+ * -e filter com.android.test.filters.SelectTest \
+ * -e selectTest package1.,package2.classA,package3.ClassB#methodZ \
+ * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
+ * will select out all tests in package1, all tests in classA, and ClassB#methodZ test.
+ *
+ * <p>Note that when this option is specified with either {@code -e package} or {@code -e class}
+ * option, filtering behaves as logically conjunction. Other options, such as {@code -e notPackage},
+ * {@code -e notClass}, {@code -e annotation}, and {@code -e notAnnotation}, should work as expected
+ * with this SelectTest option.
+ *
+ * <p>When specified with {@code -e selectTest_verbose true} option, {@link SelectTest} verbosely
+ * logs to logcat while parsing {@code -e selectTest} option.
+ */
+public class SelectTest extends Filter {
+
+ private static final String TAG = SelectTest.class.getSimpleName();
+
+ @VisibleForTesting
+ static final String OPTION_SELECT_TEST = "selectTest";
+ @VisibleForTesting
+ static final String OPTION_SELECT_TEST_VERBOSE = OPTION_SELECT_TEST + "_verbose";
+
+ private static final String ARGUMENT_ITEM_SEPARATOR = ",";
+ private static final String PACKAGE_NAME_SEPARATOR = ".";
+ private static final String METHOD_SEPARATOR = "#";
+
+ @Nullable
+ private final PackageSet mPackageSet;
+
+ /**
+ * Construct {@link SelectTest} filter from instrumentation arguments in {@link Bundle}.
+ *
+ * @param testArgs instrumentation test arguments.
+ */
+ public SelectTest(@NonNull Bundle testArgs) {
+ mPackageSet = parseSelectTest(testArgs);
+ }
+
+ @Override
+ public boolean shouldRun(Description description) {
+ if (mPackageSet == null) {
+ // Accept all tests because this filter is disabled.
+ return true;
+ }
+ String testClassName = description.getClassName();
+ String testMethodName = description.getMethodName();
+ return mPackageSet.accept(testClassName, testMethodName);
+ }
+
+ @Override
+ public String describe() {
+ return OPTION_SELECT_TEST + "=" + mPackageSet;
+ }
+
+ /**
+ * Create {@link #OPTION_SELECT_TEST} argument and add it to {@code testArgs}.
+ *
+ * <p>This method is intended to be used at constructor of extended {@link Filter} class.
+ *
+ * @param testArgs instrumentation test arguments.
+ * @param selectTests array of class name to be selected to run.
+ * @return modified instrumentation test arguments.
+ */
+ @NonNull
+ protected static Bundle addSelectTest(
+ @NonNull Bundle testArgs, @NonNull String... selectTests) {
+ if (selectTests.length == 0) {
+ return testArgs;
+ }
+ testArgs.putString(OPTION_SELECT_TEST, join(Arrays.asList(selectTests)));
+ return testArgs;
+ }
+
+ /**
+ * Parse {@code -e selectTest} argument.
+ * @param testArgs instrumentation test arguments.
+ * @return {@link PackageSet} that will filter tests. Returns {@code null} when no
+ * {@code -e selectTest} option is specified, thus this filter gets disabled.
+ */
+ @Nullable
+ private static PackageSet parseSelectTest(Bundle testArgs) {
+ final String selectTestArgs = testArgs.getString(OPTION_SELECT_TEST);
+ if (selectTestArgs == null) {
+ Log.w(TAG, "Disabled because no " + OPTION_SELECT_TEST + " option specified");
+ return null;
+ }
+
+ final boolean verbose = new Boolean(testArgs.getString(OPTION_SELECT_TEST_VERBOSE));
+ final PackageSet packageSet = new PackageSet(verbose);
+ for (String selectTestArg : selectTestArgs.split(ARGUMENT_ITEM_SEPARATOR)) {
+ packageSet.add(selectTestArg);
+ }
+ return packageSet;
+ }
+
+ private static String getPackageName(String selectTestArg) {
+ int endPackagePos = selectTestArg.lastIndexOf(PACKAGE_NAME_SEPARATOR);
+ return (endPackagePos < 0) ? "" : selectTestArg.substring(0, endPackagePos);
+ }
+
+ @Nullable
+ private static String getClassName(String selectTestArg) {
+ if (selectTestArg.endsWith(PACKAGE_NAME_SEPARATOR)) {
+ return null;
+ }
+ int methodSepPos = selectTestArg.indexOf(METHOD_SEPARATOR);
+ return (methodSepPos < 0) ? selectTestArg : selectTestArg.substring(0, methodSepPos);
+ }
+
+ @Nullable
+ private static String getMethodName(String selectTestArg) {
+ int methodSepPos = selectTestArg.indexOf(METHOD_SEPARATOR);
+ return (methodSepPos < 0) ? null : selectTestArg.substring(methodSepPos + 1);
+ }
+
+ /** Package level filter */
+ private static class PackageSet {
+ private final boolean mVerbose;
+ /**
+ * Java package name to {@link ClassSet} map. To represent package filtering, a map value
+ * can be {@code null}.
+ */
+ private final Map<String, ClassSet> mClassSetMap = new LinkedHashMap<>();
+
+ PackageSet(boolean verbose) {
+ mVerbose = verbose;
+ }
+
+ void add(final String selectTestArg) {
+ final String packageName = getPackageName(selectTestArg);
+ final String className = getClassName(selectTestArg);
+
+ if (className == null) {
+ ClassSet classSet = mClassSetMap.put(packageName, null); // package filtering.
+ if (mVerbose) {
+ logging("Select package " + selectTestArg, classSet != null,
+ "; supersede " + classSet);
+ }
+ return;
+ }
+
+ ClassSet classSet = mClassSetMap.get(packageName);
+ if (classSet == null) {
+ if (mClassSetMap.containsKey(packageName)) {
+ if (mVerbose) {
+ logging("Select package " + packageName + PACKAGE_NAME_SEPARATOR, true,
+ " ignore " + selectTestArg);
+ }
+ return;
+ }
+ classSet = new ClassSet(mVerbose);
+ mClassSetMap.put(packageName, classSet);
+ }
+ classSet.add(selectTestArg);
+ }
+
+ boolean accept(String className, @Nullable String methodName) {
+ String packageName = getPackageName(className);
+ if (!mClassSetMap.containsKey(packageName)) {
+ return false;
+ }
+ ClassSet classSet = mClassSetMap.get(packageName);
+ return classSet == null || classSet.accept(className, methodName);
+ }
+
+ @Override
+ public String toString() {
+ StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR);
+ for (String packageName : mClassSetMap.keySet()) {
+ ClassSet classSet = mClassSetMap.get(packageName);
+ joiner.add(classSet == null
+ ? packageName + PACKAGE_NAME_SEPARATOR : classSet.toString());
+ }
+ return joiner.toString();
+ }
+ }
+
+ /** Class level filter */
+ private static class ClassSet {
+ private final boolean mVerbose;
+ /**
+ * Java class name to set of method names map. To represent class filtering, a map value
+ * can be {@code null}.
+ */
+ private final Map<String, Set<String>> mMethodSetMap = new LinkedHashMap<>();
+
+ ClassSet(boolean verbose) {
+ mVerbose = verbose;
+ }
+
+ void add(String selectTestArg) {
+ final String className = getClassName(selectTestArg);
+ final String methodName = getMethodName(selectTestArg);
+
+ if (methodName == null) {
+ Set<String> methodSet = mMethodSetMap.put(className, null); // class filtering.
+ if (mVerbose) {
+ logging("Select class " + selectTestArg, methodSet != null,
+ "; supersede " + toString(className, methodSet));
+ }
+ return;
+ }
+
+ Set<String> methodSet = mMethodSetMap.get(className);
+ if (methodSet == null) {
+ if (mMethodSetMap.containsKey(className)) {
+ if (mVerbose) {
+ logging("Select class " + className, true, "; ignore " + selectTestArg);
+ }
+ return;
+ }
+ methodSet = new LinkedHashSet<>();
+ mMethodSetMap.put(className, methodSet);
+ }
+
+ methodSet.add(methodName);
+ if (mVerbose) {
+ logging("Select method " + selectTestArg, false, null);
+ }
+ }
+
+ boolean accept(String className, @Nullable String methodName) {
+ if (!mMethodSetMap.containsKey(className)) {
+ return false;
+ }
+ Set<String> methodSet = mMethodSetMap.get(className);
+ return methodName == null || methodSet == null || methodSet.contains(methodName);
+ }
+
+ @Override
+ public String toString() {
+ StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR);
+ for (String className : mMethodSetMap.keySet()) {
+ joiner.add(toString(className, mMethodSetMap.get(className)));
+ }
+ return joiner.toString();
+ }
+
+ private static String toString(String className, @Nullable Set<String> methodSet) {
+ if (methodSet == null) {
+ return className;
+ }
+ StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR);
+ for (String methodName : methodSet) {
+ joiner.add(className + METHOD_SEPARATOR + methodName);
+ }
+ return joiner.toString();
+ }
+ }
+
+ private static void logging(String infoLog, boolean isWarning, String warningLog) {
+ if (isWarning) {
+ Log.w(TAG, infoLog + warningLog);
+ } else {
+ Log.i(TAG, infoLog);
+ }
+ }
+
+ private static String join(Collection<String> list) {
+ StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR);
+ for (String text : list) {
+ joiner.add(text);
+ }
+ return joiner.toString();
+ }
+}
diff --git a/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java
new file mode 100644
index 0000000..163b00a
--- /dev/null
+++ b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java
@@ -0,0 +1,220 @@
+/*
+ * 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 com.android.test.filters;
+
+import static com.android.test.filters.SelectTest.OPTION_SELECT_TEST;
+import static com.android.test.filters.SelectTest.OPTION_SELECT_TEST_VERBOSE;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Bundle;
+import android.util.ArraySet;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.manipulation.Filter;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.StringJoiner;
+
+public class SelectTestTests {
+
+ private static final String PACKAGE_A = "packageA.";
+ private static final String PACKAGE_B = "packageB.";
+ private static final String PACKAGE_C = "packageC.";
+ private static final String CLASS_A1 = PACKAGE_A + "Class1";
+ private static final String CLASS_A2 = PACKAGE_A + "Class2";
+ private static final String CLASS_B3 = PACKAGE_B + "Class3";
+ private static final String CLASS_B4 = PACKAGE_B + "Class4";
+ private static final String CLASS_C5 = PACKAGE_C + "Class5";
+ private static final String CLASS_C6 = PACKAGE_C + "Class6";
+ private static final String METHOD_A1K = CLASS_A1 + "#methodK";
+ private static final String METHOD_A1L = CLASS_A1 + "#methodL";
+ private static final String METHOD_A2M = CLASS_A2 + "#methodM";
+ private static final String METHOD_A2N = CLASS_A2 + "#methodN";
+ private static final String METHOD_B3P = CLASS_B3 + "#methodP";
+ private static final String METHOD_B3Q = CLASS_B3 + "#methodQ";
+ private static final String METHOD_B4R = CLASS_B4 + "#methodR";
+ private static final String METHOD_B4S = CLASS_B4 + "#methodS";
+ private static final String METHOD_C5W = CLASS_C5 + "#methodW";
+ private static final String METHOD_C5X = CLASS_C5 + "#methodX";
+ private static final String METHOD_C6Y = CLASS_C6 + "#methodY";
+ private static final String METHOD_C6Z = CLASS_C6 + "#methodZ";
+
+ private static final Set<Description> TEST_METHOD_A1K = methodTest(METHOD_A1K);
+ private static final Set<Description> TEST_METHOD_A1L = methodTest(METHOD_A1L);
+ private static final Set<Description> TEST_METHOD_A2M = methodTest(METHOD_A2M);
+ private static final Set<Description> TEST_METHOD_A2N = methodTest(METHOD_A2N);
+ private static final Set<Description> TEST_METHOD_B3P = methodTest(METHOD_B3P);
+ private static final Set<Description> TEST_METHOD_B3Q = methodTest(METHOD_B3Q);
+ private static final Set<Description> TEST_METHOD_B4R = methodTest(METHOD_B4R);
+ private static final Set<Description> TEST_METHOD_B4S = methodTest(METHOD_B4S);
+ private static final Set<Description> TEST_METHOD_C5W = methodTest(METHOD_C5W);
+ private static final Set<Description> TEST_METHOD_C5X = methodTest(METHOD_C5X);
+ private static final Set<Description> TEST_METHOD_C6Y = methodTest(METHOD_C6Y);
+ private static final Set<Description> TEST_METHOD_C6Z = methodTest(METHOD_C6Z);
+ private static final Set<Description> TEST_CLASS_A1 = merge(TEST_METHOD_A1K, TEST_METHOD_A1L);
+ private static final Set<Description> TEST_CLASS_A2 = merge(TEST_METHOD_A2M, TEST_METHOD_A2N);
+ private static final Set<Description> TEST_CLASS_B3 = merge(TEST_METHOD_B3P, TEST_METHOD_B3Q);
+ private static final Set<Description> TEST_CLASS_B4 = merge(TEST_METHOD_B4R, TEST_METHOD_B4S);
+ private static final Set<Description> TEST_CLASS_C5 = merge(TEST_METHOD_C5W, TEST_METHOD_C5X);
+ private static final Set<Description> TEST_CLASS_C6 = merge(TEST_METHOD_C6Y, TEST_METHOD_C6Z);
+ private static final Set<Description> TEST_PACKAGE_A = merge(TEST_CLASS_A1, TEST_CLASS_A2);
+ private static final Set<Description> TEST_PACKAGE_B = merge(TEST_CLASS_B3, TEST_CLASS_B4);
+ private static final Set<Description> TEST_PACKAGE_C = merge(TEST_CLASS_C5, TEST_CLASS_C6);
+ private static final Set<Description> TEST_ALL =
+ merge(TEST_PACKAGE_A, TEST_PACKAGE_B, TEST_PACKAGE_C);
+
+ private SelectTestBuilder mBuilder;
+
+ @Before
+ public void setUp() {
+ mBuilder = new SelectTestBuilder();
+ }
+
+ private static class SelectTestBuilder {
+ private final Bundle mTestArgs = new Bundle();
+
+ Filter build() {
+ mTestArgs.putString(OPTION_SELECT_TEST_VERBOSE, Boolean.TRUE.toString());
+ return new SelectTest(mTestArgs);
+ }
+
+ SelectTestBuilder withSelectTest(String... selectTestArgs) {
+ putTestOption(OPTION_SELECT_TEST, selectTestArgs);
+ return this;
+ }
+
+ private void putTestOption(String option, String... args) {
+ if (args.length > 0) {
+ StringJoiner joiner = new StringJoiner(",");
+ for (String arg : args) {
+ joiner.add(arg);
+ }
+ mTestArgs.putString(option, joiner.toString());
+ }
+ }
+ }
+
+ private static Set<Description> methodTest(String testName) {
+ int methodSep = testName.indexOf("#");
+ String className = testName.substring(0, methodSep);
+ String methodName = testName.substring(methodSep + 1);
+ final Set<Description> tests = new ArraySet<>();
+ tests.add(Description.createSuiteDescription(className));
+ tests.add(Description.createTestDescription(className, methodName));
+ return Collections.unmodifiableSet(tests);
+ }
+
+ @SafeVarargs
+ private static Set<Description> merge(Set<Description>... testSpecs) {
+ final Set<Description> merged = new LinkedHashSet<>();
+ for (Set<Description> testSet : testSpecs) {
+ merged.addAll(testSet);
+ }
+ return Collections.unmodifiableSet(merged);
+ }
+
+ @SafeVarargs
+ private static void acceptTests(Filter filter, Set<Description>... testSpecs) {
+ final Set<Description> accepts = merge(testSpecs);
+ for (Description test : TEST_ALL) {
+ if (accepts.contains(test)) {
+ assertTrue("accept " + test, filter.shouldRun(test));
+ } else {
+ assertFalse("reject " + test, filter.shouldRun(test));
+ }
+ }
+ }
+
+ @Test
+ public void testFilterDisabled() {
+ final Filter filter = mBuilder.build();
+ acceptTests(filter, TEST_ALL);
+ }
+
+ @Test
+ public void testSelectPackage() {
+ final Filter filter = mBuilder.withSelectTest(PACKAGE_A, PACKAGE_B).build();
+ acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_B);
+ }
+
+ @Test
+ public void testSelectClass() {
+ final Filter filter = mBuilder.withSelectTest(CLASS_A1, CLASS_A2, CLASS_B3).build();
+ acceptTests(filter, TEST_CLASS_A1, TEST_CLASS_A2, TEST_CLASS_B3);
+ }
+
+ @Test
+ public void testSelectMethod() {
+ final Filter filter = mBuilder
+ .withSelectTest(METHOD_A1K, METHOD_A2M, METHOD_A2N, METHOD_B3P).build();
+ acceptTests(filter, TEST_METHOD_A1K, TEST_METHOD_A2M, TEST_METHOD_A2N, TEST_METHOD_B3P);
+ }
+
+ @Test
+ public void testSelectClassAndPackage() {
+ final Filter filter = mBuilder.withSelectTest(CLASS_A1, PACKAGE_B, CLASS_C5).build();
+ acceptTests(filter, TEST_CLASS_A1, TEST_PACKAGE_B, TEST_CLASS_C5);
+ }
+
+ @Test
+ public void testSelectMethodAndPackage() {
+ final Filter filter = mBuilder.withSelectTest(METHOD_A1K, PACKAGE_B, METHOD_C5W).build();
+ acceptTests(filter, TEST_METHOD_A1K, TEST_PACKAGE_B, TEST_METHOD_C5W);
+ }
+
+ @Test
+ public void testSelectMethodAndClass() {
+ final Filter filter = mBuilder.withSelectTest(METHOD_A1K, CLASS_C5, METHOD_B3P).build();
+ acceptTests(filter, TEST_METHOD_A1K, TEST_CLASS_C5, TEST_METHOD_B3P);
+ }
+
+ @Test
+ public void testSelectClassAndSamePackage() {
+ final Filter filter = mBuilder.withSelectTest(
+ CLASS_A1, PACKAGE_A, CLASS_B3, PACKAGE_C, CLASS_C5).build();
+ acceptTests(filter, TEST_PACKAGE_A, TEST_CLASS_B3, TEST_PACKAGE_C);
+ }
+
+ @Test
+ public void testSelectMethodAndSameClass() {
+ final Filter filter = mBuilder.withSelectTest(
+ METHOD_A1K, METHOD_A2M, CLASS_A1, CLASS_B3, METHOD_B3P, METHOD_B4R).build();
+ acceptTests(filter, TEST_CLASS_A1, TEST_METHOD_A2M, TEST_CLASS_B3, TEST_METHOD_B4R);
+ }
+
+ @Test
+ public void testSelectMethodAndSamePackage() {
+ final Filter filter = mBuilder.withSelectTest(
+ METHOD_A1K, METHOD_A1L, METHOD_A2M, PACKAGE_A,
+ PACKAGE_C, METHOD_C5W, METHOD_C5X, METHOD_C6Y).build();
+ acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_C);
+ }
+
+ @Test
+ public void testSelectMethodAndClassAndPackage() {
+ final Filter filter = mBuilder.withSelectTest(
+ METHOD_A1K, CLASS_A1, METHOD_A1L, METHOD_A2M, PACKAGE_A,
+ PACKAGE_B, METHOD_B3Q, CLASS_B3, METHOD_B4R, METHOD_B3P).build();
+ acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_B);
+ }
+}
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 92beb4e..0512bdc 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -469,16 +469,12 @@
return false;
}
- // Read the file as a string
- char buffer_2[data->size()];
- memcpy(&buffer_2, data->data(), data->size());
- StringPiece content(buffer_2, data->size());
-
BigBuffer crunched_png_buffer(4096);
io::BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer);
// Ensure that we only keep the chunks we care about if we end up
// using the original PNG instead of the crunched one.
+ const StringPiece content(reinterpret_cast<const char*>(data->data()), data->size());
PngChunkFilter png_chunk_filter(content);
std::unique_ptr<Image> image = ReadPng(context, path_data.source, &png_chunk_filter);
if (!image) {
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index d1fe43e..75c3eba 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -297,6 +297,7 @@
class V2LineParser(object):
__slots__ = ["tokenized", "current", "len"]
+ FIELD_KINDS = ("field", "property", "enum_constant")
MODIFIERS = set("public protected internal private abstract default static final transient volatile synchronized native operator sealed strictfp infix inline suspend vararg".split())
JAVA_LANG_TYPES = set("AbstractMethodError AbstractStringBuilder Appendable ArithmeticException ArrayIndexOutOfBoundsException ArrayStoreException AssertionError AutoCloseable Boolean BootstrapMethodError Byte Character CharSequence Class ClassCastException ClassCircularityError ClassFormatError ClassLoader ClassNotFoundException Cloneable CloneNotSupportedException Comparable Compiler Deprecated Double Enum EnumConstantNotPresentException Error Exception ExceptionInInitializerError Float FunctionalInterface IllegalAccessError IllegalAccessException IllegalArgumentException IllegalMonitorStateException IllegalStateException IllegalThreadStateException IncompatibleClassChangeError IndexOutOfBoundsException InheritableThreadLocal InstantiationError InstantiationException Integer InternalError InterruptedException Iterable LinkageError Long Math NegativeArraySizeException NoClassDefFoundError NoSuchFieldError NoSuchFieldException NoSuchMethodError NoSuchMethodException NullPointerException Number NumberFormatException Object OutOfMemoryError Override Package package-info.java Process ProcessBuilder ProcessEnvironment ProcessImpl Readable ReflectiveOperationException Runnable Runtime RuntimeException RuntimePermission SafeVarargs SecurityException SecurityManager Short StackOverflowError StackTraceElement StrictMath String StringBuffer StringBuilder StringIndexOutOfBoundsException SuppressWarnings System Thread ThreadDeath ThreadGroup ThreadLocal Throwable TypeNotPresentException UNIXProcess UnknownError UnsatisfiedLinkError UnsupportedClassVersionError UnsupportedOperationException VerifyError VirtualMachineError Void".split())
@@ -355,7 +356,7 @@
self.parse_eof()
def parse_into_field(self, field):
- kind = self.parse_one_of("field", "property")
+ kind = self.parse_one_of(*V2LineParser.FIELD_KINDS)
field.split = [kind]
annotations = self.parse_annotations()
if "@Deprecated" in annotations:
@@ -591,6 +592,14 @@
sig_format = 1
re_blame = re.compile("^([a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$")
+
+ field_prefixes = map(lambda kind: " %s" % (kind,), V2LineParser.FIELD_KINDS)
+ def startsWithFieldPrefix(raw):
+ for prefix in field_prefixes:
+ if raw.startswith(prefix):
+ return True
+ return False
+
for raw in f:
line += 1
raw = raw.rstrip()
@@ -611,7 +620,7 @@
clazz.ctors.append(Method(clazz, line, raw, blame, sig_format=sig_format))
elif raw.startswith(" method"):
clazz.methods.append(Method(clazz, line, raw, blame, sig_format=sig_format))
- elif raw.startswith(" field") or raw.startswith(" property"):
+ elif startsWithFieldPrefix(raw):
clazz.fields.append(Field(clazz, line, raw, blame, sig_format=sig_format))
elif raw.startswith(" }") and clazz:
yield clazz
diff --git a/tools/apilint/apilint_test.py b/tools/apilint/apilint_test.py
index fde61a9..9c261d5 100644
--- a/tools/apilint/apilint_test.py
+++ b/tools/apilint/apilint_test.py
@@ -143,6 +143,27 @@
out_classes_with_base=classes_with_base)
self.assertEquals(map(lambda x: x.fullname, classes_with_base), ["android.app.WallpaperColors"])
+class ParseV2Stream(unittest.TestCase):
+ def test_field_kinds(self):
+ api = apilint._parse_stream("""
+// Signature format: 2.0
+package android {
+ public enum SomeEnum {
+ enum_constant public static final android.SomeEnum ENUM_CONST;
+ field public static final int FIELD_CONST;
+ property public final int someProperty;
+ ctor public SomeEnum();
+ method public Object? getObject();
+ }
+}
+ """.strip().split('\n'))
+
+ self.assertEquals(api['android.SomeEnum'].fields[0].split[0], 'enum_constant')
+ self.assertEquals(api['android.SomeEnum'].fields[1].split[0], 'field')
+ self.assertEquals(api['android.SomeEnum'].fields[2].split[0], 'property')
+ self.assertEquals(api['android.SomeEnum'].ctors[0].split[0], 'ctor')
+ self.assertEquals(api['android.SomeEnum'].methods[0].split[0], 'method')
+
class V2TokenizerTests(unittest.TestCase):
def _test(self, raw, expected):
self.assertEquals(apilint.V2Tokenizer(raw).tokenize(), expected)
@@ -211,6 +232,10 @@
self.assertEquals('Interface', cls.implements)
self.assertEquals('pkg.Some.Name', cls.fullname)
+ def test_enum(self):
+ cls = self._cls("public enum Some.Name {")
+ self._field("enum_constant public static final android.ValueType COLOR;")
+
def test_interface(self):
cls = self._cls("@Deprecated @IntRange(from=1, to=2) public interface Some.Name extends Interface<Class> {")
self.assertTrue('deprecated' in cls.split)
diff --git a/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py
index 01728fa1..2a8f695 100755
--- a/tools/hiddenapi/generate_hiddenapi_lists.py
+++ b/tools/hiddenapi/generate_hiddenapi_lists.py
@@ -17,6 +17,7 @@
Generate API lists for non-SDK API enforcement.
"""
import argparse
+from collections import defaultdict
import os
import sys
import re
@@ -27,16 +28,20 @@
FLAG_BLACKLIST = "blacklist"
FLAG_GREYLIST_MAX_O = "greylist-max-o"
FLAG_GREYLIST_MAX_P = "greylist-max-p"
+FLAG_CORE_PLATFORM_API = "core-platform-api"
# List of all known flags.
-FLAGS = [
+FLAGS_API_LIST = [
FLAG_WHITELIST,
FLAG_GREYLIST,
FLAG_BLACKLIST,
FLAG_GREYLIST_MAX_O,
FLAG_GREYLIST_MAX_P,
]
-FLAGS_SET = set(FLAGS)
+ALL_FLAGS = FLAGS_API_LIST + [ FLAG_CORE_PLATFORM_API ]
+
+FLAGS_API_LIST_SET = set(FLAGS_API_LIST)
+ALL_FLAGS_SET = set(ALL_FLAGS)
# Suffix used in command line args to express that only known and
# otherwise unassigned entries should be assign the given flag.
@@ -62,7 +67,7 @@
SERIALIZATION_REGEX = re.compile(r'.*->(' + '|'.join(SERIALIZATION_PATTERNS) + r')$')
# Predicates to be used with filter_apis.
-IS_UNASSIGNED = lambda api, flags: not flags
+HAS_NO_API_LIST_ASSIGNED = lambda api, flags: not FLAGS_API_LIST_SET.intersection(flags)
IS_SERIALIZATION = lambda api, flags: SERIALIZATION_REGEX.match(api)
def get_args():
@@ -73,12 +78,10 @@
"""
parser = argparse.ArgumentParser()
parser.add_argument('--output', required=True)
- parser.add_argument('--public', required=True, help='list of all public entries')
- parser.add_argument('--private', required=True, help='list of all private entries')
parser.add_argument('--csv', nargs='*', default=[], metavar='CSV_FILE',
help='CSV files to be merged into output')
- for flag in FLAGS:
+ for flag in ALL_FLAGS:
ignore_conflicts_flag = flag + FLAG_IGNORE_CONFLICTS_SUFFIX
parser.add_argument('--' + flag, dest=flag, nargs='*', default=[], metavar='TXT_FILE',
help='lists of entries with flag "' + flag + '"')
@@ -118,26 +121,9 @@
f.writelines(lines)
class FlagsDict:
- def __init__(self, public_api, private_api):
- # Bootstrap the entries dictionary.
-
- # Check that the two sets do not overlap.
- public_api_set = set(public_api)
- private_api_set = set(private_api)
- assert public_api_set.isdisjoint(private_api_set), (
- "Lists of public and private API overlap. " +
- "This suggests an issue with the `hiddenapi` build tool.")
-
- # Compute the whole key set
- self._dict_keyset = public_api_set.union(private_api_set)
-
- # Create a dict that creates entries for both public and private API,
- # and assigns public API to the whitelist.
- self._dict = {}
- for api in public_api:
- self._dict[api] = set([ FLAG_WHITELIST ])
- for api in private_api:
- self._dict[api] = set()
+ def __init__(self):
+ self._dict_keyset = set()
+ self._dict = defaultdict(set)
def _check_entries_set(self, keys_subset, source):
assert isinstance(keys_subset, set)
@@ -150,12 +136,12 @@
def _check_flags_set(self, flags_subset, source):
assert isinstance(flags_subset, set)
- assert flags_subset.issubset(FLAGS_SET), (
+ assert flags_subset.issubset(ALL_FLAGS_SET), (
"Error processing: {}\n"
"The following flags were not recognized: \n"
"{}\n"
"Please visit go/hiddenapi for more information.").format(
- source, "\n".join(flags_subset - FLAGS_SET))
+ source, "\n".join(flags_subset - ALL_FLAGS_SET))
def filter_apis(self, filter_fn):
"""Returns APIs which match a given predicate.
@@ -173,7 +159,7 @@
def get_valid_subset_of_unassigned_apis(self, api_subset):
"""Sanitizes a key set input to only include keys which exist in the dictionary
- and have not been assigned any flags.
+ and have not been assigned any API list flags.
Args:
entries_subset (set/list): Key set to be sanitized.
@@ -182,7 +168,7 @@
Sanitized key set.
"""
assert isinstance(api_subset, set)
- return api_subset.intersection(self.filter_apis(IS_UNASSIGNED))
+ return api_subset.intersection(self.filter_apis(HAS_NO_API_LIST_ASSIGNED))
def generate_csv(self):
"""Constructs CSV entries from a dictionary.
@@ -203,14 +189,13 @@
source (string): Origin of `csv_lines`. Will be printed in error messages.
Throws:
- AssertionError if parsed API signatures of flags are invalid.
+ AssertionError if parsed flags are invalid.
"""
# Split CSV lines into arrays of values.
csv_values = [ line.split(',') for line in csv_lines ]
- # Check that all entries exist in the dict.
- csv_keys = set([ csv[0] for csv in csv_values ])
- self._check_entries_set(csv_keys, source)
+ # Update the full set of API signatures.
+ self._dict_keyset.update([ csv[0] for csv in csv_values ])
# Check that all flags are known.
csv_flags = set(reduce(lambda x, y: set(x).union(y), [ csv[1:] for csv in csv_values ], []))
@@ -224,7 +209,7 @@
"""Assigns a flag to given subset of entries.
Args:
- flag (string): One of FLAGS.
+ flag (string): One of ALL_FLAGS.
apis (set): Subset of APIs to recieve the flag.
source (string): Origin of `entries_subset`. Will be printed in error messages.
@@ -245,18 +230,23 @@
# Parse arguments.
args = vars(get_args())
- flags = FlagsDict(read_lines(args["public"]), read_lines(args["private"]))
+ # Initialize API->flags dictionary.
+ flags = FlagsDict()
+
+ # Merge input CSV files into the dictionary.
+ # Do this first because CSV files produced by parsing API stubs will
+ # contain the full set of APIs. Subsequent additions from text files
+ # will be able to detect invalid entries, and/or filter all as-yet
+ # unassigned entries.
+ for filename in args["csv"]:
+ flags.parse_and_merge_csv(read_lines(filename), filename)
# Combine inputs which do not require any particular order.
# (1) Assign serialization API to whitelist.
flags.assign_flag(FLAG_WHITELIST, flags.filter_apis(IS_SERIALIZATION))
- # (2) Merge input CSV files into the dictionary.
- for filename in args["csv"]:
- flags.parse_and_merge_csv(read_lines(filename), filename)
-
- # (3) Merge text files with a known flag into the dictionary.
- for flag in FLAGS:
+ # (2) Merge text files with a known flag into the dictionary.
+ for flag in ALL_FLAGS:
for filename in args[flag]:
flags.assign_flag(flag, read_lines(filename), filename)
@@ -265,13 +255,13 @@
# (a) the entry exists, and
# (b) it has not been assigned any other flag.
# Because of (b), this must run after all strict assignments have been performed.
- for flag in FLAGS:
+ for flag in ALL_FLAGS:
for filename in args[flag + FLAG_IGNORE_CONFLICTS_SUFFIX]:
valid_entries = flags.get_valid_subset_of_unassigned_apis(read_lines(filename))
flags.assign_flag(flag, valid_entries, filename)
# Assign all remaining entries to the blacklist.
- flags.assign_flag(FLAG_BLACKLIST, flags.filter_apis(IS_UNASSIGNED))
+ flags.assign_flag(FLAG_BLACKLIST, flags.filter_apis(HAS_NO_API_LIST_ASSIGNED))
# Write output.
write_lines(args["output"], flags.generate_csv())
diff --git a/tools/processors/unsupportedappusage/Android.bp b/tools/processors/unsupportedappusage/Android.bp
index 1aca3ed..0e33fdd 100644
--- a/tools/processors/unsupportedappusage/Android.bp
+++ b/tools/processors/unsupportedappusage/Android.bp
@@ -1,6 +1,8 @@
-java_library_host {
+java_plugin {
name: "unsupportedappusage-annotation-processor",
+ processor_class: "android.processor.unsupportedappusage.UnsupportedAppUsageProcessor",
+
java_resources: [
"META-INF/**/*",
],
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 8c00ceb..1fd45e7 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -2084,6 +2084,8 @@
public static final int WIFI_FEATURE_LOW_LATENCY = 0x40000000; // Low Latency modes
/** @hide */
public static final int WIFI_FEATURE_DPP = 0x80000000; // DPP (Easy-Connect)
+ /** @hide */
+ public static final long WIFI_FEATURE_P2P_RAND_MAC = 0x100000000L; // Random P2P MAC
private long getSupportedFeatures() {
try {
@@ -3717,10 +3719,8 @@
* @param SSID, in the format of WifiConfiguration's SSID.
* @hide
*/
- @SystemApi
@RequiresPermission(anyOf = {
android.Manifest.permission.NETWORK_SETTINGS,
- android.Manifest.permission.NETWORK_SETUP_WIZARD,
android.Manifest.permission.NETWORK_STACK
})
public void disableEphemeralNetwork(String SSID) {
diff --git a/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java
index aa1669e..52ee742 100644
--- a/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java
+++ b/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java
@@ -28,6 +28,7 @@
import android.net.NetworkSpecifier;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import java.util.Objects;
@@ -50,12 +51,24 @@
*/
private final int mOriginalRequestorUid;
+ /**
+ * The package name of the app that requested a specific wifi network using
+ * {@link WifiNetworkSpecifier}.
+ *
+ * Will only be filled when the device connects to a wifi network as a result of a
+ * {@link NetworkRequest} with {@link WifiNetworkSpecifier}. Will be set to null if the device
+ * auto-connected to a wifi network.
+ */
+ private final String mOriginalRequestorPackageName;
+
public WifiNetworkAgentSpecifier(@NonNull WifiConfiguration wifiConfiguration,
- int originalRequestorUid) {
+ int originalRequestorUid,
+ @Nullable String originalRequestorPackageName) {
checkNotNull(wifiConfiguration);
mWifiConfiguration = wifiConfiguration;
mOriginalRequestorUid = originalRequestorUid;
+ mOriginalRequestorPackageName = originalRequestorPackageName;
}
/**
@@ -67,7 +80,9 @@
public WifiNetworkAgentSpecifier createFromParcel(@NonNull Parcel in) {
WifiConfiguration wifiConfiguration = in.readParcelable(null);
int originalRequestorUid = in.readInt();
- return new WifiNetworkAgentSpecifier(wifiConfiguration, originalRequestorUid);
+ String originalRequestorPackageName = in.readString();
+ return new WifiNetworkAgentSpecifier(
+ wifiConfiguration, originalRequestorUid, originalRequestorPackageName);
}
@Override
@@ -85,6 +100,7 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeParcelable(mWifiConfiguration, flags);
dest.writeInt(mOriginalRequestorUid);
+ dest.writeString(mOriginalRequestorPackageName);
}
@Override
@@ -137,6 +153,9 @@
if (ns.requestorUid != this.mOriginalRequestorUid) {
return false;
}
+ if (!TextUtils.equals(ns.requestorPackageName, this.mOriginalRequestorPackageName)) {
+ return false;
+ }
return true;
}
@@ -146,7 +165,8 @@
mWifiConfiguration.SSID,
mWifiConfiguration.BSSID,
mWifiConfiguration.allowedKeyManagement,
- mOriginalRequestorUid);
+ mOriginalRequestorUid,
+ mOriginalRequestorPackageName);
}
@Override
@@ -162,7 +182,9 @@
&& Objects.equals(this.mWifiConfiguration.BSSID, lhs.mWifiConfiguration.BSSID)
&& Objects.equals(this.mWifiConfiguration.allowedKeyManagement,
lhs.mWifiConfiguration.allowedKeyManagement)
- && mOriginalRequestorUid == lhs.mOriginalRequestorUid;
+ && mOriginalRequestorUid == lhs.mOriginalRequestorUid
+ && TextUtils.equals(mOriginalRequestorPackageName,
+ lhs.mOriginalRequestorPackageName);
}
@Override
@@ -172,6 +194,7 @@
.append(", SSID=").append(mWifiConfiguration.SSID)
.append(", BSSID=").append(mWifiConfiguration.BSSID)
.append(", mOriginalRequestorUid=").append(mOriginalRequestorUid)
+ .append(", mOriginalRequestorPackageName=").append(mOriginalRequestorPackageName)
.append("]");
return sb.toString();
}
diff --git a/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java b/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java
index ecee5ff..42d4393 100644
--- a/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java
+++ b/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityThread;
import android.net.MacAddress;
import android.net.NetworkRequest;
import android.net.NetworkSpecifier;
@@ -586,7 +587,8 @@
mSsidPatternMatcher,
mBssidPatternMatcher,
buildWifiConfiguration(),
- Process.myUid());
+ Process.myUid(),
+ ActivityThread.currentApplication().getApplicationContext().getOpPackageName());
}
/**
@@ -648,7 +650,8 @@
buildWifiConfiguration(),
mIsAppInteractionRequired,
mIsUserInteractionRequired,
- Process.myUid());
+ Process.myUid(),
+ ActivityThread.currentApplication().getApplicationContext().getOpPackageName());
}
}
diff --git a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
index 6e4eeef..a5f4675 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
@@ -25,6 +25,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PatternMatcher;
+import android.text.TextUtils;
import android.util.Pair;
import java.util.Objects;
@@ -63,18 +64,25 @@
*/
public final int requestorUid;
+ /**
+ * The package name of the app initializing this network specifier.
+ */
+ public final String requestorPackageName;
+
public WifiNetworkSpecifier(@NonNull PatternMatcher ssidPatternMatcher,
@NonNull Pair<MacAddress, MacAddress> bssidPatternMatcher,
@NonNull WifiConfiguration wifiConfiguration,
- int requestorUid) {
+ int requestorUid, @NonNull String requestorPackageName) {
checkNotNull(ssidPatternMatcher);
checkNotNull(bssidPatternMatcher);
checkNotNull(wifiConfiguration);
+ checkNotNull(requestorPackageName);
this.ssidPatternMatcher = ssidPatternMatcher;
this.bssidPatternMatcher = bssidPatternMatcher;
this.wifiConfiguration = wifiConfiguration;
this.requestorUid = requestorUid;
+ this.requestorPackageName = requestorPackageName;
}
public static final Creator<WifiNetworkSpecifier> CREATOR =
@@ -88,8 +96,9 @@
Pair.create(baseAddress, mask);
WifiConfiguration wifiConfiguration = in.readParcelable(null);
int requestorUid = in.readInt();
+ String requestorPackageName = in.readString();
return new WifiNetworkSpecifier(ssidPatternMatcher, bssidPatternMatcher,
- wifiConfiguration, requestorUid);
+ wifiConfiguration, requestorUid, requestorPackageName);
}
@Override
@@ -110,6 +119,7 @@
dest.writeParcelable(bssidPatternMatcher.second, flags);
dest.writeParcelable(wifiConfiguration, flags);
dest.writeInt(requestorUid);
+ dest.writeString(requestorPackageName);
}
@Override
@@ -136,7 +146,7 @@
ssidPatternMatcher.getType(),
bssidPatternMatcher,
wifiConfiguration.allowedKeyManagement,
- requestorUid);
+ requestorUid, requestorPackageName);
}
@Override
@@ -156,7 +166,8 @@
lhs.bssidPatternMatcher)
&& Objects.equals(this.wifiConfiguration.allowedKeyManagement,
lhs.wifiConfiguration.allowedKeyManagement)
- && requestorUid == lhs.requestorUid;
+ && requestorUid == lhs.requestorUid
+ && TextUtils.equals(requestorPackageName, lhs.requestorPackageName);
}
@Override
@@ -168,6 +179,7 @@
.append(", SSID=").append(wifiConfiguration.SSID)
.append(", BSSID=").append(wifiConfiguration.BSSID)
.append(", requestorUid=").append(requestorUid)
+ .append(", requestorPackageName=").append(requestorPackageName)
.append("]")
.toString();
}
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index 3c90eb7..6b05dfc 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -18,8 +18,10 @@
import static com.android.internal.util.Preconditions.checkNotNull;
+import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import java.util.List;
import java.util.Objects;
@@ -58,17 +60,25 @@
*/
public final int suggestorUid;
+ /**
+ * The package name of the process initializing this network suggestion.
+ * @hide
+ */
+ public final String suggestorPackageName;
+
/** @hide */
- public WifiNetworkSuggestion(WifiConfiguration wifiConfiguration,
+ public WifiNetworkSuggestion(@NonNull WifiConfiguration wifiConfiguration,
boolean isAppInteractionRequired,
boolean isUserInteractionRequired,
- int suggestorUid) {
+ int suggestorUid, @NonNull String suggestorPackageName) {
checkNotNull(wifiConfiguration);
+ checkNotNull(suggestorPackageName);
this.wifiConfiguration = wifiConfiguration;
this.isAppInteractionRequired = isAppInteractionRequired;
this.isUserInteractionRequired = isUserInteractionRequired;
this.suggestorUid = suggestorUid;
+ this.suggestorPackageName = suggestorPackageName;
}
public static final Creator<WifiNetworkSuggestion> CREATOR =
@@ -79,7 +89,8 @@
in.readParcelable(null), // wifiConfiguration
in.readBoolean(), // isAppInteractionRequired
in.readBoolean(), // isUserInteractionRequired
- in.readInt() // suggestorUid
+ in.readInt(), // suggestorUid
+ in.readString() // suggestorPackageName
);
}
@@ -100,12 +111,13 @@
dest.writeBoolean(isAppInteractionRequired);
dest.writeBoolean(isUserInteractionRequired);
dest.writeInt(suggestorUid);
+ dest.writeString(suggestorPackageName);
}
@Override
public int hashCode() {
return Objects.hash(wifiConfiguration.SSID, wifiConfiguration.BSSID,
- wifiConfiguration.allowedKeyManagement, suggestorUid);
+ wifiConfiguration.allowedKeyManagement, suggestorUid, suggestorPackageName);
}
/**
@@ -124,7 +136,8 @@
&& Objects.equals(this.wifiConfiguration.BSSID, lhs.wifiConfiguration.BSSID)
&& Objects.equals(this.wifiConfiguration.allowedKeyManagement,
lhs.wifiConfiguration.allowedKeyManagement)
- && suggestorUid == lhs.suggestorUid;
+ && suggestorUid == lhs.suggestorUid
+ && TextUtils.equals(suggestorPackageName, lhs.suggestorPackageName);
}
@Override
@@ -135,6 +148,7 @@
.append(", isAppInteractionRequired=").append(isAppInteractionRequired)
.append(", isUserInteractionRequired=").append(isUserInteractionRequired)
.append(", suggestorUid=").append(suggestorUid)
+ .append(", suggestorPackageName=").append(suggestorPackageName)
.append("]");
return sb.toString();
}
diff --git a/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java b/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java
index 1ee874a..1d499b6 100644
--- a/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java
+++ b/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java
@@ -65,9 +65,9 @@
public static final int OSU_FAILURE_PROVISIONING_NOT_AVAILABLE = 7;
/**
- * The reason code for provisioning failure due to invalid server url.
+ * The reason code for provisioning failure due to invalid web url format for an OSU web page.
*/
- public static final int OSU_FAILURE_INVALID_SERVER_URL = 8;
+ public static final int OSU_FAILURE_INVALID_URL_FORMAT_FOR_OSU = 8;
/**
* The reason code for provisioning failure when a command received is not the expected command
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java
index 2258e4d..e6eece8 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java
@@ -38,6 +38,8 @@
public class WifiNetworkAgentSpecifierTest {
private static final int TEST_UID = 5;
private static final int TEST_UID_1 = 8;
+ private static final String TEST_PACKAGE = "com.test";
+ private static final String TEST_PACKAGE_1 = "com.test.1";
private static final String TEST_SSID = "Test123";
private static final String TEST_SSID_PATTERN = "Test";
private static final String TEST_SSID_1 = "456test";
@@ -104,14 +106,14 @@
WifiNetworkAgentSpecifier specifier1 =
new WifiNetworkAgentSpecifier(
wifiConfiguration1,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1);
wifiConfiguration2.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkAgentSpecifier specifier2 =
new WifiNetworkAgentSpecifier(
wifiConfiguration2,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertFalse(specifier2.equals(specifier1));
}
@@ -128,14 +130,14 @@
WifiNetworkAgentSpecifier specifier1 =
new WifiNetworkAgentSpecifier(
wifiConfiguration1,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1);
wifiConfiguration2.SSID = TEST_SSID_1;
WifiNetworkAgentSpecifier specifier2 =
new WifiNetworkAgentSpecifier(
wifiConfiguration2,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertFalse(specifier2.equals(specifier1));
}
@@ -152,14 +154,14 @@
WifiNetworkAgentSpecifier specifier1 =
new WifiNetworkAgentSpecifier(
wifiConfiguration1,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1);
wifiConfiguration2.BSSID = TEST_BSSID_1;
WifiNetworkAgentSpecifier specifier2 =
new WifiNetworkAgentSpecifier(
wifiConfiguration2,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertFalse(specifier2.equals(specifier1));
}
@@ -214,7 +216,7 @@
ssidPattern,
bssidPattern,
wificonfigurationNetworkSpecifier,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertTrue(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
assertTrue(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -243,7 +245,7 @@
ssidPattern,
bssidPattern,
wificonfigurationNetworkSpecifier,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertTrue(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
assertTrue(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -272,7 +274,7 @@
ssidPattern,
bssidPattern,
wificonfigurationNetworkSpecifier,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertTrue(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
assertTrue(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -292,7 +294,7 @@
WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier =
new WifiNetworkAgentSpecifier(
wifiConfigurationNetworkAgent,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
PatternMatcher ssidPattern =
new PatternMatcher(TEST_SSID_PATTERN, PatternMatcher.PATTERN_PREFIX);
@@ -305,7 +307,7 @@
ssidPattern,
bssidPattern,
wificonfigurationNetworkSpecifier,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -325,7 +327,7 @@
WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier =
new WifiNetworkAgentSpecifier(
wifiConfigurationNetworkAgent,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
PatternMatcher ssidPattern =
new PatternMatcher(".*", PatternMatcher.PATTERN_SIMPLE_GLOB);
@@ -339,7 +341,7 @@
ssidPattern,
bssidPattern,
wificonfigurationNetworkSpecifier,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -359,7 +361,7 @@
WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier =
new WifiNetworkAgentSpecifier(
wifiConfigurationNetworkAgent,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
PatternMatcher ssidPattern =
new PatternMatcher(TEST_SSID_PATTERN, PatternMatcher.PATTERN_PREFIX);
@@ -373,7 +375,7 @@
ssidPattern,
bssidPattern,
wificonfigurationNetworkSpecifier,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -401,7 +403,7 @@
ssidPattern,
bssidPattern,
wificonfigurationNetworkSpecifier,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -430,7 +432,7 @@
ssidPattern,
bssidPattern,
wificonfigurationNetworkSpecifier,
- TEST_UID_1);
+ TEST_UID_1, TEST_PACKAGE_1);
assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -446,7 +448,8 @@
}
private WifiNetworkAgentSpecifier createDefaultNetworkAgentSpecifier() {
- return new WifiNetworkAgentSpecifier(createDefaultWifiConfiguration(), TEST_UID);
+ return new WifiNetworkAgentSpecifier(createDefaultWifiConfiguration(), TEST_UID,
+ TEST_PACKAGE);
}
}
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java
index 2a8df8d..fce247f 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java
@@ -38,6 +38,7 @@
@SmallTest
public class WifiNetworkSpecifierTest {
private static final int TEST_UID = 5;
+ private static final String TEST_PACKAGE_NAME = "com.test";
private static final String TEST_SSID = "Test123";
private static final String TEST_BSSID_OUI_BASE_ADDRESS = "12:12:12:00:00:00";
private static final String TEST_BSSID_OUI_MASK = "ff:ff:ff:00:00:00";
@@ -56,7 +57,7 @@
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
Parcel parcelW = Parcel.obtain();
specifier.writeToParcel(parcelW, 0);
@@ -88,7 +89,7 @@
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
assertTrue(specifier.satisfiedBy(null));
assertTrue(specifier.satisfiedBy(new MatchAllNetworkSpecifier()));
@@ -111,14 +112,14 @@
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
WifiNetworkSpecifier specifier2 =
new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
assertTrue(specifier2.satisfiedBy(specifier1));
}
@@ -140,7 +141,7 @@
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration1,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
WifiConfiguration wifiConfiguration2 = new WifiConfiguration();
wifiConfiguration2.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
@@ -149,7 +150,7 @@
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration2,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
assertFalse(specifier2.satisfiedBy(specifier1));
}
@@ -171,14 +172,14 @@
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
WifiNetworkSpecifier specifier2 =
new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
assertFalse(specifier2.satisfiedBy(specifier1));
}
@@ -200,13 +201,42 @@
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
WifiNetworkSpecifier specifier2 =
new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS),
wifiConfiguration,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
+
+ assertFalse(specifier2.satisfiedBy(specifier1));
+ }
+
+ /**
+ * Validate NetworkSpecifier matching.
+ * a) Create network specifier 1 for WPA_PSK network
+ * b) Create network specifier 2 with different package name .
+ * c) Ensure that the specifier 2 is not satisfied by specifier 1.
+ */
+ @Test
+ public void testWifiNetworkSpecifierDoesNotSatisfyWhenPackageNameDifferent() {
+ WifiConfiguration wifiConfiguration = new WifiConfiguration();
+ wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+ wifiConfiguration.preSharedKey = TEST_PRESHARED_KEY;
+
+ WifiNetworkSpecifier specifier1 =
+ new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
+ Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
+ MacAddress.fromString(TEST_BSSID_OUI_MASK)),
+ wifiConfiguration,
+ TEST_UID, TEST_PACKAGE_NAME);
+
+ WifiNetworkSpecifier specifier2 =
+ new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
+ Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
+ MacAddress.fromString(TEST_BSSID_OUI_MASK)),
+ wifiConfiguration,
+ TEST_UID, TEST_PACKAGE_NAME + "blah");
assertFalse(specifier2.satisfiedBy(specifier1));
}
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
index 31f501f..5f76055 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
@@ -29,6 +29,10 @@
*/
@SmallTest
public class WifiNetworkSuggestionTest {
+ private static final int TEST_UID = 45677;
+ private static final int TEST_UID_OTHER = 45673;
+ private static final String TEST_PACKAGE_NAME = "com.test.packagename";
+ private static final String TEST_PACKAGE_NAME_OTHER = "com.test.packagenameother";
private static final String TEST_SSID = "\"Test123\"";
private static final String TEST_BSSID = "12:12:12:12:12:12";
private static final String TEST_SSID_1 = "\"Test1234\"";
@@ -43,7 +47,7 @@
configuration.BSSID = TEST_BSSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, false, true, 0);
+ new WifiNetworkSuggestion(configuration, false, true, TEST_UID, TEST_PACKAGE_NAME);
Parcel parcelW = Parcel.obtain();
suggestion.writeToParcel(parcelW, 0);
@@ -77,14 +81,16 @@
configuration.BSSID = TEST_BSSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, true, false, 0);
+ new WifiNetworkSuggestion(configuration, true, false, TEST_UID,
+ TEST_PACKAGE_NAME);
WifiConfiguration configuration1 = new WifiConfiguration();
configuration1.SSID = TEST_SSID;
configuration1.BSSID = TEST_BSSID;
configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration1, false, true, 0);
+ new WifiNetworkSuggestion(configuration1, false, true, TEST_UID,
+ TEST_PACKAGE_NAME);
assertEquals(suggestion, suggestion1);
}
@@ -99,13 +105,15 @@
configuration.SSID = TEST_SSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, false, false, 0);
+ new WifiNetworkSuggestion(configuration, false, false, TEST_UID,
+ TEST_PACKAGE_NAME);
WifiConfiguration configuration1 = new WifiConfiguration();
configuration1.SSID = TEST_SSID_1;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration1, false, false, 0);
+ new WifiNetworkSuggestion(configuration1, false, false, TEST_UID,
+ TEST_PACKAGE_NAME);
assertNotEquals(suggestion, suggestion1);
}
@@ -121,13 +129,15 @@
configuration.BSSID = TEST_BSSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, false, false, 0);
+ new WifiNetworkSuggestion(configuration, false, false, TEST_UID,
+ TEST_PACKAGE_NAME);
WifiConfiguration configuration1 = new WifiConfiguration();
configuration1.SSID = TEST_SSID;
configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration1, false, false, 0);
+ new WifiNetworkSuggestion(configuration1, false, false, TEST_UID,
+ TEST_PACKAGE_NAME);
assertNotEquals(suggestion, suggestion1);
}
@@ -142,13 +152,15 @@
configuration.SSID = TEST_SSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, false, false, 0);
+ new WifiNetworkSuggestion(configuration, false, false, TEST_UID,
+ TEST_PACKAGE_NAME);
WifiConfiguration configuration1 = new WifiConfiguration();
configuration1.SSID = TEST_SSID;
configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration1, false, false, 0);
+ new WifiNetworkSuggestion(configuration1, false, false, TEST_UID,
+ TEST_PACKAGE_NAME);
assertNotEquals(suggestion, suggestion1);
}
@@ -163,10 +175,31 @@
configuration.SSID = TEST_SSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, false, false, 0);
+ new WifiNetworkSuggestion(configuration, false, false, TEST_UID,
+ TEST_PACKAGE_NAME);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration, false, false, 1);
+ new WifiNetworkSuggestion(configuration, false, false, TEST_UID_OTHER,
+ TEST_PACKAGE_NAME);
+
+ assertNotEquals(suggestion, suggestion1);
+ }
+
+ /**
+ * Check NetworkSuggestion equals returns {@code false} for 2 network suggestions with the same
+ * SSID, BSSID and key mgmt, but different package name.
+ */
+ @Test
+ public void testWifiNetworkSuggestionEqualsFailsWhenPackageNameIsDifferent() {
+ WifiConfiguration configuration = new WifiConfiguration();
+ configuration.SSID = TEST_SSID;
+ configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+ WifiNetworkSuggestion suggestion =
+ new WifiNetworkSuggestion(configuration, false, false, TEST_UID, TEST_PACKAGE_NAME);
+
+ WifiNetworkSuggestion suggestion1 =
+ new WifiNetworkSuggestion(configuration, false, false, TEST_UID,
+ TEST_PACKAGE_NAME_OTHER);
assertNotEquals(suggestion, suggestion1);
}