Merge "Set ApplicationInfo (long)versionCode for APEX."
diff --git a/Android.bp b/Android.bp
index e63463c..ab7d709 100644
--- a/Android.bp
+++ b/Android.bp
@@ -732,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",
@@ -790,6 +793,11 @@
],
}
+filegroup {
+ name: "framework-hidl-jarjar",
+ srcs: ["jarjar_rules_hidl.txt"],
+}
+
java_library {
name: "framework",
defaults: ["framework-defaults"],
@@ -800,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
diff --git a/api/current.txt b/api/current.txt
index 442be315..fba5597 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6823,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";
@@ -11499,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;
@@ -11507,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);
@@ -11573,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);
@@ -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();
@@ -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);
@@ -24638,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;
@@ -43041,6 +43043,15 @@
method public abstract void onScreenCall(@NonNull android.telecom.Call.Details);
method public final void provideCallIdentification(@NonNull android.telecom.Call.Details, @NonNull android.telecom.CallIdentification);
method public final void respondToCall(@NonNull android.telecom.Call.Details, @NonNull android.telecom.CallScreeningService.CallResponse);
+ field public static final String ACTION_NUISANCE_CALL_STATUS_CHANGED = "android.telecom.action.NUISANCE_CALL_STATUS_CHANGED";
+ field public static final int CALL_DURATION_LONG = 4; // 0x4
+ field public static final int CALL_DURATION_MEDIUM = 3; // 0x3
+ field public static final int CALL_DURATION_SHORT = 2; // 0x2
+ field public static final int CALL_DURATION_VERY_SHORT = 1; // 0x1
+ field public static final String EXTRA_CALL_DURATION = "android.telecom.extra.CALL_DURATION";
+ field public static final String EXTRA_CALL_HANDLE = "android.telecom.extra.CALL_HANDLE";
+ field public static final String EXTRA_CALL_TYPE = "android.telecom.extra.CALL_TYPE";
+ field public static final String EXTRA_IS_NUISANCE = "android.telecom.extra.IS_NUISANCE";
field public static final String SERVICE_INTERFACE = "android.telecom.CallScreeningService";
}
@@ -43646,6 +43657,7 @@
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, String);
method @RequiresPermission(anyOf={android.Manifest.permission.CALL_PHONE, android.Manifest.permission.MANAGE_OWN_CALLS}) public void placeCall(android.net.Uri, android.os.Bundle);
method public void registerPhoneAccount(android.telecom.PhoneAccount);
+ method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void reportNuisanceCallStatus(@NonNull android.net.Uri, boolean);
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void showInCallScreen(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void silenceRinger();
method public void unregisterPhoneAccount(android.telecom.PhoneAccountHandle);
@@ -44194,6 +44206,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();
@@ -44495,16 +44508,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
}
@@ -52634,12 +52647,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 {
@@ -52997,7 +53010,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
@@ -56844,7 +56857,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 e232227..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 {
diff --git a/api/system-current.txt b/api/system-current.txt
index cdd2d80..9537e57 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -244,6 +244,7 @@
}
public static final class R.style {
+ field public static final int Theme_DeviceDefault_DocumentsUI = 16974562; // 0x10302e2
field public static final int Theme_Leanback_FormWizard = 16974544; // 0x10302d0
}
@@ -1095,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
@@ -1109,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);
@@ -1124,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
}
}
@@ -1672,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;
@@ -3394,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 {
@@ -3569,6 +3579,10 @@
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();
@@ -5556,6 +5570,12 @@
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);
@@ -6012,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 {
@@ -6029,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 {
@@ -7641,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();
@@ -9073,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 {
diff --git a/api/test-current.txt b/api/test-current.txt
index eca7a7a..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 {
@@ -1684,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 {
@@ -1701,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 {
@@ -2273,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);
@@ -2333,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/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 1942b2e..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";
@@ -204,6 +205,7 @@
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.
@@ -4466,7 +4468,7 @@
// SIMx or eSEx.
optional string terminal = 2;
- optional string packageName = 3;
+ optional string package_name = 3;
}
/**
@@ -4497,3 +4499,25 @@
}
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/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 1063be4..92f47e7 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -8218,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/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/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 5868771f..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
@@ -4816,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/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 7cc953c..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.
@@ -9493,18 +9518,12 @@
}
/**
- * Allows the device owner or profile owner to enable or disable the backup service.
+ * Allows the device owner to enable or disable the backup service.
*
- * <p> Each user has its own backup service which manages the backup and restore mechanisms in
- * that user. Disabling the backup service will prevent data from being backed up or restored.
+ * <p> Backup service manages all backup and restore mechanisms on the device. Setting this to
+ * false will prevent data from being backed up or restored.
*
- * <p> Device owner calls this API to control backup services across all users on the device.
- * Profile owner can use this API to enable or disable the profile's backup service. However,
- * for a managed profile its backup functionality is only enabled if both the device owner
- * and the profile owner have enabled the backup service.
- *
- * <p> By default, backup service is disabled on a device with device owner, and within a
- * managed profile.
+ * <p> Backup service is off by default when device owner is present.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param enabled {@code true} to enable the backup service, {@code false} to disable it.
@@ -9520,12 +9539,7 @@
}
/**
- * Return whether the backup service is enabled by the device owner or profile owner for the
- * current user, as previously set by {@link #setBackupServiceEnabled(ComponentName, boolean)}.
- *
- * <p> Whether the backup functionality is actually enabled or not depends on settings from both
- * the current user and the device owner, please see
- * {@link #setBackupServiceEnabled(ComponentName, boolean)} for details.
+ * Return whether the backup service is enabled by the device owner.
*
* <p> Backup service manages all backup and restore mechanisms on the device.
*
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/content/Intent.java b/core/java/android/content/Intent.java
index a6b47ff..b46203c 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2377,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.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 4efcd30..9e2f316 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5772,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);
/**
@@ -5786,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
@@ -5801,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);
/**
@@ -5815,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/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/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/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/NetworkStack.java b/core/java/android/net/NetworkStack.java
index af043ee..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;
@@ -84,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.
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/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/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/Process.java b/core/java/android/os/Process.java
index f2a9adb..07c4933 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -32,16 +32,6 @@
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;
@@ -479,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.
@@ -538,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/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/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index cd3efb4..87efbf3 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -103,6 +103,24 @@
@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/Settings.java b/core/java/android/provider/Settings.java
index 643c473..fa3f54a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11506,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>
@@ -11867,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
*/
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/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 7c1465b..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) {
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 4b1d1ec..dd6231d 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -231,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/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 2014ec2..9d0c9f4 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8203,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>
@@ -8608,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) {
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/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/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 10b99ec..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;
@@ -1881,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);
@@ -2484,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/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/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/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index b28f4cd..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,6 +155,43 @@
*/
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. */
@@ -189,6 +258,51 @@
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,
@@ -259,20 +373,483 @@
*/
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/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/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 8194a92..0752efe 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -64,7 +64,6 @@
void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId);
boolean isInputMethodPickerShownForTest();
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/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_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/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/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 96b8dc2..c7cba62 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -487,6 +487,7 @@
<protected-broadcast android:name="android.security.action.TRUST_STORE_CHANGED" />
<protected-broadcast android:name="android.security.action.KEYCHAIN_CHANGED" />
<protected-broadcast android:name="android.security.action.KEY_ACCESS_CHANGED" />
+ <protected-broadcast android:name="android.telecom.action.NUISANCE_CALL_STATUS_CHANGED" />
<protected-broadcast android:name="android.telecom.action.PHONE_ACCOUNT_REGISTERED" />
<protected-broadcast android:name="android.telecom.action.PHONE_ACCOUNT_UNREGISTERED" />
<protected-broadcast android:name="android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 16d1d1a..1a21306 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -530,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>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 777886a..4235341 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2944,6 +2944,8 @@
</public-group>
<public-group type="style" first-id="0x010302e2">
+ <!-- @hide @SystemApi -->
+ <public name="Theme.DeviceDefault.DocumentsUI" />
</public-group>
<public-group type="id" first-id="0x01020046">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7517218..83982c7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1879,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" />
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 75a727b..1603508 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -1711,4 +1711,6 @@
<item name="notificationHeaderTextAppearance">@style/TextAppearance.DeviceDefault.Notification.Info</item>
</style>
+ <!-- @hide DeviceDefault theme for the DocumentsUI app. -->
+ <style name="Theme.DeviceDefault.DocumentsUI" parent="Theme.DeviceDefault.DayNight" />
</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/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 87ad3d1..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,
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/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/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/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/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/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/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/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/session/ControllerCallbackLink.java b/media/java/android/media/session/ControllerCallbackLink.java
index 2d59e44..adc14a5 100644
--- a/media/java/android/media/session/ControllerCallbackLink.java
+++ b/media/java/android/media/session/ControllerCallbackLink.java
@@ -315,12 +315,6 @@
}
private void ensureMediaControlPermission() {
- // Allow API calls from the System UI
- if (mContext.checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
- == PackageManager.PERMISSION_GRANTED) {
- return;
- }
-
// 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.
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 4896d08..1a185e9 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -481,7 +481,7 @@
* @hide
*/
@SystemApi
- ControllerLink getControllerLink() {
+ public ControllerLink getControllerLink() {
return mControllerLink;
}
diff --git a/media/java/android/media/session/SessionCallbackLink.java b/media/java/android/media/session/SessionCallbackLink.java
index 0dbf427..3bcb65c 100644
--- a/media/java/android/media/session/SessionCallbackLink.java
+++ b/media/java/android/media/session/SessionCallbackLink.java
@@ -944,12 +944,6 @@
}
private void ensureMediaControlPermission() {
- // Allow API calls from the System UI
- if (mContext.checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
- == PackageManager.PERMISSION_GRANTED) {
- return;
- }
-
// 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.
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/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/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/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/services/net/java/android/net/apf/ApfFilter.java b/packages/NetworkStack/src/android/net/apf/ApfFilter.java
similarity index 98%
rename from services/net/java/android/net/apf/ApfFilter.java
rename to packages/NetworkStack/src/android/net/apf/ApfFilter.java
index 4943952..50c4dfc 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/packages/NetworkStack/src/android/net/apf/ApfFilter.java
@@ -16,10 +16,6 @@
package android.net.apf;
-import static android.net.util.NetworkConstants.ICMPV6_ECHO_REQUEST_TYPE;
-import static android.net.util.NetworkConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
-import static android.net.util.NetworkConstants.ICMPV6_ROUTER_ADVERTISEMENT;
-import static android.net.util.NetworkConstants.ICMPV6_ROUTER_SOLICITATION;
import static android.net.util.SocketUtils.makePacketSocketAddress;
import static android.system.OsConstants.AF_PACKET;
import static android.system.OsConstants.ARPHRD_ETHER;
@@ -35,6 +31,10 @@
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;
@@ -46,7 +46,7 @@
import android.net.NetworkUtils;
import android.net.apf.ApfGenerator.IllegalInstructionException;
import android.net.apf.ApfGenerator.Register;
-import android.net.ip.IpClientCallbacks;
+import android.net.ip.IpClient.IpClientCallbacksWrapper;
import android.net.metrics.ApfProgramEvent;
import android.net.metrics.ApfStats;
import android.net.metrics.IpConnectivityLog;
@@ -337,7 +337,7 @@
private static final int APF_MAX_ETH_TYPE_BLACK_LIST_LEN = 20;
private final ApfCapabilities mApfCapabilities;
- private final IpClientCallbacks mIpClientCallback;
+ private final IpClientCallbacksWrapper mIpClientCallback;
private final InterfaceParams mInterfaceParams;
private final IpConnectivityLog mMetricsLog;
@@ -378,7 +378,7 @@
@VisibleForTesting
ApfFilter(Context context, ApfConfiguration config, InterfaceParams ifParams,
- IpClientCallbacks ipClientCallback, IpConnectivityLog log) {
+ IpClientCallbacksWrapper ipClientCallback, IpConnectivityLog log) {
mApfCapabilities = config.apfCapabilities;
mIpClientCallback = ipClientCallback;
mInterfaceParams = ifParams;
@@ -1420,7 +1420,7 @@
* filtering using APF programs.
*/
public static ApfFilter maybeCreate(Context context, ApfConfiguration config,
- InterfaceParams ifParams, IpClientCallbacks ipClientCallback) {
+ InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback) {
if (context == null || config == null || ifParams == null) return null;
ApfCapabilities apfCapabilities = config.apfCapabilities;
if (apfCapabilities == null) return null;
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/services/net/java/android/net/ip/ConnectivityPacketTracker.java b/packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java
similarity index 100%
rename from services/net/java/android/net/ip/ConnectivityPacketTracker.java
rename to packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java
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/services/net/java/android/net/ip/IpNeighborMonitor.java b/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java
similarity index 100%
rename from services/net/java/android/net/ip/IpNeighborMonitor.java
rename to packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java
diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java
similarity index 100%
rename from services/net/java/android/net/ip/IpReachabilityMonitor.java
rename to packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java
diff --git a/services/net/java/android/net/util/ConnectivityPacketSummary.java b/packages/NetworkStack/src/android/net/util/ConnectivityPacketSummary.java
similarity index 79%
rename from services/net/java/android/net/util/ConnectivityPacketSummary.java
rename to packages/NetworkStack/src/android/net/util/ConnectivityPacketSummary.java
index ec833b0..08c3f60 100644
--- a/services/net/java/android/net/util/ConnectivityPacketSummary.java
+++ b/packages/NetworkStack/src/android/net/util/ConnectivityPacketSummary.java
@@ -16,47 +16,46 @@
package android.net.util;
-import static android.net.util.NetworkConstants.ARP_HWTYPE_ETHER;
-import static android.net.util.NetworkConstants.ARP_PAYLOAD_LEN;
-import static android.net.util.NetworkConstants.ARP_REPLY;
-import static android.net.util.NetworkConstants.ARP_REQUEST;
-import static android.net.util.NetworkConstants.DHCP4_CLIENT_PORT;
-import static android.net.util.NetworkConstants.ETHER_ADDR_LEN;
-import static android.net.util.NetworkConstants.ETHER_DST_ADDR_OFFSET;
-import static android.net.util.NetworkConstants.ETHER_HEADER_LEN;
-import static android.net.util.NetworkConstants.ETHER_SRC_ADDR_OFFSET;
-import static android.net.util.NetworkConstants.ETHER_TYPE_ARP;
-import static android.net.util.NetworkConstants.ETHER_TYPE_IPV4;
-import static android.net.util.NetworkConstants.ETHER_TYPE_IPV6;
-import static android.net.util.NetworkConstants.ETHER_TYPE_OFFSET;
-import static android.net.util.NetworkConstants.ICMPV6_HEADER_MIN_LEN;
-import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR;
-import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_MIN_LENGTH;
-import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_MTU;
-import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_SLLA;
-import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_TLLA;
-import static android.net.util.NetworkConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
-import static android.net.util.NetworkConstants.ICMPV6_NEIGHBOR_SOLICITATION;
-import static android.net.util.NetworkConstants.ICMPV6_ROUTER_ADVERTISEMENT;
-import static android.net.util.NetworkConstants.ICMPV6_ROUTER_SOLICITATION;
-import static android.net.util.NetworkConstants.IPV4_ADDR_LEN;
-import static android.net.util.NetworkConstants.IPV4_DST_ADDR_OFFSET;
-import static android.net.util.NetworkConstants.IPV4_FLAGS_OFFSET;
-import static android.net.util.NetworkConstants.IPV4_FRAGMENT_MASK;
-import static android.net.util.NetworkConstants.IPV4_HEADER_MIN_LEN;
-import static android.net.util.NetworkConstants.IPV4_IHL_MASK;
-import static android.net.util.NetworkConstants.IPV4_PROTOCOL_OFFSET;
-import static android.net.util.NetworkConstants.IPV4_SRC_ADDR_OFFSET;
-import static android.net.util.NetworkConstants.IPV6_ADDR_LEN;
-import static android.net.util.NetworkConstants.IPV6_HEADER_LEN;
-import static android.net.util.NetworkConstants.IPV6_PROTOCOL_OFFSET;
-import static android.net.util.NetworkConstants.IPV6_SRC_ADDR_OFFSET;
-import static android.net.util.NetworkConstants.UDP_HEADER_LEN;
-import static android.net.util.NetworkConstants.asString;
-import static android.net.util.NetworkConstants.asUint;
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;
@@ -412,4 +411,25 @@
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 cca71e7..4080ddf 100644
--- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java
+++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
@@ -39,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;
@@ -50,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.
@@ -80,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")
@@ -138,6 +146,24 @@
}
@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) {
checkDumpPermission();
@@ -145,6 +171,33 @@
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 a3d7852..6b31b82 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
@@ -247,7 +247,6 @@
private final TelephonyManager mTelephonyManager;
private final WifiManager mWifiManager;
private final ConnectivityManager mCm;
- private final NetworkRequest mDefaultRequest;
private final IpConnectivityLog mMetricsLog;
private final Dependencies mDependencies;
@@ -336,7 +335,6 @@
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);
@@ -486,8 +484,7 @@
}
private boolean isValidationRequired() {
- return NetworkMonitorUtils.isValidationRequired(
- mDefaultRequest.networkCapabilities, mNetworkCapabilities);
+ return NetworkMonitorUtils.isValidationRequired(mNetworkCapabilities);
}
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/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/tests/net/java/android/net/apf/ApfTest.java b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java
similarity index 98%
rename from tests/net/java/android/net/apf/ApfTest.java
rename to packages/NetworkStack/tests/src/android/net/apf/ApfTest.java
index 3c3e7ce..f76e412 100644
--- a/tests/net/java/android/net/apf/ApfTest.java
+++ b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java
@@ -16,8 +16,6 @@
package android.net.apf;
-import static android.net.util.NetworkConstants.ICMPV6_ECHO_REQUEST_TYPE;
-import static android.net.util.NetworkConstants.ICMPV6_ROUTER_ADVERTISEMENT;
import static android.system.OsConstants.AF_UNIX;
import static android.system.OsConstants.ARPHRD_ETHER;
import static android.system.OsConstants.ETH_P_ARP;
@@ -28,12 +26,14 @@
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;
@@ -42,10 +42,14 @@
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;
@@ -57,8 +61,9 @@
import android.text.format.DateUtils;
import android.util.Log;
-import com.android.frameworks.tests.net.R;
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;
@@ -100,7 +105,7 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
// Load up native shared library containing APF interpreter exposed via JNI.
- System.loadLibrary("frameworksnettestsjni");
+ System.loadLibrary("networkstacktestsjni");
}
private static final String TAG = "ApfTest";
@@ -915,10 +920,14 @@
HexDump.toHexString(data, false), result);
}
- private class MockIpClientCallback extends IpClientCallbacks {
+ 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;
@@ -946,7 +955,7 @@
private final long mFixedTimeMs = SystemClock.elapsedRealtime();
public TestApfFilter(Context context, ApfConfiguration config,
- IpClientCallbacks ipClientCallback, IpConnectivityLog log) throws Exception {
+ IpClientCallbacksWrapper ipClientCallback, IpConnectivityLog log) throws Exception {
super(context, config, InterfaceParams.getByName("lo"), ipClientCallback, log);
}
@@ -1075,8 +1084,8 @@
private static final byte[] IPV4_ANY_HOST_ADDR = {0, 0, 0, 0};
// Helper to initialize a default apfFilter.
- private ApfFilter setupApfFilter(IpClientCallbacks ipClientCallback, ApfConfiguration config)
- throws Exception {
+ 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);
@@ -1294,7 +1303,7 @@
// 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);
+ 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.
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/tests/net/java/android/net/ip/IpClientTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
similarity index 93%
rename from tests/net/java/android/net/ip/IpClientTest.java
rename to packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
index 5110ce1..f21809f 100644
--- a/tests/net/java/android/net/ip/IpClientTest.java
+++ b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
@@ -16,10 +16,13 @@
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;
@@ -87,7 +90,7 @@
@Mock private INetworkManagementService mNMService;
@Mock private INetd mNetd;
@Mock private Resources mResources;
- @Mock private IpClientCallbacks mCb;
+ @Mock private IIpClientCallbacks mCb;
@Mock private AlarmManager mAlarm;
@Mock private IpClient.Dependencies mDependecies;
private MockContentResolver mContentResolver;
@@ -209,7 +212,8 @@
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(eq(makeEmptyLinkProperties(iface)));
+ .onLinkPropertiesChange(argThat(
+ lp -> fromStableParcelable(lp).equals(makeEmptyLinkProperties(iface))));
}
@Test
@@ -254,13 +258,15 @@
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));
+ 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(eq(makeEmptyLinkProperties(iface)));
+ .onLinkPropertiesChange(argThat(
+ lp -> fromStableParcelable(lp).equals(makeEmptyLinkProperties(iface))));
}
@Test
@@ -492,11 +498,11 @@
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'));
+ 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
@@ -506,11 +512,11 @@
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'));
+ 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
diff --git a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpReachabilityMonitorTest.java
similarity index 100%
rename from tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
rename to packages/NetworkStack/tests/src/android/net/ip/IpReachabilityMonitorTest.java
diff --git a/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java b/packages/NetworkStack/tests/src/android/net/util/ConnectivityPacketSummaryTest.java
similarity index 100%
rename from tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java
rename to packages/NetworkStack/tests/src/android/net/util/ConnectivityPacketSummaryTest.java
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/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/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/strings.xml b/packages/SystemUI/res/values/strings.xml
index d64e2f9..13d27bb 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2279,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/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/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 76d394d..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
@@ -32,7 +32,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.UiOffloadThread;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.StatusBarStateController;
@@ -40,6 +39,8 @@
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;
@@ -128,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.
@@ -160,6 +162,40 @@
}
};
+ /**
+ * 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,
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 0c37666..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)
@@ -6022,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/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/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/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 9f7d33f..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,
@@ -2559,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) {
@@ -2574,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() {
@@ -2594,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;
}
@@ -2614,7 +2611,7 @@
Slog.v(TAG, "calling Augmented Autofill Service ("
+ remoteService.getComponentName().toShortString() + ") on view "
+ mCurrentViewId + " using suggestion mode "
- + smartSuggestionFlagsToString(mode)
+ + getSmartSuggestionModeToString(mode)
+ " when server returned null for session " + this.id);
}
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 b9a6f3c..4f58d79 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -636,14 +636,18 @@
}
@Override
- public void opComplete(int token, long result) throws RemoteException {
- int userId = binderGetCallingUserId();
+ public void opCompleteForUser(int userId, int token, long result) throws RemoteException {
if (isUserReadyForBackup(userId)) {
- mService.opComplete(binderGetCallingUserId(), token, result);
+ 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) {
return isUserReadyForBackup(userId) ? mService.getAvailableRestoreToken(userId,
packageName) : 0;
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/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index fae7a8d..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) {
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 b3a0f64..8dcc1d5 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -849,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")
@@ -2900,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);
}
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 2ce2f8d..89ff338 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -62,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;
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/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/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index eca371a..3fd3945 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4106,25 +4106,6 @@
return mCurrentSubtype;
}
- @Override
- public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
- synchronized (mMethodMap) {
- // TODO: Make this work even for non-current users?
- if (!calledFromValidUserLocked()) {
- 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(userId);
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index a31b3b4..3222143 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -1519,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/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/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/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/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/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 085b4af..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;
@@ -46,6 +45,7 @@
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;
@@ -55,14 +55,16 @@
import java.io.File;
import java.io.IOException;
+import java.security.SecureRandom;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Map;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
+import java.util.Random;
import java.util.Set;
import java.util.function.Consumer;
@@ -82,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")
@@ -209,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;
@@ -230,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());
}
}
}
@@ -272,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.
@@ -282,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
@@ -302,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;
@@ -353,7 +355,7 @@
// 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)) {
@@ -380,12 +382,12 @@
addRecentlyExecutedRollback(rollback);
sendSuccess(statusReceiver);
- Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED,
- Uri.fromParts("package", targetPackageName,
- Manifest.permission.MANAGE_ROLLBACKS));
+ 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);
}
);
@@ -427,7 +429,7 @@
while (iter.hasNext()) {
RollbackData data = iter.next();
for (PackageRollbackInfo info : data.packages) {
- if (info.packageName.equals(packageName)) {
+ if (info.getPackageName().equals(packageName)) {
iter.remove();
mRollbackStore.deleteAvailableRollback(data);
break;
@@ -469,7 +471,15 @@
@GuardedBy("mLock")
private void loadAllRollbackDataLocked() {
mAvailableRollbacks = mRollbackStore.loadAvailableRollbacks();
+ for (RollbackData data : mAvailableRollbacks) {
+ mAllocatedRollbackIds.put(data.rollbackId, true);
+ }
+
mRecentlyExecutedRollbacks = mRollbackStore.loadRecentlyExecutedRollbacks();
+ for (RollbackInfo info : mRecentlyExecutedRollbacks) {
+ mAllocatedRollbackIds.put(info.getRollbackId(), true);
+ }
+
scheduleExpiration(0);
}
@@ -481,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();
@@ -490,8 +499,10 @@
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();
mRollbackStore.deleteAvailableRollback(data);
break;
@@ -514,7 +525,7 @@
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;
}
@@ -689,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);
@@ -701,8 +711,8 @@
Log.e(TAG, packageName + " is not installed");
return false;
}
- PackageRollbackInfo.PackageVersion installedVersion =
- new PackageRollbackInfo.PackageVersion(installedPackage.getLongVersionCode());
+ VersionedPackage installedVersion = new VersionedPackage(packageName,
+ installedPackage.getLongVersionCode());
for (int user : installedUsers) {
final int storageFlags;
@@ -723,8 +733,7 @@
}
}
- PackageRollbackInfo info = new PackageRollbackInfo(
- packageName, newVersion, installedVersion);
+ PackageRollbackInfo info = new PackageRollbackInfo(newVersion, installedVersion);
RollbackData data;
try {
@@ -732,7 +741,8 @@
mChildSessions.put(childSessionId, parentSessionId);
data = mPendingRollbacks.get(parentSessionId);
if (data == null) {
- data = mRollbackStore.createAvailableRollback();
+ int rollbackId = allocateRollbackIdLocked();
+ data = mRollbackStore.createAvailableRollback(rollbackId);
mPendingRollbacks.put(parentSessionId, data);
}
data.packages.add(info);
@@ -819,7 +829,7 @@
* 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 {
@@ -828,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 {
@@ -904,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;
}
}
@@ -912,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
index eb06bb2..7738be9 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -16,6 +16,7 @@
package com.android.server.rollback;
+import android.content.pm.VersionedPackage;
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.RollbackInfo;
import android.util.Log;
@@ -29,7 +30,6 @@
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
-import java.nio.file.Files;
import java.time.Instant;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
@@ -58,7 +58,7 @@
// base.apk
// recently_executed.json
//
- // * XXX, YYY are random strings from Files.createTempDirectory
+ // * 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.
//
@@ -113,13 +113,14 @@
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(packageName,
- new PackageRollbackInfo.PackageVersion(higherVersionCode),
- new PackageRollbackInfo.PackageVersion(lowerVersionCode));
- RollbackInfo rollback = new RollbackInfo(target);
+ 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) {
@@ -135,9 +136,9 @@
/**
* Creates a new RollbackData instance with backupDir assigned.
*/
- RollbackData createAvailableRollback() throws IOException {
- File backupDir = Files.createTempDirectory(mAvailableRollbacksDir.toPath(), null).toFile();
- return new RollbackData(backupDir);
+ RollbackData createAvailableRollback(int rollbackId) throws IOException {
+ File backupDir = new File(mAvailableRollbacksDir, Integer.toString(rollbackId));
+ return new RollbackData(rollbackId, backupDir);
}
/**
@@ -157,11 +158,14 @@
JSONArray packagesJson = new JSONArray();
for (PackageRollbackInfo info : data.packages) {
JSONObject infoJson = new JSONObject();
- infoJson.put("packageName", info.packageName);
- infoJson.put("higherVersionCode", info.higherVersion.versionCode);
- infoJson.put("lowerVersionCode", info.lowerVersion.versionCode);
+ 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());
@@ -195,9 +199,12 @@
for (int i = 0; i < recentlyExecutedRollbacks.size(); ++i) {
RollbackInfo rollback = recentlyExecutedRollbacks.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);
+ 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);
}
@@ -216,19 +223,22 @@
*/
private RollbackData loadRollbackData(File backupDir) throws IOException {
try {
- RollbackData data = new RollbackData(backupDir);
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(packageName,
- new PackageRollbackInfo.PackageVersion(higherVersionCode),
- new PackageRollbackInfo.PackageVersion(lowerVersionCode)));
+ data.packages.add(new PackageRollbackInfo(
+ new VersionedPackage(packageName, higherVersionCode),
+ new VersionedPackage(packageName, lowerVersionCode)));
}
data.timestamp = Instant.parse(dataJson.getString("timestamp"));
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/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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index fb7e47d..54053a8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -7667,7 +7667,18 @@
}
// Shutting down backup manager service permanently.
- toggleBackupServiceActive(UserHandle.USER_SYSTEM, /* makeActive= */ false);
+ long ident = mInjector.binderClearCallingIdentity();
+ try {
+ if (mInjector.getIBackupManager() != null) {
+ mInjector.getIBackupManager()
+ .setBackupServiceActive(UserHandle.USER_SYSTEM, false);
+ }
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Failed deactivating backup service.", e);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
+
if (isAdb()) {
// Log device owner provisioning was started using adb.
MetricsLogger.action(mContext, PROVISIONING_ENTRY_POINT_ADB, LOG_TAG_DEVICE_OWNER);
@@ -7695,7 +7706,7 @@
saveUserRestrictionsLocked(userId);
}
- long ident = mInjector.binderClearCallingIdentity();
+ ident = mInjector.binderClearCallingIdentity();
try {
// TODO Send to system too?
sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, userId);
@@ -7952,9 +7963,6 @@
.write();
}
- // Shutting down backup manager service permanently.
- toggleBackupServiceActive(userHandle, /* makeActive= */ false);
-
mOwners.setProfileOwner(who, ownerName, userHandle);
mOwners.writeProfileOwner(userHandle);
Slog.i(LOG_TAG, "Profile owner set: " + who + " on user " + userHandle);
@@ -7978,24 +7986,6 @@
}
}
-
- private void toggleBackupServiceActive(int userId, boolean makeActive) {
- // Shutting down backup manager service permanently.
- enforceUserUnlocked(userId);
- long ident = mInjector.binderClearCallingIdentity();
- try {
- if (mInjector.getIBackupManager() != null) {
- mInjector.getIBackupManager()
- .setBackupServiceActive(userId, makeActive);
- }
- } catch (RemoteException e) {
- throw new IllegalStateException("Failed deactivating backup service.", e);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
-
- }
-
@Override
public void clearProfileOwner(ComponentName who) {
if (!mHasFeature) {
@@ -12748,9 +12738,22 @@
return;
}
Preconditions.checkNotNull(admin);
- enforceProfileOrDeviceOwner(admin);
- int userId = mInjector.userHandleGetCallingUserId();
- toggleBackupServiceActive(userId, enabled);
+ synchronized (getLockObject()) {
+ getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ }
+
+ final long ident = mInjector.binderClearCallingIdentity();
+ try {
+ IBackupManager ibm = mInjector.getIBackupManager();
+ if (ibm != null) {
+ ibm.setBackupServiceActive(UserHandle.USER_SYSTEM, enabled);
+ }
+ } catch (RemoteException e) {
+ throw new IllegalStateException(
+ "Failed " + (enabled ? "" : "de") + "activating backup service.", e);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
}
@Override
@@ -12759,13 +12762,11 @@
if (!mHasFeature) {
return true;
}
-
- enforceProfileOrDeviceOwner(admin);
synchronized (getLockObject()) {
try {
+ getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
IBackupManager ibm = mInjector.getIBackupManager();
- return ibm != null && ibm.isBackupServiceActive(
- mInjector.userHandleGetCallingUserId());
+ return ibm != null && ibm.isBackupServiceActive(UserHandle.USER_SYSTEM);
} catch (RemoteException e) {
throw new IllegalStateException("Failed requesting backup service state.", e);
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 623990b..65eaf554 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -873,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();
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/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index 04ac9a3..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,1037 +16,14 @@
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
+ * 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;
-
- // 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/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java
index 233b86f..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,222 +16,206 @@
package android.net.ip;
-import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable;
+import static android.net.shared.LinkPropertiesParcelableUtil.toStableParcelable;
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.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.shared.InitialConfiguration;
-import android.net.util.InterfaceParams;
-import android.net.util.NetdService;
-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.ArrayUtils;
-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.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;
/**
- * TODO: remove after migrating clients to use IpClientCallbacks directly
* @see IpClientCallbacks
*/
public static class Callback extends IpClientCallbacks {}
/**
- * TODO: remove once clients are migrated to IpClientUtil.WaitForProvisioningCallback
- * @see IpClientUtil.WaitForProvisioningCallbacks
+ * IpClient callback that allows clients to block until provisioning is complete.
*/
- public static class WaitForProvisioningCallback
- extends IpClientUtil.WaitForProvisioningCallbacks {}
+ public static class WaitForProvisioningCallback extends Callback {
+ private final ConditionVariable mCV = new ConditionVariable();
+ private LinkProperties mCallbackLinkProperties;
- // 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 IpClientCallbacks {
- private static final String PREFIX = "INVOKE ";
- private final IpClientCallbacks mCallback;
-
- LoggingCallbackWrapper(IpClientCallbacks callback) {
- mCallback = (callback != null) ? callback : new IpClientCallbacks();
+ /**
+ * Block until either {@link #onProvisioningSuccess(LinkProperties)} or
+ * {@link #onProvisioningFailure(LinkProperties)} is called.
+ */
+ public LinkProperties waitForProvisioning() {
+ mCV.block();
+ return mCallbackLinkProperties;
}
- private void log(String msg) {
- mLog.log(PREFIX + msg);
- }
-
- @Override
- public void onPreDhcpAction() {
- log("onPreDhcpAction()");
- mCallback.onPreDhcpAction();
- }
- @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);
+ mCallbackLinkProperties = newLp;
+ mCV.open();
}
+
@Override
public void onProvisioningFailure(LinkProperties newLp) {
- log("onProvisioningFailure({" + newLp + "})");
- mCallback.onProvisioningFailure(newLp);
+ mCallbackLinkProperties = null;
+ mCV.open();
}
+ }
+
+ private class CallbackImpl extends IpClientUtil.IpClientCallbacksProxy {
+ /**
+ * Create a new IpClientCallbacksProxy.
+ */
+ CallbackImpl(IpClientCallbacks cb) {
+ super(cb);
+ }
+
@Override
- public void onLinkPropertiesChange(LinkProperties newLp) {
- log("onLinkPropertiesChange({" + newLp + "})");
- mCallback.onLinkPropertiesChange(newLp);
+ public void onIpClientCreated(IIpClient ipClient) {
+ mIpClient = ipClient;
+ mIpClientCv.open();
+ super.onIpClientCreated(ipClient);
}
- @Override
- public void onReachabilityLost(String logMsg) {
- log("onReachabilityLost(" + logMsg + ")");
- mCallback.onReachabilityLost(logMsg);
- }
+
@Override
public void onQuit() {
- log("onQuit()");
- mCallback.onQuit();
+ mShutdownCv.open();
+ super.onQuit();
}
- @Override
- public void installPacketFilter(byte[] filter) {
- log("installPacketFilter(byte[" + filter.length + "])");
- mCallback.installPacketFilter(filter);
+ }
+
+ /**
+ * Create a new IpClient.
+ */
+ public IpClient(Context context, String iface, Callback callback) {
+ mIpClientCv = new ConditionVariable(false);
+ mShutdownCv = new ConditionVariable(false);
+
+ IpClientUtil.makeIpClient(context, iface, new CallbackImpl(callback));
+ }
+
+ /**
+ * @see IpClient#IpClient(Context, String, IpClient.Callback)
+ */
+ public IpClient(Context context, String iface, Callback callback,
+ INetworkManagementService nms) {
+ this(context, iface, callback);
+ }
+
+ private interface IpClientAction {
+ void useIpClient(IIpClient ipClient) throws RemoteException;
+ }
+
+ private void doWithIpClient(IpClientAction action) {
+ mIpClientCv.block(IPCLIENT_BLOCK_TIMEOUT_MS);
+ try {
+ action.useIpClient(mIpClient);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error communicating with IpClient", e);
}
- @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);
- }
+ }
+
+ /**
+ * Notify IpClient that PreDhcpAction is completed.
+ */
+ public void completedPreDhcpAction() {
+ doWithIpClient(c -> c.completedPreDhcpAction());
+ }
+
+ /**
+ * Confirm the provisioning configuration.
+ */
+ public void confirmConfiguration() {
+ doWithIpClient(c -> c.confirmConfiguration());
+ }
+
+ /**
+ * 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() {
+ doWithIpClient(c -> c.shutdown());
+ }
+
+ /**
+ * 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() {
+ mShutdownCv.block(IPCLIENT_BLOCK_TIMEOUT_MS);
+ }
+
+ /**
+ * Create a new ProvisioningConfiguration.
+ */
+ public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
+ return new ProvisioningConfiguration.Builder();
}
/**
@@ -333,1430 +317,4 @@
}
}
}
-
- 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;
- // 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 IpClientCallbacks 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, IpClientCallbacks 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, IpClientCallbacks callback,
- INetworkManagementService nwService) {
- this(context, ifName, callback, new Dependencies() {
- @Override
- public INetworkManagementService getNMS() {
- return nwService;
- }
- });
- }
-
- @VisibleForTesting
- IpClient(Context context, String ifName, IpClientCallbacks 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);
- 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();
-
- // 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() {
- IpClient.this.completedPreDhcpAction();
- }
- @Override
- public void confirmConfiguration() {
- IpClient.this.confirmConfiguration();
- }
- @Override
- public void readPacketFilterComplete(byte[] data) {
- IpClient.this.readPacketFilterComplete(data);
- }
- @Override
- public void shutdown() {
- IpClient.this.shutdown();
- }
- @Override
- public void startProvisioning(ProvisioningConfigurationParcelable req) {
- IpClient.this.startProvisioning(
- android.net.shared.ProvisioningConfiguration.fromStableParcelable(req));
- }
- @Override
- public void stop() {
- IpClient.this.stop();
- }
- @Override
- public void setTcpBufferSizes(String tcpBufferSizes) {
- IpClient.this.setTcpBufferSizes(tcpBufferSizes);
- }
- @Override
- public void setHttpProxy(ProxyInfoParcelable proxyInfo) {
- IpClient.this.setHttpProxy(fromStableParcelable(proxyInfo));
- }
- @Override
- public void setMulticastFilter(boolean enabled) {
- IpClient.this.setMulticastFilter(enabled);
- }
- // TODO: remove and have IpClient logs dumped in NetworkStack dumpsys
- public void dumpIpClientLogs(FileDescriptor fd, PrintWriter pw, String[] args) {
- IpClient.this.dump(fd, pw, args);
- }
- }
-
- 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);
- }
-
- // 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.
- public void awaitShutdown() {
- try {
- mShutdownLatch.await();
- } catch (InterruptedException e) {
- mLog.e("Interrupted while awaiting shutdown: " + e);
- }
- }
-
- public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
- return new ProvisioningConfiguration.Builder();
- }
-
- /**
- * Start provisioning with the provided parameters.
- */
- public void startProvisioning(android.net.shared.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));
- }
-
- // TODO: Delete this.
- public void startProvisioning(StaticIpConfiguration staticIpConfig) {
- startProvisioning(buildProvisioningConfiguration()
- .withStaticConfiguration(staticIpConfig)
- .build());
- }
-
- public void startProvisioning() {
- startProvisioning(new android.net.shared.ProvisioningConfiguration());
- }
-
- /**
- * 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/services/net/java/android/net/ip/IpClientUtil.java b/services/net/java/android/net/ip/IpClientUtil.java
index 0aec101..2a2a67a 100644
--- a/services/net/java/android/net/ip/IpClientUtil.java
+++ b/services/net/java/android/net/ip/IpClientUtil.java
@@ -16,8 +16,15 @@
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;
@@ -31,8 +38,8 @@
* @hide
*/
public class IpClientUtil {
- // TODO: remove once IpClient dumps are moved to NetworkStack and callers don't need this arg
- public static final String DUMP_ARG = IpClient.DUMP_ARG;
+ // TODO: remove with its callers
+ public static final String DUMP_ARG = "ipclient";
/**
* Subclass of {@link IpClientCallbacks} allowing clients to block until provisioning is
@@ -69,24 +76,129 @@
*
* <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) {
- // TODO: request IpClient asynchronously from NetworkStack.
- final IpClient ipClient = new IpClient(context, ifName, callback);
- callback.onIpClientCreated(ipClient.makeConnector());
+ 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 logging from this method once IpClient logs are dumped in NetworkStack dumpsys,
- * then remove callers and delete.
+ * TODO: remove callers and delete
*/
public static void dumpIpClient(
IIpClient connector, FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!(connector instanceof IpClient.IpClientConnector)) {
- pw.println("Invalid connector");
- return;
- }
- ((IpClient.IpClientConnector) connector).dumpIpClientLogs(fd, pw, args);
+ pw.println("IpClient logs have moved to dumpsys network_stack");
}
}
diff --git a/services/net/java/android/net/ip/IpServer.java b/services/net/java/android/net/ip/IpServer.java
index 7910c9a..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;
diff --git a/services/net/java/android/net/util/NetdService.java b/services/net/java/android/net/shared/NetdService.java
similarity index 99%
rename from services/net/java/android/net/util/NetdService.java
rename to services/net/java/android/net/shared/NetdService.java
index 80b2c27..be0f5f2 100644
--- a/services/net/java/android/net/util/NetdService.java
+++ b/services/net/java/android/net/shared/NetdService.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.net.util;
+package android.net.shared;
import android.net.INetd;
import android.os.RemoteException;
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/util/InterfaceParams.java b/services/net/java/android/net/util/InterfaceParams.java
index 7b060da..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.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;
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/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/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/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/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/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/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java
index 826ad82..818ebd9 100644
--- a/telecomm/java/android/telecom/CallScreeningService.java
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -16,6 +16,7 @@
package android.telecom;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.app.Service;
@@ -32,6 +33,9 @@
import com.android.internal.telecom.ICallScreeningAdapter;
import com.android.internal.telecom.ICallScreeningService;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* This service can be implemented by the default dialer (see
* {@link TelecomManager#getDefaultDialerPackage()}) or a third party app to allow or disallow
@@ -88,6 +92,128 @@
* </pre>
*/
public abstract class CallScreeningService extends Service {
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = { "CALL_DURATION_" },
+ value = {CALL_DURATION_VERY_SHORT, CALL_DURATION_SHORT, CALL_DURATION_MEDIUM,
+ CALL_DURATION_LONG})
+ public @interface CallDuration {}
+
+ /**
+ * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the
+ * {@link CallScreeningService} the duration of a call for which the user reported the nuisance
+ * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}). The
+ * {@link CallScreeningService} can use this as a signal for training nuisance detection
+ * algorithms. The call duration is reported in coarse grained buckets to minimize exposure of
+ * identifying call log information to the {@link CallScreeningService}.
+ * <p>
+ * Indicates the call was < 3 seconds in duration.
+ */
+ public static final int CALL_DURATION_VERY_SHORT = 1;
+
+ /**
+ * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the
+ * {@link CallScreeningService} the duration of a call for which the user reported the nuisance
+ * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}). The
+ * {@link CallScreeningService} can use this as a signal for training nuisance detection
+ * algorithms. The call duration is reported in coarse grained buckets to minimize exposure of
+ * identifying call log information to the {@link CallScreeningService}.
+ * <p>
+ * Indicates the call was greater than 3 seconds, but less than 60 seconds in duration.
+ */
+ public static final int CALL_DURATION_SHORT = 2;
+
+ /**
+ * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the
+ * {@link CallScreeningService} the duration of a call for which the user reported the nuisance
+ * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}). The
+ * {@link CallScreeningService} can use this as a signal for training nuisance detection
+ * algorithms. The call duration is reported in coarse grained buckets to minimize exposure of
+ * identifying call log information to the {@link CallScreeningService}.
+ * <p>
+ * Indicates the call was greater than 60 seconds, but less than 120 seconds in duration.
+ */
+ public static final int CALL_DURATION_MEDIUM = 3;
+
+ /**
+ * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the
+ * {@link CallScreeningService} the duration of a call for which the user reported the nuisance
+ * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}). The
+ * {@link CallScreeningService} can use this as a signal for training nuisance detection
+ * algorithms. The call duration is reported in coarse grained buckets to minimize exposure of
+ * identifying call log information to the {@link CallScreeningService}.
+ * <p>
+ * Indicates the call was greater than 120 seconds.
+ */
+ public static final int CALL_DURATION_LONG = 4;
+
+ /**
+ * Telecom sends this intent to the {@link CallScreeningService} which the user has chosen to
+ * fill the call screening role when the user indicates through the default dialer whether a
+ * call is a nuisance call or not (see
+ * {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}).
+ * <p>
+ * The following extra values are provided for the call:
+ * <ol>
+ * <li>{@link #EXTRA_CALL_HANDLE} - the handle of the call.</li>
+ * <li>{@link #EXTRA_IS_NUISANCE} - {@code true} if the user reported the call as a nuisance
+ * call, {@code false} otherwise.</li>
+ * <li>{@link #EXTRA_CALL_TYPE} - reports the type of call (incoming, rejected, missed,
+ * blocked).</li>
+ * <li>{@link #EXTRA_CALL_DURATION} - the duration of the call (see
+ * {@link #EXTRA_CALL_DURATION} for valid values).</li>
+ * </ol>
+ * <p>
+ * {@link CallScreeningService} implementations which want to track whether the user reports
+ * calls are nuisance calls should use declare a broadcast receiver in their manifest for this
+ * intent.
+ * <p>
+ * Note: Only {@link CallScreeningService} implementations which have provided
+ * {@link CallIdentification} information for calls at some point will receive this intent.
+ */
+ public static final String ACTION_NUISANCE_CALL_STATUS_CHANGED =
+ "android.telecom.action.NUISANCE_CALL_STATUS_CHANGED";
+
+ /**
+ * Extra used to provide the handle of the call for
+ * {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED}. The call handle is reported as a
+ * {@link Uri}.
+ */
+ public static final String EXTRA_CALL_HANDLE = "android.telecom.extra.CALL_HANDLE";
+
+ /**
+ * Boolean extra used to indicate whether the user reported a call as a nuisance call.
+ * When {@code true}, the user reported that a call was a nuisance call, {@code false}
+ * otherwise. Sent with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED}.
+ */
+ public static final String EXTRA_IS_NUISANCE = "android.telecom.extra.IS_NUISANCE";
+
+ /**
+ * Integer extra used with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED} to report the type of
+ * call. Valid values are:
+ * <UL>
+ * <li>{@link android.provider.CallLog.Calls#MISSED_TYPE}</li>
+ * <li>{@link android.provider.CallLog.Calls#INCOMING_TYPE}</li>
+ * <li>{@link android.provider.CallLog.Calls#BLOCKED_TYPE}</li>
+ * <li>{@link android.provider.CallLog.Calls#REJECTED_TYPE}</li>
+ * </UL>
+ */
+ public static final String EXTRA_CALL_TYPE = "android.telecom.extra.CALL_TYPE";
+
+ /**
+ * Integer extra used to with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED} to report how long
+ * the call lasted. Valid values are:
+ * <UL>
+ * <LI>{@link #CALL_DURATION_VERY_SHORT}</LI>
+ * <LI>{@link #CALL_DURATION_SHORT}</LI>
+ * <LI>{@link #CALL_DURATION_MEDIUM}</LI>
+ * <LI>{@link #CALL_DURATION_LONG}</LI>
+ * </UL>
+ */
+ public static final String EXTRA_CALL_DURATION = "android.telecom.extra.CALL_DURATION";
+
/**
* The {@link Intent} that must be declared as handled by the service.
*/
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 0fe5e08..12a5344 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1970,6 +1970,33 @@
}
/**
+ * Called by the default dialer to report to Telecom when the user has marked a previous
+ * incoming call as a nuisance call or not.
+ * <p>
+ * Where the user has chosen a {@link CallScreeningService} to fill the call screening role,
+ * Telecom will notify that {@link CallScreeningService} of the user's report.
+ * <p>
+ * Requires that the caller is the default dialer app.
+ *
+ * @param handle The phone number of an incoming call which the user is reporting as either a
+ * nuisance of non-nuisance call.
+ * @param isNuisanceCall {@code true} if the user is reporting the call as a nuisance call,
+ * {@code false} if the user is reporting the call as a non-nuisance call.
+ */
+ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ public void reportNuisanceCallStatus(@NonNull Uri handle, boolean isNuisanceCall) {
+ ITelecomService service = getTelecomService();
+ if (service != null) {
+ try {
+ service.reportNuisanceCallStatus(handle, isNuisanceCall,
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelecomService#showCallScreen", e);
+ }
+ }
+ }
+
+ /**
* Handles {@link Intent#ACTION_CALL} intents trampolined from UserCallActivity.
* @param intent The {@link Intent#ACTION_CALL} intent to handle.
* @hide
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index e1d5c17..5030f90 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -285,6 +285,8 @@
*/
boolean isInEmergencyCall();
+ oneway void reportNuisanceCallStatus(in Uri address, boolean isNuisance, String callingPackage);
+
/**
* @see TelecomServiceImpl#handleCallIntent
*/
@@ -299,4 +301,5 @@
void addOrRemoveTestCallCompanionApp(String packageName, boolean isAdded);
void setTestAutoModeApp(String packageName);
+
}
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/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/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/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 ec6f4b5..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;
@@ -98,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;
@@ -116,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).
@@ -135,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
@@ -153,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);
@@ -208,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();
@@ -225,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);
@@ -278,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();
@@ -295,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);
@@ -348,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();
@@ -364,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();
}
@@ -404,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);
@@ -499,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.");
@@ -549,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);
@@ -670,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
@@ -680,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/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 1c26418..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);
diff --git a/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java
index a6b0102..163b00a 100644
--- a/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java
+++ b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java
@@ -190,21 +190,22 @@
@Test
public void testSelectClassAndSamePackage() {
- final Filter filter = mBuilder.withSelectTest(CLASS_A1, CLASS_A2, PACKAGE_A,
- CLASS_C5, CLASS_C6, PACKAGE_C).build();
- acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_C);
+ 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_A1L, METHOD_A2M, CLASS_A1,
- CLASS_B3, METHOD_B3P, METHOD_B3Q, METHOD_B4R).build();
+ 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,
+ 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);
}
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/**/*",
],