diff options
359 files changed, 9043 insertions, 2631 deletions
diff --git a/Android.bp b/Android.bp index 9fb108034d4f..995fe3e2465e 100644 --- a/Android.bp +++ b/Android.bp @@ -602,6 +602,7 @@ java_defaults { "telephony/java/com/android/internal/telephony/IOnSubscriptionsChangedListener.aidl", "telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl", "telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl", + "telephony/java/com/android/internal/telephony/ISetOpportunisticDataCallback.aidl", "telephony/java/com/android/internal/telephony/ISms.aidl", "telephony/java/com/android/internal/telephony/ISub.aidl", "telephony/java/com/android/internal/telephony/IOns.aidl", @@ -741,10 +742,8 @@ java_defaults { 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.1-java", "android.hardware.cas-V1.0-java", @@ -774,7 +773,7 @@ java_defaults { "android.hardware.vibrator-V1.3-java", "android.hardware.wifi-V1.0-java-constants", "networkstack-aidl-interfaces-java", - "netd_aidl_interface-java", + "netd_aidl_parcelables-java", "devicepolicyprotosnano", ], @@ -1188,12 +1187,21 @@ packages_to_document = [ "org/apache/http/params", ] +// Make the api/current.txt file available for use by modules in other +// directories. +filegroup { + name: "frameworks-base-api-current.txt", + srcs: [ + "api/current.txt", + ], +} + framework_docs_only_args = " -android -manifest $(location core/res/AndroidManifest.xml) " + "-werror -lerror -hide 111 -hide 113 -hide 125 -hide 126 -hide 127 -hide 128 " + "-overview $(location core/java/overview.html) " + // Federate Support Library references against local API file. "-federate SupportLib https://developer.android.com " + - "-federationapi SupportLib $(location current/support-api.txt) " + "-federationapi SupportLib $(location :current-support-api) " framework_docs_only_libs = [ "voip-common", diff --git a/Android.mk b/Android.mk index 9a91dd1c491a..c58f7af1d7d5 100644 --- a/Android.mk +++ b/Android.mk @@ -77,8 +77,6 @@ docs offline-sdk-docs: $(OUT_DOCS)/offline-sdk-timestamp # Run this for checkbuild checkbuild: doc-comment-check-docs -# Check comment when you are updating the API -update-api: doc-comment-check-docs # ==== hiddenapi lists ======================================= ifneq ($(UNSAFE_DISABLE_HIDDENAPI_FLAGS),true) diff --git a/apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java b/apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java index 92348497101e..0c30302d63a3 100644 --- a/apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java +++ b/apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java @@ -40,7 +40,7 @@ public class KernelCpuThreadReaderPerfTest { public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); private final KernelCpuThreadReader mKernelCpuThreadReader = - KernelCpuThreadReader.create(8, uid -> 1000 <= uid && uid < 2000); + KernelCpuThreadReader.create(8, uid -> 1000 <= uid && uid < 2000, 0); @Test public void timeReadCurrentProcessCpuUsage() { diff --git a/api/current.txt b/api/current.txt index a256c49859b8..d11b6a4c6525 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6684,7 +6684,7 @@ package android.app.admin { method public boolean installKeyPair(@Nullable android.content.ComponentName, @NonNull java.security.PrivateKey, @NonNull java.security.cert.Certificate, @NonNull String); method public boolean installKeyPair(@Nullable android.content.ComponentName, @NonNull java.security.PrivateKey, @NonNull java.security.cert.Certificate[], @NonNull String, boolean); method public boolean installKeyPair(@Nullable android.content.ComponentName, @NonNull java.security.PrivateKey, @NonNull java.security.cert.Certificate[], @NonNull String, int); - method public void installSystemUpdate(@NonNull android.content.ComponentName, @NonNull android.net.Uri, @NonNull java.util.concurrent.Executor, @NonNull android.app.admin.DevicePolicyManager.InstallUpdateCallback); + method public void installSystemUpdate(@NonNull android.content.ComponentName, @NonNull android.net.Uri, @NonNull java.util.concurrent.Executor, @NonNull android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback); method public boolean isActivePasswordSufficient(); method public boolean isAdminActive(@NonNull android.content.ComponentName); method public boolean isAffiliatedUser(); @@ -6951,8 +6951,8 @@ package android.app.admin { field public static final int WIPE_SILENTLY = 8; // 0x8 } - public abstract static class DevicePolicyManager.InstallUpdateCallback { - ctor public DevicePolicyManager.InstallUpdateCallback(); + public abstract static class DevicePolicyManager.InstallSystemUpdateCallback { + ctor public DevicePolicyManager.InstallSystemUpdateCallback(); method public void onInstallUpdateError(int, String); field public static final int UPDATE_ERROR_BATTERY_LOW = 5; // 0x5 field public static final int UPDATE_ERROR_FILE_NOT_FOUND = 4; // 0x4 @@ -9517,7 +9517,6 @@ package android.content { method public static void cancelSync(android.content.SyncRequest); method @Nullable public final android.net.Uri canonicalize(@NonNull android.net.Uri); method public final int delete(@RequiresPermission.Write @NonNull android.net.Uri, @Nullable String, @Nullable String[]); - method public android.os.Bundle getCache(android.net.Uri); method @Deprecated public static android.content.SyncInfo getCurrentSync(); method public static java.util.List<android.content.SyncInfo> getCurrentSyncs(); method public static int getIsSyncable(android.accounts.Account, String); @@ -9548,7 +9547,6 @@ package android.content { method @Nullable public final android.content.res.AssetFileDescriptor openTypedAssetFile(@NonNull android.net.Uri, @NonNull String, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException; method @Nullable public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(@NonNull android.net.Uri, @NonNull String, @Nullable android.os.Bundle) throws java.io.FileNotFoundException; method @Nullable public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(@NonNull android.net.Uri, @NonNull String, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException; - method public void putCache(android.net.Uri, android.os.Bundle); method @Nullable public final android.database.Cursor query(@RequiresPermission.Read @NonNull android.net.Uri, @Nullable String[], @Nullable String, @Nullable String[], @Nullable String); method @Nullable public final android.database.Cursor query(@RequiresPermission.Read @NonNull android.net.Uri, @Nullable String[], @Nullable String, @Nullable String[], @Nullable String, @Nullable android.os.CancellationSignal); method @Nullable public final android.database.Cursor query(@RequiresPermission.Read @NonNull android.net.Uri, @Nullable String[], @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal); @@ -22676,6 +22674,7 @@ package android.location { field public static final int MULTIPATH_INDICATOR_DETECTED = 1; // 0x1 field public static final int MULTIPATH_INDICATOR_NOT_DETECTED = 2; // 0x2 field public static final int MULTIPATH_INDICATOR_UNKNOWN = 0; // 0x0 + field public static final int STATE_2ND_CODE_LOCK = 65536; // 0x10000 field public static final int STATE_BDS_D2_BIT_SYNC = 256; // 0x100 field public static final int STATE_BDS_D2_SUBFRAME_SYNC = 512; // 0x200 field public static final int STATE_BIT_SYNC = 2; // 0x2 @@ -27446,6 +27445,7 @@ package android.media.session { method public void seekTo(long); method public void sendCustomAction(@NonNull android.media.session.PlaybackState.CustomAction, @Nullable android.os.Bundle); method public void sendCustomAction(@NonNull String, @Nullable android.os.Bundle); + method public void setPlaybackSpeed(float); method public void setRating(android.media.Rating); method public void skipToNext(); method public void skipToPrevious(); @@ -27496,6 +27496,7 @@ package android.media.session { method public void onPrepareFromUri(android.net.Uri, android.os.Bundle); method public void onRewind(); method public void onSeekTo(long); + method public void onSetPlaybackSpeed(float); method public void onSetRating(@NonNull android.media.Rating); method public void onSkipToNext(); method public void onSkipToPrevious(); @@ -44382,6 +44383,7 @@ package android.telephony { method public int getChannelNumber(); method public String getMccString(); method public String getMncString(); + method public long getNci(); method public int getPci(); method public int getTac(); method public void writeToParcel(android.os.Parcel, int); @@ -44395,6 +44397,7 @@ package android.telephony { method public String getMccString(); method public String getMncString(); method @Nullable public String getMobileNetworkOperator(); + method public int getUarfcn(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.telephony.CellIdentityTdscdma> CREATOR; } @@ -45069,12 +45072,12 @@ package android.telephony { method public boolean canChangeDtmfToneLength(); method @Nullable public android.telephony.TelephonyManager createForPhoneAccountHandle(android.telecom.PhoneAccountHandle); method public android.telephony.TelephonyManager createForSubscriptionId(int); - method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public java.util.List<android.telephony.CellInfo> getAllCellInfo(); + method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public java.util.List<android.telephony.CellInfo> getAllCellInfo(); method public int getCallState(); method public int getCardIdForDefaultEuicc(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) @WorkerThread public android.os.PersistableBundle getCarrierConfig(); method public int getCarrierIdFromSimMccMnc(); - method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.CellLocation getCellLocation(); + method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public android.telephony.CellLocation getCellLocation(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) @Nullable public java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>> getCurrentEmergencyNumberList(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) @Nullable public java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>> getCurrentEmergencyNumberList(int); method public int getDataActivity(); @@ -45104,7 +45107,7 @@ package android.telephony { method public int getPhoneCount(); method public int getPhoneType(); method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription(); - method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telephony.ServiceState getServiceState(); + method @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState(); method @Nullable public android.telephony.SignalStrength getSignalStrength(); method public int getSimCarrierId(); method @Nullable public CharSequence getSimCarrierIdName(); @@ -45146,8 +45149,8 @@ package android.telephony { method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle); method public boolean isWorldPhone(); method public void listen(android.telephony.PhoneStateListener, int); - method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void requestCellInfoUpdate(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback); + method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestCellInfoUpdate(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback); + method @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback); method public void sendDialerSpecialCode(String); method public String sendEnvelopeWithStatus(String); method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void sendUssdRequest(String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler); @@ -45216,7 +45219,6 @@ package android.telephony { field public static final String EXTRA_STATE_RINGING; field public static final String EXTRA_SUBSCRIPTION_ID = "android.telephony.extra.SUBSCRIPTION_ID"; field public static final String EXTRA_VOICEMAIL_NUMBER = "android.telephony.extra.VOICEMAIL_NUMBER"; - field public static final int INVALID_CARD_ID = -1; // 0xffffffff field public static final String METADATA_HIDE_VOICEMAIL_SETTINGS_MENU = "android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU"; field public static final int NETWORK_TYPE_1xRTT = 7; // 0x7 field public static final int NETWORK_TYPE_CDMA = 4; // 0x4 @@ -45242,6 +45244,9 @@ package android.telephony { field public static final int PHONE_TYPE_GSM = 1; // 0x1 field public static final int PHONE_TYPE_NONE = 0; // 0x0 field public static final int PHONE_TYPE_SIP = 3; // 0x3 + field public static final int SET_OPPORTUNISTIC_SUB_INVALID_PARAMETER = 2; // 0x2 + field public static final int SET_OPPORTUNISTIC_SUB_SUCCESS = 0; // 0x0 + field public static final int SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED = 1; // 0x1 field public static final int SIM_STATE_ABSENT = 1; // 0x1 field public static final int SIM_STATE_CARD_IO_ERROR = 8; // 0x8 field public static final int SIM_STATE_CARD_RESTRICTED = 9; // 0x9 @@ -45252,7 +45257,9 @@ package android.telephony { field public static final int SIM_STATE_PUK_REQUIRED = 3; // 0x3 field public static final int SIM_STATE_READY = 5; // 0x5 field public static final int SIM_STATE_UNKNOWN = 0; // 0x0 + field public static final int UNINITIALIZED_CARD_ID = -2; // 0xfffffffe field public static final int UNKNOWN_CARRIER_ID = -1; // 0xffffffff + field public static final int UNSUPPORTED_CARD_ID = -1; // 0xffffffff field public static final int USSD_ERROR_SERVICE_UNAVAIL = -2; // 0xfffffffe field public static final int USSD_RETURN_FAILURE = -1; // 0xffffffff field public static final String VVM_TYPE_CVVM = "vvm_type_cvvm"; @@ -48994,6 +49001,7 @@ package android.util { } public final class StatsLog { + method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public static boolean logBinaryPushStateChanged(@NonNull String, long, int, int, @NonNull long[]); method public static boolean logEvent(int); method public static boolean logStart(int); method public static boolean logStop(int); @@ -50801,7 +50809,7 @@ package android.view { method @android.view.ViewDebug.ExportedProperty(category="drawing") public float getAlpha(); method public android.view.animation.Animation getAnimation(); method public android.os.IBinder getApplicationWindowToken(); - method @NonNull public java.util.List<java.lang.Integer> getAttributeResolutionStack(); + method @NonNull public java.util.List<java.lang.Integer> getAttributeResolutionStack(@AttrRes int); method @NonNull public java.util.Map<java.lang.Integer,java.lang.Integer> getAttributeSourceResourceMap(); method @android.view.ViewDebug.ExportedProperty @Nullable public String[] getAutofillHints(); method public final android.view.autofill.AutofillId getAutofillId(); @@ -50851,6 +50859,8 @@ package android.view { method public void getHitRect(android.graphics.Rect); method public int getHorizontalFadingEdgeLength(); method protected int getHorizontalScrollbarHeight(); + method @Nullable public android.graphics.drawable.Drawable getHorizontalScrollbarThumbDrawable(); + method @Nullable public android.graphics.drawable.Drawable getHorizontalScrollbarTrackDrawable(); method @android.view.ViewDebug.CapturedViewProperty @IdRes public int getId(); method @android.view.ViewDebug.ExportedProperty(category="accessibility", mapping={@android.view.ViewDebug.IntToString(from=android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, to="auto"), @android.view.ViewDebug.IntToString(from=android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES, to="yes"), @android.view.ViewDebug.IntToString(from=android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO, to="no"), @android.view.ViewDebug.IntToString(from=android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS, to="noHideDescendants")}) public int getImportantForAccessibility(); method @android.view.ViewDebug.ExportedProperty(mapping={@android.view.ViewDebug.IntToString(from=android.view.View.IMPORTANT_FOR_AUTOFILL_AUTO, to="auto"), @android.view.ViewDebug.IntToString(from=android.view.View.IMPORTANT_FOR_AUTOFILL_YES, to="yes"), @android.view.ViewDebug.IntToString(from=android.view.View.IMPORTANT_FOR_AUTOFILL_NO, to="no"), @android.view.ViewDebug.IntToString(from=android.view.View.IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS, to="yesExcludeDescendants"), @android.view.ViewDebug.IntToString(from=android.view.View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS, to="noExcludeDescendants")}) public int getImportantForAutofill(); @@ -50941,6 +50951,8 @@ package android.view { method public long getUniqueDrawingId(); method public int getVerticalFadingEdgeLength(); method public int getVerticalScrollbarPosition(); + method @Nullable public android.graphics.drawable.Drawable getVerticalScrollbarThumbDrawable(); + method @Nullable public android.graphics.drawable.Drawable getVerticalScrollbarTrackDrawable(); method public int getVerticalScrollbarWidth(); method public android.view.ViewTreeObserver getViewTreeObserver(); method @android.view.ViewDebug.ExportedProperty(mapping={@android.view.ViewDebug.IntToString(from=android.view.View.VISIBLE, to="VISIBLE"), @android.view.ViewDebug.IntToString(from=android.view.View.INVISIBLE, to="INVISIBLE"), @android.view.ViewDebug.IntToString(from=android.view.View.GONE, to="GONE")}) public int getVisibility(); @@ -51184,6 +51196,8 @@ package android.view { method public void setHasTransientState(boolean); method public void setHorizontalFadingEdgeEnabled(boolean); method public void setHorizontalScrollBarEnabled(boolean); + method public void setHorizontalScrollbarThumbDrawable(@Nullable android.graphics.drawable.Drawable); + method public void setHorizontalScrollbarTrackDrawable(@Nullable android.graphics.drawable.Drawable); method public void setHovered(boolean); method public void setId(@IdRes int); method public void setImportantForAccessibility(int); @@ -51273,6 +51287,8 @@ package android.view { method public void setVerticalFadingEdgeEnabled(boolean); method public void setVerticalScrollBarEnabled(boolean); method public void setVerticalScrollbarPosition(int); + method public void setVerticalScrollbarThumbDrawable(@Nullable android.graphics.drawable.Drawable); + method public void setVerticalScrollbarTrackDrawable(@Nullable android.graphics.drawable.Drawable); method public void setVisibility(int); method @Deprecated public void setWillNotCacheDrawing(boolean); method public void setWillNotDraw(boolean); @@ -51731,6 +51747,7 @@ package android.view { method public android.view.View getChildAt(int); method public int getChildCount(); method protected int getChildDrawingOrder(int, int); + method public final int getChildDrawingOrder(int); method public static int getChildMeasureSpec(int, int, int); method protected boolean getChildStaticTransformation(android.view.View, android.view.animation.Transformation); method public boolean getChildVisibleRect(android.view.View, android.graphics.Rect, android.graphics.Point); @@ -53459,6 +53476,7 @@ package android.view.contentcapture { method public void close(); method @NonNull public final android.view.contentcapture.ContentCaptureSession createContentCaptureSession(@NonNull android.view.contentcapture.ContentCaptureContext); method public final void destroy(); + method @Nullable public final android.view.contentcapture.ContentCaptureContext getContentCaptureContext(); method public final android.view.contentcapture.ContentCaptureSessionId getContentCaptureSessionId(); 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); @@ -53466,6 +53484,7 @@ package android.view.contentcapture { method public final void notifyViewDisappeared(@NonNull android.view.autofill.AutofillId); method public final void notifyViewTextChanged(@NonNull android.view.autofill.AutofillId, @Nullable CharSequence); method public final void notifyViewsDisappeared(@NonNull android.view.autofill.AutofillId, @NonNull long[]); + method public final void setContentCaptureContext(@Nullable android.view.contentcapture.ContentCaptureContext); } public final class ContentCaptureSessionId implements android.os.Parcelable { diff --git a/api/removed.txt b/api/removed.txt index f5bd434c3cc0..c4ed871d0661 100644 --- a/api/removed.txt +++ b/api/removed.txt @@ -558,7 +558,7 @@ package android.telephony { public class TelephonyManager { method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public java.util.List<android.telephony.NeighboringCellInfo> getNeighboringCellInfo(); - method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, android.telephony.TelephonyScanManager.NetworkScanCallback); + method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, android.telephony.TelephonyScanManager.NetworkScanCallback); } } diff --git a/api/system-current.txt b/api/system-current.txt index c660edfc7e05..4eb285b6e99c 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -641,7 +641,7 @@ package android.app.admin { method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean packageHasActiveAdmins(String); method @Deprecated @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException; method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied(); - method public void setProfileOwnerCanAccessDeviceIdsForUser(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle); + method @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIdsForUser(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle); field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED"; field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED"; field public static final String ACTION_PROVISION_FINALIZATION = "android.app.action.PROVISION_FINALIZATION"; @@ -1283,7 +1283,9 @@ package android.content { } public abstract class ContentResolver implements android.content.ContentInterface { + method public android.os.Bundle getCache(android.net.Uri); method public android.graphics.drawable.Drawable getTypeDrawable(String); + method public void putCache(android.net.Uri, android.os.Bundle); } public abstract class Context { @@ -1578,7 +1580,7 @@ package android.content.pm { method @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public abstract void revokeRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle); method public void sendDeviceCustomizationReadyBroadcast(); method @RequiresPermission(allOf={android.Manifest.permission.SET_PREFERRED_APPLICATIONS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public abstract boolean setDefaultBrowserPackageNameAsUser(String, int); - method @NonNull @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] setDistractingPackageRestrictions(@NonNull String[], @android.content.pm.PackageManager.DistractionRestriction int); + method @NonNull @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] setDistractingPackageRestrictions(@NonNull String[], int); method @RequiresPermission(android.Manifest.permission.SET_HARMFUL_APP_WARNINGS) public void setHarmfulAppWarning(@NonNull String, @Nullable CharSequence); method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable String); method @Nullable @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable android.content.pm.SuspendDialogInfo); @@ -1656,9 +1658,6 @@ package android.content.pm { method public abstract void onDexModuleRegistered(String, boolean, String); } - @IntDef(flag=true, prefix={"RESTRICTION_"}, value={android.content.pm.PackageManager.RESTRICTION_NONE, android.content.pm.PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS, android.content.pm.PackageManager.RESTRICTION_HIDE_NOTIFICATIONS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.DistractionRestriction { - } - public static interface PackageManager.OnPermissionsChangedListener { method public void onPermissionsChanged(int); } @@ -5780,6 +5779,13 @@ package android.provider { field public static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled"; } + public static interface DeviceConfig.Rollback { + field public static final String BOOT_NAMESPACE = "rollback_boot"; + field public static final String ENABLE_ROLLBACK_TIMEOUT = "enable_rollback_timeout"; + field public static final String NAMESPACE = "rollback"; + field public static final String ROLLBACK_LIFETIME_IN_MILLIS = "rollback_lifetime_in_millis"; + } + public static interface DeviceConfig.Runtime { field public static final String NAMESPACE = "runtime"; field public static final String USE_PRECOMPILED_LAYOUT = "view.precompiled_layout_enabled"; @@ -5939,6 +5945,7 @@ package android.provider { public final class Settings { field public static final String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS"; field public static final String ACTION_LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS = "android.settings.LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS"; + field public static final String ACTION_REQUEST_ENABLE_CONTENT_CAPTURE = "android.settings.REQUEST_ENABLE_CONTENT_CAPTURE"; field public static final String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS"; } @@ -6958,6 +6965,7 @@ package android.telecom { field public static final String EXTRA_CALL_BACK_INTENT = "android.telecom.extra.CALL_BACK_INTENT"; field public static final String EXTRA_CLEAR_MISSED_CALLS_INTENT = "android.telecom.extra.CLEAR_MISSED_CALLS_INTENT"; field public static final String EXTRA_CONNECTION_SERVICE = "android.telecom.extra.CONNECTION_SERVICE"; + field public static final String EXTRA_IS_USER_INTENT_EMERGENCY_CALL = "android.telecom.extra.IS_USER_INTENT_EMERGENCY_CALL"; field public static final int TTY_MODE_FULL = 1; // 0x1 field public static final int TTY_MODE_HCO = 2; // 0x2 field public static final int TTY_MODE_OFF = 0; // 0x0 @@ -7751,6 +7759,7 @@ package android.telephony { method public void requestEmbeddedSubscriptionInfoListRefresh(int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultDataSubId(int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultSmsSubId(int); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPreferredDataSubscriptionId(int, boolean, @NonNull java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setSubscriptionEnabled(int, boolean); field public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI; field public static final int PROFILE_CLASS_DEFAULT = -1; // 0xffffffff @@ -7843,7 +7852,7 @@ package android.telephony { method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isVisualVoicemailEnabled(android.telecom.PhoneAccountHandle); method public boolean needsOtaServiceProvisioning(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio(); - method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback); + method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestNumberVerification(@NonNull android.telephony.PhoneNumberRange, long, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.NumberVerificationCallback); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean resetRadioConfig(); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>); @@ -8231,6 +8240,7 @@ package android.telephony.ims { method public int getServiceType(); method public static int getVideoStateFromCallType(int); method public static int getVideoStateFromImsCallProfile(android.telephony.ims.ImsCallProfile); + method public boolean hasKnownUserIntentEmergency(); method public boolean isEmergencyCallTesting(); method public boolean isVideoCall(); method public boolean isVideoPaused(); @@ -8243,6 +8253,7 @@ package android.telephony.ims { method public void setEmergencyCallTesting(boolean); method public void setEmergencyServiceCategories(int); method public void setEmergencyUrns(java.util.List<java.lang.String>); + method public void setHasKnownUserIntentEmergency(boolean); method public void updateCallExtras(android.telephony.ims.ImsCallProfile); method public void updateCallType(android.telephony.ims.ImsCallProfile); method public void updateMediaProfile(android.telephony.ims.ImsCallProfile); @@ -9307,6 +9318,7 @@ package android.view.contentcapture { public final class ContentCaptureEvent implements android.os.Parcelable { method public int describeContents(); + method @Nullable public android.view.contentcapture.ContentCaptureContext getContentCaptureContext(); method public long getEventTime(); method @Nullable public android.view.autofill.AutofillId getId(); method @Nullable public java.util.List<android.view.autofill.AutofillId> getIds(); @@ -9315,6 +9327,7 @@ package android.view.contentcapture { method @Nullable public android.view.contentcapture.ViewNode getViewNode(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.view.contentcapture.ContentCaptureEvent> CREATOR; + field public static final int TYPE_CONTEXT_UPDATED = 6; // 0x6 field public static final int TYPE_INITIAL_VIEW_TREE_APPEARED = 5; // 0x5 field public static final int TYPE_INITIAL_VIEW_TREE_APPEARING = 4; // 0x4 field public static final int TYPE_VIEW_APPEARED = 1; // 0x1 diff --git a/api/test-current.txt b/api/test-current.txt index 37d34eb5deab..8194785fd991 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1815,6 +1815,7 @@ package android.provider { public final class Settings { field public static final String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS"; + field public static final String ACTION_REQUEST_ENABLE_CONTENT_CAPTURE = "android.settings.REQUEST_ENABLE_CONTENT_CAPTURE"; field public static final int RESET_MODE_PACKAGE_DEFAULTS = 1; // 0x1 } @@ -2716,6 +2717,7 @@ package android.view.contentcapture { public final class ContentCaptureEvent implements android.os.Parcelable { method public int describeContents(); + method @Nullable public android.view.contentcapture.ContentCaptureContext getContentCaptureContext(); method public long getEventTime(); method @Nullable public android.view.autofill.AutofillId getId(); method @Nullable public java.util.List<android.view.autofill.AutofillId> getIds(); @@ -2724,6 +2726,7 @@ package android.view.contentcapture { method @Nullable public android.view.contentcapture.ViewNode getViewNode(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.view.contentcapture.ContentCaptureEvent> CREATOR; + field public static final int TYPE_CONTEXT_UPDATED = 6; // 0x6 field public static final int TYPE_INITIAL_VIEW_TREE_APPEARED = 5; // 0x5 field public static final int TYPE_INITIAL_VIEW_TREE_APPEARING = 4; // 0x4 field public static final int TYPE_VIEW_APPEARED = 1; // 0x1 diff --git a/cmds/bootanimation/bootanim.rc b/cmds/bootanimation/bootanim.rc index 1b3c32b20503..469c9646a4aa 100644 --- a/cmds/bootanimation/bootanim.rc +++ b/cmds/bootanimation/bootanim.rc @@ -2,7 +2,6 @@ service bootanim /system/bin/bootanimation class core animation user graphics group graphics audio - updatable disabled oneshot writepid /dev/stune/top-app/tasks diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index faf2053835fa..f4086557870d 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -76,6 +76,7 @@ cc_defaults { "src/external/SubsystemSleepStatePuller.cpp", "src/external/PowerStatsPuller.cpp", "src/external/ResourceHealthManagerPuller.cpp", + "src/external/TrainInfoPuller.cpp", "src/external/StatsPullerManager.cpp", "src/external/puller_util.cpp", "src/logd/LogEvent.cpp", @@ -238,6 +239,7 @@ cc_test { "tests/guardrail/StatsdStats_test.cpp", "tests/metrics/metrics_test_helper.cpp", "tests/statsd_test_util.cpp", + "tests/storage/StorageManager_test.cpp", "tests/e2e/WakelockDuration_e2e_test.cpp", "tests/e2e/MetricActivation_e2e_test.cpp", "tests/e2e/MetricConditionLink_e2e_test.cpp", diff --git a/cmds/statsd/benchmark/metric_util.cpp b/cmds/statsd/benchmark/metric_util.cpp index 067b6eddf254..cca6d525ea16 100644 --- a/cmds/statsd/benchmark/metric_util.cpp +++ b/cmds/statsd/benchmark/metric_util.cpp @@ -367,7 +367,8 @@ sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const Stat sp<AlarmMonitor> periodicAlarmMonitor; sp<StatsLogProcessor> processor = new StatsLogProcessor(uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, - timeBaseSec * NS_PER_SEC, [](const ConfigKey&) { return true; }); + timeBaseSec * NS_PER_SEC, [](const ConfigKey&) { return true; }, + [](const int&, const vector<int64_t>&) { return true; }); processor->OnConfigUpdated(timeBaseSec * NS_PER_SEC, key, config); return processor; } @@ -393,4 +394,4 @@ int64_t StringToId(const string& str) { } // namespace statsd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index dd18bd4cc8ad..653ef2ec9869 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -88,12 +88,15 @@ StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap, const sp<AlarmMonitor>& anomalyAlarmMonitor, const sp<AlarmMonitor>& periodicAlarmMonitor, const int64_t timeBaseNs, - const std::function<bool(const ConfigKey&)>& sendBroadcast) + const std::function<bool(const ConfigKey&)>& sendBroadcast, + const std::function<bool( + const int&, const vector<int64_t>&)>& activateBroadcast) : mUidMap(uidMap), mPullerManager(pullerManager), mAnomalyAlarmMonitor(anomalyAlarmMonitor), mPeriodicAlarmMonitor(periodicAlarmMonitor), mSendBroadcast(sendBroadcast), + mSendActivationBroadcast(activateBroadcast), mTimeBaseNs(timeBaseNs), mLargestTimestampSeen(0), mLastTimestampSeen(0) { @@ -223,11 +226,73 @@ void StatsLogProcessor::OnLogEvent(LogEvent* event) { mapIsolatedUidToHostUidIfNecessaryLocked(event); } + std::unordered_set<int> uidsWithActiveConfigsChanged; + std::unordered_map<int, std::vector<int64_t>> activeConfigsPerUid; // pass the event to metrics managers. for (auto& pair : mMetricsManagers) { + int uid = pair.first.GetUid(); + int64_t configId = pair.first.GetId(); + bool isPrevActive = pair.second->isActive(); pair.second->onLogEvent(*event); + bool isCurActive = pair.second->isActive(); + // Map all active configs by uid. + if (isCurActive) { + auto activeConfigs = activeConfigsPerUid.find(uid); + if (activeConfigs != activeConfigsPerUid.end()) { + activeConfigs->second.push_back(configId); + } else { + vector<int64_t> newActiveConfigs; + newActiveConfigs.push_back(configId); + activeConfigsPerUid[uid] = newActiveConfigs; + } + } + // The activation state of this config changed. + if (isPrevActive != isCurActive) { + VLOG("Active status changed for uid %d", uid); + uidsWithActiveConfigsChanged.insert(uid); + StatsdStats::getInstance().noteActiveStatusChanged(pair.first, isCurActive); + } flushIfNecessaryLocked(event->GetElapsedTimestampNs(), pair.first, *(pair.second)); } + + for (int uid : uidsWithActiveConfigsChanged) { + // Send broadcast so that receivers can pull data. + auto lastBroadcastTime = mLastActivationBroadcastTimes.find(uid); + if (lastBroadcastTime != mLastActivationBroadcastTimes.end()) { + if (currentTimestampNs - lastBroadcastTime->second < + StatsdStats::kMinActivationBroadcastPeriodNs) { + VLOG("StatsD would've sent an activation broadcast but the rate limit stopped us."); + return; + } + } + auto activeConfigs = activeConfigsPerUid.find(uid); + if (activeConfigs != activeConfigsPerUid.end()) { + if (mSendActivationBroadcast(uid, activeConfigs->second)) { + VLOG("StatsD sent activation notice for uid %d", uid); + mLastActivationBroadcastTimes[uid] = currentTimestampNs; + } + } else { + std::vector<int64_t> emptyActiveConfigs; + if (mSendActivationBroadcast(uid, emptyActiveConfigs)) { + VLOG("StatsD sent EMPTY activation notice for uid %d", uid); + mLastActivationBroadcastTimes[uid] = currentTimestampNs; + } + } + } +} + +void StatsLogProcessor::GetActiveConfigs(const int uid, vector<int64_t>& outActiveConfigs) { + std::lock_guard<std::mutex> lock(mMetricsMutex); + GetActiveConfigsLocked(uid, outActiveConfigs); +} + +void StatsLogProcessor::GetActiveConfigsLocked(const int uid, vector<int64_t>& outActiveConfigs) { + outActiveConfigs.clear(); + for (auto& pair : mMetricsManagers) { + if (pair.first.GetUid() == uid && pair.second->isActive()) { + outActiveConfigs.push_back(pair.first.GetId()); + } + } } void StatsLogProcessor::OnConfigUpdated(const int64_t timestampNs, const ConfigKey& key, @@ -444,6 +509,18 @@ void StatsLogProcessor::OnConfigRemoved(const ConfigKey& key) { mLastBroadcastTimes.erase(key); + int uid = key.GetUid(); + bool lastConfigForUid = true; + for (auto it : mMetricsManagers) { + if (it.first.GetUid() == uid) { + lastConfigForUid = false; + break; + } + } + if (lastConfigForUid) { + mLastActivationBroadcastTimes.erase(uid); + } + if (mMetricsManagers.empty()) { mPullerManager->ForceClearPullerCache(); } diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index caf1a713986d..ea9c6e704017 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -49,7 +49,9 @@ public: const sp<AlarmMonitor>& anomalyAlarmMonitor, const sp<AlarmMonitor>& subscriberTriggerAlarmMonitor, const int64_t timeBaseNs, - const std::function<bool(const ConfigKey&)>& sendBroadcast); + const std::function<bool(const ConfigKey&)>& sendBroadcast, + const std::function<bool(const int&, + const vector<int64_t>&)>& sendActivationBroadcast); virtual ~StatsLogProcessor(); void OnLogEvent(LogEvent* event); @@ -60,6 +62,8 @@ public: size_t GetMetricsSize(const ConfigKey& key) const; + void GetActiveConfigs(const int uid, vector<int64_t>& outActiveConfigs); + void onDumpReport(const ConfigKey& key, const int64_t dumpTimeNs, const bool include_current_partial_bucket, const bool erase_data, const DumpReportReason dumpReportReason, vector<uint8_t>* outData); @@ -125,6 +129,9 @@ private: std::unordered_map<ConfigKey, long> mLastBroadcastTimes; + // Last time we sent a broadcast to this uid that the active configs had changed. + std::unordered_map<int, long> mLastActivationBroadcastTimes; + // Tracks when we last checked the bytes consumed for each config key. std::unordered_map<ConfigKey, long> mLastByteSizeTimes; @@ -144,6 +151,8 @@ private: void OnConfigUpdatedLocked( const int64_t currentTimestampNs, const ConfigKey& key, const StatsdConfig& config); + void GetActiveConfigsLocked(const int uid, vector<int64_t>& outActiveConfigs); + void WriteDataToDiskLocked(const DumpReportReason dumpReportReason); void WriteDataToDiskLocked(const ConfigKey& key, const int64_t timestampNs, const DumpReportReason dumpReportReason); @@ -174,6 +183,10 @@ private: // to retrieve the stored data. std::function<bool(const ConfigKey& key)> mSendBroadcast; + // Function used to send a broadcast so that receiver can be notified of which configs + // are currently active. + std::function<bool(const int& uid, const vector<int64_t>& configIds)> mSendActivationBroadcast; + const int64_t mTimeBaseNs; // Largest timestamp of the events that we have processed. diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index bd21a955729d..fb603b9ce163 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -31,6 +31,7 @@ #include <binder/IPCThreadState.h> #include <binder/IServiceManager.h> #include <binder/PermissionController.h> +#include <cutils/multiuser.h> #include <dirent.h> #include <frameworks/base/cmds/statsd/src/statsd_config.pb.h> #include <private/android_filesystem_config.h> @@ -47,6 +48,7 @@ using namespace android; using android::base::StringPrintf; using android::util::FIELD_COUNT_REPEATED; +using android::util::FIELD_TYPE_INT64; using android::util::FIELD_TYPE_MESSAGE; namespace android { @@ -62,6 +64,8 @@ constexpr const char* kOpUsage = "android:get_usage_stats"; // for StatsDataDumpProto const int FIELD_ID_REPORTS_LIST = 1; +// for TrainInfo experiment id serialization +const int FIELD_ID_EXPERIMENT_ID = 1; static binder::Status ok() { return binder::Status::ok(); @@ -176,6 +180,21 @@ StatsService::StatsService(const sp<Looper>& handlerLooper) sc->sendDataBroadcast(receiver, mProcessor->getLastReportTimeNs(key)); return true; } + }, + [this](const int& uid, const vector<int64_t>& activeConfigs) { + auto receiver = mConfigManager->GetActiveConfigsChangedReceiver(uid); + sp<IStatsCompanionService> sc = getStatsCompanionService(); + if (sc == nullptr) { + VLOG("Could not access statsCompanion"); + return false; + } else if (receiver == nullptr) { + VLOG("Could not find receiver for uid %d", uid); + return false; + } else { + sc->sendActiveConfigsChangedBroadcast(receiver, activeConfigs); + VLOG("StatsService::active configs broadcast succeeded for uid %d" , uid); + return true; + } }); mConfigManager->AddListener(mProcessor); @@ -357,6 +376,9 @@ status_t StatsService::command(int in, int out, int err, Vector<String8>& args, if (!args[0].compare(String8("print-logs"))) { return cmd_print_logs(out, args); } + if (!args[0].compare(String8("send-active-configs"))) { + return cmd_trigger_active_config_broadcast(out, args); + } if (!args[0].compare(String8("data-subscribe"))) { if (mShellSubscriber == nullptr) { mShellSubscriber = new ShellSubscriber(mUidMap, mPullerManager); @@ -449,6 +471,19 @@ void StatsService::print_cmd_help(int out) { dprintf(out, " NAME The name of the configuration\n"); dprintf(out, "\n"); dprintf(out, "\n"); + dprintf(out, + "usage: adb shell cmd stats send-active-configs [--uid=UID] [--configs] " + "[NAME1] [NAME2] [NAME3..]\n"); + dprintf(out, " Send a broadcast that informs the subscriber of the current active configs.\n"); + dprintf(out, " --uid=UID The uid of the configurations. It is only possible to pass\n"); + dprintf(out, " the UID parameter on eng builds. If UID is omitted the\n"); + dprintf(out, " calling uid is used.\n"); + dprintf(out, " --configs Send the list of configs in the name list instead of\n"); + dprintf(out, " the currently active configs\n"); + dprintf(out, " NAME LIST List of configuration names to be included in the broadcast.\n"); + + dprintf(out, "\n"); + dprintf(out, "\n"); dprintf(out, "usage: adb shell cmd stats print-stats\n"); dprintf(out, " Prints some basic stats.\n"); dprintf(out, " --proto Print proto binary instead of string format.\n"); @@ -499,6 +534,59 @@ status_t StatsService::cmd_trigger_broadcast(int out, Vector<String8>& args) { return NO_ERROR; } +status_t StatsService::cmd_trigger_active_config_broadcast(int out, Vector<String8>& args) { + const int argCount = args.size(); + int uid; + vector<int64_t> configIds; + if (argCount == 1) { + // Automatically pick the uid and send a broadcast that has no active configs. + uid = IPCThreadState::self()->getCallingUid(); + mProcessor->GetActiveConfigs(uid, configIds); + } else { + int curArg = 1; + if(args[curArg].find("--uid=") == 0) { + string uidArgStr(args[curArg].c_str()); + string uidStr = uidArgStr.substr(6); + if (!getUidFromString(uidStr.c_str(), uid)) { + dprintf(out, "Invalid UID. Note that the config can only be set for " + "other UIDs on eng or userdebug builds.\n"); + return UNKNOWN_ERROR; + } + curArg++; + } else { + uid = IPCThreadState::self()->getCallingUid(); + } + if (curArg == argCount || args[curArg] != "--configs") { + VLOG("Reached end of args, or specify configs not set. Sending actual active configs,"); + mProcessor->GetActiveConfigs(uid, configIds); + } else { + // Flag specified, use the given list of configs. + curArg++; + for (int i = curArg; i < argCount; i++) { + char* endp; + int64_t configID = strtoll(args[i].c_str(), &endp, 10); + if (endp == args[i].c_str() || *endp != '\0') { + dprintf(out, "Error parsing config ID.\n"); + return UNKNOWN_ERROR; + } + VLOG("Adding config id %ld", static_cast<long>(configID)); + configIds.push_back(configID); + } + } + } + auto receiver = mConfigManager->GetActiveConfigsChangedReceiver(uid); + sp<IStatsCompanionService> sc = getStatsCompanionService(); + if (sc == nullptr) { + VLOG("Could not access statsCompanion"); + } else if (receiver == nullptr) { + VLOG("Could not find receiver for uid %d", uid); + } else { + sc->sendActiveConfigsChangedBroadcast(receiver, configIds); + VLOG("StatsService::trigger active configs changed broadcast succeeded for uid %d" , uid); + } + return NO_ERROR; +} + status_t StatsService::cmd_config(int in, int out, int err, Vector<String8>& args) { const int argCount = args.size(); if (argCount >= 2) { @@ -762,7 +850,10 @@ status_t StatsService::cmd_print_logs(int out, const Vector<String8>& args) { } bool StatsService::getUidFromArgs(const Vector<String8>& args, size_t uidArgIndex, int32_t& uid) { - const char* s = args[uidArgIndex].c_str(); + return getUidFromString(args[uidArgIndex].c_str(), uid); +} + +bool StatsService::getUidFromString(const char* s, int32_t& uid) { if (*s == '\0') { return false; } @@ -998,8 +1089,13 @@ Status StatsService::setActiveConfigsChangedOperation(const sp<android::IBinder> ENFORCE_DUMP_AND_USAGE_STATS(packageName); IPCThreadState* ipc = IPCThreadState::self(); - mConfigManager->SetActiveConfigsChangedReceiver(ipc->getCallingUid(), intentSender); - //TODO: Return the list of configs that are already active + int uid = ipc->getCallingUid(); + mConfigManager->SetActiveConfigsChangedReceiver(uid, intentSender); + if (output != nullptr) { + mProcessor->GetActiveConfigs(uid, *output); + } else { + ALOGW("StatsService::setActiveConfigsChanged output was nullptr"); + } return Status::ok(); } @@ -1075,6 +1171,56 @@ Status StatsService::unregisterPullerCallback(int32_t atomTag, const String16& p return Status::ok(); } +Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& trainName, + int64_t trainVersionCode, int options, + int32_t state, + const std::vector<int64_t>& experimentIds) { + uid_t uid = IPCThreadState::self()->getCallingUid(); + // For testing + if (uid == AID_ROOT || uid == AID_SYSTEM || uid == AID_SHELL) { + return ok(); + } + + // Caller must be granted these permissions + if (!checkCallingPermission(String16(kPermissionDump))) { + return exception(binder::Status::EX_SECURITY, + StringPrintf("UID %d lacks permission %s", uid, kPermissionDump)); + } + if (!checkCallingPermission(String16(kPermissionUsage))) { + return exception(binder::Status::EX_SECURITY, + StringPrintf("UID %d lacks permission %s", uid, kPermissionUsage)); + } + // TODO: add verifier permission + + userid_t userId = multiuser_get_user_id(uid); + + bool requiresStaging = options | IStatsManager::FLAG_REQUIRE_STAGING; + bool rollbackEnabled = options | IStatsManager::FLAG_ROLLBACK_ENABLED; + bool requiresLowLatencyMonitor = options | IStatsManager::FLAG_REQUIRE_LOW_LATENCY_MONITOR; + + ProtoOutputStream proto; + for (const auto& expId : experimentIds) { + proto.write(FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED | FIELD_ID_EXPERIMENT_ID, + (long long)expId); + } + + vector<uint8_t> buffer; + buffer.resize(proto.size()); + size_t pos = 0; + auto iter = proto.data(); + while (iter.readBuffer() != NULL) { + size_t toRead = iter.currentToRead(); + std::memcpy(&(buffer[pos]), iter.readBuffer(), toRead); + pos += toRead; + iter.rp()->move(toRead); + } + LogEvent event(std::string(String8(trainName).string()), trainVersionCode, requiresStaging, + rollbackEnabled, requiresLowLatencyMonitor, state, buffer, userId); + mProcessor->OnLogEvent(&event); + StorageManager::writeTrainInfo(trainVersionCode, buffer); + return Status::ok(); +} + hardware::Return<void> StatsService::reportSpeakerImpedance( const SpeakerImpedance& speakerImpedance) { LogEvent event(getWallClockSec() * NS_PER_SEC, getElapsedRealtimeNs(), speakerImpedance); diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index 941ed462b303..d24565a63054 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -31,6 +31,7 @@ #include <android/frameworks/stats/1.0/types.h> #include <android/os/BnStatsManager.h> #include <android/os/IStatsCompanionService.h> +#include <android/os/IStatsManager.h> #include <binder/IResultReceiver.h> #include <utils/Looper.h> @@ -186,6 +187,13 @@ public: virtual Status unregisterPullerCallback(int32_t atomTag, const String16& packageName) override; /** + * Binder call to log BinaryPushStateChanged atom. + */ + virtual Status sendBinaryPushStateChangedAtom( + const android::String16& trainName, int64_t trainVersionCode, int options, + int32_t state, const std::vector<int64_t>& experimentIds) override; + + /** * Binder call to get SpeakerImpedance atom. */ virtual Return<void> reportSpeakerImpedance(const SpeakerImpedance& speakerImpedance) override; @@ -275,6 +283,12 @@ private: */ status_t cmd_trigger_broadcast(int outFd, Vector<String8>& args); + + /** + * Trigger an active configs changed broadcast. + */ + status_t cmd_trigger_active_config_broadcast(int outFd, Vector<String8>& args); + /** * Handle the config sub-command. */ @@ -341,6 +355,15 @@ private: bool getUidFromArgs(const Vector<String8>& args, size_t uidArgIndex, int32_t& uid); /** + * Writes the value of uidSting into uid. + * Returns whether the uid is reasonable (type uid_t) and whether + * 1. it is equal to the calling uid, or + * 2. the device is mEngBuild, or + * 3. the caller is AID_ROOT and the uid is AID_SHELL (i.e. ROOT can impersonate SHELL). + */ + bool getUidFromString(const char* uidString, int32_t& uid); + + /** * Adds a configuration after checking permissions and obtaining UID from binder call. */ bool addConfigurationChecked(int uid, int64_t key, const vector<uint8_t>& config); diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index da7e4daaf22b..873b772d06ea 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -31,6 +31,7 @@ import "frameworks/base/core/proto/android/bluetooth/hfp/enums.proto"; import "frameworks/base/core/proto/android/bluetooth/smp/enums.proto"; import "frameworks/base/core/proto/android/debug/enums.proto"; import "frameworks/base/core/proto/android/hardware/biometrics/enums.proto"; +import "frameworks/base/core/proto/android/hardware/sensor/assist/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"; @@ -241,6 +242,9 @@ message Atom { BluetoothSocketConnectionStateChanged bluetooth_socket_connection_state_changed = 171; DeviceIdentifierAccessDenied device_identifier_access_denied = 172; BubbleDeveloperErrorReported bubble_developer_error_reported = 173; + AssistGestureStageReported assist_gesture_stage_reported = 174; + AssistGestureFeedbackReported assist_gesture_feedback_reported = 175; + AssistGestureProgressReported assist_gesture_progress_reported = 176; } // Pulled events will start at field 10000. @@ -3152,6 +3156,13 @@ message FlagFlipUpdateOccurred { optional int64 order_id = 2; } +/** + * Potential experiment ids that goes with a train install. + */ +message TrainExperimentIds { + repeated int64 experiment_id = 1; +} + /* * Logs when a binary push state changes. * Logged by the installer via public api. @@ -3178,8 +3189,14 @@ message BinaryPushStateChanged { INSTALL_FAILURE = 6; INSTALL_CANCELLED = 7; INSTALLER_ROLLBACK_REQUESTED = 8; + INSTALLER_ROLLBACK_SUCCESS = 9; + INSTALLER_ROLLBACK_FAILURE = 10; } optional State state = 6; + // Possible experiment ids for monitoring this push. + optional TrainExperimentIds experiment_ids = 7 [(log_mode) = MODE_BYTES]; + // user id + optional int32 user_id = 8; } /** Represents USB port overheat event. */ @@ -5507,13 +5524,6 @@ message DeviceIdentifierAccessDenied { } /** - * Potential experiment ids that goes with a train install. - */ -message TrainExperimentIds { - repeated int64 experiment_id = 1; -} - -/** * Pulls the ongoing mainline install train version code. * Pulled from StatsCompanionService */ @@ -5522,3 +5532,35 @@ message TrainInfo { optional TrainExperimentIds train_experiment_id = 2; } + +/** + * Logs the gesture stage changed event. + * + * Logged from: + * frameworks/base/packages/SystemUI/ + */ +message AssistGestureStageReported { + optional android.hardware.sensor.assist.AssistGestureStageEnum gesture_stage = 1; +} + +/** + * Logs the feedback type. + * + * Logged from: + * frameworks/base/packages/SystemUI/ + */ +message AssistGestureFeedbackReported { + // Whether or not the gesture was used. + optional android.hardware.sensor.assist.AssistGestureFeedbackEnum feedback_type = 1; +} + +/** + * Logs the progress. + * + * Logged from: + * frameworks/base/packages/SystemUI/ + */ +message AssistGestureProgressReported { + // [0,100] progress for the assist gesture. + optional int32 progress = 1; +} diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp index aa22333ab26c..fc949b494194 100644 --- a/cmds/statsd/src/config/ConfigManager.cpp +++ b/cmds/statsd/src/config/ConfigManager.cpp @@ -144,10 +144,20 @@ void ConfigManager::RemoveConfig(const ConfigKey& key) { { lock_guard <mutex> lock(mMutex); - auto uidIt = mConfigs.find(key.GetUid()); + auto uid = key.GetUid(); + auto uidIt = mConfigs.find(uid); if (uidIt != mConfigs.end() && uidIt->second.find(key) != uidIt->second.end()) { // Remove from map uidIt->second.erase(key); + + // No more configs for this uid, lets remove the active configs callback. + if (uidIt->second.empty()) { + auto itActiveConfigsChangedReceiver = mActiveConfigsChangedReceivers.find(uid); + if (itActiveConfigsChangedReceiver != mActiveConfigsChangedReceivers.end()) { + mActiveConfigsChangedReceivers.erase(itActiveConfigsChangedReceiver); + } + } + for (const sp<ConfigListener>& listener : mListeners) { broadcastList.push_back(listener); } diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index ed72b2914a34..98f810fd9a2c 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -29,10 +29,11 @@ #include "../statscompanion_util.h" #include "PowerStatsPuller.h" #include "ResourceHealthManagerPuller.h" -#include "StatsCompanionServicePuller.h" #include "StatsCallbackPuller.h" +#include "StatsCompanionServicePuller.h" #include "StatsPullerManager.h" #include "SubsystemSleepStatePuller.h" +#include "TrainInfoPuller.h" #include "statslog.h" #include <iostream> @@ -152,7 +153,7 @@ std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_HIGH_WATER_MARK)}}, // temperature {android::util::TEMPERATURE, - {.puller = new StatsCompanionServicePuller(android::util::TEMPERATURE)}}, + {.puller = new StatsCompanionServicePuller(android::util::TEMPERATURE)}}, // binder_calls {android::util::BINDER_CALLS, {.additiveFields = {4, 5, 6, 8, 12}, @@ -231,6 +232,8 @@ std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { // PermissionState. {android::util::DANGEROUS_PERMISSION_STATE, {.puller = new StatsCompanionServicePuller(android::util::DANGEROUS_PERMISSION_STATE)}}, + // TrainInfo. + {android::util::TRAIN_INFO, {.puller = new TrainInfoPuller()}}, }; StatsPullerManager::StatsPullerManager() : mNextPullTimeNs(NO_ALARM_UPDATE) { diff --git a/cmds/statsd/src/external/TrainInfoPuller.cpp b/cmds/statsd/src/external/TrainInfoPuller.cpp new file mode 100644 index 000000000000..9d0924297912 --- /dev/null +++ b/cmds/statsd/src/external/TrainInfoPuller.cpp @@ -0,0 +1,53 @@ +/* + * 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "external/StatsPuller.h" + +#include "TrainInfoPuller.h" +#include "logd/LogEvent.h" +#include "stats_log_util.h" +#include "statslog.h" +#include "storage/StorageManager.h" + +using std::make_shared; +using std::shared_ptr; + +namespace android { +namespace os { +namespace statsd { + +TrainInfoPuller::TrainInfoPuller() : + StatsPuller(android::util::TRAIN_INFO) { +} + +bool TrainInfoPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) { + InstallTrainInfo trainInfo; + bool ret = StorageManager::readTrainInfo(trainInfo); + if (!ret) { + ALOGW("Failed to read train info."); + return false; + } + auto event = make_shared<LogEvent>(getWallClockNs(), getElapsedRealtimeNs(), trainInfo); + data->push_back(event); + return true; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/external/TrainInfoPuller.h b/cmds/statsd/src/external/TrainInfoPuller.h new file mode 100644 index 000000000000..615d02351fd3 --- /dev/null +++ b/cmds/statsd/src/external/TrainInfoPuller.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "StatsPuller.h" + +namespace android { +namespace os { +namespace statsd { + +/** + * Reads train info from disk. + */ +class TrainInfoPuller : public StatsPuller { + public: + TrainInfoPuller(); + + private: + bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override; +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp index 40329b7c86ab..d661ee8651be 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.cpp +++ b/cmds/statsd/src/guardrail/StatsdStats.cpp @@ -82,6 +82,8 @@ const int FIELD_ID_CONFIG_STATS_METRIC_STATS = 15; const int FIELD_ID_CONFIG_STATS_ALERT_STATS = 16; const int FIELD_ID_CONFIG_STATS_METRIC_DIMENSION_IN_CONDITION_STATS = 17; const int FIELD_ID_CONFIG_STATS_ANNOTATION = 18; +const int FIELD_ID_CONFIG_STATS_ACTIVATION = 22; +const int FIELD_ID_CONFIG_STATS_DEACTIVATION = 23; const int FIELD_ID_CONFIG_STATS_ANNOTATION_INT64 = 1; const int FIELD_ID_CONFIG_STATS_ANNOTATION_INT32 = 2; @@ -206,6 +208,25 @@ void StatsdStats::noteBroadcastSent(const ConfigKey& key, int32_t timeSec) { it->second->broadcast_sent_time_sec.push_back(timeSec); } +void StatsdStats::noteActiveStatusChanged(const ConfigKey& key, bool activated) { + noteActiveStatusChanged(key, activated, getWallClockSec()); +} + +void StatsdStats::noteActiveStatusChanged(const ConfigKey& key, bool activated, int32_t timeSec) { + lock_guard<std::mutex> lock(mLock); + auto it = mConfigStats.find(key); + if (it == mConfigStats.end()) { + ALOGE("Config key %s not found!", key.ToString().c_str()); + return; + } + auto& vec = activated ? it->second->activation_time_sec + : it->second->deactivation_time_sec; + if (vec.size() == kMaxTimestampCount) { + vec.pop_front(); + } + vec.push_back(timeSec); +} + void StatsdStats::noteDataDropped(const ConfigKey& key, const size_t totalBytes) { noteDataDropped(key, totalBytes, getWallClockSec()); } @@ -457,6 +478,11 @@ void StatsdStats::noteBucketDropped(int64_t metricId) { getAtomMetricStats(metricId).bucketDropped++; } +void StatsdStats::noteBucketUnknownCondition(int64_t metricId) { + lock_guard<std::mutex> lock(mLock); + getAtomMetricStats(metricId).bucketUnknownCondition++; +} + void StatsdStats::noteConditionChangeInNextBucket(int64_t metricId) { lock_guard<std::mutex> lock(mLock); getAtomMetricStats(metricId).conditionChangeInNextBucket++; @@ -476,7 +502,7 @@ void StatsdStats::noteBucketBoundaryDelayNs(int64_t metricId, int64_t timeDelayN std::min(pullStats.minBucketBoundaryDelayNs, timeDelayNs); } -StatsdStats::AtomMetricStats& StatsdStats::getAtomMetricStats(int metricId) { +StatsdStats::AtomMetricStats& StatsdStats::getAtomMetricStats(int64_t metricId) { auto atomMetricStatsIter = mAtomMetricStats.find(metricId); if (atomMetricStatsIter != mAtomMetricStats.end()) { return atomMetricStatsIter->second; @@ -501,6 +527,8 @@ void StatsdStats::resetInternalLocked() { mLogLossStats.clear(); for (auto& config : mConfigStats) { config.second->broadcast_sent_time_sec.clear(); + config.second->activation_time_sec.clear(); + config.second->deactivation_time_sec.clear(); config.second->data_drop_time_sec.clear(); config.second->data_drop_bytes.clear(); config.second->dump_report_stats.clear(); @@ -558,6 +586,14 @@ void StatsdStats::dumpStats(int out) const { dprintf(out, "\tbroadcast time: %d\n", broadcastTime); } + for (const int& activationTime : configStats->activation_time_sec) { + dprintf(out, "\tactivation time: %d\n", activationTime); + } + + for (const int& deactivationTime : configStats->deactivation_time_sec) { + dprintf(out, "\tdeactivation time: %d\n", deactivationTime); + } + auto dropTimePtr = configStats->data_drop_time_sec.begin(); auto dropBytesPtr = configStats->data_drop_bytes.begin(); for (int i = 0; i < (int)configStats->data_drop_time_sec.size(); @@ -586,6 +622,14 @@ void StatsdStats::dumpStats(int out) const { (long long)broadcastTime); } + for (const int& activationTime : configStats->activation_time_sec) { + dprintf(out, "\tactivation time: %d\n", activationTime); + } + + for (const int& deactivationTime : configStats->deactivation_time_sec) { + dprintf(out, "\tdeactivation time: %d\n", deactivationTime); + } + auto dropTimePtr = configStats->data_drop_time_sec.begin(); auto dropBytesPtr = configStats->data_drop_bytes.begin(); for (int i = 0; i < (int)configStats->data_drop_time_sec.size(); @@ -696,6 +740,16 @@ void addConfigStatsToProto(const ConfigStats& configStats, ProtoOutputStream* pr broadcast); } + for (const auto& activation : configStats.activation_time_sec) { + proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_ACTIVATION | FIELD_COUNT_REPEATED, + activation); + } + + for (const auto& deactivation : configStats.deactivation_time_sec) { + proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_DEACTIVATION | FIELD_COUNT_REPEATED, + deactivation); + } + for (const auto& drop_time : configStats.data_drop_time_sec) { proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_DATA_DROP_TIME | FIELD_COUNT_REPEATED, drop_time); diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h index 65e8a324d7d3..e039be2b4395 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.h +++ b/cmds/statsd/src/guardrail/StatsdStats.h @@ -42,6 +42,13 @@ struct ConfigStats { bool is_valid; std::list<int32_t> broadcast_sent_time_sec; + + // Times at which this config is activated. + std::list<int32_t> activation_time_sec; + + // Times at which this config is deactivated. + std::list<int32_t> deactivation_time_sec; + std::list<int32_t> data_drop_time_sec; // Number of bytes dropped at corresponding time. std::list<int64_t> data_drop_bytes; @@ -132,6 +139,9 @@ public: /* Min period between two checks of byte size per config key in nanoseconds. */ static const int64_t kMinByteSizeCheckPeriodNs = 60 * NS_PER_SEC; + /* Minimum period between two activation broadcasts in nanoseconds. */ + static const int64_t kMinActivationBroadcastPeriodNs = 10 * NS_PER_SEC; + // Maximum age (30 days) that files on disk can exist in seconds. static const int kMaxAgeSecond = 60 * 60 * 24 * 30; @@ -175,6 +185,13 @@ public: void noteBroadcastSent(const ConfigKey& key); /** + * Report that a config has become activated or deactivated. + * This can be different from whether or not a broadcast is sent if the + * guardrail prevented the broadcast from being sent. + */ + void noteActiveStatusChanged(const ConfigKey& key, bool activate); + + /** * Report a config's metrics data has been dropped. */ void noteDataDropped(const ConfigKey& key, const size_t totalBytes); @@ -392,6 +409,11 @@ public: void noteBucketBoundaryDelayNs(int64_t metricId, int64_t timeDelayNs); /** + * Number of buckets with unknown condition. + */ + void noteBucketUnknownCondition(int64_t metricId); + + /** * Reset the historical stats. Including all stats in icebox, and the tracked stats about * metrics, matchers, and atoms. The active configs will be kept and StatsdStats will continue * to collect stats after reset() has been called. @@ -441,6 +463,7 @@ public: long bucketDropped = 0; int64_t minBucketBoundaryDelayNs = 0; int64_t maxBucketBoundaryDelayNs = 0; + long bucketUnknownCondition = 0; } AtomMetricStats; private: @@ -471,7 +494,7 @@ private: std::map<int, PulledAtomStats> mPulledAtomStats; // Maps metric ID to its stats. The size is capped by the number of metrics. - std::map<int, AtomMetricStats> mAtomMetricStats; + std::map<int64_t, AtomMetricStats> mAtomMetricStats; struct LogLossStats { LogLossStats(int32_t sec, int32_t count, int32_t error) @@ -507,13 +530,15 @@ private: void noteBroadcastSent(const ConfigKey& key, int32_t timeSec); + void noteActiveStatusChanged(const ConfigKey& key, bool activate, int32_t timeSec); + void addToIceBoxLocked(std::shared_ptr<ConfigStats>& stats); /** * Get a reference to AtomMetricStats for a metric. If none exists, create it. The reference * will live as long as `this`. */ - StatsdStats::AtomMetricStats& getAtomMetricStats(int metricId); + StatsdStats::AtomMetricStats& getAtomMetricStats(int64_t metricId); FRIEND_TEST(StatsdStatsTest, TestValidConfigAdd); FRIEND_TEST(StatsdStatsTest, TestInvalidConfigAdd); diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index 40a4070d2974..dec36b54a1ce 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -20,6 +20,8 @@ #include "stats_log_util.h" #include "statslog.h" +#include <binder/IPCThreadState.h> + namespace android { namespace os { namespace statsd { @@ -180,6 +182,25 @@ LogEvent::LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedT } } +LogEvent::LogEvent(const string& trainName, int64_t trainVersionCode, bool requiresStaging, + bool rollbackEnabled, bool requiresLowLatencyMonitor, int32_t state, + const std::vector<uint8_t>& experimentIds, int32_t userId) { + mLogdTimestampNs = getWallClockNs(); + mElapsedTimestampNs = getElapsedRealtimeNs(); + mTagId = android::util::BINARY_PUSH_STATE_CHANGED; + mLogUid = android::IPCThreadState::self()->getCallingUid(); + + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(1)), Value(trainName))); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), Value(trainVersionCode))); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(3)), Value((int)requiresStaging))); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)), Value((int)rollbackEnabled))); + mValues.push_back( + FieldValue(Field(mTagId, getSimpleField(5)), Value((int)requiresLowLatencyMonitor))); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(6)), Value(state))); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(7)), Value(experimentIds))); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(8)), Value(userId))); +} + LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, const SpeakerImpedance& speakerImpedance) { mLogdTimestampNs = wallClockTimestampNs; @@ -340,6 +361,16 @@ LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, } } +LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, + const InstallTrainInfo& trainInfo) { + mLogdTimestampNs = wallClockTimestampNs; + mElapsedTimestampNs = elapsedTimestampNs; + mTagId = android::util::TRAIN_INFO; + mValues.push_back( + FieldValue(Field(mTagId, getSimpleField(1)), Value(trainInfo.trainVersionCode))); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), Value(trainInfo.experimentIds))); +} + LogEvent::LogEvent(int32_t tagId, int64_t timestampNs) : LogEvent(tagId, timestampNs, 0) {} LogEvent::LogEvent(int32_t tagId, int64_t timestampNs, int32_t uid) { diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index 784376a1580c..111a619760df 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -55,6 +55,11 @@ struct AttributionNodeInternal { int32_t mUid; std::string mTag; }; + +struct InstallTrainInfo { + int64_t trainVersionCode; + std::vector<uint8_t> experimentIds; +}; /** * Wrapper for the log_msg structure. */ @@ -97,6 +102,11 @@ public: const std::map<int32_t, std::string>& string_map, const std::map<int32_t, float>& float_map); + // Constructs a BinaryPushStateChanged LogEvent from API call. + explicit LogEvent(const std::string& trainName, int64_t trainVersionCode, bool requiresStaging, + bool rollbackEnabled, bool requiresLowLatencyMonitor, int32_t state, + const std::vector<uint8_t>& experimentIds, int32_t userId); + explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, const SpeakerImpedance& speakerImpedance); @@ -127,6 +137,9 @@ public: explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, const VendorAtom& vendorAtom); + explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, + const InstallTrainInfo& installTrainInfo); + ~LogEvent(); /** diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp index 350745b2c326..5707de544648 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.cpp +++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp @@ -250,7 +250,7 @@ void CountMetricProducer::dropDataLocked(const int64_t dropTimeNs) { void CountMetricProducer::onConditionChangedLocked(const bool conditionMet, const int64_t eventTime) { VLOG("Metric %lld onConditionChanged", (long long)mMetricId); - mCondition = conditionMet; + mCondition = conditionMet ? ConditionState::kTrue : ConditionState::kFalse; } bool CountMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index 6c1c47bbc093..da6b97cc4e59 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -433,7 +433,7 @@ void DurationMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondit void DurationMetricProducer::onConditionChangedLocked(const bool conditionMet, const int64_t eventTime) { VLOG("Metric %lld onConditionChanged", (long long)mMetricId); - mCondition = conditionMet; + mCondition = conditionMet ? ConditionState::kTrue : ConditionState::kFalse; flushIfNeededLocked(eventTime); for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { for (auto& pair : whatIt.second) { @@ -767,12 +767,13 @@ void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, !mSameConditionDimensionsInTracker, !mHasLinksToAllConditionDimensionsInTracker, &dimensionKeysInCondition); - condition = (conditionState == ConditionState::kTrue); + condition = conditionState == ConditionState::kTrue; if (mDimensionsInCondition.empty() && condition) { dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY); } } else { - condition = mCondition; + // TODO: The unknown condition state is not handled here, we should fix it. + condition = mCondition == ConditionState::kTrue; if (condition) { dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY); } diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h index 1b830a3f69f5..ec561b527025 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.h +++ b/cmds/statsd/src/metrics/DurationMetricProducer.h @@ -137,6 +137,7 @@ private: FRIEND_TEST(DurationMetricTrackerTest, TestNoCondition); FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedCondition); + FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedConditionUnknownState); FRIEND_TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade); FRIEND_TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket); FRIEND_TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgrade); diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp index 7e695a69988f..3b4af6533e34 100644 --- a/cmds/statsd/src/metrics/EventMetricProducer.cpp +++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp @@ -132,7 +132,7 @@ void EventMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, void EventMetricProducer::onConditionChangedLocked(const bool conditionMet, const int64_t eventTime) { VLOG("Metric %lld onConditionChanged", (long long)mMetricId); - mCondition = conditionMet; + mCondition = conditionMet ? ConditionState::kTrue : ConditionState::kFalse; } void EventMetricProducer::onMatchedLogEventInternalLocked( diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index d56a355f15d6..837d532f7e09 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -320,15 +320,15 @@ void GaugeMetricProducer::pullAndMatchEventsLocked(const int64_t timestampNs) { // When the metric wants to do random sampling and there is already one gauge atom for the // current bucket, do not do it again. case GaugeMetric::RANDOM_ONE_SAMPLE: { - triggerPuller = mCondition && mCurrentSlicedBucket->empty(); + triggerPuller = mCondition == ConditionState::kTrue && mCurrentSlicedBucket->empty(); break; } case GaugeMetric::CONDITION_CHANGE_TO_TRUE: { - triggerPuller = mCondition; + triggerPuller = mCondition == ConditionState::kTrue; break; } case GaugeMetric::FIRST_N_SAMPLES: { - triggerPuller = mCondition; + triggerPuller = mCondition == ConditionState::kTrue; break; } default: @@ -364,7 +364,7 @@ void GaugeMetricProducer::onConditionChangedLocked(const bool conditionMet, const int64_t eventTimeNs) { VLOG("GaugeMetric %lld onConditionChanged", (long long)mMetricId); flushIfNeededLocked(eventTimeNs); - mCondition = conditionMet; + mCondition = conditionMet ? ConditionState::kTrue : ConditionState::kFalse; if (mIsPulled && mTriggerAtomId == -1) { pullAndMatchEventsLocked(eventTimeNs); } // else: Push mode. No need to proactively pull the gauge data. @@ -377,7 +377,7 @@ void GaugeMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition flushIfNeededLocked(eventTimeNs); // If the condition is sliced, mCondition is true if any of the dimensions is true. And we will // pull for every dimension. - mCondition = overallCondition; + mCondition = overallCondition ? ConditionState::kTrue : ConditionState::kFalse; if (mIsPulled && mTriggerAtomId == -1) { pullAndMatchEventsLocked(eventTimeNs); } // else: Push mode. No need to proactively pull the gauge data. diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp index 495138ee9b77..4cf5333947d3 100644 --- a/cmds/statsd/src/metrics/MetricProducer.cpp +++ b/cmds/statsd/src/metrics/MetricProducer.cpp @@ -45,7 +45,8 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo &dimensionKeysInCondition); condition = (conditionState == ConditionState::kTrue); } else { - condition = mCondition; + // TODO: The unknown condition state is not handled here, we should fix it. + condition = mCondition == ConditionState::kTrue; } if (mDimensionsInCondition.empty() && condition) { @@ -94,7 +95,7 @@ void MetricProducer::flushIfExpire(int64_t elapsedTimestampNs) { void MetricProducer::addActivation(int activationTrackerIndex, int64_t ttl_seconds) { std::lock_guard<std::mutex> lock(mMutex); // When a metric producer does not depend on any activation, its mIsActive is true. - // Therefor, if this is the 1st activation, mIsActive will turn to false. Otherwise it does not + // Therefore, if this is the 1st activation, mIsActive will turn to false. Otherwise it does not // change. if (mEventActivationMap.empty()) { mIsActive = false; @@ -107,7 +108,8 @@ void MetricProducer::activateLocked(int activationTrackerIndex, int64_t elapsedT if (it == mEventActivationMap.end()) { return; } - if (mActivationType == MetricActivation::ACTIVATE_ON_BOOT) { + if (mActivationType == MetricActivation::ACTIVATE_ON_BOOT && + it->second.state == ActivationState::kNotActive) { it->second.state = ActivationState::kActiveOnBoot; return; } diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h index 849cb76ec392..8ab3b0680276 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -59,7 +59,7 @@ public: mTimeBaseNs(timeBaseNs), mCurrentBucketStartTimeNs(timeBaseNs), mCurrentBucketNum(0), - mCondition(conditionIndex >= 0 ? false : true), + mCondition(conditionIndex >= 0 ? ConditionState::kUnknown : ConditionState::kTrue), mConditionSliced(false), mWizard(wizard), mConditionTrackerIndex(conditionIndex), @@ -315,7 +315,7 @@ protected: int64_t mBucketSizeNs; - bool mCondition; + ConditionState mCondition; bool mConditionSliced; diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp index 6ed6ab500597..4851a8d40baa 100644 --- a/cmds/statsd/src/metrics/MetricsManager.cpp +++ b/cmds/statsd/src/metrics/MetricsManager.cpp @@ -118,6 +118,16 @@ MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config, ALOGE("This config has too many alerts! Reject!"); mConfigValid = false; } + + mIsAlwaysActive = (mMetricIndexesWithActivation.size() != mAllMetricProducers.size()) || + (mAllMetricProducers.size() == 0); + bool isActive = mIsAlwaysActive; + for (int metric : mMetricIndexesWithActivation) { + isActive |= mAllMetricProducers[metric]->isActive(); + } + mIsActive = isActive; + VLOG("mIsActive is initialized to %d", mIsActive) + // no matter whether this config is valid, log it in the stats. StatsdStats::getInstance().noteConfigReceived( key, mAllMetricProducers.size(), mAllConditionTrackers.size(), mAllAtomMatchers.size(), @@ -332,12 +342,14 @@ void MetricsManager::onLogEvent(const LogEvent& event) { int tagId = event.GetTagId(); int64_t eventTimeNs = event.GetElapsedTimestampNs(); - bool isActive = false; + bool isActive = mIsAlwaysActive; for (int metric : mMetricIndexesWithActivation) { mAllMetricProducers[metric]->flushIfExpire(eventTimeNs); isActive |= mAllMetricProducers[metric]->isActive(); } + mIsActive = isActive; + if (mTagIds.find(tagId) == mTagIds.end()) { // not interesting... return; diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h index cb1cefbf2063..eab1f762b390 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -128,6 +128,8 @@ public: // Does not change the state. virtual size_t byteSize(); + // Returns whether or not this config is active. + // The config is active if any metric in the config is active. inline bool isActive() const { return mIsActive; } @@ -241,9 +243,12 @@ private: // The metrics that don't need to be uploaded or even reported. std::set<int64_t> mNoReportMetricIds; - // Any metric active means the config is active. + // The config is active if any metric in the config is active. bool mIsActive; + // The config is always active if any metric in the config does not have an activation signal. + bool mIsAlwaysActive; + FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions); FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks); FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid); diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index 851ae9962d09..bc7c8727284f 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -154,7 +154,7 @@ ValueMetricProducer::ValueMetricProducer( // Adjust start for partial bucket mCurrentBucketStartTimeNs = startTimeNs; // Kicks off the puller immediately if condition is true and diff based. - if (mIsPulled && mCondition && mUseDiff) { + if (mIsPulled && mCondition == ConditionState::kTrue && mUseDiff) { pullAndMatchEventsLocked(startTimeNs); } VLOG("value metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), @@ -341,17 +341,21 @@ void ValueMetricProducer::onConditionChangedLocked(const bool condition, flushIfNeededLocked(eventTimeNs); // Pull on condition changes. - if (mIsPulled && (mCondition != condition)) { + bool conditionChanged = mCondition != condition; + bool unknownToFalse = mCondition == ConditionState::kUnknown + && condition == ConditionState::kFalse; + // We do not need to pull when we go from unknown to false. + if (mIsPulled && conditionChanged && !unknownToFalse) { pullAndMatchEventsLocked(eventTimeNs); } // when condition change from true to false, clear diff base but don't // reset other counters as we may accumulate more value in the bucket. - if (mUseDiff && mCondition && !condition) { + if (mUseDiff && mCondition == ConditionState::kTrue && condition == ConditionState::kFalse) { resetBase(); } - mCondition = condition; + mCondition = condition ? ConditionState::kTrue : ConditionState::kFalse; } void ValueMetricProducer::pullAndMatchEventsLocked(const int64_t timestampNs) { @@ -372,7 +376,7 @@ int64_t ValueMetricProducer::calcPreviousBucketEndTime(const int64_t currentTime void ValueMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData, bool pullSuccess, int64_t originalPullTimeNs) { std::lock_guard<std::mutex> lock(mMutex); - if (mCondition) { + if (mCondition == ConditionState::kTrue) { if (!pullSuccess) { // If the pull failed, we won't be able to compute a diff. invalidateCurrentBucket(); @@ -725,6 +729,10 @@ void ValueMetricProducer::flushIfNeededLocked(const int64_t& eventTimeNs) { } void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs) { + if (mCondition == ConditionState::kUnknown) { + StatsdStats::getInstance().noteBucketUnknownCondition(mMetricId); + } + VLOG("finalizing bucket for %ld, dumping %d slices", (long)mCurrentBucketStartTimeNs, (int)mCurrentSlicedBucket.size()); int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs(); diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto index 863261ae1626..09b8fed60dcc 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -367,6 +367,8 @@ message StatsdStatsReport { optional int32 field_int32 = 2; } repeated Annotation annotation = 18; + repeated int32 activation_time_sec = 22; + repeated int32 deactivation_time_sec = 23; } repeated ConfigStats config_stats = 3; @@ -423,6 +425,7 @@ message StatsdStatsReport { optional int64 bucket_dropped = 8; optional int64 min_bucket_boundary_delay_ns = 9; optional int64 max_bucket_boundary_delay_ns = 10; + optional int64 bucket_unknown_condition = 11; } repeated AtomMetricStats atom_metric_stats = 17; diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp index 0ebf2ca4e035..ca645e186614 100644 --- a/cmds/statsd/src/stats_log_util.cpp +++ b/cmds/statsd/src/stats_log_util.cpp @@ -84,6 +84,7 @@ const int FIELD_ID_INVALIDATED_BUCKET = 7; const int FIELD_ID_BUCKET_DROPPED = 8; const int FIELD_ID_MIN_BUCKET_BOUNDARY_DELAY_NS = 9; const int FIELD_ID_MAX_BUCKET_BOUNDARY_DELAY_NS = 10; +const int FIELD_ID_BUCKET_UNKNOWN_CONDITION = 11; namespace { @@ -489,11 +490,11 @@ void writePullerStatsToStream(const std::pair<int, StatsdStats::PulledAtomStats> protoOutput->end(token); } -void writeAtomMetricStatsToStream(const std::pair<int, StatsdStats::AtomMetricStats> &pair, +void writeAtomMetricStatsToStream(const std::pair<int64_t, StatsdStats::AtomMetricStats> &pair, util::ProtoOutputStream *protoOutput) { uint64_t token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_METRIC_STATS | FIELD_COUNT_REPEATED); - protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_METRIC_ID, (int32_t)pair.first); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_METRIC_ID, (long long)pair.first); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_HARD_DIMENSION_LIMIT_REACHED, (long long)pair.second.hardDimensionLimitReached); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_LATE_LOG_EVENT_SKIPPED, @@ -512,6 +513,8 @@ void writeAtomMetricStatsToStream(const std::pair<int, StatsdStats::AtomMetricSt (long long)pair.second.minBucketBoundaryDelayNs); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_MAX_BUCKET_BOUNDARY_DELAY_NS, (long long)pair.second.maxBucketBoundaryDelayNs); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_UNKNOWN_CONDITION, + (long long)pair.second.bucketUnknownCondition); protoOutput->end(token); } diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h index dcea0e653e22..59d4865f8977 100644 --- a/cmds/statsd/src/stats_log_util.h +++ b/cmds/statsd/src/stats_log_util.h @@ -74,7 +74,7 @@ void writePullerStatsToStream(const std::pair<int, StatsdStats::PulledAtomStats> util::ProtoOutputStream* protoOutput); // Helper function to write AtomMetricStats to ProtoOutputStream -void writeAtomMetricStatsToStream(const std::pair<int, StatsdStats::AtomMetricStats> &pair, +void writeAtomMetricStatsToStream(const std::pair<int64_t, StatsdStats::AtomMetricStats> &pair, util::ProtoOutputStream *protoOutput); template<class T> diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp index 90f641a34b85..165e57c73743 100644 --- a/cmds/statsd/src/storage/StorageManager.cpp +++ b/cmds/statsd/src/storage/StorageManager.cpp @@ -38,10 +38,13 @@ using std::map; #define STATS_DATA_DIR "/data/misc/stats-data" #define STATS_SERVICE_DIR "/data/misc/stats-service" +#define TRAIN_INFO_DIR "/data/misc/train-info" // for ConfigMetricsReportList const int FIELD_ID_REPORTS = 2; +std::mutex StorageManager::sTrainInfoMutex; + using android::base::StringPrintf; using std::unique_ptr; @@ -92,6 +95,71 @@ void StorageManager::writeFile(const char* file, const void* buffer, int numByte close(fd); } +bool StorageManager::writeTrainInfo(int64_t trainVersionCode, + const std::vector<uint8_t>& experimentIds) { + std::lock_guard<std::mutex> lock(sTrainInfoMutex); + + deleteAllFiles(TRAIN_INFO_DIR); + + string file_name = StringPrintf("%s/%lld", TRAIN_INFO_DIR, (long long)trainVersionCode); + + int fd = open(file_name.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); + if (fd == -1) { + VLOG("Attempt to access %s but failed", file_name.c_str()); + return false; + } + + size_t result = write(fd, experimentIds.data(), experimentIds.size()); + if (result == experimentIds.size()) { + VLOG("Successfully wrote %s", file_name.c_str()); + } else { + VLOG("Failed to write %s", file_name.c_str()); + return false; + } + + result = fchown(fd, AID_STATSD, AID_STATSD); + if (result) { + VLOG("Failed to chown %s to statsd", file_name.c_str()); + return false; + } + + close(fd); + return true; +} + +bool StorageManager::readTrainInfo(InstallTrainInfo& trainInfo) { + std::lock_guard<std::mutex> lock(sTrainInfoMutex); + + unique_ptr<DIR, decltype(&closedir)> dir(opendir(TRAIN_INFO_DIR), closedir); + + if (dir == NULL) { + VLOG("Directory does not exist: %s", TRAIN_INFO_DIR); + return false; + } + + dirent* de; + while ((de = readdir(dir.get()))) { + char* name = de->d_name; + if (name[0] == '.') { + continue; + } + trainInfo.trainVersionCode = StrToInt64(name); + string fullPath = StringPrintf("%s/%s", TRAIN_INFO_DIR, name); + int fd = open(fullPath.c_str(), O_RDONLY | O_CLOEXEC); + if (fd != -1) { + string str; + if (android::base::ReadFdToString(fd, &str)) { + close(fd); + std::copy(str.begin(), str.end(), std::back_inserter(trainInfo.experimentIds)); + VLOG("Read train info file successful: %s", fullPath.c_str()); + return true; + } + } + close(fd); + } + return false; +} + void StorageManager::deleteFile(const char* file) { if (remove(file) != 0) { VLOG("Attempt to delete %s but is not found", file); diff --git a/cmds/statsd/src/storage/StorageManager.h b/cmds/statsd/src/storage/StorageManager.h index dcf3bb607380..d6df8674e59b 100644 --- a/cmds/statsd/src/storage/StorageManager.h +++ b/cmds/statsd/src/storage/StorageManager.h @@ -29,6 +29,11 @@ namespace statsd { using android::util::ProtoOutputStream; +struct TrainInfo { + int64_t trainVersionCode; + std::vector<uint8_t> experimentIds; +}; + class StorageManager : public virtual RefBase { public: /** @@ -37,6 +42,16 @@ public: static void writeFile(const char* file, const void* buffer, int numBytes); /** + * Writes train info. + */ + static bool writeTrainInfo(int64_t trainVersionCode, const std::vector<uint8_t>& experimentIds); + + /** + * Reads train info. + */ + static bool readTrainInfo(InstallTrainInfo& trainInfo); + + /** * Reads the file content to the buffer. */ static bool readFileToString(const char* file, string* content); @@ -109,6 +124,8 @@ private: * Prints disk usage statistics about a directory related to statsd. */ static void printDirStats(int out, const char* path); + + static std::mutex sTrainInfoMutex; }; } // namespace statsd diff --git a/cmds/statsd/statsd.rc b/cmds/statsd/statsd.rc index e0cbd5d25716..a98ecd586b42 100644 --- a/cmds/statsd/statsd.rc +++ b/cmds/statsd/statsd.rc @@ -27,3 +27,4 @@ on post-fs-data mkdir /data/misc/stats-data/ 0770 statsd system mkdir /data/misc/stats-service/ 0770 statsd system mkdir /data/misc/stats-active-metric/ 0770 statsd system + mkdir /data/misc/train-info/ 0770 statsd system diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp index 60df165f102c..5f3aae3ab93a 100644 --- a/cmds/statsd/tests/StatsLogProcessor_test.cpp +++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp @@ -65,7 +65,8 @@ TEST(StatsLogProcessorTest, TestRateLimitByteSize) { sp<AlarmMonitor> periodicAlarmMonitor; // Construct the processor with a dummy sendBroadcast function that does nothing. StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, 0, - [](const ConfigKey& key) { return true; }); + [](const ConfigKey& key) { return true; }, + [](const int&, const vector<int64_t>&) {return true;}); MockMetricsManager mockMetricsManager; @@ -88,7 +89,8 @@ TEST(StatsLogProcessorTest, TestRateLimitBroadcast) { [&broadcastCount](const ConfigKey& key) { broadcastCount++; return true; - }); + }, + [](const int&, const vector<int64_t>&) {return true;}); MockMetricsManager mockMetricsManager; @@ -118,7 +120,8 @@ TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge) { [&broadcastCount](const ConfigKey& key) { broadcastCount++; return true; - }); + }, + [](const int&, const vector<int64_t>&) {return true;}); MockMetricsManager mockMetricsManager; @@ -162,7 +165,8 @@ TEST(StatsLogProcessorTest, TestUidMapHasSnapshot) { [&broadcastCount](const ConfigKey& key) { broadcastCount++; return true; - }); + }, + [](const int&, const vector<int64_t>&) {return true;}); ConfigKey key(3, 4); StatsdConfig config = MakeConfig(true); p.OnConfigUpdated(0, key, config); @@ -192,7 +196,8 @@ TEST(StatsLogProcessorTest, TestEmptyConfigHasNoUidMap) { [&broadcastCount](const ConfigKey& key) { broadcastCount++; return true; - }); + }, + [](const int&, const vector<int64_t>&) {return true;}); ConfigKey key(3, 4); StatsdConfig config = MakeConfig(false); p.OnConfigUpdated(0, key, config); @@ -218,7 +223,8 @@ TEST(StatsLogProcessorTest, TestReportIncludesSubConfig) { [&broadcastCount](const ConfigKey& key) { broadcastCount++; return true; - }); + }, + [](const int&, const vector<int64_t>&) {return true;}); ConfigKey key(3, 4); StatsdConfig config; auto annotation = config.add_annotation(); diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp index f0d9cf188661..c04a40cfebd9 100644 --- a/cmds/statsd/tests/UidMap_test.cpp +++ b/cmds/statsd/tests/UidMap_test.cpp @@ -45,7 +45,8 @@ TEST(UidMapTest, TestIsolatedUID) { sp<AlarmMonitor> subscriberAlarmMonitor; // Construct the processor with a dummy sendBroadcast function that does nothing. StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, - [](const ConfigKey& key) { return true; }); + [](const ConfigKey& key) { return true; }, + [](const int&, const vector<int64_t>&) {return true;}); LogEvent addEvent(android::util::ISOLATED_UID_CHANGED, 1); addEvent.write(100); // parent UID addEvent.write(101); // isolated UID diff --git a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp index 44a88f049443..1ff7982e1232 100644 --- a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp +++ b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp @@ -133,6 +133,13 @@ TEST(StatsdStatsTest, TestSubStats) { stats.noteMetricsReportSent(key, 0); stats.noteMetricsReportSent(key, 0); + // activation_time_sec -> 2 + stats.noteActiveStatusChanged(key, true); + stats.noteActiveStatusChanged(key, true); + + // deactivation_time_sec -> 1 + stats.noteActiveStatusChanged(key, false); + vector<uint8_t> output; stats.dumpStats(&output, true); // Dump and reset stats StatsdStatsReport report; @@ -146,6 +153,8 @@ TEST(StatsdStatsTest, TestSubStats) { EXPECT_EQ(123, configReport.data_drop_bytes(0)); EXPECT_EQ(3, configReport.dump_report_time_sec_size()); EXPECT_EQ(3, configReport.dump_report_data_size_size()); + EXPECT_EQ(2, configReport.activation_time_sec_size()); + EXPECT_EQ(1, configReport.deactivation_time_sec_size()); EXPECT_EQ(1, configReport.annotation_size()); EXPECT_EQ(123, configReport.annotation(0).field_int64()); EXPECT_EQ(456, configReport.annotation(0).field_int32()); @@ -344,6 +353,8 @@ TEST(StatsdStatsTest, TestTimestampThreshold) { stats.noteDataDropped(key, timestamps[i]); stats.noteBroadcastSent(key, timestamps[i]); stats.noteMetricsReportSent(key, 0, timestamps[i]); + stats.noteActiveStatusChanged(key, true, timestamps[i]); + stats.noteActiveStatusChanged(key, false, timestamps[i]); } int32_t newTimestamp = 10000; @@ -352,6 +363,8 @@ TEST(StatsdStatsTest, TestTimestampThreshold) { stats.noteDataDropped(key, 123, 10000); stats.noteBroadcastSent(key, 10000); stats.noteMetricsReportSent(key, 0, 10000); + stats.noteActiveStatusChanged(key, true, 10000); + stats.noteActiveStatusChanged(key, false, 10000); EXPECT_TRUE(stats.mConfigStats.find(key) != stats.mConfigStats.end()); const auto& configStats = stats.mConfigStats[key]; @@ -360,17 +373,23 @@ TEST(StatsdStatsTest, TestTimestampThreshold) { EXPECT_EQ(maxCount, configStats->broadcast_sent_time_sec.size()); EXPECT_EQ(maxCount, configStats->data_drop_time_sec.size()); EXPECT_EQ(maxCount, configStats->dump_report_stats.size()); + EXPECT_EQ(maxCount, configStats->activation_time_sec.size()); + EXPECT_EQ(maxCount, configStats->deactivation_time_sec.size()); // the oldest timestamp is the second timestamp in history EXPECT_EQ(1, configStats->broadcast_sent_time_sec.front()); - EXPECT_EQ(1, configStats->broadcast_sent_time_sec.front()); - EXPECT_EQ(1, configStats->broadcast_sent_time_sec.front()); + EXPECT_EQ(1, configStats->data_drop_bytes.front()); + EXPECT_EQ(1, configStats->dump_report_stats.front().first); + EXPECT_EQ(1, configStats->activation_time_sec.front()); + EXPECT_EQ(1, configStats->deactivation_time_sec.front()); // the last timestamp is the newest timestamp. EXPECT_EQ(newTimestamp, configStats->broadcast_sent_time_sec.back()); EXPECT_EQ(newTimestamp, configStats->data_drop_time_sec.back()); EXPECT_EQ(123, configStats->data_drop_bytes.back()); EXPECT_EQ(newTimestamp, configStats->dump_report_stats.back().first); + EXPECT_EQ(newTimestamp, configStats->activation_time_sec.back()); + EXPECT_EQ(newTimestamp, configStats->deactivation_time_sec.back()); } TEST(StatsdStatsTest, TestSystemServerCrash) { diff --git a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp index b54096441d3f..b294cad1802c 100644 --- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp @@ -117,6 +117,7 @@ TEST(DurationMetricTrackerTest, TestNonSlicedCondition) { DurationMetricProducer durationProducer( kConfigKey, metric, 0 /* condition index */, 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs); + durationProducer.mCondition = ConditionState::kFalse; EXPECT_FALSE(durationProducer.mCondition); EXPECT_FALSE(durationProducer.isConditionSliced()); @@ -140,6 +141,51 @@ TEST(DurationMetricTrackerTest, TestNonSlicedCondition) { EXPECT_EQ(1LL, buckets2[0].mDuration); } +TEST(DurationMetricTrackerTest, TestNonSlicedConditionUnknownState) { + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; + + DurationMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + metric.set_aggregation_type(DurationMetric_AggregationType_SUM); + + int tagId = 1; + LogEvent event1(tagId, bucketStartTimeNs + 1); + event1.init(); + LogEvent event2(tagId, bucketStartTimeNs + 2); + event2.init(); + LogEvent event3(tagId, bucketStartTimeNs + bucketSizeNs + 1); + event3.init(); + LogEvent event4(tagId, bucketStartTimeNs + bucketSizeNs + 3); + event4.init(); + + FieldMatcher dimensions; + DurationMetricProducer durationProducer( + kConfigKey, metric, 0 /* condition index */, 1 /* start index */, 2 /* stop index */, + 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs); + + EXPECT_EQ(ConditionState::kUnknown, durationProducer.mCondition); + EXPECT_FALSE(durationProducer.isConditionSliced()); + + durationProducer.onMatchedLogEvent(1 /* start index*/, event1); + durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); + durationProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); + EXPECT_EQ(0UL, durationProducer.mPastBuckets.size()); + + durationProducer.onMatchedLogEvent(1 /* start index*/, event3); + durationProducer.onConditionChanged(true /* condition */, bucketStartTimeNs + bucketSizeNs + 2); + durationProducer.onMatchedLogEvent(2 /* stop index*/, event4); + durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); + EXPECT_EQ(1UL, durationProducer.mPastBuckets.size()); + const auto& buckets2 = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; + EXPECT_EQ(1UL, buckets2.size()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets2[0].mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets2[0].mBucketEndNs); + EXPECT_EQ(1LL, buckets2[0].mDuration); +} + TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade) { /** * The duration starts from the first bucket, through the two partial buckets (10-70sec), diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp index 572b1991f426..7e7ffeda8053 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -2176,7 +2176,7 @@ TEST(ValueMetricProducerTest, TestResetBaseOnPullFailBeforeConditionChange) { eventMatcherWizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.mCondition = true; + valueProducer.mCondition = ConditionState::kTrue; vector<shared_ptr<LogEvent>> allData; valueProducer.onDataPulled(allData, /** succeed */ false, bucketStartTimeNs); @@ -2226,7 +2226,7 @@ TEST(ValueMetricProducerTest, TestResetBaseOnPullDelayExceeded) { eventMatcherWizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.mCondition = false; + valueProducer.mCondition = ConditionState::kFalse; // Max delay is set to 0 so pull will exceed max delay. valueProducer.onConditionChanged(true, bucketStartTimeNs + 1); @@ -2257,7 +2257,7 @@ TEST(ValueMetricProducerTest, TestResetBaseOnPullTooLate) { eventMatcherWizard, tagId, bucket2StartTimeNs, bucket2StartTimeNs, pullerManager); - valueProducer.mCondition = false; + valueProducer.mCondition = ConditionState::kFalse; // Event should be skipped since it is from previous bucket. // Pull should not be called. @@ -2299,7 +2299,7 @@ TEST(ValueMetricProducerTest, TestBaseSetOnConditionChange) { eventMatcherWizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.mCondition = false; + valueProducer.mCondition = ConditionState::kFalse; valueProducer.mHasGlobalBase = false; valueProducer.onConditionChanged(true, bucketStartTimeNs + 1); @@ -2351,7 +2351,7 @@ TEST(ValueMetricProducerTest, TestInvalidBucketWhenOneConditionFailed) { eventMatcherWizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.mCondition = true; + valueProducer.mCondition = ConditionState::kTrue; // Bucket start. vector<shared_ptr<LogEvent>> allData; @@ -2429,7 +2429,7 @@ TEST(ValueMetricProducerTest, TestInvalidBucketWhenGuardRailHit) { eventMatcherWizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.mCondition = false; + valueProducer.mCondition = ConditionState::kFalse; valueProducer.onConditionChanged(true, bucket2StartTimeNs + 2); EXPECT_EQ(true, valueProducer.mCurrentBucketIsInvalid); EXPECT_EQ(0UL, valueProducer.mCurrentSlicedBucket.size()); @@ -2481,7 +2481,7 @@ TEST(ValueMetricProducerTest, TestInvalidBucketWhenInitialPullFailed) { eventMatcherWizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.mCondition = true; + valueProducer.mCondition = ConditionState::kTrue; // Bucket start. vector<shared_ptr<LogEvent>> allData; @@ -2564,7 +2564,7 @@ TEST(ValueMetricProducerTest, TestInvalidBucketWhenLastPullFailed) { eventMatcherWizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.mCondition = true; + valueProducer.mCondition = ConditionState::kTrue; // Bucket start. vector<shared_ptr<LogEvent>> allData; diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp index b8b1a1db2c12..2c4f3c7692c9 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -461,7 +461,8 @@ sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs, const in [](const sp<IStatsCompanionService>&){}); sp<StatsLogProcessor> processor = new StatsLogProcessor(uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, - timeBaseNs, [](const ConfigKey&) { return true; }); + timeBaseNs, [](const ConfigKey&) { return true; }, + [](const int&, const vector<int64_t>&) {return true;}); processor->OnConfigUpdated(currentTimeNs, key, config); return processor; } @@ -826,4 +827,4 @@ void backfillStartEndTimestamp(ConfigMetricsReportList *config_report_list) { } // namespace statsd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/cmds/statsd/tests/storage/StorageManager_test.cpp b/cmds/statsd/tests/storage/StorageManager_test.cpp new file mode 100644 index 000000000000..f66de0518f80 --- /dev/null +++ b/cmds/statsd/tests/storage/StorageManager_test.cpp @@ -0,0 +1,52 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <stdio.h> +#include "src/storage/StorageManager.h" + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +using namespace testing; +using std::make_shared; +using std::shared_ptr; +using std::vector; +using testing::Contains; + +TEST(StorageManagerTest, TrainInfoReadWriteTest) { + InstallTrainInfo trainInfo; + trainInfo.trainVersionCode = 12345; + const char* expIds = "test_ids"; + trainInfo.experimentIds.assign(expIds, expIds + strlen(expIds)); + + StorageManager::writeTrainInfo(trainInfo.trainVersionCode, trainInfo.experimentIds); + + InstallTrainInfo result; + StorageManager::readTrainInfo(result); + EXPECT_EQ(trainInfo.trainVersionCode, result.trainVersionCode); + EXPECT_EQ(trainInfo.experimentIds.size(), result.experimentIds.size()); + EXPECT_EQ(trainInfo.experimentIds, result.experimentIds); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt index b72ce8977128..dc2ed4cc0d69 100644 --- a/config/hiddenapi-greylist.txt +++ b/config/hiddenapi-greylist.txt @@ -542,8 +542,6 @@ Landroid/net/IConnectivityManager;->getTetherableWifiRegexs()[Ljava/lang/String; Landroid/net/IConnectivityManager;->getTetheredIfaces()[Ljava/lang/String; Landroid/net/IConnectivityManager;->getTetheringErroredIfaces()[Ljava/lang/String; Landroid/net/IConnectivityManager;->startLegacyVpn(Lcom/android/internal/net/VpnProfile;)V -Landroid/net/INetd$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/INetd; -Landroid/net/INetd;->interfaceAddAddress(Ljava/lang/String;Ljava/lang/String;I)V Landroid/net/INetworkManagementEventObserver$Stub;-><init>()V Landroid/net/INetworkPolicyListener$Stub;-><init>()V Landroid/net/INetworkPolicyManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/INetworkPolicyManager; @@ -2825,7 +2823,6 @@ Lcom/android/internal/telephony/gsm/UsimPhoneBookManager;->reset()V Lcom/android/internal/telephony/GsmAlphabet$TextEncodingDetails;-><init>()V Lcom/android/internal/telephony/GsmCdmaCall;->attachFake(Lcom/android/internal/telephony/Connection;Lcom/android/internal/telephony/Call$State;)V Lcom/android/internal/telephony/GsmCdmaCallTracker;->clearDisconnected()V -Lcom/android/internal/telephony/GsmCdmaCallTracker;->dialThreeWay(Ljava/lang/String;)Lcom/android/internal/telephony/Connection; Lcom/android/internal/telephony/GsmCdmaCallTracker;->disableDataCallInEmergencyCall(Ljava/lang/String;)V Lcom/android/internal/telephony/GsmCdmaCallTracker;->fakeHoldForegroundBeforeDial()V Lcom/android/internal/telephony/GsmCdmaCallTracker;->getPhone()Lcom/android/internal/telephony/GsmCdmaPhone; diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 0eadd1dcd903..5f778da44a1c 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -65,6 +65,7 @@ import android.net.Uri; import android.os.BadParcelableException; import android.os.Build; import android.os.Bundle; +import android.os.GraphicsEnvironment; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -7708,6 +7709,8 @@ public class Activity extends ContextThemeWrapper } } + GraphicsEnvironment.getInstance().showAngleInUseDialogBox(this); + mActivityTransitionState.enterReady(this); dispatchActivityPostStarted(); } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index db3b720642aa..ca3c72627e3b 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -2019,8 +2019,9 @@ public class ActivityManager { return getTaskService().isActivityStartAllowedOnDisplay(displayId, intent, intent.resolveTypeIfNeeded(context.getContentResolver()), context.getUserId()); } catch (RemoteException e) { - throw new RuntimeException("Failure from system", e); + e.rethrowFromSystemServer(); } + return false; } /** diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 1a728c12e138..6908ca27480c 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -2101,7 +2101,8 @@ class ContextImpl extends Context { } private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName, - int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) { + int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo, + String[] overlayDirs) { final String[] splitResDirs; final ClassLoader classLoader; try { @@ -2113,7 +2114,7 @@ class ContextImpl extends Context { return ResourcesManager.getInstance().getResources(activityToken, pi.getResDir(), splitResDirs, - pi.getOverlayDirs(), + overlayDirs, pi.getApplicationInfo().sharedLibraryFiles, displayId, overrideConfig, @@ -2131,9 +2132,11 @@ class ContextImpl extends Context { new UserHandle(UserHandle.getUserId(application.uid)), flags, null, null); final int displayId = getDisplayId(); - + // overlayDirs is retrieved directly from ApplicationInfo since ActivityThread may have + // a LoadedApk containing Resources with stale overlays for a remote application. + final String[] overlayDirs = application.resourceDirs; c.setResources(createResources(mActivityToken, pi, null, displayId, null, - getDisplayAdjustments(displayId).getCompatibilityInfo())); + getDisplayAdjustments(displayId).getCompatibilityInfo(), overlayDirs)); if (c.mResources != null) { return c; } @@ -2168,7 +2171,7 @@ class ContextImpl extends Context { final int displayId = getDisplayId(); c.setResources(createResources(mActivityToken, pi, null, displayId, null, - getDisplayAdjustments(displayId).getCompatibilityInfo())); + getDisplayAdjustments(displayId).getCompatibilityInfo(), pi.getOverlayDirs())); if (c.mResources != null) { return c; } @@ -2218,7 +2221,8 @@ class ContextImpl extends Context { final int displayId = getDisplayId(); context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId, - overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo())); + overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo(), + mPackageInfo.getOverlayDirs())); return context; } @@ -2233,7 +2237,8 @@ class ContextImpl extends Context { final int displayId = display.getDisplayId(); context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId, - null, getDisplayAdjustments(displayId).getCompatibilityInfo())); + null, getDisplayAdjustments(displayId).getCompatibilityInfo(), + mPackageInfo.getOverlayDirs())); context.mDisplay = display; return context; } @@ -2416,7 +2421,7 @@ class ContextImpl extends Context { ContextImpl context = new ContextImpl(null, systemContext.mMainThread, packageInfo, null, null, null, 0, null, null); context.setResources(createResources(null, packageInfo, null, displayId, null, - packageInfo.getCompatibilityInfo())); + packageInfo.getCompatibilityInfo(), packageInfo.getOverlayDirs())); context.updateDisplay(displayId); return context; } diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index d46dbedfe8c2..5c4c0052cfbb 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -870,8 +870,9 @@ public final class LoadedApk { } } - // /vendor/lib, /odm/lib and /product/lib are added to the native lib search - // paths of the classloader. Note that this is done AFTER the classloader is + // /aepx/com.android.runtime/lib, /vendor/lib, /odm/lib and /product/lib + // are added to the native lib search paths of the classloader. + // Note that this is done AFTER the classloader is // created by ApplicationLoaders.getDefault().getClassLoader(...). The // reason is because if we have added the paths when creating the classloader // above, the paths are also added to the search path of the linker namespace @@ -888,8 +889,11 @@ public final class LoadedApk { // System.loadLibrary(). In order to prevent the problem, we explicitly // add the paths only to the classloader, and not to the native loader // (linker namespace). - List<String> extraLibPaths = new ArrayList<>(3); + List<String> extraLibPaths = new ArrayList<>(4); String abiSuffix = VMRuntime.getRuntime().is64Bit() ? "64" : ""; + if (!defaultSearchPaths.contains("/apex/com.android.runtime/lib")) { + extraLibPaths.add("/apex/com.android.runtime/lib" + abiSuffix); + } if (!defaultSearchPaths.contains("/vendor/lib")) { extraLibPaths.add("/vendor/lib" + abiSuffix); } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 807b7f2ac349..cca8bd00b646 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -101,7 +101,6 @@ import android.net.IConnectivityManager; import android.net.IEthernetManager; import android.net.IIpMemoryStore; import android.net.IIpSecService; -import android.net.INetd; import android.net.INetworkPolicyManager; import android.net.IpMemoryStore; import android.net.IpSecManager; @@ -330,11 +329,10 @@ final class SystemServiceRegistry { return new ConnectivityManager(context, service); }}); - registerService(Context.NETD_SERVICE, INetd.class, new StaticServiceFetcher<INetd>() { + registerService(Context.NETD_SERVICE, IBinder.class, new StaticServiceFetcher<IBinder>() { @Override - public INetd createService() throws ServiceNotFoundException { - return INetd.Stub.asInterface( - ServiceManager.getServiceOrThrow(Context.NETD_SERVICE)); + public IBinder createService() throws ServiceNotFoundException { + return ServiceManager.getServiceOrThrow(Context.NETD_SERVICE); } }); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 478ac9c8dacd..806536b6947c 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2125,7 +2125,7 @@ public class DevicePolicyManager { * Callback used in {@link #installSystemUpdate} to indicate that there was an error while * trying to install an update. */ - public abstract static class InstallUpdateCallback { + public abstract static class InstallSystemUpdateCallback { /** Represents an unknown error while trying to install an update. */ public static final int UPDATE_ERROR_UNKNOWN = 1; @@ -2144,7 +2144,12 @@ public class DevicePolicyManager { /** Represents the battery being too low to apply an update. */ public static final int UPDATE_ERROR_BATTERY_LOW = 5; - /** Method invoked when there was an error while installing an update. */ + /** + * Method invoked when there was an error while installing an update. + * + * <p>The given error message is not intended to be user-facing. It is intended to be + * reported back to the IT admin to be read. + */ public void onInstallUpdateError( @InstallUpdateCallbackErrorConstants int errorCode, String errorMessage) { } @@ -2154,11 +2159,11 @@ public class DevicePolicyManager { * @hide */ @IntDef(prefix = { "UPDATE_ERROR_" }, value = { - InstallUpdateCallback.UPDATE_ERROR_UNKNOWN, - InstallUpdateCallback.UPDATE_ERROR_INCORRECT_OS_VERSION, - InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID, - InstallUpdateCallback.UPDATE_ERROR_FILE_NOT_FOUND, - InstallUpdateCallback.UPDATE_ERROR_BATTERY_LOW + InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN, + InstallSystemUpdateCallback.UPDATE_ERROR_INCORRECT_OS_VERSION, + InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID, + InstallSystemUpdateCallback.UPDATE_ERROR_FILE_NOT_FOUND, + InstallSystemUpdateCallback.UPDATE_ERROR_BATTERY_LOW }) @Retention(RetentionPolicy.SOURCE) public @interface InstallUpdateCallbackErrorConstants {} @@ -10431,9 +10436,9 @@ public class DevicePolicyManager { * doesn't necessarily mean that the update has been applied successfully. The caller should * additionally check the system version with {@link android.os.Build#FINGERPRINT} or {@link * android.os.Build.VERSION}. If an error occurs during processing the OTA before the reboot, - * the caller will be notified by {@link InstallUpdateCallback}. If device does not have + * the caller will be notified by {@link InstallSystemUpdateCallback}. If device does not have * sufficient battery level, the installation will fail with error {@link - * InstallUpdateCallback#UPDATE_ERROR_BATTERY_LOW}. + * InstallSystemUpdateCallback#UPDATE_ERROR_BATTERY_LOW}. * * @param admin The {@link DeviceAdminReceiver} that this request is associated with. * @param updateFilePath An Uri of the file that contains the update. The file should be @@ -10445,7 +10450,7 @@ public class DevicePolicyManager { public void installSystemUpdate( @NonNull ComponentName admin, @NonNull Uri updateFilePath, @NonNull @CallbackExecutor Executor executor, - @NonNull InstallUpdateCallback callback) { + @NonNull InstallSystemUpdateCallback callback) { throwIfParentInstance("installUpdate"); if (mService == null) { return; @@ -10465,19 +10470,20 @@ public class DevicePolicyManager { } catch (FileNotFoundException e) { Log.w(TAG, e); executeCallback( - InstallUpdateCallback.UPDATE_ERROR_FILE_NOT_FOUND, Log.getStackTraceString(e), + InstallSystemUpdateCallback.UPDATE_ERROR_FILE_NOT_FOUND, + Log.getStackTraceString(e), executor, callback); } catch (IOException e) { Log.w(TAG, e); executeCallback( - InstallUpdateCallback.UPDATE_ERROR_UNKNOWN, Log.getStackTraceString(e), + InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN, Log.getStackTraceString(e), executor, callback); } } private void executeCallback(int errorCode, String errorMessage, @NonNull @CallbackExecutor Executor executor, - @NonNull InstallUpdateCallback callback) { + @NonNull InstallSystemUpdateCallback callback) { executor.execute(() -> callback.onInstallUpdateError(errorCode, errorMessage)); } @@ -10537,6 +10543,8 @@ public class DevicePolicyManager { * @hide */ @SystemApi + @RequiresPermission(value = android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, + conditional = true) public void setProfileOwnerCanAccessDeviceIdsForUser( @NonNull ComponentName who, @NonNull UserHandle userHandle) { if (mService == null) { diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index f1bfe8671eec..7672ccfcd995 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -2998,7 +2998,9 @@ public abstract class ContentResolver implements ContentInterface { * * @param key the key to add * @param value the value to add + * {@hide} */ + @SystemApi public void putCache(Uri key, Bundle value) { try { getContentService().putCache(mContext.getPackageName(), key, value, @@ -3014,7 +3016,9 @@ public abstract class ContentResolver implements ContentInterface { * @param key the key to get the value * @return the matched value. If the key doesn't exist, will return null. * @see #putCache(Uri, Bundle) + * {@hide} */ + @SystemApi public Bundle getCache(Uri key) { try { final Bundle bundle = getContentService().getCache(mContext.getPackageName(), key, diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index eaf6c5a9d1cc..3ea78df6ae39 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -5910,24 +5910,24 @@ public abstract class PackageManager { /** * Flag to denote no restrictions. This should be used to clear any restrictions that may have * been previously set for the package. - * @see PackageManager.DistractionRestriction * @hide + * @see #setDistractingPackageRestrictions(String[], int) */ @SystemApi public static final int RESTRICTION_NONE = 0x0; /** * Flag to denote that a package should be hidden from any suggestions to the user. - * @see PackageManager.DistractionRestriction * @hide + * @see #setDistractingPackageRestrictions(String[], int) */ @SystemApi public static final int RESTRICTION_HIDE_FROM_SUGGESTIONS = 0x00000001; /** * Flag to denote that a package's notifications should be hidden. - * @see PackageManager.DistractionRestriction * @hide + * @see #setDistractingPackageRestrictions(String[], int) */ @SystemApi public static final int RESTRICTION_HIDE_NOTIFICATIONS = 0x00000002; @@ -5939,7 +5939,6 @@ public abstract class PackageManager { * @see #setDistractingPackageRestrictions(String[], int) * @hide */ - @SystemApi @IntDef(flag = true, prefix = {"RESTRICTION_"}, value = { RESTRICTION_NONE, RESTRICTION_HIDE_FROM_SUGGESTIONS, @@ -5957,14 +5956,16 @@ public abstract class PackageManager { * <p>The caller must hold {@link android.Manifest.permission#SUSPEND_APPS} to use this API. * * @param packages Packages to mark as distracting. - * @param restrictionFlags Any combination of {@link DistractionRestriction restrictions} to - * impose on the given packages. {@link #RESTRICTION_NONE} can be used - * to clear any existing restrictions. + * @param restrictionFlags Any combination of restrictions to impose on the given packages. + * {@link #RESTRICTION_NONE} can be used to clear any existing + * restrictions. * @return A list of packages that could not have the {@code restrictionFlags} set. The system * may prevent restricting critical packages to preserve normal device function. * - * @see DistractionRestriction * @hide + * @see #RESTRICTION_NONE + * @see #RESTRICTION_HIDE_FROM_SUGGESTIONS + * @see #RESTRICTION_HIDE_NOTIFICATIONS */ @SystemApi @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 0abd5eaaf2aa..0f67262bf901 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -4848,7 +4848,7 @@ public class PackageParser { } /** - * Sets every the max aspect ratio of every child activity that doesn't already have an aspect + * Sets every the min aspect ratio of every child activity that doesn't already have an aspect * ratio set. */ private void setMinAspectRatio(Package owner) { diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index fe68b8a048c2..b1553250d638 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -2273,7 +2273,8 @@ public final class ShortcutInfo implements Parcelable { CharSequence disabledMessage, int disabledMessageResId, String disabledMessageResName, Set<String> categories, Intent[] intentsWithExtras, int rank, PersistableBundle extras, long lastChangedTimestamp, - int flags, int iconResId, String iconResName, String bitmapPath, int disabledReason) { + int flags, int iconResId, String iconResName, String bitmapPath, int disabledReason, + Person[] persons) { mUserId = userId; mId = id; mPackageName = packageName; @@ -2299,5 +2300,6 @@ public final class ShortcutInfo implements Parcelable { mIconResName = iconResName; mBitmapPath = bitmapPath; mDisabledReason = disabledReason; + mPersons = persons; } } diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index f3ebd7f36fd6..ac44fe93ac31 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -16,6 +16,8 @@ package android.hardware.display; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.ParceledListSlice; @@ -229,7 +231,17 @@ public final class DisplayManagerGlobal { return getCompatibleDisplay(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); } - public void registerDisplayListener(DisplayListener listener, Handler handler) { + /** + * Register a listener for display-related changes. + * + * @param listener The listener that will be called when display changes occur. + * @param handler Handler for the thread that will be receiving the callbacks. May be null. + * If null, listener will use the handler for the current thread, and if still null, + * the handler for the main thread. + * If that is still null, a runtime exception will be thrown. + */ + public void registerDisplayListener(@NonNull DisplayListener listener, + @Nullable Handler handler) { if (listener == null) { throw new IllegalArgumentException("listener must not be null"); } @@ -237,7 +249,8 @@ public final class DisplayManagerGlobal { synchronized (mLock) { int index = findDisplayListenerLocked(listener); if (index < 0) { - mDisplayListeners.add(new DisplayListenerDelegate(listener, handler)); + Looper looper = getLooperForHandler(handler); + mDisplayListeners.add(new DisplayListenerDelegate(listener, looper)); registerCallbackIfNeededLocked(); } } @@ -258,6 +271,17 @@ public final class DisplayManagerGlobal { } } + private static Looper getLooperForHandler(@Nullable Handler handler) { + Looper looper = handler != null ? handler.getLooper() : Looper.myLooper(); + if (looper == null) { + looper = Looper.getMainLooper(); + } + if (looper == null) { + throw new RuntimeException("Could not get Looper for the UI thread."); + } + return looper; + } + private int findDisplayListenerLocked(DisplayListener listener) { final int numListeners = mDisplayListeners.size(); for (int i = 0; i < numListeners; i++) { @@ -636,8 +660,8 @@ public final class DisplayManagerGlobal { private static final class DisplayListenerDelegate extends Handler { public final DisplayListener mListener; - public DisplayListenerDelegate(DisplayListener listener, Handler handler) { - super(handler != null ? handler.getLooper() : Looper.myLooper(), null, true /*async*/); + DisplayListenerDelegate(DisplayListener listener, @NonNull Looper looper) { + super(looper, null, true /*async*/); mListener = listener; } diff --git a/core/java/android/net/InterfaceConfiguration.java b/core/java/android/net/InterfaceConfiguration.java index 62cf7d7ceb25..b9d49c14f6c6 100644 --- a/core/java/android/net/InterfaceConfiguration.java +++ b/core/java/android/net/InterfaceConfiguration.java @@ -36,8 +36,9 @@ public class InterfaceConfiguration implements Parcelable { private LinkAddress mAddr; private HashSet<String> mFlags = Sets.newHashSet(); - private static final String FLAG_UP = INetd.IF_STATE_UP; - private static final String FLAG_DOWN = INetd.IF_STATE_DOWN; + // Must be kept in sync with constant in INetd.aidl + private static final String FLAG_UP = "up"; + private static final String FLAG_DOWN = "down"; private static final String[] EMPTY_STRING_ARRAY = new String[0]; diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index 5ab34e9aa6e8..bf272625e713 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -92,16 +92,6 @@ public class NetworkPolicyManager { public static final int MASK_ALL_NETWORKS = 0b11110000; public static final int FIREWALL_RULE_DEFAULT = 0; - public static final int FIREWALL_RULE_ALLOW = INetd.FIREWALL_RULE_ALLOW; - public static final int FIREWALL_RULE_DENY = INetd.FIREWALL_RULE_DENY; - - public static final int FIREWALL_TYPE_WHITELIST = INetd.FIREWALL_WHITELIST; - public static final int FIREWALL_TYPE_BLACKLIST = INetd.FIREWALL_BLACKLIST; - - public static final int FIREWALL_CHAIN_NONE = INetd.FIREWALL_CHAIN_NONE; - public static final int FIREWALL_CHAIN_DOZABLE = INetd.FIREWALL_CHAIN_DOZABLE; - public static final int FIREWALL_CHAIN_STANDBY = INetd.FIREWALL_CHAIN_STANDBY; - public static final int FIREWALL_CHAIN_POWERSAVE = INetd.FIREWALL_CHAIN_POWERSAVE; public static final String FIREWALL_CHAIN_NAME_NONE = "none"; public static final String FIREWALL_CHAIN_NAME_DOZABLE = "dozable"; diff --git a/core/java/android/net/NetworkStack.java b/core/java/android/net/NetworkStack.java index 7a4c9bc16ac7..ca49438390e9 100644 --- a/core/java/android/net/NetworkStack.java +++ b/core/java/android/net/NetworkStack.java @@ -15,6 +15,7 @@ */ package android.net; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL; @@ -27,6 +28,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.pm.PackageManager; import android.net.dhcp.DhcpServingParamsParcel; import android.net.dhcp.IDhcpServerCallbacks; import android.net.ip.IIpClientCallbacks; @@ -201,7 +203,33 @@ public class NetworkStack { final ComponentName comp = intent.resolveSystemService(context.getPackageManager(), 0); intent.setComponent(comp); - if (comp == null || !context.bindServiceAsUser(intent, new NetworkStackConnection(), + if (comp == null) { + Slog.wtf(TAG, "Could not resolve the network stack with " + intent); + // TODO: crash/reboot system server ? + return; + } + + final PackageManager pm = context.getPackageManager(); + int uid = -1; + try { + uid = pm.getPackageUid(comp.getPackageName(), UserHandle.USER_SYSTEM); + } catch (PackageManager.NameNotFoundException e) { + Slog.wtf("Network stack package not found", e); + // Fall through + } + + if (uid != Process.NETWORK_STACK_UID) { + throw new SecurityException("Invalid network stack UID: " + uid); + } + + final int hasPermission = + pm.checkPermission(PERMISSION_MAINLINE_NETWORK_STACK, comp.getPackageName()); + if (hasPermission != PERMISSION_GRANTED) { + throw new SecurityException( + "Network stack does not have permission " + PERMISSION_MAINLINE_NETWORK_STACK); + } + + if (!context.bindServiceAsUser(intent, new NetworkStackConnection(), Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.SYSTEM)) { Slog.wtf(TAG, "Could not bind to network stack in-process, or in app with " + intent); diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java index 1f3369376b10..a851e04e78ec 100644 --- a/core/java/android/os/AsyncTask.java +++ b/core/java/android/os/AsyncTask.java @@ -21,13 +21,14 @@ import android.annotation.Nullable; import android.annotation.WorkerThread; import java.util.ArrayDeque; -import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.FutureTask; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -190,13 +191,19 @@ import java.util.concurrent.atomic.AtomicInteger; public abstract class AsyncTask<Params, Progress, Result> { private static final String LOG_TAG = "AsyncTask"; - private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); - // We want at least 2 threads and at most 4 threads in the core pool, - // preferring to have 1 less than the CPU count to avoid saturating - // the CPU with background work - private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); - private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; - private static final int KEEP_ALIVE_SECONDS = 30; + // We keep only a single pool thread around all the time. + // We let the pool grow to a fairly large number of threads if necessary, + // but let them time out quickly. In the unlikely case that we run out of threads, + // we fall back to a simple unbounded-queue executor. + // This combination ensures that: + // 1. We normally keep few threads (1) around. + // 2. We queue only after launching a significantly larger, but still bounded, set of threads. + // 3. We keep the total number of threads bounded, but still allow an unbounded set + // of tasks to be queued. + private static final int CORE_POOL_SIZE = 1; + private static final int MAXIMUM_POOL_SIZE = 20; + private static final int BACKUP_POOL_SIZE = 5; + private static final int KEEP_ALIVE_SECONDS = 3; private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); @@ -206,8 +213,29 @@ public abstract class AsyncTask<Params, Progress, Result> { } }; - private static final BlockingQueue<Runnable> sPoolWorkQueue = - new LinkedBlockingQueue<Runnable>(128); + // Used only for rejected executions. + // Initialization protected by sRunOnSerialPolicy lock. + private static ThreadPoolExecutor sBackupExecutor; + private static LinkedBlockingQueue<Runnable> sBackupExecutorQueue; + + private static final RejectedExecutionHandler sRunOnSerialPolicy = + new RejectedExecutionHandler() { + public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { + android.util.Log.w(LOG_TAG, "Exceeded ThreadPoolExecutor pool size"); + // As a last ditch fallback, run it on an executor with an unbounded queue. + // Create this executor lazily, hopefully almost never. + synchronized (this) { + if (sBackupExecutor == null) { + sBackupExecutorQueue = new LinkedBlockingQueue<Runnable>(); + sBackupExecutor = new ThreadPoolExecutor( + BACKUP_POOL_SIZE, BACKUP_POOL_SIZE, KEEP_ALIVE_SECONDS, + TimeUnit.SECONDS, sBackupExecutorQueue, sThreadFactory); + sBackupExecutor.allowCoreThreadTimeOut(true); + } + } + sBackupExecutor.execute(r); + } + }; /** * An {@link Executor} that can be used to execute tasks in parallel. @@ -217,8 +245,8 @@ public abstract class AsyncTask<Params, Progress, Result> { static { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, - sPoolWorkQueue, sThreadFactory); - threadPoolExecutor.allowCoreThreadTimeOut(true); + new SynchronousQueue<Runnable>(), sThreadFactory); + threadPoolExecutor.setRejectedExecutionHandler(sRunOnSerialPolicy); THREAD_POOL_EXECUTOR = threadPoolExecutor; } diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index 269c781397ad..cc241b3b7756 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -16,6 +16,7 @@ package android.os; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -24,14 +25,10 @@ import android.content.pm.PackageManager; 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 android.widget.Toast; import dalvik.system.VMRuntime; @@ -67,8 +64,7 @@ public class GraphicsEnvironment { 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 static final String GAME_DRIVER_WHITELIST_ALL = "*"; private ClassLoader mClassLoader; private String mLayerPath; @@ -222,9 +218,17 @@ public class GraphicsEnvironment { } - private static List<String> getGlobalSettingsString(Bundle bundle, String globalSetting) { - List<String> valueList = null; - final String settingsValue = bundle.getString(globalSetting); + private static List<String> getGlobalSettingsString(ContentResolver contentResolver, + Bundle bundle, + String globalSetting) { + final List<String> valueList; + final String settingsValue; + + if (bundle != null) { + settingsValue = bundle.getString(globalSetting); + } else { + settingsValue = Settings.Global.getString(contentResolver, globalSetting); + } if (settingsValue != null) { valueList = new ArrayList<>(Arrays.asList(settingsValue.split(","))); @@ -246,17 +250,27 @@ public class GraphicsEnvironment { return -1; } - private static String getDriverForPkg(Bundle bundle, String packageName) { - final String allUseAngle = - bundle.getString(Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE); + private static String getDriverForPkg(Context context, Bundle bundle, String packageName) { + final String allUseAngle; + if (bundle != null) { + allUseAngle = + bundle.getString(Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE); + } else { + ContentResolver contentResolver = context.getContentResolver(); + allUseAngle = Settings.Global.getString(contentResolver, + Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE); + } if ((allUseAngle != null) && allUseAngle.equals("1")) { return sDriverMap.get(OpenGlDriverChoice.ANGLE); } - final List<String> globalSettingsDriverPkgs = getGlobalSettingsString( - bundle, Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS); - final List<String> globalSettingsDriverValues = getGlobalSettingsString( - bundle, Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES); + final ContentResolver contentResolver = context.getContentResolver(); + final List<String> globalSettingsDriverPkgs = + getGlobalSettingsString(contentResolver, bundle, + Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS); + final List<String> globalSettingsDriverValues = + getGlobalSettingsString(contentResolver, bundle, + Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES); // Make sure we have a good package name if ((packageName == null) || (packageName.isEmpty())) { @@ -308,7 +322,7 @@ public class GraphicsEnvironment { * True: Temporary rules file was loaded. * False: Temporary rules file was *not* loaded. */ - private boolean setupAngleWithTempRulesFile(Context context, + private static boolean setupAngleWithTempRulesFile(Context context, String packageName, String paths, String devOptIn) { @@ -372,7 +386,7 @@ public class GraphicsEnvironment { * True: APK rules file was loaded. * False: APK rules file was *not* loaded. */ - private boolean setupAngleRulesApk(String anglePkgName, + private static boolean setupAngleRulesApk(String anglePkgName, ApplicationInfo angleInfo, PackageManager pm, String packageName, @@ -405,23 +419,32 @@ public class GraphicsEnvironment { /** * Pull ANGLE whitelist from GlobalSettings and compare against current package */ - private boolean checkAngleWhitelist(Bundle bundle, String packageName) { + private static boolean checkAngleWhitelist(Context context, Bundle bundle, String packageName) { + final ContentResolver contentResolver = context.getContentResolver(); final List<String> angleWhitelist = - getGlobalSettingsString(bundle, Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST); + getGlobalSettingsString(contentResolver, bundle, + Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST); return angleWhitelist.contains(packageName); } /** * Pass ANGLE details down to trigger enable logic + * + * @param context + * @param bundle + * @param packageName + * @return true: ANGLE setup successfully + * false: ANGLE not setup (not on whitelist, ANGLE not present, etc.) */ - public void setupAngle(Context context, Bundle bundle, PackageManager pm, String packageName) { + public boolean setupAngle(Context context, Bundle bundle, PackageManager pm, + String packageName) { if (packageName.isEmpty()) { Log.v(TAG, "No package name available yet, skipping ANGLE setup"); - return; + return false; } - final String devOptIn = getDriverForPkg(bundle, packageName); + final String devOptIn = getDriverForPkg(context, bundle, packageName); if (DEBUG) { Log.v(TAG, "ANGLE Developer option for '" + packageName + "' " + "set to: '" + devOptIn + "'"); @@ -439,11 +462,11 @@ public class GraphicsEnvironment { // load a driver, GraphicsEnv::shouldUseAngle() has seen the package name before // and can confidently answer yes/no based on the previously set developer // option value. - final boolean whitelisted = checkAngleWhitelist(bundle, packageName); + final boolean whitelisted = checkAngleWhitelist(context, bundle, packageName); final boolean defaulted = devOptIn.equals(sDriverMap.get(OpenGlDriverChoice.DEFAULT)); final boolean rulesCheck = (whitelisted || !defaulted); if (!rulesCheck) { - return; + return false; } if (whitelisted) { @@ -456,7 +479,7 @@ public class GraphicsEnvironment { final String anglePkgName = getAnglePackageName(pm); if (anglePkgName.isEmpty()) { Log.e(TAG, "Failed to find ANGLE package."); - return; + return false; } final ApplicationInfo angleInfo; @@ -464,7 +487,7 @@ public class GraphicsEnvironment { angleInfo = pm.getApplicationInfo(anglePkgName, PackageManager.MATCH_SYSTEM_ONLY); } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "ANGLE package '" + anglePkgName + "' not installed"); - return; + return false; } final String abi = chooseAbi(angleInfo); @@ -480,12 +503,62 @@ public class GraphicsEnvironment { if (setupAngleWithTempRulesFile(context, packageName, paths, devOptIn)) { // We setup ANGLE with a temp rules file, so we're done here. - return; + return true; } if (setupAngleRulesApk(anglePkgName, angleInfo, pm, packageName, paths, devOptIn)) { // We setup ANGLE with rules from the APK, so we're done here. - return; + return true; + } + + return false; + } + + /** + * Determine if the "ANGLE In Use" dialog box should be shown. + */ + private boolean shouldShowAngleInUseDialogBox(Context context) { + try { + ContentResolver contentResolver = context.getContentResolver(); + final int showDialogBox = Settings.Global.getInt(contentResolver, + Settings.Global.GLOBAL_SETTINGS_SHOW_ANGLE_IN_USE_DIALOG_BOX); + + return (showDialogBox == 1); + } catch (Settings.SettingNotFoundException | SecurityException e) { + // Do nothing and move on + } + + // No setting, so assume false + return false; + } + + /** + * Determine if ANGLE should be used. + */ + private boolean shouldUseAngle(Context context, String packageName) { + // Need to make sure we are evaluating ANGLE usage for the correct circumstances + if (!setupAngle(context, null, context.getPackageManager(), packageName)) { + Log.v(TAG, "Package '" + packageName + "' should use not ANGLE"); + return false; + } + + final boolean useAngle = getShouldUseAngle(packageName); + Log.v(TAG, "Package '" + packageName + "' should use ANGLE = '" + useAngle + "'"); + + return useAngle; + } + + /** + * Show the ANGLE in Use Dialog Box + * @param context + */ + public void showAngleInUseDialogBox(Context context) { + final String packageName = context.getPackageName(); + + if (shouldShowAngleInUseDialogBox(context) && shouldUseAngle(context, packageName)) { + final String toastMsg = packageName + " is using ANGLE"; + final Toast toast = Toast.makeText(context, toastMsg, Toast.LENGTH_LONG); + toast.show(); } } @@ -541,53 +614,33 @@ public class GraphicsEnvironment { if (gameDriverAllApps != 1) { // GAME_DRIVER_OPT_OUT_APPS has higher priority than GAME_DRIVER_OPT_IN_APPS - if (getGlobalSettingsString(coreSettings, Settings.Global.GAME_DRIVER_OPT_OUT_APPS) - .contains(packageName)) { + if (getGlobalSettingsString(null, coreSettings, + Settings.Global.GAME_DRIVER_OPT_OUT_APPS).contains(packageName)) { if (DEBUG) { Log.w(TAG, packageName + " opts out from Game Driver."); } return false; } final boolean isOptIn = - getGlobalSettingsString(coreSettings, Settings.Global.GAME_DRIVER_OPT_IN_APPS) - .contains(packageName); - if (!isOptIn - && !getGlobalSettingsString(coreSettings, Settings.Global.GAME_DRIVER_WHITELIST) - .contains(packageName)) { + getGlobalSettingsString(null, coreSettings, + Settings.Global.GAME_DRIVER_OPT_IN_APPS).contains(packageName); + final List<String> whitelist = getGlobalSettingsString(null, coreSettings, + Settings.Global.GAME_DRIVER_WHITELIST); + if (!isOptIn && whitelist.indexOf(GAME_DRIVER_WHITELIST_ALL) != 0 + && !whitelist.contains(packageName)) { if (DEBUG) { Log.w(TAG, packageName + " is not on the whitelist."); } return false; } - if (!isOptIn) { - // 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. - final String base64String = - coreSettings.getString(Settings.Global.GAME_DRIVER_BLACKLIST); - if (base64String != null && !base64String.isEmpty()) { - final Blacklists blacklistsProto = - Blacklists.parseFrom(Base64.decode(base64String, BASE64_FLAGS)); - final List<Blacklist> blacklists = blacklistsProto.getBlacklistsList(); - final long driverVersionCode = driverAppInfo.longVersionCode; - for (Blacklist blacklist : blacklists) { - if (blacklist.getVersionCode() == driverVersionCode) { - for (String pkgName : blacklist.getPackageNamesList()) { - if (pkgName == packageName) { - return false; - } - } - break; - } - } - } - } catch (InvalidProtocolBufferException e) { - if (DEBUG) { - Log.w(TAG, "Can't parse blacklist, skip and continue..."); - } - } + // If the application is not opted-in and check whether it's on the blacklist, + // terminate early if it's on the blacklist and fallback to system driver. + if (!isOptIn + && getGlobalSettingsString(null, coreSettings, + Settings.Global.GAME_DRIVER_BLACKLIST) + .contains(ai.packageName)) { + return false; } } @@ -660,4 +713,5 @@ public class GraphicsEnvironment { long driverVersionCode, String appPackageName); private static native void setAngleInfo(String path, String appPackage, String devOptIn, FileDescriptor rulesFd, long rulesOffset, long rulesLength); + private static native boolean getShouldUseAngle(String packageName); } diff --git a/core/java/android/os/IStatsCompanionService.aidl b/core/java/android/os/IStatsCompanionService.aidl index dde46cd93d5f..0751b964f85e 100644 --- a/core/java/android/os/IStatsCompanionService.aidl +++ b/core/java/android/os/IStatsCompanionService.aidl @@ -69,6 +69,12 @@ interface IStatsCompanionService { oneway void sendDataBroadcast(in IBinder intentSender, long lastReportTimeNs); /** + * Send a broadcast to the specified PendingIntent's as IBinder notifying it that the list + * of active configs has changed. + */ + oneway void sendActiveConfigsChangedBroadcast(in IBinder intentSender, in long[] configIds); + + /** * Requests StatsCompanionService to send a broadcast using the given intentSender * (which should cast to an IIntentSender), along with the other information specified. */ diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl index f1bba1ab2977..6d4c5a034b54 100644 --- a/core/java/android/os/IStatsManager.aidl +++ b/core/java/android/os/IStatsManager.aidl @@ -196,4 +196,25 @@ interface IStatsManager { * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS */ oneway void unregisterPullerCallback(int atomTag, String packageName); + + /** + * The install requires staging. + */ + const int FLAG_REQUIRE_STAGING = 0x01; + + /** + * Rollback is enabled with this install. + */ + const int FLAG_ROLLBACK_ENABLED = 0x02; + + /** + * Requires low latency monitoring. + */ + const int FLAG_REQUIRE_LOW_LATENCY_MONITOR = 0x04; + + /** + * Logs an event for binary push for module updates. + */ + oneway void sendBinaryPushStateChangedAtom(in String trainName, in long trainVersionCode, + in int options, in int state, in long[] experimentId); } diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java index 8492b0cf58e2..3ee54ceebaa9 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -32,6 +32,7 @@ import android.content.pm.PackageManager; import android.provider.Settings; import android.telephony.euicc.EuiccManager; import android.text.TextUtils; +import android.text.format.DateFormat; import android.util.Log; import android.view.Display; import android.view.WindowManager; @@ -762,7 +763,8 @@ public class RecoverySystem { String reasonArg = null; if (!TextUtils.isEmpty(reason)) { - reasonArg = "--reason=" + sanitizeArg(reason); + String timeStamp = DateFormat.format("yyyy-MM-ddTHH:mm:ssZ", System.currentTimeMillis()).toString(); + reasonArg = "--reason=" + sanitizeArg(reason + "," + timeStamp); } final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ; diff --git a/core/java/android/os/connectivity/CellularBatteryStats.java b/core/java/android/os/connectivity/CellularBatteryStats.java index 2593c85cff12..c99ecb32d418 100644 --- a/core/java/android/os/connectivity/CellularBatteryStats.java +++ b/core/java/android/os/connectivity/CellularBatteryStats.java @@ -44,6 +44,7 @@ public final class CellularBatteryStats implements Parcelable { private long[] mTimeInRatMs; private long[] mTimeInRxSignalStrengthLevelMs; private long[] mTxTimeMs; + private long mMonitoredRailChargeConsumedMaMs; public static final Parcelable.Creator<CellularBatteryStats> CREATOR = new Parcelable.Creator<CellularBatteryStats>() { @@ -74,6 +75,7 @@ public final class CellularBatteryStats implements Parcelable { out.writeLongArray(mTimeInRatMs); out.writeLongArray(mTimeInRxSignalStrengthLevelMs); out.writeLongArray(mTxTimeMs); + out.writeLong(mMonitoredRailChargeConsumedMaMs); } public void readFromParcel(Parcel in) { @@ -90,6 +92,7 @@ public final class CellularBatteryStats implements Parcelable { in.readLongArray(mTimeInRatMs); in.readLongArray(mTimeInRxSignalStrengthLevelMs); in.readLongArray(mTxTimeMs); + mMonitoredRailChargeConsumedMaMs = in.readLong(); } public long getLoggingDurationMs() { @@ -144,6 +147,10 @@ public final class CellularBatteryStats implements Parcelable { return mTxTimeMs; } + public long getMonitoredRailChargeConsumedMaMs() { + return mMonitoredRailChargeConsumedMaMs; + } + public void setLoggingDurationMs(long t) { mLoggingDurationMs = t; return; @@ -211,6 +218,11 @@ public final class CellularBatteryStats implements Parcelable { return; } + public void setMonitoredRailChargeConsumedMaMs(long monitoredRailEnergyConsumedMaMs) { + mMonitoredRailChargeConsumedMaMs = monitoredRailEnergyConsumedMaMs; + return; + } + public int describeContents() { return 0; } @@ -237,6 +249,7 @@ public final class CellularBatteryStats implements Parcelable { Arrays.fill(mTimeInRxSignalStrengthLevelMs, 0); mTxTimeMs = new long[ModemActivityInfo.TX_POWER_LEVELS]; Arrays.fill(mTxTimeMs, 0); + mMonitoredRailChargeConsumedMaMs = 0; return; } }
\ No newline at end of file diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 735f4f253594..43c906495cb6 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -1545,7 +1545,7 @@ public class StorageManager { public static boolean hasIsolatedStorage() { // Prefer to use snapshot for current boot when available return SystemProperties.getBoolean(PROP_ISOLATED_STORAGE_SNAPSHOT, - SystemProperties.getBoolean(PROP_ISOLATED_STORAGE, false)); + SystemProperties.getBoolean(PROP_ISOLATED_STORAGE, true)); } /** diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 18e1f5902a31..7eb03007ee6a 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -308,6 +308,28 @@ public final class DeviceConfig { } /** + * Namespace for Rollback. + * + * @hide + */ + @SystemApi + public interface Rollback { + String NAMESPACE = "rollback"; + + String BOOT_NAMESPACE = "rollback_boot"; + + /** + * Timeout in milliseconds for enabling package rollback. + */ + String ENABLE_ROLLBACK_TIMEOUT = "enable_rollback_timeout"; + + /** + * The lifetime duration of rollback packages in millis + */ + String ROLLBACK_LIFETIME_IN_MILLIS = "rollback_lifetime_in_millis"; + } + + /** * Namespace for storage-related features. * * @hide diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 5f1c56016fa7..570834265237 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -853,10 +853,8 @@ public final class DocumentsContract { private static final String PATH_DOCUMENT = "document"; private static final String PATH_CHILDREN = "children"; private static final String PATH_SEARCH = "search"; - // TODO(b/72055774): make private again once ScopedAccessProvider is refactored - /** {@hide} */ @UnsupportedAppUsage - public static final String PATH_TREE = "tree"; + private static final String PATH_TREE = "tree"; private static final String PARAM_QUERY = "query"; private static final String PARAM_MANAGE = "manage"; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 22329baa5109..d925d7ee2676 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1657,6 +1657,30 @@ public final class Settings { public static final String ACTION_STORAGE_VOLUME_ACCESS_SETTINGS = "android.settings.STORAGE_VOLUME_ACCESS_SETTINGS"; + + /** + * Activity Action: Show screen that let user select enable (or disable) Content Capture. + * <p> + * Input: Nothing. + * + * <p> + * Output: {@link android.app.Activity#RESULT_OK} if user enabled Content Capture, + * {@link android.app.Activity#RESULT_CANCELED} if user disabled it, cancelled, or if the caller + * is not the Content Capture service associated with the user. + * + * <p> + * <b>NOTE: </b> Caller should call + * {@link android.view.contentcapture.ContentCaptureManager#isContentCaptureFeatureEnabled()} + * first to check whether the feature is already enabled. + * + * @hide + */ + @SystemApi + @TestApi + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_REQUEST_ENABLE_CONTENT_CAPTURE = + "android.settings.REQUEST_ENABLE_CONTENT_CAPTURE"; + // End of Intent actions for Settings /** @@ -6103,7 +6127,7 @@ public final class Settings { * Indicates which clock face to show on lock screen and AOD while docked. * @hide */ - private static final String DOCKED_CLOCK_FACE = "docked_clock_face"; + public static final String DOCKED_CLOCK_FACE = "docked_clock_face"; /** * Set by the system to track if the user needs to see the call to action for @@ -12318,6 +12342,14 @@ public final class Settings { "angle_whitelist"; /** + * Show the "ANGLE In Use" dialog box to the user when ANGLE is the OpenGL driver. + * The value is a boolean (1 or 0). + * @hide + */ + public static final String GLOBAL_SETTINGS_SHOW_ANGLE_IN_USE_DIALOG_BOX = + "show_angle_in_use_dialog_box"; + + /** * Game Driver global preference for all Apps. * 0 = Default * 1 = All Apps use Game Driver @@ -12347,6 +12379,12 @@ public final class Settings { public static final String GAME_DRIVER_BLACKLIST = "game_driver_blacklist"; /** + * List of blacklists, each blacklist is a blacklist for a specific version of Game Driver. + * @hide + */ + public static final String GAME_DRIVER_BLACKLISTS = "game_driver_blacklists"; + + /** * Apps on the whitelist that are allowed to use Game Driver. * The string is a list of application package names, seperated by comma. * i.e. <apk1>,<apk2>,...,<apkN> @@ -14354,6 +14392,7 @@ public final class Settings { * <pre> * num_buckets (int) * collected_uids (string) + * minimum_total_cpu_usage_millis (int) * </pre> * * @hide diff --git a/core/java/android/service/contentcapture/ContentCaptureEventsRequest.java b/core/java/android/service/contentcapture/ContentCaptureEventsRequest.java index ca6676d87479..bbcf7a9c97b1 100644 --- a/core/java/android/service/contentcapture/ContentCaptureEventsRequest.java +++ b/core/java/android/service/contentcapture/ContentCaptureEventsRequest.java @@ -28,7 +28,8 @@ import java.util.List; /** * Not needed anymore... * - * @deprecated + * @deprecated ContentCaptureService should use + * {@code #onContentCaptureEvent(ContentCaptureSessionId, ContentCaptureEvent)} instead. * * @hide */ diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java index c98f09e13d97..d2f98599e9a6 100644 --- a/core/java/android/service/contentcapture/ContentCaptureService.java +++ b/core/java/android/service/contentcapture/ContentCaptureService.java @@ -339,7 +339,7 @@ public abstract class ContentCaptureService extends Service { } switch (event.getType()) { case ContentCaptureEvent.TYPE_SESSION_STARTED: - final ContentCaptureContext clientContext = event.getClientContext(); + final ContentCaptureContext clientContext = event.getContentCaptureContext(); clientContext.setParentSessionId(event.getParentSessionId()); mSessionUids.put(sessionIdString, uid); onCreateContentCaptureSession(clientContext, sessionId); @@ -383,8 +383,8 @@ public abstract class ContentCaptureService extends Service { } final Integer rightUid = mSessionUids.get(sessionId); if (rightUid == null) { - if (DEBUG) { - Log.d(TAG, "handleIsRightCallerFor(" + event + "): no session for " + sessionId + if (VERBOSE) { + Log.v(TAG, "handleIsRightCallerFor(" + event + "): no session for " + sessionId + ": " + mSessionUids); } // Just ignore, as the session could have been finished already diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java index ffb524d5c2e4..a46d04765e2c 100644 --- a/core/java/android/service/euicc/EuiccService.java +++ b/core/java/android/service/euicc/EuiccService.java @@ -76,6 +76,11 @@ import java.util.concurrent.atomic.AtomicInteger; * filter with the appropriate action, the {@link #CATEGORY_EUICC_UI} category, and a non-zero * priority. * + * <p>Old implementations of EuiccService may support passing in slot IDs equal to + * {@link android.telephony.SubscriptionManager#INVALID_SIM_SLOT_INDEX}, which allows the LPA to + * decide which eUICC to target when there are multiple eUICCs. This behavior is not supported in + * Android Q or later. + * * @hide */ @SystemApi @@ -546,7 +551,7 @@ public abstract class EuiccService extends Service { int resultCode = EuiccService.this.onDownloadSubscription( slotId, subscription, switchAfterDownload, forceDeactivateSim); result = new DownloadSubscriptionResult(resultCode, - 0 /* resolvableErrors */, TelephonyManager.INVALID_CARD_ID); + 0 /* resolvableErrors */, TelephonyManager.UNSUPPORTED_CARD_ID); } try { callback.onComplete(result); diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java index 551bb8ada044..954dc3943019 100644 --- a/core/java/android/service/notification/StatusBarNotification.java +++ b/core/java/android/service/notification/StatusBarNotification.java @@ -372,6 +372,13 @@ public class StatusBarNotification implements Parcelable { /** * @hide */ + public void clearPackageContext() { + mContext = null; + } + + /** + * @hide + */ @UnsupportedAppUsage public Context getPackageContext(Context context) { if (mContext == null) { diff --git a/core/java/android/text/style/DynamicDrawableSpan.java b/core/java/android/text/style/DynamicDrawableSpan.java index be772af529ef..575401458691 100644 --- a/core/java/android/text/style/DynamicDrawableSpan.java +++ b/core/java/android/text/style/DynamicDrawableSpan.java @@ -77,6 +77,13 @@ public abstract class DynamicDrawableSpan extends ReplacementSpan { */ public static final int ALIGN_BASELINE = 1; + /** + * A constant indicating that this span should be vertically centered between + * the top and the lowest descender. + * @hide + */ + public static final int ALIGN_CENTER = 2; + protected final int mVerticalAlignment; @UnsupportedAppUsage @@ -142,6 +149,8 @@ public abstract class DynamicDrawableSpan extends ReplacementSpan { int transY = bottom - b.getBounds().bottom; if (mVerticalAlignment == ALIGN_BASELINE) { transY -= paint.getFontMetricsInt().descent; + } else if (mVerticalAlignment == ALIGN_CENTER) { + transY = (bottom - top) / 2 - b.getBounds().height() / 2; } canvas.translate(x, transY); diff --git a/core/java/android/util/MathUtils.java b/core/java/android/util/MathUtils.java index 56558d04e50d..0eeef70a79a6 100644 --- a/core/java/android/util/MathUtils.java +++ b/core/java/android/util/MathUtils.java @@ -166,6 +166,26 @@ public final class MathUtils { } /** + * Returns the interpolation scalar (s) that satisfies the equation: {@code value = }{@link + * #lerp}{@code (a, b, s)} + * + * <p>If {@code a == b}, then this function will return 0. + */ + public static float lerpInv(float a, float b, float value) { + return a != b ? ((value - a) / (b - a)) : 0.0f; + } + + /** Returns the single argument constrained between [0.0, 1.0]. */ + public static float saturate(float value) { + return constrain(value, 0.0f, 1.0f); + } + + /** Returns the saturated (constrained between [0, 1]) result of {@link #lerpInv}. */ + public static float lerpInvSat(float a, float b, float value) { + return saturate(lerpInv(a, b, value)); + } + + /** * Returns an interpolated angle in degrees between a set of start and end * angles. * <p> @@ -195,6 +215,32 @@ public final class MathUtils { } /** + * Calculates a value in [rangeMin, rangeMax] that maps value in [valueMin, valueMax] to + * returnVal in [rangeMin, rangeMax]. + * <p> + * Always returns a constrained value in the range [rangeMin, rangeMax], even if value is + * outside [valueMin, valueMax]. + * <p> + * Eg: + * constrainedMap(0f, 100f, 0f, 1f, 0.5f) = 50f + * constrainedMap(20f, 200f, 10f, 20f, 20f) = 200f + * constrainedMap(20f, 200f, 10f, 20f, 50f) = 200f + * constrainedMap(10f, 50f, 10f, 20f, 5f) = 10f + * + * @param rangeMin minimum of the range that should be returned. + * @param rangeMax maximum of the range that should be returned. + * @param valueMin minimum of range to map {@code value} to. + * @param valueMax maximum of range to map {@code value} to. + * @param value to map to the range [{@code valueMin}, {@code valueMax}]. Note, can be outside + * this range, resulting in a clamped value. + * @return the mapped value, constrained to [{@code rangeMin}, {@code rangeMax}. + */ + public static float constrainedMap( + float rangeMin, float rangeMax, float valueMin, float valueMax, float value) { + return lerp(rangeMin, rangeMax, lerpInvSat(valueMin, valueMax, value)); + } + + /** * Perform Hermite interpolation between two values. * Eg: * smoothStep(0, 0.5f, 0.5f) = 1f diff --git a/core/java/android/util/StatsLog.java b/core/java/android/util/StatsLog.java index ace4bf477af6..29ced3eda4ec 100644 --- a/core/java/android/util/StatsLog.java +++ b/core/java/android/util/StatsLog.java @@ -16,7 +16,14 @@ package android.util; +import static android.Manifest.permission.DUMP; +import static android.Manifest.permission.PACKAGE_USAGE_STATS; + +import android.Manifest; import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.app.IActivityManager; +import android.content.Context; import android.os.IStatsManager; import android.os.RemoteException; import android.os.ServiceManager; @@ -31,7 +38,10 @@ public final class StatsLog extends StatsLogInternal { private static IStatsManager sService; - private StatsLog() {} + private static Object sLogLock = new Object(); + + private StatsLog() { + } /** * Logs a start event. @@ -40,11 +50,13 @@ public final class StatsLog extends StatsLogInternal { * @return True if the log request was sent to statsd. */ public static boolean logStart(int label) { - synchronized (StatsLog.class) { + synchronized (sLogLock) { try { IStatsManager service = getIStatsManagerLocked(); if (service == null) { - if (DEBUG) Slog.d(TAG, "Failed to find statsd when logging start"); + if (DEBUG) { + Slog.d(TAG, "Failed to find statsd when logging start"); + } return false; } service.sendAppBreadcrumbAtom(label, @@ -52,7 +64,9 @@ public final class StatsLog extends StatsLogInternal { return true; } catch (RemoteException e) { sService = null; - if (DEBUG) Slog.d(TAG, "Failed to connect to statsd when logging start"); + if (DEBUG) { + Slog.d(TAG, "Failed to connect to statsd when logging start"); + } return false; } } @@ -65,18 +79,22 @@ public final class StatsLog extends StatsLogInternal { * @return True if the log request was sent to statsd. */ public static boolean logStop(int label) { - synchronized (StatsLog.class) { + synchronized (sLogLock) { try { IStatsManager service = getIStatsManagerLocked(); if (service == null) { - if (DEBUG) Slog.d(TAG, "Failed to find statsd when logging stop"); + if (DEBUG) { + Slog.d(TAG, "Failed to find statsd when logging stop"); + } return false; } service.sendAppBreadcrumbAtom(label, StatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP); return true; } catch (RemoteException e) { sService = null; - if (DEBUG) Slog.d(TAG, "Failed to connect to statsd when logging stop"); + if (DEBUG) { + Slog.d(TAG, "Failed to connect to statsd when logging stop"); + } return false; } } @@ -89,11 +107,13 @@ public final class StatsLog extends StatsLogInternal { * @return True if the log request was sent to statsd. */ public static boolean logEvent(int label) { - synchronized (StatsLog.class) { + synchronized (sLogLock) { try { IStatsManager service = getIStatsManagerLocked(); if (service == null) { - if (DEBUG) Slog.d(TAG, "Failed to find statsd when logging event"); + if (DEBUG) { + Slog.d(TAG, "Failed to find statsd when logging event"); + } return false; } service.sendAppBreadcrumbAtom( @@ -101,7 +121,51 @@ public final class StatsLog extends StatsLogInternal { return true; } catch (RemoteException e) { sService = null; - if (DEBUG) Slog.d(TAG, "Failed to connect to statsd when logging event"); + if (DEBUG) { + Slog.d(TAG, "Failed to connect to statsd when logging event"); + } + return false; + } + } + } + + /** + * Logs an event for binary push for module updates. + * + * @param trainName name of install train. + * @param trainVersionCode version code of the train. + * @param options optional flags about this install. + * @param state current install state. + * @param experimentIds experiment ids. + * @return True if the log request was sent to statsd. + */ + @RequiresPermission(allOf = {DUMP, PACKAGE_USAGE_STATS}) + public static boolean logBinaryPushStateChanged(@NonNull String trainName, + long trainVersionCode, int options, int state, + @NonNull long[] experimentIds) { + synchronized (sLogLock) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + if (DEBUG) { + Slog.d(TAG, "Failed to find statsd when logging event"); + } + return false; + } + int userId = IActivityManager.Stub.asInterface( + ServiceManager.getService("activity")) + .getCurrentUser() + .id; + service.sendBinaryPushStateChangedAtom( + trainName, trainVersionCode, options, state, experimentIds); + return true; + } catch (RemoteException e) { + sService = null; + if (DEBUG) { + Slog.d(TAG, + "Failed to connect to StatsCompanionService when logging " + + "BinaryPushStateChanged"); + } return false; } } @@ -118,7 +182,7 @@ public final class StatsLog extends StatsLogInternal { /** * Add a log to the stats log. * - * @param id The id of the atom + * @param id The id of the atom * @param params The parameters of the atom's message. */ public static void write(int id, @NonNull Object... params) { @@ -128,4 +192,13 @@ public final class StatsLog extends StatsLogInternal { (boolean) params[4], (int) params[5]); } } + + private static void enforceDumpCallingPermission(Context context) { + context.enforceCallingPermission(android.Manifest.permission.DUMP, "Need DUMP permission."); + } + + private static void enforcesageStatsCallingPermission(Context context) { + context.enforceCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS, + "Need PACKAGE_USAGE_STATS permission."); + } } diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 62ed901c5e06..f37c9162d98a 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -336,7 +336,7 @@ public final class AccessibilityInteractionController { } mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; final View root = findViewByAccessibilityId(accessibilityViewId); - if (root != null) { + if (root != null && isShown(root)) { mPrefetcher.prefetchAccessibilityNodeInfos( root, virtualDescendantId, flags, infos, arguments); } @@ -448,7 +448,7 @@ public final class AccessibilityInteractionController { } mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; final View root = findViewByAccessibilityId(accessibilityViewId); - if (root != null) { + if (root != null && isShown(root)) { AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider(); if (provider != null) { infos = provider.findAccessibilityNodeInfosByText(text, @@ -531,7 +531,7 @@ public final class AccessibilityInteractionController { } mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; final View root = findViewByAccessibilityId(accessibilityViewId); - if (root != null) { + if (root != null && isShown(root)) { switch (focusType) { case AccessibilityNodeInfo.FOCUS_ACCESSIBILITY: { View host = mViewRootImpl.mAccessibilityFocusedHost; @@ -621,7 +621,7 @@ public final class AccessibilityInteractionController { } mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; final View root = findViewByAccessibilityId(accessibilityViewId); - if (root != null) { + if (root != null && isShown(root)) { View nextView = root.focusSearch(direction); if (nextView != null) { next = nextView.createAccessibilityNodeInfo(); @@ -676,7 +676,7 @@ public final class AccessibilityInteractionController { } mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; final View target = findViewByAccessibilityId(accessibilityViewId); - if (target != null) { + if (target != null && isShown(target)) { if (action == R.id.accessibilityActionClickOnClickableSpan) { // Handle this hidden action separately succeeded = handleClickableSpanActionUiThread( @@ -759,9 +759,7 @@ public final class AccessibilityInteractionController { if (accessibilityId == AccessibilityNodeInfo.ROOT_ITEM_ID) { return mViewRootImpl.mView; } else { - final View foundView = - AccessibilityNodeIdManager.getInstance().findView(accessibilityId); - return isShown(foundView) ? foundView : null; + return AccessibilityNodeIdManager.getInstance().findView(accessibilityId); } } diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 49bae280321e..cb5100a4d57a 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -76,7 +76,7 @@ public final class Display { private final int mLayerStack; private final int mFlags; private final int mType; - private final String mAddress; + private final DisplayAddress mAddress; private final int mOwnerUid; private final String mOwnerPackageName; private final Resources mResources; @@ -557,7 +557,7 @@ public final class Display { * @hide */ @UnsupportedAppUsage - public String getAddress() { + public DisplayAddress getAddress() { return mAddress; } diff --git a/core/java/android/view/DisplayAddress.java b/core/java/android/view/DisplayAddress.java new file mode 100644 index 000000000000..17ea4c423be5 --- /dev/null +++ b/core/java/android/view/DisplayAddress.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +/** Display identifier that is stable across reboots. + * + * @hide + */ +public abstract class DisplayAddress implements Parcelable { + /** + * Creates an address for a physical display given its stable ID. + * + * A physical display ID is stable if the display can be identified using EDID information. + * + * @param physicalDisplayId A physical display ID. + * @return The {@link Physical} address, or {@code null} if the ID is not stable. + * @see SurfaceControl#getPhysicalDisplayIds + */ + @Nullable + public static Physical fromPhysicalDisplayId(long physicalDisplayId) { + final Physical address = new Physical(physicalDisplayId); + return address.getModel() == 0 ? null : address; + } + + /** + * Creates an address for a network display given its MAC address. + * + * @param macAddress A MAC address in colon notation. + * @return The {@link Network} address. + */ + @NonNull + public static Network fromMacAddress(String macAddress) { + return new Network(macAddress); + } + + /** + * Address for a physically connected display. + * + * A {@link Physical} address is represented by a 64-bit identifier combining the port and model + * of a display. The port, located in the least significant byte, uniquely identifies a physical + * connector on the device for display output like eDP or HDMI. The model, located in the upper + * bits, uniquely identifies a display model across manufacturers by encoding EDID information. + */ + public static final class Physical extends DisplayAddress { + private static final int PHYSICAL_DISPLAY_ID_MODEL_SHIFT = 8; + private static final int PORT_MASK = 0xFF; + + private final long mPhysicalDisplayId; + + /** + * Physical port to which the display is connected. + */ + public byte getPort() { + return (byte) mPhysicalDisplayId; + } + + /** + * Model identifier unique across manufacturers. + */ + public long getModel() { + return mPhysicalDisplayId >>> PHYSICAL_DISPLAY_ID_MODEL_SHIFT; + } + + @Override + public boolean equals(Object other) { + return other instanceof Physical + && mPhysicalDisplayId == ((Physical) other).mPhysicalDisplayId; + } + + @Override + public String toString() { + return new StringBuilder("{") + .append("port=").append(getPort() & PORT_MASK) + .append(", model=0x").append(Long.toHexString(getModel())) + .append("}") + .toString(); + } + + @Override + public int hashCode() { + return Long.hashCode(mPhysicalDisplayId); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeLong(mPhysicalDisplayId); + } + + private Physical(long physicalDisplayId) { + mPhysicalDisplayId = physicalDisplayId; + } + + public static final Parcelable.Creator<Physical> CREATOR = + new Parcelable.Creator<Physical>() { + @Override + public Physical createFromParcel(Parcel in) { + return new Physical(in.readLong()); + } + + @Override + public Physical[] newArray(int size) { + return new Physical[size]; + } + }; + } + + /** + * Address for a network-connected display. + */ + public static final class Network extends DisplayAddress { + private final String mMacAddress; + + @Override + public boolean equals(Object other) { + return other instanceof Network && mMacAddress.equals(((Network) other).mMacAddress); + } + + @Override + public String toString() { + return mMacAddress; + } + + @Override + public int hashCode() { + return mMacAddress.hashCode(); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeString(mMacAddress); + } + + private Network(String macAddress) { + mMacAddress = macAddress; + } + + public static final Parcelable.Creator<Network> CREATOR = + new Parcelable.Creator<Network>() { + @Override + public Network createFromParcel(Parcel in) { + return new Network(in.readString()); + } + + @Override + public Network[] newArray(int size) { + return new Network[size]; + } + }; + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index ad8fee978c9a..3aa779bc3df6 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -61,7 +61,7 @@ public final class DisplayInfo implements Parcelable { * Display address, or null if none. * Interpretation varies by display type. */ - public String address; + public DisplayAddress address; /** * The human-readable name of the display. @@ -385,7 +385,7 @@ public final class DisplayInfo implements Parcelable { layerStack = source.readInt(); flags = source.readInt(); type = source.readInt(); - address = source.readString(); + address = source.readParcelable(null); name = source.readString(); appWidth = source.readInt(); appHeight = source.readInt(); @@ -432,7 +432,7 @@ public final class DisplayInfo implements Parcelable { dest.writeInt(layerStack); dest.writeInt(this.flags); dest.writeInt(type); - dest.writeString(address); + dest.writeParcelable(address, flags); dest.writeString(name); dest.writeInt(appWidth); dest.writeInt(appHeight); diff --git a/core/java/android/view/GhostView.java b/core/java/android/view/GhostView.java index 74c801b1dc5b..3286bd623694 100644 --- a/core/java/android/view/GhostView.java +++ b/core/java/android/view/GhostView.java @@ -20,6 +20,7 @@ import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.RecordingCanvas; import android.graphics.RenderNode; +import android.os.Build; import android.widget.FrameLayout; import java.util.ArrayList; @@ -135,12 +136,12 @@ public class GhostView extends View { return ghostView; } - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public static GhostView addGhost(View view, ViewGroup viewGroup) { return addGhost(view, viewGroup, null); } - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public static void removeGhost(View view) { GhostView ghostView = view.mGhostView; if (ghostView != null) { diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java index b28ac4714e90..f13cb5a197d8 100644 --- a/core/java/android/view/LayoutInflater.java +++ b/core/java/android/view/LayoutInflater.java @@ -129,7 +129,7 @@ public abstract class LayoutInflater { static final Class<?>[] mConstructorSignature = new Class[] { Context.class, AttributeSet.class}; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769490) private static final HashMap<String, Constructor<? extends View>> sConstructorMap = new HashMap<String, Constructor<? extends View>>(); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 83df33e9742e..2b440dc80cfd 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -24,6 +24,7 @@ import static java.lang.Math.max; import android.animation.AnimatorInflater; import android.animation.StateListAnimator; +import android.annotation.AttrRes; import android.annotation.CallSuper; import android.annotation.ColorInt; import android.annotation.DrawableRes; @@ -5108,7 +5109,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private SparseIntArray mAttributeSourceResId; @Nullable - private int[] mAttributeResolutionStack; + private SparseArray<int[]> mAttributeResolutionStacks; @StyleRes private int mExplicitStyle; @@ -5963,11 +5964,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <b>Note:</b> this method will only return actual values if the view attribute debugging * is enabled in Android developer options. * + * @param attribute Attribute resource ID for which the resolution stack should be returned. * @return ordered list of resource ID that are considered when resolving attribute values for * this {@link View}. */ @NonNull - public List<Integer> getAttributeResolutionStack() { + public List<Integer> getAttributeResolutionStack(@AttrRes int attribute) { ArrayList<Integer> stack = new ArrayList<>(); if (!sDebugViewAttributes) { return stack; @@ -5975,8 +5977,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mSourceLayoutId != ID_NULL) { stack.add(mSourceLayoutId); } - for (int i = 0; i < mAttributeResolutionStack.length; i++) { - stack.add(mAttributeResolutionStack[i]); + int[] attributeResolutionStack = mAttributeResolutionStacks.get(attribute); + if (attributeResolutionStack == null) { + return stack; + } + for (int i = 0; i < attributeResolutionStack.length; i++) { + stack.add(attributeResolutionStack[i]); } return stack; } @@ -6143,9 +6149,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return; } - mAttributeResolutionStack = context.getTheme().getAttributeResolutionStack( + int[] attributeResolutionStack = context.getTheme().getAttributeResolutionStack( defStyleAttr, defStyleRes, mExplicitStyle); + if (mAttributeResolutionStacks == null) { + mAttributeResolutionStacks = new SparseArray<>(); + } + if (mAttributeSourceResId == null) { mAttributeSourceResId = new SparseIntArray(); } @@ -6154,6 +6164,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, for (int j = 0; j < indexCount; ++j) { final int index = t.getIndex(j); mAttributeSourceResId.append(styleable[index], t.getSourceResourceId(index, 0)); + mAttributeResolutionStacks.append(styleable[index], attributeResolutionStack); } } @@ -6456,6 +6467,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, arr.recycle(); } + private void initializeScrollBarDrawable() { + initScrollCache(); + + if (mScrollCache.scrollBar == null) { + mScrollCache.scrollBar = new ScrollBarDrawable(); + mScrollCache.scrollBar.setState(getDrawableState()); + mScrollCache.scrollBar.setCallback(this); + } + } + /** * <p> * Initializes the scrollbars from a given set of styled attributes. This @@ -6541,6 +6562,106 @@ public class View implements Drawable.Callback, KeyEvent.Callback, resolvePadding(); } + /** + * Defines the vertical scrollbar thumb drawable + * @attr ref android.R.styleable#View_scrollbarThumbVertical + * + * @see #awakenScrollBars(int) + * @see #isVerticalScrollBarEnabled() + * @see #setVerticalScrollBarEnabled(boolean) + */ + public void setVerticalScrollbarThumbDrawable(@Nullable Drawable drawable) { + initializeScrollBarDrawable(); + mScrollCache.scrollBar.setVerticalThumbDrawable(drawable); + } + + /** + * Defines the vertical scrollbar track drawable + * @attr ref android.R.styleable#View_scrollbarTrackVertical + * + * @see #awakenScrollBars(int) + * @see #isVerticalScrollBarEnabled() + * @see #setVerticalScrollBarEnabled(boolean) + */ + public void setVerticalScrollbarTrackDrawable(@Nullable Drawable drawable) { + initializeScrollBarDrawable(); + mScrollCache.scrollBar.setVerticalTrackDrawable(drawable); + } + + /** + * Defines the horizontal thumb drawable + * @attr ref android.R.styleable#View_scrollbarThumbHorizontal + * + * @see #awakenScrollBars(int) + * @see #isHorizontalScrollBarEnabled() + * @see #setHorizontalScrollBarEnabled(boolean) + */ + public void setHorizontalScrollbarThumbDrawable(@Nullable Drawable drawable) { + initializeScrollBarDrawable(); + mScrollCache.scrollBar.setHorizontalThumbDrawable(drawable); + } + + /** + * Defines the horizontal track drawable + * @attr ref android.R.styleable#View_scrollbarTrackHorizontal + * + * @see #awakenScrollBars(int) + * @see #isHorizontalScrollBarEnabled() + * @see #setHorizontalScrollBarEnabled(boolean) + */ + public void setHorizontalScrollbarTrackDrawable(@Nullable Drawable drawable) { + initializeScrollBarDrawable(); + mScrollCache.scrollBar.setHorizontalTrackDrawable(drawable); + } + + /** + * Returns the currently configured Drawable for the thumb of the vertical scroll bar if it + * exists, null otherwise. + * + * @see #awakenScrollBars(int) + * @see #isVerticalScrollBarEnabled() + * @see #setVerticalScrollBarEnabled(boolean) + */ + public @Nullable Drawable getVerticalScrollbarThumbDrawable() { + return mScrollCache != null ? mScrollCache.scrollBar.getVerticalThumbDrawable() : null; + } + + /** + * Returns the currently configured Drawable for the track of the vertical scroll bar if it + * exists, null otherwise. + * + * @see #awakenScrollBars(int) + * @see #isVerticalScrollBarEnabled() + * @see #setVerticalScrollBarEnabled(boolean) + */ + public @Nullable Drawable getVerticalScrollbarTrackDrawable() { + return mScrollCache != null ? mScrollCache.scrollBar.getVerticalTrackDrawable() : null; + } + + /** + * Returns the currently configured Drawable for the thumb of the horizontal scroll bar if it + * exists, null otherwise. + * + * @see #awakenScrollBars(int) + * @see #isHorizontalScrollBarEnabled() + * @see #setHorizontalScrollBarEnabled(boolean) + */ + public @Nullable Drawable getHorizontalScrollbarThumbDrawable() { + return mScrollCache != null ? mScrollCache.scrollBar.getHorizontalThumbDrawable() : null; + } + + /** + * Returns the currently configured Drawable for the track of the horizontal scroll bar if it + * exists, null otherwise. + * + * @see #awakenScrollBars(int) + * @see #isHorizontalScrollBarEnabled() + * @see #setHorizontalScrollBarEnabled(boolean) + */ + public @Nullable Drawable getHorizontalScrollbarTrackDrawable() { + return mScrollCache != null ? mScrollCache.scrollBar.getHorizontalTrackDrawable() : null; + } + private void initializeScrollIndicatorsInternal() { // Some day maybe we'll break this into top/left/start/etc. and let the // client control it. Until then, you can have any scroll indicator you @@ -9366,7 +9487,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Gets the session used to notify Content Capture events. * * @return session explicitly set by {@link #setContentCaptureSession(ContentCaptureSession)}, - * inherited by ancestore, default session or {@code null} if content capture is disabled for + * inherited by ancestors, default session or {@code null} if content capture is disabled for * this view. */ @Nullable diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index aaf1d110c8c2..f2474a58b176 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -4301,6 +4301,20 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return i; } + /** + * The public version of getChildDrawingOrder(). + * + * Returns the index of the child to draw for this iteration. + * + * @param i The current iteration. + * @return The index of the child to draw this iteration. + * + * @see #getChildDrawingOrder(int, int)} + */ + public final int getChildDrawingOrder(int i) { + return getChildDrawingOrder(getChildCount(), i); + } + private boolean hasChildWithZ() { for (int i = 0; i < mChildrenCount; i++) { if (mChildren[i].getZ() != 0) return true; diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 06207a9290d7..384cdbb3831c 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -174,7 +174,7 @@ public final class AccessibilityManager { final Handler.Callback mCallback; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) boolean mIsEnabled; int mRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK; diff --git a/core/java/android/view/contentcapture/ChildContentCaptureSession.java b/core/java/android/view/contentcapture/ChildContentCaptureSession.java index acb81e086461..13e8a6584218 100644 --- a/core/java/android/view/contentcapture/ChildContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ChildContentCaptureSession.java @@ -20,10 +20,6 @@ import android.annotation.Nullable; import android.view.autofill.AutofillId; import android.view.contentcapture.ViewNode.ViewStructureImpl; -import com.android.internal.util.Preconditions; - -import java.io.PrintWriter; - /** * A session that is explicitly created by the app (and hence is a descendant of * {@link MainContentCaptureSession}). @@ -35,21 +31,11 @@ final class ChildContentCaptureSession extends ContentCaptureSession { @NonNull private final ContentCaptureSession mParent; - /** - * {@link ContentCaptureContext} set by client, or {@code null} when it's the - * {@link ContentCaptureManager#getMainContentCaptureSession() default session} for the - * context. - * - * @hide - */ - @NonNull - private final ContentCaptureContext mClientContext; - /** @hide */ protected ChildContentCaptureSession(@NonNull ContentCaptureSession parent, @NonNull ContentCaptureContext clientContext) { + super(clientContext); mParent = parent; - mClientContext = Preconditions.checkNotNull(clientContext); } @Override @@ -73,6 +59,11 @@ final class ChildContentCaptureSession extends ContentCaptureSession { } @Override + public void updateContentCaptureContext(@Nullable ContentCaptureContext context) { + getMainCaptureSession().notifyContextUpdated(mId, context); + } + + @Override void onDestroy() { getMainCaptureSession().notifyChildSessionFinished(mParent.mId, mId); } @@ -101,13 +92,4 @@ final class ChildContentCaptureSession extends ContentCaptureSession { boolean isContentCaptureEnabled() { return getMainCaptureSession().isContentCaptureEnabled(); } - - @Override - void dump(String prefix, PrintWriter pw) { - if (mClientContext != null) { - // NOTE: we don't dump clientContent because it could have PII - pw.print(prefix); pw.println("hasClientContext"); - } - super.dump(prefix, pw); - } } diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java index 22254cd94059..9cdbefac3d1d 100644 --- a/core/java/android/view/contentcapture/ContentCaptureEvent.java +++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java @@ -91,13 +91,22 @@ public final class ContentCaptureEvent implements Parcelable { */ public static final int TYPE_INITIAL_VIEW_TREE_APPEARED = 5; + /** + * Called after a call to + * {@link ContentCaptureSession#setContentCaptureContext(ContentCaptureContext)}. + * + * <p>The passed context is available through {@link #getContentCaptureContext()}. + */ + public static final int TYPE_CONTEXT_UPDATED = 6; + /** @hide */ @IntDef(prefix = { "TYPE_" }, value = { TYPE_VIEW_APPEARED, TYPE_VIEW_DISAPPEARED, TYPE_VIEW_TEXT_CHANGED, TYPE_INITIAL_VIEW_TREE_APPEARING, - TYPE_INITIAL_VIEW_TREE_APPEARED + TYPE_INITIAL_VIEW_TREE_APPEARED, + TYPE_CONTEXT_UPDATED }) @Retention(RetentionPolicy.SOURCE) public @interface EventType{} @@ -193,12 +202,13 @@ public final class ContentCaptureEvent implements Parcelable { } /** - * Used by {@link #TYPE_SESSION_STARTED}. + * Gets the {@link ContentCaptureContext} set calls to + * {@link ContentCaptureSession#setContentCaptureContext(ContentCaptureContext)}. * - * @hide + * <p>Only set on {@link #TYPE_CONTEXT_UPDATED} events. */ @Nullable - public ContentCaptureContext getClientContext() { + public ContentCaptureContext getContentCaptureContext() { return mClientContext; } @@ -220,8 +230,8 @@ public final class ContentCaptureEvent implements Parcelable { * Gets the type of the event. * * @return one of {@link #TYPE_VIEW_APPEARED}, {@link #TYPE_VIEW_DISAPPEARED}, - * {@link #TYPE_VIEW_TEXT_CHANGED}, {@link #TYPE_INITIAL_VIEW_TREE_APPEARING}, or - * {@link #TYPE_INITIAL_VIEW_TREE_APPEARED}. + * {@link #TYPE_VIEW_TEXT_CHANGED}, {@link #TYPE_INITIAL_VIEW_TREE_APPEARING}, + * {@link #TYPE_INITIAL_VIEW_TREE_APPEARED}, or {@link #TYPE_CONTEXT_UPDATED}. */ public @EventType int getType() { return mType; @@ -299,6 +309,10 @@ public final class ContentCaptureEvent implements Parcelable { if (mText != null) { pw.print(", text="); pw.println(getSanitizedString(mText)); } + if (mClientContext != null) { + pw.print(", context="); mClientContext.dump(pw); pw.println(); + + } } @Override @@ -325,6 +339,9 @@ public final class ContentCaptureEvent implements Parcelable { if (mText != null) { string.append(", text=").append(getSanitizedString(mText)); } + if (mClientContext != null) { + string.append(", context=").append(mClientContext); + } return string.append(']').toString(); } @@ -345,7 +362,7 @@ public final class ContentCaptureEvent implements Parcelable { if (mType == TYPE_SESSION_STARTED || mType == TYPE_SESSION_FINISHED) { parcel.writeString(mParentSessionId); } - if (mType == TYPE_SESSION_STARTED) { + if (mType == TYPE_SESSION_STARTED || mType == TYPE_CONTEXT_UPDATED) { parcel.writeParcelable(mClientContext, flags); } } @@ -375,7 +392,7 @@ public final class ContentCaptureEvent implements Parcelable { if (type == TYPE_SESSION_STARTED || type == TYPE_SESSION_FINISHED) { event.setParentSessionId(parcel.readString()); } - if (type == TYPE_SESSION_STARTED) { + if (type == TYPE_SESSION_STARTED || type == TYPE_CONTEXT_UPDATED) { event.setClientContext(parcel.readParcelable(null)); } return event; @@ -404,6 +421,8 @@ public final class ContentCaptureEvent implements Parcelable { return "INITIAL_VIEW_HIERARCHY_STARTED"; case TYPE_INITIAL_VIEW_TREE_APPEARED: return "INITIAL_VIEW_HIERARCHY_FINISHED"; + case TYPE_CONTEXT_UPDATED: + return "CONTEXT_UPDATED"; default: return "UKNOWN_TYPE: " + type; } diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index e028961692f9..b8d3fa6f4404 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -166,6 +166,14 @@ public abstract class ContentCaptureSession implements AutoCloseable { private ContentCaptureSessionId mContentCaptureSessionId; /** + * {@link ContentCaptureContext} set by client, or {@code null} when it's the + * {@link ContentCaptureManager#getMainContentCaptureSession() default session} for the + * context. + */ + @Nullable + private ContentCaptureContext mClientContext; + + /** * List of children session. */ @Nullable @@ -183,6 +191,12 @@ public abstract class ContentCaptureSession implements AutoCloseable { mId = Preconditions.checkNotNull(id); } + // Used by ChildCOntentCaptureSession + ContentCaptureSession(@NonNull ContentCaptureContext initialContext) { + this(); + mClientContext = Preconditions.checkNotNull(initialContext); + } + /** @hide */ @NonNull abstract MainContentCaptureSession getMainCaptureSession(); @@ -240,6 +254,30 @@ public abstract class ContentCaptureSession implements AutoCloseable { abstract void flush(@FlushReason int reason); /** + * Sets the {@link ContentCaptureContext} associated with the session. + * + * <p>Typically used to change the context associated with the default session from an activity. + */ + public final void setContentCaptureContext(@Nullable ContentCaptureContext context) { + mClientContext = context; + updateContentCaptureContext(context); + } + + abstract void updateContentCaptureContext(@Nullable ContentCaptureContext context); + + /** + * Gets the {@link ContentCaptureContext} associated with the session. + * + * @return context set on constructor or by + * {@link #setContentCaptureContext(ContentCaptureContext)}, or {@code null} if never + * explicitly set. + */ + @Nullable + public final ContentCaptureContext getContentCaptureContext() { + return mClientContext; + } + + /** * Destroys this session, flushing out all pending notifications to the service. * * <p>Once destroyed, any new notification will be dropped. @@ -424,6 +462,9 @@ public abstract class ContentCaptureSession implements AutoCloseable { @CallSuper void dump(@NonNull String prefix, @NonNull PrintWriter pw) { pw.print(prefix); pw.print("id: "); pw.println(mId); + if (mClientContext != null) { + pw.print(prefix); mClientContext.dump(pw); pw.println(); + } synchronized (mLock) { pw.print(prefix); pw.print("destroyed: "); pw.println(mDestroyed); if (mChildren != null && !mChildren.isEmpty()) { diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index 810c967ce2c8..d949f45ba5e6 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -15,6 +15,7 @@ */ package android.view.contentcapture; +import static android.view.contentcapture.ContentCaptureEvent.TYPE_CONTEXT_UPDATED; import static android.view.contentcapture.ContentCaptureEvent.TYPE_INITIAL_VIEW_TREE_APPEARED; import static android.view.contentcapture.ContentCaptureEvent.TYPE_INITIAL_VIEW_TREE_APPEARING; import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_FINISHED; @@ -269,11 +270,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { private void sendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) { final int eventType = event.getType(); if (VERBOSE) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event); - if (!hasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED) { + if (!hasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED + && eventType != ContentCaptureEvent.TYPE_CONTEXT_UPDATED) { // TODO(b/120494182): comment when this could happen (dialogs?) Log.v(TAG, "handleSendEvent(" + getDebugState() + ", " + ContentCaptureEvent.getTypeAsString(eventType) - + "): session not started yet"); + + "): dropping because session not started yet"); return; } if (mDisabled.get()) { @@ -476,6 +478,11 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } } + @Override + public void updateContentCaptureContext(@Nullable ContentCaptureContext context) { + notifyContextUpdated(mId, context); + } + /** * Resets the buffer and return a {@link ParceledListSlice} with the previous events. */ @@ -613,6 +620,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } } + void notifyContextUpdated(@NonNull String sessionId, + @Nullable ContentCaptureContext context) { + sendEvent(new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED) + .setClientContext(context)); + } + @Override void dump(@NonNull String prefix, @NonNull PrintWriter pw) { pw.print(prefix); pw.print("mContext: "); pw.println(mContext); diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 2171fc52a0ba..3555822b1f1f 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -760,7 +760,7 @@ public class WebView extends AbsoluteLayout * <p> * The {@code encoding} parameter specifies whether the data is base64 or URL * encoded. If the data is base64 encoded, the value of the encoding - * parameter must be 'base64'. HTML can be encoded with {@link + * parameter must be {@code "base64"}. HTML can be encoded with {@link * android.util.Base64#encodeToString(byte[],int)} like so: * <pre> * String unencodedHtml = @@ -768,11 +768,15 @@ public class WebView extends AbsoluteLayout * String encodedHtml = Base64.encodeToString(unencodedHtml.getBytes(), Base64.NO_PADDING); * webView.loadData(encodedHtml, "text/html", "base64"); * </pre> - * <p> + * <p class="note"> * For all other values of {@code encoding} (including {@code null}) it is assumed that the * data uses ASCII encoding for octets inside the range of safe URL characters and use the * standard %xx hex encoding of URLs for octets outside that range. See <a * href="https://tools.ietf.org/html/rfc3986#section-2.2">RFC 3986</a> for more information. + * Applications targeting {@link android.os.Build.VERSION_CODES#Q} or later must either use + * base64 or encode any {@code #} characters in the content as {@code %23}, otherwise they + * will be treated as the end of the content and the remaining text used as a document + * fragment identifier. * <p> * The {@code mimeType} parameter specifies the format of the data. * If WebView can't handle the specified MIME type, it will download the data. @@ -820,7 +824,8 @@ public class WebView extends AbsoluteLayout * <p> * If the base URL uses the data scheme, this method is equivalent to * calling {@link #loadData(String,String,String) loadData()} and the - * historyUrl is ignored, and the data will be treated as part of a data: URL. + * historyUrl is ignored, and the data will be treated as part of a data: URL, + * including the requirement that the content be URL-encoded or base64 encoded. * If the base URL uses any other scheme, then the data will be loaded into * the WebView as a plain string (i.e. not part of a data URL) and any URL-encoded * entities in the string will not be decoded. diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 4b7c393b1b96..f01babe7b894 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -325,13 +325,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te /** * The current position of the selector in the list. */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) int mSelectorPosition = INVALID_POSITION; /** * Defines the selector's location and dimension at drawing time */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) Rect mSelectorRect = new Rect(); /** diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 9c21ba60a034..ded3be4e4ef5 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -6020,9 +6020,6 @@ public class Editor { } updateSelection(event); - if (mTextView.hasSelection() && mEndHandle != null) { - mEndHandle.updateMagnifier(event); - } break; case MotionEvent.ACTION_UP: @@ -6030,9 +6027,6 @@ public class Editor { break; } updateSelection(event); - if (mEndHandle != null) { - mEndHandle.dismissMagnifier(); - } // No longer dragging to select text, let the parent intercept events. mTextView.getParent().requestDisallowInterceptTouchEvent(false); diff --git a/core/java/android/widget/ScrollBarDrawable.java b/core/java/android/widget/ScrollBarDrawable.java index e91f87e7ec3d..1bed32ecf347 100644 --- a/core/java/android/widget/ScrollBarDrawable.java +++ b/core/java/android/widget/ScrollBarDrawable.java @@ -17,12 +17,15 @@ package android.widget; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.os.Build; +import android.view.View; import com.android.internal.widget.ScrollBarUtils; @@ -36,7 +39,7 @@ import com.android.internal.widget.ScrollBarUtils; public class ScrollBarDrawable extends Drawable implements Drawable.Callback { private Drawable mVerticalTrack; private Drawable mHorizontalTrack; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768422) private Drawable mVerticalThumb; private Drawable mHorizontalThumb; @@ -226,7 +229,10 @@ public class ScrollBarDrawable extends Drawable implements Drawable.Callback { } } - @UnsupportedAppUsage + /** + * @see android.view.View#setVerticalThumbDrawable(Drawable) + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public void setVerticalThumbDrawable(Drawable thumb) { if (mVerticalThumb != null) { mVerticalThumb.setCallback(null); @@ -236,6 +242,37 @@ public class ScrollBarDrawable extends Drawable implements Drawable.Callback { mVerticalThumb = thumb; } + /** + * @see View#getVerticalTrackDrawable() + */ + public @Nullable Drawable getVerticalTrackDrawable() { + return mVerticalTrack; + } + + /** + * @see View#getVerticalThumbDrawable() + */ + public @Nullable Drawable getVerticalThumbDrawable() { + return mVerticalThumb; + } + + /** + * @see View#getHorizontalTrackDrawable() + */ + public @Nullable Drawable getHorizontalTrackDrawable() { + return mHorizontalTrack; + } + + /** + * @see View#getHorizontalThumbDrawable() + */ + public @Nullable Drawable getHorizontalThumbDrawable() { + return mHorizontalThumb; + } + + /** + * @see android.view.View#setVerticalTrackDrawable(Drawable) + */ public void setVerticalTrackDrawable(Drawable track) { if (mVerticalTrack != null) { mVerticalTrack.setCallback(null); @@ -245,7 +282,10 @@ public class ScrollBarDrawable extends Drawable implements Drawable.Callback { mVerticalTrack = track; } - @UnsupportedAppUsage + /** + * @see android.view.View#setHorizontalThumbDrawable(Drawable) + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public void setHorizontalThumbDrawable(Drawable thumb) { if (mHorizontalThumb != null) { mHorizontalThumb.setCallback(null); diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 119a015cd5ea..8ebcef5133b6 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -46,6 +46,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; +import android.database.Cursor; import android.database.DataSetObserver; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -69,6 +70,8 @@ import android.os.ResultReceiver; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageManager; +import android.provider.DocumentsContract; +import android.provider.OpenableColumns; import android.service.chooser.ChooserTarget; import android.service.chooser.ChooserTargetService; import android.service.chooser.IChooserTargetResult; @@ -87,7 +90,6 @@ import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.widget.AbsListView; import android.widget.BaseAdapter; -import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; @@ -373,50 +375,6 @@ public class ChooserActivity extends ResolverActivity { super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents, null, false); - Button copyButton = findViewById(R.id.copy_button); - copyButton.setOnClickListener(view -> { - Intent targetIntent = getTargetIntent(); - if (targetIntent == null) { - finish(); - } else { - final String action = targetIntent.getAction(); - - ClipData clipData = null; - if (Intent.ACTION_SEND.equals(action)) { - String extraText = targetIntent.getStringExtra(Intent.EXTRA_TEXT); - Uri extraStream = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM); - - if (extraText != null) { - clipData = ClipData.newPlainText(null, extraText); - } else if (extraStream != null) { - clipData = ClipData.newUri(getContentResolver(), null, extraStream); - } else { - Log.w(TAG, "No data available to copy to clipboard"); - return; - } - } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) { - final ArrayList<Uri> streams = targetIntent.getParcelableArrayListExtra( - Intent.EXTRA_STREAM); - clipData = ClipData.newUri(getContentResolver(), null, streams.get(0)); - for (int i = 1; i < streams.size(); i++) { - clipData.addItem(getContentResolver(), new ClipData.Item(streams.get(i))); - } - } else { - // expected to only be visible with ACTION_SEND or ACTION_SEND_MULTIPLE - // so warn about unexpected action - Log.w(TAG, "Action (" + action + ") not supported for copying to clipboard"); - return; - } - - ClipboardManager clipboardManager = (ClipboardManager) getSystemService( - Context.CLIPBOARD_SERVICE); - clipboardManager.setPrimaryClip(clipData); - Toast.makeText(getApplicationContext(), R.string.copied, Toast.LENGTH_SHORT).show(); - - finish(); - } - }); - mChooserShownTime = System.currentTimeMillis(); final long systemCost = mChooserShownTime - intentReceivedTime; @@ -474,6 +432,10 @@ public class ChooserActivity extends ResolverActivity { return; } + if (mChooserListAdapter == null || mChooserListAdapter.getCount() == 0) { + return; + } + int previewType = findPreferredContentPreview(targetIntent, getContentResolver()); getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_SHARE_WITH_PREVIEW) @@ -481,6 +443,49 @@ public class ChooserActivity extends ResolverActivity { displayContentPreview(previewType, targetIntent); } + private void onCopyButtonClicked(View v) { + Intent targetIntent = getTargetIntent(); + if (targetIntent == null) { + finish(); + } else { + final String action = targetIntent.getAction(); + + ClipData clipData = null; + if (Intent.ACTION_SEND.equals(action)) { + String extraText = targetIntent.getStringExtra(Intent.EXTRA_TEXT); + Uri extraStream = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM); + + if (extraText != null) { + clipData = ClipData.newPlainText(null, extraText); + } else if (extraStream != null) { + clipData = ClipData.newUri(getContentResolver(), null, extraStream); + } else { + Log.w(TAG, "No data available to copy to clipboard"); + return; + } + } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) { + final ArrayList<Uri> streams = targetIntent.getParcelableArrayListExtra( + Intent.EXTRA_STREAM); + clipData = ClipData.newUri(getContentResolver(), null, streams.get(0)); + for (int i = 1; i < streams.size(); i++) { + clipData.addItem(getContentResolver(), new ClipData.Item(streams.get(i))); + } + } else { + // expected to only be visible with ACTION_SEND or ACTION_SEND_MULTIPLE + // so warn about unexpected action + Log.w(TAG, "Action (" + action + ") not supported for copying to clipboard"); + return; + } + + ClipboardManager clipboardManager = (ClipboardManager) getSystemService( + Context.CLIPBOARD_SERVICE); + clipboardManager.setPrimaryClip(clipData); + Toast.makeText(getApplicationContext(), R.string.copied, Toast.LENGTH_SHORT).show(); + + finish(); + } + } + private void displayContentPreview(@ContentPreviewType int previewType, Intent targetIntent) { switch (previewType) { case CONTENT_PREVIEW_TEXT: @@ -501,6 +506,8 @@ public class ChooserActivity extends ResolverActivity { ViewGroup contentPreviewLayout = findViewById(R.id.content_preview_text_area); contentPreviewLayout.setVisibility(View.VISIBLE); + findViewById(R.id.copy_button).setOnClickListener(this::onCopyButtonClicked); + CharSequence sharingText = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT); if (sharingText == null) { findViewById(R.id.content_preview_text_layout).setVisibility(View.GONE); @@ -510,7 +517,7 @@ public class ChooserActivity extends ResolverActivity { } String previewTitle = targetIntent.getStringExtra(Intent.EXTRA_TITLE); - if (previewTitle == null || previewTitle.trim().isEmpty()) { + if (TextUtils.isEmpty(previewTitle)) { findViewById(R.id.content_preview_title_layout).setVisibility(View.GONE); } else { TextView previewTitleView = findViewById(R.id.content_preview_title); @@ -561,6 +568,7 @@ public class ChooserActivity extends ResolverActivity { if (imageUris.size() == 0) { Log.i(TAG, "Attempted to display image preview area with zero" + " available images detected in EXTRA_STREAM list"); + contentPreviewLayout.setVisibility(View.GONE); return; } @@ -580,15 +588,95 @@ public class ChooserActivity extends ResolverActivity { } } + private static class FileInfo { + public final String name; + public final boolean hasThumbnail; + + FileInfo(String name, boolean hasThumbnail) { + this.name = name; + this.hasThumbnail = hasThumbnail; + } + } + + private FileInfo extractFileInfo(Uri uri, ContentResolver resolver) { + String fileName = null; + boolean hasThumbnail = false; + Cursor cursor = resolver.query(uri, null, null, null, null); + if (cursor != null && cursor.getCount() > 0) { + int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + int flagsIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_FLAGS); + + cursor.moveToFirst(); + fileName = cursor.getString(nameIndex); + if (flagsIndex != -1) { + hasThumbnail = (cursor.getInt(flagsIndex) + & DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL) != 0; + } + } + + if (TextUtils.isEmpty(fileName)) { + fileName = uri.getPath(); + int index = fileName.lastIndexOf('/'); + if (index != -1) { + fileName = fileName.substring(index + 1); + } + } + + return new FileInfo(fileName, hasThumbnail); + } + private void displayFileContentPreview(Intent targetIntent) { - // support coming + ViewGroup contentPreviewLayout = findViewById(R.id.content_preview_file_area); + contentPreviewLayout.setVisibility(View.VISIBLE); + + // TODO(b/120417119): Disable file copy until after moving to sysui, + // due to permissions issues + findViewById(R.id.file_copy_button).setVisibility(View.GONE); + + ContentResolver resolver = getContentResolver(); + TextView fileNameView = findViewById(R.id.content_preview_filename); + String action = targetIntent.getAction(); + if (Intent.ACTION_SEND.equals(action)) { + Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM); + + FileInfo fileInfo = extractFileInfo(uri, resolver); + fileNameView.setText(fileInfo.name); + + if (fileInfo.hasThumbnail) { + loadUriIntoView(R.id.content_preview_file_thumbnail, uri); + } else { + ImageView fileIconView = findViewById(R.id.content_preview_file_icon); + fileIconView.setVisibility(View.VISIBLE); + fileIconView.setImageResource(R.drawable.ic_doc_generic); + } + } else { + List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); + if (uris.size() == 0) { + contentPreviewLayout.setVisibility(View.GONE); + Log.i(TAG, + "Appears to be no uris available in EXTRA_STREAM, removing preview area"); + return; + } + + FileInfo fileInfo = extractFileInfo(uris.get(0), resolver); + int remFileCount = uris.size() - 1; + String fileName = getResources().getQuantityString(R.plurals.file_count, + remFileCount, fileInfo.name, remFileCount); + + fileNameView.setText(fileName); + ImageView fileIconView = findViewById(R.id.content_preview_file_icon); + fileIconView.setVisibility(View.VISIBLE); + fileIconView.setImageResource(R.drawable.ic_file_copy); + } } private RoundedRectImageView loadUriIntoView(int imageResourceId, Uri uri) { RoundedRectImageView imageView = findViewById(imageResourceId); - imageView.setVisibility(View.VISIBLE); Bitmap bmp = loadThumbnail(uri, new Size(200, 200)); - imageView.setImageBitmap(bmp); + if (bmp != null) { + imageView.setVisibility(View.VISIBLE); + imageView.setImageBitmap(bmp); + } return imageView; } @@ -1261,9 +1349,8 @@ public class ChooserActivity extends ResolverActivity { } try { - return ImageUtils.decodeSampledBitmapFromStream(getContentResolver(), - uri, size.getWidth(), size.getHeight()); - } catch (IOException | NullPointerException ex) { + return ImageUtils.loadThumbnail(getContentResolver(), uri, size); + } catch (IOException | NullPointerException | SecurityException ex) { Log.w(TAG, "Error loading preview thumbnail for uri: " + uri.toString(), ex); } return null; diff --git a/core/java/com/android/internal/os/KernelCpuThreadReader.java b/core/java/com/android/internal/os/KernelCpuThreadReader.java index b1328e862273..6bbfc2b1277a 100644 --- a/core/java/com/android/internal/os/KernelCpuThreadReader.java +++ b/core/java/com/android/internal/os/KernelCpuThreadReader.java @@ -109,6 +109,12 @@ public class KernelCpuThreadReader { private Predicate<Integer> mUidPredicate; /** + * If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it + * will not be reported + */ + private int mMinimumTotalCpuUsageMillis; + + /** * Where the proc filesystem is mounted */ private final Path mProcPath; @@ -142,10 +148,12 @@ public class KernelCpuThreadReader { public KernelCpuThreadReader( int numBuckets, Predicate<Integer> uidPredicate, + int minimumTotalCpuUsageMillis, Path procPath, Path initialTimeInStatePath, Injector injector) throws IOException { mUidPredicate = uidPredicate; + mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis; mProcPath = procPath; mProcTimeInStateReader = new ProcTimeInStateReader(initialTimeInStatePath); mInjector = injector; @@ -158,11 +166,13 @@ public class KernelCpuThreadReader { * @return the reader, null if an exception was thrown during creation */ @Nullable - public static KernelCpuThreadReader create(int numBuckets, Predicate<Integer> uidPredicate) { + public static KernelCpuThreadReader create( + int numBuckets, Predicate<Integer> uidPredicate, int minimumTotalCpuUsageMillis) { try { return new KernelCpuThreadReader( numBuckets, uidPredicate, + minimumTotalCpuUsageMillis, DEFAULT_PROC_PATH, DEFAULT_INITIAL_TIME_IN_STATE_PATH, new Injector()); @@ -308,6 +318,18 @@ public class KernelCpuThreadReader { } /** + * If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it + * will not be reported + */ + void setMinimumTotalCpuUsageMillis(int minimumTotalCpuUsageMillis) { + if (minimumTotalCpuUsageMillis < 0) { + Slog.w(TAG, "Negative minimumTotalCpuUsageMillis: " + minimumTotalCpuUsageMillis); + return; + } + mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis; + } + + /** * Get the CPU frequencies that correspond to the times reported in * {@link ThreadCpuUsage#usageTimesMillis} */ @@ -346,6 +368,15 @@ public class KernelCpuThreadReader { } int[] cpuUsages = mFrequencyBucketCreator.getBucketedValues(cpuUsagesLong); + // Check if the total CPU usage below the threshold + int totalCpuUsage = 0; + for (int i = 0; i < cpuUsages.length; i++) { + totalCpuUsage += cpuUsages[i]; + } + if (totalCpuUsage < mMinimumTotalCpuUsageMillis) { + return null; + } + return new ThreadCpuUsage(threadId, threadName, cpuUsages); } diff --git a/core/java/com/android/internal/os/KernelCpuThreadReaderSettingsObserver.java b/core/java/com/android/internal/os/KernelCpuThreadReaderSettingsObserver.java index 77f6a17acdc9..718bcb43bd9a 100644 --- a/core/java/com/android/internal/os/KernelCpuThreadReaderSettingsObserver.java +++ b/core/java/com/android/internal/os/KernelCpuThreadReaderSettingsObserver.java @@ -59,6 +59,13 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver { private static final String COLLECTED_UIDS_SETTINGS_KEY = "collected_uids"; private static final String COLLECTED_UIDS_DEFAULT = "1000-1000"; + /** + * Minimum total CPU usage to report + */ + private static final String MINIMUM_TOTAL_CPU_USAGE_MILLIS_SETTINGS_KEY = + "minimum_total_cpu_usage_millis"; + private static final int MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT = 0; + private final Context mContext; @Nullable @@ -87,7 +94,8 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver { mContext = context; mKernelCpuThreadReader = KernelCpuThreadReader.create( NUM_BUCKETS_DEFAULT, - UidPredicate.fromString(COLLECTED_UIDS_DEFAULT)); + UidPredicate.fromString(COLLECTED_UIDS_DEFAULT), + MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT); } @Override @@ -124,6 +132,9 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver { mKernelCpuThreadReader.setNumBuckets( parser.getInt(NUM_BUCKETS_SETTINGS_KEY, NUM_BUCKETS_DEFAULT)); mKernelCpuThreadReader.setUidPredicate(uidPredicate); + mKernelCpuThreadReader.setMinimumTotalCpuUsageMillis(parser.getInt( + MINIMUM_TOTAL_CPU_USAGE_MILLIS_SETTINGS_KEY, + MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT)); } /** diff --git a/core/java/com/android/internal/util/ImageUtils.java b/core/java/com/android/internal/util/ImageUtils.java index 195ae52ce977..274a5136d8e9 100644 --- a/core/java/com/android/internal/util/ImageUtils.java +++ b/core/java/com/android/internal/util/ImageUtils.java @@ -16,20 +16,25 @@ package com.android.internal.util; +import android.content.ContentProviderClient; import android.content.ContentResolver; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; -import android.graphics.BitmapFactory; import android.graphics.Canvas; +import android.graphics.ImageDecoder; +import android.graphics.ImageDecoder.ImageInfo; +import android.graphics.ImageDecoder.Source; import android.graphics.Matrix; import android.graphics.Paint; +import android.graphics.Point; import android.graphics.PorterDuff; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Bundle; +import android.util.Size; import java.io.IOException; -import java.io.InputStream; /** * Utility class for image analysis and processing. @@ -166,21 +171,18 @@ public class ImageUtils { /** * @see https://developer.android.com/topic/performance/graphics/load-bitmap */ - public static int calculateInSampleSize(BitmapFactory.Options options, - int reqWidth, int reqHeight) { - // Raw height and width of image - final int height = options.outHeight; - final int width = options.outWidth; + public static int calculateSampleSize(Size currentSize, Size requestedSize) { int inSampleSize = 1; - if (height > reqHeight || width > reqWidth) { - final int halfHeight = height / 2; - final int halfWidth = width / 2; + if (currentSize.getHeight() > requestedSize.getHeight() + || currentSize.getWidth() > requestedSize.getWidth()) { + final int halfHeight = currentSize.getHeight() / 2; + final int halfWidth = currentSize.getWidth() / 2; // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. - while ((halfHeight / inSampleSize) >= reqHeight - && (halfWidth / inSampleSize) >= reqWidth) { + while ((halfHeight / inSampleSize) >= requestedSize.getHeight() + && (halfWidth / inSampleSize) >= requestedSize.getWidth()) { inSampleSize *= 2; } } @@ -190,27 +192,27 @@ public class ImageUtils { /** * Load a bitmap, and attempt to downscale to the required size, to save - * on memory. + * on memory. Updated to use newer and more compatible ImageDecoder. * * @see https://developer.android.com/topic/performance/graphics/load-bitmap */ - public static Bitmap decodeSampledBitmapFromStream(ContentResolver resolver, - Uri uri, int reqWidth, int reqHeight) throws IOException { - - final BitmapFactory.Options options = new BitmapFactory.Options(); - try (InputStream is = resolver.openInputStream(uri)) { - // First decode with inJustDecodeBounds=true to check dimensions - options.inJustDecodeBounds = true; - BitmapFactory.decodeStream(is, null, options); - - options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); - } - - // need to do this twice as the InputStream is consumed in the first call, - // and not all InputStreams support marks - try (InputStream is = resolver.openInputStream(uri)) { - options.inJustDecodeBounds = false; - return BitmapFactory.decodeStream(is, null, options); + public static Bitmap loadThumbnail(ContentResolver resolver, Uri uri, Size size) + throws IOException { + + try (ContentProviderClient client = resolver.acquireContentProviderClient(uri)) { + final Bundle opts = new Bundle(); + opts.putParcelable(ContentResolver.EXTRA_SIZE, Point.convert(size)); + + return ImageDecoder.decodeBitmap(ImageDecoder.createSource(() -> { + return client.openTypedAssetFile(uri, "image/*", opts, null); + }), (ImageDecoder decoder, ImageInfo info, Source source) -> { + decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); + + final int sample = calculateSampleSize(info.getSize(), size); + if (sample > 1) { + decoder.setTargetSampleSize(sample); + } + }); } } } diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 345058bd4f6f..af0b7c307ef6 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -287,8 +287,8 @@ cc_library_shared { "libsoundtrigger", "libminikin", "libprocessgroup", - "libnativebridge", - "libnativeloader", + "libnativebridge_lazy", + "libnativeloader_lazy", "libmemunreachable", "libhidlbase", "libhidltransport", diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp index e2e66ceb6fbe..95f99b760382 100644 --- a/core/jni/android_os_GraphicsEnvironment.cpp +++ b/core/jni/android_os_GraphicsEnvironment.cpp @@ -55,6 +55,11 @@ void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jstring appNa devOptInChars.c_str(), rulesFd_native, rulesOffset, rulesLength); } +bool shouldUseAngle_native(JNIEnv* env, jobject clazz, jstring appName) { + ScopedUtfChars appNameChars(env, appName); + return android::GraphicsEnv::getInstance().shouldUseAngle(appNameChars.c_str()); +} + void setLayerPaths_native(JNIEnv* env, jobject clazz, jobject classLoader, jstring layerPaths) { android::NativeLoaderNamespace* appNamespace = android::FindNativeLoaderNamespaceByClassLoader( env, classLoader); @@ -81,6 +86,7 @@ const JNINativeMethod g_methods[] = { { "setDriverPath", "(Ljava/lang/String;)V", reinterpret_cast<void*>(setDriverPath) }, { "setGpuStats", "(Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;)V", reinterpret_cast<void*>(setGpuStats_native) }, { "setAngleInfo", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/io/FileDescriptor;JJ)V", reinterpret_cast<void*>(setAngleInfo_native) }, + { "getShouldUseAngle", "(Ljava/lang/String;)Z", reinterpret_cast<void*>(shouldUseAngle_native) }, { "setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V", reinterpret_cast<void*>(setLayerPaths_native) }, { "setDebugLayers", "(Ljava/lang/String;)V", reinterpret_cast<void*>(setDebugLayers_native) }, { "setDebugLayersGLES", "(Ljava/lang/String;)V", reinterpret_cast<void*>(setDebugLayersGLES_native) }, diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index d04db92294d7..5cecf66a593c 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -742,7 +742,7 @@ static void MountEmulatedStorage(uid_t uid, jint mount_mode, return; } - if (GetBoolProperty(kIsolatedStorageSnapshot, GetBoolProperty(kIsolatedStorage, false))) { + if (GetBoolProperty(kIsolatedStorageSnapshot, GetBoolProperty(kIsolatedStorage, true))) { if (mount_mode == MOUNT_EXTERNAL_FULL || mount_mode == MOUNT_EXTERNAL_LEGACY) { storageSource = (mount_mode == MOUNT_EXTERNAL_FULL) ? "/mnt/runtime/full" : "/mnt/runtime/write"; diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto index 30b9b7847fb6..5497b8665cf0 100644 --- a/core/proto/android/app/settings_enums.proto +++ b/core/proto/android/app/settings_enums.proto @@ -610,6 +610,11 @@ enum Action { // CATEGORY: SETTINGS // OS: Q ACTION_SET_NEW_PARENT_PROFILE_PASSWORD = 1646; + + // ACTION: An interaction with a Slice or other component in the Panel. + // CATEGORY: SETTINGS + // OS: Q + ACTION_PANEL_INTERACTION = 1658; } /** @@ -2214,4 +2219,16 @@ enum PageId { // CATEGORY: SETTINGS // OS: Q SET_NEW_PASSWORD_ACTIVITY = 1644; + + // Panel for Internet Connectivity + PANEL_INTERNET_CONNECTIVITY = 1654; + + // Panel for Volume + PANEL_VOLUME = 1655; + + // Panel for NFC + PANEL_NFC = 1656; + + // Panel for Media Output + PANEL_MEDIA_OUTPUT = 1657; } diff --git a/core/proto/android/hardware/sensor/assist/enums.proto b/core/proto/android/hardware/sensor/assist/enums.proto new file mode 100644 index 000000000000..8c5841a32d54 --- /dev/null +++ b/core/proto/android/hardware/sensor/assist/enums.proto @@ -0,0 +1,34 @@ +/* + * 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.hardware.sensor.assist; + +option java_outer_classname = "AssistGestureProtoEnums"; +option java_multiple_files = true; + +enum AssistGestureStageEnum { + ASSIST_GESTURE_STAGE_UNKNOWN = 0; + ASSIST_GESTURE_STAGE_PROGRESS = 1; + ASSIST_GESTURE_STAGE_PRIMED = 2; + ASSIST_GESTURE_STAGE_DETECTED = 3; +} + +enum AssistGestureFeedbackEnum { + ASSIST_GESTURE_FEEDBACK_UNKNOWN = 0; + ASSIST_GESTURE_FEEDBACK_NOT_USED = 1; + ASSIST_GESTURE_FEEDBACK_USED = 2; +}
\ No newline at end of file diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto index d5776534bb90..c9957f369473 100644 --- a/core/proto/android/providers/settings/global.proto +++ b/core/proto/android/providers/settings/global.proto @@ -448,6 +448,11 @@ message GlobalSettingsProto { optional SettingProto game_driver_whitelist = 12; // ANGLE - List of Apps that can check ANGLE rules optional SettingProto angle_whitelist = 13; + // Game Driver - List of blacklists, each blacklist is a blacklist for + // a specific Game Driver version + optional SettingProto game_driver_blacklists = 14; + // ANGLE - Show a dialog box when ANGLE is selected for the currently running PKG + optional SettingProto show_angle_in_use_dialog = 15; } optional Gpu gpu = 59; diff --git a/core/proto/android/server/connectivity/Android.bp b/core/proto/android/server/connectivity/Android.bp new file mode 100644 index 000000000000..c0ac2cb8f800 --- /dev/null +++ b/core/proto/android/server/connectivity/Android.bp @@ -0,0 +1,25 @@ +// 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. + +java_library_static { + name: "datastallprotosnano", + proto: { + type: "nano", + }, + srcs: [ + "data_stall_event.proto", + ], + sdk_version: "system_current", + no_framework_libs: true, +}
\ No newline at end of file diff --git a/core/res/res/drawable/bottomsheet_background.xml b/core/res/res/drawable/bottomsheet_background.xml index bc32ba6e3896..3a8ad71187a0 100644 --- a/core/res/res/drawable/bottomsheet_background.xml +++ b/core/res/res/drawable/bottomsheet_background.xml @@ -16,7 +16,7 @@ <shape android:shape="rectangle" xmlns:android="http://schemas.android.com/apk/res/android"> <corners - android:topLeftRadius="?attr/dialogCornerRadius" - android:topRightRadius="?attr/dialogCornerRadius" /> + android:topLeftRadius="@dimen/config_bottomDialogCornerRadius" + android:topRightRadius="@dimen/config_bottomDialogCornerRadius"/> <solid android:color="?attr/colorBackgroundFloating" /> </shape> diff --git a/core/res/res/drawable/ic_file_copy.xml b/core/res/res/drawable/ic_file_copy.xml new file mode 100644 index 000000000000..b6d5e7328c40 --- /dev/null +++ b/core/res/res/drawable/ic_file_copy.xml @@ -0,0 +1,24 @@ +<!-- + 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM15,5l6,6v10c0,1.1 -0.9,2 -2,2L7.99,23C6.89,23 6,22.1 6,21l0.01,-14c0,-1.1 0.89,-2 1.99,-2h7zM14,12h5.5L14,6.5L14,12z" + android:fillColor="#FF737373"/> +</vector> diff --git a/core/res/res/drawable/ic_qs_night_display_on.xml b/core/res/res/drawable/ic_qs_night_display_on.xml index 35907cc83fe0..a4755ee256e2 100644 --- a/core/res/res/drawable/ic_qs_night_display_on.xml +++ b/core/res/res/drawable/ic_qs_night_display_on.xml @@ -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. @@ -14,14 +14,13 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="64dp" - android:height="64dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - <group - android:translateX="-1.0"> - <path - android:pathData="M13,12c0,-3.57 2.2,-6.62 5.31,-7.87 0.89,-0.36 0.75,-1.69 -0.19,-1.9 -1.1,-0.24 -2.27,-0.3 -3.48,-0.14 -4.51,0.6 -8.12,4.31 -8.59,8.83C5.43,16.93 10.12,22 16,22c0.73,0 1.43,-0.08 2.12,-0.23 0.95,-0.21 1.1,-1.53 0.2,-1.9A8.471,8.471 0,0 1,13 12z" - android:fillColor="#FFF"/> - </group> -</vector>
\ No newline at end of file + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="#FFFFFF" + android:pathData="M6.28,4.81c0,0.16,0.01,0.32,0.02,0.48c0.38,6.58,5.83,12.03,12.41,12.41c0.16,0.01,0.32,0.02,0.47,0.02 c-1.58,1.2-3.53,1.88-5.56,1.88c-0.46,0-0.93-0.03-1.4-0.1c-3.96-0.58-7.13-3.75-7.71-7.71C4.13,9.24,4.8,6.75,6.28,4.81 M8.27,0.6 c-0.08,0-0.17,0.02-0.25,0.07c-3.8,2.2-6.2,6.56-5.49,11.4c0.7,4.82,4.59,8.7,9.4,9.4c0.57,0.08,1.13,0.12,1.69,0.12 c4.15,0,7.78-2.26,9.72-5.62c0.2-0.35-0.07-0.76-0.44-0.76c-0.05,0-0.1,0.01-0.15,0.02c-1.03,0.31-2.12,0.48-3.25,0.48 c-0.22,0-0.44-0.01-0.67-0.02C13.23,15.38,8.62,10.77,8.29,5.17C8.21,3.81,8.38,2.49,8.75,1.26C8.86,0.91,8.59,0.6,8.27,0.6 L8.27,0.6z" /> +</vector> + diff --git a/core/res/res/layout/chooser_grid.xml b/core/res/res/layout/chooser_grid.xml index 14a5310a4ff2..3683bfd02e87 100644 --- a/core/res/res/layout/chooser_grid.xml +++ b/core/res/res/layout/chooser_grid.xml @@ -213,6 +213,65 @@ </LinearLayout> </LinearLayout> + <!-- Layout Option 3: File preview, icon, filename, copy--> + <LinearLayout + android:id="@+id/content_preview_file_area" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingBottom="@dimen/chooser_view_spacing" + android:visibility="gone" + android:background="?attr/colorBackgroundFloating"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:paddingLeft="@dimen/chooser_edge_margin_normal" + android:paddingRight="@dimen/chooser_edge_margin_normal" + android:layout_marginBottom="@dimen/chooser_view_spacing" + android:id="@+id/content_preview_file_layout"> + + <view class="com.android.internal.app.ChooserActivity$RoundedRectImageView" + android:id="@+id/content_preview_file_thumbnail" + android:layout_width="75dp" + android:layout_height="75dp" + android:layout_marginRight="16dp" + android:adjustViewBounds="true" + android:layout_gravity="center_vertical" + android:gravity="center" + android:scaleType="centerCrop" + android:visibility="gone"/> + <ImageView + android:id="@+id/content_preview_file_icon" + android:layout_width="36dp" + android:layout_height="36dp" + android:layout_marginRight="16dp" + android:adjustViewBounds="true" + android:layout_gravity="center_vertical" + android:gravity="center" + android:scaleType="fitCenter" + android:visibility="gone"/> + <TextView + android:id="@+id/content_preview_filename" + android:layout_width="0dp" + android:layout_weight="1" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:ellipsize="middle" + android:gravity="start|top" + android:paddingRight="24dp" + android:singleLine="true"/> + <Button + android:id="@+id/file_copy_button" + android:layout_width="24dp" + android:layout_height="24dp" + android:gravity="center" + android:layout_gravity="center_vertical" + android:background="@drawable/ic_content_copy_gm2"/> + </LinearLayout> + </LinearLayout> + <ListView android:layout_width="match_parent" android:layout_height="match_parent" diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml index 64d91ad867f9..13fef67c5e2f 100644 --- a/core/res/res/layout/notification_template_material_media.xml +++ b/core/res/res/layout/notification_template_material_media.xml @@ -66,7 +66,7 @@ android:id="@+id/media_actions" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="bottom|end" + android:layout_gravity="top|end" android:layout_marginStart="10dp" android:layoutDirection="ltr" android:orientation="horizontal" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index d14164f69850..dae2692c7722 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3252,7 +3252,7 @@ skinny aspect ratio that is not expected to be widely used. --> <item name="config_pictureInPictureMinAspectRatio" format="float" type="dimen">0.41841004184</item> - <!-- The minimum aspect ratio (width/height) that is supported for picture-in-picture. Any + <!-- The maximum aspect ratio (width/height) that is supported for picture-in-picture. Any ratio larger than this is considered to wide and short to be usable. Currently 2.39:1. --> <item name="config_pictureInPictureMaxAspectRatio" format="float" type="dimen">2.39</item> @@ -3273,6 +3273,11 @@ --> <integer name="config_dockedStackDividerSnapMode">0</integer> + <!-- The maximum aspect ratio (longerSide/shorterSide) that is treated as close-to-square. If + config_forceDefaultOrientation is set to true, the rotation on a close-to-square display + will be fixed. --> + <item name="config_closeToSquareDisplayMaxAspectRatio" format="float" type="dimen">1.333</item> + <!-- List of comma separated package names for which we the system will not show crash, ANR, etc. dialogs. --> <string translatable="false" name="config_appsNotReportingCrashes"></string> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 62e79da1ff6a..0cdf38849d8b 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5284,4 +5284,9 @@ <!-- Strings for car --> <!-- String displayed when loading a user in the car [CHAR LIMIT=30] --> <string name="car_loading_profile">Loading</string> + + <plurals name="file_count"> + <item quantity="one"><xliff:g id="file_name">%s</xliff:g> + <xliff:g id="count">%d</xliff:g> file</item> + <item quantity="other"><xliff:g id="file_name">%s</xliff:g> + <xliff:g id="count">%d</xliff:g> files</item> + </plurals> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 5a7d71f17139..01abffeed384 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -50,6 +50,11 @@ <java-symbol type="id" name="characterPicker" /> <java-symbol type="id" name="clearDefaultHint" /> <java-symbol type="id" name="contentPanel" /> + <java-symbol type="id" name="content_preview_file_area" /> + <java-symbol type="id" name="content_preview_file_icon" /> + <java-symbol type="id" name="content_preview_file_layout" /> + <java-symbol type="id" name="content_preview_file_thumbnail" /> + <java-symbol type="id" name="content_preview_filename" /> <java-symbol type="id" name="content_preview_image_area" /> <java-symbol type="id" name="content_preview_image_1_large" /> <java-symbol type="id" name="content_preview_image_2_large" /> @@ -62,6 +67,7 @@ <java-symbol type="id" name="content_preview_title" /> <java-symbol type="id" name="content_preview_title_layout" /> <java-symbol type="id" name="copy_button" /> + <java-symbol type="id" name="file_copy_button" /> <java-symbol type="id" name="current_scene" /> <java-symbol type="id" name="scene_layoutid_cache" /> <java-symbol type="id" name="customPanel" /> @@ -397,6 +403,7 @@ <java-symbol type="integer" name="config_defaultPictureInPictureGravity" /> <java-symbol type="dimen" name="config_pictureInPictureMinAspectRatio" /> <java-symbol type="dimen" name="config_pictureInPictureMaxAspectRatio" /> + <java-symbol type="dimen" name="config_closeToSquareDisplayMaxAspectRatio" /> <java-symbol type="integer" name="config_wifi_framework_5GHz_preference_boost_threshold" /> <java-symbol type="integer" name="config_wifi_framework_5GHz_preference_boost_factor" /> <java-symbol type="integer" name="config_wifi_framework_5GHz_preference_penalty_threshold" /> @@ -1266,6 +1273,7 @@ <java-symbol type="string" name="tooltip_popup_title" /> <java-symbol type="plurals" name="bugreport_countdown" /> + <java-symbol type="plurals" name="file_count" /> <java-symbol type="plurals" name="last_num_days" /> <java-symbol type="plurals" name="matches_found" /> <java-symbol type="plurals" name="restr_pin_countdown" /> @@ -1307,6 +1315,7 @@ <java-symbol type="drawable" name="default_wallpaper" /> <java-symbol type="drawable" name="default_lock_wallpaper" /> <java-symbol type="drawable" name="indicator_input_error" /> + <java-symbol type="drawable" name="ic_file_copy" /> <java-symbol type="drawable" name="popup_bottom_dark" /> <java-symbol type="drawable" name="popup_bottom_bright" /> <java-symbol type="drawable" name="popup_bottom_medium" /> diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 206682a1955b..c57b609023ca 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -491,8 +491,10 @@ public class SettingsBackupTest { Settings.Global.GAME_DRIVER_ALL_APPS, Settings.Global.GAME_DRIVER_OPT_IN_APPS, Settings.Global.GAME_DRIVER_OPT_OUT_APPS, + Settings.Global.GAME_DRIVER_BLACKLISTS, Settings.Global.GAME_DRIVER_BLACKLIST, Settings.Global.GAME_DRIVER_WHITELIST, + Settings.Global.GLOBAL_SETTINGS_SHOW_ANGLE_IN_USE_DIALOG_BOX, Settings.Global.GPU_DEBUG_LAYER_APP, Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING, Settings.Global.INSTALL_CARRIER_APP_NOTIFICATION_PERSISTENT, @@ -619,6 +621,7 @@ public class SettingsBackupTest { Settings.Secure.DISABLED_PRINT_SERVICES, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS, Settings.Secure.DISPLAY_DENSITY_FORCED, + Settings.Secure.DOCKED_CLOCK_FACE, Settings.Secure.DOZE_PULSE_ON_LONG_PRESS, Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION, Settings.Secure.ENABLED_INPUT_METHODS, // Intentionally removed in P diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureEventTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureEventTest.java index f325d8943ea3..a97c3fa362eb 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureEventTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureEventTest.java @@ -15,6 +15,7 @@ */ package android.view.contentcapture; +import static android.view.contentcapture.ContentCaptureEvent.TYPE_CONTEXT_UPDATED; import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_FINISHED; import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED; import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED; @@ -174,7 +175,8 @@ public class ContentCaptureEventTest { assertThat(event.getIds()).isNull(); assertThat(event.getText()).isNull(); assertThat(event.getViewNode()).isNull(); - final ContentCaptureContext clientContext = event.getClientContext(); + final ContentCaptureContext clientContext = event.getContentCaptureContext(); + assertThat(clientContext).isNotNull(); assertThat(clientContext.getAction()).isEqualTo("WHATEVER"); } @@ -205,9 +207,44 @@ public class ContentCaptureEventTest { assertThat(event.getIds()).isNull(); assertThat(event.getText()).isNull(); assertThat(event.getViewNode()).isNull(); - assertThat(event.getClientContext()).isNull(); + assertThat(event.getContentCaptureContext()).isNull(); } + + @Test + public void testContextUpdated_directly() { + final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_CONTEXT_UPDATED) + .setClientContext(mClientContext); + assertThat(event).isNotNull(); + assertContextUpdatedEvent(event); + } + + @Test + public void testContextUpdated_throughParcel() { + final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_CONTEXT_UPDATED) + .setClientContext(mClientContext); + assertThat(event).isNotNull(); + final ContentCaptureEvent clone = cloneThroughParcel(event); + assertContextUpdatedEvent(clone); + } + + private void assertContextUpdatedEvent(ContentCaptureEvent event) { + assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_UPDATED); + assertThat(event.getEventTime()).isAtLeast(MY_EPOCH); + assertThat(event.getSessionId()).isEqualTo("42"); + assertThat(event.getParentSessionId()).isNull(); + assertThat(event.getId()).isNull(); + assertThat(event.getIds()).isNull(); + assertThat(event.getText()).isNull(); + assertThat(event.getViewNode()).isNull(); + final ContentCaptureContext clientContext = event.getContentCaptureContext(); + assertThat(clientContext).isNotNull(); + assertThat(clientContext.getAction()).isEqualTo("WHATEVER"); + } + + // TODO(b/123036895): add test for all events type (right now we're just testing the 3 types + // that use logic to write to parcel + private ContentCaptureEvent cloneThroughParcel(ContentCaptureEvent event) { Parcel parcel = Parcel.obtain(); diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java index 34fdebfdf348..b6717e16256f 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java @@ -160,5 +160,10 @@ public class ContentCaptureSessionTest { public void internalNotifyViewHierarchyEvent(boolean started) { throw new UnsupportedOperationException("should not have been called"); } + + @Override + public void updateContentCaptureContext(ContentCaptureContext context) { + throw new UnsupportedOperationException("should not have been called"); + } } } diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java index 3d59835a6719..7f104b1b0a14 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -409,8 +409,7 @@ public class ChooserActivityTest { @Test public void copyTextToClipboard() throws Exception { Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(1); + List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); when(ChooserWrapperActivity.sOverrides.resolverListController.getResolversForIntent( Mockito.anyBoolean(), @@ -421,6 +420,7 @@ public class ChooserActivityTest { .launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); + onView(withId(R.id.copy_button)).check(matches(isDisplayed())); onView(withId(R.id.copy_button)).perform(click()); ClipboardManager clipboard = (ClipboardManager) activity.getSystemService( Context.CLIPBOARD_SERVICE); @@ -439,8 +439,9 @@ public class ChooserActivityTest { ArrayList<Uri> uris = new ArrayList<>(); uris.add(uri); - Intent sendIntent = createSendImageIntentWithPreview(uris); + Intent sendIntent = createSendUriIntentWithPreview(uris); sOverrides.previewThumbnail = createBitmap(); + sOverrides.isImageType = true; List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); @@ -464,8 +465,9 @@ public class ChooserActivityTest { uris.add(uri); uris.add(uri); - Intent sendIntent = createSendImageIntentWithPreview(uris); + Intent sendIntent = createSendUriIntentWithPreview(uris); sOverrides.previewThumbnail = createBitmap(); + sOverrides.isImageType = true; List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); @@ -492,8 +494,9 @@ public class ChooserActivityTest { uris.add(uri); uris.add(uri); - Intent sendIntent = createSendImageIntentWithPreview(uris); + Intent sendIntent = createSendUriIntentWithPreview(uris); sOverrides.previewThumbnail = createBitmap(); + sOverrides.isImageType = true; List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); @@ -538,12 +541,11 @@ public class ChooserActivityTest { ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class); mActivityRule.launchActivity(Intent.createChooser(sendIntent, "empty preview logger test")); waitForIdle(); - verify(mockLogger, Mockito.times(2)).write(logMakerCaptor.capture()); + + verify(mockLogger, Mockito.times(1)).write(logMakerCaptor.capture()); // First invocation is from onCreate - assertThat(logMakerCaptor.getAllValues().get(1).getCategory(), - is(MetricsProto.MetricsEvent.ACTION_SHARE_WITH_PREVIEW)); - assertThat(logMakerCaptor.getAllValues().get(1).getSubtype(), - is(CONTENT_PREVIEW_TEXT)); + assertThat(logMakerCaptor.getAllValues().get(0).getCategory(), + is(MetricsProto.MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN)); } @Test @@ -552,9 +554,17 @@ public class ChooserActivityTest { MetricsLogger mockLogger = sOverrides.metricsLogger; ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class); + + List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); + + when(ChooserWrapperActivity.sOverrides.resolverListController.getResolversForIntent( + Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); + mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); - verify(mockLogger, Mockito.times(2)).write(logMakerCaptor.capture()); + verify(mockLogger, Mockito.times(3)).write(logMakerCaptor.capture()); // First invocation is from onCreate assertThat(logMakerCaptor.getAllValues().get(1).getCategory(), is(MetricsProto.MetricsEvent.ACTION_SHARE_WITH_PREVIEW)); @@ -570,8 +580,9 @@ public class ChooserActivityTest { ArrayList<Uri> uris = new ArrayList<>(); uris.add(uri); - Intent sendIntent = createSendImageIntentWithPreview(uris); + Intent sendIntent = createSendUriIntentWithPreview(uris); sOverrides.previewThumbnail = createBitmap(); + sOverrides.isImageType = true; List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); @@ -595,6 +606,51 @@ public class ChooserActivityTest { is(CONTENT_PREVIEW_IMAGE)); } + @Test + public void oneVisibleFilePreview() throws InterruptedException { + Uri uri = Uri.parse("content://com.android.frameworks.coretests/app.pdf"); + + ArrayList<Uri> uris = new ArrayList<>(); + uris.add(uri); + + Intent sendIntent = createSendUriIntentWithPreview(uris); + + List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); + + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); + mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); + waitForIdle(); + onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); + onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf"))); + onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); + } + + + @Test + public void moreThanOneVisibleFilePreview() throws InterruptedException { + Uri uri = Uri.parse("content://com.android.frameworks.coretests/app.pdf"); + + ArrayList<Uri> uris = new ArrayList<>(); + uris.add(uri); + uris.add(uri); + uris.add(uri); + + Intent sendIntent = createSendUriIntentWithPreview(uris); + + List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); + + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); + mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); + waitForIdle(); + onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); + onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf + 2 files"))); + onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); + } + private Intent createSendTextIntent() { Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); @@ -615,7 +671,7 @@ public class ChooserActivityTest { return sendIntent; } - private Intent createSendImageIntentWithPreview(ArrayList<Uri> uris) { + private Intent createSendUriIntentWithPreview(ArrayList<Uri> uris) { Intent sendIntent = new Intent(); if (uris.size() > 1) { diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java index f60467bd3df2..096b78b95fbd 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java @@ -89,11 +89,7 @@ public class ChooserWrapperActivity extends ChooserActivity { @Override protected boolean isImageType(String mimeType) { - if (sOverrides.previewThumbnail != null) { - return true; - } - - return super.isImageType(mimeType); + return sOverrides.isImageType; } @Override @@ -112,6 +108,7 @@ public class ChooserWrapperActivity extends ChooserActivity { public Function<TargetInfo, Boolean> onSafelyStartCallback; public ResolverListController resolverListController; public Boolean isVoiceInteraction; + public boolean isImageType; public Bitmap previewThumbnail; public MetricsLogger metricsLogger; @@ -120,6 +117,7 @@ public class ChooserWrapperActivity extends ChooserActivity { isVoiceInteraction = null; createPackageManager = null; previewThumbnail = null; + isImageType = false; resolverListController = mock(ResolverListController.class); metricsLogger = mock(MetricsLogger.class); } diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderEndToEndTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderEndToEndTest.java index 64b7c2cce946..1c84829c5491 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderEndToEndTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderEndToEndTest.java @@ -124,7 +124,7 @@ public class KernelCpuThreadReaderEndToEndTest { // Get thread data from KernelCpuThreadReader final KernelCpuThreadReader kernelCpuThreadReader = - KernelCpuThreadReader.create(8, uid -> uid == Process.myUid()); + KernelCpuThreadReader.create(8, uid -> uid == Process.myUid(), 0); assertNotNull(kernelCpuThreadReader); final ProcessCpuUsage currentProcessCpuUsage = kernelCpuThreadReader.getCurrentProcessCpuUsage(); diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java index b9744f599a9a..442ece5a884d 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java @@ -103,6 +103,7 @@ public class KernelCpuThreadReaderTest { final KernelCpuThreadReader kernelCpuThreadReader = new KernelCpuThreadReader( 8, uid -> 1000 <= uid && uid < 2000, + 0, mProcDirectory.toPath(), mProcDirectory.toPath().resolve("self/task/" + THREAD_IDS[0] + "/time_in_state"), processUtils); @@ -144,6 +145,7 @@ public class KernelCpuThreadReaderTest { final KernelCpuThreadReader kernelCpuThreadReader = new KernelCpuThreadReader( 8, uidPredicate, + 0, mProcDirectory.toPath(), mProcDirectory.toPath().resolve(uids[0] + "/task/" + uids[0] + "/time_in_state"), processUtils); @@ -162,6 +164,60 @@ public class KernelCpuThreadReaderTest { } } + @Test + public void testReader_filtersLowUsage() throws IOException { + int[] uids = new int[]{0, 1, 2, 3, 4}; + int[] cpuUsage = new int[]{10, 0, 2, 100, 3}; + int[] expectedUids = new int[]{0, 3, 4}; + Predicate<Integer> uidPredicate = uid -> true; + KernelCpuThreadReader.Injector processUtils = + new KernelCpuThreadReader.Injector() { + @Override + public int myPid() { + return 0; + } + + @Override + public int myUid() { + return 0; + } + + @Override + public int getUidForPid(int pid) { + return pid; + } + }; + + for (int i = 0; i < uids.length; i++) { + int uid = uids[i]; + setupDirectory( + mProcDirectory.toPath().resolve(String.valueOf(uid)), + new int[]{uid * 10}, + "process" + uid, + new String[]{"thread" + uid}, + new int[]{1000}, + new int[][]{{cpuUsage[i]}}); + } + final KernelCpuThreadReader kernelCpuThreadReader = new KernelCpuThreadReader( + 8, + uidPredicate, + 30, + mProcDirectory.toPath(), + mProcDirectory.toPath().resolve(uids[0] + "/task/" + uids[0] + "/time_in_state"), + processUtils); + ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsageByUids = + kernelCpuThreadReader.getProcessCpuUsageByUids(); + processCpuUsageByUids.sort(Comparator.comparing(usage -> usage.uid)); + + assertEquals(expectedUids.length, processCpuUsageByUids.size()); + for (int i = 0; i < expectedUids.length; i++) { + KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = + processCpuUsageByUids.get(i); + assertEquals(expectedUids[i], processCpuUsage.uid); + } + + } + private void setupDirectory(Path processPath, int[] threadIds, String processName, String[] threadNames, int[] cpuFrequencies, int[][] cpuTimes) throws IOException { // Make /proc/$PID @@ -328,7 +384,6 @@ public class KernelCpuThreadReaderTest { new long[]{1, 1, 1, 1, 1, 1, 1, 1})); } - @Test public void testGetBigFrequenciesStartIndex_simple() { assertEquals( diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 738221340aba..915cf954f665 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -328,4 +328,9 @@ applications that come with the platform <permission name="android.permission.CONTROL_VPN"/> </privapp-permissions> + <privapp-permissions package="com.android.dynandroid"> + <permission name="android.permission.REBOOT"/> + <permission name="android.permission.MANAGE_DYNAMIC_ANDROID"/> + </privapp-permissions> + </permissions> diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 56be05b51d1a..ef9255f66695 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -127,7 +127,10 @@ public class Typeface { static Typeface sDefaultTypeface; // Following two fields are not used but left for hiddenapi private list - @UnsupportedAppUsage + /** + * Use {@link SystemFonts#getAvailableFonts()} instead. + */ + @UnsupportedAppUsage(trackingBug = 123769347) static final Map<String, Typeface> sSystemFontMap; // We cannot support sSystemFallbackMap since we will migrate to public FontFamily API. diff --git a/libs/hwui/FrameMetricsObserver.h b/libs/hwui/FrameMetricsObserver.h index 237fc622dd2e..b93f07853242 100644 --- a/libs/hwui/FrameMetricsObserver.h +++ b/libs/hwui/FrameMetricsObserver.h @@ -23,7 +23,7 @@ namespace uirenderer { class FrameMetricsObserver : public VirtualLightRefBase { public: - virtual void notify(const int64_t* buffer); + virtual void notify(const int64_t* buffer) = 0; }; } // namespace uirenderer diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp index 76c56609ef47..2ffda839c210 100644 --- a/libs/hwui/Readback.cpp +++ b/libs/hwui/Readback.cpp @@ -107,7 +107,7 @@ CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, Matrix4& texTran if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) { mRenderThread.requireGlContext(); } else { - mRenderThread.vulkanManager().initialize(); + mRenderThread.requireVkContext(); } if (!image.get()) { return CopyResult::UnknownError; diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index 15f53f286261..87cffb52c150 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -120,7 +120,7 @@ bool SkiaVulkanPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect } DeferredLayerUpdater* SkiaVulkanPipeline::createTextureLayer() { - mVkManager.initialize(); + mRenderThread.requireVkContext(); return new DeferredLayerUpdater(mRenderThread.renderState()); } @@ -136,8 +136,9 @@ bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBeh setSurfaceColorProperties(colorMode); if (surface) { + mRenderThread.requireVkContext(); mVkSurface = mVkManager.createSurface(surface, colorMode, mSurfaceColorSpace, - mSurfaceColorType); + mSurfaceColorType, mRenderThread.getGrContext()); } return mVkSurface != nullptr; diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index 3904ed20fd77..fc63819120d6 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -173,10 +173,10 @@ void RenderThread::initThreadLocals() { initializeDisplayEventReceiver(); mEglManager = new EglManager(); mRenderState = new RenderState(*this); - mVkManager = new VulkanManager(*this); + mVkManager = new VulkanManager(); mCacheManager = new CacheManager(mDisplayInfo); if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) { - mVkManager->initialize(); + requireVkContext(); } } @@ -195,8 +195,7 @@ void RenderThread::requireGlContext() { LOG_ALWAYS_FATAL_IF(!glInterface.get()); GrContextOptions options; - options.fPreferExternalImagesOverES3 = true; - options.fDisableDistanceFieldPaths = true; + initGrContextOptions(options); auto glesVersion = reinterpret_cast<const char*>(glGetString(GL_VERSION)); auto size = glesVersion ? strlen(glesVersion) : -1; cacheManager().configureContext(&options, glesVersion, size); @@ -205,6 +204,25 @@ void RenderThread::requireGlContext() { setGrContext(grContext); } +void RenderThread::requireVkContext() { + if (mVkManager->hasVkContext()) { + return; + } + mVkManager->initialize(); + GrContextOptions options; + initGrContextOptions(options); + // TODO: get a string describing the SPIR-V compiler version and use it here + cacheManager().configureContext(&options, nullptr, 0); + sk_sp<GrContext> grContext = mVkManager->createContext(options); + LOG_ALWAYS_FATAL_IF(!grContext.get()); + setGrContext(grContext); +} + +void RenderThread::initGrContextOptions(GrContextOptions& options) { + options.fPreferExternalImagesOverES3 = true; + options.fDisableDistanceFieldPaths = true; +} + void RenderThread::destroyRenderingContext() { mFunctorManager.onContextDestroyed(); if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) { diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h index b18292820c6b..419e7c7a6b51 100644 --- a/libs/hwui/renderthread/RenderThread.h +++ b/libs/hwui/renderthread/RenderThread.h @@ -112,6 +112,7 @@ public: void dumpGraphicsMemory(int fd); void requireGlContext(); + void requireVkContext(); void destroyRenderingContext(); /** @@ -122,6 +123,8 @@ public: */ static bool isCurrent(); + static void initGrContextOptions(GrContextOptions& options); + protected: virtual bool threadLoop() override; diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index 90397fddf618..1e685abd8afa 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -55,11 +55,7 @@ static void free_features_extensions_structs(const VkPhysicalDeviceFeatures2& fe #define GET_INST_PROC(F) m##F = (PFN_vk##F)vkGetInstanceProcAddr(mInstance, "vk" #F) #define GET_DEV_PROC(F) m##F = (PFN_vk##F)vkGetDeviceProcAddr(mDevice, "vk" #F) -VulkanManager::VulkanManager(RenderThread& thread) : mRenderThread(thread) {} - void VulkanManager::destroy() { - mRenderThread.setGrContext(nullptr); - // We don't need to explicitly free the command buffer since it automatically gets freed when we // delete the VkCommandPool below. mDummyCB = VK_NULL_HANDLE; @@ -333,29 +329,10 @@ void VulkanManager::initialize() { LOG_ALWAYS_FATAL_IF(mEnumerateInstanceVersion(&instanceVersion)); LOG_ALWAYS_FATAL_IF(instanceVersion < VK_MAKE_VERSION(1, 1, 0)); - GrVkExtensions extensions; - this->setupDevice(extensions, mPhysicalDeviceFeatures2); + this->setupDevice(mExtensions, mPhysicalDeviceFeatures2); mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue); - auto getProc = [] (const char* proc_name, VkInstance instance, VkDevice device) { - if (device != VK_NULL_HANDLE) { - return vkGetDeviceProcAddr(device, proc_name); - } - return vkGetInstanceProcAddr(instance, proc_name); - }; - - GrVkBackendContext backendContext; - backendContext.fInstance = mInstance; - backendContext.fPhysicalDevice = mPhysicalDevice; - backendContext.fDevice = mDevice; - backendContext.fQueue = mGraphicsQueue; - backendContext.fGraphicsQueueIndex = mGraphicsQueueIndex; - backendContext.fMaxAPIVersion = mAPIVersion; - backendContext.fVkExtensions = &extensions; - backendContext.fDeviceFeatures2 = &mPhysicalDeviceFeatures2; - backendContext.fGetProc = std::move(getProc); - // create the command pool for the command buffers if (VK_NULL_HANDLE == mCommandPool) { VkCommandPoolCreateInfo commandPoolInfo; @@ -376,22 +353,35 @@ void VulkanManager::initialize() { } LOG_ALWAYS_FATAL_IF(mDummyCB == VK_NULL_HANDLE); - mGetDeviceQueue(mDevice, mPresentQueueIndex, 0, &mPresentQueue); - GrContextOptions options; - options.fDisableDistanceFieldPaths = true; - // TODO: get a string describing the SPIR-V compiler version and use it here - mRenderThread.cacheManager().configureContext(&options, nullptr, 0); - sk_sp<GrContext> grContext(GrContext::MakeVulkan(backendContext, options)); - LOG_ALWAYS_FATAL_IF(!grContext.get()); - mRenderThread.setGrContext(grContext); - if (Properties::enablePartialUpdates && Properties::useBufferAge) { mSwapBehavior = SwapBehavior::BufferAge; } } +sk_sp<GrContext> VulkanManager::createContext(GrContextOptions options) { + auto getProc = [] (const char* proc_name, VkInstance instance, VkDevice device) { + if (device != VK_NULL_HANDLE) { + return vkGetDeviceProcAddr(device, proc_name); + } + return vkGetInstanceProcAddr(instance, proc_name); + }; + + GrVkBackendContext backendContext; + backendContext.fInstance = mInstance; + backendContext.fPhysicalDevice = mPhysicalDevice; + backendContext.fDevice = mDevice; + backendContext.fQueue = mGraphicsQueue; + backendContext.fGraphicsQueueIndex = mGraphicsQueueIndex; + backendContext.fMaxAPIVersion = mAPIVersion; + backendContext.fVkExtensions = &mExtensions; + backendContext.fDeviceFeatures2 = &mPhysicalDeviceFeatures2; + backendContext.fGetProc = std::move(getProc); + + return GrContext::MakeVulkan(backendContext, options); +} + VkFunctorInitParams VulkanManager::getVkFunctorInitParams() const { return VkFunctorInitParams{ .instance = mInstance, @@ -470,8 +460,9 @@ SkSurface* VulkanManager::getBackbufferSurface(VulkanSurface** surfaceOut) { ColorMode colorMode = surface->mColorMode; sk_sp<SkColorSpace> colorSpace = surface->mColorSpace; SkColorType colorType = surface->mColorType; + GrContext* grContext = surface->mGrContext; destroySurface(surface); - *surfaceOut = createSurface(window, colorMode, colorSpace, colorType); + *surfaceOut = createSurface(window, colorMode, colorSpace, colorType, grContext); surface = *surfaceOut; if (!surface) { return nullptr; @@ -650,7 +641,7 @@ void VulkanManager::createBuffers(VulkanSurface* surface, VkFormat format, VkExt VulkanSurface::ImageInfo& imageInfo = surface->mImageInfos[i]; imageInfo.mSurface = SkSurface::MakeFromBackendRenderTarget( - mRenderThread.getGrContext(), backendRT, kTopLeft_GrSurfaceOrigin, + surface->mGrContext, backendRT, kTopLeft_GrSurfaceOrigin, surface->mColorType, surface->mColorSpace, &props); } @@ -880,15 +871,15 @@ bool VulkanManager::createSwapchain(VulkanSurface* surface) { VulkanSurface* VulkanManager::createSurface(ANativeWindow* window, ColorMode colorMode, sk_sp<SkColorSpace> surfaceColorSpace, - SkColorType surfaceColorType) { - initialize(); - + SkColorType surfaceColorType, + GrContext* grContext) { + LOG_ALWAYS_FATAL_IF(!hasVkContext(), "Not initialized"); if (!window) { return nullptr; } VulkanSurface* surface = new VulkanSurface(colorMode, window, surfaceColorSpace, - surfaceColorType); + surfaceColorType, grContext); VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo; memset(&surfaceCreateInfo, 0, sizeof(VkAndroidSurfaceCreateInfoKHR)); diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h index 1fe6c65b35b8..97636865a629 100644 --- a/libs/hwui/renderthread/VulkanManager.h +++ b/libs/hwui/renderthread/VulkanManager.h @@ -22,6 +22,8 @@ #endif #include <vulkan/vulkan.h> +#include <GrContextOptions.h> +#include <vk/GrVkExtensions.h> #include <SkSurface.h> #include <ui/Fence.h> #include <utils/StrongPointer.h> @@ -39,9 +41,9 @@ class RenderThread; class VulkanSurface { public: VulkanSurface(ColorMode colorMode, ANativeWindow* window, sk_sp<SkColorSpace> colorSpace, - SkColorType colorType) + SkColorType colorType, GrContext* grContext) : mColorMode(colorMode), mNativeWindow(window), mColorSpace(colorSpace), - mColorType(colorType) {} + mColorType(colorType), mGrContext(grContext) {} sk_sp<SkSurface> getBackBufferSurface() { return mBackbuffer; } @@ -93,6 +95,7 @@ private: SkColorType mColorType; VkSurfaceTransformFlagBitsKHR mTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; SkMatrix mPreTransform; + GrContext* mGrContext; }; // This class contains the shared global Vulkan objects, such as VkInstance, VkDevice and VkQueue, @@ -100,6 +103,9 @@ private: // windowing contexts. The VulkanManager must be initialized before use. class VulkanManager { public: + explicit VulkanManager() {} + ~VulkanManager() { destroy(); } + // Sets up the vulkan context that is shared amonst all clients of the VulkanManager. This must // be call once before use of the VulkanManager. Multiple calls after the first will simiply // return. @@ -112,7 +118,8 @@ public: // VulkanSurface object which is returned. VulkanSurface* createSurface(ANativeWindow* window, ColorMode colorMode, sk_sp<SkColorSpace> surfaceColorSpace, - SkColorType surfaceColorType); + SkColorType surfaceColorType, + GrContext* grContext); // Destroy the VulkanSurface and all associated vulkan objects. void destroySurface(VulkanSurface* surface); @@ -143,12 +150,9 @@ public: // Returned pointers are owned by VulkanManager. VkFunctorInitParams getVkFunctorInitParams() const; -private: - friend class RenderThread; - - explicit VulkanManager(RenderThread& thread); - ~VulkanManager() { destroy(); } + sk_sp<GrContext> createContext(GrContextOptions options); +private: // Sets up the VkInstance and VkDevice objects. Also fills out the passed in // VkPhysicalDeviceFeatures struct. void setupDevice(GrVkExtensions&, VkPhysicalDeviceFeatures2&); @@ -231,8 +235,6 @@ private: VkPtr<PFN_vkWaitForFences> mWaitForFences; VkPtr<PFN_vkResetFences> mResetFences; - RenderThread& mRenderThread; - VkInstance mInstance = VK_NULL_HANDLE; VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE; VkDevice mDevice = VK_NULL_HANDLE; @@ -256,6 +258,7 @@ private: BufferAge, }; SwapBehavior mSwapBehavior = SwapBehavior::Discard; + GrVkExtensions mExtensions; }; } /* namespace renderthread */ diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp index a9f651d38a06..e8ba15fe92af 100644 --- a/libs/hwui/tests/common/TestUtils.cpp +++ b/libs/hwui/tests/common/TestUtils.cpp @@ -100,7 +100,7 @@ void TestUtils::TestTask::run() { // RenderState only valid once RenderThread is running, so queried here renderthread::RenderThread& renderThread = renderthread::RenderThread::getInstance(); if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) { - renderThread.vulkanManager().initialize(); + renderThread.requireVkContext(); } else { renderThread.requireGlContext(); } diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index d742cc34b57e..733b866d9c4c 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -115,10 +115,14 @@ PointerController::~PointerController() { mLocked.pointerSprite.clear(); - for (size_t i = 0; i < mLocked.spots.size(); i++) { - delete mLocked.spots.itemAt(i); + for (auto& it : mLocked.spotsByDisplay) { + const std::vector<Spot*>& spots = it.second; + size_t numSpots = spots.size(); + for (size_t i = 0; i < numSpots; i++) { + delete spots[i]; + } } - mLocked.spots.clear(); + mLocked.spotsByDisplay.clear(); mLocked.recycledSprites.clear(); } @@ -271,22 +275,30 @@ void PointerController::setPresentation(Presentation presentation) { } void PointerController::setSpots(const PointerCoords* spotCoords, - const uint32_t* spotIdToIndex, BitSet32 spotIdBits) { + const uint32_t* spotIdToIndex, BitSet32 spotIdBits, int32_t displayId) { #if DEBUG_POINTER_UPDATES ALOGD("setSpots: idBits=%08x", spotIdBits.value); for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) { uint32_t id = idBits.firstMarkedBit(); idBits.clearBit(id); const PointerCoords& c = spotCoords[spotIdToIndex[id]]; - ALOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f", id, + ALOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f, displayId=%" PRId32 ".", id, c.getAxisValue(AMOTION_EVENT_AXIS_X), c.getAxisValue(AMOTION_EVENT_AXIS_Y), - c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); + c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), + displayId); } #endif AutoMutex _l(mLock); + std::vector<Spot*> newSpots; + std::map<int32_t, std::vector<Spot*>>::const_iterator iter = + mLocked.spotsByDisplay.find(displayId); + if (iter != mLocked.spotsByDisplay.end()) { + newSpots = iter->second; + } + mSpriteController->openTransaction(); // Add or move spots for fingers that are down. @@ -298,17 +310,17 @@ void PointerController::setSpots(const PointerCoords* spotCoords, float x = c.getAxisValue(AMOTION_EVENT_AXIS_X); float y = c.getAxisValue(AMOTION_EVENT_AXIS_Y); - Spot* spot = getSpotLocked(id); + Spot* spot = getSpot(id, newSpots); if (!spot) { - spot = createAndAddSpotLocked(id); + spot = createAndAddSpotLocked(id, newSpots); } - spot->updateSprite(&icon, x, y); + spot->updateSprite(&icon, x, y, displayId); } // Remove spots for fingers that went up. - for (size_t i = 0; i < mLocked.spots.size(); i++) { - Spot* spot = mLocked.spots.itemAt(i); + for (size_t i = 0; i < newSpots.size(); i++) { + Spot* spot = newSpots[i]; if (spot->id != Spot::INVALID_ID && !spotIdBits.hasBit(spot->id)) { fadeOutAndReleaseSpotLocked(spot); @@ -316,6 +328,7 @@ void PointerController::setSpots(const PointerCoords* spotCoords, } mSpriteController->closeTransaction(); + mLocked.spotsByDisplay[displayId] = newSpots; } void PointerController::clearSpots() { @@ -539,21 +552,33 @@ bool PointerController::doFadingAnimationLocked(nsecs_t timestamp) { } // Animate spots that are fading out and being removed. - for (size_t i = 0; i < mLocked.spots.size();) { - Spot* spot = mLocked.spots.itemAt(i); - if (spot->id == Spot::INVALID_ID) { - spot->alpha -= float(frameDelay) / SPOT_FADE_DURATION; - if (spot->alpha <= 0) { - mLocked.spots.removeAt(i); - releaseSpotLocked(spot); - continue; - } else { - spot->sprite->setAlpha(spot->alpha); - keepAnimating = true; + for(auto it = mLocked.spotsByDisplay.begin(); it != mLocked.spotsByDisplay.end();) { + std::vector<Spot*>& spots = it->second; + size_t numSpots = spots.size(); + for (size_t i = 0; i < numSpots;) { + Spot* spot = spots[i]; + if (spot->id == Spot::INVALID_ID) { + spot->alpha -= float(frameDelay) / SPOT_FADE_DURATION; + if (spot->alpha <= 0) { + spots.erase(spots.begin() + i); + releaseSpotLocked(spot); + numSpots--; + continue; + } else { + spot->sprite->setAlpha(spot->alpha); + keepAnimating = true; + } } + ++i; + } + + if (spots.size() == 0) { + it = mLocked.spotsByDisplay.erase(it); + } else { + ++it; } - ++i; } + return keepAnimating; } @@ -655,47 +680,49 @@ void PointerController::updatePointerLocked() REQUIRES(mLock) { mSpriteController->closeTransaction(); } -PointerController::Spot* PointerController::getSpotLocked(uint32_t id) { - for (size_t i = 0; i < mLocked.spots.size(); i++) { - Spot* spot = mLocked.spots.itemAt(i); +PointerController::Spot* PointerController::getSpot(uint32_t id, const std::vector<Spot*>& spots) { + for (size_t i = 0; i < spots.size(); i++) { + Spot* spot = spots[i]; if (spot->id == id) { return spot; } } - return NULL; + + return nullptr; } -PointerController::Spot* PointerController::createAndAddSpotLocked(uint32_t id) { +PointerController::Spot* PointerController::createAndAddSpotLocked(uint32_t id, + std::vector<Spot*>& spots) { // Remove spots until we have fewer than MAX_SPOTS remaining. - while (mLocked.spots.size() >= MAX_SPOTS) { - Spot* spot = removeFirstFadingSpotLocked(); + while (spots.size() >= MAX_SPOTS) { + Spot* spot = removeFirstFadingSpotLocked(spots); if (!spot) { - spot = mLocked.spots.itemAt(0); - mLocked.spots.removeAt(0); + spot = spots[0]; + spots.erase(spots.begin()); } releaseSpotLocked(spot); } // Obtain a sprite from the recycled pool. sp<Sprite> sprite; - if (! mLocked.recycledSprites.isEmpty()) { - sprite = mLocked.recycledSprites.top(); - mLocked.recycledSprites.pop(); + if (! mLocked.recycledSprites.empty()) { + sprite = mLocked.recycledSprites.back(); + mLocked.recycledSprites.pop_back(); } else { sprite = mSpriteController->createSprite(); } // Return the new spot. Spot* spot = new Spot(id, sprite); - mLocked.spots.push(spot); + spots.push_back(spot); return spot; } -PointerController::Spot* PointerController::removeFirstFadingSpotLocked() { - for (size_t i = 0; i < mLocked.spots.size(); i++) { - Spot* spot = mLocked.spots.itemAt(i); +PointerController::Spot* PointerController::removeFirstFadingSpotLocked(std::vector<Spot*>& spots) { + for (size_t i = 0; i < spots.size(); i++) { + Spot* spot = spots[i]; if (spot->id == Spot::INVALID_ID) { - mLocked.spots.removeAt(i); + spots.erase(spots.begin() + i); return spot; } } @@ -706,7 +733,7 @@ void PointerController::releaseSpotLocked(Spot* spot) { spot->sprite->clearIcon(); if (mLocked.recycledSprites.size() < MAX_RECYCLED_SPRITES) { - mLocked.recycledSprites.push(spot->sprite); + mLocked.recycledSprites.push_back(spot->sprite); } delete spot; @@ -720,9 +747,13 @@ void PointerController::fadeOutAndReleaseSpotLocked(Spot* spot) { } void PointerController::fadeOutAndReleaseAllSpotsLocked() { - for (size_t i = 0; i < mLocked.spots.size(); i++) { - Spot* spot = mLocked.spots.itemAt(i); - fadeOutAndReleaseSpotLocked(spot); + for (auto& it : mLocked.spotsByDisplay) { + const std::vector<Spot*>& spots = it.second; + size_t numSpots = spots.size(); + for (size_t i = 0; i < numSpots; i++) { + Spot* spot = spots[i]; + fadeOutAndReleaseSpotLocked(spot); + } } } @@ -743,12 +774,13 @@ void PointerController::loadResourcesLocked() REQUIRES(mLock) { // --- PointerController::Spot --- -void PointerController::Spot::updateSprite(const SpriteIcon* icon, float x, float y) { +void PointerController::Spot::updateSprite(const SpriteIcon* icon, float x, float y, + int32_t displayId) { sprite->setLayer(Sprite::BASE_LAYER_SPOT + id); sprite->setAlpha(alpha); sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale)); sprite->setPosition(x, y); - + sprite->setDisplayId(displayId); this->x = x; this->y = y; diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index be057867890d..52305b8244a6 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -103,7 +103,7 @@ public: virtual void setPresentation(Presentation presentation); virtual void setSpots(const PointerCoords* spotCoords, - const uint32_t* spotIdToIndex, BitSet32 spotIdBits); + const uint32_t* spotIdToIndex, BitSet32 spotIdBits, int32_t displayId); virtual void clearSpots(); void updatePointerIcon(int32_t iconId); @@ -133,7 +133,7 @@ private: : id(id), sprite(sprite), alpha(1.0f), scale(1.0f), x(0.0f), y(0.0f), lastIcon(NULL) { } - void updateSprite(const SpriteIcon* icon, float x, float y); + void updateSprite(const SpriteIcon* icon, float x, float y, int32_t displayId); private: const SpriteIcon* lastIcon; @@ -180,8 +180,8 @@ private: int32_t buttonState; - Vector<Spot*> spots; - Vector<sp<Sprite> > recycledSprites; + std::map<int32_t /* displayId */, std::vector<Spot*>> spotsByDisplay; + std::vector<sp<Sprite> > recycledSprites; } mLocked GUARDED_BY(mLock); bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const; @@ -200,9 +200,9 @@ private: void removeInactivityTimeoutLocked(); void updatePointerLocked(); - Spot* getSpotLocked(uint32_t id); - Spot* createAndAddSpotLocked(uint32_t id); - Spot* removeFirstFadingSpotLocked(); + Spot* getSpot(uint32_t id, const std::vector<Spot*>& spots); + Spot* createAndAddSpotLocked(uint32_t id, std::vector<Spot*>& spots); + Spot* removeFirstFadingSpotLocked(std::vector<Spot*>& spots); void releaseSpotLocked(Spot* spot); void fadeOutAndReleaseSpotLocked(Spot* spot); void fadeOutAndReleaseAllSpotsLocked(); diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java index 59eff6401deb..a545f2edd1bf 100644 --- a/location/java/android/location/GnssMeasurement.java +++ b/location/java/android/location/GnssMeasurement.java @@ -96,7 +96,7 @@ public final class GnssMeasurement implements Parcelable { STATE_TOW_DECODED, STATE_MSEC_AMBIGUOUS, STATE_SYMBOL_SYNC, STATE_GLO_STRING_SYNC, STATE_GLO_TOD_DECODED, STATE_BDS_D2_BIT_SYNC, STATE_BDS_D2_SUBFRAME_SYNC, STATE_GAL_E1BC_CODE_LOCK, STATE_GAL_E1C_2ND_CODE_LOCK, STATE_GAL_E1B_PAGE_SYNC, - STATE_SBAS_SYNC, STATE_TOW_KNOWN, STATE_GLO_TOD_KNOWN + STATE_SBAS_SYNC, STATE_TOW_KNOWN, STATE_GLO_TOD_KNOWN, STATE_2ND_CODE_LOCK }) @Retention(RetentionPolicy.SOURCE) public @interface State {} @@ -144,6 +144,9 @@ public final class GnssMeasurement implements Parcelable { */ public static final int STATE_GLO_TOD_KNOWN = (1<<15); + /** This GNSS measurement's tracking state has secondary code lock. */ + public static final int STATE_2ND_CODE_LOCK = (1 << 16); + /** * All the GNSS receiver state flags, for bit masking purposes (not a sensible state for any * individual measurement.) @@ -517,6 +520,9 @@ public final class GnssMeasurement implements Parcelable { if ((mState & STATE_SBAS_SYNC) != 0) { builder.append("SbasSync|"); } + if ((mState & STATE_2ND_CODE_LOCK) != 0) { + builder.append("2ndCodeLock|"); + } int remainingStates = mState & ~STATE_ALL; if (remainingStates > 0) { @@ -531,96 +537,315 @@ public final class GnssMeasurement implements Parcelable { /** * Gets the received GNSS satellite time, at the measurement time, in nanoseconds. * - * <p>For GPS & QZSS, this is: - * <ul> - * <li>Received GPS Time-of-Week at the measurement time, in nanoseconds.</li> - * <li>The value is relative to the beginning of the current GPS week.</li> - * </ul> - * - * <p>Given the highest sync state that can be achieved, per each satellite, valid range - * for this field can be: - * <pre> - * Searching : [ 0 ] : STATE_UNKNOWN - * C/A code lock : [ 0 1ms ] : STATE_CODE_LOCK is set - * Bit sync : [ 0 20ms ] : STATE_BIT_SYNC is set - * Subframe sync : [ 0 6s ] : STATE_SUBFRAME_SYNC is set - * TOW decoded : [ 0 1week ] : STATE_TOW_DECODED is set - * TOW Known : [ 0 1week ] : STATE_TOW_KNOWN set</pre> - * - * Note: TOW Known refers to the case where TOW is possibly not decoded over the air but has + * <p>The received satellite time is relative to the beginning of the system week for all + * constellations except for Glonass where it is relative to the beginning of the Glonass + * system day. + * + * <p>The table below indicates the valid range of the received GNSS satellite time. These + * ranges depend on the constellation and code being tracked and the state of the tracking + * algorithms given by the {@link #getState} method. The minimum value of this field is zero. + * The maximum value of this field is determined by looking across all of the state flags + * that are set, for the given constellation and code type, and finding the the maximum value + * in this table. + * + * <p>For example, for GPS L1 C/A, if STATE_TOW_KNOWN is set, this field can be any value from 0 + * to 1 week (in nanoseconds), and for GAL E1B code, if only STATE_GAL_E1BC_CODE_LOCK is set, + * then this field can be any value from 0 to 4 milliseconds (in nanoseconds.) + * + * <table border="1"> + * <thead> + * <tr> + * <td /> + * <td colspan="3"><strong>GPS/QZSS</strong></td> + * <td><strong>GLNS</strong></td> + * <td colspan="2"><strong>BDS</strong></td> + * <td colspan="3"><strong>GAL</strong></td> + * <td><strong>SBAS</strong></td> + * </tr> + * <tr> + * <td><strong>State Flag</strong></td> + * <td><strong>L1 C/A</strong></td> + * <td><strong>L5I</strong></td> + * <td><strong>L5Q</strong></td> + * <td><strong>L1OF</strong></td> + * <td><strong>B1I (D1)</strong></td> + * <td><strong>B1I (D2)</strong></td> + * <td><strong>E1B</strong></td> + * <td><strong>E1C</strong></td> + * <td><strong>E5AQ</strong></td> + * <td><strong>L1 C/A</strong></td> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td> + * <strong>STATE_UNKNOWN</strong> + * </td> + * <td>0</td> + * <td>0</td> + * <td>0</td> + * <td>0</td> + * <td>0</td> + * <td>0</td> + * <td>0</td> + * <td>0</td> + * <td>0</td> + * <td>0</td> + * </tr> + * <tr> + * <td> + * <strong>STATE_CODE_LOCK</strong> + * </td> + * <td>1 ms</td> + * <td>1 ms</td> + * <td>1 ms</td> + * <td>1 ms</td> + * <td>1 ms</td> + * <td>1 ms</td> + * <td>-</td> + * <td>-</td> + * <td>1 ms</td> + * <td>1 ms</td> + * </tr> + * <tr> + * <td> + * <strong>STATE_SYMBOL_SYNC</strong> + * </td> + * <td>20 ms (optional)</td> + * <td>10 ms</td> + * <td>1 ms (optional)</td> + * <td>10 ms</td> + * <td>20 ms (optional)</td> + * <td>2 ms</td> + * <td>4 ms (optional)</td> + * <td>4 ms (optional)</td> + * <td>1 ms (optional)</td> + * <td>2 ms</td> + * </tr> + * <tr> + * <td> + * <strong>STATE_BIT_SYNC</strong> + * </td> + * <td>20 ms</td> + * <td>20 ms</td> + * <td>1 ms (optional)</td> + * <td>20 ms</td> + * <td>20 ms</td> + * <td>-</td> + * <td>8 ms</td> + * <td>-</td> + * <td>1 ms (optional)</td> + * <td>4 ms</td> + * </tr> + * <tr> + * <td> + * <strong>STATE_SUBFRAME_SYNC</strong> + * </td> + * <td>6s</td> + * <td>6s</td> + * <td>-</td> + * <td>2 s</td> + * <td>6 s</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>100 ms</td> + * <td>-</td> + * </tr> + * <tr> + * <td> + * <strong>STATE_TOW_DECODED</strong> + * </td> + * <td colspan="2">1 week</td> + * <td>-</td> + * <td>1 day</td> + * <td colspan="2">1 week</td> + * <td colspan="2">1 week</td> + * <td>-</td> + * <td>1 week</td> + * </tr> + * <tr> + * <td> + * <strong>STATE_TOW_KNOWN</strong> + * </td> + * <td colspan="3">1 week</td> + * <td>1 day</td> + * <td colspan="2">1 week</td> + * <td colspan="3">1 week</td> + * <td>1 week</td> + * </tr> + * <tr> + * <td> + * <strong>STATE_GLO_STRING_SYNC</strong> + * </td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>2 s</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * </tr> + * <tr> + * <td> + * <strong>STATE_GLO_TOD_DECODED</strong> + * </td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>1 day</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * </tr> + * <tr> + * <td> + * <strong>STATE_GLO_TOD_KNOWN</strong> + * </td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>1 day</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * </tr> + * <tr> + * <td> + * <strong>STATE_BDS_D2_BIT_SYNC</strong> + * </td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>2 ms</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * </tr> + * <tr> + * <td> + * <strong>STATE_BDS_D2_SUBFRAME_SYNC</strong> + * </td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>600 ms</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * </tr> + * <tr> + * <td> + * <strong>STATE_GAL_E1BC_CODE_LOCK</strong> + * </td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>4 ms</td> + * <td>4 ms</td> + * <td>-</td> + * <td>-</td> + * </tr> + * <tr> + * <td> + * <strong>STATE_GAL_E1C_2ND_CODE_LOCK</strong> + * </td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>100 ms</td> + * <td>-</td> + * <td>-</td> + * </tr> + * <tr> + * <td> + * <strong>STATE_2ND_CODE_LOCK</strong> + * </td> + * <td>-</td> + * <td>10 ms (optional)</td> + * <td>20 ms</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>100 ms (optional)</td> + * <td>100 ms</td> + * <td>-</td> + * </tr> + * <tr> + * <td> + * <strong>STATE_GAL_E1B_PAGE_SYNC</strong> + * </td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>2 s</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * </tr> + * <tr> + * <td> + * <strong>STATE_SBAS_SYNC</strong> + * </td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>-</td> + * <td>1 s</td> + * </tr> + * </tbody> + * </table> + * + * <p>Note: TOW Known refers to the case where TOW is possibly not decoded over the air but has * been determined from other sources. If TOW decoded is set then TOW Known must also be set. * - * <p>Note well: if there is any ambiguity in integer millisecond, {@code STATE_MSEC_AMBIGUOUS} - * must be set accordingly, in the 'state' field. - * - * <p>This value must be populated if 'state' != {@code STATE_UNKNOWN}. + * <p>Note well: if there is any ambiguity in integer millisecond, STATE_MSEC_AMBIGUOUS must be + * set accordingly, in the 'state' field. This value must be populated, unless the 'state' == + * STATE_UNKNOWN. * - * <p>For Glonass, this is: + * <p>Note on optional flags: * <ul> - * <li>Received Glonass time of day, at the measurement time in nanoseconds.</li> + * <li> For L1 C/A and B1I, STATE_SYMBOL_SYNC is optional since the symbol length is the + * same as the bit length. + * <li> For L5Q and E5aQ, STATE_BIT_SYNC and STATE_SYMBOL_SYNC are optional since they are + * implied by STATE_CODE_LOCK. + * <li> STATE_2ND_CODE_LOCK for L5I is optional since it is implied by STATE_SYMBOL_SYNC. + * <li> STATE_2ND_CODE_LOCK for E1C is optional since it is implied by + * STATE_GAL_E1C_2ND_CODE_LOCK. + * <li> For E1B and E1C, STATE_SYMBOL_SYNC is optional, because it is implied by + * STATE_GAL_E1BC_CODE_LOCK. * </ul> - * - * <p>Given the highest sync state that can be achieved, per each satellite, valid range for - * this field can be: - * <pre> - * Searching : [ 0 ] : STATE_UNKNOWN - * C/A code lock : [ 0 1ms ] : STATE_CODE_LOCK is set - * Symbol sync : [ 0 10ms ] : STATE_SYMBOL_SYNC is set - * Bit sync : [ 0 20ms ] : STATE_BIT_SYNC is set - * String sync : [ 0 2s ] : STATE_GLO_STRING_SYNC is set - * Time of day decoded : [ 0 1day ] : STATE_GLO_TOD_DECODED is set - * Time of day known : [ 0 1day ] : STATE_GLO_TOD_KNOWN set</pre> - * - * Note: Time of day known refers to the case where it is possibly not decoded over the air but - * has been determined from other sources. If Time of day decoded is set then Time of day known - * must also be set. - * - * <p>For Beidou, this is: - * <ul> - * <li>Received Beidou time of week, at the measurement time in nanoseconds.</li> - * </ul> - * - * <p>Given the highest sync state that can be achieved, per each satellite, valid range for - * this field can be: - * <pre> - * Searching : [ 0 ] : STATE_UNKNOWN - * C/A code lock : [ 0 1ms ] : STATE_CODE_LOCK is set - * Bit sync (D2) : [ 0 2ms ] : STATE_BDS_D2_BIT_SYNC is set - * Bit sync (D1) : [ 0 20ms ] : STATE_BIT_SYNC is set - * Subframe (D2) : [ 0 0.6s ] : STATE_BDS_D2_SUBFRAME_SYNC is set - * Subframe (D1) : [ 0 6s ] : STATE_SUBFRAME_SYNC is set - * Time of week decoded : [ 0 1week ] : STATE_TOW_DECODED is set - * Time of week known : [ 0 1week ] : STATE_TOW_KNOWN set</pre> - * - * Note: TOW Known refers to the case where TOW is possibly not decoded over the air but has - * been determined from other sources. If TOW decoded is set then TOW Known must also be set. - * - * <p>For Galileo, this is: - * <ul> - * <li>Received Galileo time of week, at the measurement time in nanoseconds.</li> - * </ul> - * <pre> - * E1BC code lock : [ 0 4ms ] : STATE_GAL_E1BC_CODE_LOCK is set - * E1C 2nd code lock : [ 0 100ms ] : STATE_GAL_E1C_2ND_CODE_LOCK is set - * E1B page : [ 0 2s ] : STATE_GAL_E1B_PAGE_SYNC is set - * Time of week decoded : [ 0 1week ] : STATE_TOW_DECODED is set - * Time of week known : [ 0 1week ] : STATE_TOW_KNOWN set</pre> - * - * Note: TOW Known refers to the case where TOW is possibly not decoded over the air but has - * been determined from other sources. If TOW decoded is set then TOW Known must also be set. - * - * <p>For SBAS, this is: - * <ul> - * <li>Received SBAS time, at the measurement time in nanoseconds.</li> - * </ul> - * - * <p>Given the highest sync state that can be achieved, per each satellite, valid range for - * this field can be: - * <pre> - * Searching : [ 0 ] : STATE_UNKNOWN - * C/A code lock : [ 0 1ms ] : STATE_CODE_LOCK is set - * Symbol sync : [ 0 2ms ] : STATE_SYMBOL_SYNC is set - * Message : [ 0 1s ] : STATE_SBAS_SYNC is set</pre> */ public long getReceivedSvTimeNanos() { return mReceivedSvTimeNanos; diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java index 0caa0c5b377a..b3953fddb622 100644 --- a/location/java/android/location/LocationRequest.java +++ b/location/java/android/location/LocationRequest.java @@ -184,8 +184,7 @@ public final class LocationRequest implements Parcelable { * @return a new location request */ public static LocationRequest create() { - LocationRequest request = new LocationRequest(); - return request; + return new LocationRequest(); } /** @hide */ @@ -230,12 +229,10 @@ public final class LocationRequest implements Parcelable { quality = ACCURACY_FINE; break; default: { - switch (criteria.getPowerRequirement()) { - case Criteria.POWER_HIGH: - quality = POWER_HIGH; - break; - default: - quality = POWER_LOW; + if (criteria.getPowerRequirement() == Criteria.POWER_HIGH) { + quality = POWER_HIGH; + } else { + quality = POWER_LOW; } } } @@ -288,7 +285,7 @@ public final class LocationRequest implements Parcelable { * * @param quality an accuracy or power constant * @return the same object, so that setters can be chained - * @throws InvalidArgumentException if the quality constant is not valid + * @throws IllegalArgumentException if the quality constant is not valid */ public LocationRequest setQuality(int quality) { checkQuality(quality); @@ -331,7 +328,7 @@ public final class LocationRequest implements Parcelable { * * @param millis desired interval in millisecond, inexact * @return the same object, so that setters can be chained - * @throws InvalidArgumentException if the interval is less than zero + * @throws IllegalArgumentException if the interval is less than zero */ public LocationRequest setInterval(long millis) { checkInterval(millis); @@ -433,7 +430,7 @@ public final class LocationRequest implements Parcelable { * * @param millis fastest interval for updates in milliseconds, exact * @return the same object, so that setters can be chained - * @throws InvalidArgumentException if the interval is less than zero + * @throws IllegalArgumentException if the interval is less than zero */ public LocationRequest setFastestInterval(long millis) { checkInterval(millis); @@ -528,7 +525,7 @@ public final class LocationRequest implements Parcelable { * * @param numUpdates the number of location updates requested * @return the same object, so that setters can be chained - * @throws InvalidArgumentException if numUpdates is 0 or less + * @throws IllegalArgumentException if numUpdates is 0 or less */ public LocationRequest setNumUpdates(int numUpdates) { if (numUpdates <= 0) { @@ -668,7 +665,7 @@ public final class LocationRequest implements Parcelable { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private static void checkProvider(String name) { if (name == null) { - throw new IllegalArgumentException("invalid provider: " + name); + throw new IllegalArgumentException("invalid provider: null"); } } @@ -758,9 +755,11 @@ public final class LocationRequest implements Parcelable { if (mNumUpdates != Integer.MAX_VALUE) { s.append(" num=").append(mNumUpdates); } - s.append(" lowPowerMode=").append(mLowPowerMode); + if (mLowPowerMode) { + s.append(" lowPowerMode"); + } if (mLocationSettingsIgnored) { - s.append(" ignoreSettings"); + s.append(" locationSettingsIgnored"); } s.append(']'); return s.toString(); diff --git a/location/java/com/android/internal/location/ProviderRequest.java b/location/java/com/android/internal/location/ProviderRequest.java index a45c20d9d09d..af8123ac52f4 100644 --- a/location/java/com/android/internal/location/ProviderRequest.java +++ b/location/java/com/android/internal/location/ProviderRequest.java @@ -40,7 +40,7 @@ public final class ProviderRequest implements Parcelable { * restrictions or any other restricting factors and always satisfy this request to the best of * their ability. This flag should only be used in event of an emergency. */ - public boolean forceLocation = false; + public boolean locationSettingsIgnored = false; /** * Whether provider shall make stronger than normal tradeoffs to substantially restrict power @@ -70,6 +70,7 @@ public final class ProviderRequest implements Parcelable { request.reportLocation = in.readInt() == 1; request.interval = in.readLong(); request.lowPowerMode = in.readBoolean(); + request.locationSettingsIgnored = in.readBoolean(); int count = in.readInt(); for (int i = 0; i < count; i++) { request.locationRequests.add(LocationRequest.CREATOR.createFromParcel(in)); @@ -93,6 +94,7 @@ public final class ProviderRequest implements Parcelable { parcel.writeInt(reportLocation ? 1 : 0); parcel.writeLong(interval); parcel.writeBoolean(lowPowerMode); + parcel.writeBoolean(locationSettingsIgnored); parcel.writeInt(locationRequests.size()); for (LocationRequest request : locationRequests) { request.writeToParcel(parcel, flags); @@ -107,7 +109,12 @@ public final class ProviderRequest implements Parcelable { s.append("ON"); s.append(" interval="); TimeUtils.formatDuration(interval, s); - s.append(" lowPowerMode=" + lowPowerMode); + if (lowPowerMode) { + s.append(" lowPowerMode"); + } + if (locationSettingsIgnored) { + s.append(" locationSettingsIgnored"); + } } else { s.append("OFF"); } diff --git a/location/lib/api/current.txt b/location/lib/api/current.txt index 67d64965ac96..dbb581fe54b2 100644 --- a/location/lib/api/current.txt +++ b/location/lib/api/current.txt @@ -31,6 +31,7 @@ package com.android.location.provider { method public long getInterval(); method public int getQuality(); method public float getSmallestDisplacement(); + method public boolean isLocationSettingsIgnored(); field public static final int ACCURACY_BLOCK = 102; // 0x66 field public static final int ACCURACY_CITY = 104; // 0x68 field public static final int ACCURACY_FINE = 100; // 0x64 @@ -44,10 +45,10 @@ package com.android.location.provider { } public final class ProviderRequestUnbundled { - method public boolean getForceLocation(); method public long getInterval(); method public java.util.List<com.android.location.provider.LocationRequestUnbundled> getLocationRequests(); method public boolean getReportLocation(); + method public boolean isLocationSettingsIgnored(); } } diff --git a/location/lib/java/com/android/location/provider/LocationRequestUnbundled.java b/location/lib/java/com/android/location/provider/LocationRequestUnbundled.java index 41fd769b5abd..2511c39caf5c 100644 --- a/location/lib/java/com/android/location/provider/LocationRequestUnbundled.java +++ b/location/lib/java/com/android/location/provider/LocationRequestUnbundled.java @@ -121,6 +121,15 @@ public final class LocationRequestUnbundled { return delegate.getSmallestDisplacement(); } + /** + * Returns true if location settings will be ignored in order to satisfy this request. + * + * @return true if location settings will be ignored in order to satisfy this request + */ + public boolean isLocationSettingsIgnored() { + return delegate.isLocationSettingsIgnored(); + } + @Override public String toString() { return delegate.toString(); diff --git a/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java b/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java index b825b58cd3e9..febbf1b23e0c 100644 --- a/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java +++ b/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java @@ -46,15 +46,15 @@ public final class ProviderRequestUnbundled { return mRequest.interval; } - public boolean getForceLocation() { - return mRequest.forceLocation; + public boolean isLocationSettingsIgnored() { + return mRequest.locationSettingsIgnored; } /** * Never null. */ public List<LocationRequestUnbundled> getLocationRequests() { - List<LocationRequestUnbundled> result = new ArrayList<LocationRequestUnbundled>( + List<LocationRequestUnbundled> result = new ArrayList<>( mRequest.locationRequests.size()); for (LocationRequest r : mRequest.locationRequests) { result.add(new LocationRequestUnbundled(r)); diff --git a/media/Android.bp b/media/Android.bp index 141d415cbaf3..86dc509501a4 100644 --- a/media/Android.bp +++ b/media/Android.bp @@ -3,7 +3,6 @@ java_library { srcs: [ ":updatable-media-srcs", - ":framework-media-annotation-srcs", ], aidl: { @@ -28,7 +27,12 @@ java_library { installable: true, // Make sure that the implementaion only relies on SDK or system APIs. - sdk_version: "system_current", + no_framework_libs: true, + libs: [ + // The order matters. android_system_* library should come later. + "framework_media_annotation", + "android_system_stubs_current", + ], } filegroup { @@ -125,3 +129,8 @@ java_library { sdk_version: "28", } +java_library { + name: "framework_media_annotation", + srcs: [":framework-media-annotation-srcs"], + installable: false, +} diff --git a/media/apex/java/android/media/MediaController2.java b/media/apex/java/android/media/MediaController2.java index 887b4475a4d1..e85d99774986 100644 --- a/media/apex/java/android/media/MediaController2.java +++ b/media/apex/java/android/media/MediaController2.java @@ -425,7 +425,7 @@ public class MediaController2 implements AutoCloseable { public void onDisconnected(@NonNull MediaController2 controller) {} /** - * Called when the playback of the session's playback activeness is changed. + * Called when the session's playback activeness is changed. * * @param controller the controller for this event * @param playbackActive {@code true} if the session's playback is active. diff --git a/media/apex/java/android/media/MediaPlayer2.java b/media/apex/java/android/media/MediaPlayer2.java index c38a83131a6c..89a954061958 100644 --- a/media/apex/java/android/media/MediaPlayer2.java +++ b/media/apex/java/android/media/MediaPlayer2.java @@ -45,8 +45,8 @@ import android.util.Size; import android.view.Surface; import android.view.SurfaceHolder; -import com.android.framework.protobuf.InvalidProtocolBufferException; import com.android.internal.annotations.GuardedBy; +import com.android.media.protobuf.InvalidProtocolBufferException; import java.io.ByteArrayOutputStream; import java.io.File; diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index fb18c3b67480..e4d356b48f6d 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -1180,13 +1180,8 @@ public class MediaPlayer extends PlayerBase } final File file = new File(path); - if (file.exists()) { - FileInputStream is = new FileInputStream(file); - FileDescriptor fd = is.getFD(); - setDataSource(fd); - is.close(); - } else { - throw new IOException("setDataSource failed."); + try (FileInputStream is = new FileInputStream(file)) { + setDataSource(is.getFD()); } } @@ -2868,15 +2863,9 @@ public class MediaPlayer extends PlayerBase throw new IllegalArgumentException(msg); } - File file = new File(path); - if (file.exists()) { - FileInputStream is = new FileInputStream(file); - FileDescriptor fd = is.getFD(); - addTimedTextSource(fd, mimeType); - is.close(); - } else { - // We do not support the case where the path is not a file. - throw new IOException(path); + final File file = new File(path); + try (FileInputStream is = new FileInputStream(file)) { + addTimedTextSource(is.getFD(), mimeType); } } diff --git a/media/java/android/media/session/ControllerLink.java b/media/java/android/media/session/ControllerLink.java index 64d283f168e1..40c716607697 100644 --- a/media/java/android/media/session/ControllerLink.java +++ b/media/java/android/media/session/ControllerLink.java @@ -493,6 +493,22 @@ public final class ControllerLink implements Parcelable { } /** + * Tell system that a controller requests changing the playback speed. + * + * @param packageName the package name of the controller + * @param caller the {@link ControllerCallbackLink} of the controller + * @param speed the playback speed + */ + void setPlaybackSpeed(@NonNull String packageName, @NonNull ControllerCallbackLink caller, + float speed) { + try { + mISessionController.setPlaybackSpeed(packageName, caller, speed); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** * Tell system that a controller sends a custom action. * * @param packageName the package name of the controller @@ -759,6 +775,11 @@ public final class ControllerLink implements Parcelable { @NonNull Rating rating) { } + /** Stub method for ISessionController.setPlaybackSpeed */ + public void setPlaybackSpeed(@NonNull String packageName, + @NonNull ControllerCallbackLink caller, float speed) { + } + /** Stub method for ISessionController.sendCustomAction */ public void sendCustomAction(@NonNull String packageName, @NonNull ControllerCallbackLink caller, @NonNull String action, @@ -953,6 +974,12 @@ public final class ControllerLink implements Parcelable { } @Override + public void setPlaybackSpeed(String packageName, ControllerCallbackLink caller, + float speed) { + mControllerStub.setPlaybackSpeed(packageName, caller, speed); + } + + @Override public void sendCustomAction(String packageName, ControllerCallbackLink caller, String action, Bundle args) { mControllerStub.sendCustomAction(packageName, caller, action, args); diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl index 9b86bfced340..cd33c044d142 100644 --- a/media/java/android/media/session/ISessionCallback.aidl +++ b/media/java/android/media/session/ISessionCallback.aidl @@ -60,6 +60,8 @@ oneway interface ISessionCallback { long pos); void notifyRate(String packageName, int pid, int uid, in ControllerCallbackLink caller, in Rating rating); + void notifySetPlaybackSpeed(String packageName, int pid, int uid, + in ControllerCallbackLink caller, float speed); void notifyCustomAction(String packageName, int pid, int uid, in ControllerCallbackLink caller, String action, in Bundle args); diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl index e697c65e11c0..3e7b4fbdebd3 100644 --- a/media/java/android/media/session/ISessionController.aidl +++ b/media/java/android/media/session/ISessionController.aidl @@ -76,6 +76,7 @@ interface ISessionController { void rewind(String packageName, in ControllerCallbackLink caller); void seekTo(String packageName, in ControllerCallbackLink caller, long pos); void rate(String packageName, in ControllerCallbackLink caller, in Rating rating); + void setPlaybackSpeed(String packageName, in ControllerCallbackLink caller, float speed); void sendCustomAction(String packageName, in ControllerCallbackLink caller, String action, in Bundle args); MediaMetadata getMetadata(); diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java index 6e2c8c51c734..9e4199cba47a 100644 --- a/media/java/android/media/session/MediaController.java +++ b/media/java/android/media/session/MediaController.java @@ -865,6 +865,19 @@ public final class MediaController { } /** + * Set the playback speed. + * + * @param speed The playback speed + */ + public void setPlaybackSpeed(float speed) { + try { + mSessionBinder.setPlaybackSpeed(mContext.getPackageName(), mCbStub, speed); + } catch (RuntimeException e) { + Log.wtf(TAG, "Error calling setPlaybackSpeed.", e); + } + } + + /** * Send a custom action back for the {@link MediaSession} to perform. * * @param customAction The action to perform. diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index 1b9ebdafd328..8ab893b03a62 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -681,6 +681,19 @@ public final class MediaSession { } /** + * Override to handle the playback speed change. + * To update the new playback speed, create a new {@link PlaybackState} by using {@link + * PlaybackState.Builder#setState(int, long, float)}, and set it with + * {@link #setPlaybackState(PlaybackState)}. + * + * @param speed the playback speed + * @see #setPlaybackState(PlaybackState) + * @see PlaybackState.Builder#setState(int, long, float) + */ + public void onSetPlaybackSpeed(float speed) { + } + + /** * Called when a {@link MediaController} wants a {@link PlaybackState.CustomAction} to be * performed. * diff --git a/media/java/android/media/session/MediaSessionEngine.java b/media/java/android/media/session/MediaSessionEngine.java index e19bdbcf923e..266bf3226c49 100644 --- a/media/java/android/media/session/MediaSessionEngine.java +++ b/media/java/android/media/session/MediaSessionEngine.java @@ -538,6 +538,10 @@ public final class MediaSessionEngine implements AutoCloseable { postToCallback(caller, CallbackMessageHandler.MSG_RATE, rating, null); } + void dispatchSetPlaybackSpeed(RemoteUserInfo caller, float speed) { + postToCallback(caller, CallbackMessageHandler.MSG_SET_PLAYBACK_SPEED, speed, null); + } + void dispatchCustomAction(RemoteUserInfo caller, String action, Bundle args) { postToCallback(caller, CallbackMessageHandler.MSG_CUSTOM_ACTION, action, args); } @@ -871,6 +875,17 @@ public final class MediaSessionEngine implements AutoCloseable { } /** + * Override to handle the playback speed change. + * + * @param speed the playback speed + */ + public void onSetPlaybackSpeed(float speed) { + if (mCallback != null) { + mCallback.onSetPlaybackSpeed(speed); + } + } + + /** * Called when a {@link MediaController} wants a {@link PlaybackState.CustomAction} to be * performed. * @@ -1092,10 +1107,11 @@ public final class MediaSessionEngine implements AutoCloseable { private static final int MSG_REWIND = 17; private static final int MSG_SEEK_TO = 18; private static final int MSG_RATE = 19; - private static final int MSG_CUSTOM_ACTION = 20; - private static final int MSG_ADJUST_VOLUME = 21; - private static final int MSG_SET_VOLUME = 22; - private static final int MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT = 23; + private static final int MSG_SET_PLAYBACK_SPEED = 20; + private static final int MSG_CUSTOM_ACTION = 21; + private static final int MSG_ADJUST_VOLUME = 22; + private static final int MSG_SET_VOLUME = 23; + private static final int MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT = 24; @SuppressWarnings("WeakerAccess") /* synthetic access */ CallbackWrapper mCallbackWrapper; @@ -1186,6 +1202,9 @@ public final class MediaSessionEngine implements AutoCloseable { case MSG_RATE: mCallbackWrapper.onSetRating((Rating) obj); break; + case MSG_SET_PLAYBACK_SPEED: + mCallbackWrapper.onSetPlaybackSpeed((Float) obj); + break; case MSG_CUSTOM_ACTION: mCallbackWrapper.onCustomAction((String) obj, msg.getData()); break; diff --git a/media/java/android/media/session/SessionCallbackLink.java b/media/java/android/media/session/SessionCallbackLink.java index f59a69d6e157..f9fa45a1b619 100644 --- a/media/java/android/media/session/SessionCallbackLink.java +++ b/media/java/android/media/session/SessionCallbackLink.java @@ -462,6 +462,25 @@ public final class SessionCallbackLink implements Parcelable { } /** + * Notify session that a controller requests changing playback speed. + * + * @param packageName the package name of the controller + * @param pid the pid of the controller + * @param uid the uid of the controller + * @param caller the {@link ControllerCallbackLink} of the controller + * @param speed the playback speed + */ + @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL) + public void notifySetPlaybackSpeed(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, float speed) { + try { + mISessionCallback.notifySetPlaybackSpeed(packageName, pid, uid, caller, speed); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** * Notify session that a controller sends a custom action. * * @param packageName the package name of the controller @@ -871,6 +890,23 @@ public final class SessionCallbackLink implements Parcelable { } } + @Override + public void notifySetPlaybackSpeed(String packageName, int pid, int uid, + ControllerCallbackLink caller, float speed) { + ensureMediaControlPermission(); + final long token = Binder.clearCallingIdentity(); + try { + MediaSessionEngine sessionImpl = mSessionImpl.get(); + if (sessionImpl != null) { + sessionImpl.dispatchSetPlaybackSpeed( + createRemoteUserInfo(packageName, pid, uid), speed); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override public void notifyCustomAction(String packageName, int pid, int uid, ControllerCallbackLink caller, String action, Bundle args) { ensureMediaControlPermission(); diff --git a/media/native/midi/Android.bp b/media/native/midi/Android.bp index d069bd2f6284..58317edbea68 100644 --- a/media/native/midi/Android.bp +++ b/media/native/midi/Android.bp @@ -16,7 +16,7 @@ cc_library_shared { name: "libamidi", srcs: [ - "midi.cpp", + "amidi.cpp", ":IMidiDeviceServer.aidl", ], @@ -48,10 +48,10 @@ ndk_headers { from: "include", - to: "amidi", + to: "", - srcs: ["include/midi.h"], - license: "include/NOTICE", + srcs: ["include/amidi/AMidi.h"], + license: "include/amidi/NOTICE", } ndk_library { diff --git a/media/native/midi/midi.cpp b/media/native/midi/amidi.cpp index a5bdba8569b8..1e9a194d76c8 100644 --- a/media/native/midi/midi.cpp +++ b/media/native/midi/amidi.cpp @@ -28,8 +28,8 @@ #include "android/media/midi/BpMidiDeviceServer.h" #include "media/MidiDeviceInfo.h" -#include "include/midi.h" -#include "midi_internal.h" +#include "include/amidi/AMidi.h" +#include "amidi_internal.h" using namespace android::media::midi; diff --git a/media/native/midi/midi_internal.h b/media/native/midi/amidi_internal.h index cb3ecce13533..fce85963d217 100644 --- a/media/native/midi/midi_internal.h +++ b/media/native/midi/amidi_internal.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_MEDIA_MIDI_INTERNAL_H_ -#define ANDROID_MEDIA_MIDI_INTERNAL_H_ +#ifndef ANDROID_MEDIA_AMIDI_INTERNAL_H_ +#define ANDROID_MEDIA_AMIDI_INTERNAL_H_ #include <jni.h> @@ -38,4 +38,4 @@ struct AMidiDevice { AMidiDeviceInfo deviceInfo; /* Attributes of the device. */ }; -#endif // ANDROID_MEDIA_MIDI_INTERNAL_H_ +#endif // ANDROID_MEDIA_AMIDI_INTERNAL_H_ diff --git a/media/native/midi/include/midi.h b/media/native/midi/include/amidi/AMidi.h index 755d09fc4ff2..0d60b0dd63d4 100644 --- a/media/native/midi/include/midi.h +++ b/media/native/midi/include/amidi/AMidi.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_MEDIA_MIDI_H_ -#define ANDROID_MEDIA_MIDI_H_ +#ifndef ANDROID_MEDIA_AMIDI_H_ +#define ANDROID_MEDIA_AMIDI_H_ #include <stdarg.h> #include <stdint.h> @@ -66,9 +66,9 @@ enum { * @param outDevicePtrPtr Points to the pointer to receive the AMidiDevice * * @return AMEDIA_OK on success, or a negative error value: - * @see AMEDIA_ERROR_INVALID_OBJECT {@link AMEDIA_ERROR_INVALID_OBJECT} - the midiDeviceObj + * @see AMEDIA_ERROR_INVALID_OBJECT - the midiDeviceObj * is null or already connected to a native AMidiDevice - * @see AMEDIA_ERROR_UNKNOWN {@link AMEDIA_ERROR_UNKNOWN} - an unknown error occurred. + * @see AMEDIA_ERROR_UNKNOWN - an unknown error occurred. */ media_status_t AMIDI_API AMidiDevice_fromJava( JNIEnv *env, jobject midiDeviceObj, AMidiDevice **outDevicePtrPtr) __INTRODUCED_IN(29); @@ -80,13 +80,10 @@ media_status_t AMIDI_API AMidiDevice_fromJava( * * @return AMEDIA_OK on success, * or a negative error value: - * @see AMEDIA_ERROR_INVALID_PARAMETER {@link AMEDIA_ERROR_INVALID_PARAMETER} - * - the device parameter is NULL. - * @see AMEDIA_ERROR_INVALID_OBJECT {@link AMEDIA_ERROR_INVALID_OBJECT} - * - the device is not consistent with the associated Java MidiDevice. - * @see AMEDIA_ERROR_INVALID_OBJECT {@link AMEDIA_ERROR_INVALID_OBJECT} - * - the JNI interface initialization to the associated java MidiDevice failed. - * @see AMEDIA_ERROR_UNKNOWN {@link AMEDIA_ERROR_UNKNOWN} - couldn't retrieve the device info. + * @see AMEDIA_ERROR_INVALID_PARAMETER - the device parameter is NULL. + * @see AMEDIA_ERROR_INVALID_OBJECT - the device is not consistent with the associated Java MidiDevice. + * @see AMEDIA_ERROR_INVALID_OBJECT - the JNI interface initialization to the associated java MidiDevice failed. + * @see AMEDIA_ERROR_UNKNOWN - couldn't retrieve the device info. */ media_status_t AMIDI_API AMidiDevice_release(const AMidiDevice *midiDevice) __INTRODUCED_IN(29); @@ -100,9 +97,8 @@ media_status_t AMIDI_API AMidiDevice_release(const AMidiDevice *midiDevice) __IN * AMIDI_DEVICE_TYPE_VIRTUAL * AMIDI_DEVICE_TYPE_BLUETOOTH * or a negative error value: - * @see AMEDIA_ERROR_INVALID_PARAMETER {@link AMEDIA_ERROR_INVALID_PARAMETER} - the device - * parameter is NULL. - * @see AMEDIA_ERROR_UNKNOWN {@link AMEDIA_ERROR_UNKNOWN} - Unknown error. + * @see AMEDIA_ERROR_INVALID_PARAMETER - the device parameter is NULL. + * @see AMEDIA_ERROR_UNKNOWN - Unknown error. */ int32_t AMIDI_API AMidiDevice_getType(const AMidiDevice *device) __INTRODUCED_IN(29); @@ -113,9 +109,8 @@ int32_t AMIDI_API AMidiDevice_getType(const AMidiDevice *device) __INTRODUCED_IN * * @return If successful, returns the number of MIDI input (sending) ports available on the * device. If an error occurs, returns a negative value indicating the error: - * @see AMEDIA_ERROR_INVALID_PARAMETER {@link AMEDIA_ERROR_INVALID_PARAMETER} - the device - * parameter is NULL. - * @see AMEDIA_ERROR_UNKNOWN {@link AMEDIA_ERROR_UNKNOWN} - couldn't retrieve the device info. + * @see AMEDIA_ERROR_INVALID_PARAMETER - the device parameter is NULL. + * @see AMEDIA_ERROR_UNKNOWN - couldn't retrieve the device info. */ ssize_t AMIDI_API AMidiDevice_getNumInputPorts(const AMidiDevice *device) __INTRODUCED_IN(29); @@ -126,9 +121,8 @@ ssize_t AMIDI_API AMidiDevice_getNumInputPorts(const AMidiDevice *device) __INTR * * @return If successful, returns the number of MIDI output (receiving) ports available on the * device. If an error occurs, returns a negative value indicating the error: - * @see AMEDIA_ERROR_INVALID_PARAMETER {@link AMEDIA_ERROR_INVALID_PARAMETER} - the device - * parameter is NULL. - * @see AMEDIA_ERROR_UNKNOWN {@link AMEDIA_ERROR_UNKNOWN}- couldn't retrieve the device info. + * @see AMEDIA_ERROR_INVALID_PARAMETER - the device parameter is NULL. + * @see AMEDIA_ERROR_UNKNOWN - couldn't retrieve the device info. */ ssize_t AMIDI_API AMidiDevice_getNumOutputPorts(const AMidiDevice *device) __INTRODUCED_IN(29); @@ -146,7 +140,7 @@ ssize_t AMIDI_API AMidiDevice_getNumOutputPorts(const AMidiDevice *device) __INT * @param outOutputPortPtr Receives the native API port identifier of the opened port. * * @return AMEDIA_OK, or a negative error code: - * @see AMEDIA_ERROR_UNKNOWN {@link AMEDIA_ERROR_UNKNOWN} - Unknown Error. + * @see AMEDIA_ERROR_UNKNOWN - Unknown Error. */ media_status_t AMIDI_API AMidiOutputPort_open(const AMidiDevice *device, int32_t portNumber, AMidiOutputPort **outOutputPortPtr) __INTRODUCED_IN(29); @@ -174,7 +168,7 @@ void AMIDI_API AMidiOutputPort_close(const AMidiOutputPort *outputPort) __INTROD * (the current value of the running Java Virtual Machine's high-resolution time source, * in nanoseconds) * @return the number of messages received (either 0 or 1), or a negative error code: - * @see AMEDIA_ERROR_UNKNOWN {@link AMEDIA_ERROR_UNKNOWN} - Unknown Error. + * @see AMEDIA_ERROR_UNKNOWN - Unknown Error. */ ssize_t AMIDI_API AMidiOutputPort_receive(const AMidiOutputPort *outputPort, int32_t *opcodePtr, uint8_t *buffer, size_t maxBytes, size_t* numBytesReceivedPtr, int64_t *outTimestampPtr) __INTRODUCED_IN(29); @@ -193,7 +187,7 @@ ssize_t AMIDI_API AMidiOutputPort_receive(const AMidiOutputPort *outputPort, int * @param outInputPortPtr Receives the native API port identifier of the opened port. * * @return AMEDIA_OK, or a negative error code: - * @see AMEDIA_ERROR_UNKNOWN {@link AMEDIA_ERROR_UNKNOWN} - Unknown Error. + * @see AMEDIA_ERROR_UNKNOWN - Unknown Error. */ media_status_t AMIDI_API AMidiInputPort_open(const AMidiDevice *device, int32_t portNumber, AMidiInputPort **outInputPortPtr) __INTRODUCED_IN(29); @@ -206,8 +200,7 @@ media_status_t AMIDI_API AMidiInputPort_open(const AMidiDevice *device, int32_t * @param numBytes Specifies the number of bytes to write. * * @return The number of bytes sent, which could be less than specified or a negative error code: - * @see AMEDIA_ERROR_INVALID_PARAMETER {@link AMEDIA_ERROR_INVALID_PARAMETER} The specified port - * was NULL, the specified buffer was NULL. + * @see AMEDIA_ERROR_INVALID_PARAMETER - The specified port was NULL, the specified buffer was NULL. */ ssize_t AMIDI_API AMidiInputPort_send(const AMidiInputPort *inputPort, const uint8_t *buffer, size_t numBytes) __INTRODUCED_IN(29); @@ -221,8 +214,7 @@ ssize_t AMIDI_API AMidiInputPort_send(const AMidiInputPort *inputPort, const uin * @param timestamp The CLOCK_MONOTONIC time in nanoseconds to associate with the sent data. * * @return The number of bytes sent, which could be less than specified or a negative error code: - * @see AMEDIA_ERROR_INVALID_PARAMETER {@link AMEDIA_ERROR_INVALID_PARAMETER} The specified port - * was NULL, the specified buffer was NULL. + * @see AMEDIA_ERROR_INVALID_PARAMETER - The specified port was NULL, the specified buffer was NULL. */ ssize_t AMIDI_API AMidiInputPort_sendWithTimestamp(const AMidiInputPort *inputPort, const uint8_t *buffer, size_t numBytes, int64_t timestamp) __INTRODUCED_IN(29); @@ -233,10 +225,9 @@ ssize_t AMIDI_API AMidiInputPort_sendWithTimestamp(const AMidiInputPort *inputPo * * @param inputPort The identifier of the port to send the flush command to. * - * @returns @see AMEDIA_OK {@link AMEDIA_OK} if successful, otherwise a negative error code: - * @see AMEDIA_ERROR_INVALID_PARAMETER {@link AMEDIA_ERROR_INVALID_PARAMETER} The specified port - * was NULL - * @see AMEDIA_ERROR_UNSUPPORTED {@link AMEDIA_ERROR_UNSUPPORTED} The FLUSH command couldn't + * @returns @see AMEDIA_OK if successful, otherwise a negative error code: + * @see AMEDIA_ERROR_INVALID_PARAMETER - The specified port was NULL + * @see AMEDIA_ERROR_UNSUPPORTED - The FLUSH command couldn't * be sent. */ media_status_t AMIDI_API AMidiInputPort_sendFlush(const AMidiInputPort *inputPort) __INTRODUCED_IN(29); @@ -252,4 +243,4 @@ void AMIDI_API AMidiInputPort_close(const AMidiInputPort *inputPort) __INTRODUCE } #endif -#endif /* ANDROID_MEDIA_MIDI_H_ */ +#endif /* ANDROID_MEDIA_AMIDI_H_ */ diff --git a/media/native/midi/include/NOTICE b/media/native/midi/include/amidi/NOTICE index e72ff94a8401..e72ff94a8401 100644 --- a/media/native/midi/include/NOTICE +++ b/media/native/midi/include/amidi/NOTICE diff --git a/media/proto/jarjar-rules.txt b/media/proto/jarjar-rules.txt index 7be6e732d3fd..bfb0b2782486 100644 --- a/media/proto/jarjar-rules.txt +++ b/media/proto/jarjar-rules.txt @@ -1,2 +1,2 @@ -rule com.google.protobuf.** com.android.framework.protobuf.@1 +rule com.google.protobuf.** com.android.media.protobuf.@1 diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index 730c409a91fb..a3db2d6a5055 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -146,6 +146,7 @@ LIBANDROID { AHardwareBuffer_getNativeHandle; # introduced=26 AHardwareBuffer_isSupported; # introduced=29 AHardwareBuffer_lock; # introduced=26 + AHardwareBuffer_lockPlanes; # introduced=29 AHardwareBuffer_recvHandleFromUnixSocket; # introduced=26 AHardwareBuffer_release; # introduced=26 AHardwareBuffer_sendHandleToUnixSocket; # introduced=26 diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java index ce627ce47717..a288d010e59b 100644 --- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java +++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java @@ -175,6 +175,7 @@ public class CaptivePortalLoginActivity extends Activity { webSettings.setSupportZoom(true); webSettings.setBuiltInZoomControls(true); webSettings.setDisplayZoomControls(false); + webSettings.setDomStorageEnabled(true); mWebViewClient = new MyWebViewClient(); webview.setWebViewClient(mWebViewClient); webview.setWebChromeClient(new MyWebChromeClient()); diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index 2dba1d5bc257..c5a951ca5249 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -221,30 +221,6 @@ public class CarStatusBar extends StatusBar implements } } - @Override - public void destroy() { - mCarBatteryController.stopListening(); - mConnectedDeviceSignalController.stopListening(); - mActivityManagerWrapper.unregisterTaskStackListener(mTaskStackListener); - mDrivingStateHelper.disconnectFromCarService(); - - if (mNavigationBarWindow != null) { - mWindowManager.removeViewImmediate(mNavigationBarWindow); - mNavigationBarView = null; - } - - if (mLeftNavigationBarWindow != null) { - mWindowManager.removeViewImmediate(mLeftNavigationBarWindow); - mLeftNavigationBarView = null; - } - - if (mRightNavigationBarWindow != null) { - mWindowManager.removeViewImmediate(mRightNavigationBarWindow); - mRightNavigationBarView = null; - } - super.destroy(); - } - @Override protected void makeStatusBarView() { diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java index f36b4aa87636..55c9361ce6c4 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java @@ -106,6 +106,7 @@ public class CaptivePortalLoginActivity extends Activity { webSettings.setLoadWithOverviewMode(true); webSettings.setSupportZoom(true); webSettings.setBuiltInZoomControls(true); + webSettings.setDomStorageEnabled(true); mWebViewClient = new MyWebViewClient(); mWebView.setWebViewClient(mWebViewClient); mWebView.setWebChromeClient(new MyWebChromeClient()); diff --git a/packages/DynamicAndroidInstallationService/Android.mk b/packages/DynamicAndroidInstallationService/Android.mk new file mode 100644 index 000000000000..13d96ac3906e --- /dev/null +++ b/packages/DynamicAndroidInstallationService/Android.mk @@ -0,0 +1,19 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, res) + +LOCAL_USE_AAPT2 := true + +LOCAL_PACKAGE_NAME := DynamicAndroidInstallationService +LOCAL_CERTIFICATE := platform +LOCAL_PRIVILEGED_MODULE := true +LOCAL_PRIVATE_PLATFORM_APIS := true + +LOCAL_PROGUARD_ENABLED := disabled + + +include $(BUILD_PACKAGE) diff --git a/packages/DynamicAndroidInstallationService/AndroidManifest.xml b/packages/DynamicAndroidInstallationService/AndroidManifest.xml new file mode 100644 index 000000000000..1c1c72c46206 --- /dev/null +++ b/packages/DynamicAndroidInstallationService/AndroidManifest.xml @@ -0,0 +1,47 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.dynandroid" + android:sharedUserId="android.uid.system"> + + <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.MANAGE_DYNAMNIC_ANDROID" /> + <uses-permission android:name="android.permission.REBOOT" /> + <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> + + <application + android:allowBackup="false" + android:label="@string/app_name"> + + <service + android:name=".DynamicAndroidInstallationService" + android:enabled="true" + android:exported="true" + android:permission="android.permission.MANAGE_DYNAMNIC_ANDROID" + android:process=":dynandroid"> + <intent-filter> + <action android:name="android.content.action.NOTIFY_IF_IN_USE" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </service> + + <activity android:name=".VerificationActivity" + android:exported="true" + android:permission="android.permission.MANAGE_DYNAMNIC_ANDROID" + android:theme="@android:style/Theme.Material.Light.Dialog.NoActionBar" + android:process=":dynandroid"> + <intent-filter> + <action android:name="android.content.action.START_INSTALL" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> + + <receiver + android:name=".BootCompletedReceiver" + android:enabled="true" + android:exported="false"> + <intent-filter> + <action android:name="android.intent.action.BOOT_COMPLETED" /> + </intent-filter> + </receiver> + </application> +</manifest> diff --git a/packages/DynamicAndroidInstallationService/MODULE_LICENSE_APACHE2 b/packages/DynamicAndroidInstallationService/MODULE_LICENSE_APACHE2 new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/packages/DynamicAndroidInstallationService/MODULE_LICENSE_APACHE2 diff --git a/packages/DynamicAndroidInstallationService/NOTICE b/packages/DynamicAndroidInstallationService/NOTICE new file mode 100644 index 000000000000..c5b1efa7aac7 --- /dev/null +++ b/packages/DynamicAndroidInstallationService/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-2008, The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/packages/DynamicAndroidInstallationService/res/drawable/ic_system_update_googblue_24dp.xml b/packages/DynamicAndroidInstallationService/res/drawable/ic_system_update_googblue_24dp.xml new file mode 100644 index 000000000000..acf1567ab7fe --- /dev/null +++ b/packages/DynamicAndroidInstallationService/res/drawable/ic_system_update_googblue_24dp.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M17,1.01L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19L7,19L7,5h10v14zM16,13h-3L13,8h-2v5L8,13l4,4 4,-4z" + android:fillColor="#4285F4"/> +</vector> diff --git a/packages/DynamicAndroidInstallationService/res/values/strings.xml b/packages/DynamicAndroidInstallationService/res/values/strings.xml new file mode 100644 index 000000000000..221e1d75b133 --- /dev/null +++ b/packages/DynamicAndroidInstallationService/res/values/strings.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <!-- application name [CHAR LIMIT=32] --> + <string name="app_name">AndroidOnTap Installer</string> + + <!-- notification channel name [CHAR LIMIT=32] --> + <string name="notification_channel_name">AndroidOnTap Installer</string> + + <!-- password page title [CHAR LIMIT=32] --> + <string name="keyguard_title">AndroidOnTap Installer</string> + + <!-- password page description [CHAR LIMIT=128] --> + <string name="keyguard_description">Please enter your password and continue to AndroidOnTap installation</string> + + <!-- Displayed on notification: DynAndroid installation is completed [CHAR LIMIT=128] --> + <string name="notification_install_completed">Installation is completed, you can reboot into the new installed system now.</string> + <!-- Displayed on notification: DynAndroid installation is in progress [CHAR LIMIT=128] --> + <string name="notification_install_inprogress">Installation is in progress.</string> + <!-- Displayed on notification: DynAndroid installation is in progress [CHAR LIMIT=128] --> + <string name="notification_install_failed">Installation Failed.</string> + <!-- Displayed on notification: We are running in AndroidOnTap [CHAR LIMIT=128] --> + <string name="notification_dynandroid_in_use">We are running in AndroidOnTap.</string> + + <!-- Action on notification: Cancel installation [CHAR LIMIT=16] --> + <string name="notification_action_cancel">Cancel</string> + <!-- Action on notification: Discard installation [CHAR LIMIT=16] --> + <string name="notification_action_discard">Discard</string> + <!-- Action on notification: Uninstall AndroidOnTap [CHAR LIMIT=16] --> + <string name="notification_action_uninstall">Uninstall</string> + <!-- Action on notification: Reboot to AndroidOnTap [CHAR LIMIT=16] --> + <string name="notification_action_reboot_to_dynandroid">Reboot</string> + +</resources> diff --git a/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/BootCompletedReceiver.java b/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/BootCompletedReceiver.java new file mode 100644 index 000000000000..dd1be897b2ea --- /dev/null +++ b/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/BootCompletedReceiver.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dynandroid; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DynamicAndroidClient; +import android.content.Intent; +import android.os.UserHandle; +import android.util.Log; + + +/** + * A BoardcastReceiver waiting for ACTION_BOOT_COMPLETED and ask + * the service to display a notification if we are currently running + * in DynamicAndroid. + */ +public class BootCompletedReceiver extends BroadcastReceiver { + + private static final String TAG = "BootCompletedReceiver"; + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + + Log.d(TAG, "Broadcast received: " + action); + + if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { + Intent startServiceIntent = new Intent( + context, DynamicAndroidInstallationService.class); + + startServiceIntent.setAction(DynamicAndroidClient.ACTION_NOTIFY_IF_IN_USE); + context.startServiceAsUser(startServiceIntent, UserHandle.SYSTEM); + } + } +} diff --git a/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/DynamicAndroidInstallationService.java b/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/DynamicAndroidInstallationService.java new file mode 100644 index 000000000000..7755cbc9ca3b --- /dev/null +++ b/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/DynamicAndroidInstallationService.java @@ -0,0 +1,483 @@ +/* + * 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.dynandroid; + +import static android.content.DynamicAndroidClient.ACTION_NOTIFY_IF_IN_USE; +import static android.content.DynamicAndroidClient.ACTION_START_INSTALL; +import static android.content.DynamicAndroidClient.CAUSE_ERROR_EXCEPTION; +import static android.content.DynamicAndroidClient.CAUSE_ERROR_INVALID_URL; +import static android.content.DynamicAndroidClient.CAUSE_ERROR_IO; +import static android.content.DynamicAndroidClient.CAUSE_INSTALL_CANCELLED; +import static android.content.DynamicAndroidClient.CAUSE_INSTALL_COMPLETED; +import static android.content.DynamicAndroidClient.CAUSE_NOT_SPECIFIED; +import static android.content.DynamicAndroidClient.STATUS_IN_PROGRESS; +import static android.content.DynamicAndroidClient.STATUS_IN_USE; +import static android.content.DynamicAndroidClient.STATUS_NOT_STARTED; +import static android.content.DynamicAndroidClient.STATUS_READY; +import static android.os.AsyncTask.Status.FINISHED; +import static android.os.AsyncTask.Status.PENDING; +import static android.os.AsyncTask.Status.RUNNING; + +import static com.android.dynandroid.InstallationAsyncTask.RESULT_ERROR_EXCEPTION; +import static com.android.dynandroid.InstallationAsyncTask.RESULT_ERROR_INVALID_URL; +import static com.android.dynandroid.InstallationAsyncTask.RESULT_ERROR_IO; +import static com.android.dynandroid.InstallationAsyncTask.RESULT_OK; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.DynamicAndroidClient; +import android.content.Intent; +import android.os.Bundle; +import android.os.DynamicAndroidManager; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.PowerManager; +import android.os.RemoteException; +import android.util.Log; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; + +/** + * This class is the service in charge of DynamicAndroid installation. + * It also posts status to notification bar and wait for user's + * cancel and confirm commnands. + */ +public class DynamicAndroidInstallationService extends Service + implements InstallationAsyncTask.InstallStatusListener { + + private static final String TAG = "DynAndroidInstallationService"; + + /* + * Intent actions + */ + private static final String ACTION_CANCEL_INSTALL = + "com.android.dynandroid.ACTION_CANCEL_INSTALL"; + private static final String ACTION_REBOOT_TO_DYN_ANDROID = + "com.android.dynandroid.ACTION_REBOOT_TO_DYN_ANDROID"; + private static final String ACTION_REBOOT_TO_NORMAL = + "com.android.dynandroid.ACTION_REBOOT_TO_NORMAL"; + + /* + * For notification + */ + private static final String NOTIFICATION_CHANNEL_ID = "com.android.dynandroid"; + private static final int NOTIFICATION_ID = 1; + + /* + * IPC + */ + /** Keeps track of all current registered clients. */ + ArrayList<Messenger> mClients = new ArrayList<>(); + + /** Handler of incoming messages from clients. */ + final Messenger mMessenger = new Messenger(new IncomingHandler(this)); + + static class IncomingHandler extends Handler { + private final WeakReference<DynamicAndroidInstallationService> mWeakService; + + IncomingHandler(DynamicAndroidInstallationService service) { + mWeakService = new WeakReference<>(service); + } + + @Override + public void handleMessage(Message msg) { + DynamicAndroidInstallationService service = mWeakService.get(); + + if (service != null) { + service.handleMessage(msg); + } + } + } + + private DynamicAndroidManager mDynAndroid; + private NotificationManager mNM; + + private long mSystemSize; + private long mInstalledSize; + private boolean mJustCancelledByUser; + + private PendingIntent mPiCancel; + private PendingIntent mPiRebootToDynamicAndroid; + private PendingIntent mPiUninstallAndReboot; + + private InstallationAsyncTask mInstallTask; + + + @Override + public void onCreate() { + super.onCreate(); + + prepareNotification(); + + mDynAndroid = (DynamicAndroidManager) getSystemService(Context.DYNAMIC_ANDROID_SERVICE); + } + + @Override + public void onDestroy() { + // Cancel the persistent notification. + mNM.cancel(NOTIFICATION_ID); + } + + @Override + public IBinder onBind(Intent intent) { + return mMessenger.getBinder(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + String action = intent.getAction(); + + Log.d(TAG, "onStartCommand(): action=" + action); + + if (ACTION_START_INSTALL.equals(action)) { + executeInstallCommand(intent); + } else if (ACTION_CANCEL_INSTALL.equals(action)) { + executeCancelCommand(); + } else if (ACTION_REBOOT_TO_DYN_ANDROID.equals(action)) { + executeRebootToDynAndroidCommand(); + } else if (ACTION_REBOOT_TO_NORMAL.equals(action)) { + executeRebootToNormalCommand(); + } else if (ACTION_NOTIFY_IF_IN_USE.equals(action)) { + executeNotifyIfInUseCommand(); + } + + return Service.START_NOT_STICKY; + } + + @Override + public void onProgressUpdate(long installedSize) { + mInstalledSize = installedSize; + postStatus(STATUS_IN_PROGRESS, CAUSE_NOT_SPECIFIED); + } + + @Override + public void onResult(int result) { + if (result == RESULT_OK) { + postStatus(STATUS_READY, CAUSE_INSTALL_COMPLETED); + return; + } + + // if it's not successful, reset the task and stop self. + resetTaskAndStop(); + + switch (result) { + case RESULT_ERROR_IO: + postStatus(STATUS_NOT_STARTED, CAUSE_ERROR_IO); + break; + + case RESULT_ERROR_INVALID_URL: + postStatus(STATUS_NOT_STARTED, CAUSE_ERROR_INVALID_URL); + break; + + case RESULT_ERROR_EXCEPTION: + postStatus(STATUS_NOT_STARTED, CAUSE_ERROR_EXCEPTION); + break; + } + } + + @Override + public void onCancelled() { + resetTaskAndStop(); + postStatus(STATUS_NOT_STARTED, CAUSE_INSTALL_CANCELLED); + } + + private void executeInstallCommand(Intent intent) { + if (!verifyRequest(intent)) { + Log.e(TAG, "Verification failed. Did you use VerificationActivity?"); + return; + } + + if (mInstallTask != null) { + Log.e(TAG, "There is already an install task running"); + return; + } + + if (isInDynamicAndroid()) { + Log.e(TAG, "We are already running in DynamicAndroid"); + return; + } + + String url = intent.getStringExtra(DynamicAndroidClient.KEY_SYSTEM_URL); + mSystemSize = intent.getLongExtra(DynamicAndroidClient.KEY_SYSTEM_SIZE, 0); + long userdata = intent.getLongExtra(DynamicAndroidClient.KEY_USERDATA_SIZE, 0); + + mInstallTask = new InstallationAsyncTask(url, mSystemSize, userdata, mDynAndroid, this); + mInstallTask.execute(); + + // start fore ground + startForeground(NOTIFICATION_ID, + buildNotification(STATUS_IN_PROGRESS, CAUSE_NOT_SPECIFIED)); + } + + private void executeCancelCommand() { + if (mInstallTask == null || mInstallTask.getStatus() == PENDING) { + Log.e(TAG, "Cancel command triggered, but there is no task running"); + mNM.cancel(NOTIFICATION_ID); + + return; + } + + mJustCancelledByUser = true; + + if (mInstallTask.cancel(false)) { + // Will cleanup and post status in onCancelled() + Log.d(TAG, "Cancel request filed successfully"); + } else { + Log.d(TAG, "Requested cancel, completed task will be discarded"); + + resetTaskAndStop(); + postStatus(STATUS_NOT_STARTED, CAUSE_INSTALL_CANCELLED); + } + + } + + private void executeRebootToDynAndroidCommand() { + if (mInstallTask == null || mInstallTask.getStatus() != FINISHED) { + Log.e(TAG, "Trying to reboot to DynamicAndroid, but there is no complete installation"); + return; + } + + if (!mInstallTask.commit()) { + // TODO: b/123673280 better UI response + Log.e(TAG, "Failed to commit installation because of native runtime error."); + mNM.cancel(NOTIFICATION_ID); + + return; + } + + PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); + + if (powerManager != null) { + powerManager.reboot("dynandroid"); + } + } + + private void executeRebootToNormalCommand() { + mDynAndroid.remove(); + + PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); + + if (powerManager != null) { + powerManager.reboot(null); + } + } + + private void executeNotifyIfInUseCommand() { + if (isInDynamicAndroid()) { + startForeground(NOTIFICATION_ID, + buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED)); + } + } + + private void resetTaskAndStop() { + mInstallTask = null; + + stopForeground(true); + + // stop self, but this service is not destroyed yet if it's still bound + stopSelf(); + } + + private void prepareNotification() { + NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, + getString(R.string.notification_channel_name), + NotificationManager.IMPORTANCE_LOW); + + mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + + if (mNM != null) { + mNM.createNotificationChannel(chan); + } + + Intent intentCancel = new Intent(this, DynamicAndroidInstallationService.class); + intentCancel.setAction(ACTION_CANCEL_INSTALL); + mPiCancel = PendingIntent.getService(this, 0, intentCancel, 0); + + Intent intentRebootToDyn = new Intent(this, DynamicAndroidInstallationService.class); + intentRebootToDyn.setAction(ACTION_REBOOT_TO_DYN_ANDROID); + mPiRebootToDynamicAndroid = PendingIntent.getService(this, 0, intentRebootToDyn, 0); + + Intent intentUninstallAndReboot = new Intent(this, DynamicAndroidInstallationService.class); + intentUninstallAndReboot.setAction(ACTION_REBOOT_TO_NORMAL); + mPiUninstallAndReboot = PendingIntent.getService(this, 0, intentUninstallAndReboot, 0); + } + + private Notification buildNotification(int status, int cause) { + Notification.Builder builder = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID) + .setSmallIcon(R.drawable.ic_system_update_googblue_24dp) + .setProgress(0, 0, false); + + switch (status) { + case STATUS_IN_PROGRESS: + builder.setContentText(getString(R.string.notification_install_inprogress)); + + int max = (int) Math.max(mSystemSize >> 20, 1); + int progress = (int) mInstalledSize >> 20; + + builder.setProgress(max, progress, false); + + builder.addAction(new Notification.Action.Builder( + null, getString(R.string.notification_action_cancel), + mPiCancel).build()); + + break; + + case STATUS_READY: + builder.setContentText(getString(R.string.notification_install_completed)); + + builder.addAction(new Notification.Action.Builder( + null, getString(R.string.notification_action_reboot_to_dynandroid), + mPiRebootToDynamicAndroid).build()); + + builder.addAction(new Notification.Action.Builder( + null, getString(R.string.notification_action_cancel), + mPiCancel).build()); + + break; + + case STATUS_IN_USE: + builder.setContentText(getString(R.string.notification_dynandroid_in_use)); + + builder.addAction(new Notification.Action.Builder( + null, getString(R.string.notification_action_uninstall), + mPiUninstallAndReboot).build()); + + break; + + case STATUS_NOT_STARTED: + if (cause != CAUSE_NOT_SPECIFIED && cause != CAUSE_INSTALL_CANCELLED) { + builder.setContentText(getString(R.string.notification_install_failed)); + } else { + // no need to notify the user if the task is not started, or cancelled. + } + break; + + default: + throw new IllegalStateException("status is invalid"); + } + + return builder.build(); + } + + private boolean verifyRequest(Intent intent) { + String url = intent.getStringExtra(DynamicAndroidClient.KEY_SYSTEM_URL); + + return VerificationActivity.isVerified(url); + } + + private void postStatus(int status, int cause) { + Log.d(TAG, "postStatus(): statusCode=" + status + ", causeCode=" + cause); + + boolean notifyOnNotificationBar = true; + + if (status == STATUS_NOT_STARTED + && cause == CAUSE_INSTALL_CANCELLED + && mJustCancelledByUser) { + // if task is cancelled by user, do not notify them + notifyOnNotificationBar = false; + mJustCancelledByUser = false; + } + + if (notifyOnNotificationBar) { + mNM.notify(NOTIFICATION_ID, buildNotification(status, cause)); + } + + for (int i = mClients.size() - 1; i >= 0; i--) { + try { + notifyOneClient(mClients.get(i), status, cause); + } catch (RemoteException e) { + mClients.remove(i); + } + } + } + + private void notifyOneClient(Messenger client, int status, int cause) throws RemoteException { + Bundle bundle = new Bundle(); + + bundle.putLong(DynamicAndroidClient.KEY_INSTALLED_SIZE, mInstalledSize); + + client.send(Message.obtain(null, + DynamicAndroidClient.MSG_POST_STATUS, status, cause, bundle)); + } + + private int getStatus() { + if (isInDynamicAndroid()) { + return STATUS_IN_USE; + + } else if (mInstallTask == null) { + return STATUS_NOT_STARTED; + + } + + switch (mInstallTask.getStatus()) { + case PENDING: + return STATUS_NOT_STARTED; + + case RUNNING: + return STATUS_IN_PROGRESS; + + case FINISHED: + int result = mInstallTask.getResult(); + + if (result == RESULT_OK) { + return STATUS_READY; + } else { + throw new IllegalStateException("A failed InstallationTask is not reset"); + } + + default: + return STATUS_NOT_STARTED; + } + } + + private boolean isInDynamicAndroid() { + return mDynAndroid.isInUse(); + } + + void handleMessage(Message msg) { + switch (msg.what) { + case DynamicAndroidClient.MSG_REGISTER_LISTENER: + try { + Messenger client = msg.replyTo; + + int status = getStatus(); + + // tell just registered client my status, but do not specify cause + notifyOneClient(client, status, CAUSE_NOT_SPECIFIED); + + mClients.add(client); + } catch (RemoteException e) { + // do nothing if we cannot send update to the client + e.printStackTrace(); + } + + break; + case DynamicAndroidClient.MSG_UNREGISTER_LISTENER: + mClients.remove(msg.replyTo); + break; + default: + // do nothing + } + } +} diff --git a/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/InstallationAsyncTask.java b/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/InstallationAsyncTask.java new file mode 100644 index 000000000000..3c759e948b4e --- /dev/null +++ b/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/InstallationAsyncTask.java @@ -0,0 +1,205 @@ +/* + * 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.dynandroid; + +import android.os.AsyncTask; +import android.os.DynamicAndroidManager; +import android.util.Log; +import android.webkit.URLUtil; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Arrays; +import java.util.Locale; +import java.util.zip.GZIPInputStream; + + +class InstallationAsyncTask extends AsyncTask<String, Long, Integer> { + + private static final String TAG = "InstallationAsyncTask"; + + private static final int READ_BUFFER_SIZE = 1 << 19; + + private class InvalidImageUrlException extends RuntimeException { + private InvalidImageUrlException(String message) { + super(message); + } + } + + + /** Not completed, including being cancelled */ + static final int NO_RESULT = 0; + static final int RESULT_OK = 1; + static final int RESULT_ERROR_IO = 2; + static final int RESULT_ERROR_INVALID_URL = 3; + static final int RESULT_ERROR_EXCEPTION = 6; + + interface InstallStatusListener { + void onProgressUpdate(long installedSize); + void onResult(int resultCode); + void onCancelled(); + } + + private final String mUrl; + private final long mSystemSize; + private final long mUserdataSize; + private final DynamicAndroidManager mDynamicAndroid; + private final InstallStatusListener mListener; + private DynamicAndroidManager.Session mInstallationSession; + + private long mInstalledSize; + private long mReportedInstalledSize; + private int mResult = NO_RESULT; + + private InputStream mStream; + + + InstallationAsyncTask(String url, long systemSize, long userdataSize, + DynamicAndroidManager dynAndroid, InstallStatusListener listener) { + mUrl = url; + mSystemSize = systemSize; + mUserdataSize = userdataSize; + mDynamicAndroid = dynAndroid; + mListener = listener; + } + + @Override + protected void onPreExecute() { + mListener.onProgressUpdate(0); + } + + @Override + protected Integer doInBackground(String... voids) { + Log.d(TAG, "Start doInBackground(), URL: " + mUrl); + + try { + // call start in background + mInstallationSession = mDynamicAndroid.startInstallation(mSystemSize, mUserdataSize); + + if (mInstallationSession == null) { + Log.e(TAG, "Failed to start installation with requested size: " + + (mSystemSize + mUserdataSize)); + + return RESULT_ERROR_IO; + } + + initInputStream(); + + byte[] bytes = new byte[READ_BUFFER_SIZE]; + + int numBytesRead; + long minStepToReport = mSystemSize / 100; + + Log.d(TAG, "Start installation loop"); + while ((numBytesRead = mStream.read(bytes, 0, READ_BUFFER_SIZE)) != -1) { + if (isCancelled()) { + break; + } + + byte[] writeBuffer = numBytesRead == READ_BUFFER_SIZE + ? bytes : Arrays.copyOf(bytes, numBytesRead); + + if (!mInstallationSession.write(writeBuffer)) { + throw new IOException("Failed write() to DynamicAndroid"); + } + + mInstalledSize += numBytesRead; + + if (mInstalledSize > mReportedInstalledSize + minStepToReport) { + publishProgress(mInstalledSize); + mReportedInstalledSize = mInstalledSize; + } + } + + return RESULT_OK; + + } catch (IOException e) { + e.printStackTrace(); + return RESULT_ERROR_IO; + + } catch (InvalidImageUrlException e) { + e.printStackTrace(); + return RESULT_ERROR_INVALID_URL; + + } catch (Exception e) { + e.printStackTrace(); + return RESULT_ERROR_EXCEPTION; + + } finally { + close(); + } + } + + @Override + protected void onCancelled() { + Log.d(TAG, "onCancelled(), URL: " + mUrl); + + close(); + + mListener.onCancelled(); + } + + @Override + protected void onPostExecute(Integer result) { + Log.d(TAG, "onPostExecute(), URL: " + mUrl + ", result: " + result); + + close(); + + mResult = result; + mListener.onResult(mResult); + } + + @Override + protected void onProgressUpdate(Long... values) { + long progress = values[0]; + mListener.onProgressUpdate(progress); + } + + private void initInputStream() throws IOException, InvalidImageUrlException { + if (URLUtil.isNetworkUrl(mUrl) || URLUtil.isFileUrl(mUrl)) { + mStream = new BufferedInputStream(new GZIPInputStream(new URL(mUrl).openStream())); + } else { + throw new InvalidImageUrlException( + String.format(Locale.US, "Unsupported file source: %s", mUrl)); + } + } + + private void close() { + try { + if (mStream != null) { + mStream.close(); + mStream = null; + } + } catch (IOException e) { + // ignore + } + } + + int getResult() { + return mResult; + } + + boolean commit() { + if (mInstallationSession == null) { + return false; + } + + return mInstallationSession.commit(); + } +} diff --git a/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/VerificationActivity.java b/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/VerificationActivity.java new file mode 100644 index 000000000000..c18c4fe689c9 --- /dev/null +++ b/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/VerificationActivity.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dynandroid; + +import static android.content.DynamicAndroidClient.KEY_SYSTEM_SIZE; +import static android.content.DynamicAndroidClient.KEY_SYSTEM_URL; +import static android.content.DynamicAndroidClient.KEY_USERDATA_SIZE; + +import android.app.Activity; +import android.app.KeyguardManager; +import android.content.Context; +import android.content.DynamicAndroidClient; +import android.content.Intent; +import android.os.Bundle; +import android.os.UserHandle; +import android.util.Log; + + +/** + * This Activity starts KeyguardManager and ask the user to confirm + * before any installation request. If the device is not protected by + * a password, it approves the request by default. + */ +public class VerificationActivity extends Activity { + + private static final String TAG = "VerificationActivity"; + + private static final int REQUEST_CODE = 1; + + // For install request verification + private static String sVerifiedUrl; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); + + if (km != null) { + String title = getString(R.string.keyguard_title); + String description = getString(R.string.keyguard_description); + Intent intent = km.createConfirmDeviceCredentialIntent(title, description); + + if (intent == null) { + Log.d(TAG, "This device is not protected by a password/pin"); + startInstallationService(); + finish(); + } else { + startActivityForResult(intent, REQUEST_CODE); + } + } else { + finish(); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) { + startInstallationService(); + } + + finish(); + } + + private void startInstallationService() { + // retrieve data from calling intent + Intent callingIntent = getIntent(); + + String url = callingIntent.getStringExtra(KEY_SYSTEM_URL); + long systemSize = callingIntent.getLongExtra(KEY_SYSTEM_SIZE, 0); + long userdataSize = callingIntent.getLongExtra(KEY_USERDATA_SIZE, 0); + + sVerifiedUrl = url; + + // start service + Intent intent = new Intent(this, DynamicAndroidInstallationService.class); + intent.setAction(DynamicAndroidClient.ACTION_START_INSTALL); + intent.putExtra(KEY_SYSTEM_URL, url); + intent.putExtra(KEY_SYSTEM_SIZE, systemSize); + intent.putExtra(KEY_USERDATA_SIZE, userdataSize); + + Log.d(TAG, "Starting Installation Service"); + startServiceAsUser(intent, UserHandle.SYSTEM); + } + + static boolean isVerified(String url) { + return sVerifiedUrl != null && sVerifiedUrl.equals(url); + } +} diff --git a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java index f372fe55dfb0..24fa87a7f39b 100644 --- a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java +++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java @@ -15,11 +15,10 @@ */ package android.ext.services.notification; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.Notification; import android.app.Person; import android.app.RemoteAction; +import android.app.RemoteInput; import android.content.Context; import android.graphics.drawable.Icon; import android.os.Bundle; @@ -27,7 +26,9 @@ import android.os.Parcelable; import android.os.Process; import android.service.notification.NotificationAssistantService; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.LruCache; +import android.util.Pair; import android.view.textclassifier.ConversationAction; import android.view.textclassifier.ConversationActions; import android.view.textclassifier.TextClassificationContext; @@ -35,6 +36,8 @@ import android.view.textclassifier.TextClassificationManager; import android.view.textclassifier.TextClassifier; import android.view.textclassifier.TextClassifierEvent; +import com.android.internal.util.ArrayUtils; + import java.time.Instant; import java.time.ZoneOffset; import java.time.ZonedDateTime; @@ -43,11 +46,13 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Deque; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; public class SmartActionsHelper { private static final String KEY_ACTION_TYPE = "action_type"; + private static final String KEY_ACTION_SCORE = "action_score"; // If a notification has any of these flags set, it's inelgibile for actions being added. private static final int FLAG_MASK_INELGIBILE_FOR_ACTIONS = Notification.FLAG_ONGOING_EVENT @@ -58,75 +63,136 @@ public class SmartActionsHelper { private static final List<String> HINTS = Collections.singletonList(ConversationActions.Request.HINT_FOR_NOTIFICATION); + private static final ConversationActions EMPTY_CONVERSATION_ACTIONS = + new ConversationActions(Collections.emptyList(), null); private Context mContext; - @Nullable private TextClassifier mTextClassifier; - @NonNull private AssistantSettings mSettings; - private LruCache<String, String> mNotificationKeyToResultIdCache = - new LruCache<>(MAX_RESULT_ID_TO_CACHE); + private LruCache<String, Session> mSessionCache = new LruCache<>(MAX_RESULT_ID_TO_CACHE); SmartActionsHelper(Context context, AssistantSettings settings) { mContext = context; TextClassificationManager textClassificationManager = mContext.getSystemService(TextClassificationManager.class); - if (textClassificationManager != null) { - mTextClassifier = textClassificationManager.getTextClassifier(); - } + mTextClassifier = textClassificationManager.getTextClassifier(); mSettings = settings; } - SmartSuggestions suggest(@NonNull NotificationEntry entry) { + SmartSuggestions suggest(NotificationEntry entry) { // Whenever suggest() is called on a notification, its previous session is ended. - mNotificationKeyToResultIdCache.remove(entry.getSbn().getKey()); + mSessionCache.remove(entry.getSbn().getKey()); boolean eligibleForReplyAdjustment = mSettings.mGenerateReplies && isEligibleForReplyAdjustment(entry); boolean eligibleForActionAdjustment = mSettings.mGenerateActions && isEligibleForActionAdjustment(entry); - List<ConversationAction> conversationActions = + ConversationActions conversationActionsResult = suggestConversationActions( entry, eligibleForReplyAdjustment, eligibleForActionAdjustment); - ArrayList<CharSequence> replies = conversationActions.stream() - .map(ConversationAction::getTextReply) - .filter(textReply -> !TextUtils.isEmpty(textReply)) - .collect(Collectors.toCollection(ArrayList::new)); + String resultId = conversationActionsResult.getId(); + List<ConversationAction> conversationActions = + conversationActionsResult.getConversationActions(); + + ArrayList<CharSequence> replies = new ArrayList<>(); + Map<CharSequence, Float> repliesScore = new ArrayMap<>(); + for (ConversationAction conversationAction : conversationActions) { + CharSequence textReply = conversationAction.getTextReply(); + if (TextUtils.isEmpty(textReply)) { + continue; + } + replies.add(textReply); + repliesScore.put(textReply, conversationAction.getConfidenceScore()); + } ArrayList<Notification.Action> actions = conversationActions.stream() .filter(conversationAction -> conversationAction.getAction() != null) - .map(action -> createNotificationAction(action.getAction(), action.getType())) + .map(action -> createNotificationAction( + action.getAction(), action.getType(), action.getConfidenceScore())) .collect(Collectors.toCollection(ArrayList::new)); + + // Start a new session for logging if necessary. + if (!TextUtils.isEmpty(resultId) + && !conversationActions.isEmpty() + && suggestionsMightBeUsedInNotification( + entry, !actions.isEmpty(), !replies.isEmpty())) { + mSessionCache.put(entry.getSbn().getKey(), new Session(resultId, repliesScore)); + } + return new SmartSuggestions(replies, actions); } /** + * Returns whether the suggestion might be used in the notifications in SysUI. + * <p> + * Currently, NAS has no idea if suggestions will actually be used in the notification, and thus + * this function tries to make a heuristic. This function tries to optimize the precision, + * that means when it is unsure, it will return false. The objective is to avoid false positive, + * which could pollute the log and CTR as we are logging click rate of suggestions that could + * be never visible to users. On the other hand, it is fine to have false negative because + * it would be just like sampling. + */ + private boolean suggestionsMightBeUsedInNotification( + NotificationEntry notificationEntry, boolean hasSmartAction, boolean hasSmartReply) { + Notification notification = notificationEntry.getNotification(); + boolean hasAppGeneratedContextualActions = !notification.getContextualActions().isEmpty(); + + Pair<RemoteInput, Notification.Action> freeformRemoteInputAndAction = + notification.findRemoteInputActionPair(/* requiresFreeform */ true); + boolean hasAppGeneratedReplies = false; + boolean allowGeneratedReplies = false; + if (freeformRemoteInputAndAction != null) { + RemoteInput freeformRemoteInput = freeformRemoteInputAndAction.first; + Notification.Action actionWithFreeformRemoteInput = freeformRemoteInputAndAction.second; + hasAppGeneratedReplies = !ArrayUtils.isEmpty(freeformRemoteInput.getChoices()); + allowGeneratedReplies = actionWithFreeformRemoteInput.getAllowGeneratedReplies(); + } + + if (hasAppGeneratedReplies || hasAppGeneratedContextualActions) { + return false; + } + return hasSmartAction && notification.getAllowSystemGeneratedContextualActions() + || hasSmartReply && allowGeneratedReplies; + } + + private void reportActionsGenerated( + String resultId, List<ConversationAction> conversationActions) { + if (TextUtils.isEmpty(resultId)) { + return; + } + TextClassifierEvent textClassifierEvent = + createTextClassifierEventBuilder( + TextClassifierEvent.TYPE_ACTIONS_GENERATED, resultId) + .setEntityTypes(conversationActions.stream() + .map(ConversationAction::getType) + .toArray(String[]::new)) + .build(); + mTextClassifier.onTextClassifierEvent(textClassifierEvent); + } + + /** * Adds action adjustments based on the notification contents. */ - @NonNull - private List<ConversationAction> suggestConversationActions( - @NonNull NotificationEntry entry, + private ConversationActions suggestConversationActions( + NotificationEntry entry, boolean includeReplies, boolean includeActions) { if (!includeReplies && !includeActions) { - return Collections.emptyList(); - } - if (mTextClassifier == null) { - return Collections.emptyList(); + return EMPTY_CONVERSATION_ACTIONS; } List<ConversationActions.Message> messages = extractMessages(entry.getNotification()); if (messages.isEmpty()) { - return Collections.emptyList(); + return EMPTY_CONVERSATION_ACTIONS; } // Do not generate smart actions if the last message is from the local user. ConversationActions.Message lastMessage = messages.get(messages.size() - 1); if (arePersonsEqual( ConversationActions.Message.PERSON_USER_SELF, lastMessage.getAuthor())) { - return Collections.emptyList(); + return EMPTY_CONVERSATION_ACTIONS; } TextClassifier.EntityConfig.Builder typeConfigBuilder = @@ -146,25 +212,20 @@ public class SmartActionsHelper { .setHints(HINTS) .setTypeConfig(typeConfigBuilder.build()) .build(); - - ConversationActions conversationActionsResult = + ConversationActions conversationActions = mTextClassifier.suggestConversationActions(request); - - String resultId = conversationActionsResult.getId(); - if (!TextUtils.isEmpty(resultId) - && !conversationActionsResult.getConversationActions().isEmpty()) { - mNotificationKeyToResultIdCache.put(entry.getSbn().getKey(), resultId); - } - return conversationActionsResult.getConversationActions(); + reportActionsGenerated( + conversationActions.getId(), conversationActions.getConversationActions()); + return conversationActions; } - void onNotificationExpansionChanged(@NonNull NotificationEntry entry, boolean isUserAction, + void onNotificationExpansionChanged(NotificationEntry entry, boolean isUserAction, boolean isExpanded) { if (!isExpanded) { return; } - String resultId = mNotificationKeyToResultIdCache.get(entry.getSbn().getKey()); - if (resultId == null) { + Session session = mSessionCache.get(entry.getSbn().getKey()); + if (session == null) { return; } // Only report if this is the first time the user sees these suggestions. @@ -173,56 +234,50 @@ public class SmartActionsHelper { } entry.setShowActionEventLogged(); TextClassifierEvent textClassifierEvent = - createTextClassifierEventBuilder(TextClassifierEvent.TYPE_ACTIONS_SHOWN, - resultId) + createTextClassifierEventBuilder( + TextClassifierEvent.TYPE_ACTIONS_SHOWN, session.resultId) .build(); // TODO: If possible, report which replies / actions are actually seen by user. mTextClassifier.onTextClassifierEvent(textClassifierEvent); } - void onNotificationDirectReplied(@NonNull String key) { - if (mTextClassifier == null) { - return; - } - String resultId = mNotificationKeyToResultIdCache.get(key); - if (resultId == null) { + void onNotificationDirectReplied(String key) { + Session session = mSessionCache.get(key); + if (session == null) { return; } TextClassifierEvent textClassifierEvent = - createTextClassifierEventBuilder(TextClassifierEvent.TYPE_MANUAL_REPLY, resultId) + createTextClassifierEventBuilder( + TextClassifierEvent.TYPE_MANUAL_REPLY, session.resultId) .build(); mTextClassifier.onTextClassifierEvent(textClassifierEvent); } - void onSuggestedReplySent(@NonNull String key, @NonNull CharSequence reply, + void onSuggestedReplySent(String key, CharSequence reply, @NotificationAssistantService.Source int source) { - if (mTextClassifier == null) { - return; - } if (source != NotificationAssistantService.SOURCE_FROM_ASSISTANT) { return; } - String resultId = mNotificationKeyToResultIdCache.get(key); - if (resultId == null) { + Session session = mSessionCache.get(key); + if (session == null) { return; } TextClassifierEvent textClassifierEvent = - createTextClassifierEventBuilder(TextClassifierEvent.TYPE_SMART_ACTION, resultId) + createTextClassifierEventBuilder( + TextClassifierEvent.TYPE_SMART_ACTION, session.resultId) .setEntityTypes(ConversationAction.TYPE_TEXT_REPLY) + .setScore(session.repliesScores.getOrDefault(reply, 0f)) .build(); mTextClassifier.onTextClassifierEvent(textClassifierEvent); } - void onActionClicked(@NonNull String key, @NonNull Notification.Action action, + void onActionClicked(String key, Notification.Action action, @NotificationAssistantService.Source int source) { - if (mTextClassifier == null) { - return; - } if (source != NotificationAssistantService.SOURCE_FROM_ASSISTANT) { return; } - String resultId = mNotificationKeyToResultIdCache.get(key); - if (resultId == null) { + Session session = mSessionCache.get(key); + if (session == null) { return; } String actionType = action.getExtras().getString(KEY_ACTION_TYPE); @@ -230,28 +285,32 @@ public class SmartActionsHelper { return; } TextClassifierEvent textClassifierEvent = - createTextClassifierEventBuilder(TextClassifierEvent.TYPE_SMART_ACTION, resultId) + createTextClassifierEventBuilder( + TextClassifierEvent.TYPE_SMART_ACTION, session.resultId) .setEntityTypes(actionType) .build(); mTextClassifier.onTextClassifierEvent(textClassifierEvent); } private Notification.Action createNotificationAction( - RemoteAction remoteAction, String actionType) { + RemoteAction remoteAction, String actionType, float score) { Icon icon = remoteAction.shouldShowIcon() ? remoteAction.getIcon() : Icon.createWithResource(mContext, com.android.internal.R.drawable.ic_action_open); + Bundle extras = new Bundle(); + extras.putString(KEY_ACTION_TYPE, actionType); + extras.putFloat(KEY_ACTION_SCORE, score); return new Notification.Action.Builder( icon, remoteAction.getTitle(), remoteAction.getActionIntent()) .setContextual(true) - .addExtras(Bundle.forPair(KEY_ACTION_TYPE, actionType)) + .addExtras(extras) .build(); } private TextClassifierEvent.Builder createTextClassifierEventBuilder( - int eventType, @NonNull String resultId) { + int eventType, String resultId) { return new TextClassifierEvent.Builder( TextClassifierEvent.CATEGORY_CONVERSATION_ACTIONS, eventType) .setEventTime(System.currentTimeMillis()) @@ -269,7 +328,7 @@ public class SmartActionsHelper { * to fundamental phone functionality where any error would result in a very negative user * experience. */ - private boolean isEligibleForActionAdjustment(@NonNull NotificationEntry entry) { + private boolean isEligibleForActionAdjustment(NotificationEntry entry) { Notification notification = entry.getNotification(); String pkg = entry.getSbn().getPackageName(); if (!Process.myUserHandle().equals(entry.getSbn().getUser())) { @@ -285,7 +344,7 @@ public class SmartActionsHelper { return entry.isMessaging(); } - private boolean isEligibleForReplyAdjustment(@NonNull NotificationEntry entry) { + private boolean isEligibleForReplyAdjustment(NotificationEntry entry) { if (!Process.myUserHandle().equals(entry.getSbn().getUser())) { return false; } @@ -306,8 +365,7 @@ public class SmartActionsHelper { } /** Returns the text most salient for action extraction in a notification. */ - @Nullable - private List<ConversationActions.Message> extractMessages(@NonNull Notification notification) { + private List<ConversationActions.Message> extractMessages(Notification notification) { Parcelable[] messages = notification.extras.getParcelableArray(Notification.EXTRA_MESSAGES); if (messages == null || messages.length == 0) { return Collections.singletonList(new ConversationActions.Message.Builder( @@ -343,7 +401,7 @@ public class SmartActionsHelper { return new ArrayList<>(extractMessages); } - private static boolean arePersonsEqual(@NonNull Person left, @NonNull Person right) { + private static boolean arePersonsEqual(Person left, Person right) { return Objects.equals(left.getKey(), right.getKey()) && Objects.equals(left.getName(), right.getName()) && Objects.equals(left.getUri(), right.getUri()); @@ -359,4 +417,14 @@ public class SmartActionsHelper { this.actions = actions; } } + + private static class Session { + public final String resultId; + public final Map<CharSequence, Float> repliesScores; + + Session(String resultId, Map<CharSequence, Float> repliesScores) { + this.resultId = resultId; + this.repliesScores = repliesScores; + } + } } diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionsHelperTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionsHelperTest.java index 74c20fc09df2..d0b6d0061166 100644 --- a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionsHelperTest.java +++ b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionsHelperTest.java @@ -19,7 +19,9 @@ import static com.google.common.truth.Truth.assertAbout; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -53,8 +55,8 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatcher; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.time.Instant; @@ -71,9 +73,12 @@ import javax.annotation.Nullable; public class SmartActionsHelperTest { private static final String NOTIFICATION_KEY = "key"; private static final String RESULT_ID = "id"; + private static final float SCORE = 0.7f; + private static final CharSequence SMART_REPLY = "Home"; private static final ConversationAction REPLY_ACTION = new ConversationAction.Builder(ConversationAction.TYPE_TEXT_REPLY) - .setTextReply("Home") + .setTextReply(SMART_REPLY) + .setConfidenceScore(SCORE) .build(); private static final String MESSAGE = "Where are you?"; @@ -197,8 +202,16 @@ public class SmartActionsHelperTest { List<ConversationActions.Message> messages = runSuggestAndCaptureRequest().getConversation(); + assertThat(messages).hasSize(1); MessageSubject.assertThat(messages.get(0)).hasText(MESSAGE); + ArgumentCaptor<TextClassifierEvent> argumentCaptor = + ArgumentCaptor.forClass(TextClassifierEvent.class); + verify(mTextClassifier).onTextClassifierEvent(argumentCaptor.capture()); + TextClassifierEvent textClassifierEvent = argumentCaptor.getValue(); + assertTextClassifierEvent(textClassifierEvent, TextClassifierEvent.TYPE_ACTIONS_GENERATED); + assertThat(textClassifierEvent.getEntityTypes()).asList() + .containsExactly(ConversationAction.TYPE_TEXT_REPLY); } @Test @@ -249,6 +262,14 @@ public class SmartActionsHelperTest { MessageSubject.assertThat(fourthMessage).hasPerson(userB); MessageSubject.assertThat(fourthMessage) .hasReferenceTime(createZonedDateTimeFromMsUtc(4000)); + + ArgumentCaptor<TextClassifierEvent> argumentCaptor = + ArgumentCaptor.forClass(TextClassifierEvent.class); + verify(mTextClassifier).onTextClassifierEvent(argumentCaptor.capture()); + TextClassifierEvent textClassifierEvent = argumentCaptor.getValue(); + assertTextClassifierEvent(textClassifierEvent, TextClassifierEvent.TYPE_ACTIONS_GENERATED); + assertThat(textClassifierEvent.getEntityTypes()).asList() + .containsExactly(ConversationAction.TYPE_TEXT_REPLY); } @Test @@ -299,13 +320,15 @@ public class SmartActionsHelperTest { mSmartActionsHelper.suggest(createNotificationEntry()); mSmartActionsHelper.onSuggestedReplySent( - NOTIFICATION_KEY, MESSAGE, NotificationAssistantService.SOURCE_FROM_ASSISTANT); + NOTIFICATION_KEY, SMART_REPLY, NotificationAssistantService.SOURCE_FROM_ASSISTANT); ArgumentCaptor<TextClassifierEvent> argumentCaptor = ArgumentCaptor.forClass(TextClassifierEvent.class); - verify(mTextClassifier).onTextClassifierEvent(argumentCaptor.capture()); - TextClassifierEvent textClassifierEvent = argumentCaptor.getValue(); - assertTextClassifierEvent(textClassifierEvent, TextClassifierEvent.TYPE_SMART_ACTION); + verify(mTextClassifier, times(2)).onTextClassifierEvent(argumentCaptor.capture()); + List<TextClassifierEvent> events = argumentCaptor.getAllValues(); + assertTextClassifierEvent(events.get(0), TextClassifierEvent.TYPE_ACTIONS_GENERATED); + assertTextClassifierEvent(events.get(1), TextClassifierEvent.TYPE_SMART_ACTION); + assertThat(events.get(1).getScore()).isEqualTo(SCORE); } @Test @@ -317,24 +340,22 @@ public class SmartActionsHelperTest { mSmartActionsHelper.onSuggestedReplySent( "something_else", MESSAGE, NotificationAssistantService.SOURCE_FROM_ASSISTANT); - verify(mTextClassifier, never()) - .onTextClassifierEvent(Mockito.any(TextClassifierEvent.class)); + verify(mTextClassifier, never()).onTextClassifierEvent( + argThat(new TextClassifierEventMatcher(TextClassifierEvent.TYPE_SMART_ACTION))); } @Test public void testOnSuggestedReplySent_missingResultId() { when(mTextClassifier.suggestConversationActions(any(ConversationActions.Request.class))) - .thenReturn(new ConversationActions(Collections.emptyList(), null)); - + .thenReturn(new ConversationActions(Collections.singletonList(REPLY_ACTION), null)); Notification notification = createMessageNotification(); when(mStatusBarNotification.getNotification()).thenReturn(notification); mSmartActionsHelper.suggest(createNotificationEntry()); mSmartActionsHelper.onSuggestedReplySent( - "something_else", MESSAGE, NotificationAssistantService.SOURCE_FROM_ASSISTANT); + NOTIFICATION_KEY, SMART_REPLY, NotificationAssistantService.SOURCE_FROM_ASSISTANT); - verify(mTextClassifier, never()) - .onTextClassifierEvent(Mockito.any(TextClassifierEvent.class)); + verify(mTextClassifier, never()).onTextClassifierEvent(any(TextClassifierEvent.class)); } @Test @@ -347,9 +368,10 @@ public class SmartActionsHelperTest { ArgumentCaptor<TextClassifierEvent> argumentCaptor = ArgumentCaptor.forClass(TextClassifierEvent.class); - verify(mTextClassifier).onTextClassifierEvent(argumentCaptor.capture()); - TextClassifierEvent textClassifierEvent = argumentCaptor.getValue(); - assertTextClassifierEvent(textClassifierEvent, TextClassifierEvent.TYPE_MANUAL_REPLY); + verify(mTextClassifier, times(2)).onTextClassifierEvent(argumentCaptor.capture()); + List<TextClassifierEvent> events = argumentCaptor.getAllValues(); + assertTextClassifierEvent(events.get(0), TextClassifierEvent.TYPE_ACTIONS_GENERATED); + assertTextClassifierEvent(events.get(1), TextClassifierEvent.TYPE_MANUAL_REPLY); } @Test @@ -362,9 +384,10 @@ public class SmartActionsHelperTest { ArgumentCaptor<TextClassifierEvent> argumentCaptor = ArgumentCaptor.forClass(TextClassifierEvent.class); - verify(mTextClassifier).onTextClassifierEvent(argumentCaptor.capture()); - TextClassifierEvent textClassifierEvent = argumentCaptor.getValue(); - assertTextClassifierEvent(textClassifierEvent, TextClassifierEvent.TYPE_ACTIONS_SHOWN); + verify(mTextClassifier, times(2)).onTextClassifierEvent(argumentCaptor.capture()); + List<TextClassifierEvent> events = argumentCaptor.getAllValues(); + assertTextClassifierEvent(events.get(0), TextClassifierEvent.TYPE_ACTIONS_GENERATED); + assertTextClassifierEvent(events.get(1), TextClassifierEvent.TYPE_ACTIONS_SHOWN); } @Test @@ -376,7 +399,7 @@ public class SmartActionsHelperTest { mSmartActionsHelper.onNotificationExpansionChanged(createNotificationEntry(), false, false); verify(mTextClassifier, never()).onTextClassifierEvent( - Mockito.any(TextClassifierEvent.class)); + argThat(new TextClassifierEventMatcher(TextClassifierEvent.TYPE_ACTIONS_SHOWN))); } @Test @@ -389,9 +412,10 @@ public class SmartActionsHelperTest { ArgumentCaptor<TextClassifierEvent> argumentCaptor = ArgumentCaptor.forClass(TextClassifierEvent.class); - verify(mTextClassifier).onTextClassifierEvent(argumentCaptor.capture()); - TextClassifierEvent textClassifierEvent = argumentCaptor.getValue(); - assertTextClassifierEvent(textClassifierEvent, TextClassifierEvent.TYPE_ACTIONS_SHOWN); + verify(mTextClassifier, times(2)).onTextClassifierEvent(argumentCaptor.capture()); + List<TextClassifierEvent> events = argumentCaptor.getAllValues(); + assertTextClassifierEvent(events.get(0), TextClassifierEvent.TYPE_ACTIONS_GENERATED); + assertTextClassifierEvent(events.get(1), TextClassifierEvent.TYPE_ACTIONS_SHOWN); } private ZonedDateTime createZonedDateTimeFromMsUtc(long msUtc) { @@ -490,4 +514,21 @@ public class SmartActionsHelperTest { return assertAbout(FACTORY).that(message); } } + + private final class TextClassifierEventMatcher implements ArgumentMatcher<TextClassifierEvent> { + + private int mType; + + private TextClassifierEventMatcher(int type) { + mType = type; + } + + @Override + public boolean matches(TextClassifierEvent textClassifierEvent) { + if (textClassifierEvent == null) { + return false; + } + return mType == textClassifierEvent.getEventType(); + } + } } diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml index e4d35915c77c..860ebfbf6da7 100644 --- a/packages/NetworkStack/AndroidManifest.xml +++ b/packages/NetworkStack/AndroidManifest.xml @@ -31,6 +31,7 @@ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> <uses-permission android:name="android.permission.NETWORK_STACK" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> + <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" /> <application android:label="NetworkStack" android:defaultToDeviceProtectedStorage="true" diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java index 9e5991298834..b1f6d246563e 100644 --- a/packages/NetworkStack/src/android/net/ip/IpClient.java +++ b/packages/NetworkStack/src/android/net/ip/IpClient.java @@ -46,6 +46,7 @@ import android.net.shared.ProvisioningConfiguration; import android.net.util.InterfaceParams; import android.net.util.SharedLog; import android.os.ConditionVariable; +import android.os.IBinder; import android.os.Message; import android.os.RemoteException; import android.os.SystemClock; @@ -380,6 +381,13 @@ public class IpClient extends StateMachine { public InterfaceParams getInterfaceParams(String ifname) { return InterfaceParams.getByName(ifname); } + + /** + * Get a INetd connector. + */ + public INetd getNetd(Context context) { + return INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)); + } } public IpClient(Context context, String ifName, IIpClientCallbacks callback, @@ -413,7 +421,7 @@ public class IpClient extends StateMachine { // TODO: Consider creating, constructing, and passing in some kind of // InterfaceController.Dependencies class. - mNetd = mContext.getSystemService(INetd.class); + mNetd = deps.getNetd(mContext); mInterfaceCtrl = new InterfaceController(mInterfaceName, mNetd, mLog); mLinkObserver = new IpClientLinkObserver( diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java index cedcb84e9d08..c6a207f26577 100644 --- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java +++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java @@ -114,7 +114,8 @@ public class NetworkStackService extends Service { NetworkStackConnector(Context context) { mContext = context; - mNetd = (INetd) context.getSystemService(Context.NETD_SERVICE); + mNetd = INetd.Stub.asInterface( + (IBinder) context.getSystemService(Context.NETD_SERVICE)); mObserverRegistry = new NetworkObserverRegistry(); mCm = context.getSystemService(ConnectivityManager.class); diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java index 2e72d8296a37..b9e901b5c5e3 100644 --- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java +++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java @@ -67,15 +67,9 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; -import android.telephony.CellIdentityCdma; -import android.telephony.CellIdentityGsm; -import android.telephony.CellIdentityLte; -import android.telephony.CellIdentityWcdma; -import android.telephony.CellInfo; -import android.telephony.CellInfoCdma; -import android.telephony.CellInfoGsm; -import android.telephony.CellInfoLte; -import android.telephony.CellInfoWcdma; +import android.telephony.AccessNetworkConstants; +import android.telephony.NetworkRegistrationState; +import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; @@ -1312,6 +1306,7 @@ public class NetworkMonitor extends StateMachine { urlConnection.setInstanceFollowRedirects(probeType == ValidationProbeEvent.PROBE_PAC); urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS); urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS); + urlConnection.setRequestProperty("Connection", "close"); urlConnection.setUseCaches(false); if (mCaptivePortalUserAgent != null) { urlConnection.setRequestProperty("User-Agent", mCaptivePortalUserAgent); @@ -1485,10 +1480,6 @@ public class NetworkMonitor extends StateMachine { */ private void sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal, long requestTimestampMs, long responseTimestampMs) { - if (!mWifiManager.isScanAlwaysAvailable()) { - return; - } - if (!mSystemReady) { return; } @@ -1496,6 +1487,10 @@ public class NetworkMonitor extends StateMachine { Intent latencyBroadcast = new Intent(NetworkMonitorUtils.ACTION_NETWORK_CONDITIONS_MEASURED); if (mNetworkCapabilities.hasTransport(TRANSPORT_WIFI)) { + if (!mWifiManager.isScanAlwaysAvailable()) { + return; + } + WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo(); if (currentWifiInfo != null) { // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not @@ -1515,39 +1510,21 @@ public class NetworkMonitor extends StateMachine { } latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_WIFI); } else if (mNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) { + // TODO(b/123893112): Support multi-sim. latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_NETWORK_TYPE, mTelephonyManager.getNetworkType()); - List<CellInfo> info = mTelephonyManager.getAllCellInfo(); - if (info == null) return; - int numRegisteredCellInfo = 0; - for (CellInfo cellInfo : info) { - if (cellInfo.isRegistered()) { - numRegisteredCellInfo++; - if (numRegisteredCellInfo > 1) { - if (VDBG) { - logw("more than one registered CellInfo." - + " Can't tell which is active. Bailing."); - } - return; - } - if (cellInfo instanceof CellInfoCdma) { - CellIdentityCdma cellId = ((CellInfoCdma) cellInfo).getCellIdentity(); - latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId); - } else if (cellInfo instanceof CellInfoGsm) { - CellIdentityGsm cellId = ((CellInfoGsm) cellInfo).getCellIdentity(); - latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId); - } else if (cellInfo instanceof CellInfoLte) { - CellIdentityLte cellId = ((CellInfoLte) cellInfo).getCellIdentity(); - latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId); - } else if (cellInfo instanceof CellInfoWcdma) { - CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity(); - latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId); - } else { - if (VDBG) logw("Registered cellinfo is unrecognized"); - return; - } - } + final ServiceState dataSs = mTelephonyManager.getServiceState(); + if (dataSs == null) { + logw("failed to retrieve ServiceState"); + return; } + // See if the data sub is registered for PS services on cell. + final NetworkRegistrationState nrs = dataSs.getNetworkRegistrationState( + NetworkRegistrationState.DOMAIN_PS, + AccessNetworkConstants.TransportType.WWAN); + latencyBroadcast.putExtra( + NetworkMonitorUtils.EXTRA_CELL_ID, + nrs == null ? null : nrs.getCellIdentity()); latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_MOBILE); } else { return; diff --git a/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java index 7e57d1eb00b0..aaaff0279fed 100644 --- a/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java +++ b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java @@ -104,8 +104,8 @@ public class IpClientTest { when(mContext.getSystemService(eq(Context.ALARM_SERVICE))).thenReturn(mAlarm); when(mContext.getSystemService(eq(ConnectivityManager.class))).thenReturn(mCm); - when(mContext.getSystemService(INetd.class)).thenReturn(mNetd); when(mContext.getResources()).thenReturn(mResources); + when(mDependencies.getNetd(any())).thenReturn(mNetd); when(mResources.getInteger(R.integer.config_networkAvoidBadWifi)) .thenReturn(DEFAULT_AVOIDBADWIFI_CONFIG_VALUE); diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 36ee81308e98..caa928f43cc9 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -21,6 +21,7 @@ android_library { "SettingsLibActionButtonsPreference", "SettingsLibEntityHeaderWidgets", "SettingsLibBarChartPreference", + "SettingsLibProgressBar", ], // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES diff --git a/packages/SettingsLib/AppPreference/res/layout/preference_app.xml b/packages/SettingsLib/AppPreference/res/layout/preference_app.xml index 6d35550f1b77..b198f5a35630 100644 --- a/packages/SettingsLib/AppPreference/res/layout/preference_app.xml +++ b/packages/SettingsLib/AppPreference/res/layout/preference_app.xml @@ -52,7 +52,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:singleLine="true" - android:textAppearance="@android:style/TextAppearance.Material.Subhead" + android:textAppearance="?android:attr/textAppearanceListItem" android:ellipsize="marquee" android:fadingEdge="horizontal"/> @@ -65,7 +65,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" - android:textAppearance="@android:style/TextAppearance.Material.Small" + android:textAppearance="?android:attr/textAppearanceSmall" android:textAlignment="viewStart" android:textColor="?android:attr/textColorSecondary"/> @@ -73,7 +73,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" - android:textAppearance="@android:style/TextAppearance.Material.Small" + android:textAppearance="?android:attr/textAppearanceSmall" android:textAlignment="viewEnd" android:textColor="?android:attr/textColorSecondary" android:maxLines="1" diff --git a/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_view.xml b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_view.xml index 96045120fec4..013d2d004471 100644 --- a/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_view.xml +++ b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_view.xml @@ -23,7 +23,7 @@ android:layout_marginEnd="16dp" android:gravity="center" android:clickable="true" - android:background="?android:attr/selectableItemBackground" + android:background="@*android:drawable/btn_borderless_material" android:orientation="vertical"> <ImageView diff --git a/packages/SettingsLib/ProgressBar/Android.bp b/packages/SettingsLib/ProgressBar/Android.bp new file mode 100644 index 000000000000..eae21d8858dc --- /dev/null +++ b/packages/SettingsLib/ProgressBar/Android.bp @@ -0,0 +1,9 @@ +android_library { + name: "SettingsLibProgressBar", + + srcs: ["src/**/*.java"], + resource_dirs: ["res"], + + sdk_version: "system_current", + min_sdk_version: "21", +}
\ No newline at end of file diff --git a/packages/SettingsLib/ProgressBar/AndroidManifest.xml b/packages/SettingsLib/ProgressBar/AndroidManifest.xml new file mode 100644 index 000000000000..256b8f3ea477 --- /dev/null +++ b/packages/SettingsLib/ProgressBar/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib.widget"> + + <uses-sdk android:minSdkVersion="21" /> + +</manifest> diff --git a/packages/SettingsLib/ProgressBar/res/drawable/progress_indeterminate_horizontal_material_trimmed.xml b/packages/SettingsLib/ProgressBar/res/drawable/progress_indeterminate_horizontal_material_trimmed.xml new file mode 100644 index 000000000000..2b7535ab91d1 --- /dev/null +++ b/packages/SettingsLib/ProgressBar/res/drawable/progress_indeterminate_horizontal_material_trimmed.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<!-- Variant of progress_indeterminate_horizontal_material in frameworks/base/core/res, which + draws the whole height of the progress bar instead having blank space above and below the + bar. --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/vector_drawable_progress_indeterminate_horizontal_trimmed" > + <target + android:name="rect2_grp" + android:animation="@*android:anim/progress_indeterminate_horizontal_rect2" /> + <target + android:name="rect1_grp" + android:animation="@*android:anim/progress_indeterminate_horizontal_rect1" /> +</animated-vector>
\ No newline at end of file diff --git a/packages/SettingsLib/ProgressBar/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml b/packages/SettingsLib/ProgressBar/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml new file mode 100644 index 000000000000..2f604d0301ba --- /dev/null +++ b/packages/SettingsLib/ProgressBar/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<!-- Variant of vector_drawable_progress_indeterminate_horizontal in frameworks/base/core/res, which + draws the whole height of the progress bar instead having blank space above and below the + bar. --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:height="10dp" + android:width="360dp" + android:viewportHeight="10" + android:viewportWidth="360" > + <group + android:name="progress_group" + android:translateX="180" + android:translateY="5" > + <path + android:name="background_track" + android:pathData="M -180.0,-5.0 l 360.0,0 l 0,10.0 l -360.0,0 Z" + android:fillColor="?android:attr/colorControlActivated" + android:fillAlpha="?android:attr/disabledAlpha"/> + <group + android:name="rect2_grp" + android:translateX="-197.60001" + android:scaleX="0.1" > + <path + android:name="rect2" + android:pathData="M -144.0,-5.0 l 288.0,0 l 0,10.0 l -288.0,0 Z" + android:fillColor="?android:attr/colorControlActivated" /> + </group> + <group + android:name="rect1_grp" + android:translateX="-522.59998" + android:scaleX="0.1" > + <path + android:name="rect1" + android:pathData="M -144.0,-5.0 l 288.0,0 l 0,10.0 l -288.0,0 Z" + android:fillColor="?android:attr/colorControlActivated" /> + </group> + </group> +</vector>
\ No newline at end of file diff --git a/packages/SettingsLib/ProgressBar/res/layout/progress_header.xml b/packages/SettingsLib/ProgressBar/res/layout/progress_header.xml new file mode 100644 index 000000000000..268858bebf0c --- /dev/null +++ b/packages/SettingsLib/ProgressBar/res/layout/progress_header.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="3dp"> + <View + android:id="@+id/progress_bar_background" + style="@style/TrimmedHorizontalProgressBar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?android:attr/colorSecondary" /> + <ProgressBar + android:id="@+id/progress_bar_animation" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@style/TrimmedHorizontalProgressBar" + android:indeterminate="true" /> +</FrameLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/ProgressBar/res/values/styles.xml b/packages/SettingsLib/ProgressBar/res/values/styles.xml new file mode 100644 index 000000000000..5f57c1db0238 --- /dev/null +++ b/packages/SettingsLib/ProgressBar/res/values/styles.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<resources> + + <style name="TrimmedHorizontalProgressBar" + parent="android:Widget.Material.ProgressBar.Horizontal"> + <item name="android:indeterminateDrawable"> + @drawable/progress_indeterminate_horizontal_material_trimmed + </item> + <item name="android:minHeight">3dp</item> + <item name="android:maxHeight">3dp</item> + </style> + +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/res/drawable/ic_info_outline_24.xml b/packages/SettingsLib/res/drawable/ic_info_outline_24.xml new file mode 100644 index 000000000000..317e43b95cdd --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_info_outline_24.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?android:attr/colorControlNormal"> + <path + android:fillColor="#FF000000" + android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/> +</vector> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java index a1bf93654774..c1933fd87633 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java @@ -73,9 +73,8 @@ public class HeadsetProfile implements LocalBluetoothProfile { BluetoothProfile.STATE_CONNECTED); device.refresh(); } - - mProfileManager.callServiceConnectedListeners(); mIsProfileReady=true; + mProfileManager.callServiceConnectedListeners(); } public void onServiceDisconnected(int profile) { diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java index 305a1ff97e71..dd6d563b3197 100644 --- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java +++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java @@ -40,6 +40,7 @@ import androidx.loader.content.AsyncTaskLoader; import com.android.settingslib.NetworkPolicyEditor; import java.time.ZonedDateTime; +import java.util.ArrayList; import java.util.Iterator; /** @@ -52,6 +53,7 @@ public abstract class NetworkCycleDataLoader<D> extends AsyncTaskLoader<D> { protected final int mNetworkType; private final NetworkPolicy mPolicy; private final NetworkTemplate mNetworkTemplate; + private final ArrayList<Long> mCycles; @VisibleForTesting final INetworkStatsService mNetworkStatsService; @@ -60,6 +62,7 @@ public abstract class NetworkCycleDataLoader<D> extends AsyncTaskLoader<D> { mSubId = builder.mSubId; mNetworkType = builder.mNetworkType; mNetworkTemplate = builder.mNetworkTemplate; + mCycles = builder.mCycles; mNetworkStatsManager = (NetworkStatsManager) builder.mContext.getSystemService(Context.NETWORK_STATS_SERVICE); mNetworkStatsService = INetworkStatsService.Stub.asInterface( @@ -77,7 +80,9 @@ public abstract class NetworkCycleDataLoader<D> extends AsyncTaskLoader<D> { } public D loadInBackground() { - if (mPolicy == null) { + if (mCycles != null && mCycles.size() > 1) { + loadDataForSpecificCycles(); + } else if (mPolicy == null) { loadFourWeeksData(); } else { loadPolicyData(); @@ -132,6 +137,17 @@ public abstract class NetworkCycleDataLoader<D> extends AsyncTaskLoader<D> { } @VisibleForTesting + void loadDataForSpecificCycles() { + long cycleEnd = mCycles.get(0); + final int lastCycleIndex = mCycles.size() - 1; + for (int i = 1; i <= lastCycleIndex; i++) { + final long cycleStart = mCycles.get(i); + recordUsage(cycleStart, cycleEnd); + cycleEnd = cycleStart; + } + } + + @VisibleForTesting abstract void recordUsage(long start, long end); abstract D getCycleUsage(); @@ -157,11 +173,17 @@ public abstract class NetworkCycleDataLoader<D> extends AsyncTaskLoader<D> { return bytes; } + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + public ArrayList<Long> getCycles() { + return mCycles; + } + public static abstract class Builder<T extends NetworkCycleDataLoader> { private final Context mContext; private String mSubId; private int mNetworkType; private NetworkTemplate mNetworkTemplate; + private ArrayList<Long> mCycles; public Builder (Context context) { mContext = context; @@ -178,6 +200,16 @@ public abstract class NetworkCycleDataLoader<D> extends AsyncTaskLoader<D> { return this; } + /** + * Sets the network cycles to be used to query the usage data. + * @param cycles the time slots for the network cycle to be used to query the network usage. + * @return the builder + */ + public Builder<T> setCycles(ArrayList<Long> cycles) { + mCycles = cycles; + return this; + } + public abstract T build(); } diff --git a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java index a106846fac26..2a1281027169 100644 --- a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java @@ -56,7 +56,7 @@ public class FooterPreference extends Preference { } private void init() { - setIcon(com.android.internal.R.drawable.ic_info_outline_24); + setIcon(R.drawable.ic_info_outline_24); setKey(KEY_FOOTER); setOrder(ORDER_FOOTER); setSelectable(false); diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index b9a5f2347f16..8ea0f4b6ba61 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -53,6 +53,7 @@ import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; +import com.android.internal.util.CollectionUtils; import com.android.settingslib.R; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; @@ -566,9 +567,6 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro AccessPoint accessPoint = getCachedOrCreate(entry.getValue(), cachedAccessPoints); - if (mLastInfo != null && mLastNetworkInfo != null) { - accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo); - } // Update the matching config if there is one, to populate saved network info accessPoint.update(configsByKey.get(entry.getKey())); @@ -578,68 +576,20 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro List<ScanResult> cachedScanResults = new ArrayList<>(mScanResultCache.values()); - // Add a unique Passpoint R1 AccessPoint for each Passpoint profile's FQDN. - List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> passpointConfigsAndScans = - mWifiManager.getAllMatchingWifiConfigs(cachedScanResults); - Set<String> seenFQDNs = new ArraySet<>(); - for (Pair<WifiConfiguration, - Map<Integer, List<ScanResult>>> pairing : passpointConfigsAndScans) { - WifiConfiguration config = pairing.first; - - List<ScanResult> scanResults = new ArrayList<>(); + // Add a unique Passpoint AccessPoint for each Passpoint profile's FQDN. + accessPoints.addAll(updatePasspointAccessPoints( + mWifiManager.getAllMatchingWifiConfigs(cachedScanResults), cachedAccessPoints)); - List<ScanResult> homeScans = - pairing.second.get(WifiManager.PASSPOINT_HOME_NETWORK); - List<ScanResult> roamingScans = - pairing.second.get(WifiManager.PASSPOINT_ROAMING_NETWORK); + // Add OSU Provider AccessPoints + accessPoints.addAll(updateOsuAccessPoints( + mWifiManager.getMatchingOsuProviders(cachedScanResults), cachedAccessPoints)); - if (homeScans == null) { - homeScans = new ArrayList<>(); - } - if (roamingScans == null) { - roamingScans = new ArrayList<>(); - } - - // TODO(b/118705403): Differentiate home network vs roaming network for summary info - if (!homeScans.isEmpty()) { - scanResults.addAll(homeScans); - } else { - scanResults.addAll(roamingScans); - } - - if (seenFQDNs.add(config.FQDN)) { - int bestRssi = Integer.MIN_VALUE; - for (ScanResult result : scanResults) { - if (result.level >= bestRssi) { - bestRssi = result.level; - config.SSID = AccessPoint.convertToQuotedString(result.SSID); - } - } - - AccessPoint accessPoint = - getCachedOrCreatePasspoint(scanResults, cachedAccessPoints, config); - accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo); - accessPoints.add(accessPoint); - } - } - - // Add Passpoint OSU Provider AccessPoints - Map<OsuProvider, List<ScanResult>> providersAndScans = - mWifiManager.getMatchingOsuProviders(cachedScanResults); - Set<OsuProvider> alreadyProvisioned = mWifiManager - .getMatchingPasspointConfigsForOsuProviders( - providersAndScans.keySet()).keySet(); - for (OsuProvider provider : providersAndScans.keySet()) { - if (!alreadyProvisioned.contains(provider)) { - AccessPoint accessPointOsu = - getCachedOrCreateOsu(providersAndScans.get(provider), - cachedAccessPoints, provider); - accessPointOsu.update(connectionConfig, mLastInfo, mLastNetworkInfo); - accessPoints.add(accessPointOsu); + if (mLastInfo != null && mLastNetworkInfo != null) { + for (AccessPoint ap : accessPoints) { + ap.update(connectionConfig, mLastInfo, mLastNetworkInfo); } } - // If there were no scan results, create an AP for the currently connected network (if // it exists). if (accessPoints.isEmpty() && connectionConfig != null) { @@ -686,7 +636,67 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro } @VisibleForTesting - AccessPoint getCachedOrCreate( + List<AccessPoint> updatePasspointAccessPoints( + List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> passpointConfigsAndScans, + List<AccessPoint> accessPointCache) { + List<AccessPoint> accessPoints = new ArrayList<>(); + + Set<String> seenFQDNs = new ArraySet<>(); + for (Pair<WifiConfiguration, + Map<Integer, List<ScanResult>>> pairing : passpointConfigsAndScans) { + WifiConfiguration config = pairing.first; + if (seenFQDNs.add(config.FQDN)) { + List<ScanResult> apScanResults = new ArrayList<>(); + + List<ScanResult> homeScans = + pairing.second.get(WifiManager.PASSPOINT_HOME_NETWORK); + List<ScanResult> roamingScans = + pairing.second.get(WifiManager.PASSPOINT_ROAMING_NETWORK); + + // TODO(b/118705403): Differentiate home network vs roaming network for summary info + if (!CollectionUtils.isEmpty(homeScans)) { + apScanResults.addAll(homeScans); + } else if (!CollectionUtils.isEmpty(roamingScans)) { + apScanResults.addAll(roamingScans); + } + + int bestRssi = Integer.MIN_VALUE; + for (ScanResult result : apScanResults) { + if (result.level >= bestRssi) { + bestRssi = result.level; + config.SSID = AccessPoint.convertToQuotedString(result.SSID); + } + } + + AccessPoint accessPoint = + getCachedOrCreatePasspoint(apScanResults, accessPointCache, config); + accessPoints.add(accessPoint); + } + } + return accessPoints; + } + + @VisibleForTesting + List<AccessPoint> updateOsuAccessPoints( + Map<OsuProvider, List<ScanResult>> providersAndScans, + List<AccessPoint> accessPointCache) { + List<AccessPoint> accessPoints = new ArrayList<>(); + + Set<OsuProvider> alreadyProvisioned = mWifiManager + .getMatchingPasspointConfigsForOsuProviders( + providersAndScans.keySet()).keySet(); + for (OsuProvider provider : providersAndScans.keySet()) { + if (!alreadyProvisioned.contains(provider)) { + AccessPoint accessPointOsu = + getCachedOrCreateOsu(providersAndScans.get(provider), + accessPointCache, provider); + accessPoints.add(accessPointOsu); + } + } + return accessPoints; + } + + private AccessPoint getCachedOrCreate( List<ScanResult> scanResults, List<AccessPoint> cache) { AccessPoint accessPoint = getCachedByKey(cache, AccessPoint.getKey(scanResults.get(0))); diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java index 42eb0b940938..7d227883b1fe 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -48,11 +49,15 @@ import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.net.wifi.WifiNetworkScoreCache; import android.net.wifi.WifiSsid; +import android.net.wifi.hotspot2.OsuProvider; +import android.net.wifi.hotspot2.PasspointConfiguration; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.SystemClock; import android.provider.Settings; +import android.util.ArraySet; +import android.util.Pair; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -74,7 +79,10 @@ import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -94,6 +102,8 @@ public class WifiTrackerTest { private static final int RSSI_1 = -30; private static final byte SCORE_1 = 10; private static final int BADGE_1 = AccessPoint.Speed.MODERATE; + private static final String FQDN_1 = "fqdn1"; + private static final String PROVIDER_FRIENDLY_NAME_1 = "providerFriendlyName1"; private static final String SSID_2 = "ssid2"; private static final String BSSID_2 = "AA:AA:AA:AA:AA:AA"; @@ -102,6 +112,8 @@ public class WifiTrackerTest { private static final int RSSI_2 = -30; private static final byte SCORE_2 = 15; private static final int BADGE_2 = AccessPoint.Speed.FAST; + private static final String FQDN_2 = "fqdn2"; + private static final String PROVIDER_FRIENDLY_NAME_2 = "providerFriendlyName2"; private static final String SSID_3 = "ssid3"; private static final String BSSID_3 = "CC:00:00:00:00:00"; @@ -271,6 +283,61 @@ public class WifiTrackerTest { 0 /* microsecond timestamp */); } + private static WifiConfiguration buildPasspointConfiguration(String fqdn, String friendlyName) { + WifiConfiguration config = spy(new WifiConfiguration()); + config.FQDN = fqdn; + config.providerFriendlyName = friendlyName; + when(config.isPasspoint()).thenReturn(true); + return config; + } + + private List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> + createPasspointMatchingWifiConfigsWithDuplicates() { + List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> matchingList = + new ArrayList<>(); + Map<Integer, List<ScanResult>> mapping = new HashMap<>(); + + mapping.put(WifiManager.PASSPOINT_HOME_NETWORK, Arrays.asList(buildScanResult1())); + + WifiConfiguration passpointConfig1 = + buildPasspointConfiguration(FQDN_1, PROVIDER_FRIENDLY_NAME_1); + WifiConfiguration passpointConfig2 = + buildPasspointConfiguration(FQDN_2, PROVIDER_FRIENDLY_NAME_2); + + matchingList.add(new Pair(passpointConfig1, mapping)); + matchingList.add(new Pair(passpointConfig1, mapping)); + matchingList.add(new Pair(passpointConfig2, mapping)); + matchingList.add(new Pair(passpointConfig2, mapping)); + + return matchingList; + } + + private List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> + createPasspointMatchingWifiConfigWithScanResults( + List<ScanResult> homeList, List<ScanResult> roamingList) { + List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> matchingList = + new ArrayList<>(); + Map<Integer, List<ScanResult>> mapping = new HashMap<>(); + + if (homeList != null) { + mapping.put(WifiManager.PASSPOINT_HOME_NETWORK, homeList); + } + if (roamingList != null) { + mapping.put(WifiManager.PASSPOINT_ROAMING_NETWORK, roamingList); + } + + matchingList.add(new Pair(buildPasspointConfiguration(FQDN_1, PROVIDER_FRIENDLY_NAME_1), + mapping)); + + return matchingList; + } + + private static OsuProvider buildOsuProvider(String friendlyName) { + Map<String, String> friendlyNames = new HashMap<>(); + friendlyNames.put("en", friendlyName); + return new OsuProvider(null, friendlyNames, null, null, null, null, null); + } + private WifiTracker createTrackerWithImmediateBroadcastsAndInjectInitialScanResults( Intent ... intents) throws InterruptedException { @@ -926,4 +993,172 @@ public class WifiTrackerTest { assertThat(tracker.getAccessPoints().get(0).getBssid()).isEqualTo(BSSID_1); assertThat(tracker.getAccessPoints().get(1).getBssid()).isEqualTo(BSSID_2); } + + /** + * Verifies that updatePasspointAccessPoints will only return AccessPoints whose + * isPasspoint() evaluates as true. + */ + @Test + public void updatePasspointAccessPoints_returnedAccessPointsArePasspoint() { + WifiTracker tracker = createMockedWifiTracker(); + + List<AccessPoint> passpointAccessPoints = tracker.updatePasspointAccessPoints( + createPasspointMatchingWifiConfigsWithDuplicates(), new ArrayList<>()); + + assertTrue(passpointAccessPoints.size() != 0); + for (AccessPoint ap : passpointAccessPoints) { + assertTrue(ap.isPasspoint()); + } + } + + /** + * Verifies that updatePasspointAccessPoints will return the same amount of AccessPoints as + * unique WifiConfigurations, even if duplicate FQDNs exist. + */ + @Test + public void updatePasspointAccessPoints_ignoresDuplicateFQDNs() { + WifiTracker tracker = createMockedWifiTracker(); + + // Process matching list of four configs with two duplicate FQDNs. + List<AccessPoint> passpointAccessPoints = tracker.updatePasspointAccessPoints( + createPasspointMatchingWifiConfigsWithDuplicates(), new ArrayList<>()); + + // Should have 2 APs with unique FQDNs, ignoring the 2 duplicate FQDNs. + assertThat(passpointAccessPoints).hasSize(2); + + Set<String> fqdns = new ArraySet<>(Arrays.asList(FQDN_1, FQDN_2)); + + assertTrue(fqdns.remove(passpointAccessPoints.get(0).getConfig().FQDN)); + assertTrue(fqdns.remove(passpointAccessPoints.get(1).getConfig().FQDN)); + } + + /** + * Verifies that updatePasspointAccessPoints will return matching cached APs and update their + * scan results instead of creating new APs. + */ + @Test + public void updatePasspointAccessPoints_usesCachedAccessPoints() { + WifiTracker tracker = createMockedWifiTracker(); + + ScanResult result = buildScanResult1(); + + List<AccessPoint> passpointAccessPointsFirstUpdate = tracker.updatePasspointAccessPoints( + createPasspointMatchingWifiConfigWithScanResults(Arrays.asList(result), + null), new ArrayList<>()); + List<AccessPoint> cachedAccessPoints = new ArrayList<>(passpointAccessPointsFirstUpdate); + + int prevRssi = result.level; + int newRssi = prevRssi + 10; + result.level = newRssi; + + List<AccessPoint> passpointAccessPointsSecondUpdate = tracker.updatePasspointAccessPoints( + createPasspointMatchingWifiConfigWithScanResults(Arrays.asList(result), + null), cachedAccessPoints); + + // Verify second update AP is the same object as the first update AP + assertThat(passpointAccessPointsFirstUpdate.get(0)) + .isSameAs(passpointAccessPointsSecondUpdate.get(0)); + // Verify second update AP has the average of the first and second update RSSIs + assertThat(passpointAccessPointsSecondUpdate.get(0).getRssi()) + .isEqualTo((prevRssi + newRssi) / 2); + } + + /** + * Verifies that updateOsuAccessPoints will only return AccessPoints whose + * isOsuProvider() evaluates as true. + */ + @Test + public void updateOsuAccessPoints_returnedAccessPointsAreOsuProviders() { + WifiTracker tracker = createMockedWifiTracker(); + + Map<OsuProvider, List<ScanResult>> providersAndScans = new HashMap<>(); + providersAndScans.put( + buildOsuProvider(PROVIDER_FRIENDLY_NAME_1), Arrays.asList(buildScanResult1())); + providersAndScans.put( + buildOsuProvider(PROVIDER_FRIENDLY_NAME_2), Arrays.asList(buildScanResult2())); + + List<AccessPoint> osuAccessPoints = tracker.updateOsuAccessPoints( + providersAndScans, new ArrayList<>()); + + assertThat(osuAccessPoints).hasSize(2); + for (AccessPoint ap: osuAccessPoints) { + assertThat(ap.isOsuProvider()).isTrue(); + } + } + + /** + * Verifies that updateOsuAccessPoints will not return Osu AccessPoints for already provisioned + * networks + */ + @Test + public void updateOsuAccessPoints_doesNotReturnAlreadyProvisionedOsuAccessPoints() { + WifiTracker tracker = createMockedWifiTracker(); + + // Start with two Osu Providers + Map<OsuProvider, List<ScanResult>> providersAndScans = new HashMap<>(); + providersAndScans.put( + buildOsuProvider(PROVIDER_FRIENDLY_NAME_1), Arrays.asList(buildScanResult1())); + providersAndScans.put( + buildOsuProvider(PROVIDER_FRIENDLY_NAME_2), Arrays.asList(buildScanResult2())); + + // First update + List<AccessPoint> osuAccessPoints = tracker.updateOsuAccessPoints( + providersAndScans, new ArrayList<>()); + + // Make sure both Osu Providers' APs are returned + assertThat(osuAccessPoints).hasSize(2); + List<String> friendlyNames = Arrays.asList( + osuAccessPoints.get(0).getTitle(), osuAccessPoints.get(1).getTitle()); + assertThat(friendlyNames) + .containsExactly(PROVIDER_FRIENDLY_NAME_1, PROVIDER_FRIENDLY_NAME_2); + + // Simulate Osu Provider 1 being provisioned + Map<OsuProvider, PasspointConfiguration> matchingPasspointConfigForOsuProvider = + new HashMap<>(); + matchingPasspointConfigForOsuProvider.put(buildOsuProvider(PROVIDER_FRIENDLY_NAME_1), null); + when(mockWifiManager.getMatchingPasspointConfigsForOsuProviders(any())).thenReturn( + matchingPasspointConfigForOsuProvider); + + // Second update + osuAccessPoints = tracker.updateOsuAccessPoints( + providersAndScans, new ArrayList<>()); + + // Returned AP should only be for Osu Provider 2 + assertThat(osuAccessPoints).hasSize(1); + assertThat(osuAccessPoints.get(0).getTitle()).isEqualTo(PROVIDER_FRIENDLY_NAME_2); + } + + /** + * Verifies that updateOsuAccessPoints will return matching cached APs and update their + * scan results instead of creating new APs. + */ + @Test + public void updateOsuAccessPoints_usesCachedAccessPoints() { + WifiTracker tracker = createMockedWifiTracker(); + + ScanResult result = buildScanResult1(); + + Map<OsuProvider, List<ScanResult>> providersAndScans = new HashMap<>(); + providersAndScans.put( + buildOsuProvider(PROVIDER_FRIENDLY_NAME_1), Arrays.asList(result)); + + List<AccessPoint> osuAccessPointsFirstUpdate = tracker.updateOsuAccessPoints( + providersAndScans, new ArrayList<>()); + List<AccessPoint> cachedAccessPoints = new ArrayList<>(osuAccessPointsFirstUpdate); + + // New RSSI for second update + int prevRssi = result.level; + int newRssi = prevRssi + 10; + result.level = newRssi; + + List<AccessPoint> osuAccessPointsSecondUpdate = tracker.updateOsuAccessPoints( + providersAndScans, cachedAccessPoints); + + // Verify second update AP is the same object as the first update AP + assertTrue(osuAccessPointsFirstUpdate.get(0) + == osuAccessPointsSecondUpdate.get(0)); + // Verify second update AP has the average of the first and second update RSSIs + assertThat(osuAccessPointsSecondUpdate.get(0).getRssi()) + .isEqualTo((prevRssi + newRssi) / 2); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java index b8a143a376fd..c5f54bb0f0d9 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java @@ -46,6 +46,7 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.util.ReflectionHelpers; import java.time.ZonedDateTime; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -99,6 +100,20 @@ public class NetworkCycleDataLoaderTest { } @Test + public void loadInBackground_hasCyclePeriod_shouldLoadDataForSpecificCycles() { + mLoader = spy(new NetworkCycleDataTestLoader(mContext)); + doNothing().when(mLoader).loadDataForSpecificCycles(); + final ArrayList<Long> cycles = new ArrayList<>(); + cycles.add(67890L); + cycles.add(12345L); + ReflectionHelpers.setField(mLoader, "mCycles", cycles); + + mLoader.loadInBackground(); + + verify(mLoader).loadDataForSpecificCycles(); + } + + @Test public void loadPolicyData_shouldRecordUsageFromPolicyCycle() { final int networkType = ConnectivityManager.TYPE_MOBILE; final String subId = "TestSubscriber"; @@ -139,6 +154,27 @@ public class NetworkCycleDataLoaderTest { verify(mLoader).recordUsage(fourWeeksAgo, now); } + @Test + public void loadDataForSpecificCycles_shouldRecordUsageForSpecifiedTime() { + mLoader = spy(new NetworkCycleDataTestLoader(mContext)); + final long now = System.currentTimeMillis(); + final long tenDaysAgo = now - (DateUtils.DAY_IN_MILLIS * 10); + final long twentyDaysAgo = now - (DateUtils.DAY_IN_MILLIS * 20); + final long thirtyDaysAgo = now - (DateUtils.DAY_IN_MILLIS * 30); + final ArrayList<Long> cycles = new ArrayList<>(); + cycles.add(now); + cycles.add(tenDaysAgo); + cycles.add(twentyDaysAgo); + cycles.add(thirtyDaysAgo); + ReflectionHelpers.setField(mLoader, "mCycles", cycles); + + mLoader.loadDataForSpecificCycles(); + + verify(mLoader).recordUsage(tenDaysAgo, now); + verify(mLoader).recordUsage(twentyDaysAgo, tenDaysAgo); + verify(mLoader).recordUsage(thirtyDaysAgo, twentyDaysAgo); + } + public class NetworkCycleDataTestLoader extends NetworkCycleDataLoader<List<NetworkCycleData>> { private NetworkCycleDataTestLoader(Context context) { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 6152b8cbd562..db4b131a0f6f 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -698,6 +698,9 @@ class SettingsProtoDumpUtil { Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST, GlobalSettingsProto.Gpu.ANGLE_WHITELIST); dumpSetting(s, p, + Settings.Global.GLOBAL_SETTINGS_SHOW_ANGLE_IN_USE_DIALOG_BOX, + GlobalSettingsProto.Gpu.SHOW_ANGLE_IN_USE_DIALOG); + dumpSetting(s, p, Settings.Global.GPU_DEBUG_LAYER_APP, GlobalSettingsProto.Gpu.DEBUG_LAYER_APP); dumpSetting(s, p, @@ -718,6 +721,9 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.Global.GAME_DRIVER_WHITELIST, GlobalSettingsProto.Gpu.GAME_DRIVER_WHITELIST); + dumpSetting(s, p, + Settings.Global.GAME_DRIVER_BLACKLISTS, + GlobalSettingsProto.Gpu.GAME_DRIVER_BLACKLISTS); p.end(gpuToken); final long hdmiToken = p.start(GlobalSettingsProto.HDMI); diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml index 51f6a4b92413..dc45b4b51925 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml @@ -28,11 +28,20 @@ android:clipToPadding="false" android:orientation="vertical" android:layout_centerHorizontal="true"> + <TextView + android:id="@+id/title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingStart="64dp" + android:paddingEnd="64dp" + android:visibility="gone" + android:textColor="?attr/wallpaperTextColor" + android:theme="@style/TextAppearance.Keyguard" + /> <view class="com.android.keyguard.KeyguardSliceView$Row" android:id="@+id/row" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="@dimen/subtitle_clock_padding" android:orientation="horizontal" android:gravity="center" /> diff --git a/packages/SystemUI/res-keyguard/layout/text_clock.xml b/packages/SystemUI/res-keyguard/layout/text_clock.xml index b61ad9c4fc11..9f7ea0d44357 100644 --- a/packages/SystemUI/res-keyguard/layout/text_clock.xml +++ b/packages/SystemUI/res-keyguard/layout/text_clock.xml @@ -16,9 +16,10 @@ --> <TextClock xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" + android:gravity="center_horizontal" android:letterSpacing="0.03" android:textColor="?attr/wallpaperTextColor" android:singleLine="true" diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index e2ba23ec898c..b6a41c19ec32 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -42,18 +42,18 @@ <dimen name="eca_overlap">-10dip</dimen> <!-- Slice header --> - <dimen name="widget_title_font_size">24dp</dimen> - <dimen name="widget_title_bottom_margin">14dp</dimen> - <dimen name="bottom_text_spacing_digital">0dp</dimen> + <dimen name="widget_title_font_size">22dp</dimen> + <dimen name="header_subtitle_padding">4dp</dimen> + <dimen name="header_icon_size">20dp</dimen> <!-- Slice subtitle --> <dimen name="widget_label_font_size">16dp</dimen> <!-- Clock without header --> <dimen name="widget_big_font_size">64dp</dimen> + <dimen name="bottom_text_spacing_digital">0dp</dimen> <!-- Clock with header --> - <dimen name="widget_small_clock_padding">-25dp</dimen> - <dimen name="widget_small_font_size">24dp</dimen> - <dimen name="widget_small_font_stroke">0.6dp</dimen> + <dimen name="widget_small_font_size">22dp</dimen> <dimen name="widget_vertical_padding">32dp</dimen> + <dimen name="widget_vertical_padding_clock">30dp</dimen> <!-- Subtitle paddings --> <dimen name="widget_horizontal_padding">8dp</dimen> <dimen name="widget_icon_size">16dp</dimen> diff --git a/packages/SystemUI/res/drawable/ic_qs_bluetooth_connected.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_connected.xml index b673e4f3b081..dd124b713a72 100644 --- a/packages/SystemUI/res/drawable/ic_qs_bluetooth_connected.xml +++ b/packages/SystemUI/res/drawable/ic_qs_bluetooth_connected.xml @@ -14,12 +14,19 @@ Copyright (C) 2017 The Android Open Source Project limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0" - android:tint="?android:attr/colorControlNormal" > + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="?android:attr/colorControlNormal"> + + <path + android:fillColor="#FFFFFFFF" + android:pathData="M17.71,7.71L12,2h-1v7.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L11,14.41V22h1l5.71-5.71L13.41,12L17.71,7.71z M13,5.83 l1.88,1.88L13,9.59V5.83z M14.88,16.29L13,18.17v-3.76L14.88,16.29z" /> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M 5 10.5 C 5.82842712475 10.5 6.5 11.1715728753 6.5 12 C 6.5 12.8284271247 5.82842712475 13.5 5 13.5 C 4.17157287525 13.5 3.5 12.8284271247 3.5 12 C 3.5 11.1715728753 4.17157287525 10.5 5 10.5 Z" /> <path - android:pathData="M13.51,12l3.75,-3.74c0.41,-0.41 0.41,-1.07 0,-1.48l-4.47,-4.47 -0.03,-0.03a1.046,1.046 0,0 0,-1.76 0.76v6.44L6.95,5.43c-0.41,-0.41 -1.06,-0.41 -1.47,0s-0.41,1.06 0,1.47l5.09,5.1 -5.09,5.09c-0.41,0.41 -0.41,1.06 0,1.47s1.06,0.41 1.47,0L11,14.51v6.45a1.04,1.04 0,0 0,1.75 0.76l0.05,-0.05 4.46,-4.46c0.41,-0.41 0.41,-1.07 0,-1.48L13.51,12zM12.99,9.67v-4.3l2.15,2.15 -2.15,2.15zM12.99,18.62v-4.3l2.15,2.15 -2.15,2.15zM6.06,13.06c-0.59,0.59 -1.54,0.59 -2.12,0a1.49,1.49 0,0 1,0 -2.12,1.49 1.49,0 0,1 2.12,0c0.59,0.59 0.59,1.53 0,2.12zM20.06,10.94c0.59,0.59 0.59,1.54 0,2.12 -0.59,0.59 -1.54,0.59 -2.12,0a1.49,1.49 0,0 1,0 -2.12,1.49 1.49,0 0,1 2.12,0z" - android:fillColor="#FFFFFFFF" /> + android:fillColor="#FFFFFFFF" + android:pathData="M 19 10.5 C 19.8284271247 10.5 20.5 11.1715728753 20.5 12 C 20.5 12.8284271247 19.8284271247 13.5 19 13.5 C 18.1715728753 13.5 17.5 12.8284271247 17.5 12 C 17.5 11.1715728753 18.1715728753 10.5 19 10.5 Z" /> </vector> diff --git a/packages/SystemUI/res/drawable/ic_qs_bluetooth_on.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_on.xml index 8cc6caa8abc8..220c63ccca6d 100644 --- a/packages/SystemUI/res/drawable/ic_qs_bluetooth_on.xml +++ b/packages/SystemUI/res/drawable/ic_qs_bluetooth_on.xml @@ -14,12 +14,13 @@ Copyright (C) 2017 The Android Open Source Project limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="64dp" - android:height="64dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0" - android:tint="?android:attr/colorControlNormal"> + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="?android:attr/colorControlNormal"> + <path android:fillColor="#FFFFFFFF" - android:pathData="M13.5,12l3.8,-3.7c0.4,-0.4 0.4,-1.1 0,-1.5l-4.5,-4.5c-0.4,-0.4 -1.1,-0.4 -1.5,0.1C11.1,2.5 11,2.8 11,3v6.4L6.9,5.4C6.5,5 5.9,5 5.5,5.4s-0.4,1.1 0,1.5l5.1,5.1l-5.1,5.1c-0.4,0.4 -0.4,1.1 0,1.5s1.1,0.4 1.5,0l4.1,-4V21c0,0.6 0.5,1 1,1c0.3,0 0.5,-0.1 0.7,-0.3l0.1,0l4.5,-4.5c0.4,-0.4 0.4,-1.1 0,-1.5L13.5,12zM13,9.7V5.4l2.1,2.2L13,9.7zM13,18.6v-4.3l2.1,2.2L13,18.6z"/> + android:pathData="M17.71,7.71L12,2h-1v7.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L11,14.41V22h1l5.71-5.71L13.41,12L17.71,7.71z M13,5.83 l1.88,1.88L13,9.59V5.83z M14.88,16.29L13,18.17v-3.76L14.88,16.29z" /> </vector> diff --git a/packages/SystemUI/res/drawable/ic_signal_airplane.xml b/packages/SystemUI/res/drawable/ic_signal_airplane.xml index 0a4d7526a55d..f708ed9cb8a6 100644 --- a/packages/SystemUI/res/drawable/ic_signal_airplane.xml +++ b/packages/SystemUI/res/drawable/ic_signal_airplane.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - 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. @@ -15,16 +15,12 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="48dp" - android:height="48dp" - android:viewportWidth="20.5" - android:viewportHeight="20.5"> - <group - android:translateX="1.75" - android:translateY="1.4"> - <path - android:pathData="M16.01,9.87l-6.24,-3.9v-4.7C9.77,0.57 9.21,0 8.5,0S7.23,0.57 7.23,1.28v4.7L0.99,9.88c-0.37,0.23 -0.6,0.64 -0.6,1.08v0.41c0,0.29 0.29,0.5 0.55,0.41l6.27,-1.97v4.7l-1.37,1.02c-0.21,0.16 -0.34,0.41 -0.34,0.68v0.57c0,0.15 0.12,0.23 0.27,0.2 1.67,-0.47 1.12,-0.31 2.73,-0.78 1.03,0.3 1.7,0.49 2.72,0.78 0.15,0.03 0.27,-0.06 0.27,-0.2v-0.57c0,-0.27 -0.13,-0.52 -0.34,-0.68l-1.37,-1.02v-4.7l6.27,1.97c0.28,0.09 0.55,-0.12 0.55,-0.41v-0.41c0.01,-0.45 -0.23,-0.87 -0.59,-1.09z" - android:fillColor="#FFF"/> - </group> -</vector> + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> +<path + android:fillColor="#FFFFFF" + android:pathData="M21,16v-2l-8-5V3.5C13,2.67,12.33,2,11.5,2S10,2.67,10,3.5V9l-8,5v2l8-2.5V19l-2,1.5V22l3.5-1l3.5,1v-1.5L13,19v-5.5L21,16z" /> +</vector> diff --git a/packages/SystemUI/res/layout-land/global_actions_grid.xml b/packages/SystemUI/res/layout-land/global_actions_grid.xml new file mode 100644 index 000000000000..911b661d48eb --- /dev/null +++ b/packages/SystemUI/res/layout-land/global_actions_grid.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="utf-8"?> +<com.android.systemui.globalactions.GlobalActionsGridLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@id/global_actions_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal" + android:clipToPadding="false" + android:theme="@style/qs_theme" + android:gravity="top|right" + android:clipChildren="false" +> + + <LinearLayout + android:layout_height="match_parent" + android:layout_width="wrap_content" + android:gravity="top|right" + android:padding="0dp" + android:orientation="vertical" + android:layoutDirection="ltr" + android:layout_marginRight="@dimen/global_actions_grid_container_bottom_margin" + > + <!-- Grid of action items --> + <com.android.systemui.globalactions.ListGridLayout + android:id="@android:id/list" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layoutDirection="ltr" + android:layout_marginTop="@dimen/global_actions_grid_side_margin" + android:translationZ="@dimen/global_actions_translate" + android:paddingLeft="@dimen/global_actions_grid_top_padding" + android:paddingRight="@dimen/global_actions_grid_bottom_padding" + android:paddingTop="@dimen/global_actions_grid_left_padding" + android:paddingBottom="@dimen/global_actions_grid_right_padding" + android:background="?android:attr/colorBackgroundFloating" + > + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layoutDirection="ltr" + android:orientation="horizontal" + /> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layoutDirection="ltr" + android:orientation="horizontal" + /> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layoutDirection="ltr" + android:orientation="horizontal" + /> + </com.android.systemui.globalactions.ListGridLayout> + + <!-- For separated items--> + <LinearLayout + android:id="@+id/separated_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/global_actions_grid_side_margin" + android:layout_marginBottom="@dimen/global_actions_grid_side_margin" + android:paddingTop="@dimen/global_actions_grid_left_padding" + android:paddingLeft="@dimen/global_actions_grid_top_padding" + android:paddingBottom="@dimen/global_actions_grid_right_padding" + android:paddingRight="@dimen/global_actions_grid_bottom_padding" + android:orientation="horizontal" + android:layoutDirection="ltr" + android:background="?android:attr/colorBackgroundFloating" + android:translationZ="@dimen/global_actions_translate" + /> + + </LinearLayout> + +</com.android.systemui.globalactions.GlobalActionsGridLayout> diff --git a/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml b/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml new file mode 100644 index 000000000000..669be1b40567 --- /dev/null +++ b/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="utf-8"?> +<com.android.systemui.globalactions.GlobalActionsGridLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@id/global_actions_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal" + android:clipToPadding="false" + android:theme="@style/qs_theme" + android:gravity="top|left" + android:clipChildren="false" +> + + <LinearLayout + android:layout_height="match_parent" + android:layout_width="wrap_content" + android:gravity="bottom|left" + android:padding="0dp" + android:orientation="vertical" + android:layout_marginLeft="@dimen/global_actions_grid_container_bottom_margin" + > + <!-- For separated items--> + <LinearLayout + android:id="@+id/separated_button" + android:layout_gravity="top|left" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/global_actions_grid_side_margin" + android:layout_marginBottom="@dimen/global_actions_grid_side_margin" + android:paddingTop="@dimen/global_actions_grid_left_padding" + android:paddingLeft="@dimen/global_actions_grid_top_padding" + android:paddingBottom="@dimen/global_actions_grid_right_padding" + android:paddingRight="@dimen/global_actions_grid_bottom_padding" + android:orientation="horizontal" + android:layoutDirection="rtl" + android:background="?android:attr/colorBackgroundFloating" + android:translationZ="@dimen/global_actions_translate" + /> + + <!-- Grid of action items --> + <com.android.systemui.globalactions.ListGridLayout + android:id="@android:id/list" + android:layout_gravity="bottom|left" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_marginTop="@dimen/global_actions_grid_side_margin" + android:translationZ="@dimen/global_actions_translate" + android:paddingLeft="@dimen/global_actions_grid_top_padding" + android:paddingRight="@dimen/global_actions_grid_bottom_padding" + android:paddingTop="@dimen/global_actions_grid_left_padding" + android:paddingBottom="@dimen/global_actions_grid_right_padding" + android:background="?android:attr/colorBackgroundFloating" + > + <LinearLayout + android:layout_gravity="bottom" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layoutDirection="rtl" + android:orientation="horizontal" + /> + <LinearLayout + android:layout_gravity="bottom" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layoutDirection="rtl" + android:orientation="horizontal" + /> + <LinearLayout + android:layout_gravity="bottom" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layoutDirection="rtl" + android:orientation="horizontal" + /> + </com.android.systemui.globalactions.ListGridLayout> + </LinearLayout> + +</com.android.systemui.globalactions.GlobalActionsGridLayout> diff --git a/packages/SystemUI/res/layout/global_actions_grid.xml b/packages/SystemUI/res/layout/global_actions_grid.xml index e6f2376ae76b..1b56fa089281 100644 --- a/packages/SystemUI/res/layout/global_actions_grid.xml +++ b/packages/SystemUI/res/layout/global_actions_grid.xml @@ -12,10 +12,11 @@ > <LinearLayout - android:layout_height="290dp" - android:layout_width="412dp" - android:gravity="bottom" + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:gravity="bottom | right" android:padding="0dp" + android:layoutDirection="ltr" android:layout_marginBottom="@dimen/global_actions_grid_container_bottom_margin" > <!-- For separated items--> @@ -34,15 +35,11 @@ android:translationZ="@dimen/global_actions_translate" /> - <Space android:layout_width="match_parent" android:layout_height="2dp" - android:layout_weight="1" /> - <!-- Grid of action items --> <com.android.systemui.globalactions.ListGridLayout android:id="@android:id/list" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:gravity="right" android:orientation="horizontal" android:layoutDirection="rtl" android:layout_marginRight="@dimen/global_actions_grid_side_margin" @@ -56,25 +53,19 @@ <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="bottom|right" android:visibility="gone" - android:gravity="bottom" android:orientation="vertical" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="bottom|right" android:visibility="gone" - android:gravity="bottom" android:orientation="vertical" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="bottom|right" android:visibility="gone" - android:gravity="bottom" android:orientation="vertical" /> </com.android.systemui.globalactions.ListGridLayout> diff --git a/packages/SystemUI/res/layout/global_actions_grid_item.xml b/packages/SystemUI/res/layout/global_actions_grid_item.xml index 0c11cd977256..a8938390690f 100644 --- a/packages/SystemUI/res/layout/global_actions_grid_item.xml +++ b/packages/SystemUI/res/layout/global_actions_grid_item.xml @@ -47,6 +47,7 @@ android:gravity="center" android:textSize="12sp" android:textAppearance="?android:attr/textAppearanceSmall" + android:singleLine="true" /> <TextView @@ -57,5 +58,6 @@ android:gravity="center" android:textColor="?android:attr/textColorTertiary" android:textAppearance="?android:attr/textAppearanceSmall" + android:singleLine="true" /> </LinearLayout> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 7d009b5a9f18..b4131d738d13 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2323,7 +2323,7 @@ <!-- Action for accepting the Ongoing privacy dialog [CHAR LIMIT=10]--> <string name="ongoing_privacy_dialog_ok">Got it</string> - <!-- Action on Ongoing Privacy Dialog to open privacy hub [CHAR LIMIT=20]--> + <!-- Action on Ongoing Privacy Dialog to open privacy hub [CHAR LIMIT=23]--> <string name="ongoing_privacy_dialog_open_settings">Privacy settings</string> <!-- Text for item in Ongoing Privacy Dialog title when only one app is using app ops [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl index 2ff98ba8ab30..37abab9f1ddb 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl @@ -16,19 +16,32 @@ package com.android.systemui.shared.recents; +import android.graphics.Region; +import android.os.Bundle; import android.view.MotionEvent; import com.android.systemui.shared.recents.ISystemUiProxy; oneway interface IOverviewProxy { - void onBind(in ISystemUiProxy sysUiProxy); + + void onActiveNavBarRegionChanges(in Region activeRegion) = 11; + + void onInitialize(in Bundle params) = 12; + + + /** + * @deprecated + */ + void onBind(in ISystemUiProxy sysUiProxy) = 0; /** * Called once immediately prior to the first onMotionEvent() call, providing a hint to the * target the initial source of the subsequent motion events. * * @param downHitTarget is one of the {@link NavigationBarCompat.HitTarget}s + * + * @deprecated */ - void onPreMotionEvent(int downHitTarget); + void onPreMotionEvent(int downHitTarget) = 1; /** * Proxies motion events from the nav bar in SystemUI to the OverviewProxyService. The sender @@ -38,40 +51,48 @@ oneway interface IOverviewProxy { * Quick scrub: DOWN, (MOVE/POINTER_DOWN/POINTER_UP)*, SCRUB_START, SCRUB_PROGRESS*, SCRUB_END * * Once quick scrub is sent, then no further motion events will be provided. + * + * @deprecated */ - void onMotionEvent(in MotionEvent event); + void onMotionEvent(in MotionEvent event) = 2; /** * Sent when the user starts to actively scrub the nav bar to switch tasks. Once this event is * sent the caller will stop sending any motion events and will no longer preemptively cancel * any recents animations started as a part of the motion event handling. + * + * @deprecated */ - void onQuickScrubStart(); + void onQuickScrubStart() = 3; /** * Sent when the user stops actively scrubbing the nav bar to switch tasks. + * + * @deprecated */ - void onQuickScrubEnd(); + void onQuickScrubEnd() = 4; /** * Sent for each movement over the nav bar while the user is scrubbing it to switch tasks. + * + * @deprecated */ - void onQuickScrubProgress(float progress); + void onQuickScrubProgress(float progress) = 5; /** * Sent when overview button is pressed to toggle show/hide of overview. */ - void onOverviewToggle(); + void onOverviewToggle() = 6; /** * Sent when overview is to be shown. */ - void onOverviewShown(boolean triggeredFromAltTab); + void onOverviewShown(boolean triggeredFromAltTab) = 7; /** * Sent when overview is to be hidden. */ - void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey); + void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) = 8; /** * Sent when a user swipes up over the navigation bar to launch overview. Swipe up is determined @@ -83,11 +104,13 @@ oneway interface IOverviewProxy { * visible, this event will still be sent if user swipes up). When this signal is sent, * navigation bar will not handle any gestures such as quick scrub and the home button will * cancel (long) press. + * + * @deprecated */ - void onQuickStep(in MotionEvent event); + void onQuickStep(in MotionEvent event) = 9; /** * Sent when there was an action on one of the onboarding tips view. */ - void onTip(int actionType, int viewType); + void onTip(int actionType, int viewType) = 10; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java index f7ccb816b675..804f4f112c14 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java @@ -16,6 +16,7 @@ package com.android.systemui.shared.system; +import android.os.Bundle; import android.os.Looper; import android.util.Pair; import android.view.BatchedInputEventReceiver; @@ -53,6 +54,16 @@ public class InputChannelCompat { } /** + * Creates a dispatcher from the extras received as part on onInitialize + */ + public static InputEventReceiver fromBundle(Bundle params, String key, + Looper looper, Choreographer choreographer, InputEventListener listener) { + + InputChannel channel = params.getParcelable(key); + return new InputEventReceiver(channel, looper, choreographer, listener); + } + + /** * @see BatchedInputEventReceiver */ public static class InputEventReceiver { @@ -90,7 +101,7 @@ public class InputChannelCompat { private final InputChannel mInputChannel; private final InputEventSender mSender; - private InputEventDispatcher(InputChannel inputChannel, Looper looper) { + public InputEventDispatcher(InputChannel inputChannel, Looper looper) { mInputChannel = inputChannel; mSender = new InputEventSender(inputChannel, looper) { }; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/NavigationBarCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/NavigationBarCompat.java index 69aea2c59029..b363b4a242e1 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/NavigationBarCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/NavigationBarCompat.java @@ -17,31 +17,15 @@ package com.android.systemui.shared.system; import android.annotation.IntDef; -import android.content.Context; -import android.content.res.Resources; -import android.util.DisplayMetrics; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -public class NavigationBarCompat { - /** - * Touch slopes and thresholds for quick step operations. Drag slop is the point where the - * home button press/long press over are ignored and will start to drag when exceeded and the - * touch slop is when the respected operation will occur when exceeded. Touch slop must be - * larger than the drag slop. - */ - public static int getQuickStepDragSlopPx() { - return convertDpToPixel(10); - } - - public static int getQuickStepTouchSlopPx() { - return convertDpToPixel(24); - } +/** + * TODO: Remove this class + */ +public class NavigationBarCompat extends QuickStepContract { - public static int getQuickScrubTouchSlopPx() { - return convertDpToPixel(24); - } @Retention(RetentionPolicy.SOURCE) @IntDef({HIT_TARGET_NONE, HIT_TARGET_BACK, HIT_TARGET_HOME, HIT_TARGET_OVERVIEW}) @@ -75,8 +59,4 @@ public class NavigationBarCompat { * Interaction type: show/hide the overview button while this service is connected to launcher */ public static final int FLAG_SHOW_OVERVIEW_BUTTON = 0x4; - - private static int convertDpToPixel(float dp){ - return (int) (dp * Resources.getSystem().getDisplayMetrics().density); - } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java new file mode 100644 index 000000000000..6d7abd089861 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -0,0 +1,52 @@ +/* + * 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.systemui.shared.system; + +import android.content.res.Resources; + +/** + * Various shared constants between Launcher and SysUI as part of quickstep + */ +public class QuickStepContract { + + public static final String KEY_EXTRA_SYSUI_PROXY = "extra_sysui_proxy"; + public static final String KEY_EXTRA_INPUT_CHANNEL = "extra_input_channel"; + public static final String KEY_EXTRA_WINDOW_CORNER_RADIUS = "extra_window_corner_radius"; + public static final String KEY_EXTRA_SUPPORTS_WINDOW_CORNERS = "extra_supports_window_corners"; + + /** + * Touch slopes and thresholds for quick step operations. Drag slop is the point where the + * home button press/long press over are ignored and will start to drag when exceeded and the + * touch slop is when the respected operation will occur when exceeded. Touch slop must be + * larger than the drag slop. + */ + public static int getQuickStepDragSlopPx() { + return convertDpToPixel(10); + } + + public static int getQuickStepTouchSlopPx() { + return convertDpToPixel(24); + } + + public static int getQuickScrubTouchSlopPx() { + return convertDpToPixel(24); + } + + private static int convertDpToPixel(float dp) { + return (int) (dp * Resources.getSystem().getDisplayMetrics().density); + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 822920e63460..8de84bf4e2af 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -1,10 +1,19 @@ package com.android.keyguard; +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; import android.app.WallpaperManager; import android.content.Context; import android.graphics.Paint; import android.graphics.Paint.Style; +import android.transition.ChangeBounds; +import android.transition.Transition; +import android.transition.TransitionManager; +import android.transition.TransitionValues; import android.util.AttributeSet; +import android.util.MathUtils; +import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -16,6 +25,7 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.colorextraction.ColorExtractor; import com.android.keyguard.clock.ClockManager; import com.android.systemui.Dependency; +import com.android.systemui.Interpolators; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.plugins.ClockPlugin; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -28,6 +38,7 @@ import java.util.TimeZone; */ public class KeyguardClockSwitch extends RelativeLayout { + private final Transition mTransition; /** * Optional/alternative clock injected via plugin. */ @@ -53,6 +64,10 @@ public class KeyguardClockSwitch extends RelativeLayout { * Maintain state so that a newly connected plugin can be initialized. */ private float mDarkAmount; + /** + * If the Keyguard Slice has a header (big center-aligned text.) + */ + private boolean mShowingHeader; private boolean mSupportsDarkText; private int[] mColorPalette; @@ -98,6 +113,7 @@ public class KeyguardClockSwitch extends RelativeLayout { public KeyguardClockSwitch(Context context, AttributeSet attrs) { super(context, attrs); + mTransition = new ClockBoundsTransition(); } /** @@ -286,6 +302,26 @@ public class KeyguardClockSwitch extends RelativeLayout { } } + /** + * Sets if the keyguard slice is showing a center-aligned header. We need a smaller clock + * in these cases. + */ + public void setKeyguardShowingHeader(boolean hasHeader) { + if (mShowingHeader == hasHeader || hasCustomClock()) { + return; + } + mShowingHeader = hasHeader; + + TransitionManager.beginDelayedTransition((ViewGroup) mClockView.getParent(), mTransition); + int fontSize = mContext.getResources().getDimensionPixelSize(mShowingHeader + ? R.dimen.widget_small_font_size : R.dimen.widget_big_font_size); + int paddingBottom = mContext.getResources().getDimensionPixelSize(mShowingHeader + ? R.dimen.widget_vertical_padding_clock : R.dimen.header_subtitle_padding); + mClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize); + mClockView.setPadding(mClockView.getPaddingLeft(), mClockView.getPaddingTop(), + mClockView.getPaddingRight(), paddingBottom); + } + @VisibleForTesting (otherwise = VisibleForTesting.NONE) ClockManager.ClockChangedListener getClockChangedListener() { return mClockChangedListener; @@ -295,4 +331,54 @@ public class KeyguardClockSwitch extends RelativeLayout { StatusBarStateController.StateListener getStateListener() { return mStateListener; } + + /** + * Special layout transition that scales the clock view as its bounds change, to make it look + * like the text is shrinking. + */ + private class ClockBoundsTransition extends ChangeBounds { + + ClockBoundsTransition() { + setDuration(KeyguardSliceView.DEFAULT_ANIM_DURATION / 2); + setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); + } + + @Override + public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, + TransitionValues endValues) { + Animator animator = super.createAnimator(sceneRoot, startValues, endValues); + if (animator == null || startValues.view != mClockView) { + return animator; + } + + ValueAnimator boundsAnimator = null; + if (animator instanceof AnimatorSet) { + Animator first = ((AnimatorSet) animator).getChildAnimations().get(0); + if (first instanceof ValueAnimator) { + boundsAnimator = (ValueAnimator) first; + } + } else if (animator instanceof ValueAnimator) { + boundsAnimator = (ValueAnimator) animator; + } + + if (boundsAnimator != null) { + float bigFontSize = mContext.getResources() + .getDimensionPixelSize(R.dimen.widget_big_font_size); + float smallFontSize = mContext.getResources() + .getDimensionPixelSize(R.dimen.widget_small_font_size); + float startScale = mShowingHeader + ? bigFontSize / smallFontSize : smallFontSize / bigFontSize; + boundsAnimator.addUpdateListener(animation -> { + float scale = MathUtils.lerp(startScale, 1f /* stop */, + animation.getAnimatedFraction()); + mClockView.setPivotX(mClockView.getWidth() / 2); + mClockView.setPivotY(0); + mClockView.setScaleX(scale); + mClockView.setScaleY(scale); + }); + } + + return animator; + } + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index bac7844c024b..2040a76b61d3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -37,10 +37,10 @@ import android.text.TextUtils.TruncateAt; import android.util.AttributeSet; import android.util.Log; import android.view.View; -import android.view.ViewGroup; import android.view.animation.Animation; import android.widget.Button; import android.widget.LinearLayout; +import android.widget.TextView; import androidx.lifecycle.LiveData; import androidx.lifecycle.Observer; @@ -58,6 +58,7 @@ import com.android.internal.graphics.ColorUtils; import com.android.settingslib.Utils; import com.android.systemui.Dependency; import com.android.systemui.Interpolators; +import com.android.systemui.R; import com.android.systemui.keyguard.KeyguardSliceProvider; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.tuner.TunerService; @@ -78,6 +79,8 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe private final HashMap<View, PendingIntent> mClickActions; private Uri mKeyguardSliceUri; + @VisibleForTesting + TextView mTitle; private Row mRow; private int mTextColor; private float mDarkAmount = 0; @@ -91,6 +94,8 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe private Runnable mContentChangeListener; private Slice mSlice; private boolean mHasHeader; + private final int mRowWithHeaderPadding; + private final int mRowPadding; public KeyguardSliceView(Context context) { this(context, null, 0); @@ -107,6 +112,9 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe tunerService.addTunable(this, Settings.Secure.KEYGUARD_SLICE_URI); mClickActions = new HashMap<>(); + mRowPadding = context.getResources().getDimensionPixelSize(R.dimen.subtitle_clock_padding); + mRowWithHeaderPadding = context.getResources() + .getDimensionPixelSize(R.dimen.header_subtitle_padding); LayoutTransition transition = new LayoutTransition(); transition.setStagger(LayoutTransition.CHANGE_APPEARING, DEFAULT_ANIM_DURATION / 2); @@ -117,13 +125,13 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe transition.setInterpolator(LayoutTransition.APPEARING, Interpolators.FAST_OUT_SLOW_IN); transition.setInterpolator(LayoutTransition.DISAPPEARING, Interpolators.ALPHA_OUT); transition.setAnimateParentHierarchy(false); - transition.addTransitionListener(new SliceViewTransitionListener()); setLayoutTransition(transition); } @Override protected void onFinishInflate() { super.onFinishInflate(); + mTitle = findViewById(R.id.title); mRow = findViewById(R.id.row); mTextColor = Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor); mIconSize = (int) mContext.getResources().getDimension(R.dimen.widget_icon_size); @@ -160,6 +168,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe private void showSlice() { Trace.beginSection("KeyguardSliceView#showSlice"); if (mSlice == null) { + mTitle.setVisibility(GONE); mRow.setVisibility(GONE); mHasHeader = false; if (mContentChangeListener != null) { @@ -170,8 +179,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe ListContent lc = new ListContent(getContext(), mSlice); SliceContent headerContent = lc.getHeader(); - mHasHeader = headerContent != null - && !headerContent.getSliceItem().hasHint(HINT_LIST_ITEM); + mHasHeader = headerContent != null && !headerContent.getSliceItem().hasHint(HINT_LIST_ITEM); List<SliceContent> subItems = new ArrayList<>(); for (int i = 0; i < lc.getRowItems().size(); i++) { SliceContent subItem = lc.getRowItems().get(i); @@ -181,12 +189,26 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe subItems.add(subItem); } } + if (!mHasHeader) { + mTitle.setVisibility(GONE); + } else { + mTitle.setVisibility(VISIBLE); + + RowContent header = lc.getHeader(); + SliceItem mainTitle = header.getTitleItem(); + CharSequence title = mainTitle != null ? mainTitle.getText() : null; + mTitle.setText(title); + } mClickActions.clear(); final int subItemsCount = subItems.size(); final int blendedColor = getTextColor(); final int startIndex = mHasHeader ? 1 : 0; // First item is header; skip it mRow.setVisibility(subItemsCount > 0 ? VISIBLE : GONE); + LinearLayout.LayoutParams layoutParams = (LayoutParams) mRow.getLayoutParams(); + layoutParams.topMargin = mHasHeader ? mRowWithHeaderPadding : mRowPadding; + mRow.setLayoutParams(layoutParams); + for (int i = startIndex; i < subItemsCount; i++) { RowContent rc = (RowContent) subItems.get(i); SliceItem item = rc.getSliceItem(); @@ -250,6 +272,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe private void updateTextColors() { final int blendedColor = getTextColor(); + mTitle.setTextColor(blendedColor); int childCount = mRow.getChildCount(); for (int i = 0; i < childCount; i++) { View v = mRow.getChildAt(i); @@ -294,7 +317,10 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe setupUri(newValue); } - private void setupUri(String uriString) { + /** + * Sets the slice provider Uri. + */ + public void setupUri(String uriString) { if (uriString == null) { uriString = KeyguardSliceProvider.KEYGUARD_SLICE_URI; } @@ -512,29 +538,4 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe } } } - - private class SliceViewTransitionListener implements LayoutTransition.TransitionListener { - @Override - public void startTransition(LayoutTransition transition, ViewGroup container, View view, - int transitionType) { - switch (transitionType) { - case LayoutTransition.APPEARING: - int translation = getResources().getDimensionPixelSize( - R.dimen.pulsing_notification_appear_translation); - view.setTranslationY(translation); - view.animate() - .translationY(0) - .setDuration(DEFAULT_ANIM_DURATION) - .setInterpolator(Interpolators.ALPHA_IN) - .start(); - break; - } - } - - @Override - public void endTransition(LayoutTransition transition, ViewGroup container, View view, - int transitionType) { - - } - } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java index bb549ada6830..b0670fd0a91a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -16,14 +16,11 @@ package com.android.keyguard; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.app.ActivityManager; import android.app.IActivityManager; import android.content.Context; import android.content.res.Resources; import android.graphics.Color; -import android.graphics.Paint; import android.os.Handler; import android.os.Looper; import android.os.RemoteException; @@ -37,15 +34,12 @@ import android.util.Slog; import android.util.TypedValue; import android.view.View; import android.widget.GridLayout; -import android.widget.LinearLayout; import android.widget.TextView; import androidx.core.graphics.ColorUtils; import com.android.internal.widget.LockPatternUtils; -import com.android.internal.widget.ViewClippingUtil; import com.android.systemui.Dependency; -import com.android.systemui.Interpolators; import com.android.systemui.statusbar.policy.ConfigurationController; import com.google.android.collect.Sets; @@ -54,14 +48,13 @@ import java.util.Locale; import java.util.TimeZone; public class KeyguardStatusView extends GridLayout implements - ConfigurationController.ConfigurationListener, View.OnLayoutChangeListener { + ConfigurationController.ConfigurationListener { private static final boolean DEBUG = KeyguardConstants.DEBUG; private static final String TAG = "KeyguardStatusView"; private static final int MARQUEE_DELAY_MS = 2000; private final LockPatternUtils mLockPatternUtils; private final IActivityManager mIActivityManager; - private final float mSmallClockScale; private TextView mLogoutView; private KeyguardClockSwitch mClockView; @@ -74,8 +67,6 @@ public class KeyguardStatusView extends GridLayout implements private boolean mPulsing; private float mDarkAmount = 0; private int mTextColor; - private int mLastLayoutHeight; - private int mSmallClockPadding; private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { @@ -135,8 +126,6 @@ public class KeyguardStatusView extends GridLayout implements mIActivityManager = ActivityManager.getService(); mLockPatternUtils = new LockPatternUtils(getContext()); mHandler = new Handler(Looper.myLooper()); - mSmallClockScale = getResources().getDimension(R.dimen.widget_small_font_size) - / getResources().getDimension(R.dimen.widget_big_font_size); onDensityOrFontScaleChanged(); } @@ -189,9 +178,6 @@ public class KeyguardStatusView extends GridLayout implements mVisibleInDoze = Sets.newArraySet(mClockView, mKeyguardSlice); mTextColor = mClockView.getCurrentTextColor(); - int clockStroke = getResources().getDimensionPixelSize(R.dimen.widget_small_font_stroke); - mClockView.getPaint().setStrokeWidth(clockStroke); - mClockView.addOnLayoutChangeListener(this); mKeyguardSlice.setContentChangeListener(this::onSliceContentChanged); onSliceContentChanged(); @@ -207,72 +193,20 @@ public class KeyguardStatusView extends GridLayout implements * Moves clock, adjusting margins when slice content changes. */ private void onSliceContentChanged() { - LinearLayout.LayoutParams layoutParams = - (LinearLayout.LayoutParams) mClockView.getLayoutParams(); - layoutParams.bottomMargin = mKeyguardSlice.hasHeader() ? mSmallClockPadding : 0; - mClockView.setLayoutParams(layoutParams); - } - - /** - * Animate clock when necessary. - */ - @Override - public void onLayoutChange(View view, int left, int top, int right, int bottom, - int oldLeft, int oldTop, int oldRight, int oldBottom) { - boolean smallClock = mKeyguardSlice.hasHeader(); - int heightOffset = smallClock ? 0 : getHeight() - mLastLayoutHeight; - long duration = KeyguardSliceView.DEFAULT_ANIM_DURATION; - long delay = smallClock ? 0 : duration / 4; - - boolean shouldAnimate = mKeyguardSlice.getLayoutTransition() != null - && mKeyguardSlice.getLayoutTransition().isRunning(); - if (view == mClockView) { - float clockScale = smallClock ? mSmallClockScale : 1; - Paint.Style style = smallClock ? Paint.Style.FILL_AND_STROKE : Paint.Style.FILL; - mClockView.animate().cancel(); - if (shouldAnimate) { - mClockView.setY(oldTop + heightOffset); - mClockView.animate() - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .setDuration(duration) - .setListener(new ClipChildrenAnimationListener()) - .setStartDelay(delay) - .y(top) - .scaleX(clockScale) - .scaleY(clockScale) - .withEndAction(() -> { - mClockView.setStyle(style); - mClockView.invalidate(); - }) - .start(); - } else { - mClockView.setY(top); - mClockView.setScaleX(clockScale); - mClockView.setScaleY(clockScale); - mClockView.setStyle(style); - mClockView.invalidate(); - } - } + mClockView.setKeyguardShowingHeader(mKeyguardSlice.hasHeader()); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - mClockView.setPivotX(mClockView.getWidth() / 2); - mClockView.setPivotY(0); - mLastLayoutHeight = getHeight(); layoutOwnerInfo(); } @Override public void onDensityOrFontScaleChanged() { - mSmallClockPadding = getResources() - .getDimensionPixelSize(R.dimen.widget_small_clock_padding); if (mClockView != null) { mClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimensionPixelSize(R.dimen.widget_big_font_size)); - mClockView.getPaint().setStrokeWidth( - getResources().getDimensionPixelSize(R.dimen.widget_small_font_stroke)); } if (mOwnerInfo != null) { mOwnerInfo.setTextSize(TypedValue.COMPLEX_UNIT_PX, @@ -461,24 +395,4 @@ public class KeyguardStatusView extends GridLayout implements Log.e(TAG, "Failed to logout user", re); } } - - private class ClipChildrenAnimationListener extends AnimatorListenerAdapter implements - ViewClippingUtil.ClippingParameters { - - ClipChildrenAnimationListener() { - ViewClippingUtil.setClippingDeactivated(mClockView, true /* deactivated */, - this /* clippingParams */); - } - - @Override - public void onAnimationEnd(Animator animation) { - ViewClippingUtil.setClippingDeactivated(mClockView, false /* deactivated */, - this /* clippingParams */); - } - - @Override - public boolean shouldFinish(View view) { - return view == getParent(); - } - } } diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java index 95981427b642..078108d658ee 100644 --- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java +++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java @@ -15,6 +15,7 @@ */ package com.android.keyguard.clock; +import android.annotation.Nullable; import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; @@ -25,16 +26,18 @@ import android.os.Looper; import android.provider.Settings; import android.view.LayoutInflater; +import androidx.annotation.VisibleForTesting; + import com.android.keyguard.R; +import com.android.systemui.dock.DockManager; +import com.android.systemui.dock.DockManager.DockEventListener; import com.android.systemui.plugins.ClockPlugin; import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.ExtensionController.Extension; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.function.Consumer; -import java.util.function.Supplier; import javax.inject.Inject; import javax.inject.Singleton; @@ -45,7 +48,6 @@ import javax.inject.Singleton; @Singleton public final class ClockManager { - private final LayoutInflater mLayoutInflater; private final ContentResolver mContentResolver; private final List<ClockInfo> mClockInfos = new ArrayList<>(); @@ -62,7 +64,6 @@ public final class ClockManager { } } }; - private final ExtensionController mExtensionController; /** * Used to select between plugin or default implementations of ClockPlugin interface. @@ -72,13 +73,35 @@ public final class ClockManager { * Consumer that accepts the a new ClockPlugin implementation when the Extension reloads. */ private final Consumer<ClockPlugin> mClockPluginConsumer = this::setClockPlugin; + /** + * Supplier of default ClockPlugin implementation. + */ + private final DefaultClockSupplier mDefaultClockSupplier; + /** + * Observe changes to dock state to know when to switch the clock face. + */ + private final DockEventListener mDockEventListener = + new DockEventListener() { + @Override + public void onEvent(int event) { + final boolean isDocked = (event == DockManager.STATE_DOCKED + || event == DockManager.STATE_DOCKED_HIDE); + mDefaultClockSupplier.setDocked(isDocked); + if (mClockExtension != null) { + mClockExtension.reload(); + } + } + }; + @Nullable + private final DockManager mDockManager; private final List<ClockChangedListener> mListeners = new ArrayList<>(); @Inject - public ClockManager(Context context, ExtensionController extensionController) { + public ClockManager(Context context, ExtensionController extensionController, + @Nullable DockManager dockManager) { mExtensionController = extensionController; - mLayoutInflater = LayoutInflater.from(context); + mDockManager = dockManager; mContentResolver = context.getContentResolver(); Resources res = context.getResources(); @@ -110,6 +133,9 @@ public final class ClockManager { .setThumbnail(() -> BitmapFactory.decodeResource(res, R.drawable.type_thumbnail)) .setPreview(() -> BitmapFactory.decodeResource(res, R.drawable.type_preview)) .build()); + + mDefaultClockSupplier = new DefaultClockSupplier(new SettingsWrapper(mContentResolver), + LayoutInflater.from(context)); } /** @@ -154,41 +180,32 @@ public final class ClockManager { mContentResolver.registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE), false, mContentObserver); + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.DOCKED_CLOCK_FACE), + false, mContentObserver); + if (mDockManager != null) { + mDockManager.addListener(mDockEventListener); + } mClockExtension = mExtensionController.newExtension(ClockPlugin.class) .withPlugin(ClockPlugin.class) .withCallback(mClockPluginConsumer) - // Using withDefault even though this isn't the default as a workaround. - // ExtensionBuilder doesn't provide the ability to supply a ClockPlugin - // instance based off of the value of a setting. Since multiple "default" - // can be provided, using a supplier that changes the settings value. - // A null return will cause Extension#reload to look at the next "default" - // supplier. - .withDefault( - new SettingsGattedSupplier( - mContentResolver, - Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, - BubbleClockController.class.getName(), - () -> BubbleClockController.build(mLayoutInflater))) - .withDefault( - new SettingsGattedSupplier( - mContentResolver, - Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, - StretchAnalogClockController.class.getName(), - () -> StretchAnalogClockController.build(mLayoutInflater))) - .withDefault( - new SettingsGattedSupplier( - mContentResolver, - Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, - TypeClockController.class.getName(), - () -> TypeClockController.build(mLayoutInflater))) + .withDefault(mDefaultClockSupplier) .build(); } private void unregister() { mContentResolver.unregisterContentObserver(mContentObserver); + if (mDockManager != null) { + mDockManager.removeListener(mDockEventListener); + } mClockExtension.destroy(); } + @VisibleForTesting + boolean isDocked() { + return mDefaultClockSupplier.isDocked(); + } + /** * Listener for events that should cause the custom clock face to change. */ @@ -200,44 +217,4 @@ public final class ClockManager { */ void onClockChanged(ClockPlugin clock); } - - /** - * Supplier that only gets an instance when a settings value matches expected value. - */ - private static class SettingsGattedSupplier implements Supplier<ClockPlugin> { - - private final ContentResolver mContentResolver; - private final String mKey; - private final String mValue; - private final Supplier<ClockPlugin> mSupplier; - - /** - * Constructs a supplier that changes secure setting key against value. - * - * @param contentResolver Used to look up settings value. - * @param key Settings key. - * @param value If the setting matches this values that get supplies a ClockPlugin - * instance. - * @param supplier Supplier of ClockPlugin instance, only used if the setting - * matches value. - */ - SettingsGattedSupplier(ContentResolver contentResolver, String key, String value, - Supplier<ClockPlugin> supplier) { - mContentResolver = contentResolver; - mKey = key; - mValue = value; - mSupplier = supplier; - } - - /** - * Returns null if the settings value doesn't match the expected value. - * - * A null return causes Extension#reload to skip this supplier and move to the next. - */ - @Override - public ClockPlugin get() { - final String currentValue = Settings.Secure.getString(mContentResolver, mKey); - return Objects.equals(currentValue, mValue) ? mSupplier.get() : null; - } - } } diff --git a/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockSupplier.java b/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockSupplier.java new file mode 100644 index 000000000000..7fdd2357bc8e --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockSupplier.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.keyguard.clock; + +import android.util.ArrayMap; +import android.view.LayoutInflater; + +import com.android.systemui.plugins.ClockPlugin; + +import java.util.Map; +import java.util.function.Supplier; + +/** + * Supplier that only gets an instance when a settings value matches expected value. + */ +public class DefaultClockSupplier implements Supplier<ClockPlugin> { + + private final SettingsWrapper mSettingsWrapper; + /** + * Map from expected value stored in settings to supplier of custom clock face. + */ + private final Map<String, Supplier<ClockPlugin>> mClocks = new ArrayMap<>(); + /** + * When docked, the DOCKED_CLOCK_FACE setting will be checked for the custom clock face + * to show. + */ + private boolean mIsDocked; + + /** + * Constructs a supplier that changes secure setting key against value. + * + * @param settingsWrapper Wrapper around settings used to look up the custom clock face. + * @param layoutInflater Provided to clocks as dependency to inflate clock views. + */ + public DefaultClockSupplier(SettingsWrapper settingsWrapper, LayoutInflater layoutInflater) { + mSettingsWrapper = settingsWrapper; + + mClocks.put(BubbleClockController.class.getName(), + () -> BubbleClockController.build(layoutInflater)); + mClocks.put(StretchAnalogClockController.class.getName(), + () -> StretchAnalogClockController.build(layoutInflater)); + mClocks.put(TypeClockController.class.getName(), + () -> TypeClockController.build(layoutInflater)); + } + + /** + * Sets the dock state. + * + * @param isDocked True when docked, false otherwise. + */ + public void setDocked(boolean isDocked) { + mIsDocked = isDocked; + } + + boolean isDocked() { + return mIsDocked; + } + + /** + * Get the custom clock face based on values in settings. + * + * @return Custom clock face, null if the settings value doesn't match a custom clock. + */ + @Override + public ClockPlugin get() { + ClockPlugin plugin = null; + if (mIsDocked) { + final String name = mSettingsWrapper.getDockedClockFace(); + if (name != null) { + Supplier<ClockPlugin> supplier = mClocks.get(name); + if (supplier != null) { + plugin = supplier.get(); + if (plugin != null) { + return plugin; + } + } + } + } + final String name = mSettingsWrapper.getLockScreenCustomClockFace(); + if (name != null) { + Supplier<ClockPlugin> supplier = mClocks.get(name); + if (supplier != null) { + plugin = supplier.get(); + } + } + return plugin; + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/clock/SettingsWrapper.java b/packages/SystemUI/src/com/android/keyguard/clock/SettingsWrapper.java new file mode 100644 index 000000000000..58e11553af9d --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/clock/SettingsWrapper.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.keyguard.clock; + +import android.content.ContentResolver; +import android.provider.Settings; + +/** + * Wrapper around Settings used for testing. + */ +public class SettingsWrapper { + + private static final String CUSTOM_CLOCK_FACE = Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE; + private static final String DOCKED_CLOCK_FACE = Settings.Secure.DOCKED_CLOCK_FACE; + + private ContentResolver mContentResolver; + + public SettingsWrapper(ContentResolver contentResolver) { + mContentResolver = contentResolver; + } + + /** + * Gets the value stored in settings for the custom clock face. + */ + public String getLockScreenCustomClockFace() { + return Settings.Secure.getString(mContentResolver, CUSTOM_CLOCK_FACE); + } + + /** + * Gets the value stored in settings for the clock face to use when docked. + */ + public String getDockedClockFace() { + return Settings.Secure.getString(mContentResolver, DOCKED_CLOCK_FACE); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/DependencyBinder.java index ce9c637cebf6..3c6f081d0373 100644 --- a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java +++ b/packages/SystemUI/src/com/android/systemui/DependencyBinder.java @@ -21,11 +21,13 @@ import com.android.systemui.appops.AppOpsControllerImpl; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.VolumeDialogController; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.PowerNotificationWarnings; import com.android.systemui.power.PowerUI; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QSTileHost; import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl; import com.android.systemui.statusbar.phone.ManagedProfileController; import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl; @@ -194,6 +196,12 @@ public abstract class DependencyBinder { /** */ @Binds + public abstract StatusBarStateController provideStatusBarStateController( + StatusBarStateControllerImpl controllerImpl); + + /** + */ + @Binds public abstract StatusBarIconController provideStatusBarIconController( StatusBarIconControllerImpl controllerImpl); diff --git a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java index e28aa9d369cb..2a1d066d356e 100644 --- a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java +++ b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java @@ -23,7 +23,6 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; -import android.content.res.Configuration; import android.provider.Settings; import android.util.AttributeSet; import android.view.Gravity; @@ -59,7 +58,6 @@ public class HardwareUiLayout extends MultiListLayout implements Tunable { private int mEndPoint; private boolean mEdgeBleed; private boolean mRoundedDivider; - private int mRotation = ROTATION_NONE; private boolean mRotatedBackground; private boolean mSwapOrientation = true; @@ -89,7 +87,7 @@ public class HardwareUiLayout extends MultiListLayout implements Tunable { } @Override - public ViewGroup getParentView(boolean separated, int index) { + public ViewGroup getParentView(boolean separated, int index, boolean reverse) { if (separated) { return getSeparatedView(); } else { @@ -174,7 +172,6 @@ public class HardwareUiLayout extends MultiListLayout implements Tunable { mSeparatedView.setBackground(mSeparatedViewBackground); updateEdgeMargin(mEdgeBleed ? 0 : getEdgePadding()); mOldHeight = mList.getMeasuredHeight(); - updateRotation(); } else { return; } @@ -188,25 +185,13 @@ public class HardwareUiLayout extends MultiListLayout implements Tunable { post(() -> updatePosition()); } - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - updateRotation(); - } - public void setSwapOrientation(boolean swapOrientation) { mSwapOrientation = swapOrientation; } - private void updateRotation() { - int rotation = RotationUtils.getRotation(getContext()); - if (rotation != mRotation) { - rotate(mRotation, rotation); - mRotation = rotation; - } - } - - private void rotate(int from, int to) { + @Override + protected void rotate(int from, int to) { + super.rotate(from, to); if (from != ROTATION_NONE && to != ROTATION_NONE) { // Rather than handling this confusing case, just do 2 rotations. rotate(from, ROTATION_NONE); diff --git a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java index 85265f458370..00ff518ce212 100644 --- a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java +++ b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java @@ -17,11 +17,14 @@ package com.android.systemui; import android.content.Context; +import android.content.res.Configuration; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; +import com.android.systemui.util.leak.RotationUtils; + /** * Layout class representing the Global Actions menu which appears when the power button is held. */ @@ -32,8 +35,12 @@ public abstract class MultiListLayout extends LinearLayout { protected int mExpectedSeparatedItemCount; protected int mExpectedListItemCount; + protected int mRotation; + protected RotationListener mRotationListener; + public MultiListLayout(Context context, AttributeSet attrs) { super(context, attrs); + mRotation = RotationUtils.getRotation(context); } protected abstract ViewGroup getSeparatedView(); @@ -50,10 +57,12 @@ public abstract class MultiListLayout extends LinearLayout { * @param separated Whether or not this index refers to a position in the separated or list * container. * @param index The index of the item within the container. + * @param reverse If the MultiListLayout contains sub-lists within the list container, reverse + * the order that they are filled. * @return The parent ViewGroup which will be used to contain the specified item * after it has been added to the layout. */ - public abstract ViewGroup getParentView(boolean separated, int index); + public abstract ViewGroup getParentView(boolean separated, int index, boolean reverse); /** * Sets the divided view, which may have a differently-colored background. @@ -111,6 +120,26 @@ public abstract class MultiListLayout extends LinearLayout { setFocusable(true); } + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + int newRotation = RotationUtils.getRotation(mContext); + if (newRotation != mRotation) { + rotate(mRotation, newRotation); + mRotation = newRotation; + } + } + + protected void rotate(int from, int to) { + if (mRotationListener != null) { + mRotationListener.onRotate(from, to); + } + } + + public void setRotationListener(RotationListener listener) { + mRotationListener = listener; + } + /** * Retrieve the MultiListLayout associated with the given view. */ @@ -121,4 +150,8 @@ public abstract class MultiListLayout extends LinearLayout { } return null; } + + interface RotationListener { + void onRotate(int from, int to); + } } diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 755d6fcffb43..6d583df6082b 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -31,6 +31,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.assist.AssistManager; import com.android.systemui.classifier.FalsingManager; +import com.android.systemui.dock.DockManager; import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -41,7 +42,6 @@ import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl; import com.android.systemui.statusbar.ScrimView; -import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.collection.NotificationData; @@ -153,15 +153,6 @@ public class SystemUIFactory { return new VolumeDialogComponent(systemUi, context); } - /** - * Provides status bar state controller implementation - */ - @Singleton - @Provides - public StatusBarStateController provideStatusBarStateController(Context context) { - return new StatusBarStateControllerImpl(); - } - @Singleton @Provides public NotificationData.KeyguardEnvironment provideKeyguardEnvironment(Context context) { @@ -228,6 +219,16 @@ public class SystemUIFactory { return SysUiServiceProvider.getComponent(context, StatusBar.class); } + /** + * Provides DockManager. + */ + @Singleton + @Provides + @Nullable + public DockManager providesDockManager(Context context) { + return SysUiServiceProvider.getComponent(context, DockManager.class); + } + @Module protected static class ContextHolder { private Context mContext; diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java index 4d708908975e..038f4912e40f 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java @@ -290,10 +290,6 @@ public class AssistManager implements ConfigurationChangedReceiver { return mAssistUtils.isSessionRunning(); } - public void destroy() { - mWindowManager.removeViewImmediate(mView); - } - private void maybeSwapSearchIcon(@NonNull ComponentName assistComponent, boolean isService) { replaceDrawable(mView.getOrb().getLogo(), assistComponent, ASSIST_ICON_METADATA_NAME, isService); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java new file mode 100644 index 000000000000..4778434e2052 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java @@ -0,0 +1,55 @@ +/* + * 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.systemui.bubbles; + + +import android.view.LayoutInflater; + +import com.android.systemui.R; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; + +/** + * Encapsulates the data and UI elements of a bubble. + */ +class Bubble { + + public BubbleView iconView; + public BubbleExpandedView expandedView; + public String key; + public NotificationEntry entry; + + Bubble(NotificationEntry e, LayoutInflater inflater, BubbleStackView stackView, + BubbleExpandedView.OnBubbleBlockedListener listener) { + entry = e; + key = entry.key; + + iconView = (BubbleView) inflater.inflate( + R.layout.bubble_view, stackView, false /* attachToRoot */); + iconView.setNotif(entry); + + expandedView = (BubbleExpandedView) inflater.inflate( + R.layout.bubble_expanded_view, stackView, false /* attachToRoot */); + expandedView.setEntry(entry, stackView); + + expandedView.setOnBlockedListener(listener); + } + + public void setEntry(NotificationEntry entry) { + key = entry.key; + iconView.update(entry); + // TODO: should also update the expanded view here (e.g. height change) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 41bc1b29075c..a67e1b7032c6 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -16,10 +16,6 @@ package com.android.systemui.bubbles; -import static android.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS; -import static android.util.StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING; -import static android.util.StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE; -import static android.util.StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__DOCUMENT_LAUNCH_NOT_ALWAYS; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; @@ -33,21 +29,14 @@ import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; import android.app.INotificationManager; import android.app.Notification; -import android.app.PendingIntent; import android.content.Context; -import android.content.pm.ActivityInfo; -import android.graphics.Point; import android.graphics.Rect; import android.os.RemoteException; import android.os.ServiceManager; import android.provider.Settings; import android.service.notification.StatusBarNotification; -import android.util.Log; -import android.util.StatsLog; import android.view.Display; -import android.view.LayoutInflater; import android.view.ViewGroup; -import android.view.WindowManager; import android.widget.FrameLayout; import androidx.annotation.MainThread; @@ -66,10 +55,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationInflater; import com.android.systemui.statusbar.phone.StatusBarWindowController; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; @@ -105,11 +91,9 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe private final BubbleTaskStackListener mTaskStackListener; private BubbleStateChangeListener mStateChangeListener; private BubbleExpandListener mExpandListener; - private LayoutInflater mInflater; - private final Map<String, BubbleView> mBubbles = new HashMap<>(); + private BubbleData mBubbleData; private BubbleStackView mStackView; - private final Point mDisplaySize; // Bubbles get added to the status bar view private final StatusBarWindowController mStatusBarWindowController; @@ -168,10 +152,6 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe @Inject public BubbleController(Context context, StatusBarWindowController statusBarWindowController) { mContext = context; - WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - mDisplaySize = new Point(); - wm.getDefaultDisplay().getSize(mDisplaySize); - mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mNotificationEntryManager = Dependency.get(NotificationEntryManager.class); mNotificationEntryManager.addNotificationEntryListener(mEntryListener); @@ -190,6 +170,8 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe mActivityTaskManager = ActivityTaskManager.getService(); mTaskStackListener = new BubbleTaskStackListener(); ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); + + mBubbleData = BubbleData.getInstance(); } /** @@ -219,8 +201,11 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe * screen (e.g. if on AOD). */ public boolean hasBubbles() { - for (BubbleView bv : mBubbles.values()) { - if (!bv.getEntry().isBubbleDismissed()) { + if (mStackView == null) { + return false; + } + for (Bubble bubble : mBubbleData.getBubbles()) { + if (!bubble.entry.isBubbleDismissed()) { return true; } } @@ -250,10 +235,6 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe if (mStackView == null) { return; } - Set<String> keys = mBubbles.keySet(); - for (String key: keys) { - mBubbles.get(key).getEntry().setBubbleDismissed(true); - } mStackView.stackDismissed(); updateVisibility(); @@ -267,10 +248,9 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe * @param updatePosition whether this update should promote the bubble to the top of the stack. */ public void updateBubble(NotificationEntry notif, boolean updatePosition) { - if (mBubbles.containsKey(notif.key)) { + if (mStackView != null && mBubbleData.getBubble(notif.key) != null) { // It's an update - BubbleView bubble = mBubbles.get(notif.key); - mStackView.updateBubble(bubble, notif, updatePosition); + mStackView.updateBubble(notif, updatePosition); } else { if (mStackView == null) { mStackView = new BubbleStackView(mContext); @@ -286,15 +266,7 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe mStackView.setOnBlockedListener(this); } // It's new - BubbleView bubble = (BubbleView) mInflater.inflate( - R.layout.bubble_view, mStackView, false /* attachToRoot */); - bubble.setNotif(notif); - PendingIntent bubbleIntent = getValidBubbleIntent(notif); - if (bubbleIntent != null) { - bubble.setBubbleIntent(bubbleIntent); - } - mBubbles.put(bubble.getKey(), bubble); - mStackView.addBubble(bubble); + mStackView.addBubble(notif); } updateVisibility(); } @@ -306,25 +278,18 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe */ @MainThread void removeBubble(String key) { - BubbleView bv = mBubbles.remove(key); - if (mStackView != null && bv != null) { - mStackView.removeBubble(bv); - bv.destroyActivityView(mStackView); - } - - NotificationEntry entry = bv != null ? bv.getEntry() : null; - if (entry != null) { - entry.setBubbleDismissed(true); - mNotificationEntryManager.updateNotifications(); + if (mStackView != null) { + mStackView.removeBubble(key); } + mNotificationEntryManager.updateNotifications(); updateVisibility(); } @Override public void onBubbleBlocked(NotificationEntry entry) { - Object[] bubbles = mBubbles.values().toArray(); + Object[] bubbles = mBubbleData.getBubbles().toArray(); for (int i = 0; i < bubbles.length; i++) { - NotificationEntry e = ((BubbleView) bubbles[i]).getEntry(); + NotificationEntry e = ((Bubble) bubbles[i]).entry; boolean samePackage = entry.notification.getPackageName().equals( e.notification.getPackageName()); if (samePackage) { @@ -368,10 +333,8 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe && alertAgain(entry, entry.notification.getNotification())) { entry.setShowInShadeWhenBubble(true); entry.setBubbleDismissed(false); // updates come back as bubbles even if dismissed - if (mBubbles.containsKey(entry.key)) { - mBubbles.get(entry.key).updateDotVisibility(); - } updateBubble(entry, true /* updatePosition */); + mStackView.updateDotVisibility(entry.key); } } @@ -383,8 +346,8 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe return; } entry.setShowInShadeWhenBubble(false); - if (mBubbles.containsKey(entry.key)) { - mBubbles.get(entry.key).updateDotVisibility(); + if (mStackView != null) { + mStackView.updateDotVisibility(entry.key); } if (!removedByUser) { // This was a cancel so we should remove the bubble @@ -441,65 +404,6 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe return mStackView; } - @Nullable - private PendingIntent getValidBubbleIntent(NotificationEntry notif) { - Notification notification = notif.notification.getNotification(); - String packageName = notif.notification.getPackageName(); - Notification.BubbleMetadata data = notif.getBubbleMetadata(); - if (data != null && canLaunchInActivityView(data.getIntent(), - true /* enable logging for bubbles */, packageName)) { - return data.getIntent(); - } - if (shouldUseContentIntent(mContext) - && canLaunchInActivityView(notification.contentIntent, - false /* disable logging for notifications */, packageName)) { - Log.d(TAG, "[addBubble " + notif.key - + "]: No appOverlayIntent, using contentIntent."); - return notification.contentIntent; - } - Log.d(TAG, "[addBubble " + notif.key + "]: No supported intent for ActivityView."); - return null; - } - - /** - * Whether an intent is properly configured to display in an {@link android.app.ActivityView}. - * - * @param intent the pending intent of the bubble. - * @param enableLogging whether bubble developer error should be logged. - * @param packageName the notification package name for this bubble. - * @return - */ - private boolean canLaunchInActivityView(PendingIntent intent, boolean enableLogging, - String packageName) { - if (intent == null) { - return false; - } - ActivityInfo info = - intent.getIntent().resolveActivityInfo(mContext.getPackageManager(), 0); - if (info == null) { - if (enableLogging) { - StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName, - BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING); - } - return false; - } - if (!ActivityInfo.isResizeableMode(info.resizeMode)) { - if (enableLogging) { - StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName, - BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE); - } - return false; - } - if (info.documentLaunchMode != DOCUMENT_LAUNCH_ALWAYS) { - if (enableLogging) { - StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName, - BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__DOCUMENT_LAUNCH_NOT_ALWAYS); - } - return false; - } - return (info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) != 0; - } - /** * Whether the notification has been developer configured to bubble and is allowed by the user. */ @@ -620,7 +524,7 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe ENABLE_AUTO_BUBBLE_ALL, 0) != 0; } - private static boolean shouldUseContentIntent(Context context) { + static boolean shouldUseContentIntent(Context context) { return Settings.Secure.getInt(context.getContentResolver(), ENABLE_BUBBLE_CONTENT_INTENT, 0) != 0; } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java new file mode 100644 index 000000000000..89b0de85e18d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -0,0 +1,74 @@ +/* + * 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.systemui.bubbles; + +import androidx.annotation.Nullable; + +import com.android.systemui.statusbar.notification.collection.NotificationEntry; + +import java.util.Collection; +import java.util.HashMap; + +/** + * Keeps track of active bubbles. + */ +class BubbleData { + + private HashMap<String, Bubble> mBubbles = new HashMap<>(); + + private static BubbleData sBubbleData = null; + + private BubbleData() {} + + public static BubbleData getInstance() { + if (sBubbleData == null) { + sBubbleData = new BubbleData(); + } + return sBubbleData; + } + + /** + * The set of bubbles. + */ + public Collection<Bubble> getBubbles() { + return mBubbles.values(); + } + + @Nullable + public Bubble getBubble(String key) { + return mBubbles.get(key); + } + + public void addBubble(Bubble b) { + mBubbles.put(b.key, b); + } + + @Nullable + public Bubble removeBubble(String key) { + return mBubbles.remove(key); + } + + public void updateBubble(String key, NotificationEntry newEntry) { + Bubble oldBubble = mBubbles.get(key); + if (oldBubble != null) { + oldBubble.setEntry(newEntry); + } + } + + public void clear() { + mBubbles.clear(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index 976a766dcc09..3389c46f66b7 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -16,19 +16,28 @@ package com.android.systemui.bubbles; +import static android.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS; +import static android.util.StatsLogInternal.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING; +import static android.util.StatsLogInternal.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE; +import static android.util.StatsLogInternal.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__DOCUMENT_LAUNCH_NOT_ALWAYS; + import android.animation.LayoutTransition; import android.animation.ObjectAnimator; import android.annotation.Nullable; +import android.app.ActivityView; import android.app.INotificationManager; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; +import android.graphics.Insets; +import android.graphics.Point; import android.graphics.drawable.Drawable; import android.graphics.drawable.ShapeDrawable; import android.os.RemoteException; @@ -39,16 +48,21 @@ import android.util.AttributeSet; import android.util.Log; import android.util.StatsLog; import android.view.View; +import android.view.ViewGroup; +import android.view.WindowInsets; import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.recents.TriangleShape; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.stack.ExpandableViewState; /** * Container for the expanded bubble view, handles rendering the caret and header of the view. @@ -68,8 +82,15 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList // Permission view private View mPermissionView; - // The view that is being displayed for the expanded state - private View mExpandedView; + // Views for expanded state + private ExpandableNotificationRow mNotifRow; + private ActivityView mActivityView; + + private boolean mActivityViewReady = false; + private PendingIntent mBubbleIntent; + + private int mBubbleHeight; + private int mDefaultHeight; private NotificationEntry mEntry; private PackageManager mPm; @@ -77,11 +98,38 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList private Drawable mAppIcon; private INotificationManager mNotificationManagerService; + private BubbleController mBubbleController = Dependency.get(BubbleController.class); - // Need reference to let it know to collapse when new task is launched private BubbleStackView mStackView; - private OnBubbleBlockedListener mOnBubbleBlockedListener; + private BubbleExpandedView.OnBubbleBlockedListener mOnBubbleBlockedListener; + + private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() { + @Override + public void onActivityViewReady(ActivityView view) { + mActivityViewReady = true; + mActivityView.startActivity(mBubbleIntent); + } + + @Override + public void onActivityViewDestroyed(ActivityView view) { + mActivityViewReady = false; + } + + /** + * This is only called for tasks on this ActivityView, which is also set to + * single-task mode -- meaning never more than one task on this display. If a task + * is being removed, it's the top Activity finishing and this bubble should + * be removed or collapsed. + */ + @Override + public void onTaskRemovalStarted(int taskId) { + if (mEntry != null) { + // Must post because this is called from a binder thread. + post(() -> mBubbleController.removeBubble(mEntry.key)); + } + } + }; public BubbleExpandedView(Context context) { this(context, null); @@ -99,6 +147,8 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); mPm = context.getPackageManager(); + mDefaultHeight = getResources().getDimensionPixelSize( + R.dimen.bubble_expanded_default_height); try { mNotificationManagerService = INotificationManager.Stub.asInterface( ServiceManager.getServiceOrThrow(Context.NOTIFICATION_SERVICE)); @@ -152,6 +202,28 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList mPermissionView = findViewById(R.id.permission_layout); findViewById(R.id.no_bubbles_button).setOnClickListener(this); findViewById(R.id.yes_bubbles_button).setOnClickListener(this); + + mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */, + true /* singleTaskInstance */); + addView(mActivityView); + + mActivityView.setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> { + ActivityView activityView = (ActivityView) view; + // Here we assume that the position of the ActivityView on the screen + // remains regardless of IME status. When we move ActivityView, the + // forwardedInsets should be computed not against the current location + // and size, but against the post-moved location and size. + Point displaySize = new Point(); + view.getContext().getDisplay().getSize(displaySize); + int[] windowLocation = view.getLocationOnScreen(); + final int windowBottom = windowLocation[1] + view.getHeight(); + final int keyboardHeight = insets.getSystemWindowInsetBottom() + - insets.getStableInsetBottom(); + final int insetsBottom = Math.max(0, + windowBottom + keyboardHeight - displaySize.y); + activityView.setForwardedInsets(Insets.of(0, 0, 0, insetsBottom)); + return view.onApplyWindowInsets(insets); + }); } /** @@ -189,6 +261,8 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList } updateHeaderView(); updatePermissionView(); + updateExpandedView(); + mActivityView.setCallback(mStateCallback); } private void updateHeaderView() { @@ -225,6 +299,34 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList } } + private void updateExpandedView() { + mBubbleIntent = getBubbleIntent(mEntry); + if (mBubbleIntent != null) { + if (mNotifRow != null) { + // Clear out the row if we had it previously + removeView(mNotifRow); + mNotifRow = null; + } + Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); + mBubbleHeight = data != null && data.getDesiredHeight() > 0 + ? data.getDesiredHeight() + : mDefaultHeight; + // XXX: enforce max / min height + LayoutParams lp = (LayoutParams) mActivityView.getLayoutParams(); + lp.height = mBubbleHeight; + mActivityView.setLayoutParams(lp); + mActivityView.setVisibility(VISIBLE); + } else { + // Hide activity view if we had it previously + mActivityView.setVisibility(GONE); + + // Use notification view + mNotifRow = mEntry.getRow(); + addView(mNotifRow); + } + updateView(); + } + @Override public void onClick(View view) { if (mEntry == null) { @@ -279,36 +381,87 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList } /** + * Update appearance of the expanded view being displayed. + */ + public void updateView() { + if (usingActivityView() + && mActivityView.getVisibility() == VISIBLE + && mActivityView.isAttachedToWindow()) { + mActivityView.onLocationChanged(); + } else if (mNotifRow != null) { + applyRowState(mNotifRow); + } + } + + /** * Set the x position that the tip of the triangle should point to. */ - public void setPointerPosition(int x) { + public void setPointerPosition(float x) { // Adjust for the pointer size - x -= (mPointerView.getWidth() / 2); + x -= (mPointerView.getWidth() / 2f); mPointerView.setTranslationX(x); } /** - * Set the view to display for the expanded state. Passing null will clear the view. + * Removes and releases an ActivityView if one was previously created for this bubble. */ - public void setExpandedView(View view) { - if (mExpandedView == view) { + public void destroyActivityView(ViewGroup tmpParent) { + if (mActivityView == null) { return; } - if (mExpandedView != null) { - removeView(mExpandedView); + if (!mActivityViewReady) { + // release not needed, never initialized? + mActivityView = null; + return; } - mExpandedView = view; - if (mExpandedView != null) { - addView(mExpandedView); + // HACK: release() will crash if the view is not attached. + if (!isAttachedToWindow()) { + mActivityView.setVisibility(View.GONE); + tmpParent.addView(this); } + + mActivityView.release(); + ((ViewGroup) getParent()).removeView(this); + mActivityView = null; + mActivityViewReady = false; } - /** - * @return the view containing the expanded content, can be null. - */ - @Nullable - public View getExpandedView() { - return mExpandedView; + private boolean usingActivityView() { + return mBubbleIntent != null; + } + + private void applyRowState(ExpandableNotificationRow view) { + view.reset(); + view.setHeadsUp(false); + view.resetTranslation(); + view.setOnKeyguard(false); + view.setOnAmbient(false); + view.setClipBottomAmount(0); + view.setClipTopAmount(0); + view.setContentTransformationAmount(0, false); + view.setIconsVisible(true); + + // TODO - Need to reset this (and others) when view goes back in shade, leave for now + // view.setTopRoundness(1, false); + // view.setBottomRoundness(1, false); + + ExpandableViewState viewState = view.getViewState(); + viewState = viewState == null ? new ExpandableViewState() : viewState; + viewState.height = view.getIntrinsicHeight(); + viewState.gone = false; + viewState.hidden = false; + viewState.dimmed = false; + viewState.dark = false; + viewState.alpha = 1f; + viewState.notGoneIndex = -1; + viewState.xTranslation = 0; + viewState.yTranslation = 0; + viewState.zTranslation = 0; + viewState.scaleX = 1; + viewState.scaleY = 1; + viewState.inShelf = true; + viewState.headsUpIsVisible = false; + viewState.applyToView(view); } private Intent getSettingsIntent(String packageName, final int appUid) { @@ -320,6 +473,61 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList return intent; } + @Nullable + private PendingIntent getBubbleIntent(NotificationEntry entry) { + Notification notif = entry.notification.getNotification(); + String packageName = entry.notification.getPackageName(); + Notification.BubbleMetadata data = notif.getBubbleMetadata(); + if (data != null && canLaunchInActivityView(data.getIntent(), true /* enableLogging */, + packageName)) { + return data.getIntent(); + } else if (BubbleController.shouldUseContentIntent(mContext) + && canLaunchInActivityView(notif.contentIntent, false /* enableLogging */, + packageName)) { + return notif.contentIntent; + } + return null; + } + + /** + * Whether an intent is properly configured to display in an {@link android.app.ActivityView}. + * + * @param intent the pending intent of the bubble. + * @param enableLogging whether bubble developer error should be logged. + * @param packageName the notification package name for this bubble. + * @return + */ + private boolean canLaunchInActivityView(PendingIntent intent, boolean enableLogging, + String packageName) { + if (intent == null) { + return false; + } + ActivityInfo info = + intent.getIntent().resolveActivityInfo(mContext.getPackageManager(), 0); + if (info == null) { + if (enableLogging) { + StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName, + BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING); + } + return false; + } + if (!ActivityInfo.isResizeableMode(info.resizeMode)) { + if (enableLogging) { + StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName, + BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE); + } + return false; + } + if (info.documentLaunchMode != DOCUMENT_LAUNCH_ALWAYS) { + if (enableLogging) { + StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName, + BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__DOCUMENT_LAUNCH_NOT_ALWAYS); + } + return false; + } + return (info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) != 0; + } + /** * Listener that is notified when a bubble is blocked. */ diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 2b344f6cf195..8bc83d4c852b 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -19,7 +19,6 @@ package com.android.systemui.bubbles; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; -import android.app.ActivityView; import android.content.Context; import android.content.res.Resources; import android.graphics.Outline; @@ -33,13 +32,11 @@ import android.util.StatsLog; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.view.ViewTreeObserver; import android.view.WindowInsets; import android.view.WindowManager; import android.widget.FrameLayout; -import android.widget.LinearLayout; import androidx.annotation.MainThread; import androidx.annotation.Nullable; @@ -54,8 +51,6 @@ import com.android.systemui.bubbles.animation.ExpandedAnimationController; import com.android.systemui.bubbles.animation.PhysicsAnimationLayout; import com.android.systemui.bubbles.animation.StackAnimationController; 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 java.math.BigDecimal; import java.math.RoundingMode; @@ -95,22 +90,27 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F private StackAnimationController mStackAnimationController; private ExpandedAnimationController mExpandedAnimationController; - private BubbleExpandedView mExpandedViewContainer; + private FrameLayout mExpandedViewContainer; + + private BubbleData mBubbleData; private int mBubbleSize; private int mBubblePadding; private int mExpandedAnimateXDistance; private int mExpandedAnimateYDistance; + private Bubble mExpandedBubble; private boolean mIsExpanded; - private int mExpandedBubbleHeight; + private BubbleTouchHandler mTouchHandler; - private BubbleView mExpandedBubble; private BubbleController.BubbleExpandListener mExpandListener; + private BubbleExpandedView.OnBubbleBlockedListener mBlockedListener; private boolean mViewUpdatedRequested = false; private boolean mIsAnimating = false; + private LayoutInflater mInflater; + // Used for determining view / touch intersection int[] mTempLoc = new int[2]; RectF mTempRect = new RectF(); @@ -142,7 +142,9 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F public BubbleStackView(Context context) { super(context); + mBubbleData = BubbleData.getInstance(); + mInflater = LayoutInflater.from(context); mTouchHandler = new BubbleTouchHandler(context); setOnTouchListener(mTouchHandler); @@ -154,7 +156,6 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F mExpandedAnimateYDistance = res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_y_distance); - mExpandedBubbleHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height); mDisplaySize = new Point(); WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); wm.getDefaultDisplay().getSize(mDisplaySize); @@ -174,9 +175,7 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F mBubbleContainer.setClipChildren(false); addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); - mExpandedViewContainer = (BubbleExpandedView) - LayoutInflater.from(context).inflate(R.layout.bubble_expanded_view, - this /* parent */, false /* attachToRoot */); + mExpandedViewContainer = new FrameLayout(context); mExpandedViewContainer.setElevation(elevation); mExpandedViewContainer.setPadding(padding, padding, padding, padding); mExpandedViewContainer.setClipChildren(false); @@ -218,6 +217,17 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F } /** + * Updates the visibility of the 'dot' indicating an update on the bubble. + * @param key the {@link NotificationEntry#key} associated with the bubble. + */ + public void updateDotVisibility(String key) { + Bubble b = mBubbleData.getBubble(key); + if (b != null) { + b.iconView.updateDotVisibility(); + } + } + + /** * Sets the listener to notify when the bubble stack is expanded. */ public void setExpandListener(BubbleController.BubbleExpandListener listener) { @@ -228,7 +238,10 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F * Sets the listener to notify when a bubble is blocked. */ public void setOnBlockedListener(BubbleExpandedView.OnBubbleBlockedListener listener) { - mExpandedViewContainer.setOnBlockedListener(listener); + mBlockedListener = listener; + for (Bubble b : mBubbleData.getBubbles()) { + b.expandedView.setOnBlockedListener(mBlockedListener); + } } /** @@ -241,19 +254,29 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F /** * The {@link BubbleView} that is expanded, null if one does not exist. */ - public BubbleView getExpandedBubble() { + BubbleView getExpandedBubbleView() { + return mExpandedBubble != null ? mExpandedBubble.iconView : null; + } + + /** + * The {@link Bubble} that is expanded, null if one does not exist. + */ + Bubble getExpandedBubble() { return mExpandedBubble; } /** * Sets the bubble that should be expanded and expands if needed. + * + * @param key the {@link NotificationEntry#key} associated with the bubble to expand. */ - public void setExpandedBubble(BubbleView bubbleToExpand) { + void setExpandedBubble(String key) { + Bubble bubbleToExpand = mBubbleData.getBubble(key); if (mIsExpanded && !bubbleToExpand.equals(mExpandedBubble)) { // Previously expanded, notify that this bubble is no longer expanded - notifyExpansionChanged(mExpandedBubble, false /* expanded */); + notifyExpansionChanged(mExpandedBubble.entry, false /* expanded */); } - BubbleView prevBubble = mExpandedBubble; + Bubble prevBubble = mExpandedBubble; mExpandedBubble = bubbleToExpand; if (!mIsExpanded) { // If we weren't previously expanded we should animate open. @@ -268,8 +291,8 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F logBubbleEvent(prevBubble, StatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED); logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED); } - mExpandedBubble.getEntry().setShowInShadeWhenBubble(false); - notifyExpansionChanged(mExpandedBubble, true /* expanded */); + mExpandedBubble.entry.setShowInShadeWhenBubble(false); + notifyExpansionChanged(mExpandedBubble.entry, true /* expanded */); } /** @@ -280,7 +303,7 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F for (int i = 0; i < mBubbleContainer.getChildCount(); i++) { BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i); if (entry.equals(bv.getEntry())) { - setExpandedBubble(bv); + setExpandedBubble(entry.key); } } } @@ -288,48 +311,67 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F /** * Adds a bubble to the top of the stack. * - * @param bubbleView the view to add to the stack. + * @param entry the notification to add to the stack of bubbles. */ - public void addBubble(BubbleView bubbleView) { - mBubbleContainer.addView(bubbleView, 0, + public void addBubble(NotificationEntry entry) { + Bubble b = new Bubble(entry, mInflater, this /* stackView */, mBlockedListener); + mBubbleData.addBubble(b); + + mBubbleContainer.addView(b.iconView, 0, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); - ViewClippingUtil.setClippingDeactivated(bubbleView, true, mClippingParameters); + ViewClippingUtil.setClippingDeactivated(b.iconView, true, mClippingParameters); + requestUpdate(); - logBubbleEvent(bubbleView, StatsLog.BUBBLE_UICHANGED__ACTION__POSTED); + logBubbleEvent(b, StatsLog.BUBBLE_UICHANGED__ACTION__POSTED); } /** * Remove a bubble from the stack. */ - public void removeBubble(BubbleView bubbleView) { - int removedIndex = mBubbleContainer.indexOfChild(bubbleView); - mBubbleContainer.removeView(bubbleView); + public void removeBubble(String key) { + Bubble b = mBubbleData.removeBubble(key); + if (b == null) { + return; + } + b.entry.setBubbleDismissed(true); + + // Remove it from the views + int removedIndex = mBubbleContainer.indexOfChild(b.iconView); + b.expandedView.destroyActivityView(this /* tmpParent */); + mBubbleContainer.removeView(b.iconView); + int bubbleCount = mBubbleContainer.getChildCount(); if (bubbleCount == 0) { // If no bubbles remain, collapse the entire stack. collapseStack(); return; - } else if (bubbleView.equals(mExpandedBubble)) { + } else if (b.equals(mExpandedBubble)) { // Was the current bubble just removed? // If we have other bubbles and are expanded go to the next one or previous // if the bubble removed was last int nextIndex = bubbleCount > removedIndex ? removedIndex : bubbleCount - 1; BubbleView expandedBubble = (BubbleView) mBubbleContainer.getChildAt(nextIndex); if (mIsExpanded) { - setExpandedBubble(expandedBubble); + setExpandedBubble(expandedBubble.getKey()); } else { mExpandedBubble = null; } } - logBubbleEvent(bubbleView, StatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED); + logBubbleEvent(b, StatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED); } /** * Dismiss the stack of bubbles. */ public void stackDismissed() { + for (Bubble bubble : mBubbleData.getBubbles()) { + bubble.entry.setBubbleDismissed(true); + bubble.expandedView.destroyActivityView(this /* tmpParent */); + } + mBubbleData.clear(); collapseStack(); mBubbleContainer.removeAllViews(); + mExpandedViewContainer.removeAllViews(); logBubbleEvent(null /* no bubble associated with bubble stack dismiss */, StatsLog.BUBBLE_UICHANGED__ACTION__STACK_DISMISSED); } @@ -337,25 +379,26 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F /** * Updates a bubble in the stack. * - * @param bubbleView the view to update in the stack. - * @param entry the entry to update it with. + * @param entry the entry to update in the stack. * @param updatePosition whether this bubble should be moved to top of the stack. */ - public void updateBubble(BubbleView bubbleView, NotificationEntry entry, - boolean updatePosition) { - bubbleView.update(entry); + public void updateBubble(NotificationEntry entry, boolean updatePosition) { + Bubble b = mBubbleData.getBubble(entry.key); + b.iconView.update(entry); + // TODO: should also update the expanded view here (e.g. height change) + if (updatePosition && !mIsExpanded) { // If alerting it gets promoted to top of the stack. - if (mBubbleContainer.indexOfChild(bubbleView) != 0) { - mBubbleContainer.moveViewTo(bubbleView, 0); + if (mBubbleContainer.indexOfChild(b.iconView) != 0) { + mBubbleContainer.moveViewTo(b.iconView, 0); } requestUpdate(); } - if (mIsExpanded && bubbleView.equals(mExpandedBubble)) { + if (mIsExpanded && entry.equals(mExpandedBubble.entry)) { entry.setShowInShadeWhenBubble(false); requestUpdate(); } - logBubbleEvent(bubbleView, StatsLog.BUBBLE_UICHANGED__ACTION__UPDATED); + logBubbleEvent(b, StatsLog.BUBBLE_UICHANGED__ACTION__UPDATED); } /** @@ -393,7 +436,7 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F if (mIsExpanded) { // TODO: Save opened bubble & move it to top of stack animateExpansion(false /* shouldExpand */); - notifyExpansionChanged(mExpandedBubble, mIsExpanded); + notifyExpansionChanged(mExpandedBubble.entry, mIsExpanded); logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED); } } @@ -412,8 +455,8 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F @MainThread public void expandStack() { if (!mIsExpanded) { - mExpandedBubble = getTopBubble(); - setExpandedBubble(mExpandedBubble); + String expandedBubbleKey = getBubbleAt(0).getKey(); + setExpandedBubble(expandedBubbleKey); logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED); } } @@ -459,7 +502,8 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F final float yStart = Math.min( mStackAnimationController.getStackPosition().y, mExpandedAnimateYDistance); - final float yDest = getStatusBarHeight() + mExpandedBubble.getHeight() + mBubblePadding; + final float yDest = getStatusBarHeight() + + mExpandedBubble.iconView.getHeight() + mBubblePadding; if (shouldExpand) { mExpandedViewContainer.setTranslationX(xStart); @@ -484,17 +528,12 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F + mBubbleContainer.getPaddingStart(); } - private void notifyExpansionChanged(BubbleView bubbleView, boolean expanded) { + private void notifyExpansionChanged(NotificationEntry entry, boolean expanded) { if (mExpandListener != null) { - NotificationEntry entry = bubbleView != null ? bubbleView.getEntry() : null; mExpandListener.onBubbleExpandChanged(expanded, entry != null ? entry.key : null); } } - private BubbleView getTopBubble() { - return getBubbleAt(0); - } - /** Return the BubbleView at the given index from the bubble container. */ public BubbleView getBubbleAt(int i) { return mBubbleContainer.getChildCount() > i @@ -665,31 +704,10 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F } private void updateExpandedBubble() { - if (mExpandedBubble == null) { - return; - } - - mExpandedViewContainer.setEntry(mExpandedBubble.getEntry(), this); - if (mExpandedBubble.hasAppOverlayIntent()) { - // Bubble with activity view expanded state - ActivityView expandedView = mExpandedBubble.getActivityView(); - // XXX: gets added to linear layout - expandedView.setLayoutParams(new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, mExpandedBubbleHeight)); - - mExpandedViewContainer.setExpandedView(expandedView); - } else { - // Bubble with notification view expanded state - ExpandableNotificationRow row = mExpandedBubble.getRowView(); - if (row.getParent() != null) { - // Row might still be in the shade when we expand - ((ViewGroup) row.getParent()).removeView(row); - } - if (mIsExpanded) { - mExpandedViewContainer.setExpandedView(row); - } else { - mExpandedViewContainer.setExpandedView(null); - } + mExpandedViewContainer.removeAllViews(); + if (mExpandedBubble != null && mIsExpanded) { + mExpandedViewContainer.addView(mExpandedBubble.expandedView); + mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE); } } @@ -697,18 +715,10 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F Log.d(TAG, "applyCurrentState: mIsExpanded=" + mIsExpanded); mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE); - if (!mIsExpanded) { - mExpandedViewContainer.setExpandedView(null); - } else { - View expandedView = mExpandedViewContainer.getExpandedView(); - if (expandedView instanceof ActivityView) { - if (expandedView.isAttachedToWindow()) { - ((ActivityView) expandedView).onLocationChanged(); - } - } else { - applyRowState(mExpandedBubble.getRowView()); - } + if (mIsExpanded) { + mExpandedBubble.expandedView.updateView(); } + int bubbsCount = mBubbleContainer.getChildCount(); for (int i = 0; i < bubbsCount; i++) { BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i); @@ -730,46 +740,12 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F private void updatePointerPosition() { if (mExpandedBubble != null) { - float pointerPosition = mExpandedBubble.getPosition().x - + (mExpandedBubble.getWidth() / 2f); - mExpandedViewContainer.setPointerPosition((int) pointerPosition); + float pointerPosition = mExpandedBubble.iconView.getPosition().x + + (mExpandedBubble.iconView.getWidth() / 2f); + mExpandedBubble.expandedView.setPointerPosition(pointerPosition); } } - private void applyRowState(ExpandableNotificationRow view) { - view.reset(); - view.setHeadsUp(false); - view.resetTranslation(); - view.setOnKeyguard(false); - view.setOnAmbient(false); - view.setClipBottomAmount(0); - view.setClipTopAmount(0); - view.setContentTransformationAmount(0, false); - view.setIconsVisible(true); - - // TODO - Need to reset this (and others) when view goes back in shade, leave for now - // view.setTopRoundness(1, false); - // view.setBottomRoundness(1, false); - - ExpandableViewState viewState = view.getViewState(); - viewState = viewState == null ? new ExpandableViewState() : viewState; - viewState.height = view.getIntrinsicHeight(); - viewState.gone = false; - viewState.hidden = false; - viewState.dimmed = false; - viewState.dark = false; - viewState.alpha = 1f; - viewState.notGoneIndex = -1; - viewState.xTranslation = 0; - viewState.yTranslation = 0; - viewState.zTranslation = 0; - viewState.scaleX = 1; - viewState.scaleY = 1; - viewState.inShelf = true; - viewState.headsUpIsVisible = false; - viewState.applyToView(view); - } - /** * @return the number of bubbles in the stack view. */ @@ -780,12 +756,12 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F /** * Finds the bubble index within the stack. * - * @param bubbleView the view of the bubble. + * @param bubble the bubble to look up. * @return the index of the bubble view within the bubble stack. The range of the position * is between 0 and the bubble count minus 1. */ - public int getBubbleIndex(BubbleView bubbleView) { - return mBubbleContainer.indexOfChild(bubbleView); + int getBubbleIndex(Bubble bubble) { + return mBubbleContainer.indexOfChild(bubble.iconView); } /** @@ -813,7 +789,7 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F * the user interaction is not specific to one bubble. * @param action the user interaction enum. */ - private void logBubbleEvent(@Nullable BubbleView bubble, int action) { + private void logBubbleEvent(@Nullable Bubble bubble, int action) { if (bubble == null) { StatsLog.write(StatsLog.BUBBLE_UI_CHANGED, null /* package name */, @@ -825,7 +801,7 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F getNormalizedXPosition(), getNormalizedYPosition()); } else { - StatusBarNotification notification = bubble.getEntry().notification; + StatusBarNotification notification = bubble.entry.notification; StatsLog.write(StatsLog.BUBBLE_UI_CHANGED, notification.getPackageName(), notification.getNotification().getChannelId(), diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java index 22cd2fcc3e72..7d3c0f85c0f7 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java @@ -188,7 +188,7 @@ class BubbleTouchHandler implements View.OnTouchListener { } else { stack.onBubbleDragFinish(mBubbleDraggingOut, x, y, velX, velY); } - } else if (floatingView.equals(stack.getExpandedBubble())) { + } else if (floatingView.equals(stack.getExpandedBubbleView())) { stack.collapseStack(); } else if (isBubbleStack) { if (stack.isExpanded()) { @@ -197,7 +197,7 @@ class BubbleTouchHandler implements View.OnTouchListener { stack.expandStack(); } } else { - stack.setExpandedBubble((BubbleView) floatingView); + stack.setExpandedBubble(((BubbleView) floatingView).getKey()); } cleanUpDismissTarget(); mVelocityTracker.recycle(); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java index 74ddc8f6b8fc..1a4b19940e34 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java @@ -17,13 +17,9 @@ package com.android.systemui.bubbles; import android.annotation.Nullable; -import android.app.ActivityView; import android.app.Notification; -import android.app.PendingIntent; import android.content.Context; import android.graphics.Color; -import android.graphics.Insets; -import android.graphics.Point; import android.graphics.PointF; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; @@ -31,16 +27,10 @@ import android.graphics.drawable.Icon; import android.graphics.drawable.InsetDrawable; import android.graphics.drawable.LayerDrawable; import android.util.AttributeSet; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowInsets; import android.widget.FrameLayout; -import android.widget.LinearLayout; import android.widget.TextView; import com.android.internal.graphics.ColorUtils; -import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -62,11 +52,6 @@ public class BubbleView extends FrameLayout implements BubbleTouchHandler.Floati private int mIconInset; private NotificationEntry mEntry; - private PendingIntent mAppOverlayIntent; - private BubbleController mBubbleController; - private ActivityView mActivityView; - private boolean mActivityViewReady; - private boolean mActivityViewStarted; public BubbleView(Context context) { this(context, null); @@ -86,7 +71,6 @@ public class BubbleView extends FrameLayout implements BubbleTouchHandler.Floati // XXX: can this padding just be on the view and we look it up? mPadding = getResources().getDimensionPixelSize(R.dimen.bubble_view_padding); mIconInset = getResources().getDimensionPixelSize(R.dimen.bubble_icon_inset); - mBubbleController = Dependency.get(BubbleController.class); } @Override @@ -168,7 +152,6 @@ public class BubbleView extends FrameLayout implements BubbleTouchHandler.Floati updateViews(); } - /** * @return the {@link ExpandableNotificationRow} view to display notification content when the * bubble is expanded. @@ -235,88 +218,6 @@ public class BubbleView extends FrameLayout implements BubbleTouchHandler.Floati return ColorUtils.blendARGB(defaultTint, Color.WHITE, WHITE_SCRIM_ALPHA); } - /** - * @return a view used to display app overlay content when expanded. - */ - public ActivityView getActivityView() { - if (mActivityView == null) { - mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */, - true /* singleTaskInstance */); - Log.d(TAG, "[getActivityView] created: " + mActivityView); - mActivityView.setCallback(new ActivityView.StateCallback() { - @Override - public void onActivityViewReady(ActivityView view) { - mActivityViewReady = true; - mActivityView.startActivity(mAppOverlayIntent); - } - - @Override - public void onActivityViewDestroyed(ActivityView view) { - mActivityViewReady = false; - } - - /** - * This is only called for tasks on this ActivityView, which is also set to - * single-task mode -- meaning never more than one task on this display. If a task - * is being removed, it's the top Activity finishing and this bubble should - * be removed or collapsed. - */ - @Override - public void onTaskRemovalStarted(int taskId) { - if (mEntry != null) { - // Must post because this is called from a binder thread. - post(() -> mBubbleController.removeBubble(mEntry.key)); - } - } - }); - mActivityView.setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> { - ActivityView activityView = (ActivityView) view; - // Here we assume that the position of the ActivityView on the screen - // remains regardless of IME status. When we move ActivityView, the - // forwardedInsets should be computed not against the current location - // and size, but against the post-moved location and size. - Point displaySize = new Point(); - view.getContext().getDisplay().getSize(displaySize); - int[] windowLocation = view.getLocationOnScreen(); - final int windowBottom = windowLocation[1] + view.getHeight(); - final int keyboardHeight = insets.getSystemWindowInsetBottom() - - insets.getStableInsetBottom(); - final int insetsBottom = Math.max(0, - windowBottom + keyboardHeight - displaySize.y); - activityView.setForwardedInsets(Insets.of(0, 0, 0, insetsBottom)); - return view.onApplyWindowInsets(insets); - }); - - } - return mActivityView; - } - - /** - * Removes and releases an ActivityView if one was previously created for this bubble. - */ - public void destroyActivityView(ViewGroup tmpParent) { - if (mActivityView == null) { - return; - } - if (!mActivityViewReady) { - // release not needed, never initialized? - mActivityView = null; - return; - } - // HACK: release() will crash if the view is not attached. - if (!mActivityView.isAttachedToWindow()) { - mActivityView.setVisibility(View.GONE); - tmpParent.addView(mActivityView, new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT)); - } - - mActivityView.release(); - - ((ViewGroup) mActivityView.getParent()).removeView(mActivityView); - mActivityView = null; - } - @Override public void setPosition(float x, float y) { setPositionX(x); @@ -337,20 +238,4 @@ public class BubbleView extends FrameLayout implements BubbleTouchHandler.Floati public PointF getPosition() { return new PointF(getTranslationX(), getTranslationY()); } - - /** - * @return whether an ActivityView should be used to display the content of this Bubble - */ - public boolean hasAppOverlayIntent() { - return mAppOverlayIntent != null; - } - - public PendingIntent getAppOverlayIntent() { - return mAppOverlayIntent; - - } - - public void setBubbleIntent(PendingIntent intent) { - mAppOverlayIntent = intent; - } } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeReceiver.java b/packages/SystemUI/src/com/android/systemui/doze/DozeReceiver.java index 4a2e06c058cb..f5cf149ba868 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeReceiver.java @@ -25,9 +25,4 @@ public interface DozeReceiver { * Invoked every time a minute is elapsed in doze mode */ void dozeTimeTick(); - - /** - * When view is double tapped in doze mode. - */ - void onDozeDoubleTap(); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index 5efdc2f61d76..2d1dba6f79c8 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -131,7 +131,7 @@ public class DozeSensors { new PluginSensor( new SensorManagerPlugin.Sensor(TYPE_WAKE_LOCK_SCREEN), Settings.Secure.DOZE_WAKE_SCREEN_GESTURE, - mConfig.wakeScreenGestureAvailable(), + mConfig.wakeScreenGestureAvailable() && alwaysOn, DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN, false /* reports touch coordinates */, false /* touchscreen */), diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index 873fbc226629..e207cb13d311 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -161,7 +161,8 @@ public class DozeTriggers implements DozeMachine.Part { } else { mDozeHost.extendPulse(); } - }, sensorPerformedProxCheck || mDockManager.isDocked(), pulseReason); + }, sensorPerformedProxCheck + || (mDockManager != null && mDockManager.isDocked()), pulseReason); } if (isPickup) { @@ -224,7 +225,9 @@ public class DozeTriggers implements DozeMachine.Part { case INITIALIZED: mBroadcastReceiver.register(mContext); mDozeHost.addCallback(mHostCallback); - mDockManager.addListener(mDockEventListener); + if (mDockManager != null) { + mDockManager.addListener(mDockEventListener); + } checkTriggersAtInit(); break; case DOZE: @@ -250,7 +253,9 @@ public class DozeTriggers implements DozeMachine.Part { case FINISH: mBroadcastReceiver.unregister(mContext); mDozeHost.removeCallback(mHostCallback); - mDockManager.removeListener(mDockEventListener); + if (mDockManager != null) { + mDockManager.removeListener(mDockEventListener); + } mDozeSensors.setListening(false); mDozeSensors.setProxListening(false); break; diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index f5ac0d39d61f..7c9b2864f7f2 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -33,6 +33,7 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.UserInfo; +import android.content.res.Configuration; import android.database.ContentObserver; import android.graphics.Point; import android.graphics.drawable.Drawable; @@ -91,6 +92,7 @@ import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.util.EmergencyDialerConstants; +import com.android.systemui.util.leak.RotationUtils; import com.android.systemui.volume.SystemUIInterpolators.LogAccelerateInterpolator; import java.util.ArrayList; @@ -159,6 +161,8 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final ScreenshotHelper mScreenshotHelper; private final ScreenRecordHelper mScreenRecordHelper; + private int mLastRotation; + /** * @param context everything needs a context :( */ @@ -201,6 +205,8 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, mScreenshotHelper = new ScreenshotHelper(context); mScreenRecordHelper = new ScreenRecordHelper(context); + mLastRotation = RotationUtils.getRotation(mContext); + Dependency.get(ConfigurationController.class).addCallback(this); } @@ -426,6 +432,15 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, mContext.getTheme().applyStyle(mContext.getThemeResId(), true); } + @Override + public void onConfigChanged(Configuration newConfig) { + int rotation = RotationUtils.getRotation(mContext); + if (rotation != mLastRotation) { + mDialog.onRotate(); + } + mLastRotation = rotation; + } + public void destroy() { Dependency.get(ConfigurationController.class).removeCallback(this); } @@ -1091,7 +1106,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, } protected int getActionLayoutId(Context context) { - if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.GLOBAL_ACTIONS_GRID_ENABLED)) { + if (isGridEnabled(context)) { return com.android.systemui.R.layout.global_actions_grid_item; } return com.android.systemui.R.layout.global_actions_item; @@ -1465,7 +1480,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final Context mContext; private final MyAdapter mAdapter; - private final MultiListLayout mGlobalActionsLayout; + private MultiListLayout mGlobalActionsLayout; private final OnClickListener mClickListener; private final OnItemLongClickListener mLongClickListener; private final GradientDrawable mGradientDrawable; @@ -1505,8 +1520,13 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, window.setBackgroundDrawable(mGradientDrawable); window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); + initializeLayout(); - setContentView(getGlobalActionsLayoutId(context)); + setTitle(R.string.global_actions); + } + + private void initializeLayout() { + setContentView(getGlobalActionsLayoutId(mContext)); mGlobalActionsLayout = (MultiListLayout) findViewById(com.android.systemui.R.id.global_actions_view); mGlobalActionsLayout.setOutsideTouchListener(view -> dismiss()); @@ -1520,11 +1540,20 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, return true; } }); - setTitle(R.string.global_actions); + } + + public void onRotate() { + if (mShowing && isGridEnabled(mContext)) { + initializeLayout(); + updateList(); + } } private int getGlobalActionsLayoutId(Context context) { - if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.GLOBAL_ACTIONS_GRID_ENABLED)) { + if (isGridEnabled(context)) { + if (RotationUtils.getRotation(context) == RotationUtils.ROTATION_SEASCAPE) { + return com.android.systemui.R.layout.global_actions_grid_seascape; + } return com.android.systemui.R.layout.global_actions_grid; } return com.android.systemui.R.layout.global_actions_wrapped; @@ -1543,10 +1572,20 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, int separatedIndex = separatedActions.indexOf(action); ViewGroup parent; if (separatedIndex != -1) { - parent = mGlobalActionsLayout.getParentView(true, separatedIndex); + parent = mGlobalActionsLayout.getParentView(true, separatedIndex, false); } else { + boolean reverse = false; + + // If we're using the grid layout and we're in seascape, reverse the order + // of sublists to make sure they render in the correct positions, + // since we can't reverse vertical LinearLayouts through the layout xml. + + if (isGridEnabled(mContext) && RotationUtils.getRotation(mContext) + == RotationUtils.ROTATION_SEASCAPE) { + reverse = true; + } int listIndex = listActions.indexOf(action); - parent = mGlobalActionsLayout.getParentView(false, listIndex); + parent = mGlobalActionsLayout.getParentView(false, listIndex, reverse); } View v = mAdapter.getView(i, null, parent); final int pos = i; @@ -1665,4 +1704,11 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, mKeyguardShowing = keyguardShowing; } } + + /** + * Determines whether or not the Global Actions Dialog should use the newer grid-style layout. + */ + public static boolean isGridEnabled(Context context) { + return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.GLOBAL_ACTIONS_GRID_ENABLED); + } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java index 0e49b5f3cd2a..1d042776efc9 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java @@ -83,11 +83,11 @@ public class GlobalActionsGridLayout extends MultiListLayout { } @Override - public ViewGroup getParentView(boolean separated, int index) { + public ViewGroup getParentView(boolean separated, int index, boolean reverseOrder) { if (separated) { return getSeparatedView(); } else { - return getListView().getParentView(index); + return getListView().getParentView(index, reverseOrder); } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java index 37755155751f..d5dcd74c7ea8 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java @@ -28,16 +28,13 @@ import android.widget.LinearLayout; * * * Try to maintain a 'square' grid (equal number of columns and rows) based on the expected item * count. + * * Determine the position and parent of any item by its index and the total item count. * * Display and hide sub-lists as needed, depending on the expected item count. - * * Favor bias toward having more rows or columns depending on the orientation of the device - * (TODO(123344999): Implement this, currently always favors adding more rows.) - * * Change the orientation (horizontal vs. vertical) of the container and sub-lists to act as rows - * or columns depending on the orientation of the device. - * (TODO(123344999): Implement this, currently always columns.) * * While we could implement this behavior with a GridLayout, it would take significantly more * time and effort, and would require more substantial refactoring of the existing code in - * GlobalActionsDialog, since it would require manipulation of the child items themselves. + * GlobalActionsDialog, since it would require manipulation of layout properties on the child items + * themselves. * */ @@ -65,14 +62,25 @@ public class ListGridLayout extends LinearLayout { /** * Get the parent view associated with the item which should be placed at the given position. */ - public ViewGroup getParentView(int index) { - ViewGroup firstParent = (ViewGroup) getChildAt(0); + public ViewGroup getParentView(int index, boolean reverseSublists) { if (mRows == 0) { - return firstParent; + return null; } + int column = getParentViewIndex(index, reverseSublists); + return (ViewGroup) getChildAt(column); + } + + private int reverseSublistIndex(int index) { + return getChildCount() - (index + 1); + } + + private int getParentViewIndex(int index, boolean reverseSublists) { int column = (int) Math.floor(index / mRows); - ViewGroup parent = (ViewGroup) getChildAt(column); - return parent != null ? parent : firstParent; + int columnCount = getChildCount(); + if (reverseSublists) { + column = reverseSublistIndex(column); + } + return column; } /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java index 684175cf4212..85d975f36db5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java @@ -25,7 +25,9 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.graphics.Rect; import android.graphics.Typeface; +import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.icu.text.DateFormat; import android.icu.text.DisplayContext; @@ -35,10 +37,13 @@ import android.os.Handler; import android.os.Trace; import android.provider.Settings; import android.service.notification.ZenModeConfig; -import android.text.Spannable; import android.text.SpannableStringBuilder; +import android.text.Spanned; import android.text.TextUtils; +import android.text.style.DynamicDrawableSpan; +import android.text.style.ImageSpan; import android.text.style.StyleSpan; +import android.util.MathUtils; import androidx.core.graphics.drawable.IconCompat; import androidx.slice.Slice; @@ -72,6 +77,8 @@ public class KeyguardSliceProvider extends SliceProvider implements private static final StyleSpan BOLD_STYLE = new StyleSpan(Typeface.BOLD); public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main"; + private static final String KEYGUARD_HEADER_URI = + "content://com.android.systemui.keyguard/header"; public static final String KEYGUARD_DATE_URI = "content://com.android.systemui.keyguard/date"; public static final String KEYGUARD_NEXT_ALARM_URI = "content://com.android.systemui.keyguard/alarm"; @@ -90,6 +97,7 @@ public class KeyguardSliceProvider extends SliceProvider implements private static KeyguardSliceProvider sInstance; protected final Uri mSliceUri; + protected final Uri mHeaderUri; protected final Uri mDateUri; protected final Uri mAlarmUri; protected final Uri mDndUri; @@ -163,6 +171,7 @@ public class KeyguardSliceProvider extends SliceProvider implements KeyguardSliceProvider(Handler handler) { mHandler = handler; mSliceUri = Uri.parse(KEYGUARD_SLICE_URI); + mHeaderUri = Uri.parse(KEYGUARD_HEADER_URI); mDateUri = Uri.parse(KEYGUARD_DATE_URI); mAlarmUri = Uri.parse(KEYGUARD_NEXT_ALARM_URI); mDndUri = Uri.parse(KEYGUARD_DND_URI); @@ -213,26 +222,32 @@ public class KeyguardSliceProvider extends SliceProvider implements protected void addMediaLocked(ListBuilder listBuilder) { if (mMediaMetaData != null) { SpannableStringBuilder builder = new SpannableStringBuilder(); + + Icon notificationIcon = mMediaManager == null ? null : mMediaManager.getMediaIcon(); + if (notificationIcon != null) { + Drawable drawable = notificationIcon.loadDrawable(getContext()); + Rect mediaBounds = new Rect(0 /* left */, 0 /* top */, + drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + int iconHeaderSize = getContext().getResources() + .getDimensionPixelSize(R.dimen.header_icon_size); + MathUtils.fitRect(mediaBounds, iconHeaderSize); + drawable.setBounds(mediaBounds); + builder.append("# "); + builder.setSpan(new ImageSpan(drawable, DynamicDrawableSpan.ALIGN_CENTER), + 0 /* start */, 1 /* end */, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } + CharSequence title = mMediaMetaData.getText(MediaMetadata.METADATA_KEY_TITLE); if (TextUtils.isEmpty(title)) { title = getContext().getResources().getString(R.string.music_controls_no_title); } builder.append(title); - builder.setSpan(BOLD_STYLE, 0, title.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + listBuilder.setHeader(new ListBuilder.HeaderBuilder(mHeaderUri).setTitle(builder)); CharSequence album = mMediaMetaData.getText(MediaMetadata.METADATA_KEY_ARTIST); if (!TextUtils.isEmpty(album)) { - builder.append(" ").append(album); + listBuilder.addRow(new RowBuilder(mMediaUri).setTitle(album)); } - - RowBuilder mediaBuilder = new RowBuilder(mMediaUri).setTitle(builder); - Icon notificationIcon = mMediaManager == null ? null : mMediaManager.getMediaIcon(); - if (notificationIcon != null) { - IconCompat icon = IconCompat.createFromIcon(notificationIcon); - mediaBuilder.addEndItem(icon, ListBuilder.ICON_IMAGE); - } - - listBuilder.addRow(mediaBuilder); } } diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt index ecbf0246a8ba..1765dc866c66 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt @@ -21,7 +21,9 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView +import com.android.systemui.Dependency import com.android.systemui.R +import com.android.systemui.statusbar.policy.KeyguardMonitor class OngoingPrivacyChip @JvmOverloads constructor( context: Context, @@ -49,6 +51,8 @@ class OngoingPrivacyChip @JvmOverloads constructor( updateView() } } + @Suppress("DEPRECATION") + private val keyguardMonitor = Dependency.get(KeyguardMonitor::class.java) var builder = PrivacyDialogBuilder(context, emptyList<PrivacyItem>()) var privacyList = emptyList<PrivacyItem>() set(value) { @@ -90,16 +94,7 @@ class OngoingPrivacyChip @JvmOverloads constructor( if (!privacyList.isEmpty()) { generateContentDescription() setIcons(builder, iconsContainer) - text.visibility = if (builder.types.size == 1 && expanded) VISIBLE else GONE - if (builder.types.size == 1 && expanded) { - if (builder.app != null) { - text.setText(builder.app?.applicationName) - } else { - text.text = context.resources.getQuantityString( - R.plurals.ongoing_privacy_chip_multiple_apps, - builder.appsAndTypes.size, builder.appsAndTypes.size) - } - } + setApplicationText() } else { text.visibility = GONE iconsContainer.removeAllViews() @@ -107,13 +102,28 @@ class OngoingPrivacyChip @JvmOverloads constructor( requestLayout() } + private fun setApplicationText() { + text.visibility = if (builder.types.size == 1 && expanded) VISIBLE else GONE + if (builder.types.size == 1 && expanded) { + if (builder.app != null && !amISecure()) { + text.setText(builder.app?.applicationName) + } else { + text.text = context.resources.getQuantityString( + R.plurals.ongoing_privacy_chip_multiple_apps, + builder.appsAndTypes.size, builder.appsAndTypes.size) + } + } + } + + private fun amISecure() = keyguardMonitor.isShowing && keyguardMonitor.isSecure + private fun generateContentDescription() { val typesText = builder.joinTypes() if (builder.types.size > 1) { contentDescription = context.getString( R.string.ongoing_privacy_chip_content_multiple_apps, typesText) } else { - if (builder.app != null) { + if (builder.app != null && !amISecure()) { contentDescription = context.getString(R.string.ongoing_privacy_chip_content_single_app, builder.app?.applicationName, typesText) diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt index cff7fe449be1..75b8a056df1d 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt @@ -36,8 +36,8 @@ import com.android.systemui.plugins.ActivityStarter import java.util.concurrent.TimeUnit class OngoingPrivacyDialog constructor( - val context: Context, - val dialogBuilder: PrivacyDialogBuilder + private val context: Context, + private val dialogBuilder: PrivacyDialogBuilder ) { private val iconSize = context.resources.getDimensionPixelSize( diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt index 9c1076af9ede..bbea6b277e90 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt @@ -18,7 +18,7 @@ import android.content.Context import android.graphics.drawable.Drawable import com.android.systemui.R -class PrivacyDialogBuilder(val context: Context, itemsList: List<PrivacyItem>) { +class PrivacyDialogBuilder(private val context: Context, itemsList: List<PrivacyItem>) { val appsAndTypes: List<Pair<PrivacyApplication, List<PrivacyType>>> val types: List<PrivacyType> diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt index f1c3bf299eea..0f3393708bcd 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt @@ -26,16 +26,23 @@ import android.os.Handler import android.os.UserHandle import android.os.UserManager import com.android.internal.annotations.VisibleForTesting -import com.android.systemui.Dependency +import com.android.systemui.Dependency.BG_HANDLER_NAME +import com.android.systemui.Dependency.MAIN_HANDLER_NAME +import com.android.systemui.R import com.android.systemui.appops.AppOpItem import com.android.systemui.appops.AppOpsController -import com.android.systemui.R import java.lang.ref.WeakReference import javax.inject.Inject +import javax.inject.Named import javax.inject.Singleton @Singleton -class PrivacyItemController @Inject constructor(val context: Context) { +class PrivacyItemController @Inject constructor( + val context: Context, + private val appOpsController: AppOpsController, + @Named(MAIN_HANDLER_NAME) private val uiHandler: Handler, + @Named(BG_HANDLER_NAME) private val bgHandler: Handler +) { companion object { val OPS = intArrayOf(AppOpsManager.OP_CAMERA, @@ -48,16 +55,10 @@ class PrivacyItemController @Inject constructor(val context: Context) { const val TAG = "PrivacyItemController" const val SYSTEM_UID = 1000 } - private var privacyList = emptyList<PrivacyItem>() - @Suppress("DEPRECATION") - private val appOpsController = Dependency.get(AppOpsController::class.java) + private var privacyList = emptyList<PrivacyItem>() private val userManager = context.getSystemService(UserManager::class.java) private var currentUserIds = emptyList<Int>() - @Suppress("DEPRECATION") - private val bgHandler = Handler(Dependency.get(Dependency.BG_LOOPER)) - @Suppress("DEPRECATION") - private val uiHandler = Dependency.get(Dependency.MAIN_HANDLER) private var listening = false val systemApp = PrivacyApplication(context.getString(R.string.device_services), SYSTEM_UID, context) diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index ee9255c54a62..c0f87cb37e50 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -48,7 +48,6 @@ import android.util.Pair; import android.view.DisplayCutout; import android.view.View; import android.view.WindowInsets; -import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; @@ -553,12 +552,10 @@ public class QuickStatusBarHeader extends RelativeLayout implements Handler mUiHandler = new Handler(Looper.getMainLooper()); mUiHandler.post(() -> { Dialog mDialog = new OngoingPrivacyDialog(mContext, builder).createDialog(); - mDialog.getWindow().setType( - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - SystemUIDialog.setShowForAllUsers(mDialog, true); + SystemUIDialog.setShowForAllUsers(mDialog, false); SystemUIDialog.registerDismissListener(mDialog); SystemUIDialog.setWindowOnTop(mDialog); - mUiHandler.post(() -> mDialog.show()); + mActivityStarter.postQSRunnableDismissingKeyguard(() -> mDialog.show()); mHost.collapsePanels(); }); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index c474faf6b1e0..83c4cfc6c126 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -24,6 +24,10 @@ import static android.view.MotionEvent.ACTION_UP; import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_SWIPE_UP; import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON; import static com.android.systemui.shared.system.NavigationBarCompat.InteractionType; +import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_CHANNEL; +import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS; +import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY; +import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -32,7 +36,9 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.graphics.Rect; +import android.graphics.Region; import android.os.Binder; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -41,6 +47,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; +import android.view.InputChannel; import android.view.MotionEvent; import com.android.internal.policy.ScreenDecorationsUtils; @@ -51,6 +58,7 @@ import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener; import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.InputChannelCompat.InputEventDispatcher; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.CallbackController; @@ -93,6 +101,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private final List<OverviewProxyListener> mConnectionCallbacks = new ArrayList<>(); private final Intent mQuickStepIntent; + private Region mActiveNavBarRegion; + private IOverviewProxy mOverviewProxy; private int mConnectionBackoffAttempts; private @InteractionType int mInteractionFlags; @@ -103,6 +113,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private float mWindowCornerRadius; private boolean mSupportsRoundedCornersOnWindows; + private InputEventDispatcher mInputEventDispatcher; + private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() { public void startScreenPinning(int taskId) { @@ -309,6 +321,20 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis mCurrentBoundedUserId = -1; Log.e(TAG_OPS, "Failed to call onBind()", e); } + + Bundle params = new Bundle(); + params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder()); + params.putParcelable(KEY_EXTRA_INPUT_CHANNEL, createNewInputDispatcher()); + params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius); + params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows); + try { + mOverviewProxy.onInitialize(params); + } catch (RemoteException e) { + // Ignore error until the migration is complete. + Log.e(TAG_OPS, "Failed to call onBind()", e); + } + dispatchNavButtonBounds(); + notifyConnectionChanged(); } @@ -317,6 +343,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis Log.w(TAG_OPS, "Null binding of '" + name + "', try reconnecting"); mCurrentBoundedUserId = -1; retryConnectionWithBackoff(); + disposeInputDispatcher(); } @Override @@ -324,15 +351,32 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis Log.w(TAG_OPS, "Binding died of '" + name + "', try reconnecting"); mCurrentBoundedUserId = -1; retryConnectionWithBackoff(); + disposeInputDispatcher(); } @Override public void onServiceDisconnected(ComponentName name) { // Do nothing mCurrentBoundedUserId = -1; + disposeInputDispatcher(); } }; + private void disposeInputDispatcher() { + if (mInputEventDispatcher != null) { + mInputEventDispatcher.dispose(); + mInputEventDispatcher = null; + } + } + + private InputChannel createNewInputDispatcher() { + disposeInputDispatcher(); + + InputChannel[] channels = InputChannel.openInputChannelPair("overview-proxy-service"); + mInputEventDispatcher = new InputEventDispatcher(channels[0], Looper.getMainLooper()); + return channels[1]; + } + private final DeviceProvisionedListener mDeviceProvisionedCallback = new DeviceProvisionedListener() { @Override @@ -382,6 +426,24 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } + /** + * Sets the navbar region which can receive touch inputs + */ + public void onActiveNavBarRegionChanges(Region activeRegion) { + mActiveNavBarRegion = activeRegion; + dispatchNavButtonBounds(); + } + + private void dispatchNavButtonBounds() { + if (mOverviewProxy != null && mActiveNavBarRegion != null) { + try { + mOverviewProxy.onActiveNavBarRegionChanges(mActiveNavBarRegion); + } catch (RemoteException e) { + Log.e(TAG_OPS, "Failed to call onActiveNavBarRegionChanges()", e); + } + } + } + public float getBackButtonAlpha() { return mBackButtonAlpha; } @@ -477,6 +539,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis return mOverviewProxy; } + public InputEventDispatcher getInputEventDispatcher() { + return mInputEventDispatcher; + } + public int getInteractionFlags() { return mInteractionFlags; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java index e11ec2d24e29..2edea789d820 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java @@ -158,18 +158,6 @@ public class NavigationBarController implements DisplayListener, Callbacks { }); } - /** Removes navigation bars. */ - public void destroy() { - mDisplayManager.unregisterDisplayListener(this); - if (mNavigationBars.size() > 0) { - for (int i = 0; i < mNavigationBars.size(); i++) { - int displayId = mNavigationBars.keyAt(i); - removeNavigationBar(displayId); - } - mNavigationBars.clear(); - } - } - private void removeNavigationBar(int displayId) { NavigationBarFragment navBar = mNavigationBars.get(displayId); if (navBar != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java index c25b7cfd804c..1cc6dc391c1d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java @@ -42,8 +42,6 @@ public interface NotificationLockscreenUserManager { /** Adds a listener to be notified when the current user changes. */ void addUserChangedListener(UserChangedListener listener); - void destroy(); - SparseArray<UserInfo> getCurrentProfiles(); void setLockscreenPublicMode(boolean isProfilePublic, int userId); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 6a49b804f5ba..9cb6f11fb558 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -550,12 +550,6 @@ public class NotificationLockscreenUserManagerImpl implements return mKeyguardMonitor.isSecure() || mLockPatternUtils.isSecure(userId); } - public void destroy() { - mContext.unregisterReceiver(mBaseBroadcastReceiver); - mContext.unregisterReceiver(mAllUsersReceiver); - mListeners.clear(); - } - @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("NotificationLockscreenUserManager state:"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 01b0bb14c7ca..110d51563e2c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -348,15 +348,13 @@ public class NotificationShelf extends ActivatableNotificationView implements if (notGoneIndex == 0) { StatusBarIconView icon = row.getEntry().expandedIcon; NotificationIconContainer.IconState iconState = getIconState(icon); + // The icon state might be null in rare cases where the notification is actually + // added to the layout, but not to the shelf. An example are replied messages, since + // they don't show up on AOD if (iconState != null && iconState.clampedAppearAmount == 1.0f) { // only if the first icon is fully in the shelf we want to clip to it! backgroundTop = (int) (row.getTranslationY() - getTranslationY()); firstElementRoundness = row.getCurrentTopRoundness(); - } else if (iconState == null) { - Log.wtf(TAG, "iconState is null. ExpandedIcon: " + row.getEntry().expandedIcon - + (row.getEntry().expandedIcon != null - ? "\n icon parent: " + row.getEntry().expandedIcon.getParent() : "") - + " \n number of notifications: " + mHostLayout.getChildCount() ); } } if (row.isFirstInSection() && previousRow != null && previousRow.isLastInSection()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index 662cf514b977..ee5ac7c602aa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -24,6 +24,7 @@ import android.view.View; import android.view.ViewGroup; import com.android.systemui.R; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -61,7 +62,7 @@ public class NotificationViewHierarchyManager { protected final NotificationLockscreenUserManager mLockscreenUserManager; protected final NotificationGroupManager mGroupManager; protected final VisualStabilityManager mVisualStabilityManager; - private final StatusBarStateControllerImpl mStatusBarStateController; + private final SysuiStatusBarStateController mStatusBarStateController; private final NotificationEntryManager mEntryManager; // Lazy @@ -82,13 +83,13 @@ public class NotificationViewHierarchyManager { NotificationLockscreenUserManager notificationLockscreenUserManager, NotificationGroupManager groupManager, VisualStabilityManager visualStabilityManager, - StatusBarStateControllerImpl statusBarStateController, + StatusBarStateController statusBarStateController, NotificationEntryManager notificationEntryManager, Lazy<ShadeController> shadeController) { mLockscreenUserManager = notificationLockscreenUserManager; mGroupManager = groupManager; mVisualStabilityManager = visualStabilityManager; - mStatusBarStateController = statusBarStateController; + mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController; mEntryManager = notificationEntryManager; mShadeController = shadeController; Resources res = context.getResources(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java index 839b06cec496..1ed671fdb6fb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java @@ -47,7 +47,11 @@ public interface NotificationEntryListener { } /** - * Called when a notification is updated, before any filtering of notifications have occurred. + * Called when a notification is about to be updated. Notification- and ranking-derived fields + * on the entry have already been updated but the following have not yet occurred: + * (a) View binding (i.e. the associated view has not yet been updated / inflation has not yet + * been kicked off. + * (b) Notification filtering */ default void onPreEntryUpdated(NotificationEntry entry) { } 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 81d0e25782db..56922be73bec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -230,7 +230,6 @@ public class NotificationEntryManager implements } } } - entry.setLowPriorityStateUpdated(false); } @Override @@ -346,8 +345,7 @@ public class NotificationEntryManager implements Dependency.get(LeakDetector.class).trackInstance(entry); // Construct the expanded view. - getRowBinder().inflateViews(entry, () -> performRemoveNotification(notification), - mNotificationData.get(entry.key) != null); + getRowBinder().inflateViews(entry, () -> performRemoveNotification(notification)); abortExistingInflation(key); @@ -384,13 +382,11 @@ public class NotificationEntryManager implements mNotificationData.update(entry, ranking, notification); - getRowBinder().inflateViews(entry, () -> performRemoveNotification(notification), - mNotificationData.get(entry.key) != null); - for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onPreEntryUpdated(entry); } + getRowBinder().inflateViews(entry, () -> performRemoveNotification(notification)); updateNotifications(); if (DEBUG) { @@ -422,6 +418,7 @@ public class NotificationEntryManager implements } } + @Override public void updateNotificationRanking(NotificationListenerService.RankingMap rankingMap) { List<NotificationEntry> entries = new ArrayList<>(); entries.addAll(mNotificationData.getActiveNotifications()); @@ -447,8 +444,7 @@ public class NotificationEntryManager implements entry, oldImportances.get(entry.key), oldAdjustments.get(entry.key), - NotificationUiAdjustment.extractFromNotificationEntry(entry), - mNotificationData.get(entry.key) != null); + NotificationUiAdjustment.extractFromNotificationEntry(entry)); } updateNotifications(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java index 88f4ca239af4..769cbb7b984c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java @@ -61,11 +61,6 @@ public class NotificationListController { mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); } - /** Should be called when the list controller is being destroyed. */ - public void destroy() { - mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener); - } - @SuppressWarnings("FieldCanBeLocal") private final NotificationEntryListener mEntryListener = new NotificationEntryListener() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java index 0b8596f9cba7..f1bb0d77deaa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java @@ -124,8 +124,7 @@ public class NotificationRowBinder { */ public void inflateViews( NotificationEntry entry, - Runnable onDismissRunnable, - boolean isUpdate) + Runnable onDismissRunnable) throws InflationException { ViewGroup parent = mListContainer.getViewParentForNotification(entry); PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext, @@ -135,13 +134,13 @@ public class NotificationRowBinder { if (entry.rowExists()) { entry.updateIcons(mContext, sbn); entry.reset(); - updateNotification(entry, pmUser, sbn, entry.getRow(), isUpdate); + updateNotification(entry, pmUser, sbn, entry.getRow()); } else { entry.createIcons(mContext, sbn); new RowInflaterTask().inflate(mContext, parent, entry, row -> { bindRow(entry, pmUser, sbn, row, onDismissRunnable); - updateNotification(entry, pmUser, sbn, row, isUpdate); + updateNotification(entry, pmUser, sbn, row); }); } } @@ -197,15 +196,14 @@ public class NotificationRowBinder { NotificationEntry entry, @Nullable Integer oldImportance, NotificationUiAdjustment oldAdjustment, - NotificationUiAdjustment newAdjustment, - boolean isUpdate) { + NotificationUiAdjustment newAdjustment) { if (NotificationUiAdjustment.needReinflate(oldAdjustment, newAdjustment)) { if (entry.rowExists()) { entry.reset(); PackageManager pmUser = StatusBar.getPackageManagerForUser( mContext, entry.notification.getUser().getIdentifier()); - updateNotification(entry, pmUser, entry.notification, entry.getRow(), isUpdate); + updateNotification(entry, pmUser, entry.notification, entry.getRow()); } else { // Once the RowInflaterTask is done, it will pick up the updated entry, so // no-op here. @@ -224,12 +222,8 @@ public class NotificationRowBinder { NotificationEntry entry, PackageManager pmUser, StatusBarNotification sbn, - ExpandableNotificationRow row, - boolean isUpdate) { - boolean isLowPriority = entry.ambient; - boolean wasLowPriority = row.isLowPriority(); - row.setIsLowPriority(isLowPriority); - row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority)); + ExpandableNotificationRow row) { + row.setIsLowPriority(entry.ambient); // bind the click event to the content area checkNotNull(mNotificationClicker).register(row, sbn); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java index c886685424f7..396a3a7834fb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java @@ -45,7 +45,7 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener { private boolean mReorderingAllowed; private VisibilityLocationProvider mVisibilityLocationProvider; private ArraySet<View> mAllowedReorderViews = new ArraySet<>(); - private ArraySet<View> mLowPriorityReorderingViews = new ArraySet<>(); + private ArraySet<NotificationEntry> mLowPriorityReorderingViews = new ArraySet<>(); private ArraySet<View> mAddedChildren = new ArraySet<>(); private boolean mPulsing; @@ -53,14 +53,21 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener { public VisualStabilityManager(NotificationEntryManager notificationEntryManager) { notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() { @Override - public void onEntryReinflated(NotificationEntry entry) { - if (entry.hasLowPriorityStateUpdated()) { - onLowPriorityUpdated(entry); - if (mPresenter != null) { - mPresenter.updateNotificationViews(); - } + public void onPreEntryUpdated(NotificationEntry entry) { + final boolean mAmbientStateHasChanged = + entry.ambient != entry.getRow().isLowPriority(); + if (mAmbientStateHasChanged) { + mLowPriorityReorderingViews.add(entry); } } + + @Override + public void onPostEntryUpdated(NotificationEntry entry) { + // This line is technically not required as we'll get called as the hierarchy + // manager will call onReorderingFinished() immediately before this. + // TODO: Find a way to make this relationship more explicit + mLowPriorityReorderingViews.remove(entry); + } }); } @@ -142,7 +149,7 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener { if (mAddedChildren.contains(row)) { return true; } - if (mLowPriorityReorderingViews.contains(row)) { + if (mLowPriorityReorderingViews.contains(row.getEntry())) { return true; } if (mAllowedReorderViews.contains(row) @@ -172,10 +179,6 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener { } } - private void onLowPriorityUpdated(NotificationEntry entry) { - mLowPriorityReorderingViews.add(entry.getRow()); - } - /** * Notify the visual stability manager that a new view was added and should be allowed to * reorder next time. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index f74de5beb585..f6d4ce22e905 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -622,10 +622,6 @@ public final class NotificationEntry { return null; } - public boolean hasLowPriorityStateUpdated() { - return row != null && row.hasLowPriorityStateUpdated(); - } - public void removeRow() { if (row != null) row.setRemoved(); } @@ -650,10 +646,6 @@ public final class NotificationEntry { return parent == null; } - public void setLowPriorityStateUpdated(boolean updated) { - if (row != null) row.setLowPriorityStateUpdated(updated); - } - /** * @return Can the underlying notification be cleared? This can be different from whether the * notification can be dismissed in case notifications are sensitive on the lockscreen. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index b8e33a8e0d2d..69828c166463 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -124,6 +124,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f; private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30); private boolean mUpdateBackgroundOnUpdate; + private boolean mNotificationTranslationFinished = false; /** * Listener for when {@link ExpandableNotificationRow} is laid out. @@ -133,7 +134,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } private LayoutListener mLayoutListener; - private boolean mLowPriorityStateUpdated; private final NotificationInflater mNotificationInflater; private int mIconTransformContentShift; private int mIconTransformContentShiftNoIcon; @@ -1213,6 +1213,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView l.initView(); l.reInflateViews(); } + mStatusBarNotification.clearPackageContext(); mNotificationInflater.clearCachesAndReInflate(); onNotificationUpdated(); } @@ -1453,6 +1454,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mIsBlockingHelperShowing; } + public boolean isBlockingHelperShowingAndTranslationFinished() { + return mIsBlockingHelperShowing && mNotificationTranslationFinished; + } + public void setOnDismissRunnable(Runnable onDismissRunnable) { mOnDismissRunnable = onDismissRunnable; } @@ -1577,15 +1582,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } - - public void setLowPriorityStateUpdated(boolean lowPriorityStateUpdated) { - mLowPriorityStateUpdated = lowPriorityStateUpdated; - } - - public boolean hasLowPriorityStateUpdated() { - return mLowPriorityStateUpdated; - } - public boolean isLowPriority() { return mIsLowPriority; } @@ -1851,7 +1847,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } void onGutsOpened() { - resetTranslation(); updateContentAccessibilityImportanceForGuts(false /* isEnabled */); } @@ -1905,11 +1900,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public void setTranslation(float translationX) { - if (areGutsExposed()) { - // Don't translate if guts are showing. + if (isBlockingHelperShowingAndTranslationFinished()) { + mGuts.setTranslationX(translationX); return; - } - if (!mShouldTranslateContents) { + } else if (!mShouldTranslateContents) { setTranslationX(translationX); } else if (mTranslateableViews != null) { // Translate the group of views @@ -1925,6 +1919,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView // positioning, so we can use the scrollX instead. getEntry().expandedIcon.setScrollX((int) -translationX); } + if (mMenuRow.getMenuView() != null) { mMenuRow.onParentTranslationUpdate(translationX); } @@ -1936,6 +1931,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return getTranslationX(); } + if (isBlockingHelperShowingAndCanTranslate()) { + return mGuts.getTranslationX(); + } + if (mTranslateableViews != null && mTranslateableViews.size() > 0) { // All of the views in the list should have same translation, just use first one. return mTranslateableViews.get(0).getTranslationX(); @@ -1944,15 +1943,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return 0; } + private boolean isBlockingHelperShowingAndCanTranslate() { + return areGutsExposed() && mIsBlockingHelperShowing && mNotificationTranslationFinished; + } + public Animator getTranslateViewAnimator(final float leftTarget, AnimatorUpdateListener listener) { if (mTranslateAnim != null) { mTranslateAnim.cancel(); } - if (areGutsExposed()) { - // No translation if guts are exposed. - return null; - } + final ObjectAnimator translateAnim = ObjectAnimator.ofFloat(this, TRANSLATE_CONTENT, leftTarget); if (listener != null) { @@ -1968,6 +1968,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public void onAnimationEnd(Animator anim) { + if (mIsBlockingHelperShowing) { + mNotificationTranslationFinished = true; + } if (!cancelled && leftTarget == 0) { mMenuRow.resetMenu(); mTranslateAnim = null; 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 7105876907bf..fbf1e310abf2 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 @@ -578,6 +578,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @Override @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void onDensityOrFontScaleChanged() { + reinflateViews(); + } + + private void reinflateViews() { inflateFooterView(); inflateEmptyShadeView(); updateFooter(); @@ -608,6 +612,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd mCornerRadius = newRadius; invalidate(); } + reinflateViews(); } @VisibleForTesting 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 2a8808021715..7882fd32e044 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 @@ -195,6 +195,9 @@ public class StackScrollAlgorithm { return false; } ExpandableNotificationRow row = (ExpandableNotificationRow) v; + if (row.isBlockingHelperShowingAndTranslationFinished()) { + return true; + } if (row.areGutsExposed() || !row.getEntry().hasFinishedInitialization()) { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index 4c1c0a4b4e58..de0e194ef90c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -141,10 +141,6 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, mAnimationStateHandler = handler; } - public void destroy() { - Dependency.get(StatusBarStateController.class).removeCallback(this); - } - private void initResources() { Resources resources = mContext.getResources(); mStatusBarHeight = resources.getDimensionPixelSize( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 651670cbf2c0..ebd420478c0f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -45,6 +45,8 @@ import android.content.res.Configuration; import android.graphics.Canvas; import android.graphics.Point; import android.graphics.Rect; +import android.graphics.Region; +import android.graphics.Region.Op; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -128,8 +130,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav private Rect mBackButtonBounds = new Rect(); private Rect mRecentsButtonBounds = new Rect(); private Rect mRotationButtonBounds = new Rect(); + private final Region mActiveRegion = new Region(); private int[] mTmpPosition = new int[2]; - private Rect mTmpRect = new Rect(); private KeyButtonDrawable mBackIcon; private KeyButtonDrawable mHomeDefaultIcon; @@ -954,17 +956,22 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - updateButtonLocationOnScreen(getBackButton(), mBackButtonBounds); - updateButtonLocationOnScreen(getHomeButton(), mHomeButtonBounds); - updateButtonLocationOnScreen(getRecentsButton(), mRecentsButtonBounds); - updateButtonLocationOnScreen(getRotateSuggestionButton(), mRotationButtonBounds); + + mActiveRegion.setEmpty(); + updateButtonLocation(getBackButton(), mBackButtonBounds, true); + updateButtonLocation(getHomeButton(), mHomeButtonBounds, false); + updateButtonLocation(getRecentsButton(), mRecentsButtonBounds, false); + updateButtonLocation(getRotateSuggestionButton(), mRotationButtonBounds, true); + // TODO: Handle button visibility changes + mOverviewProxyService.onActiveNavBarRegionChanges(mActiveRegion); if (mGestureHelper != null) { mGestureHelper.onLayout(changed, left, top, right, bottom); } mRecentsOnboarding.setNavBarHeight(getMeasuredHeight()); } - private void updateButtonLocationOnScreen(ButtonDispatcher button, Rect buttonBounds) { + private void updateButtonLocation(ButtonDispatcher button, Rect buttonBounds, + boolean isActive) { View view = button.getCurrentView(); if (view == null) { buttonBounds.setEmpty(); @@ -975,6 +982,14 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav final float posY = view.getTranslationY(); view.setTranslationX(0); view.setTranslationY(0); + + if (isActive) { + view.getLocationOnScreen(mTmpPosition); + buttonBounds.set(mTmpPosition[0], mTmpPosition[1], + mTmpPosition[0] + view.getMeasuredWidth(), + mTmpPosition[1] + view.getMeasuredHeight()); + mActiveRegion.op(buttonBounds, Op.UNION); + } view.getLocationInWindow(mTmpPosition); buttonBounds.set(mTmpPosition[0], mTmpPosition[1], mTmpPosition[0] + view.getMeasuredWidth(), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 069703e9dd81..f4fa1e8246e6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -590,11 +590,6 @@ public class NotificationPanelView extends PanelView implements mClockPositionResult.clockX, CLOCK_ANIMATION_PROPERTIES, animateClock); PropertyAnimator.setProperty(mKeyguardStatusView, AnimatableProperty.Y, mClockPositionResult.clockY, CLOCK_ANIMATION_PROPERTIES, animateClock); - // Move big clock up while pulling up the bouncer - PropertyAnimator.setProperty(mBigClockContainer, AnimatableProperty.Y, - MathUtils.lerp(-mBigClockContainer.getHeight(), 0, - Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(getExpandedFraction())), - CLOCK_ANIMATION_PROPERTIES, animateClock); updateClock(); stackScrollerPadding = mClockPositionResult.stackScrollerPadding; } @@ -1334,8 +1329,7 @@ public class NotificationPanelView extends PanelView implements } }; - private void setKeyguardBottomAreaVisibility(int statusBarState, - boolean goingToFullShade) { + private void setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade) { mKeyguardBottomArea.animate().cancel(); if (goingToFullShade) { mKeyguardBottomArea.animate() @@ -1438,6 +1432,7 @@ public class NotificationPanelView extends PanelView implements if (mBarState == StatusBarState.SHADE_LOCKED || mBarState == StatusBarState.KEYGUARD) { updateKeyguardBottomAreaAlpha(); + updateBigClockAlpha(); } if (mBarState == StatusBarState.SHADE && mQsExpanded && !mStackScrollerOverscrolling && mQsScrimEnabled) { @@ -1883,6 +1878,19 @@ public class NotificationPanelView extends PanelView implements } } + /** + * Custom clock fades away when user drags up to unlock or pulls down quick settings. + * + * Updates alpha of custom clock to match the alpha of the KeyguardBottomArea. See + * {@link updateKeyguardBottomAreaAlpha}. + */ + private void updateBigClockAlpha() { + float expansionAlpha = MathUtils.map(isUnlockHintRunning() + ? 0 : KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1f, 0f, 1f, getExpandedFraction()); + float alpha = Math.min(expansionAlpha, 1 - getQsExpansionFraction()); + mBigClockContainer.setAlpha(alpha); + } + private float getNotificationsTopY() { if (mNotificationStackScroller.getNotGoneChildCount() == 0) { return getExpandedHeight(); @@ -2597,6 +2605,7 @@ public class NotificationPanelView extends PanelView implements } mNotificationStackScroller.setExpandedHeight(expandedHeight); updateKeyguardBottomAreaAlpha(); + updateBigClockAlpha(); updateStatusBarIcons(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index 18711c0d1ae3..2799191a886f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -284,25 +284,6 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, }); } - public void destroy() { - mRotationLockController.removeCallback(this); - mBluetooth.removeCallback(this); - mProvisionedController.removeCallback(this); - mZenController.removeCallback(this); - mCast.removeCallback(mCastCallback); - mHotspot.removeCallback(mHotspotCallback); - mNextAlarmController.removeCallback(mNextAlarmCallback); - mDataSaver.removeCallback(this); - mKeyguardMonitor.removeCallback(this); - mPrivacyItemController.removeCallback(this); - SysUiServiceProvider.getComponent(mContext, CommandQueue.class).removeCallback(this); - mContext.unregisterReceiver(mIntentReceiver); - - NotificationManager noMan = mContext.getSystemService(NotificationManager.class); - mCurrentNotifs.forEach(v -> noMan.cancelAsUser(v.first, SystemMessage.NOTE_INSTANT_APPS, - new UserHandle(v.second))); - } - @Override public void onZenChanged(int zen) { updateVolumeZen(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java index 84f1cef19b77..73ab5274a0ab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java @@ -56,6 +56,7 @@ import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.recents.utilities.Utilities; +import com.android.systemui.shared.system.InputChannelCompat.InputEventDispatcher; import com.android.systemui.shared.system.NavigationBarCompat; import java.io.PrintWriter; @@ -676,8 +677,13 @@ public class QuickStepController implements GestureHelper { } private boolean proxyMotionEvents(MotionEvent event) { - final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy(); event.transform(mTransformGlobalMatrix); + InputEventDispatcher dispatcher = mOverviewEventSender.getInputEventDispatcher(); + if (dispatcher != null) { + dispatcher.dispatch(event); + } + + final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy(); try { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { overviewProxy.onPreMotionEvent(mNavigationBarView.getDownHitTarget()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 9f3bec60cb6d..653ec50e7682 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -189,7 +189,6 @@ import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; @@ -653,7 +652,7 @@ public class StatusBar extends SystemUI implements DemoMode, mColorExtractor.addOnColorsChangedListener(this); mStatusBarStateController.addCallback(this, - StatusBarStateControllerImpl.RANK_STATUS_BAR); + SysuiStatusBarStateController.RANK_STATUS_BAR); mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); mDreamManager = IDreamManager.Stub.asInterface( @@ -1550,12 +1549,12 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) { - mEntryManager.updateNotificationRanking(null /* rankingMap */); + mEntryManager.updateNotifications(); } @Override public void onAmbientStateChanged(NotificationEntry entry, boolean isAmbient) { - mEntryManager.updateNotificationRanking(null); + mEntryManager.updateNotifications(); if (isAmbient) { mDozeServiceHost.fireNotificationPulse(); } else if (!mAmbientPulseManager.hasNotifications()) { @@ -2843,38 +2842,6 @@ public class StatusBar extends SystemUI implements DemoMode, startActivityDismissingKeyguard(intent, onlyProvisioned, true /* dismissShade */); } - public void destroy() { - // Begin old BaseStatusBar.destroy(). - mContext.unregisterReceiver(mBannerActionBroadcastReceiver); - mLockscreenUserManager.destroy(); - try { - mNotificationListener.unregisterAsSystemService(); - } catch (RemoteException e) { - // Ignore. - } - mNotificationListController.destroy(); - // End old BaseStatusBar.destroy(). - if (mStatusBarWindow != null) { - mWindowManager.removeViewImmediate(mStatusBarWindow); - mStatusBarWindow = null; - } - mNavigationBarController.destroy(); - mContext.unregisterReceiver(mBroadcastReceiver); - mContext.unregisterReceiver(mDemoReceiver); - mAssistManager.destroy(); - mHeadsUpManager.destroy(); - mStatusBarStateController.removeCallback(this); - - if (mQSPanel != null && mQSPanel.getHost() != null) { - mQSPanel.getHost().destroy(); - } - Dependency.get(ActivityStarterDelegate.class).setActivityStarterImpl(null); - mDeviceProvisionedController.removeCallback(mUserSetupObserver); - Dependency.get(ConfigurationController.class).removeCallback(this); - mZenController.removeCallback(this); - mAppOpsController.removeCallback(APP_OPS, this); - } - private boolean mDemoModeAllowed; private boolean mDemoMode; @@ -4036,8 +4003,7 @@ public class StatusBar extends SystemUI implements DemoMode, float viewY = screenY - mTmpInt2[1]; if (0 <= viewX && viewX <= mAmbientIndicationContainer.getWidth() && 0 <= viewY && viewY <= mAmbientIndicationContainer.getHeight()) { - if (mAmbientIndicationContainer instanceof DozeReceiver) - ((DozeReceiver) mAmbientIndicationContainer).onDozeDoubleTap(); + dispatchTap(mAmbientIndicationContainer, viewX, viewY); } } } @@ -4052,6 +4018,12 @@ public class StatusBar extends SystemUI implements DemoMode, mScrimController.setAodFrontScrimAlpha(scrimOpacity); } + private void dispatchTap(View view, float x, float y) { + long now = SystemClock.elapsedRealtime(); + dispatchTouchEvent(view, x, y, now, MotionEvent.ACTION_DOWN); + dispatchTouchEvent(view, x, y, now, MotionEvent.ACTION_UP); + } + private void dispatchTouchEvent(View view, float x, float y, long now, int action) { MotionEvent ev = MotionEvent.obtain(now, now, action, x, y, 0 /* meta */); view.dispatchTouchEvent(ev); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index e9705ff35a4d..3ce66c5de372 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -258,6 +258,11 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, } } + @Override + public void onOverlayChanged() { + onDensityOrFontScaleChanged(); + } + private void updateNotificationOnUiModeChanged() { ArrayList<NotificationEntry> userNotifications = mEntryManager.getNotificationData().getNotificationsForCurrentUser(); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java index 77895c97051c..190ce7550511 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java @@ -75,6 +75,17 @@ public class KeyguardSliceViewTest extends SysuiTestCase { } @Test + public void hasHeader_readsSliceData() { + ListBuilder builder = new ListBuilder(getContext(), mSliceUri, ListBuilder.INFINITY); + mKeyguardSliceView.onChanged(builder.build()); + Assert.assertFalse("View should not have a header", mKeyguardSliceView.hasHeader()); + + builder.setHeader(new ListBuilder.HeaderBuilder().setTitle("header title!")); + mKeyguardSliceView.onChanged(builder.build()); + Assert.assertTrue("View should have a header", mKeyguardSliceView.hasHeader()); + } + + @Test public void refresh_replacesSliceContentAndNotifiesListener() { AtomicBoolean notified = new AtomicBoolean(); mKeyguardSliceView.setContentChangeListener(()-> notified.set(true)); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java new file mode 100644 index 000000000000..f813ac693d42 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.keyguard.clock; + +import static com.google.common.truth.Truth.assertThat; + +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.LeakCheck; +import android.testing.TestableLooper.RunWithLooper; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.dock.DockManager; +import com.android.systemui.dock.DockManagerFake; +import com.android.systemui.utils.leaks.FakeExtensionController; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@RunWithLooper +public final class ClockManagerTest extends SysuiTestCase { + + private ClockManager mClockManager; + private LeakCheck mLeakCheck; + private FakeExtensionController mFakeExtensionController; + private DockManagerFake mFakeDockManager; + @Mock ClockManager.ClockChangedListener mMockListener; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mLeakCheck = new LeakCheck(); + mFakeExtensionController = new FakeExtensionController(mLeakCheck); + mFakeDockManager = new DockManagerFake(); + mClockManager = new ClockManager(getContext(), mFakeExtensionController, + mFakeDockManager); + mClockManager.addOnClockChangedListener(mMockListener); + } + + @After + public void tearDown() { + mClockManager.removeOnClockChangedListener(mMockListener); + } + + @Test + public void dockEvent() { + mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED); + assertThat(mClockManager.isDocked()).isTrue(); + } + + @Test + public void undockEvent() { + mFakeDockManager.setDockEvent(DockManager.STATE_NONE); + assertThat(mClockManager.isDocked()).isFalse(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/DefaultClockSupplierTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/DefaultClockSupplierTest.java new file mode 100644 index 000000000000..1a3b198ac0d6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/DefaultClockSupplierTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.keyguard.clock; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper.RunWithLooper; +import android.view.LayoutInflater; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.ClockPlugin; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@RunWithLooper +public final class DefaultClockSupplierTest extends SysuiTestCase { + + private static final String BUBBLE_CLOCK = BubbleClockController.class.getName(); + private static final Class<?> BUBBLE_CLOCK_CLASS = BubbleClockController.class; + + private DefaultClockSupplier mDefaultClockSupplier; + @Mock SettingsWrapper mMockSettingsWrapper; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mDefaultClockSupplier = new DefaultClockSupplier(mMockSettingsWrapper, + LayoutInflater.from(getContext())); + } + + @Test + public void get_default() { + // GIVEN that settings doesn't contain any values + when(mMockSettingsWrapper.getLockScreenCustomClockFace()).thenReturn(null); + when(mMockSettingsWrapper.getDockedClockFace()).thenReturn(null); + // WHEN get is called + ClockPlugin plugin = mDefaultClockSupplier.get(); + // THEN the result is null, indicated the default clock face should be used. + assertThat(plugin).isNull(); + } + + @Test + public void get_customClock() { + // GIVEN that settings is set to the bubble clock face + when(mMockSettingsWrapper.getLockScreenCustomClockFace()).thenReturn(BUBBLE_CLOCK); + // WHEN get is called + ClockPlugin plugin = mDefaultClockSupplier.get(); + // THEN the plugin is the bubble clock face. + assertThat(plugin).isInstanceOf(BUBBLE_CLOCK_CLASS); + } + + @Test + public void get_badSettingsValue() { + // GIVEN that settings contains a value that doesn't correspond to a + // custom clock face. + when(mMockSettingsWrapper.getLockScreenCustomClockFace()).thenReturn("bad value"); + // WHEN get is called + ClockPlugin plugin = mDefaultClockSupplier.get(); + // THEN the result is null. + assertThat(plugin).isNull(); + } + + @Test + public void get_dockedDefault() { + // GIVEN docked + mDefaultClockSupplier.setDocked(true); + // WHEN get is called + ClockPlugin plugin = mDefaultClockSupplier.get(); + // THEN the result is null, indicating the default clock face. + assertThat(plugin).isNull(); + } + + @Test + public void get_dockedCustomClock() { + // GIVEN docked and settings is set to the bubble clock face + mDefaultClockSupplier.setDocked(true); + when(mMockSettingsWrapper.getDockedClockFace()).thenReturn(BUBBLE_CLOCK); + // WHEN get is called + ClockPlugin plugin = mDefaultClockSupplier.get(); + // THEN the plugin is the bubble clock face. + assertThat(plugin).isInstanceOf(BUBBLE_CLOCK_CLASS); + } + + @Test + public void get_badDockedSettingsValue() { + // GIVEN docked and settings contains a value that doesn't correspond to + // an available clock face. + mDefaultClockSupplier.setDocked(true); + when(mMockSettingsWrapper.getDockedClockFace()).thenReturn("bad value"); + // WHEN get is called + ClockPlugin plugin = mDefaultClockSupplier.get(); + // THEN the result is null. + assertThat(plugin).isNull(); + } + + @Test + public void get_badDockedSettingsFallback() { + // GIVEN docked and settings contains a value that doesn't correspond to + // an available clock face, but locked screen settings is set to bubble + // clock. + mDefaultClockSupplier.setDocked(true); + when(mMockSettingsWrapper.getDockedClockFace()).thenReturn("bad value"); + when(mMockSettingsWrapper.getLockScreenCustomClockFace()).thenReturn(BUBBLE_CLOCK); + // WHEN get is called + ClockPlugin plugin = mDefaultClockSupplier.get(); + // THEN the plugin is the bubble clock face. + assertThat(plugin).isInstanceOf(BUBBLE_CLOCK_CLASS); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 2742577db860..d9315f937867 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -112,6 +112,9 @@ public class BubbleControllerTest extends SysuiTestCase { verify(mNotificationEntryManager, atLeastOnce()) .addNotificationEntryListener(mEntryListenerCaptor.capture()); mEntryListener = mEntryListenerCaptor.getValue(); + + // Reset the data + BubbleData.getInstance().clear(); } @Test @@ -207,12 +210,12 @@ public class BubbleControllerTest extends SysuiTestCase { verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().key); // Last added is the one that is expanded - assertEquals(mRow2.getEntry(), stackView.getExpandedBubble().getEntry()); + assertEquals(mRow2.getEntry(), stackView.getExpandedBubbleView().getEntry()); assertFalse(mRow2.getEntry().showInShadeWhenBubble()); // Switch which bubble is expanded stackView.setExpandedBubble(mRow.getEntry()); - assertEquals(mRow.getEntry(), stackView.getExpandedBubble().getEntry()); + assertEquals(mRow.getEntry(), stackView.getExpandedBubbleView().getEntry()); assertFalse(mRow.getEntry().showInShadeWhenBubble()); // collapse for previous bubble @@ -262,19 +265,19 @@ public class BubbleControllerTest extends SysuiTestCase { verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().key); // Last added is the one that is expanded - assertEquals(mRow2.getEntry(), stackView.getExpandedBubble().getEntry()); + assertEquals(mRow2.getEntry(), stackView.getExpandedBubbleView().getEntry()); assertFalse(mRow2.getEntry().showInShadeWhenBubble()); // Dismiss currently expanded - mBubbleController.removeBubble(stackView.getExpandedBubble().getKey()); + mBubbleController.removeBubble(stackView.getExpandedBubbleView().getKey()); verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().key); // Make sure next bubble is selected - assertEquals(mRow.getEntry(), stackView.getExpandedBubble().getEntry()); + assertEquals(mRow.getEntry(), stackView.getExpandedBubbleView().getEntry()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key); // Dismiss that one - mBubbleController.removeBubble(stackView.getExpandedBubble().getKey()); + mBubbleController.removeBubble(stackView.getExpandedBubbleView().getKey()); // Make sure state changes and collapse happens verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().key); diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt index 98bf3c2743a8..5d3f6cacc80f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.privacy import android.app.ActivityManager import android.app.AppOpsManager +import android.content.Context import android.content.Intent import android.content.pm.UserInfo import android.os.Handler @@ -28,6 +29,8 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import com.android.systemui.Dependency +import com.android.systemui.Dependency.BG_HANDLER +import com.android.systemui.Dependency.MAIN_HANDLER import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.appops.AppOpItem @@ -81,15 +84,20 @@ class PrivacyItemControllerTest : SysuiTestCase() { private lateinit var testableLooper: TestableLooper private lateinit var privacyItemController: PrivacyItemController + private lateinit var handler: Handler + + fun PrivacyItemController(context: Context) = + PrivacyItemController(context, appOpsController, handler, handler) @Before fun setup() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) + handler = Handler(testableLooper.looper) appOpsController = mDependency.injectMockDependency(AppOpsController::class.java) - mDependency.injectTestDependency(Dependency.BG_LOOPER, testableLooper.looper) - mDependency.injectTestDependency(Dependency.MAIN_HANDLER, Handler(testableLooper.looper)) + mDependency.injectTestDependency(Dependency.BG_HANDLER, handler) + mDependency.injectTestDependency(Dependency.MAIN_HANDLER, handler) mContext.addMockSystemService(UserManager::class.java, userManager) mContext.getOrCreateTestableResources().addOverride(R.string.device_services, DEVICE_SERVICES_STRING) diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto index 3c910696ba60..d9adec85d40e 100644 --- a/proto/src/metrics_constants/metrics_constants.proto +++ b/proto/src/metrics_constants/metrics_constants.proto @@ -6975,6 +6975,23 @@ message MetricsEvent { // formerly: histogram system_cost_for_smart_sharing FIELD_TIME_TO_APP_TARGETS = 1653; + // Open: Settings > Panel for Internet Connectivity + PANEL_INTERNET_CONNECTIVITY = 1654; + + // Open: Settings > Panel for Volume + PANEL_VOLUME = 1655; + + // Open: Settings > Panel for NFC + PANEL_NFC = 1656; + + // Open: Settings > Panel for Media Output + PANEL_MEDIA_OUTPUT = 1657; + + // ACTION: An interaction with a Slice or other component in the Panel. + // CATEGORY: SETTINGS + // OS: Q + ACTION_PANEL_INTERACTION = 1658; + // ---- End Q Constants, all Q constants go above this line ---- // Add new aosp constants above this line. // END OF AOSP CONSTANTS diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java index 9b863a9f2d26..61f63d3bbc3d 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java @@ -47,7 +47,7 @@ final class RemoteAugmentedAutofillService private static final String TAG = RemoteAugmentedAutofillService.class.getSimpleName(); - private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS; + private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS; RemoteAugmentedAutofillService(Context context, ComponentName serviceName, int userId, RemoteAugmentedAutofillServiceCallbacks callbacks, @@ -106,6 +106,12 @@ final class RemoteAugmentedAutofillService activityComponent, focusedId, focusedValue)); } + @Override + public String toString() { + return "RemoteAugmentedAutofillService[" + + ComponentName.flattenToShortString(getComponentName()) + "]"; + } + /** * Called by {@link Session} when it's time to destroy all augmented autofill requests. */ @@ -181,11 +187,13 @@ final class RemoteAugmentedAutofillService @Override protected void onTimeout(RemoteAugmentedAutofillService remoteService) { - Slog.wtf(TAG, "timed out: " + this); + // TODO(b/122858578): must update the logged AUTOFILL_AUGMENTED_REQUEST with the + // timeout + Slog.w(TAG, "PendingAutofillRequest timed out (" + TIMEOUT_REMOTE_REQUEST_MILLIS + + "ms) for " + remoteService); // NOTE: so far we don't need notify RemoteAugmentedAutofillServiceCallbacks finish(); } - } public interface RemoteAugmentedAutofillServiceCallbacks diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java index 3c52e17ce1e8..4ed5c3d263b6 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java @@ -37,6 +37,9 @@ final class ContentCaptureServerSession { final IBinder mActivityToken; private final ContentCapturePerUserService mService; private final RemoteContentCaptureService mRemoteService; + + // NOTE: this is the "internal" context (like package and taskId), not the explicit content + // set by apps - those are only send to the ContentCaptureService. private final ContentCaptureContext mContentCaptureContext; /** diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index d1cd072ee215..dad428acbfee 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -2842,6 +2842,8 @@ public class ConnectivityService extends IConnectivityManager.Stub if (DBG) { log(nai.name() + " got DISCONNECTED, was satisfying " + nai.numNetworkRequests()); } + // Clear all notifications of this network. + mNotifier.clearNotification(nai.network.netId); // A network agent has disconnected. // TODO - if we move the logic to the network agent (have them disconnect // because they lost all their requests or because their score isn't good) diff --git a/services/core/java/com/android/server/ExtconUEventObserver.java b/services/core/java/com/android/server/ExtconUEventObserver.java index b3084f50071f..eb591528bc90 100644 --- a/services/core/java/com/android/server/ExtconUEventObserver.java +++ b/services/core/java/com/android/server/ExtconUEventObserver.java @@ -22,8 +22,11 @@ import android.util.Slog; import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.regex.Pattern; /** * A specialized UEventObserver that receives UEvents from the kernel for devices in the {@code @@ -40,13 +43,14 @@ import java.util.Map; * time in that process. Once started the UEvent thread will not stop (although it can stop * notifying UEventObserver's via stopObserving()). * - * <p> - * * @hide */ public abstract class ExtconUEventObserver extends UEventObserver { private static final String TAG = "ExtconUEventObserver"; private static final boolean LOG = false; + private static final String SELINUX_POLICIES_NEED_TO_BE_CHANGED = + "This probably means the selinux policies need to be changed."; + private final Map<String, ExtconInfo> mExtconInfos = new ArrayMap<>(); @Override @@ -70,15 +74,47 @@ public abstract class ExtconUEventObserver extends UEventObserver { /** Starts observing {@link ExtconInfo#getDevicePath()}. */ public void startObserving(ExtconInfo extconInfo) { - mExtconInfos.put(extconInfo.getDevicePath(), extconInfo); - if (LOG) Slog.v(TAG, "Observing " + extconInfo.getDevicePath()); - startObserving("DEVPATH=" + extconInfo.getDevicePath()); + String devicePath = extconInfo.getDevicePath(); + if (devicePath == null) { + Slog.wtf(TAG, "Unable to start observing " + extconInfo.getName() + + " because the device path is null. " + SELINUX_POLICIES_NEED_TO_BE_CHANGED); + } else { + mExtconInfos.put(devicePath, extconInfo); + if (LOG) Slog.v(TAG, "Observing " + devicePath); + startObserving("DEVPATH=" + devicePath); + } } /** An External Connection to watch. */ public static final class ExtconInfo { private static final String TAG = "ExtconInfo"; + /** Returns a new list of all external connections whose name matches {@code regex}. */ + public static List<ExtconInfo> getExtconInfos(@Nullable String regex) { + Pattern p = regex == null ? null : Pattern.compile(regex); + File file = new File("/sys/class/extcon"); + File[] files = file.listFiles(); + if (files == null) { + Slog.wtf(TAG, file + " exists " + file.exists() + " isDir " + file.isDirectory() + + " but listFiles returns null. " + + SELINUX_POLICIES_NEED_TO_BE_CHANGED); + return new ArrayList<>(0); // Always return a new list. + } else { + ArrayList list = new ArrayList(files.length); + for (File f : files) { + String name = f.getName(); + if (p == null || p.matcher(name).matches()) { + ExtconInfo uei = new ExtconInfo(name); + list.add(uei); + if (LOG) Slog.d(TAG, name + " matches " + regex); + } else { + if (LOG) Slog.d(TAG, name + " does not match " + regex); + } + } + return list; + } + } + private final String mName; public ExtconInfo(String name) { @@ -123,6 +159,15 @@ public abstract class ExtconUEventObserver extends UEventObserver { /** Does the {@link /sys/class/extcon} directory exist */ public static boolean extconExists() { File extconDir = new File("/sys/class/extcon"); - return extconDir.exists() && extconDir.isDirectory(); + boolean retVal = extconDir.exists() && extconDir.isDirectory(); + // TODO(b/124364409): return the correct value after selinux policy is updated. + if (retVal) { + Slog.w(TAG, extconDir + " exists " + extconDir.exists() + " isDir " + + extconDir.isDirectory() + + " but reporting it does not exist until selinux policies are updated." + + " see b/124364409" + ); + } + return false; } } diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 4834ce0da9b3..0b0934bc5a5e 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -67,6 +67,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.IInterface; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; @@ -109,6 +110,7 @@ import com.android.server.location.LocationRequestStatistics.PackageProviderKey; import com.android.server.location.LocationRequestStatistics.PackageStatistics; import com.android.server.location.MockProvider; import com.android.server.location.PassiveProvider; +import com.android.server.location.RemoteListenerHelper; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -121,6 +123,8 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; +import java.util.function.Consumer; +import java.util.function.Function; /** * The service class that manages LocationProviders and issues location @@ -225,11 +229,14 @@ public class LocationManagerService extends ILocationManager.Stub { private final ArraySet<String> mIgnoreSettingsPackageWhitelist = new ArraySet<>(); @GuardedBy("mLock") - private final ArrayMap<IBinder, CallerIdentity> mGnssMeasurementsListeners = new ArrayMap<>(); - + private final ArrayMap<IBinder, LinkedListener<IGnssMeasurementsListener>> + mGnssMeasurementsListeners = new ArrayMap<>(); @GuardedBy("mLock") - private final ArrayMap<IBinder, CallerIdentity> + private final ArrayMap<IBinder, LinkedListener<IGnssNavigationMessageListener>> mGnssNavigationMessageListeners = new ArrayMap<>(); + @GuardedBy("mLock") + private final ArrayMap<IBinder, LinkedListener<IGnssStatusListener>> + mGnssStatusListeners = new ArrayMap<>(); // current active user on the device - other users are denied location data private int mCurrentUserId = UserHandle.USER_SYSTEM; @@ -243,7 +250,7 @@ public class LocationManagerService extends ILocationManager.Stub { @GuardedBy("mLock") private IBatchedLocationCallback mGnssBatchingCallback; @GuardedBy("mLock") - private LinkedCallback mGnssBatchingDeathCallback; + private LinkedListener<IBatchedLocationCallback> mGnssBatchingDeathCallback; @GuardedBy("mLock") private boolean mGnssBatchingInProgress = false; @@ -485,7 +492,7 @@ public class LocationManagerService extends ILocationManager.Stub { && record.mIsForegroundUid != foreground) { if (D) { Log.d(TAG, "request from uid " + uid + " is now " - + (foreground ? "foreground" : "background)")); + + foregroundAsString(foreground)); } record.updateForeground(foreground); @@ -499,44 +506,48 @@ public class LocationManagerService extends ILocationManager.Stub { applyRequirementsLocked(provider); } - for (Entry<IBinder, CallerIdentity> entry : mGnssMeasurementsListeners.entrySet()) { - CallerIdentity callerIdentity = entry.getValue(); - if (callerIdentity.mUid == uid) { - if (D) { - Log.d(TAG, "gnss measurements listener from uid " + uid - + " is now " + (foreground ? "foreground" : "background)")); - } - if (foreground || isThrottlingExemptLocked(entry.getValue())) { - mGnssMeasurementsProvider.addListener( - IGnssMeasurementsListener.Stub.asInterface(entry.getKey()), - callerIdentity); - } else { - mGnssMeasurementsProvider.removeListener( - IGnssMeasurementsListener.Stub.asInterface(entry.getKey())); - } + updateGnssDataProviderOnUidImportanceChangedLocked(mGnssMeasurementsListeners, + mGnssMeasurementsProvider, IGnssMeasurementsListener.Stub::asInterface, + uid, foreground); + + updateGnssDataProviderOnUidImportanceChangedLocked(mGnssNavigationMessageListeners, + mGnssNavigationMessageProvider, IGnssNavigationMessageListener.Stub::asInterface, + uid, foreground); + + updateGnssDataProviderOnUidImportanceChangedLocked(mGnssStatusListeners, + mGnssStatusProvider, IGnssStatusListener.Stub::asInterface, uid, foreground); + } + + @GuardedBy("mLock") + private <TListener extends IInterface> void updateGnssDataProviderOnUidImportanceChangedLocked( + ArrayMap<IBinder, ? extends LinkedListenerBase> gnssDataListeners, + RemoteListenerHelper<TListener> gnssDataProvider, + Function<IBinder, TListener> mapBinderToListener, int uid, boolean foreground) { + for (Entry<IBinder, ? extends LinkedListenerBase> entry : gnssDataListeners.entrySet()) { + LinkedListenerBase linkedListener = entry.getValue(); + CallerIdentity callerIdentity = linkedListener.mCallerIdentity; + if (callerIdentity.mUid != uid) { + continue; } - } - for (Entry<IBinder, CallerIdentity> entry : mGnssNavigationMessageListeners.entrySet()) { - CallerIdentity callerIdentity = entry.getValue(); - if (callerIdentity.mUid == uid) { - if (D) { - Log.d(TAG, "gnss navigation message listener from uid " - + uid + " is now " - + (foreground ? "foreground" : "background)")); - } - if (foreground || isThrottlingExemptLocked(entry.getValue())) { - mGnssNavigationMessageProvider.addListener( - IGnssNavigationMessageListener.Stub.asInterface(entry.getKey()), - callerIdentity); - } else { - mGnssNavigationMessageProvider.removeListener( - IGnssNavigationMessageListener.Stub.asInterface(entry.getKey())); - } + if (D) { + Log.d(TAG, linkedListener.mListenerName + " from uid " + + uid + " is now " + foregroundAsString(foreground)); + } + + TListener listener = mapBinderToListener.apply(entry.getKey()); + if (foreground || isThrottlingExemptLocked(callerIdentity)) { + gnssDataProvider.addListener(listener, callerIdentity); + } else { + gnssDataProvider.removeListener(listener); } } } + private static String foregroundAsString(boolean foreground) { + return foreground ? "foreground" : "background"; + } + private static boolean isImportanceForeground(int importance) { return importance <= FOREGROUND_IMPORTANCE_CUTOFF; } @@ -1218,9 +1229,8 @@ public class LocationManagerService extends ILocationManager.Stub { * A wrapper class holding either an ILocationListener or a PendingIntent to receive * location updates. */ - private final class Receiver implements IBinder.DeathRecipient, PendingIntent.OnFinished { + private final class Receiver extends LinkedListenerBase implements PendingIntent.OnFinished { private static final long WAKELOCK_TIMEOUT_MILLIS = 60 * 1000; - final CallerIdentity mCallerIdentity; private final int mAllowedResolutionLevel; // resolution level allowed to receiver private final ILocationListener mListener; @@ -1240,6 +1250,7 @@ public class LocationManagerService extends ILocationManager.Stub { private Receiver(ILocationListener listener, PendingIntent intent, int pid, int uid, String packageName, WorkSource workSource, boolean hideFromAppOps) { + super(new CallerIdentity(uid, pid, packageName), "LocationListener"); mListener = listener; mPendingIntent = intent; if (listener != null) { @@ -1248,7 +1259,6 @@ public class LocationManagerService extends ILocationManager.Stub { mKey = intent; } mAllowedResolutionLevel = getAllowedResolutionLevel(pid, uid); - mCallerIdentity = new CallerIdentity(uid, pid, packageName); if (workSource != null && workSource.isEmpty()) { workSource = null; } @@ -1486,7 +1496,7 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public void binderDied() { - if (D) Log.d(TAG, "Location listener died"); + if (D) Log.d(TAG, "Remote " + mListenerName + " died."); synchronized (mLock) { removeUpdatesLocked(this); @@ -1617,53 +1627,59 @@ public class LocationManagerService extends ILocationManager.Stub { return false; } + CallerIdentity callerIdentity = new CallerIdentity(Binder.getCallingUid(), + Binder.getCallingPid(), packageName); synchronized (mLock) { mGnssBatchingCallback = callback; - mGnssBatchingDeathCallback = new LinkedCallback(callback); - try { - callback.asBinder().linkToDeath(mGnssBatchingDeathCallback, 0 /* flags */); - } catch (RemoteException e) { - // if the remote process registering the listener is already dead, just swallow the - // exception and return - Log.e(TAG, "Remote listener already died.", e); + mGnssBatchingDeathCallback = new LinkedListener<>(callback, + "BatchedLocationCallback", callerIdentity, + (IBatchedLocationCallback listener) -> { + stopGnssBatch(); + removeGnssBatchingCallback(); + }); + if (!linkToListenerDeathNotificationLocked(callback.asBinder(), + mGnssBatchingDeathCallback)) { return false; } - return true; } } - private class LinkedCallback implements IBinder.DeathRecipient { - private final IBatchedLocationCallback mCallback; + private abstract static class LinkedListenerBase implements IBinder.DeathRecipient { + protected final CallerIdentity mCallerIdentity; + protected final String mListenerName; - private LinkedCallback(@NonNull IBatchedLocationCallback callback) { - mCallback = callback; + private LinkedListenerBase(@NonNull CallerIdentity callerIdentity, + @NonNull String listenerName) { + mCallerIdentity = callerIdentity; + mListenerName = listenerName; } + } + + private static class LinkedListener<TListener> extends LinkedListenerBase { + private final TListener mListener; + private final Consumer<TListener> mBinderDeathCallback; - @NonNull - public IBatchedLocationCallback getUnderlyingListener() { - return mCallback; + private LinkedListener(@NonNull TListener listener, String listenerName, + @NonNull CallerIdentity callerIdentity, + @NonNull Consumer<TListener> binderDeathCallback) { + super(callerIdentity, listenerName); + mListener = listener; + mBinderDeathCallback = binderDeathCallback; } @Override public void binderDied() { - Log.d(TAG, "Remote Batching Callback died: " + mCallback); - stopGnssBatch(); - removeGnssBatchingCallback(); + if (D) Log.d(TAG, "Remote " + mListenerName + " died."); + mBinderDeathCallback.accept(mListener); } } @Override public void removeGnssBatchingCallback() { synchronized (mLock) { - try { - mGnssBatchingCallback.asBinder().unlinkToDeath(mGnssBatchingDeathCallback, - 0 /* flags */); - } catch (NoSuchElementException e) { - // if the death callback isn't connected (it should be...), log error, swallow the - // exception and return - Log.e(TAG, "Couldn't unlink death callback.", e); - } + unlinkFromListenerDeathNotificationLocked(mGnssBatchingCallback.asBinder(), + mGnssBatchingDeathCallback); mGnssBatchingCallback = null; mGnssBatchingDeathCallback = null; } @@ -2053,7 +2069,7 @@ public class LocationManagerService extends ILocationManager.Stub { } if (!provider.isUseableLocked()) { if (isSettingsExemptLocked(record)) { - providerRequest.forceLocation = true; + providerRequest.locationSettingsIgnored = true; providerRequest.lowPowerMode = false; } else { continue; @@ -2063,8 +2079,9 @@ public class LocationManagerService extends ILocationManager.Stub { LocationRequest locationRequest = record.mRealRequest; long interval = locationRequest.getInterval(); + // if we're forcing location, don't apply any throttling - if (!providerRequest.forceLocation && !isThrottlingExemptLocked( + if (!providerRequest.locationSettingsIgnored && !isThrottlingExemptLocked( record.mReceiver.mCallerIdentity)) { if (!record.mIsForegroundUid) { interval = Math.max(interval, backgroundThrottleInterval); @@ -2264,10 +2281,8 @@ public class LocationManagerService extends ILocationManager.Stub { if (receiver == null) { receiver = new Receiver(listener, null, pid, uid, packageName, workSource, hideFromAppOps); - try { - receiver.getListener().asBinder().linkToDeath(receiver, 0); - } catch (RemoteException e) { - Slog.e(TAG, "linkToDeath failed:", e); + if (!linkToListenerDeathNotificationLocked(receiver.getListener().asBinder(), + receiver)) { return null; } mReceivers.put(binder, receiver); @@ -2482,7 +2497,8 @@ public class LocationManagerService extends ILocationManager.Stub { if (D) Log.i(TAG, "remove " + Integer.toHexString(System.identityHashCode(receiver))); if (mReceivers.remove(receiver.mKey) != null && receiver.isListener()) { - receiver.getListener().asBinder().unlinkToDeath(receiver, 0); + unlinkFromListenerDeathNotificationLocked(receiver.getListener().asBinder(), + receiver); receiver.clearPendingBroadcastsLocked(); } @@ -2694,18 +2710,52 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override - public boolean registerGnssStatusCallback(IGnssStatusListener callback, String packageName) { + public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName) { if (!hasGnssPermissions(packageName) || mGnssStatusProvider == null) { return false; } - return mGnssStatusProvider.addListener(callback, new CallerIdentity(Binder.getCallingUid(), - Binder.getCallingPid(), packageName)); + CallerIdentity callerIdentity = new CallerIdentity(Binder.getCallingUid(), + Binder.getCallingPid(), packageName); + LinkedListener<IGnssStatusListener> linkedListener = new LinkedListener<>(listener, + "GnssStatusListener", callerIdentity, this::unregisterGnssStatusCallback); + IBinder binder = listener.asBinder(); + synchronized (mLock) { + if (!linkToListenerDeathNotificationLocked(binder, linkedListener)) { + return false; + } + + mGnssStatusListeners.put(binder, linkedListener); + long identity = Binder.clearCallingIdentity(); + try { + if (isThrottlingExemptLocked(callerIdentity) + || isImportanceForeground( + mActivityManager.getPackageImportance(packageName))) { + mGnssStatusProvider.addListener(listener, callerIdentity); + } + return true; + } finally { + Binder.restoreCallingIdentity(identity); + } + } } @Override - public void unregisterGnssStatusCallback(IGnssStatusListener callback) { - mGnssStatusProvider.removeListener(callback); + public void unregisterGnssStatusCallback(IGnssStatusListener listener) { + if (mGnssStatusProvider == null) { + return; + } + + IBinder binder = listener.asBinder(); + synchronized (mLock) { + LinkedListener<IGnssStatusListener> linkedListener = + mGnssStatusListeners.remove(binder); + if (linkedListener == null) { + return; + } + unlinkFromListenerDeathNotificationLocked(binder, linkedListener); + mGnssStatusProvider.removeListener(listener); + } } @Override @@ -2715,22 +2765,75 @@ public class LocationManagerService extends ILocationManager.Stub { return false; } + CallerIdentity callerIdentity = new CallerIdentity(Binder.getCallingUid(), + Binder.getCallingPid(), packageName); + LinkedListener<IGnssMeasurementsListener> linkedListener = new LinkedListener<>(listener, + "GnssMeasurementsListener", callerIdentity, this::removeGnssMeasurementsListener); + IBinder binder = listener.asBinder(); synchronized (mLock) { - CallerIdentity callerIdentity = new CallerIdentity(Binder.getCallingUid(), - Binder.getCallingPid(), packageName); - mGnssMeasurementsListeners.put(listener.asBinder(), callerIdentity); + if (!linkToListenerDeathNotificationLocked(binder, linkedListener)) { + return false; + } + + mGnssMeasurementsListeners.put(binder, linkedListener); long identity = Binder.clearCallingIdentity(); try { if (isThrottlingExemptLocked(callerIdentity) || isImportanceForeground( mActivityManager.getPackageImportance(packageName))) { - return mGnssMeasurementsProvider.addListener(listener, callerIdentity); + mGnssMeasurementsProvider.addListener(listener, callerIdentity); } + return true; } finally { Binder.restoreCallingIdentity(identity); } + } + } + + @Override + public void removeGnssMeasurementsListener(IGnssMeasurementsListener listener) { + if (mGnssMeasurementsProvider == null) { + return; + } + IBinder binder = listener.asBinder(); + synchronized (mLock) { + LinkedListener<IGnssMeasurementsListener> linkedListener = + mGnssMeasurementsListeners.remove(binder); + if (linkedListener == null) { + return; + } + unlinkFromListenerDeathNotificationLocked(binder, linkedListener); + mGnssMeasurementsProvider.removeListener(listener); + } + } + + + private boolean linkToListenerDeathNotificationLocked(IBinder binder, + LinkedListenerBase linkedListener) { + try { + binder.linkToDeath(linkedListener, 0 /* flags */); + return true; + } catch (RemoteException e) { + // if the remote process registering the listener is already dead, just swallow the + // exception and return + Log.w(TAG, "Could not link " + linkedListener.mListenerName + " death callback.", + e); + return false; + } + } + + private boolean unlinkFromListenerDeathNotificationLocked(IBinder binder, + LinkedListenerBase linkedListener) { + try { + binder.unlinkToDeath(linkedListener, 0 /* flags */); return true; + } catch (NoSuchElementException e) { + // if the death callback isn't connected (it should be...), log error, + // swallow the exception and return + Log.w(TAG, "Could not unlink " + linkedListener.mListenerName + " death callback.", + e); + return false; } } @@ -2759,52 +2862,53 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override - public void removeGnssMeasurementsListener(IGnssMeasurementsListener listener) { - if (mGnssMeasurementsProvider == null) { - return; - } - - synchronized (mLock) { - mGnssMeasurementsListeners.remove(listener.asBinder()); - mGnssMeasurementsProvider.removeListener(listener); - } - } - - @Override public boolean addGnssNavigationMessageListener( - IGnssNavigationMessageListener listener, - String packageName) { + IGnssNavigationMessageListener listener, String packageName) { if (!hasGnssPermissions(packageName) || mGnssNavigationMessageProvider == null) { return false; } + CallerIdentity callerIdentity = new CallerIdentity(Binder.getCallingUid(), + Binder.getCallingPid(), packageName); + LinkedListener<IGnssNavigationMessageListener> linkedListener = + new LinkedListener<>(listener, "GnssNavigationMessageListener", callerIdentity, + this::removeGnssNavigationMessageListener); + IBinder binder = listener.asBinder(); synchronized (mLock) { - CallerIdentity callerIdentity = new CallerIdentity(Binder.getCallingUid(), - Binder.getCallingPid(), packageName); + if (!linkToListenerDeathNotificationLocked(binder, linkedListener)) { + return false; + } - mGnssNavigationMessageListeners.put(listener.asBinder(), callerIdentity); + mGnssNavigationMessageListeners.put(binder, linkedListener); long identity = Binder.clearCallingIdentity(); try { if (isThrottlingExemptLocked(callerIdentity) || isImportanceForeground( mActivityManager.getPackageImportance(packageName))) { - return mGnssNavigationMessageProvider.addListener(listener, callerIdentity); + mGnssNavigationMessageProvider.addListener(listener, callerIdentity); } + return true; } finally { Binder.restoreCallingIdentity(identity); } - - return true; } } @Override public void removeGnssNavigationMessageListener(IGnssNavigationMessageListener listener) { - if (mGnssNavigationMessageProvider != null) { - synchronized (mLock) { - mGnssNavigationMessageListeners.remove(listener.asBinder()); - mGnssNavigationMessageProvider.removeListener(listener); + if (mGnssNavigationMessageProvider == null) { + return; + } + + IBinder binder = listener.asBinder(); + synchronized (mLock) { + LinkedListener<IGnssNavigationMessageListener> linkedListener = + mGnssNavigationMessageListeners.remove(binder); + if (linkedListener == null) { + return; } + unlinkFromListenerDeathNotificationLocked(binder, linkedListener); + mGnssNavigationMessageProvider.removeListener(listener); } } @@ -3368,18 +3472,14 @@ public class LocationManagerService extends ILocationManager.Stub { pw.println(" " + record); } } + pw.println(" Active GnssMeasurement Listeners:"); - for (CallerIdentity callerIdentity : mGnssMeasurementsListeners.values()) { - pw.println(" " + callerIdentity.mPid + " " + callerIdentity.mUid + " " - + callerIdentity.mPackageName + ": " - + isThrottlingExemptLocked(callerIdentity)); - } + dumpGnssDataListenersLocked(pw, mGnssMeasurementsListeners); pw.println(" Active GnssNavigationMessage Listeners:"); - for (CallerIdentity callerIdentity : mGnssNavigationMessageListeners.values()) { - pw.println(" " + callerIdentity.mPid + " " + callerIdentity.mUid + " " - + callerIdentity.mPackageName + ": " - + isThrottlingExemptLocked(callerIdentity)); - } + dumpGnssDataListenersLocked(pw, mGnssNavigationMessageListeners); + pw.println(" Active GnssStatus Listeners:"); + dumpGnssDataListenersLocked(pw, mGnssStatusListeners); + pw.println(" Historical Records by Provider:"); for (Map.Entry<PackageProviderKey, PackageStatistics> entry : mRequestStatistics.statistics.entrySet()) { @@ -3432,4 +3532,15 @@ public class LocationManagerService extends ILocationManager.Stub { } } } + + @GuardedBy("mLock") + private void dumpGnssDataListenersLocked(PrintWriter pw, + ArrayMap<IBinder, ? extends LinkedListenerBase> gnssDataListeners) { + for (LinkedListenerBase listener : gnssDataListeners.values()) { + CallerIdentity callerIdentity = listener.mCallerIdentity; + pw.println(" " + callerIdentity.mPid + " " + callerIdentity.mUid + " " + + callerIdentity.mPackageName + ": " + + isThrottlingExemptLocked(callerIdentity)); + } + } } diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index f505b76178a4..dc394d0ad482 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -20,18 +20,18 @@ import static android.Manifest.permission.CONNECTIVITY_INTERNAL; import static android.Manifest.permission.NETWORK_SETTINGS; import static android.Manifest.permission.NETWORK_STACK; import static android.Manifest.permission.SHUTDOWN; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE; +import static android.net.INetd.FIREWALL_BLACKLIST; +import static android.net.INetd.FIREWALL_CHAIN_DOZABLE; +import static android.net.INetd.FIREWALL_CHAIN_NONE; +import static android.net.INetd.FIREWALL_CHAIN_POWERSAVE; +import static android.net.INetd.FIREWALL_CHAIN_STANDBY; +import static android.net.INetd.FIREWALL_RULE_ALLOW; +import static android.net.INetd.FIREWALL_RULE_DENY; +import static android.net.INetd.FIREWALL_WHITELIST; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NONE; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_POWERSAVE; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY; -import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW; import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT; -import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY; -import static android.net.NetworkPolicyManager.FIREWALL_TYPE_BLACKLIST; -import static android.net.NetworkPolicyManager.FIREWALL_TYPE_WHITELIST; import static android.net.NetworkStats.SET_DEFAULT; import static android.net.NetworkStats.STATS_PER_UID; import static android.net.NetworkStats.TAG_ALL; @@ -1946,7 +1946,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub int numUids = 0; if (DBG) Slog.d(TAG, "Closing sockets after enabling chain " + chainName); - if (getFirewallType(chain) == FIREWALL_TYPE_WHITELIST) { + if (getFirewallType(chain) == FIREWALL_WHITELIST) { // Close all sockets on all non-system UIDs... ranges = new UidRange[] { // TODO: is there a better way of finding all existing users? If so, we could @@ -1958,7 +1958,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub final SparseIntArray rules = getUidFirewallRulesLR(chain); exemptUids = new int[rules.size()]; for (int i = 0; i < exemptUids.length; i++) { - if (rules.valueAt(i) == NetworkPolicyManager.FIREWALL_RULE_ALLOW) { + if (rules.valueAt(i) == FIREWALL_RULE_ALLOW) { exemptUids[numUids] = rules.keyAt(i); numUids++; } @@ -1980,7 +1980,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub final SparseIntArray rules = getUidFirewallRulesLR(chain); ranges = new UidRange[rules.size()]; for (int i = 0; i < ranges.length; i++) { - if (rules.valueAt(i) == NetworkPolicyManager.FIREWALL_RULE_DENY) { + if (rules.valueAt(i) == FIREWALL_RULE_DENY) { int uid = rules.keyAt(i); ranges[numUids] = new UidRange(uid, uid); numUids++; @@ -2052,13 +2052,13 @@ public class NetworkManagementService extends INetworkManagementService.Stub private int getFirewallType(int chain) { switch (chain) { case FIREWALL_CHAIN_STANDBY: - return FIREWALL_TYPE_BLACKLIST; + return FIREWALL_BLACKLIST; case FIREWALL_CHAIN_DOZABLE: - return FIREWALL_TYPE_WHITELIST; + return FIREWALL_WHITELIST; case FIREWALL_CHAIN_POWERSAVE: - return FIREWALL_TYPE_WHITELIST; + return FIREWALL_WHITELIST; default: - return isFirewallEnabled() ? FIREWALL_TYPE_WHITELIST : FIREWALL_TYPE_BLACKLIST; + return isFirewallEnabled() ? FIREWALL_WHITELIST : FIREWALL_BLACKLIST; } } @@ -2160,14 +2160,14 @@ public class NetworkManagementService extends INetworkManagementService.Stub private @NonNull String getFirewallRuleName(int chain, int rule) { String ruleName; - if (getFirewallType(chain) == FIREWALL_TYPE_WHITELIST) { - if (rule == NetworkPolicyManager.FIREWALL_RULE_ALLOW) { + if (getFirewallType(chain) == FIREWALL_WHITELIST) { + if (rule == FIREWALL_RULE_ALLOW) { ruleName = "allow"; } else { ruleName = "deny"; } } else { // Blacklist mode - if (rule == NetworkPolicyManager.FIREWALL_RULE_DENY) { + if (rule == FIREWALL_RULE_DENY) { ruleName = "deny"; } else { ruleName = "allow"; @@ -2194,7 +2194,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub private int getFirewallRuleType(int chain, int rule) { if (rule == NetworkPolicyManager.FIREWALL_RULE_DEFAULT) { - return getFirewallType(chain) == FIREWALL_TYPE_WHITELIST + return getFirewallType(chain) == FIREWALL_WHITELIST ? INetd.FIREWALL_RULE_DENY : INetd.FIREWALL_RULE_ALLOW; } return rule; diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index e7d7434b5dc8..5da281a5ebc3 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -860,7 +860,7 @@ class StorageManagerService extends IStorageManager.Stub } else if (remote == 1) { res = true; } else { - res = false; + res = true; } Slog.d(TAG, "Isolated storage local flag " + local + " and remote flag " @@ -1533,7 +1533,7 @@ class StorageManagerService extends IStorageManager.Stub // Snapshot feature flag used for this boot SystemProperties.set(StorageManager.PROP_ISOLATED_STORAGE_SNAPSHOT, Boolean.toString( - SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false))); + SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, true))); mContext = context; mResolver = mContext.getContentResolver(); diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 2f1510e32311..81209763d3a8 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -26,6 +26,7 @@ import android.content.pm.PackageManager; import android.net.LinkProperties; import android.net.NetworkCapabilities; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -246,7 +247,10 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private PreciseDataConnectionState mPreciseDataConnectionState = new PreciseDataConnectionState(); - static final int ENFORCE_COARSE_LOCATION_PERMISSION_MASK = + // Nothing here yet, but putting it here in case we want to add more in the future. + static final int ENFORCE_COARSE_LOCATION_PERMISSION_MASK = 0; + + static final int ENFORCE_FINE_LOCATION_PERMISSION_MASK = PhoneStateListener.LISTEN_CELL_LOCATION | PhoneStateListener.LISTEN_CELL_INFO; @@ -637,8 +641,14 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { if ((events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) { try { if (VDBG) log("listen: call onSSC state=" + mServiceState[phoneId]); - r.callback.onServiceStateChanged( - new ServiceState(mServiceState[phoneId])); + ServiceState rawSs = new ServiceState(mServiceState[phoneId]); + if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) { + r.callback.onServiceStateChanged(rawSs); + } else if (checkCoarseLocationAccess(r, Build.VERSION_CODES.Q)) { + r.callback.onServiceStateChanged(rawSs.sanitizeLocationInfo(false)); + } else { + r.callback.onServiceStateChanged(rawSs.sanitizeLocationInfo(true)); + } } catch (RemoteException ex) { remove(r.binder); } @@ -673,7 +683,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { try { if (DBG_LOC) log("listen: mCellLocation = " + mCellLocation[phoneId]); - if (checkLocationAccess(r)) { + if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) { r.callback.onCellLocationChanged( new Bundle(mCellLocation[phoneId])); } @@ -722,7 +732,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { try { if (DBG_LOC) log("listen: mCellInfo[" + phoneId + "] = " + mCellInfo.get(phoneId)); - if (checkLocationAccess(r)) { + if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) { r.callback.onCellInfoChanged(mCellInfo.get(phoneId)); } } catch (RemoteException ex) { @@ -1009,13 +1019,22 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_SERVICE_STATE) && idMatch(r.subId, subId, phoneId)) { + try { + ServiceState stateToSend; + if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) { + stateToSend = new ServiceState(state); + } else if (checkCoarseLocationAccess(r, Build.VERSION_CODES.Q)) { + stateToSend = state.sanitizeLocationInfo(false); + } else { + stateToSend = state.sanitizeLocationInfo(true); + } if (DBG) { log("notifyServiceStateForSubscriber: callback.onSSC r=" + r + " subId=" + subId + " phoneId=" + phoneId + " state=" + state); } - r.callback.onServiceStateChanged(new ServiceState(state)); + r.callback.onServiceStateChanged(stateToSend); } catch (RemoteException ex) { mRemoveList.add(r.binder); } @@ -1198,7 +1217,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO) && idMatch(r.subId, subId, phoneId) && - checkLocationAccess(r)) { + checkFineLocationAccess(r, Build.VERSION_CODES.Q)) { try { if (DBG_LOC) { log("notifyCellInfo: mCellInfo=" + cellInfo + " r=" + r); @@ -1500,7 +1519,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION) && idMatch(r.subId, subId, phoneId) && - checkLocationAccess(r)) { + checkFineLocationAccess(r, Build.VERSION_CODES.Q)) { try { if (DBG_LOC) { log("notifyCellLocation: cellLocation=" + cellLocation @@ -2108,12 +2127,35 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private boolean checkListenerPermission( int events, int subId, String callingPackage, String message) { + LocationAccessPolicy.LocationPermissionQuery.Builder locationQueryBuilder = + new LocationAccessPolicy.LocationPermissionQuery.Builder() + .setCallingPackage(callingPackage) + .setMethod(message + " events: " + events) + .setCallingPid(Binder.getCallingPid()) + .setCallingUid(Binder.getCallingUid()); + + boolean shouldCheckLocationPermissions = false; if ((events & ENFORCE_COARSE_LOCATION_PERMISSION_MASK) != 0) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_COARSE_LOCATION, null); - if (mAppOps.noteOp(AppOpsManager.OP_COARSE_LOCATION, Binder.getCallingUid(), - callingPackage) != AppOpsManager.MODE_ALLOWED) { - return false; + locationQueryBuilder.setMinSdkVersionForCoarse(0); + shouldCheckLocationPermissions = true; + } + + if ((events & ENFORCE_FINE_LOCATION_PERMISSION_MASK) != 0) { + // Everything that requires fine location started in Q. So far... + locationQueryBuilder.setMinSdkVersionForFine(Build.VERSION_CODES.Q); + shouldCheckLocationPermissions = true; + } + + if (shouldCheckLocationPermissions) { + LocationAccessPolicy.LocationPermissionResult result = + LocationAccessPolicy.checkLocationPermission( + mContext, locationQueryBuilder.build()); + switch (result) { + case DENIED_HARD: + throw new SecurityException("Unable to listen for events " + events + " due to " + + "insufficient location permissions."); + case DENIED_SOFT: + return false; } } @@ -2228,15 +2270,38 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } - private boolean checkLocationAccess(Record r) { - long token = Binder.clearCallingIdentity(); - try { - return LocationAccessPolicy.canAccessCellLocation(mContext, - r.callingPackage, r.callerUid, r.callerPid, - /*throwOnDeniedPermission*/ false); - } finally { - Binder.restoreCallingIdentity(token); - } + private boolean checkFineLocationAccess(Record r, int minSdk) { + LocationAccessPolicy.LocationPermissionQuery query = + new LocationAccessPolicy.LocationPermissionQuery.Builder() + .setCallingPackage(r.callingPackage) + .setCallingPid(r.callerPid) + .setCallingUid(r.callerUid) + .setMethod("TelephonyRegistry push") + .setMinSdkVersionForFine(minSdk) + .build(); + + return Binder.withCleanCallingIdentity(() -> { + LocationAccessPolicy.LocationPermissionResult locationResult = + LocationAccessPolicy.checkLocationPermission(mContext, query); + return locationResult == LocationAccessPolicy.LocationPermissionResult.ALLOWED; + }); + } + + private boolean checkCoarseLocationAccess(Record r, int minSdk) { + LocationAccessPolicy.LocationPermissionQuery query = + new LocationAccessPolicy.LocationPermissionQuery.Builder() + .setCallingPackage(r.callingPackage) + .setCallingPid(r.callerPid) + .setCallingUid(r.callerUid) + .setMethod("TelephonyRegistry push") + .setMinSdkVersionForCoarse(minSdk) + .build(); + + return Binder.withCleanCallingIdentity(() -> { + LocationAccessPolicy.LocationPermissionResult locationResult = + LocationAccessPolicy.checkLocationPermission(mContext, query); + return locationResult == LocationAccessPolicy.LocationPermissionResult.ALLOWED; + }); } private void checkPossibleMissNotify(Record r, int phoneId) { @@ -2286,7 +2351,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { log("checkPossibleMissNotify: onCellInfoChanged[" + phoneId + "] = " + mCellInfo.get(phoneId)); } - if (checkLocationAccess(r)) { + if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) { r.callback.onCellInfoChanged(mCellInfo.get(phoneId)); } } catch (RemoteException ex) { @@ -2336,7 +2401,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { try { if (DBG_LOC) log("checkPossibleMissNotify: onCellLocationChanged mCellLocation = " + mCellLocation[phoneId]); - if (checkLocationAccess(r)) { + if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) { r.callback.onCellLocationChanged(new Bundle(mCellLocation[phoneId])); } } catch (RemoteException ex) { diff --git a/services/core/java/com/android/server/WiredAccessoryManager.java b/services/core/java/com/android/server/WiredAccessoryManager.java index 3939bee52aa2..9bbc3158757c 100644 --- a/services/core/java/com/android/server/WiredAccessoryManager.java +++ b/services/core/java/com/android/server/WiredAccessoryManager.java @@ -23,6 +23,7 @@ import android.os.Message; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.UEventObserver; +import android.util.Pair; import android.util.Slog; import android.media.AudioManager; import android.util.Log; @@ -31,6 +32,7 @@ import android.view.InputDevice; import com.android.internal.R; import com.android.server.input.InputManagerService; import com.android.server.input.InputManagerService.WiredAccessoryCallbacks; + import static com.android.server.input.InputManagerService.SW_HEADPHONE_INSERT; import static com.android.server.input.InputManagerService.SW_MICROPHONE_INSERT; import static com.android.server.input.InputManagerService.SW_LINEOUT_INSERT; @@ -41,6 +43,7 @@ import static com.android.server.input.InputManagerService.SW_LINEOUT_INSERT_BIT import java.io.File; import java.io.FileReader; import java.io.FileNotFoundException; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -52,7 +55,7 @@ import java.util.Locale; */ final class WiredAccessoryManager implements WiredAccessoryCallbacks { private static final String TAG = WiredAccessoryManager.class.getSimpleName(); - private static final boolean LOG = true; + private static final boolean LOG = false; private static final int BIT_HEADSET = (1 << 0); private static final int BIT_HEADSET_NO_MIC = (1 << 1); @@ -60,9 +63,9 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { private static final int BIT_USB_HEADSET_DGTL = (1 << 3); private static final int BIT_HDMI_AUDIO = (1 << 4); private static final int BIT_LINEOUT = (1 << 5); - private static final int SUPPORTED_HEADSETS = (BIT_HEADSET|BIT_HEADSET_NO_MIC| - BIT_USB_HEADSET_ANLG|BIT_USB_HEADSET_DGTL| - BIT_HDMI_AUDIO|BIT_LINEOUT); + private static final int SUPPORTED_HEADSETS = (BIT_HEADSET | BIT_HEADSET_NO_MIC | + BIT_USB_HEADSET_ANLG | BIT_USB_HEADSET_DGTL | + BIT_HDMI_AUDIO | BIT_LINEOUT); private static final String NAME_H2W = "h2w"; private static final String NAME_USB_AUDIO = "usb_audio"; @@ -82,30 +85,34 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { private int mSwitchValues; private final WiredAccessoryObserver mObserver; + private final WiredAccessoryExtconObserver mExtconObserver; private final InputManagerService mInputManager; private final boolean mUseDevInputEventForAudioJack; public WiredAccessoryManager(Context context, InputManagerService inputManager) { - PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); + PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WiredAccessoryManager"); mWakeLock.setReferenceCounted(false); - mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); + mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); mInputManager = inputManager; mUseDevInputEventForAudioJack = context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack); + mExtconObserver = new WiredAccessoryExtconObserver(); mObserver = new WiredAccessoryObserver(); } private void onSystemReady() { if (mUseDevInputEventForAudioJack) { int switchValues = 0; - if (mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, SW_HEADPHONE_INSERT) == 1) { + if (mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, SW_HEADPHONE_INSERT) + == 1) { switchValues |= SW_HEADPHONE_INSERT_BIT; } - if (mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, SW_MICROPHONE_INSERT) == 1) { + if (mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, SW_MICROPHONE_INSERT) + == 1) { switchValues |= SW_MICROPHONE_INSERT_BIT; } if (mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, SW_LINEOUT_INSERT) == 1) { @@ -115,20 +122,31 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT | SW_LINEOUT_INSERT_BIT); } - mObserver.init(); + + if (ExtconUEventObserver.extconExists()) { + if (mUseDevInputEventForAudioJack) { + Log.w(TAG, "Both input event and extcon are used for audio jack," + + " please just choose one."); + } + mExtconObserver.init(); + } else { + mObserver.init(); + } } @Override public void notifyWiredAccessoryChanged(long whenNanos, int switchValues, int switchMask) { - if (LOG) Slog.v(TAG, "notifyWiredAccessoryChanged: when=" + whenNanos - + " bits=" + switchCodeToString(switchValues, switchMask) - + " mask=" + Integer.toHexString(switchMask)); + if (LOG) { + Slog.v(TAG, "notifyWiredAccessoryChanged: when=" + whenNanos + + " bits=" + switchCodeToString(switchValues, switchMask) + + " mask=" + Integer.toHexString(switchMask)); + } synchronized (mLock) { int headset; mSwitchValues = (mSwitchValues & ~switchMask) | switchValues; switch (mSwitchValues & - (SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT | SW_LINEOUT_INSERT_BIT)) { + (SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT | SW_LINEOUT_INSERT_BIT)) { case 0: headset = 0; break; @@ -155,7 +173,7 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { } updateLocked(NAME_H2W, - (mHeadsetState & ~(BIT_HEADSET | BIT_HEADSET_NO_MIC | BIT_LINEOUT)) | headset); + (mHeadsetState & ~(BIT_HEADSET | BIT_HEADSET_NO_MIC | BIT_LINEOUT)) | headset); } } @@ -175,7 +193,7 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { * results in support for the last one plugged in. Similarly, unplugging either is seen as * unplugging all. * - * @param newName One of the NAME_xxx variables defined above. + * @param newName One of the NAME_xxx variables defined above. * @param newState 0 or one of the BIT_xxx variables defined above. */ private void updateLocked(String newName, int newState) { @@ -186,10 +204,12 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { int h2w_headset = headsetState & (BIT_HEADSET | BIT_HEADSET_NO_MIC | BIT_LINEOUT); boolean h2wStateChange = true; boolean usbStateChange = true; - if (LOG) Slog.v(TAG, "newName=" + newName - + " newState=" + newState - + " headsetState=" + headsetState - + " prev headsetState=" + mHeadsetState); + if (LOG) { + Slog.v(TAG, "newName=" + newName + + " newState=" + newState + + " headsetState=" + headsetState + + " prev headsetState=" + mHeadsetState); + } if (mHeadsetState == headsetState) { Log.e(TAG, "No state change."); @@ -229,7 +249,7 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { public void handleMessage(Message msg) { switch (msg.what) { case MSG_NEW_DEVICE_STATE: - setDevicesState(msg.arg1, msg.arg2, (String)msg.obj); + setDevicesState(msg.arg1, msg.arg2, (String) msg.obj); mWakeLock.release(); break; case MSG_SYSTEM_READY: @@ -269,9 +289,9 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { if (headset == BIT_HEADSET) { outDevice = AudioManager.DEVICE_OUT_WIRED_HEADSET; inDevice = AudioManager.DEVICE_IN_WIRED_HEADSET; - } else if (headset == BIT_HEADSET_NO_MIC){ + } else if (headset == BIT_HEADSET_NO_MIC) { outDevice = AudioManager.DEVICE_OUT_WIRED_HEADPHONE; - } else if (headset == BIT_LINEOUT){ + } else if (headset == BIT_LINEOUT) { outDevice = AudioManager.DEVICE_OUT_LINE; } else if (headset == BIT_USB_HEADSET_ANLG) { outDevice = AudioManager.DEVICE_OUT_ANLG_DOCK_HEADSET; @@ -280,7 +300,7 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { } else if (headset == BIT_HDMI_AUDIO) { outDevice = AudioManager.DEVICE_OUT_HDMI; } else { - Slog.e(TAG, "setDeviceState() invalid headset type: "+headset); + Slog.e(TAG, "setDeviceState() invalid headset type: " + headset); return; } @@ -290,10 +310,10 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { } if (outDevice != 0) { - mAudioManager.setWiredDeviceConnectionState(outDevice, state, "", headsetName); + mAudioManager.setWiredDeviceConnectionState(outDevice, state, "", headsetName); } if (inDevice != 0) { - mAudioManager.setWiredDeviceConnectionState(inDevice, state, "", headsetName); + mAudioManager.setWiredDeviceConnectionState(inDevice, state, "", headsetName); } } } @@ -340,7 +360,7 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { " not found while attempting to determine initial switch state"); } catch (Exception e) { Slog.e(TAG, "Error while attempting to determine initial switch state for " - + uei.getDevName() , e); + + uei.getDevName(), e); } } } @@ -350,7 +370,7 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { // observe three UEVENTs for (int i = 0; i < mUEventInfo.size(); ++i) { UEventInfo uei = mUEventInfo.get(i); - startObserving("DEVPATH="+uei.getDevPath()); + startObserving("DEVPATH=" + uei.getDevPath()); } } @@ -438,7 +458,9 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { mStateNbits = stateNbits; } - public String getDevName() { return mDevName; } + public String getDevName() { + return mDevName; + } public String getDevPath() { return String.format(Locale.US, "/devices/virtual/switch/%s", mDevName); @@ -456,11 +478,84 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { public int computeNewHeadsetState(int headsetState, int switchState) { int preserveMask = ~(mState1Bits | mState2Bits | mStateNbits); int setBits = ((switchState == 1) ? mState1Bits : - ((switchState == 2) ? mState2Bits : - ((switchState == mStateNbits) ? mStateNbits : 0))); + ((switchState == 2) ? mState2Bits : + ((switchState == mStateNbits) ? mStateNbits : 0))); return ((headsetState & preserveMask) | setBits); } } } + + private class WiredAccessoryExtconObserver extends ExtconStateObserver<Pair<Integer, Integer>> { + private final List<ExtconInfo> mExtconInfos; + + WiredAccessoryExtconObserver() { + mExtconInfos = ExtconInfo.getExtconInfos(".*audio.*"); + + } + + private void init() { + for (ExtconInfo extconInfo : mExtconInfos) { + Pair<Integer, Integer> state = null; + try { + state = parseStateFromFile(extconInfo); + } catch (FileNotFoundException e) { + Slog.w(TAG, extconInfo.getStatePath() + + " not found while attempting to determine initial state", e); + } catch (IOException e) { + Slog.e( + TAG, + "Error reading " + extconInfo.getStatePath() + + " while attempting to determine initial state", + e); + } + if (state != null) { + updateState(extconInfo, extconInfo.getName(), state); + } + if (LOG) Slog.d(TAG, "observing " + extconInfo.getName()); + startObserving(extconInfo); + } + + } + + @Override + public Pair<Integer, Integer> parseState(ExtconInfo extconInfo, String status) { + if (LOG) Slog.v(TAG, "status " + status); + int []maskAndState = {0,0}; + // extcon event state changes from kernel4.9 + // new state will be like STATE=MICROPHONE=1\nHEADPHONE=0 + updateBit(maskAndState, BIT_HEADSET_NO_MIC, status, "HEADPHONE") ; + updateBit(maskAndState, BIT_HEADSET, status,"MICROPHONE") ; + updateBit(maskAndState, BIT_HDMI_AUDIO, status,"HDMI") ; + updateBit(maskAndState, BIT_LINEOUT, status,"LINE-OUT") ; + if (LOG) Slog.v(TAG, "mask " + maskAndState[0] + " state " + maskAndState[1]); + return Pair.create(maskAndState[0],maskAndState[1]); + } + + @Override + public void updateState(ExtconInfo extconInfo, String name, + Pair<Integer, Integer> maskAndState) { + synchronized (mLock) { + int mask = maskAndState.first; + int state = maskAndState.second; + updateLocked(name, mHeadsetState | (mask & state) & ~(mask & ~state)); + return; + } + } + } + + /** + * Updates the mask bit at {@code position} to 1 and the state bit at {@code position} to true + * if {@code name=1} or false if {}@code name=0} is contained in {@code state}. + */ + private static void updateBit(int[] maskAndState, int position, String state, String name) { + maskAndState[0] |= position; + if (state.contains(name + "=1")) { + maskAndState[0] |= position; + maskAndState[1] |= position; + } else if (state.contains(name + "=0")) { + maskAndState[0] |= position; + maskAndState[1] &= ~position; + } + } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 60a45bfe04bb..2f20572caa99 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -6630,8 +6630,7 @@ public class ActivityManagerService extends IActivityManager.Stub String msg; if ((msg = checkContentProviderAssociation(r, callingUid, cpi)) != null) { - throw new SecurityException("Content provider lookup " - + cpr.name.flattenToShortString() + throw new SecurityException("Content provider lookup " + name + " failed: association not allowed with package " + msg); } checkTime(startTime, "getContentProviderImpl: before checkContentProviderPermission"); @@ -18101,8 +18100,10 @@ public class ActivityManagerService extends IActivityManager.Stub if (!queue.isIdle()) { final String msg = "Waiting for queue " + queue + " to become idle..."; pw.println(msg); + pw.println(queue.describeState()); pw.flush(); Slog.v(TAG, msg); + queue.cancelDeferrals(); idle = false; } } diff --git a/services/core/java/com/android/server/am/BroadcastDispatcher.java b/services/core/java/com/android/server/am/BroadcastDispatcher.java index 6371cd376d19..0b38ef94de1e 100644 --- a/services/core/java/com/android/server/am/BroadcastDispatcher.java +++ b/services/core/java/com/android/server/am/BroadcastDispatcher.java @@ -65,6 +65,14 @@ public class BroadcastDispatcher { broadcasts.add(br); } + int size() { + return broadcasts.size(); + } + + boolean isEmpty() { + return broadcasts.isEmpty(); + } + void writeToProto(ProtoOutputStream proto, long fieldId) { for (BroadcastRecord br : broadcasts) { br.writeToProto(proto, fieldId); @@ -252,22 +260,48 @@ public class BroadcastDispatcher { synchronized (mLock) { return mCurrentBroadcast == null && mOrderedBroadcasts.isEmpty() - && mDeferredBroadcasts.isEmpty() - && mAlarmBroadcasts.isEmpty(); + && isDeferralsListEmpty(mDeferredBroadcasts) + && isDeferralsListEmpty(mAlarmBroadcasts); + } + } + + private static int pendingInDeferralsList(ArrayList<Deferrals> list) { + int pending = 0; + final int numEntries = list.size(); + for (int i = 0; i < numEntries; i++) { + pending += list.get(i).size(); } + return pending; + } + + private static boolean isDeferralsListEmpty(ArrayList<Deferrals> list) { + return pendingInDeferralsList(list) == 0; } /** - * Not quite the traditional size() measurement; includes any in-process but - * not yet retired active outbound broadcast. + * Strictly for logging, describe the currently pending contents in a human- + * readable way */ - public int totalUndelivered() { - synchronized (mLock) { - return mAlarmBroadcasts.size() - + mDeferredBroadcasts.size() - + mOrderedBroadcasts.size() - + (mCurrentBroadcast == null ? 0 : 1); - } + public String describeStateLocked() { + final StringBuilder sb = new StringBuilder(128); + if (mCurrentBroadcast != null) { + sb.append("1 in flight, "); + } + sb.append(mOrderedBroadcasts.size()); + sb.append(" ordered"); + int n = pendingInDeferralsList(mAlarmBroadcasts); + if (n > 0) { + sb.append(", "); + sb.append(n); + sb.append(" deferrals in alarm recipients"); + } + n = pendingInDeferralsList(mDeferredBroadcasts); + if (n > 0) { + sb.append(", "); + sb.append(n); + sb.append(" deferred"); + } + return sb.toString(); } // ---------------------------------- @@ -579,6 +613,26 @@ public class BroadcastDispatcher { } } + /** + * Cancel all current deferrals; that is, make all currently-deferred broadcasts + * immediately deliverable. Used by the wait-for-broadcast-idle mechanism. + */ + public void cancelDeferrals() { + synchronized (mLock) { + zeroDeferralTimes(mAlarmBroadcasts); + zeroDeferralTimes(mDeferredBroadcasts); + } + } + + private static void zeroDeferralTimes(ArrayList<Deferrals> list) { + final int num = list.size(); + for (int i = 0; i < num; i++) { + Deferrals d = list.get(i); + // Safe to do this in-place because it won't break ordering + d.deferUntil = d.deferredBy = 0; + } + } + // ---------------------------------- /** diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index f0b137a6ccb1..d9ea1da79f56 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -898,6 +898,11 @@ public final class BroadcastQueue { for (int i = perms.length-1; i >= 0; i--) { try { PermissionInfo pi = pm.getPermissionInfo(perms[i], "android", 0); + if (pi == null) { + // a required permission that no package has actually + // defined cannot be signature-required. + return false; + } if ((pi.protectionLevel & (PermissionInfo.PROTECTION_MASK_BASE | PermissionInfo.PROTECTION_FLAG_PRIVILEGED)) != PermissionInfo.PROTECTION_SIGNATURE) { @@ -923,8 +928,8 @@ public final class BroadcastQueue { if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "processNextBroadcast [" + mQueueName + "]: " - + mParallelBroadcasts.size() + " parallel broadcasts, " - + mDispatcher.totalUndelivered() + " ordered broadcasts"); + + mParallelBroadcasts.size() + " parallel broadcasts; " + + mDispatcher.describeStateLocked()); mService.updateCpuStats(); @@ -1822,11 +1827,24 @@ public final class BroadcastQueue { record.intent == null ? "" : record.intent.getAction()); } - final boolean isIdle() { + boolean isIdle() { return mParallelBroadcasts.isEmpty() && mDispatcher.isEmpty() && (mPendingBroadcast == null); } + // Used by wait-for-broadcast-idle : fast-forward all current deferrals to + // be immediately deliverable. + void cancelDeferrals() { + mDispatcher.cancelDeferrals(); + } + + String describeState() { + synchronized (mService) { + return mParallelBroadcasts.size() + " parallel; " + + mDispatcher.describeStateLocked(); + } + } + void writeToProto(ProtoOutputStream proto, long fieldId) { long token = proto.start(fieldId); proto.write(BroadcastQueueProto.QUEUE_NAME, mQueueName); diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java index 01946247bd12..8ffb67a1405c 100644 --- a/services/core/java/com/android/server/am/CoreSettingsObserver.java +++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java @@ -65,6 +65,8 @@ final class CoreSettingsObserver extends ContentObserver { Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES, String.class); sGlobalSettingToTypeMap.put( Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST, String.class); + sGlobalSettingToTypeMap.put( + Settings.Global.GLOBAL_SETTINGS_SHOW_ANGLE_IN_USE_DIALOG_BOX, String.class); sGlobalSettingToTypeMap.put(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, int.class); sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_APP, String.class); sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_LAYERS, String.class); @@ -76,6 +78,7 @@ final class CoreSettingsObserver extends ContentObserver { sGlobalSettingToTypeMap.put(Settings.Global.GAME_DRIVER_OPT_OUT_APPS, String.class); sGlobalSettingToTypeMap.put(Settings.Global.GAME_DRIVER_BLACKLIST, String.class); sGlobalSettingToTypeMap.put(Settings.Global.GAME_DRIVER_WHITELIST, String.class); + sGlobalSettingToTypeMap.put(Settings.Global.GAME_DRIVER_BLACKLISTS, String.class); // add other global settings here... } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index f01305eab761..0d49e4cdc9d0 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -1681,7 +1681,8 @@ public final class ProcessList { public void killAppZygoteIfNeededLocked(AppZygote appZygote) { final ApplicationInfo appInfo = appZygote.getAppInfo(); ArrayList<ProcessRecord> zygoteProcesses = mAppZygoteProcesses.get(appZygote); - if (zygoteProcesses.size() == 0) { // Only remove if no longer in use now + if (zygoteProcesses != null && zygoteProcesses.size() == 0) { + // Only remove if no longer in use now mAppZygotes.remove(appInfo.processName, appInfo.uid); mAppZygoteProcesses.remove(appZygote); mAppIsolatedUidRangeAllocator.freeUidRangeLocked(appInfo); @@ -1703,6 +1704,7 @@ public final class ProcessList { ArrayList<ProcessRecord> zygoteProcesses = mAppZygoteProcesses.get(appZygote); zygoteProcesses.remove(app); if (zygoteProcesses.size() == 0) { + mService.mHandler.removeMessages(KILL_APP_ZYGOTE_MSG); Message msg = mService.mHandler.obtainMessage(KILL_APP_ZYGOTE_MSG); msg.obj = appZygote; mService.mHandler.sendMessageDelayed(msg, KILL_APP_ZYGOTE_DELAY_MS); diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java index 8995068ef504..eab38209c194 100644 --- a/services/core/java/com/android/server/biometrics/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/face/FaceService.java @@ -588,6 +588,11 @@ public class FaceService extends BiometricServiceBase { throws RemoteException { // TODO } + + @Override + public void onLockoutChanged(long duration) { + + } }; /** diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java index 6ee5665b9e42..e9ae516cc8d0 100644 --- a/services/core/java/com/android/server/display/DisplayDevice.java +++ b/services/core/java/com/android/server/display/DisplayDevice.java @@ -19,6 +19,7 @@ package com.android.server.display; import android.graphics.Rect; import android.hardware.display.DisplayViewport; import android.os.IBinder; +import android.view.DisplayAddress; import android.view.Surface; import android.view.SurfaceControl; @@ -225,8 +226,12 @@ abstract class DisplayDevice { viewport.deviceHeight = isRotated ? info.width : info.height; viewport.uniqueId = info.uniqueId; - // TODO(b/112898898) Use an actual port here. - viewport.physicalPort = null; + + if (info.address instanceof DisplayAddress.Physical) { + viewport.physicalPort = ((DisplayAddress.Physical) info.address).getPort(); + } else { + viewport.physicalPort = null; + } } /** diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index ab64f61a3b22..729ea1772066 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -19,6 +19,7 @@ package com.android.server.display; import android.hardware.display.DisplayViewport; import android.util.DisplayMetrics; import android.view.Display; +import android.view.DisplayAddress; import android.view.DisplayCutout; import android.view.Surface; @@ -274,7 +275,7 @@ final class DisplayDeviceInfo { * Display address, or null if none. * Interpretation varies by display type. */ - public String address; + public DisplayAddress address; /** * Display state. diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 80ea1dae3115..b99ba7ed0bf0 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1336,7 +1336,7 @@ public final class DisplayManagerService extends SystemService { && !TextUtils.isEmpty(info.uniqueId)) { viewportType = VIEWPORT_VIRTUAL; } else { - Slog.wtf(TAG, "Unable to populate viewport for display device: " + info); + Slog.i(TAG, "Display " + info + " does not support input device matching."); return; } diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 28f21f633ac4..489194726c5a 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -31,6 +31,7 @@ import android.util.LongSparseArray; import android.util.Slog; import android.util.SparseArray; import android.view.Display; +import android.view.DisplayAddress; import android.view.DisplayCutout; import android.view.DisplayEventReceiver; import android.view.Surface; @@ -382,6 +383,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { mInfo.presentationDeadlineNanos = phys.presentationDeadlineNanos; mInfo.state = mState; mInfo.uniqueId = getUniqueId(); + mInfo.address = DisplayAddress.fromPhysicalDisplayId(mPhysicalDisplayId); // Assume that all built-in displays that have secure output (eg. HDCP) also // support compositing from gralloc protected buffers. diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java index e8d6ad455fbf..9e4c1cb57dca 100644 --- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java +++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java @@ -16,9 +16,6 @@ package com.android.server.display; -import com.android.internal.util.DumpUtils; -import com.android.internal.util.IndentingPrintWriter; - import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -35,9 +32,13 @@ import android.os.Message; import android.os.UserHandle; import android.util.Slog; import android.view.Display; +import android.view.DisplayAddress; import android.view.Surface; import android.view.SurfaceControl; +import com.android.internal.util.DumpUtils; +import com.android.internal.util.IndentingPrintWriter; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; @@ -581,7 +582,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { private final int mHeight; private final float mRefreshRate; private final int mFlags; - private final String mAddress; + private final DisplayAddress mAddress; private final Display.Mode mMode; private Surface mSurface; @@ -596,7 +597,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { mHeight = height; mRefreshRate = refreshRate; mFlags = flags; - mAddress = address; + mAddress = DisplayAddress.fromMacAddress(address); mSurface = surface; mMode = createMode(width, height, refreshRate); } diff --git a/services/core/java/com/android/server/gpu/GpuService.java b/services/core/java/com/android/server/gpu/GpuService.java index a68ceed750ad..6899c3ffcbb1 100644 --- a/services/core/java/com/android/server/gpu/GpuService.java +++ b/services/core/java/com/android/server/gpu/GpuService.java @@ -22,24 +22,33 @@ import static android.content.Intent.ACTION_PACKAGE_REMOVED; import android.annotation.NonNull; import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.database.ContentObserver; +import android.gamedriver.GameDriverProto.Blacklist; +import android.gamedriver.GameDriverProto.Blacklists; import android.net.Uri; import android.os.Build; +import android.os.Handler; import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; +import android.util.Base64; import android.util.Slog; +import com.android.framework.protobuf.InvalidProtocolBufferException; +import com.android.internal.annotations.GuardedBy; import com.android.server.SystemService; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; +import java.util.List; /** * Service to manage GPU related features. @@ -52,17 +61,25 @@ public class GpuService extends SystemService { public static final boolean DEBUG = false; private static final String PROPERTY_GFX_DRIVER = "ro.gfx.driver.0"; - private static final String WHITELIST_FILENAME = "whitelist.txt"; + private static final String GAME_DRIVER_WHITELIST_FILENAME = "whitelist.txt"; + private static final int BASE64_FLAGS = Base64.NO_PADDING | Base64.NO_WRAP; private final Context mContext; private final String mDriverPackageName; private final PackageManager mPackageManager; + private final Object mLock = new Object(); + private ContentResolver mContentResolver; + private long mGameDriverVersionCode; + private SettingsObserver mSettingsObserver; + @GuardedBy("mLock") + private Blacklists mBlacklists; public GpuService(Context context) { super(context); mContext = context; mDriverPackageName = SystemProperties.get(PROPERTY_GFX_DRIVER); + mGameDriverVersionCode = -1; mPackageManager = context.getPackageManager(); if (mDriverPackageName != null && !mDriverPackageName.isEmpty()) { final IntentFilter packageFilter = new IntentFilter(); @@ -82,10 +99,37 @@ public class GpuService extends SystemService { @Override public void onBootPhase(int phase) { if (phase == PHASE_BOOT_COMPLETED) { + mContentResolver = mContext.getContentResolver(); + mSettingsObserver = new SettingsObserver(); if (mDriverPackageName == null || mDriverPackageName.isEmpty()) { return; } fetchGameDriverPackageProperties(); + processBlacklists(); + setBlacklist(); + } + } + + private final class SettingsObserver extends ContentObserver { + private final Uri mGameDriverBlackUri = + Settings.Global.getUriFor(Settings.Global.GAME_DRIVER_BLACKLISTS); + + SettingsObserver() { + super(new Handler()); + mContentResolver.registerContentObserver(mGameDriverBlackUri, false, this, + UserHandle.USER_ALL); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + if (uri == null) { + return; + } + + if (mGameDriverBlackUri.equals(uri)) { + processBlacklists(); + setBlacklist(); + } } } @@ -109,6 +153,7 @@ public class GpuService extends SystemService { case ACTION_PACKAGE_CHANGED: case ACTION_PACKAGE_REMOVED: fetchGameDriverPackageProperties(); + setBlacklist(); break; default: // do nothing @@ -138,16 +183,22 @@ public class GpuService extends SystemService { return; } + // Reset the whitelist. + Settings.Global.putString(mContentResolver, + Settings.Global.GAME_DRIVER_WHITELIST, ""); + mGameDriverVersionCode = driverInfo.longVersionCode; + try { final Context driverContext = mContext.createPackageContext(mDriverPackageName, Context.CONTEXT_RESTRICTED); final BufferedReader reader = new BufferedReader( - new InputStreamReader(driverContext.getAssets().open(WHITELIST_FILENAME))); + new InputStreamReader(driverContext.getAssets() + .open(GAME_DRIVER_WHITELIST_FILENAME))); final ArrayList<String> whitelistedPackageNames = new ArrayList<>(); for (String packageName; (packageName = reader.readLine()) != null; ) { whitelistedPackageNames.add(packageName); } - Settings.Global.putString(mContext.getContentResolver(), + Settings.Global.putString(mContentResolver, Settings.Global.GAME_DRIVER_WHITELIST, String.join(",", whitelistedPackageNames)); } catch (PackageManager.NameNotFoundException e) { @@ -160,4 +211,48 @@ public class GpuService extends SystemService { } } } + + private void processBlacklists() { + // TODO(b/121350991) Switch to DeviceConfig with property listener. + String base64String = + Settings.Global.getString(mContentResolver, Settings.Global.GAME_DRIVER_BLACKLISTS); + if (base64String == null || base64String.isEmpty()) { + return; + } + + synchronized (mLock) { + // Reset all blacklists + mBlacklists = null; + try { + mBlacklists = Blacklists.parseFrom(Base64.decode(base64String, BASE64_FLAGS)); + } catch (IllegalArgumentException e) { + if (DEBUG) { + Slog.w(TAG, "Can't parse blacklist, skip and continue..."); + } + } catch (InvalidProtocolBufferException e) { + if (DEBUG) { + Slog.w(TAG, "Can't parse blacklist, skip and continue..."); + } + } + } + } + + private void setBlacklist() { + Settings.Global.putString(mContentResolver, + Settings.Global.GAME_DRIVER_BLACKLIST, ""); + synchronized (mLock) { + if (mBlacklists == null) { + return; + } + List<Blacklist> blacklists = mBlacklists.getBlacklistsList(); + for (Blacklist blacklist : blacklists) { + if (blacklist.getVersionCode() == mGameDriverVersionCode) { + Settings.Global.putString(mContentResolver, + Settings.Global.GAME_DRIVER_BLACKLIST, + String.join(",", blacklist.getPackageNamesList())); + return; + } + } + } + } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index 20933db803b9..560f7a03b20f 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -342,7 +342,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { super.disableDevice(initiatedByCec, callback); assertRunOnServiceThread(); - if (!initiatedByCec && mIsActiveSource) { + if (!initiatedByCec && mIsActiveSource && mService.isControlEnabled()) { mService.sendCecCommand(HdmiCecMessageBuilder.buildInactiveSource( mAddress, mService.getPhysicalAddress())); } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 5e7ea05f799c..28393a209111 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -1805,9 +1805,10 @@ public class InputManagerService extends IInputManager.Stub } // Native callback. - private int interceptMotionBeforeQueueingNonInteractive(long whenNanos, int policyFlags) { + private int interceptMotionBeforeQueueingNonInteractive(int displayId, + long whenNanos, int policyFlags) { return mWindowManagerCallbacks.interceptMotionBeforeQueueingNonInteractive( - whenNanos, policyFlags); + displayId, whenNanos, policyFlags); } // Native callback. @@ -2021,7 +2022,13 @@ public class InputManagerService extends IInputManager.Stub public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags); - public int interceptMotionBeforeQueueingNonInteractive(long whenNanos, int policyFlags); + /** + * Provides an opportunity for the window manager policy to intercept early motion event + * processing when the device is in a non-interactive state since these events are normally + * dropped. + */ + int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos, + int policyFlags); public long interceptKeyBeforeDispatching(IBinder token, KeyEvent event, int policyFlags); diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index 2f0b388b9a7d..e71b156c3e86 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -917,7 +917,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements synchronized (mLock) { boolean enabled = ((mProviderRequest != null && mProviderRequest.reportLocation - && mProviderRequest.forceLocation) || ( + && mProviderRequest.locationSettingsIgnored) || ( mContext.getSystemService(LocationManager.class).isLocationEnabled() && !mDisableGps)) && !mShutdown; if (enabled == mEnabled) { @@ -976,7 +976,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } if (DEBUG) Log.d(TAG, "setRequest " + mProviderRequest); - if (mProviderRequest.reportLocation && !mDisableGps && isEnabled()) { + if (mProviderRequest.reportLocation && isEnabled()) { // update client uids updateClientUids(mWorkSource); diff --git a/services/core/java/com/android/server/location/RemoteListenerHelper.java b/services/core/java/com/android/server/location/RemoteListenerHelper.java index f03c99b0272e..aa8a25a36333 100644 --- a/services/core/java/com/android/server/location/RemoteListenerHelper.java +++ b/services/core/java/com/android/server/location/RemoteListenerHelper.java @@ -31,9 +31,11 @@ import java.util.HashMap; import java.util.Map; /** - * A helper class, that handles operations in remote listeners, and tracks for remote process death. + * A helper class that handles operations in remote listeners. + * + * @param <TListener> the type of GNSS data listener. */ -abstract class RemoteListenerHelper<TListener extends IInterface> { +public abstract class RemoteListenerHelper<TListener extends IInterface> { protected static final int RESULT_SUCCESS = 0; protected static final int RESULT_NOT_AVAILABLE = 1; @@ -46,7 +48,7 @@ abstract class RemoteListenerHelper<TListener extends IInterface> { protected final Handler mHandler; private final String mTag; - private final Map<IBinder, LinkedListener> mListenerMap = new HashMap<>(); + private final Map<IBinder, IdentifiedListener> mListenerMap = new HashMap<>(); protected final Context mContext; protected final AppOpsManager mAppOps; @@ -71,24 +73,21 @@ abstract class RemoteListenerHelper<TListener extends IInterface> { return mIsRegistered; } - public boolean addListener(@NonNull TListener listener, CallerIdentity callerIdentity) { + /** + * Adds GNSS data listener {@code listener} with caller identify {@code callerIdentify}. + */ + public void addListener(@NonNull TListener listener, CallerIdentity callerIdentity) { Preconditions.checkNotNull(listener, "Attempted to register a 'null' listener."); IBinder binder = listener.asBinder(); - LinkedListener deathListener = new LinkedListener(listener, callerIdentity); synchronized (mListenerMap) { if (mListenerMap.containsKey(binder)) { // listener already added - return true; - } - try { - binder.linkToDeath(deathListener, 0 /* flags */); - } catch (RemoteException e) { - // if the remote process registering the listener is already death, just swallow the - // exception and return - Log.v(mTag, "Remote listener already died.", e); - return false; + return; } - mListenerMap.put(binder, deathListener); + + IdentifiedListener identifiedListener = new IdentifiedListener(listener, + callerIdentity); + mListenerMap.put(binder, identifiedListener); // update statuses we already know about, starting from the ones that will never change int result; @@ -107,26 +106,23 @@ abstract class RemoteListenerHelper<TListener extends IInterface> { } else { // at this point if the supported flag is not set, the notification will be sent // asynchronously in the future - return true; + return; } - post(deathListener, getHandlerOperation(result)); + post(identifiedListener, getHandlerOperation(result)); } - return true; } + /** + * Remove GNSS data listener {@code listener}. + */ public void removeListener(@NonNull TListener listener) { Preconditions.checkNotNull(listener, "Attempted to remove a 'null' listener."); - IBinder binder = listener.asBinder(); - LinkedListener linkedListener; synchronized (mListenerMap) { - linkedListener = mListenerMap.remove(binder); + mListenerMap.remove(listener.asBinder()); if (mListenerMap.isEmpty()) { tryUnregister(); } } - if (linkedListener != null) { - binder.unlinkToDeath(linkedListener, 0 /* flags */); - } } protected abstract boolean isAvailableInPlatform(); @@ -198,14 +194,15 @@ abstract class RemoteListenerHelper<TListener extends IInterface> { } private void foreachUnsafe(ListenerOperation<TListener> operation) { - for (LinkedListener linkedListener : mListenerMap.values()) { - post(linkedListener, operation); + for (IdentifiedListener identifiedListener : mListenerMap.values()) { + post(identifiedListener, operation); } } - private void post(LinkedListener linkedListener, ListenerOperation<TListener> operation) { + private void post(IdentifiedListener identifiedListener, + ListenerOperation<TListener> operation) { if (operation != null) { - mHandler.post(new HandlerRunnable(linkedListener, operation)); + mHandler.post(new HandlerRunnable(identifiedListener, operation)); } } @@ -259,35 +256,31 @@ abstract class RemoteListenerHelper<TListener extends IInterface> { return RESULT_SUCCESS; } - private class LinkedListener implements IBinder.DeathRecipient { + private class IdentifiedListener { private final TListener mListener; private final CallerIdentity mCallerIdentity; - LinkedListener(@NonNull TListener listener, CallerIdentity callerIdentity) { + private IdentifiedListener(@NonNull TListener listener, CallerIdentity callerIdentity) { mListener = listener; mCallerIdentity = callerIdentity; } - - @Override - public void binderDied() { - Log.d(mTag, "Remote Listener died: " + mListener); - removeListener(mListener); - } } private class HandlerRunnable implements Runnable { - private final LinkedListener mLinkedListener; + private final IdentifiedListener mIdentifiedListener; private final ListenerOperation<TListener> mOperation; - HandlerRunnable(LinkedListener linkedListener, ListenerOperation<TListener> operation) { - mLinkedListener = linkedListener; + private HandlerRunnable(IdentifiedListener identifiedListener, + ListenerOperation<TListener> operation) { + mIdentifiedListener = identifiedListener; mOperation = operation; } @Override public void run() { try { - mOperation.execute(mLinkedListener.mListener, mLinkedListener.mCallerIdentity); + mOperation.execute(mIdentifiedListener.mListener, + mIdentifiedListener.mCallerIdentity); } catch (RemoteException e) { Log.v(mTag, "Error in monitored listener.", e); } diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index b6ef180f4b59..b221241c25e2 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -1207,6 +1207,15 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } } + public void setPlaybackSpeed(String packageName, int pid, int uid, + ControllerCallbackLink caller, float speed) { + try { + mCb.notifySetPlaybackSpeed(packageName, pid, uid, caller, speed); + } catch (RuntimeException e) { + Slog.e(TAG, "Remote failure in setPlaybackSpeed.", e); + } + } + public void adjustVolume(String packageName, int pid, int uid, ControllerCallbackLink caller, boolean asSystemService, int direction) { try { @@ -1446,6 +1455,13 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } @Override + public void setPlaybackSpeed(String packageName, ControllerCallbackLink caller, + float speed) { + mSessionCb.setPlaybackSpeed(packageName, Binder.getCallingPid(), Binder.getCallingUid(), + caller, speed); + } + + @Override public void sendCustomAction(String packageName, ControllerCallbackLink caller, String action, Bundle args) { mSessionCb.sendCustomAction(packageName, Binder.getCallingPid(), Binder.getCallingUid(), diff --git a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java index 5c7317892f3f..62d9b20b636d 100644 --- a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java @@ -284,13 +284,16 @@ public class MediaSessionServiceImpl extends MediaSessionService.ServiceImpl { * Tells the system UI that volume has changed on an active remote session. */ public void notifyRemoteVolumeChanged(int flags, MediaSessionRecord session) { - if (mRvc == null || !session.isActive()) { - return; - } - try { - mRvc.remoteVolumeChanged(session.getSessionToken(), flags); - } catch (Exception e) { - Log.wtf(TAG, "Error sending volume change to system UI.", e); + synchronized (mLock) { + if (mRvc == null || !session.isActive()) { + return; + } + try { + mRvc.remoteVolumeChanged(session.getSessionToken(), flags); + } catch (Exception e) { + Log.w(TAG, "Error sending volume change to system UI.", e); + mRvc = null; + } } } @@ -563,7 +566,7 @@ public class MediaSessionServiceImpl extends MediaSessionService.ServiceImpl { String callerPackageName, SessionCallbackLink cb, String tag) { FullUserRecord user = getFullUserRecordLocked(userId); if (user == null) { - Log.wtf(TAG, "Request from invalid user: " + userId); + Log.w(TAG, "Request from invalid user: " + userId + ", pkg=" + callerPackageName); throw new RuntimeException("Session request from invalid user."); } @@ -643,7 +646,8 @@ public class MediaSessionServiceImpl extends MediaSessionService.ServiceImpl { MediaSessionRecord record = user.mPriorityStack.getDefaultRemoteSession(userId); mRvc.updateRemoteController(record == null ? null : record.getSessionToken()); } catch (RemoteException e) { - Log.wtf(TAG, "Error sending default remote volume to sys ui.", e); + Log.w(TAG, "Error sending default remote volume to sys ui.", e); + mRvc = null; } } } @@ -1661,7 +1665,9 @@ public class MediaSessionServiceImpl extends MediaSessionService.ServiceImpl { final long token = Binder.clearCallingIdentity(); try { enforceSystemUiPermission("listen for volume changes", pid, uid); - mRvc = rvc; + synchronized (mLock) { + mRvc = rvc; + } } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java index 9e5b92a6b944..3f15b381c18b 100644 --- a/services/core/java/com/android/server/net/LockdownVpnTracker.java +++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java @@ -17,9 +17,6 @@ package com.android.server.net; import static android.Manifest.permission.CONNECTIVITY_INTERNAL; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NONE; -import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW; -import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT; import static android.provider.Settings.ACTION_VPN_SETTINGS; import android.app.Notification; @@ -30,17 +27,14 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; -import android.net.LinkProperties; import android.net.LinkAddress; +import android.net.LinkProperties; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; import android.net.NetworkInfo.State; -import android.net.NetworkPolicyManager; import android.os.INetworkManagementService; -import android.os.RemoteException; import android.security.Credentials; import android.security.KeyStore; -import android.system.Os; import android.text.TextUtils; import android.util.Slog; diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java index 4bd8f450c76b..6d82c1c257ab 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java +++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java @@ -15,15 +15,15 @@ */ package com.android.server.net; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE; +import static android.net.INetd.FIREWALL_CHAIN_DOZABLE; +import static android.net.INetd.FIREWALL_CHAIN_POWERSAVE; +import static android.net.INetd.FIREWALL_CHAIN_STANDBY; +import static android.net.INetd.FIREWALL_RULE_ALLOW; +import static android.net.INetd.FIREWALL_RULE_DENY; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_POWERSAVE; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY; -import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW; import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT; -import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY; import android.app.ActivityManager; import android.net.NetworkPolicyManager; diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index af55605975ca..75b62cb349af 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -38,6 +38,11 @@ import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLE import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED; import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED; import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.net.INetd.FIREWALL_CHAIN_DOZABLE; +import static android.net.INetd.FIREWALL_CHAIN_POWERSAVE; +import static android.net.INetd.FIREWALL_CHAIN_STANDBY; +import static android.net.INetd.FIREWALL_RULE_ALLOW; +import static android.net.INetd.FIREWALL_RULE_DENY; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; @@ -45,12 +50,7 @@ import static android.net.NetworkPolicy.LIMIT_DISABLED; import static android.net.NetworkPolicy.SNOOZE_NEVER; import static android.net.NetworkPolicy.WARNING_DISABLED; import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_POWERSAVE; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY; -import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW; import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT; -import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY; import static android.net.NetworkPolicyManager.MASK_ALL_NETWORKS; import static android.net.NetworkPolicyManager.MASK_METERED_NETWORKS; import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND; diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java new file mode 100644 index 000000000000..dac4b6ff39c3 --- /dev/null +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -0,0 +1,221 @@ +/* + * 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.s + */ + +package com.android.server.pm; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.apex.ApexInfo; +import android.apex.ApexInfoList; +import android.apex.ApexSessionInfo; +import android.apex.IApexService; +import android.content.pm.PackageInfo; +import android.content.pm.PackageParser; +import android.content.pm.PackageParser.PackageParserException; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Slog; + +import com.android.internal.util.IndentingPrintWriter; + +import java.io.File; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * ApexManager class handles communications with the apex service to perform operation and queries, + * as well as providing caching to avoid unnecessary calls to the service. + */ +class ApexManager { + static final String TAG = "ApexManager"; + private final IApexService mApexService; + private final Map<String, PackageInfo> mActivePackagesCache; + + ApexManager() { + mApexService = IApexService.Stub.asInterface( + ServiceManager.getService("apexservice")); + mActivePackagesCache = populateActivePackagesCache(); + } + + @NonNull + private Map<String, PackageInfo> populateActivePackagesCache() { + try { + List<PackageInfo> list = new ArrayList<>(); + final ApexInfo[] activePkgs = mApexService.getActivePackages(); + for (ApexInfo ai : activePkgs) { + // If the device is using flattened APEX, don't report any APEX + // packages since they won't be managed or updated by PackageManager. + if ((new File(ai.packagePath)).isDirectory()) { + break; + } + try { + list.add(PackageParser.generatePackageInfoFromApex( + new File(ai.packagePath), true /* collect certs */)); + } catch (PackageParserException pe) { + throw new IllegalStateException("Unable to parse: " + ai, pe); + } + } + return list.stream().collect(Collectors.toMap(p -> p.packageName, Function.identity())); + } catch (RemoteException re) { + Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString()); + throw new RuntimeException(re); + } + } + + /** + * Retrieves information about an active APEX package. + * + * @param packageName the package name to look for. Note that this is the package name reported + * in the APK container manifest (i.e. AndroidManifest.xml), which might + * differ from the one reported in the APEX manifest (i.e. + * apex_manifest.json). + * @return a PackageInfo object with the information about the package, or null if the package + * is not found. + */ + @Nullable PackageInfo getActivePackage(String packageName) { + return mActivePackagesCache.get(packageName); + } + + /** + * Retrieves information about all active APEX packages. + * + * @return a Collection of PackageInfo object, each one containing information about a different + * active package. + */ + Collection<PackageInfo> getActivePackages() { + return mActivePackagesCache.values(); + } + + /** + * Retrieves information about an apexd staged session i.e. the internal state used by apexd to + * track the different states of a session. + * + * @param sessionId the identifier of the session. + * @return an ApexSessionInfo object, or null if the session is not known. + */ + @Nullable ApexSessionInfo getStagedSessionInfo(int sessionId) { + try { + ApexSessionInfo apexSessionInfo = mApexService.getStagedSessionInfo(sessionId); + if (apexSessionInfo.isUnknown) { + return null; + } + return apexSessionInfo; + } catch (RemoteException re) { + Slog.e(TAG, "Unable to contact apexservice", re); + throw new RuntimeException(re); + } + } + + /** + * Submit a staged session to apex service. This causes the apex service to perform some initial + * verification and accept or reject the session. Submitting a session successfully is not + * enough for it to be activated at the next boot, the caller needs to call + * {@link #markStagedSessionReady(int)}. + * + * @param sessionId the identifier of the {@link PackageInstallerSession} being submitted. + * @param childSessionIds if {@code sessionId} is a multi-package session, this should contain + * an array of identifiers of all the child sessions. Otherwise it should + * be an empty array. + * @param apexInfoList this is an output parameter, which needs to be initialized by tha caller + * and will be filled with a list of {@link ApexInfo} objects, each of which + * contains metadata about one of the packages being submitted as part of + * the session. + * @return whether the submission of the session was successful. + */ + boolean submitStagedSession( + int sessionId, @NonNull int[] childSessionIds, @NonNull ApexInfoList apexInfoList) { + try { + return mApexService.submitStagedSession(sessionId, childSessionIds, apexInfoList); + } catch (RemoteException re) { + Slog.e(TAG, "Unable to contact apexservice", re); + throw new RuntimeException(re); + } + } + + /** + * Mark a staged session previously submitted using {@cde submitStagedSession} as ready to be + * applied at next reboot. + * + * @param sessionId the identifier of the {@link PackageInstallerSession} being marked as ready. + * @return true upon success, false if the session is unknown. + */ + boolean markStagedSessionReady(int sessionId) { + try { + return mApexService.markStagedSessionReady(sessionId); + } catch (RemoteException re) { + Slog.e(TAG, "Unable to contact apexservice", re); + throw new RuntimeException(re); + } + } + + /** + * Dumps various state information to the provided {@link PrintWriter} object. + * + * @param pw the {@link PrintWriter} object to send information to. + * @param packageName a {@link String} containing a package name, or {@code null}. If set, only + * information about that specific package will be dumped. + */ + void dump(PrintWriter pw, @Nullable String packageName) { + final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120); + ipw.println(); + ipw.println("Active APEX packages:"); + ipw.increaseIndent(); + try { + populateActivePackagesCache(); + for (PackageInfo pi : mActivePackagesCache.values()) { + if (packageName != null && !packageName.equals(pi.packageName)) { + continue; + } + ipw.println(pi.packageName); + ipw.increaseIndent(); + ipw.println("Version: " + pi.versionCode); + ipw.println("Path: " + pi.applicationInfo.sourceDir); + ipw.decreaseIndent(); + } + ipw.decreaseIndent(); + ipw.println(); + ipw.println("APEX session state:"); + ipw.increaseIndent(); + final ApexSessionInfo[] sessions = mApexService.getSessions(); + for (ApexSessionInfo si : sessions) { + ipw.println("Session ID: " + Integer.toString(si.sessionId)); + ipw.increaseIndent(); + if (si.isUnknown) { + ipw.println("State: UNKNOWN"); + } else if (si.isVerified) { + ipw.println("State: VERIFIED"); + } else if (si.isStaged) { + ipw.println("State: STAGED"); + } else if (si.isActivated) { + ipw.println("State: ACTIVATED"); + } else if (si.isActivationPendingRetry) { + ipw.println("State: ACTIVATION PENDING RETRY"); + } else if (si.isActivationFailed) { + ipw.println("State: ACTIVATION FAILED"); + } + ipw.decreaseIndent(); + } + ipw.decreaseIndent(); + } catch (RemoteException e) { + ipw.println("Couldn't communicate with apexd."); + } + } +} diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index f6bd2a95a4a3..86083494f3d6 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -186,7 +186,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } }; - public PackageInstallerService(Context context, PackageManagerService pm) { + public PackageInstallerService(Context context, PackageManagerService pm, ApexManager am) { mContext = context; mPm = pm; mPermissionManager = LocalServices.getService(PermissionManagerInternal.class); @@ -204,7 +204,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements mSessionsDir = new File(Environment.getDataSystemDirectory(), "install_sessions"); mSessionsDir.mkdirs(); - mStagingManager = new StagingManager(pm, this); + mStagingManager = new StagingManager(pm, this, am); } private void setBootCompleted() { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 874d1a719ee6..941de895e513 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -119,13 +119,11 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; -import android.apex.ApexInfo; -import android.apex.ApexSessionInfo; -import android.apex.IApexService; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AppDetailsActivity; import android.app.AppOpsManager; +import android.app.BroadcastOptions; import android.app.IActivityManager; import android.app.ResourcesManager; import android.app.admin.IDevicePolicyManager; @@ -241,6 +239,7 @@ import android.os.storage.StorageManagerInternal; import android.os.storage.VolumeInfo; import android.os.storage.VolumeRecord; import android.permission.PermissionControllerManager; +import android.provider.DeviceConfig; import android.provider.MediaStore; import android.provider.Settings.Global; import android.provider.Settings.Secure; @@ -733,10 +732,10 @@ public class PackageManagerService extends IPackageManager.Stub @GuardedBy("mPackages") final private ArraySet<PackageListObserver> mPackageListObservers = new ArraySet<>(); - private PackageManager mPackageManager; - private final ModuleInfoProvider mModuleInfoProvider; + private final ApexManager mApexManager; + class PackageParserCallback implements PackageParser.Callback { @Override public final boolean hasFeature(String feature) { return PackageManagerService.this.hasSystemFeature(feature, 0); @@ -1046,12 +1045,17 @@ public class PackageManagerService extends IPackageManager.Stub verificationIntent.setComponent(mIntentFilterVerifierComponent); verificationIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + final long whitelistTimeout = getVerificationTimeout(); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setTemporaryAppWhitelistDuration(whitelistTimeout); + DeviceIdleController.LocalService idleController = getDeviceIdleController(); idleController.addPowerSaveTempWhitelistApp(Process.myUid(), - mIntentFilterVerifierComponent.getPackageName(), getVerificationTimeout(), + mIntentFilterVerifierComponent.getPackageName(), whitelistTimeout, UserHandle.USER_SYSTEM, true, "intent filter verifier"); - mContext.sendBroadcastAsUser(verificationIntent, UserHandle.SYSTEM); + mContext.sendBroadcastAsUser(verificationIntent, UserHandle.SYSTEM, + null, options.toBundle()); if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "Sending IntentFilter verification broadcast"); } @@ -3074,7 +3078,8 @@ public class PackageManagerService extends IPackageManager.Stub } } - mInstallerService = new PackageInstallerService(context, this); + mApexManager = new ApexManager(); + mInstallerService = new PackageInstallerService(context, this, mApexManager); final Pair<ComponentName, String> instantAppResolverComponent = getInstantAppResolverLPr(); if (instantAppResolverComponent != null) { @@ -3301,7 +3306,8 @@ public class PackageManagerService extends IPackageManager.Stub // feature flags should cause us to invalidate any caches. final String cacheName = SystemProperties.digestOf( "ro.build.fingerprint", - "persist.sys.isolated_storage"); + StorageManager.PROP_ISOLATED_STORAGE, + StorageManager.PROP_ISOLATED_STORAGE_SNAPSHOT); // Reconcile cache directories, keeping only what we'd actually use. for (File cacheDir : FileUtils.listFilesOrEmpty(cacheBaseDir)) { @@ -3934,27 +3940,7 @@ public class PackageManagerService extends IPackageManager.Stub } // if (!matchFactoryOnly && (flags & MATCH_APEX) != 0) { - //TODO(b/123052859) Don't do file operations every time there is a query. - final IApexService apex = IApexService.Stub.asInterface( - ServiceManager.getService("apexservice")); - if (apex != null) { - try { - final ApexInfo activePkg = apex.getActivePackage(packageName); - if (activePkg != null && !TextUtils.isEmpty(activePkg.packagePath)) { - try { - return PackageParser.generatePackageInfoFromApex( - new File(activePkg.packagePath), true /* collect certs */); - } catch (PackageParserException pe) { - Log.e(TAG, "Unable to parse package at " - + activePkg.packagePath, pe); - } - } - } catch (RemoteException e) { - Log.e(TAG, "Unable to retrieve packages from apexservice: " + e.toString()); - } - } else { - Log.e(TAG, "Unable to connect to apexservice for querying packages."); - } + return mApexManager.getActivePackage(packageName); } } return null; @@ -7851,25 +7837,7 @@ public class PackageManagerService extends IPackageManager.Stub if (listApex) { // TODO(b/119767311): include uninstalled/inactive APEX if // MATCH_UNINSTALLED_PACKAGES is set. - final IApexService apex = IApexService.Stub.asInterface( - ServiceManager.getService("apexservice")); - if (apex != null) { - try { - final ApexInfo[] activePkgs = apex.getActivePackages(); - for (ApexInfo ai : activePkgs) { - try { - list.add(PackageParser.generatePackageInfoFromApex( - new File(ai.packagePath), true /* collect certs */)); - } catch (PackageParserException pe) { - throw new IllegalStateException("Unable to parse: " + ai, pe); - } - } - } catch (RemoteException e) { - Log.e(TAG, "Unable to retrieve packages from apexservice: " + e.toString()); - } - } else { - Log.e(TAG, "Unable to connect to apexservice for querying packages."); - } + list.addAll(mApexManager.getActivePackages()); } return new ParceledListSlice<>(list); } @@ -12836,7 +12804,7 @@ public class PackageManagerService extends IPackageManager.Stub public String[] setDistractingPackageRestrictionsAsUser(String[] packageNames, int restrictionFlags, int userId) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SUSPEND_APPS, - "setPackagesSuspendedAsUser"); + "setDistractingPackageRestrictionsAsUser"); final int callingUid = Binder.getCallingUid(); if (callingUid != Process.ROOT_UID && callingUid != Process.SYSTEM_UID @@ -14604,14 +14572,19 @@ public class PackageManagerService extends IPackageManager.Stub new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - // TODO(ruhler) b/112431924 Have a configurable setting to - // allow changing the timeout and fall back to the default - // if no such specified. + // the duration to wait for rollback to be enabled, in millis + long rollbackTimeout = DEFAULT_ENABLE_ROLLBACK_TIMEOUT; + try { + rollbackTimeout = Long.valueOf( + DeviceConfig.getProperty( + DeviceConfig.Rollback.NAMESPACE, + DeviceConfig.Rollback.ENABLE_ROLLBACK_TIMEOUT)); + } catch (NumberFormatException ignore) { + } final Message msg = mHandler.obtainMessage( ENABLE_ROLLBACK_TIMEOUT); msg.arg1 = enableRollbackToken; - mHandler.sendMessageDelayed(msg, - DEFAULT_ENABLE_ROLLBACK_TIMEOUT); + mHandler.sendMessageDelayed(msg, rollbackTimeout); } }, null, 0, null, null); @@ -21319,51 +21292,7 @@ public class PackageManagerService extends IPackageManager.Stub } if (!checkin && dumpState.isDumping(DumpState.DUMP_APEX)) { - final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120); - ipw.println(); - ipw.println("Active APEX packages:"); - ipw.increaseIndent(); - final IApexService apex = IApexService.Stub.asInterface( - ServiceManager.getService("apexservice")); - try { - final ApexInfo[] activeApexes = apex.getActivePackages(); - for (ApexInfo ai : activeApexes) { - if (packageName != null && !packageName.equals(ai.packageName)) { - continue; - } - ipw.println(ai.packageName); - ipw.increaseIndent(); - ipw.println("Version: " + Long.toString(ai.versionCode)); - ipw.println("Path: " + ai.packagePath); - ipw.decreaseIndent(); - } - ipw.decreaseIndent(); - ipw.println(); - ipw.println("APEX session state:"); - ipw.increaseIndent(); - final ApexSessionInfo[] sessions = apex.getSessions(); - for (ApexSessionInfo si : sessions) { - ipw.println("Session ID: " + Integer.toString(si.sessionId)); - ipw.increaseIndent(); - if (si.isUnknown) { - ipw.println("State: UNKNOWN"); - } else if (si.isVerified) { - ipw.println("State: VERIFIED"); - } else if (si.isStaged) { - ipw.println("State: STAGED"); - } else if (si.isActivated) { - ipw.println("State: ACTIVATED"); - } else if (si.isActivationPendingRetry) { - ipw.println("State: ACTIVATION PENDING RETRY"); - } else if (si.isActivationFailed) { - ipw.println("State: ACTIVATION FAILED"); - } - ipw.decreaseIndent(); - } - ipw.decreaseIndent(); - } catch (RemoteException e) { - ipw.println("Couldn't communicate with apexd."); - } + mApexManager.dump(pw, packageName); } } diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 84c8b606a9d9..d9a5eb901344 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -18,6 +18,7 @@ package com.android.server.pm; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.Person; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; @@ -33,6 +34,7 @@ import android.util.Log; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.server.pm.ShortcutService.DumpFilter; @@ -71,6 +73,7 @@ class ShortcutPackage extends ShortcutPackageItem { private static final String TAG_EXTRAS = "extras"; private static final String TAG_SHORTCUT = "shortcut"; private static final String TAG_CATEGORIES = "categories"; + private static final String TAG_PERSON = "person"; private static final String ATTR_NAME = "name"; private static final String ATTR_CALL_COUNT = "call-count"; @@ -96,6 +99,12 @@ class ShortcutPackage extends ShortcutPackageItem { private static final String ATTR_ICON_RES_NAME = "icon-resname"; private static final String ATTR_BITMAP_PATH = "bitmap-path"; + private static final String ATTR_PERSON_NAME = "name"; + private static final String ATTR_PERSON_URI = "uri"; + private static final String ATTR_PERSON_KEY = "key"; + private static final String ATTR_PERSON_IS_BOT = "is-bot"; + private static final String ATTR_PERSON_IS_IMPORTANT = "is-important"; + private static final String NAME_CATEGORIES = "categories"; private static final String TAG_STRING_ARRAY_XMLUTILS = "string-array"; @@ -1499,6 +1508,22 @@ class ShortcutPackage extends ShortcutPackageItem { out.endTag(null, TAG_CATEGORIES); } } + if (!forBackup) { // Don't backup the persons field. + final Person[] persons = si.getPersons(); + if (!ArrayUtils.isEmpty(persons)) { + for (int i = 0; i < persons.length; i++) { + final Person p = persons[i]; + + out.startTag(null, TAG_PERSON); + ShortcutService.writeAttr(out, ATTR_PERSON_NAME, p.getName()); + ShortcutService.writeAttr(out, ATTR_PERSON_URI, p.getUri()); + ShortcutService.writeAttr(out, ATTR_PERSON_KEY, p.getKey()); + ShortcutService.writeAttr(out, ATTR_PERSON_IS_BOT, p.isBot()); + ShortcutService.writeAttr(out, ATTR_PERSON_IS_IMPORTANT, p.isImportant()); + out.endTag(null, TAG_PERSON); + } + } + } final Intent[] intentsNoExtras = si.getIntentsNoExtras(); final PersistableBundle[] intentsExtras = si.getIntentPersistableExtrases(); final int numIntents = intentsNoExtras.length; @@ -1588,6 +1613,7 @@ class ShortcutPackage extends ShortcutPackageItem { String bitmapPath; int backupVersionCode; ArraySet<String> categories = null; + ArrayList<Person> persons = new ArrayList<>(); id = ShortcutService.parseStringAttribute(parser, ATTR_ID); activityComponent = ShortcutService.parseComponentNameAttribute(parser, @@ -1638,6 +1664,9 @@ class ShortcutPackage extends ShortcutPackageItem { case TAG_CATEGORIES: // This just contains string-array. continue; + case TAG_PERSON: + persons.add(parsePerson(parser)); + continue; case TAG_STRING_ARRAY_XMLUTILS: if (NAME_CATEGORIES.equals(ShortcutService.parseStringAttribute(parser, ATTR_NAME_XMLUTILS))) { @@ -1680,7 +1709,8 @@ class ShortcutPackage extends ShortcutPackageItem { categories, intents.toArray(new Intent[intents.size()]), rank, extras, lastChangedTimestamp, flags, - iconResId, iconResName, bitmapPath, disabledReason); + iconResId, iconResName, bitmapPath, disabledReason, + persons.toArray(new Person[persons.size()])); } private static Intent parseIntent(XmlPullParser parser) @@ -1713,6 +1743,20 @@ class ShortcutPackage extends ShortcutPackageItem { return intent; } + private static Person parsePerson(XmlPullParser parser) + throws IOException, XmlPullParserException { + CharSequence name = ShortcutService.parseStringAttribute(parser, ATTR_PERSON_NAME); + String uri = ShortcutService.parseStringAttribute(parser, ATTR_PERSON_URI); + String key = ShortcutService.parseStringAttribute(parser, ATTR_PERSON_KEY); + boolean isBot = ShortcutService.parseBooleanAttribute(parser, ATTR_PERSON_IS_BOT); + boolean isImportant = ShortcutService.parseBooleanAttribute(parser, + ATTR_PERSON_IS_IMPORTANT); + + Person.Builder builder = new Person.Builder(); + builder.setName(name).setUri(uri).setKey(key).setBot(isBot).setImportant(isImportant); + return builder.build(); + } + @VisibleForTesting List<ShortcutInfo> getAllShortcutsForTest() { return new ArrayList<>(mShortcuts.values()); diff --git a/services/core/java/com/android/server/pm/ShortcutParser.java b/services/core/java/com/android/server/pm/ShortcutParser.java index 90f08c30139a..668fc88b6b58 100644 --- a/services/core/java/com/android/server/pm/ShortcutParser.java +++ b/services/core/java/com/android/server/pm/ShortcutParser.java @@ -449,7 +449,8 @@ public class ShortcutParser { iconResId, null, // icon res name null, // bitmap path - disabledReason); + disabledReason, + null /* persons */); } private static String parseCategory(ShortcutService service, AttributeSet attrs) { diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index fa8360b1e5ba..6c212d63d77c 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -20,12 +20,12 @@ import android.annotation.NonNull; import android.apex.ApexInfo; import android.apex.ApexInfoList; import android.apex.ApexSessionInfo; -import android.apex.IApexService; import android.content.Context; import android.content.IIntentReceiver; import android.content.IIntentSender; import android.content.Intent; import android.content.IntentSender; +import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageManager; @@ -41,7 +41,6 @@ import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; -import android.text.TextUtils; import android.util.Slog; import android.util.SparseArray; import android.util.apk.ApkSignatureVerifier; @@ -68,14 +67,16 @@ public class StagingManager { private final PackageInstallerService mPi; private final PackageManagerService mPm; + private final ApexManager mApexManager; private final Handler mBgHandler; @GuardedBy("mStagedSessions") private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>(); - StagingManager(PackageManagerService pm, PackageInstallerService pi) { + StagingManager(PackageManagerService pm, PackageInstallerService pi, ApexManager am) { mPm = pm; mPi = pi; + mApexManager = am; mBgHandler = BackgroundThread.getHandler(); } @@ -100,7 +101,7 @@ public class StagingManager { return new ParceledListSlice<>(result); } - private static boolean validateApexSignature(String apexPath, String packageName) { + private boolean validateApexSignature(String apexPath, String packageName) { final SigningDetails signingDetails; try { signingDetails = ApkSignatureVerifier.verify(apexPath, SignatureSchemeVersion.JAR); @@ -109,17 +110,9 @@ public class StagingManager { return false; } - final IApexService apex = IApexService.Stub.asInterface( - ServiceManager.getService("apexservice")); - final ApexInfo apexInfo; - try { - apexInfo = apex.getActivePackage(packageName); - } catch (RemoteException re) { - Slog.e(TAG, "Unable to contact APEXD", re); - return false; - } + final PackageInfo packageInfo = mApexManager.getActivePackage(packageName); - if (apexInfo == null || TextUtils.isEmpty(apexInfo.packageName)) { + if (packageInfo == null) { // TODO: What is the right thing to do here ? This implies there's no active package // with the given name. This should never be the case in production (where we only // accept updates to existing APEXes) but may be required for testing. @@ -129,9 +122,10 @@ public class StagingManager { final SigningDetails existingSigningDetails; try { existingSigningDetails = ApkSignatureVerifier.verify( - apexInfo.packagePath, SignatureSchemeVersion.JAR); + packageInfo.applicationInfo.sourceDir, SignatureSchemeVersion.JAR); } catch (PackageParserException e) { - Slog.e(TAG, "Unable to parse APEX package: " + apexInfo.packagePath, e); + Slog.e(TAG, "Unable to parse APEX package: " + + packageInfo.applicationInfo.sourceDir, e); return false; } @@ -143,10 +137,10 @@ public class StagingManager { return false; } - private static boolean submitSessionToApexService(@NonNull PackageInstallerSession session, - List<PackageInstallerSession> childSessions, - ApexInfoList apexInfoList) { - return sendSubmitStagedSessionRequest( + private boolean submitSessionToApexService(@NonNull PackageInstallerSession session, + List<PackageInstallerSession> childSessions, + ApexInfoList apexInfoList) { + return mApexManager.submitStagedSession( session.sessionId, childSessions != null ? childSessions.stream().mapToInt(s -> s.sessionId).toArray() : @@ -154,33 +148,6 @@ public class StagingManager { apexInfoList); } - private static boolean sendSubmitStagedSessionRequest( - int sessionId, int[] childSessionIds, ApexInfoList apexInfoList) { - final IApexService apex = IApexService.Stub.asInterface( - ServiceManager.getService("apexservice")); - boolean success; - try { - success = apex.submitStagedSession(sessionId, childSessionIds, apexInfoList); - } catch (RemoteException re) { - Slog.e(TAG, "Unable to contact apexservice", re); - return false; - } - return success; - } - - private static boolean sendMarkStagedSessionReadyRequest(int sessionId) { - final IApexService apex = IApexService.Stub.asInterface( - ServiceManager.getService("apexservice")); - boolean success; - try { - success = apex.markStagedSessionReady(sessionId); - } catch (RemoteException re) { - Slog.e(TAG, "Unable to contact apexservice", re); - return false; - } - return success; - } - private static boolean isApexSession(@NonNull PackageInstallerSession session) { return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0; } @@ -189,12 +156,8 @@ public class StagingManager { boolean success = true; // STOPSHIP: TODO(b/123753157): Verify APKs through Package Verifier. - if (!sessionContainsApex(session)) { - // TODO: Decide whether we want to fail fast by detecting signature mismatches for APKs, - // right away. - session.setStagedSessionReady(); - return; - } + // TODO: Decide whether we want to fail fast by detecting signature mismatches for APKs, + // right away. final ApexInfoList apexInfoList = new ApexInfoList(); // APEX checks. For single-package sessions, check if they contain an APEX. For @@ -260,7 +223,8 @@ public class StagingManager { } session.setStagedSessionReady(); - if (!sendMarkStagedSessionReadyRequest(session.sessionId)) { + if (sessionContainsApex(session) + && !mApexManager.markStagedSessionReady(session.sessionId)) { session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "APEX staging failed, check logcat messages from apexd for more " + "details."); @@ -284,16 +248,12 @@ public class StagingManager { private void resumeSession(@NonNull PackageInstallerSession session) { if (sessionContainsApex(session)) { - // Check with apexservice whether the apex - // packages have been activated. - final IApexService apex = IApexService.Stub.asInterface( - ServiceManager.getService("apexservice")); - ApexSessionInfo apexSessionInfo; - try { - apexSessionInfo = apex.getStagedSessionInfo(session.sessionId); - } catch (RemoteException re) { - Slog.e(TAG, "Unable to contact apexservice", re); - // TODO should we retry here? Mark the session as failed? + // Check with apexservice whether the apex packages have been activated. + ApexSessionInfo apexSessionInfo = mApexManager.getStagedSessionInfo(session.sessionId); + if (apexSessionInfo == null) { + session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, + "apexd did not know anything about a staged session supposed to be" + + "activated"); return; } if (apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown) { @@ -323,8 +283,8 @@ public class StagingManager { // The APEX part of the session is activated, proceed with the installation of APKs. if (!installApksInSession(session)) { session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, - "APEX activation failed. Check logcat messages from apexd for " - + "more information."); + "Staged installation of APKs failed. Check logcat messages for" + + "more information."); return; } session.setStagedSessionApplied(); diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 2036ed73fdf2..bd577598a617 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -721,7 +721,7 @@ public final class DefaultPermissionGrantPolicy { grantSystemFixedPermissionsToSystemPackage( getDefaultSystemHandlerActivityPackage( RingtoneManager.ACTION_RINGTONE_PICKER, userId), - userId, STORAGE_PERMISSIONS); + userId, STORAGE_PERMISSIONS, MEDIA_AURAL_PERMISSIONS); // TextClassifier Service String textClassifierPackageName = diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index b00193f5453f..2e3e3e430839 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -22,7 +22,6 @@ import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; import static android.app.AppOpsManager.OP_TOAST_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.content.Context.CONTEXT_RESTRICTED; -import static android.content.Context.DISPLAY_SERVICE; import static android.content.Context.WINDOW_SERVICE; import static android.content.pm.PackageManager.FEATURE_HDMI_CEC; import static android.content.pm.PackageManager.FEATURE_LEANBACK; @@ -36,6 +35,7 @@ import static android.provider.Settings.Secure.VOLUME_HUSH_OFF; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.Display.STATE_OFF; +import static android.view.KeyEvent.KEYCODE_UNKNOWN; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW; @@ -84,10 +84,8 @@ import static android.view.WindowManagerGlobal.ADD_OKAY; import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED; import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED; -import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs - .CAMERA_LENS_COVER_ABSENT; -import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs - .CAMERA_LENS_UNCOVERED; +import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT; +import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED; import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_CLOSED; import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN; import static com.android.server.wm.WindowManagerPolicyProto.KEYGUARD_DELEGATE; @@ -371,6 +369,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { IStatusBarService mStatusBarService; StatusBarManagerInternal mStatusBarManagerInternal; AudioManagerInternal mAudioManagerInternal; + DisplayManager mDisplayManager; boolean mPreloadedRecentApps; final Object mServiceAquireLock = new Object(); Vibrator mVibrator; // Vibrator for giving feedback of orientation changes @@ -1717,7 +1716,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); mDreamManagerInternal = LocalServices.getService(DreamManagerInternal.class); mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); - mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); + mAppOpsManager = mContext.getSystemService(AppOpsManager.class); + mDisplayManager = mContext.getSystemService(DisplayManager.class); mHasFeatureWatch = mContext.getPackageManager().hasSystemFeature(FEATURE_WATCH); mHasFeatureLeanback = mContext.getPackageManager().hasSystemFeature(FEATURE_LEANBACK); mHasFeatureHdmiCec = mContext.getPackageManager().hasSystemFeature(FEATURE_HDMI_CEC); @@ -2508,8 +2508,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { return context; } - final DisplayManager dm = (DisplayManager) context.getSystemService(DISPLAY_SERVICE); - final Display targetDisplay = dm.getDisplay(displayId); + final Display targetDisplay = mDisplayManager.getDisplay(displayId); if (targetDisplay == null) { // Failed to obtain the non-default display where splash screen should be shown, // lets not show at all. @@ -3655,7 +3654,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Reset the pending key mPendingWakeKey = PENDING_KEY_NULL; } - } else if (!interactive && shouldDispatchInputWhenNonInteractive(event)) { + } else if (!interactive && shouldDispatchInputWhenNonInteractive(displayId, keyCode)) { // If we're currently dozing with the screen on and the keyguard showing, pass the key // to the application but preserve its wake key status to make sure we still move // from dozing to fully interactive if we would normally go from off to fully @@ -4126,7 +4125,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { // TODO(b/117479243): handle it in InputPolicy /** {@inheritDoc} */ @Override - public int interceptMotionBeforeQueueingNonInteractive(long whenNanos, int policyFlags) { + public int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos, + int policyFlags) { if ((policyFlags & FLAG_WAKE) != 0) { if (wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotion, PowerManager.WAKE_REASON_WAKE_MOTION, "android.policy:MOTION")) { @@ -4134,7 +4134,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - if (shouldDispatchInputWhenNonInteractive(null)) { + if (shouldDispatchInputWhenNonInteractive(displayId, KEYCODE_UNKNOWN)) { return ACTION_PASS_TO_USER; } @@ -4149,9 +4149,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { return 0; } - private boolean shouldDispatchInputWhenNonInteractive(KeyEvent event) { - final boolean displayOff = (mDefaultDisplay == null - || mDefaultDisplay.getState() == STATE_OFF); + private boolean shouldDispatchInputWhenNonInteractive(int displayId, int keyCode) { + // Apply the default display policy to unknown displays as well. + final boolean isDefaultDisplay = displayId == DEFAULT_DISPLAY + || displayId == INVALID_DISPLAY; + final Display display = isDefaultDisplay + ? mDefaultDisplay + : mDisplayManager.getDisplay(displayId); + final boolean displayOff = (display == null + || display.getState() == STATE_OFF); if (displayOff && !mHasFeatureWatch) { return false; @@ -4163,25 +4169,25 @@ public class PhoneWindowManager implements WindowManagerPolicy { } // Watches handle BACK specially - if (mHasFeatureWatch - && event != null - && (event.getKeyCode() == KeyEvent.KEYCODE_BACK - || event.getKeyCode() == KeyEvent.KEYCODE_STEM_PRIMARY)) { + if (mHasFeatureWatch && (keyCode == KeyEvent.KEYCODE_BACK + || keyCode == KeyEvent.KEYCODE_STEM_PRIMARY)) { return false; } - // Send events to a dozing dream even if the screen is off since the dream - // is in control of the state of the screen. - IDreamManager dreamManager = getDreamManager(); + // TODO(b/123372519): Refine when dream can support multi display. + if (isDefaultDisplay) { + // Send events to a dozing dream even if the screen is off since the dream + // is in control of the state of the screen. + IDreamManager dreamManager = getDreamManager(); - try { - if (dreamManager != null && dreamManager.isDreaming()) { - return true; + try { + if (dreamManager != null && dreamManager.isDreaming()) { + return true; + } + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException when checking if dreaming", e); } - } catch (RemoteException e) { - Slog.e(TAG, "RemoteException when checking if dreaming", e); } - // Otherwise, consume events since the user can't see what is being // interacted with. return false; diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index d1bd102f11dc..870d61b2ab90 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -1003,11 +1003,13 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { * affect the power state of the device, for example, waking on motions. * Generally, it's best to keep as little as possible in the queue thread * because it's the most fragile. + * @param displayId The display ID of the motion event. * @param policyFlags The policy flags associated with the motion. * * @return Actions flags: may be {@link #ACTION_PASS_TO_USER}. */ - public int interceptMotionBeforeQueueingNonInteractive(long whenNanos, int policyFlags); + int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos, + int policyFlags); /** * Called from the input dispatcher thread before a key is dispatched to a window. diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 95c3f4c43313..ceaf829290d7 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -40,6 +40,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.ParcelFileDescriptor; import android.os.Process; +import android.provider.DeviceConfig; import android.util.IntArray; import android.util.Log; import android.util.SparseBooleanArray; @@ -61,6 +62,7 @@ import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; /** * Implementation of service that manages APK level rollbacks. @@ -71,13 +73,19 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { // Rollbacks expire after 48 hours. // TODO: How to test rollback expiration works properly? - private static final long ROLLBACK_LIFETIME_DURATION_MILLIS = 48 * 60 * 60 * 1000; + private static final long DEFAULT_ROLLBACK_LIFETIME_DURATION_MILLIS = + TimeUnit.HOURS.toMillis(48); // Lock used to synchronize accesses to in-memory rollback data // structures. By convention, methods with the suffix "Locked" require // mLock is held when they are called. private final Object mLock = new Object(); + // No need for guarding with lock because value is only accessed in handler thread + // and the value will be written on boot complete. Initialization here happens before + // handler threads are running so that's fine. + private long mRollbackLifetimeDurationInMillis = DEFAULT_ROLLBACK_LIFETIME_DURATION_MILLIS; + // Used for generating rollback IDs. private final Random mRandom = new SecureRandom(); @@ -484,7 +492,25 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { }); } + private void updateRollbackLifetimeDurationInMillis() { + String strRollbackLifetimeInMillis = DeviceConfig.getProperty( + DeviceConfig.Rollback.BOOT_NAMESPACE, + DeviceConfig.Rollback.ROLLBACK_LIFETIME_IN_MILLIS); + + try { + mRollbackLifetimeDurationInMillis = (strRollbackLifetimeInMillis == null) + ? DEFAULT_ROLLBACK_LIFETIME_DURATION_MILLIS + : Long.parseLong(strRollbackLifetimeInMillis); + } catch (NumberFormatException e) { + mRollbackLifetimeDurationInMillis = DEFAULT_ROLLBACK_LIFETIME_DURATION_MILLIS; + } + } + void onBootCompleted() { + getHandler().post(() -> updateRollbackLifetimeDurationInMillis()); + // Also posts to handler thread + scheduleExpiration(0); + getHandler().post(() -> { // Check to see if any staged sessions with rollback enabled have // been applied. @@ -565,8 +591,6 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { for (RollbackInfo info : mRecentlyExecutedRollbacks) { mAllocatedRollbackIds.put(info.getRollbackId(), true); } - - scheduleExpiration(0); } /** @@ -700,8 +724,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { if (!data.isAvailable) { continue; } - - if (!now.isBefore(data.timestamp.plusMillis(ROLLBACK_LIFETIME_DURATION_MILLIS))) { + if (!now.isBefore(data.timestamp.plusMillis(mRollbackLifetimeDurationInMillis))) { iter.remove(); deleteRollback(data); } else if (oldest == null || oldest.isAfter(data.timestamp)) { @@ -711,7 +734,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } if (oldest != null) { - scheduleExpiration(now.until(oldest.plusMillis(ROLLBACK_LIFETIME_DURATION_MILLIS), + scheduleExpiration(now.until(oldest.plusMillis(mRollbackLifetimeDurationInMillis), ChronoUnit.MILLIS)); } } @@ -1144,8 +1167,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { packages.add(data.packages.get(i).getPackageName()); } mPackageHealthObserver.startObservingHealth(packages, - ROLLBACK_LIFETIME_DURATION_MILLIS); - scheduleExpiration(ROLLBACK_LIFETIME_DURATION_MILLIS); + mRollbackLifetimeDurationInMillis); + scheduleExpiration(mRollbackLifetimeDurationInMillis); } catch (IOException e) { Log.e(TAG, "Unable to enable rollback", e); deleteRollback(data); diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java index dd2cda20e0f1..f3393e2f29da 100644 --- a/services/core/java/com/android/server/stats/StatsCompanionService.java +++ b/services/core/java/com/android/server/stats/StatsCompanionService.java @@ -167,6 +167,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { public static final int CODE_DATA_BROADCAST = 1; public static final int CODE_SUBSCRIBER_BROADCAST = 1; + public static final int CODE_ACTIVE_CONFIGS_BROADCAST = 1; /** * The last report time is provided with each intent registered to * StatsManager#setFetchReportsOperation. This allows easy de-duping in the receiver if @@ -356,6 +357,22 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } @Override + public void sendActiveConfigsChangedBroadcast(IBinder intentSenderBinder, long[] configIds) { + enforceCallingPermission(); + IntentSender intentSender = new IntentSender(intentSenderBinder); + Intent intent = new Intent(); + intent.putExtra(StatsManager.EXTRA_STATS_ACTIVE_CONFIG_KEYS, configIds); + try { + intentSender.sendIntent(mContext, CODE_ACTIVE_CONFIGS_BROADCAST, intent, null, null); + if (DEBUG) { + Slog.d(TAG, "Sent broadcast with config ids " + Arrays.toString(configIds)); + } + } catch (IntentSender.SendIntentException e) { + Slog.w(TAG, "Unable to send active configs changed broadcast using IntentSender"); + } + } + + @Override public void sendSubscriberBroadcast(IBinder intentSenderBinder, long configUid, long configKey, long subscriptionId, long subscriptionRuleId, String[] cookies, StatsDimensionsValue dimensionsValue) { @@ -1169,7 +1186,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { BinderCallsStatsService.Internal binderStats = LocalServices.getService(BinderCallsStatsService.Internal.class); if (binderStats == null) { - return; + throw new IllegalStateException("binderStats is null"); } List<ExportedCallStat> callStats = binderStats.getExportedCallStats(); @@ -1200,7 +1217,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { BinderCallsStatsService.Internal binderStats = LocalServices.getService(BinderCallsStatsService.Internal.class); if (binderStats == null) { - return; + throw new IllegalStateException("binderStats is null"); } ArrayMap<String, Integer> exceptionStats = binderStats.getExportedExceptionStats(); @@ -1218,7 +1235,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { List<StatsLogEventWrapper> pulledData) { LooperStats looperStats = LocalServices.getService(LooperStats.class); if (looperStats == null) { - return; + throw new IllegalStateException("looperStats null"); } List<LooperStats.ExportedEntry> entries = looperStats.getEntries(); @@ -1689,18 +1706,19 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private void pullCpuTimePerThreadFreq(int tagId, long elapsedNanos, long wallClockNanos, List<StatsLogEventWrapper> pulledData) { if (this.mKernelCpuThreadReader == null) { - return; + throw new IllegalStateException("mKernelCpuThreadReader is null"); } ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages = this.mKernelCpuThreadReader.getProcessCpuUsageByUids(); if (processCpuUsages == null) { - return; + throw new IllegalStateException("processCpuUsages is null"); } int[] cpuFrequencies = mKernelCpuThreadReader.getCpuFrequenciesKhz(); if (cpuFrequencies.length > CPU_TIME_PER_THREAD_FREQ_MAX_NUM_FREQUENCIES) { - Slog.w(TAG, "Expected maximum " + CPU_TIME_PER_THREAD_FREQ_MAX_NUM_FREQUENCIES - + " frequencies, but got " + cpuFrequencies.length); - return; + String message = "Expected maximum " + CPU_TIME_PER_THREAD_FREQ_MAX_NUM_FREQUENCIES + + " frequencies, but got " + cpuFrequencies.length; + Slog.w(TAG, message); + throw new IllegalStateException(message); } for (int i = 0; i < processCpuUsages.size(); i++) { KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = processCpuUsages.get(i); @@ -1709,10 +1727,11 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { for (int j = 0; j < threadCpuUsages.size(); j++) { KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage = threadCpuUsages.get(j); if (threadCpuUsage.usageTimesMillis.length != cpuFrequencies.length) { - Slog.w(TAG, "Unexpected number of usage times," + String message = "Unexpected number of usage times," + " expected " + cpuFrequencies.length - + " but got " + threadCpuUsage.usageTimesMillis.length); - continue; + + " but got " + threadCpuUsage.usageTimesMillis.length; + Slog.w(TAG, message); + throw new IllegalStateException(message); } StatsLogEventWrapper e = diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 0251efb872bc..f33c518941e5 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -2677,8 +2677,9 @@ final class ActivityRecord extends ConfigurationContainer { * Get the configuration orientation by the requested screen orientation * ({@link ActivityInfo.ScreenOrientation}) of this activity. * - * @return orientation in ({@link #ORIENTATION_LANDSCAPE}, {@link #ORIENTATION_PORTRAIT}, - * {@link #ORIENTATION_UNDEFINED}). + * @return orientation in ({@link Configuration#ORIENTATION_LANDSCAPE}, + * {@link Configuration#ORIENTATION_PORTRAIT}, + * {@link Configuration#ORIENTATION_UNDEFINED}). */ int getRequestedConfigurationOrientation() { final int screenOrientation = getOrientation(); @@ -2936,14 +2937,36 @@ final class ActivityRecord extends ConfigurationContainer { // should be given the aspect ratio. activityWidth = (int) ((activityHeight * maxAspectRatio) + 0.5f); } - } else if (containingRatio < minAspectRatio && minAspectRatio != 0) { - if (containingAppWidth < containingAppHeight) { - // Width is the shorter side, so we use the height to figure-out what the max. width - // should be given the aspect ratio. + } else if (containingRatio < minAspectRatio) { + boolean adjustWidth; + switch (getRequestedConfigurationOrientation()) { + case ORIENTATION_LANDSCAPE: + // Width should be the longer side for this landscape app, so we use the width + // to figure-out what the max. height should be given the aspect ratio. + adjustWidth = false; + break; + case ORIENTATION_PORTRAIT: + // Height should be the longer side for this portrait app, so we use the height + // to figure-out what the max. width should be given the aspect ratio. + adjustWidth = true; + break; + default: + // This app doesn't have a preferred orientation, so we keep the length of the + // longer side, and use it to figure-out the length of the shorter side. + if (containingAppWidth < containingAppHeight) { + // Width is the shorter side, so we use the height to figure-out what the + // max. width should be given the aspect ratio. + adjustWidth = true; + } else { + // Height is the shorter side, so we use the width to figure-out what the + // max. height should be given the aspect ratio. + adjustWidth = false; + } + break; + } + if (adjustWidth) { activityWidth = (int) ((activityHeight / minAspectRatio) + 0.5f); } else { - // Height is the shorter side, so we use the width to figure-out what the max. - // height should be given the aspect ratio. activityHeight = (int) ((activityWidth / minAspectRatio) + 0.5f); } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 5cfc20b6339f..4795555e8ed2 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1500,8 +1500,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT / mBaseDisplayDensity; final int longSizeDp = longSize * DisplayMetrics.DENSITY_DEFAULT / mBaseDisplayDensity; - mDisplayRotation.configure(width, height, shortSizeDp, longSizeDp); mDisplayPolicy.configure(width, height, shortSizeDp); + mDisplayRotation.configure(width, height, shortSizeDp, longSizeDp); mDisplayFrames.onDisplayInfoUpdated(mDisplayInfo, calculateDisplayCutoutForRotation(mDisplayInfo.rotation)); diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 2ee30ac5c8ff..91d573defc16 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -2617,9 +2617,8 @@ public class DisplayPolicy { DisplayCutout displayCutout) { int width = fullWidth; if (hasNavigationBar()) { - // For a basic navigation bar, when we are in landscape mode we place - // the navigation bar to the side. - if (navigationBarCanMove() && fullWidth > fullHeight) { + final int navBarPosition = navigationBarPosition(fullWidth, fullHeight, rotation); + if (navBarPosition == NAV_BAR_LEFT || navBarPosition == NAV_BAR_RIGHT) { width -= getNavigationBarWidth(rotation, uiMode); } } @@ -2646,9 +2645,8 @@ public class DisplayPolicy { DisplayCutout displayCutout) { int height = fullHeight; if (hasNavigationBar()) { - // For a basic navigation bar, when we are in portrait mode we place - // the navigation bar to the bottom. - if (!navigationBarCanMove() || fullWidth < fullHeight) { + final int navBarPosition = navigationBarPosition(fullWidth, fullHeight, rotation); + if (navBarPosition == NAV_BAR_BOTTOM) { height -= getNavigationBarHeight(rotation, uiMode); } } diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 5f341ee8002c..543f19655350 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -38,6 +38,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.util.Slog; import android.util.SparseArray; +import android.view.DisplayCutout; import android.view.Surface; import com.android.internal.annotations.VisibleForTesting; @@ -70,6 +71,8 @@ public class DisplayRotation { private final int mDeskDockRotation; private final int mUndockedHdmiRotation; + private final float mCloseToSquareMaxAspectRatio; + private OrientationListener mOrientationListener; private StatusBarManagerInternal mStatusBarManagerInternal; private SettingsObserver mSettingsObserver; @@ -132,6 +135,9 @@ public class DisplayRotation { mUndockedHdmiRotation = readRotation( com.android.internal.R.integer.config_undockedHdmiRotation); + mCloseToSquareMaxAspectRatio = mContext.getResources().getFloat( + com.android.internal.R.dimen.config_closeToSquareDisplayMaxAspectRatio); + if (isDefaultDisplay) { final Handler uiHandler = UiThread.getHandler(); mOrientationListener = new OrientationListener(mContext, uiHandler); @@ -212,10 +218,12 @@ public class DisplayRotation { // so if the orientation is forced, we need to respect that no matter what. final boolean isTv = mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_LEANBACK); + final boolean isCloseToSquare = + isNonDecorDisplayCloseToSquare(Surface.ROTATION_0, width, height); final boolean forceDefaultOrientationInRes = res.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation); final boolean forceDefaultOrienation = - ((longSizeDp >= 960 && shortSizeDp >= 720) || isCar || isTv) + ((longSizeDp >= 960 && shortSizeDp >= 720) || isCar || isTv || isCloseToSquare) && forceDefaultOrientationInRes // For debug purposes the next line turns this feature off with: // $ adb shell setprop config.override_forced_orient true @@ -227,6 +235,18 @@ public class DisplayRotation { setFixedToUserRotation(forceDefaultOrienation); } + private boolean isNonDecorDisplayCloseToSquare(int rotation, int width, int height) { + final DisplayCutout displayCutout = + mDisplayContent.calculateDisplayCutoutForRotation(rotation).getDisplayCutout(); + final int uiMode = mService.mPolicy.getUiMode(); + final int w = mDisplayPolicy.getNonDecorDisplayWidth( + width, height, rotation, uiMode, displayCutout); + final int h = mDisplayPolicy.getNonDecorDisplayHeight( + width, height, rotation, uiMode, displayCutout); + final float aspectRatio = Math.max(w, h) / (float) Math.min(w, h); + return aspectRatio <= mCloseToSquareMaxAspectRatio; + } + void setRotation(int rotation) { if (mOrientationListener != null) { mOrientationListener.setCurrentRotation(rotation); diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index f9c9d33c561a..f46835eb51fc 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -163,15 +163,12 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal return mService.mPolicy.interceptKeyBeforeQueueing(event, policyFlags); } - /** - * Provides an opportunity for the window manager policy to intercept early motion event - * processing when the device is in a non-interactive state since these events are normally - * dropped. - */ + /** {@inheritDoc} */ @Override - public int interceptMotionBeforeQueueingNonInteractive(long whenNanos, int policyFlags) { + public int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos, + int policyFlags) { return mService.mPolicy.interceptMotionBeforeQueueingNonInteractive( - whenNanos, policyFlags); + displayId, whenNanos, policyFlags); } /** diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java index fb5b1d85a4a2..5c91d9e6fff2 100644 --- a/services/core/java/com/android/server/wm/RootActivityContainer.java +++ b/services/core/java/com/android/server/wm/RootActivityContainer.java @@ -300,7 +300,7 @@ class RootActivityContainer extends ConfigurationContainer * corresponding record in display manager. */ // TODO: Look into consolidating with getActivityDisplay() - ActivityDisplay getActivityDisplayOrCreate(int displayId) { + @Nullable ActivityDisplay getActivityDisplayOrCreate(int displayId) { ActivityDisplay activityDisplay = getActivityDisplay(displayId); if (activityDisplay != null) { return activityDisplay; @@ -1317,6 +1317,9 @@ class RootActivityContainer extends ConfigurationContainer if (DEBUG_STACK) Slog.v(TAG, "Display added displayId=" + displayId); synchronized (mService.mGlobalLock) { final ActivityDisplay display = getActivityDisplayOrCreate(displayId); + if (display == null) { + return; + } // Do not start home before booting, or it may accidentally finish booting before it // starts. Instead, we expect home activities to be launched when the system is ready // (ActivityManagerService#systemReady). diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index c747c6d95fcd..168c9adb61be 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -446,7 +446,8 @@ public class WindowManagerService extends IWindowManager.Stub final boolean mLimitedAlphaCompositing; final int mMaxUiWidth; - final WindowManagerPolicy mPolicy; + @VisibleForTesting + WindowManagerPolicy mPolicy; final IActivityManager mActivityManager; // TODO: Probably not needed once activities are fully in WM. @@ -4263,9 +4264,12 @@ public class WindowManagerService extends IWindowManager.Stub if (mMaxUiWidth > 0) { mRoot.forAllDisplays(displayContent -> displayContent.setMaxUiWidth(mMaxUiWidth)); } - applyForcedPropertiesForDefaultDisplay(); + final boolean changed = applyForcedPropertiesForDefaultDisplay(); mAnimator.ready(); mDisplayReady = true; + if (changed) { + reconfigureDisplayLocked(getDefaultDisplayContentLocked()); + } mIsTouchDevice = mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_TOUCHSCREEN); } @@ -4865,7 +4869,8 @@ public class WindowManagerService extends IWindowManager.Stub } /** The global settings only apply to default display. */ - private void applyForcedPropertiesForDefaultDisplay() { + private boolean applyForcedPropertiesForDefaultDisplay() { + boolean changed = false; final DisplayContent displayContent = getDefaultDisplayContentLocked(); // Display size. String sizeStr = Settings.Global.getString(mContext.getContentResolver(), @@ -4885,6 +4890,7 @@ public class WindowManagerService extends IWindowManager.Stub Slog.i(TAG_WM, "FORCED DISPLAY SIZE: " + width + "x" + height); displayContent.updateBaseDisplayMetrics(width, height, displayContent.mBaseDisplayDensity); + changed = true; } } catch (NumberFormatException ex) { } @@ -4893,17 +4899,20 @@ public class WindowManagerService extends IWindowManager.Stub // Display density. final int density = getForcedDisplayDensityForUserLocked(mCurrentUserId); - if (density != 0) { + if (density != 0 && density != displayContent.mBaseDisplayDensity) { displayContent.mBaseDisplayDensity = density; + changed = true; } // Display scaling mode. int mode = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.DISPLAY_SCALING_FORCE, 0); - if (mode != 0) { + if (displayContent.mDisplayScalingDisabled != (mode != 0)) { Slog.i(TAG_WM, "FORCED DISPLAY SCALING DISABLED"); displayContent.mDisplayScalingDisabled = true; + changed = true; } + return changed; } @Override diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 48cd6b68c416..3430987f4736 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2466,7 +2466,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP /** @return false if this window desires touch events. */ boolean cantReceiveTouchInput() { return mAppToken != null && mAppToken.getTask() != null - && mAppToken.getTask().mStack.shouldIgnoreInput(); + && (mAppToken.getTask().mStack.shouldIgnoreInput() || mAppToken.hiddenRequested); } @Override diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index c18e98bab9c4..57377c633c9a 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -249,7 +249,8 @@ public: virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags); virtual void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig); virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags); - virtual void interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags); + virtual void interceptMotionBeforeQueueing(const int32_t displayId, nsecs_t when, + uint32_t& policyFlags); virtual nsecs_t interceptKeyBeforeDispatching( const sp<IBinder>& token, const KeyEvent* keyEvent, uint32_t policyFlags); @@ -1066,7 +1067,8 @@ void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent, } } -void NativeInputManager::interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags) { +void NativeInputManager::interceptMotionBeforeQueueing(const int32_t displayId, nsecs_t when, + uint32_t& policyFlags) { ATRACE_CALL(); // Policy: // - Ignore untrusted events and pass them along. @@ -1084,7 +1086,7 @@ void NativeInputManager::interceptMotionBeforeQueueing(nsecs_t when, uint32_t& p JNIEnv* env = jniEnv(); jint wmActions = env->CallIntMethod(mServiceObj, gServiceClassInfo.interceptMotionBeforeQueueingNonInteractive, - when, policyFlags); + displayId, when, policyFlags); if (checkAndClearExceptionFromCallback(env, "interceptMotionBeforeQueueingNonInteractive")) { wmActions = 0; @@ -1794,7 +1796,7 @@ int register_android_server_InputManager(JNIEnv* env) { "interceptKeyBeforeQueueing", "(Landroid/view/KeyEvent;I)I"); GET_METHOD_ID(gServiceClassInfo.interceptMotionBeforeQueueingNonInteractive, clazz, - "interceptMotionBeforeQueueingNonInteractive", "(JI)I"); + "interceptMotionBeforeQueueingNonInteractive", "(IJI)I"); GET_METHOD_ID(gServiceClassInfo.interceptKeyBeforeDispatching, clazz, "interceptKeyBeforeDispatching", diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index cbc3791264bf..d178c3abc906 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -973,7 +973,10 @@ void GnssMeasurementCallback::translateSingleGnssMeasurement JavaObject& object) { translateSingleGnssMeasurement(&(measurement_V2_0->v1_1), object); - SET(CodeType, (static_cast<int32_t>(measurement_V2_0->codeType))); + SET(CodeType, static_cast<int32_t>(measurement_V2_0->codeType)); + + // Overwrite with v2_0.state since v2_0->v1_1->v1_0.state is deprecated. + SET(State, static_cast<int32_t>(measurement_V2_0->state)); } jobject GnssMeasurementCallback::translateGnssClock( diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/AbUpdateInstaller.java b/services/devicepolicy/java/com/android/server/devicepolicy/AbUpdateInstaller.java index de5dd1749830..d5cfab960171 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/AbUpdateInstaller.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/AbUpdateInstaller.java @@ -16,7 +16,7 @@ package com.android.server.devicepolicy; -import android.app.admin.DevicePolicyManager.InstallUpdateCallback; +import android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback; import android.app.admin.StartInstallingUpdateCallback; import android.content.Context; import android.os.ParcelFileDescriptor; @@ -62,41 +62,43 @@ class AbUpdateInstaller extends UpdateInstaller { private static Map<Integer, Integer> buildErrorCodesMap() { Map<Integer, Integer> map = new HashMap<>(); - map.put(UpdateEngine.ErrorCodeConstants.ERROR, InstallUpdateCallback.UPDATE_ERROR_UNKNOWN); + map.put( + UpdateEngine.ErrorCodeConstants.ERROR, + InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN); map.put( DOWNLOAD_STATE_INITIALIZATION_ERROR, - InstallUpdateCallback.UPDATE_ERROR_INCORRECT_OS_VERSION); + InstallSystemUpdateCallback.UPDATE_ERROR_INCORRECT_OS_VERSION); map.put( UpdateEngine.ErrorCodeConstants.PAYLOAD_TIMESTAMP_ERROR, - InstallUpdateCallback.UPDATE_ERROR_INCORRECT_OS_VERSION); + InstallSystemUpdateCallback.UPDATE_ERROR_INCORRECT_OS_VERSION); // Error constants corresponding to errors related to bad update file. map.put( UpdateEngine.ErrorCodeConstants.DOWNLOAD_PAYLOAD_VERIFICATION_ERROR, - InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID); + InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID); map.put( UpdateEngine.ErrorCodeConstants.PAYLOAD_SIZE_MISMATCH_ERROR, - InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID); + InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID); map.put( UpdateEngine.ErrorCodeConstants.PAYLOAD_MISMATCHED_TYPE_ERROR, - InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID); + InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID); map.put( UpdateEngine.ErrorCodeConstants.PAYLOAD_HASH_MISMATCH_ERROR, - InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID); + InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID); // Error constants corresponding to errors related to devices bad state. map.put( UpdateEngine.ErrorCodeConstants.POST_INSTALL_RUNNER_ERROR, - InstallUpdateCallback.UPDATE_ERROR_UNKNOWN); + InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN); map.put( UpdateEngine.ErrorCodeConstants.INSTALL_DEVICE_OPEN_ERROR, - InstallUpdateCallback.UPDATE_ERROR_UNKNOWN); + InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN); map.put( UpdateEngine.ErrorCodeConstants.DOWNLOAD_TRANSFER_ERROR, - InstallUpdateCallback.UPDATE_ERROR_UNKNOWN); + InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN); map.put( UpdateEngine.ErrorCodeConstants.UPDATED_BUT_NOT_ACTIVE, - InstallUpdateCallback.UPDATE_ERROR_UNKNOWN); + InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN); return map; } @@ -153,12 +155,13 @@ class AbUpdateInstaller extends UpdateInstaller { } catch (ZipException e) { Log.w(UpdateInstaller.TAG, e); notifyCallbackOnError( - InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID, + InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID, Log.getStackTraceString(e)); } catch (IOException e) { Log.w(UpdateInstaller.TAG, e); notifyCallbackOnError( - InstallUpdateCallback.UPDATE_ERROR_UNKNOWN, Log.getStackTraceString(e)); + InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN, + Log.getStackTraceString(e)); } } @@ -185,7 +188,7 @@ class AbUpdateInstaller extends UpdateInstaller { if (mSizeForUpdate == -1) { Log.w(UpdateInstaller.TAG, "Failed to find payload entry in the given package."); notifyCallbackOnError( - InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID, + InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID, "Failed to find payload entry in the given package."); return; } @@ -210,7 +213,7 @@ class AbUpdateInstaller extends UpdateInstaller { if (entry.getMethod() != ZipEntry.STORED) { Log.w(UpdateInstaller.TAG, "Invalid compression method."); notifyCallbackOnError( - InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID, + InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID, "Invalid compression method."); return false; } @@ -263,7 +266,7 @@ class AbUpdateInstaller extends UpdateInstaller { } else { mUpdateInstaller.notifyCallbackOnError( errorCodesMap.getOrDefault( - errorCode, InstallUpdateCallback.UPDATE_ERROR_UNKNOWN), + errorCode, InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN), errorStringsMap.getOrDefault(errorCode, UNKNOWN_ERROR + errorCode)); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NonAbUpdateInstaller.java b/services/devicepolicy/java/com/android/server/devicepolicy/NonAbUpdateInstaller.java index 5f1e92682ac1..582306c52806 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/NonAbUpdateInstaller.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/NonAbUpdateInstaller.java @@ -16,7 +16,7 @@ package com.android.server.devicepolicy; -import android.app.admin.DevicePolicyManager; +import android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback; import android.app.admin.StartInstallingUpdateCallback; import android.content.Context; import android.os.ParcelFileDescriptor; @@ -45,7 +45,7 @@ class NonAbUpdateInstaller extends UpdateInstaller { } catch (IOException e) { Log.w(TAG, "IO error while trying to install non AB update.", e); notifyCallbackOnError( - DevicePolicyManager.InstallUpdateCallback.UPDATE_ERROR_UNKNOWN, + InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN, Log.getStackTraceString(e)); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java b/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java index cf68ccf9f306..7148ed4523d1 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java @@ -18,7 +18,7 @@ package com.android.server.devicepolicy; import android.annotation.Nullable; import android.app.admin.DevicePolicyEventLogger; -import android.app.admin.DevicePolicyManager; +import android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback; import android.app.admin.StartInstallingUpdateCallback; import android.content.Context; import android.content.Intent; @@ -66,7 +66,7 @@ abstract class UpdateInstaller { mCopiedUpdateFile = null; if (!isBatteryLevelSufficient()) { notifyCallbackOnError( - DevicePolicyManager.InstallUpdateCallback.UPDATE_ERROR_BATTERY_LOW, + InstallSystemUpdateCallback.UPDATE_ERROR_BATTERY_LOW, "The battery level must be above " + mConstants.BATTERY_THRESHOLD_NOT_CHARGING + " while not charging or" + "above " + mConstants.BATTERY_THRESHOLD_CHARGING + " while charging"); @@ -76,7 +76,7 @@ abstract class UpdateInstaller { mCopiedUpdateFile = copyUpdateFileToDataOtaPackageDir(); if (mCopiedUpdateFile == null) { notifyCallbackOnError( - DevicePolicyManager.InstallUpdateCallback.UPDATE_ERROR_UNKNOWN, + InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN, "Error while copying file."); return; } @@ -111,7 +111,7 @@ abstract class UpdateInstaller { } catch (IOException e) { Log.w(TAG, "Failed to copy update file to OTA directory", e); notifyCallbackOnError( - DevicePolicyManager.InstallUpdateCallback.UPDATE_ERROR_UNKNOWN, + InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN, Log.getStackTraceString(e)); return null; } diff --git a/services/net/Android.bp b/services/net/Android.bp index 638ec95ec544..9946cc3db0e8 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -1,6 +1,9 @@ java_library_static { name: "services.net", srcs: ["java/**/*.java"], + static_libs: [ + "netd_aidl_interface-java", + ] } filegroup { diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/ByteRangeTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/ByteRangeTest.java index 8df08262c9fa..8df08262c9fa 100644 --- a/services/robotests/src/com/android/server/backup/encryption/chunking/ByteRangeTest.java +++ b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/ByteRangeTest.java diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/DiffScriptBackupWriterTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/DiffScriptBackupWriterTest.java index 2af6f2bee8ff..2af6f2bee8ff 100644 --- a/services/robotests/src/com/android/server/backup/encryption/chunking/DiffScriptBackupWriterTest.java +++ b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/DiffScriptBackupWriterTest.java diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriterTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriterTest.java index 73baf80a2c70..73baf80a2c70 100644 --- a/services/robotests/src/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriterTest.java +++ b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriterTest.java diff --git a/services/tests/servicestests/src/com/android/server/NetworkManagementInternalTest.java b/services/tests/servicestests/src/com/android/server/NetworkManagementInternalTest.java index 4bac200a22c6..ebbebcb02923 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkManagementInternalTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkManagementInternalTest.java @@ -16,12 +16,12 @@ package com.android.server; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_POWERSAVE; -import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY; -import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW; +import static android.net.INetd.FIREWALL_CHAIN_DOZABLE; +import static android.net.INetd.FIREWALL_CHAIN_POWERSAVE; +import static android.net.INetd.FIREWALL_CHAIN_STANDBY; +import static android.net.INetd.FIREWALL_RULE_ALLOW; +import static android.net.INetd.FIREWALL_RULE_DENY; import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT; -import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY; import static android.util.DebugUtils.valueToString; import static org.junit.Assert.assertEquals; diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java index 8d0365b534b5..95043810128a 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java @@ -32,6 +32,7 @@ import static org.mockito.Mockito.verify; import android.Manifest.permission; import android.app.ActivityManager; +import android.app.Person; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ShortcutInfo; @@ -890,6 +891,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { .setText("text") .setDisabledMessage("dismes") .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz")) + .setPerson(makePerson("person", "personKey", "personUri")) .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val")) .setRank(123) .setExtras(pb) @@ -901,6 +903,8 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { .setTitle("x") .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class)) .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val")) + .setPersons(list(makePerson("person1", "personKey1", "personUri1"), + makePerson("person2", "personKey2", "personUri2")).toArray(new Person[2])) .setRank(456) .build(); sorig2.setTimestamp(mInjectedCurrentTimeMillis); @@ -936,6 +940,10 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories()); assertEquals("action", si.getIntent().getAction()); assertEquals("val", si.getIntent().getStringExtra("key")); + assertEquals(1, si.getPersons().length); + assertEquals("person", si.getPersons()[0].getName()); + assertEquals("personKey", si.getPersons()[0].getKey()); + assertEquals("personUri", si.getPersons()[0].getUri()); assertEquals(0, si.getRank()); assertEquals(1, si.getExtras().getInt("k")); @@ -949,6 +957,8 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { // to test it. si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_10); assertEquals(1, si.getRank()); + assertEquals(2, si.getPersons().length); + assertEquals("personUri2", si.getPersons()[1].getUri()); dumpUserFile(USER_10); } @@ -1114,6 +1124,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { .setDisabledMessage("dismes") .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz")) .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val")) + .setPerson(makePerson("person", "personKey", "personUri")) .setRank(123) .setExtras(pb) .build(); @@ -1150,6 +1161,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories()); assertEquals("action", si.getIntent().getAction()); assertEquals("val", si.getIntent().getStringExtra("key")); + assertEquals(0, si.getPersons().length); // Don't backup the persons field assertEquals(0, si.getRank()); assertEquals(1, si.getExtras().getInt("k")); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java index a7520dcbcff9..2627ec762d7a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java @@ -30,6 +30,7 @@ import static org.junit.Assert.assertTrue; import android.graphics.Rect; import android.os.IBinder; +import android.platform.test.annotations.Presubmit; import android.view.Display; import android.view.IRemoteAnimationFinishedCallback; import android.view.IRemoteAnimationRunner; @@ -48,6 +49,7 @@ import org.junit.Test; * atest WmTests:AppChangeTransitionTests */ @SmallTest +@Presubmit public class AppChangeTransitionTests extends WindowTestsBase { private TaskStack mStack; diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java index cd1320986972..1dd72ec4fd71 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java @@ -51,7 +51,6 @@ import android.platform.test.annotations.Presubmit; import android.view.Surface; import android.view.WindowManager; -import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import org.junit.Before; @@ -142,7 +141,6 @@ public class AppWindowTokenTests extends WindowTestsBase { mToken.removeImmediately(); } - @FlakyTest(detail = "Promote to presubmit when shown to be stable.") @Test public void testLandscapeSeascapeRotationByApp() { // Some plumbing to get the service ready for rotation updates. @@ -303,7 +301,6 @@ public class AppWindowTokenTests extends WindowTestsBase { } @Test - @FlakyTest(detail = "Promote once confirmed non-flaky") public void testStuckExitingWindow() { final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW, "closingWindow"); @@ -346,7 +343,6 @@ public class AppWindowTokenTests extends WindowTestsBase { assertNoStartingWindow(mToken); } - @FlakyTest(detail = "Promote to presubmit when shown to be stable.") @Test public void testAddRemoveRace() { // There was once a race condition between adding and removing starting windows diff --git a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java index 3f83caea613a..1e02a12e83db 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java @@ -46,6 +46,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.platform.test.annotations.Presubmit; import android.util.Log; import android.view.IWindowManager; @@ -71,6 +72,7 @@ import java.util.concurrent.TimeUnit; * atest WmTests:AssistDataRequesterTest */ @MediumTest +@Presubmit public class AssistDataRequesterTest extends ActivityTestsBase { private static final String TAG = AssistDataRequesterTest.class.getSimpleName(); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java index 198e7ce63f52..b15e99aaa8c2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java @@ -60,6 +60,7 @@ import com.android.server.LocalServices; import com.android.server.UiThread; import com.android.server.policy.WindowManagerPolicy; import com.android.server.statusbar.StatusBarManagerInternal; +import com.android.server.wm.utils.WmDisplayCutout; import org.junit.After; import org.junit.Before; @@ -113,6 +114,7 @@ public class DisplayRotationTests { public static void setUpOnce() { sMockWm = mock(WindowManagerService.class); sMockWm.mPowerManagerInternal = mock(PowerManagerInternal.class); + sMockWm.mPolicy = mock(WindowManagerPolicy.class); } @Before @@ -807,6 +809,8 @@ public class DisplayRotationTests { mMockDisplayContent = mock(WindowTestUtils.TestDisplayContent.class); mMockDisplayContent.isDefaultDisplay = mIsDefaultDisplay; + when(mMockDisplayContent.calculateDisplayCutoutForRotation(anyInt())) + .thenReturn(WmDisplayCutout.NO_CUTOUT); mMockDisplayPolicy = mock(DisplayPolicy.class); diff --git a/services/tests/wmtests/src/com/android/server/wm/KeyguardDisableHandlerTest.java b/services/tests/wmtests/src/com/android/server/wm/KeyguardDisableHandlerTest.java index ce22788f7cb9..df26679dc1fb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/KeyguardDisableHandlerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/KeyguardDisableHandlerTest.java @@ -34,6 +34,7 @@ import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; import android.util.SparseBooleanArray; import com.android.server.wm.LockTaskController.LockTaskToken; @@ -43,6 +44,7 @@ import org.junit.Test; import java.lang.reflect.Constructor; +@Presubmit public class KeyguardDisableHandlerTest { private KeyguardDisableHandler mKeyguardDisable; diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index cc6a58a81635..a03d28b47057 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -40,7 +40,6 @@ import android.util.SparseBooleanArray; import android.view.IRecentsAnimationRunner; import android.view.SurfaceControl; -import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; @@ -112,7 +111,6 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { } } - @FlakyTest(bugId = 117117823) @Test public void testIncludedApps_expectTargetAndVisible() { mWm.setRecentsAnimationController(mController); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java index c595868db484..2377df406fbc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java @@ -34,10 +34,12 @@ import static org.junit.Assert.assertTrue; import android.app.IActivityTaskManager; import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; import android.util.DisplayMetrics; import android.util.Log; import android.view.Display; +import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import org.junit.Before; @@ -50,6 +52,8 @@ import org.junit.Test; * atest WmTests:TaskPositionerTests */ @SmallTest +@Presubmit +@FlakyTest public class TaskPositionerTests extends WindowTestsBase { private static final boolean DEBUGGING = false; diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java index bfb9193551f2..849772a4a26d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -156,7 +156,8 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { } @Override - public int interceptMotionBeforeQueueingNonInteractive(long whenNanos, int policyFlags) { + public int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos, + int policyFlags) { return 0; } diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/CoordinateTransformsTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/CoordinateTransformsTest.java index 649b785c992b..99ceb2011db3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/utils/CoordinateTransformsTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/utils/CoordinateTransformsTest.java @@ -31,6 +31,7 @@ import static org.junit.Assert.*; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.PointF; +import android.platform.test.annotations.Presubmit; import android.view.DisplayInfo; import org.junit.Before; @@ -42,6 +43,7 @@ import org.junit.rules.ErrorCollector; * Build/Install/Run: * atest WmTests:CoordinateTransformsTest */ +@Presubmit public class CoordinateTransformsTest { private static final int W = 200; diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index e99a289729a4..d5091680285b 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -291,6 +291,19 @@ public class TelecomManager { "android.telecom.extra.OUTGOING_CALL_EXTRAS"; /** + * An optional boolean extra on {@link android.content.Intent#ACTION_CALL_EMERGENCY} to tell + * whether the user's dial intent is emergency; this is required to specify when the dialed + * number is ambiguous, identified as both emergency number and any other non-emergency number; + * e.g. in some situation, 611 could be both an emergency number in a country and a + * non-emergency number of a carrier's customer service hotline. + * + * @hide + */ + @SystemApi + public static final String EXTRA_IS_USER_INTENT_EMERGENCY_CALL = + "android.telecom.extra.IS_USER_INTENT_EMERGENCY_CALL"; + + /** * @hide */ public static final String EXTRA_UNKNOWN_CALL_HANDLE = diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 0c63b60b030d..1b7228ae9a33 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -2424,6 +2424,14 @@ public class CarrierConfigManager { "5g_icon_configuration_string"; /** + * Support ASCII 7-BIT encoding for long SMS. This carrier config is used to enable + * this feature. + * @hide + */ + public static final String KEY_ASCII_7_BIT_SUPPORT_FOR_LONG_MESSAGE_BOOL = + "ascii_7_bit_support_for_long_message_bool"; + + /** * Controls RSRP threshold at which OpportunisticNetworkService will decide whether * the opportunistic network is good enough for internet data. */ @@ -2961,6 +2969,7 @@ public class CarrierConfigManager { sDefaults.putInt(KEY_CALL_WAITING_SERVICE_CLASS_INT, 1 /* SERVICE_CLASS_VOICE */); sDefaults.putString(KEY_5G_ICON_CONFIGURATION_STRING, "connected_mmwave:None,connected:5G,not_restricted:None,restricted:None"); + sDefaults.putBoolean(KEY_ASCII_7_BIT_SUPPORT_FOR_LONG_MESSAGE_BOOL, false); /* Default value is minimum RSRP level needed for SIGNAL_STRENGTH_GOOD */ sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSRP_INT, -108); /* Default value is minimum RSRP level needed for SIGNAL_STRENGTH_MODERATE */ diff --git a/telephony/java/android/telephony/CellIdentityNr.java b/telephony/java/android/telephony/CellIdentityNr.java index 6b1b84cd3458..856f08107fd7 100644 --- a/telephony/java/android/telephony/CellIdentityNr.java +++ b/telephony/java/android/telephony/CellIdentityNr.java @@ -30,6 +30,7 @@ public final class CellIdentityNr extends CellIdentity { private final int mNrArfcn; private final int mPci; private final int mTac; + private final long mNci; /** * @@ -44,11 +45,12 @@ public final class CellIdentityNr extends CellIdentity { * @hide */ public CellIdentityNr(int pci, int tac, int nrArfcn, String mccStr, String mncStr, - String alphal, String alphas) { + long nci, String alphal, String alphas) { super(TAG, CellInfo.TYPE_NR, mccStr, mncStr, alphal, alphas); mPci = pci; mTac = tac; mNrArfcn = nrArfcn; + mNci = nci; } /** @@ -62,7 +64,7 @@ public final class CellIdentityNr extends CellIdentity { @Override public int hashCode() { - return Objects.hash(super.hashCode(), mPci, mTac, mNrArfcn); + return Objects.hash(super.hashCode(), mPci, mTac, mNrArfcn, mNci); } @Override @@ -72,7 +74,17 @@ public final class CellIdentityNr extends CellIdentity { } CellIdentityNr o = (CellIdentityNr) other; - return super.equals(o) && mPci == o.mPci && mTac == o.mTac && mNrArfcn == o.mNrArfcn; + return super.equals(o) && mPci == o.mPci && mTac == o.mTac && mNrArfcn == o.mNrArfcn + && mNci == o.mNci; + } + + /** + * Get the NR Cell Identity. + * + * @return The NR Cell Identity in range [0, 68719476735] or Long.MAX_VALUE if unknown. + */ + public long getNci() { + return mNci; } /** @@ -122,6 +134,7 @@ public final class CellIdentityNr extends CellIdentity { .append(" mNrArfcn = ").append(mNrArfcn) .append(" mMcc = ").append(mMccStr) .append(" mMnc = ").append(mMncStr) + .append(" mNci = ").append(mNci) .append(" mAlphaLong = ").append(mAlphaLong) .append(" mAlphaShort = ").append(mAlphaShort) .append(" }") @@ -134,6 +147,7 @@ public final class CellIdentityNr extends CellIdentity { dest.writeInt(mPci); dest.writeInt(mTac); dest.writeInt(mNrArfcn); + dest.writeLong(mNci); } /** Construct from Parcel, type has already been processed */ @@ -142,6 +156,7 @@ public final class CellIdentityNr extends CellIdentity { mPci = in.readInt(); mTac = in.readInt(); mNrArfcn = in.readInt(); + mNci = in.readLong(); } /** Implement the Parcelable interface */ diff --git a/telephony/java/android/telephony/CellIdentityTdscdma.java b/telephony/java/android/telephony/CellIdentityTdscdma.java index 38143335dbf1..dba437a3a007 100644 --- a/telephony/java/android/telephony/CellIdentityTdscdma.java +++ b/telephony/java/android/telephony/CellIdentityTdscdma.java @@ -141,6 +141,14 @@ public final class CellIdentityTdscdma extends CellIdentity { return mCpid; } + /** + * @return 16-bit UMTS Absolute RF Channel Number, + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. + */ + public int getUarfcn() { + return mUarfcn; + } + /** @hide */ @Override public int getChannelNumber() { diff --git a/telephony/java/android/telephony/LocationAccessPolicy.java b/telephony/java/android/telephony/LocationAccessPolicy.java index 53d69f447a56..24db438580c9 100644 --- a/telephony/java/android/telephony/LocationAccessPolicy.java +++ b/telephony/java/android/telephony/LocationAccessPolicy.java @@ -26,11 +26,12 @@ import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.location.LocationManager; import android.os.Binder; +import android.os.Build; import android.os.Process; -import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; +import android.widget.Toast; import java.util.List; @@ -41,61 +42,236 @@ import java.util.List; public final class LocationAccessPolicy { private static final String TAG = "LocationAccessPolicy"; private static final boolean DBG = false; + public static final int MAX_SDK_FOR_ANY_ENFORCEMENT = Build.VERSION_CODES.P; - /** - * API to determine if the caller has permissions to get cell location. - * - * @param pkgName Package name of the application requesting access - * @param uid The uid of the package - * @param pid The pid of the package - * @param throwOnDeniedPermission Whether to throw if the location permission is denied. - * @return boolean true or false if permissions is granted - */ - public static boolean canAccessCellLocation(@NonNull Context context, @NonNull String pkgName, - int uid, int pid, boolean throwOnDeniedPermission) throws SecurityException { - Trace.beginSection("TelephonyLocationCheck"); - try { - // Always allow the phone process and system server to access location. This avoid - // breaking legacy code that rely on public-facing APIs to access cell location, and - // it doesn't create an info leak risk because the cell location is stored in the phone - // process anyway, and the system server already has location access. - if (uid == Process.PHONE_UID || uid == Process.SYSTEM_UID || uid == Process.ROOT_UID) { - return true; + public enum LocationPermissionResult { + ALLOWED, + /** + * Indicates that the denial is due to a transient device state + * (e.g. app-ops, location master switch) + */ + DENIED_SOFT, + /** + * Indicates that the denial is due to a misconfigured app (e.g. missing entry in manifest) + */ + DENIED_HARD, + } + + public static class LocationPermissionQuery { + public final String callingPackage; + public final int callingUid; + public final int callingPid; + public final int minSdkVersionForCoarse; + public final int minSdkVersionForFine; + public final String method; + + private LocationPermissionQuery(String callingPackage, int callingUid, int callingPid, + int minSdkVersionForCoarse, int minSdkVersionForFine, String method) { + this.callingPackage = callingPackage; + this.callingUid = callingUid; + this.callingPid = callingPid; + this.minSdkVersionForCoarse = minSdkVersionForCoarse; + this.minSdkVersionForFine = minSdkVersionForFine; + this.method = method; + } + + public static class Builder { + private String mCallingPackage; + private int mCallingUid; + private int mCallingPid; + private int mMinSdkVersionForCoarse = Integer.MAX_VALUE; + private int mMinSdkVersionForFine = Integer.MAX_VALUE; + private String mMethod; + + /** + * Mandatory parameter, used for performing permission checks. + */ + public Builder setCallingPackage(String callingPackage) { + mCallingPackage = callingPackage; + return this; } - // We always require the location permission and also require the - // location mode to be on for non-legacy apps. Legacy apps are - // required to be in the foreground to at least mitigate the case - // where a legacy app the user is not using tracks their location. - // Granting ACCESS_FINE_LOCATION to an app automatically grants it - // ACCESS_COARSE_LOCATION. - if (throwOnDeniedPermission) { - context.enforcePermission(Manifest.permission.ACCESS_COARSE_LOCATION, - pid, uid, "canAccessCellLocation"); - } else if (context.checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION, - pid, uid) == PackageManager.PERMISSION_DENIED) { - if (DBG) Log.w(TAG, "Permission checked failed (" + pid + "," + uid + ")"); - return false; + /** + * Mandatory parameter, used for performing permission checks. + */ + public Builder setCallingUid(int callingUid) { + mCallingUid = callingUid; + return this; } - final int opCode = AppOpsManager.permissionToOpCode( - Manifest.permission.ACCESS_COARSE_LOCATION); - if (opCode != AppOpsManager.OP_NONE && context.getSystemService(AppOpsManager.class) - .noteOpNoThrow(opCode, uid, pkgName) != AppOpsManager.MODE_ALLOWED) { - if (DBG) Log.w(TAG, "AppOp check failed (" + uid + "," + pkgName + ")"); - return false; + + /** + * Mandatory parameter, used for performing permission checks. + */ + public Builder setCallingPid(int callingPid) { + mCallingPid = callingPid; + return this; } - if (!isLocationModeEnabled(context, UserHandle.getUserId(uid))) { - if (DBG) Log.w(TAG, "Location disabled, failed, (" + uid + ")"); - return false; + + /** + * Apps that target at least this sdk version will be checked for coarse location + * permission. Defaults to INT_MAX (which means don't check) + */ + public Builder setMinSdkVersionForCoarse( + int minSdkVersionForCoarse) { + mMinSdkVersionForCoarse = minSdkVersionForCoarse; + return this; } - // If the user or profile is current, permission is granted. - // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission. - return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context); - } finally { - Trace.endSection(); + + /** + * Apps that target at least this sdk version will be checked for fine location + * permission. Defaults to INT_MAX (which means don't check) + */ + public Builder setMinSdkVersionForFine( + int minSdkVersionForFine) { + mMinSdkVersionForFine = minSdkVersionForFine; + return this; + } + + /** + * Optional, for logging purposes only. + */ + public Builder setMethod(String method) { + mMethod = method; + return this; + } + + public LocationPermissionQuery build() { + return new LocationPermissionQuery(mCallingPackage, mCallingUid, + mCallingPid, mMinSdkVersionForCoarse, mMinSdkVersionForFine, mMethod); + } + } + } + + private static void logError(Context context, String errorMsg) { + Log.e(TAG, errorMsg); + try { + if (Build.IS_DEBUGGABLE) { + Toast.makeText(context, errorMsg, Toast.LENGTH_SHORT).show(); + } + } catch (Throwable t) { + // whatever, not important + } + } + + private static LocationPermissionResult appOpsModeToPermissionResult(int appOpsMode) { + switch (appOpsMode) { + case AppOpsManager.MODE_ALLOWED: + return LocationPermissionResult.ALLOWED; + case AppOpsManager.MODE_ERRORED: + return LocationPermissionResult.DENIED_HARD; + default: + return LocationPermissionResult.DENIED_SOFT; } } + private static LocationPermissionResult checkAppLocationPermissionHelper(Context context, + LocationPermissionQuery query, String permissionToCheck) { + String locationTypeForLog = + Manifest.permission.ACCESS_FINE_LOCATION.equals(permissionToCheck) + ? "fine" : "coarse"; + + // Do the app-ops and the manifest check without any of the allow-overrides first. + boolean hasManifestPermission = checkManifestPermission(context, query.callingPid, + query.callingUid, permissionToCheck); + + int appOpMode = context.getSystemService(AppOpsManager.class) + .noteOpNoThrow(AppOpsManager.permissionToOpCode(permissionToCheck), + query.callingUid, query.callingPackage); + + if (hasManifestPermission && appOpMode == AppOpsManager.MODE_ALLOWED) { + // If the app did everything right, return without logging. + return LocationPermissionResult.ALLOWED; + } + + // If the app has the manifest permission but not the app-op permission, it means that + // it's aware of the requirement and the user denied permission explicitly. If we see + // this, don't let any of the overrides happen. + if (hasManifestPermission) { + Log.i(TAG, query.callingPackage + " is aware of " + locationTypeForLog + " but the" + + " app-ops permission is specifically denied."); + return appOpsModeToPermissionResult(appOpMode); + } + + int minSdkVersion = Manifest.permission.ACCESS_FINE_LOCATION.equals(permissionToCheck) + ? query.minSdkVersionForFine : query.minSdkVersionForCoarse; + + // If the app fails for some reason, see if it should be allowed to proceed. + if (minSdkVersion > MAX_SDK_FOR_ANY_ENFORCEMENT) { + String errorMsg = "Allowing " + query.callingPackage + " " + locationTypeForLog + + " because we're not enforcing API " + query.minSdkVersionForFine + " yet." + + " Please fix this app because it will break in the future. Called from " + + query.method; + logError(context, errorMsg); + return null; + } else if (!isAppAtLeastSdkVersion(context, query.callingPackage, minSdkVersion)) { + String errorMsg = "Allowing " + query.callingPackage + " " + locationTypeForLog + + " because it doesn't target API " + query.minSdkVersionForFine + " yet." + + " Please fix this app. Called from " + query.method; + logError(context, errorMsg); + return null; + } else { + // If we're not allowing it due to the above two conditions, this means that the app + // did not declare the permission in their manifest. + return LocationPermissionResult.DENIED_HARD; + } + } + + public static LocationPermissionResult checkLocationPermission( + Context context, LocationPermissionQuery query) { + // Always allow the phone process and system server to access location. This avoid + // breaking legacy code that rely on public-facing APIs to access cell location, and + // it doesn't create an info leak risk because the cell location is stored in the phone + // process anyway, and the system server already has location access. + if (query.callingUid == Process.PHONE_UID || query.callingUid == Process.SYSTEM_UID + || query.callingUid == Process.ROOT_UID) { + return LocationPermissionResult.ALLOWED; + } + + // Check the system-wide requirements. If the location master switch is off or + // the app's profile isn't in foreground, return a soft denial. + if (!checkSystemLocationAccess(context, query.callingUid, query.callingPid)) { + return LocationPermissionResult.DENIED_SOFT; + } + + // Do the check for fine, then for coarse. + if (query.minSdkVersionForFine < Integer.MAX_VALUE) { + LocationPermissionResult resultForFine = checkAppLocationPermissionHelper( + context, query, Manifest.permission.ACCESS_FINE_LOCATION); + if (resultForFine != null) { + return resultForFine; + } + } + + if (query.minSdkVersionForCoarse < Integer.MAX_VALUE) { + LocationPermissionResult resultForCoarse = checkAppLocationPermissionHelper( + context, query, Manifest.permission.ACCESS_COARSE_LOCATION); + if (resultForCoarse != null) { + return resultForCoarse; + } + } + + // At this point, we're out of location checks to do. If the app bypassed all the previous + // ones due to the SDK grandfathering schemes, allow it access. + return LocationPermissionResult.ALLOWED; + } + + + private static boolean checkManifestPermission(Context context, int pid, int uid, + String permissionToCheck) { + return context.checkPermission(permissionToCheck, pid, uid) + == PackageManager.PERMISSION_GRANTED; + } + + private static boolean checkSystemLocationAccess(@NonNull Context context, int uid, int pid) { + if (!isLocationModeEnabled(context, UserHandle.getUserId(uid))) { + if (DBG) Log.w(TAG, "Location disabled, failed, (" + uid + ")"); + return false; + } + // If the user or profile is current, permission is granted. + // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission. + return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context, uid, pid); + } + private static boolean isLocationModeEnabled(@NonNull Context context, @UserIdInt int userId) { LocationManager locationManager = context.getSystemService(LocationManager.class); if (locationManager == null) { @@ -105,10 +281,10 @@ public final class LocationAccessPolicy { return locationManager.isLocationEnabledForUser(UserHandle.of(userId)); } - private static boolean checkInteractAcrossUsersFull(@NonNull Context context) { - return context.checkCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) - == PackageManager.PERMISSION_GRANTED; + private static boolean checkInteractAcrossUsersFull( + @NonNull Context context, int pid, int uid) { + return checkManifestPermission(context, pid, uid, + Manifest.permission.INTERACT_ACROSS_USERS_FULL); } private static boolean isCurrentProfile(@NonNull Context context, int uid) { @@ -132,4 +308,18 @@ public final class LocationAccessPolicy { Binder.restoreCallingIdentity(token); } } -} + + private static boolean isAppAtLeastSdkVersion(Context context, String pkgName, int sdkVersion) { + try { + if (context.getPackageManager().getApplicationInfo(pkgName, 0).targetSdkVersion + >= sdkVersion) { + return true; + } + } catch (PackageManager.NameNotFoundException e) { + // In case of exception, assume known app (more strict checking) + // Note: This case will never happen since checkPackage is + // called to verify validity before checking app's version. + } + return false; + } +}
\ No newline at end of file diff --git a/telephony/java/android/telephony/NetworkRegistrationState.java b/telephony/java/android/telephony/NetworkRegistrationState.java index ceb76b57ae0c..6e6d59e62148 100644 --- a/telephony/java/android/telephony/NetworkRegistrationState.java +++ b/telephony/java/android/telephony/NetworkRegistrationState.java @@ -27,6 +27,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Objects; +import java.util.stream.Collectors; /** * Description of a mobile network registration state @@ -151,7 +152,7 @@ public class NetworkRegistrationState implements Parcelable { private final int[] mAvailableServices; @Nullable - private final CellIdentity mCellIdentity; + private CellIdentity mCellIdentity; @Nullable private VoiceSpecificRegistrationStates mVoiceSpecificStates; @@ -360,7 +361,34 @@ public class NetworkRegistrationState implements Parcelable { return 0; } - private static String regStateToString(int regState) { + /** + * Convert service type to string + * + * @hide + * + * @param serviceType The service type + * @return The service type in string format + */ + public static String serviceTypeToString(@ServiceType int serviceType) { + switch (serviceType) { + case SERVICE_TYPE_VOICE: return "VOICE"; + case SERVICE_TYPE_DATA: return "DATA"; + case SERVICE_TYPE_SMS: return "SMS"; + case SERVICE_TYPE_VIDEO: return "VIDEO"; + case SERVICE_TYPE_EMERGENCY: return "EMERGENCY"; + } + return "Unknown service type " + serviceType; + } + + /** + * Convert registration state to string + * + * @hide + * + * @param regState The registration state + * @return The reg state in string + */ + public static String regStateToString(@RegState int regState) { switch (regState) { case REG_STATE_NOT_REG_NOT_SEARCHING: return "NOT_REG_NOT_SEARCHING"; case REG_STATE_HOME: return "HOME"; @@ -389,14 +417,17 @@ public class NetworkRegistrationState implements Parcelable { public String toString() { return new StringBuilder("NetworkRegistrationState{") .append(" domain=").append((mDomain == DOMAIN_CS) ? "CS" : "PS") - .append("transportType=").append(mTransportType) + .append(" transportType=").append(TransportType.toString(mTransportType)) .append(" regState=").append(regStateToString(mRegState)) - .append(" roamingType=").append(mRoamingType) + .append(" roamingType=").append(ServiceState.roamingTypeToString(mRoamingType)) .append(" accessNetworkTechnology=") .append(TelephonyManager.getNetworkTypeName(mAccessNetworkTechnology)) .append(" rejectCause=").append(mRejectCause) .append(" emergencyEnabled=").append(mEmergencyOnly) - .append(" supportedServices=").append(mAvailableServices) + .append(" availableServices=").append("[" + (mAvailableServices != null + ? Arrays.stream(mAvailableServices) + .mapToObj(type -> serviceTypeToString(type)) + .collect(Collectors.joining(",")) : null) + "]") .append(" cellIdentity=").append(mCellIdentity) .append(" voiceSpecificStates=").append(mVoiceSpecificStates) .append(" dataSpecificStates=").append(mDataSpecificStates) @@ -490,4 +521,22 @@ public class NetworkRegistrationState implements Parcelable { return new NetworkRegistrationState[size]; } }; + + /** + * @hide + */ + public NetworkRegistrationState sanitizeLocationInfo() { + NetworkRegistrationState result = copy(); + result.mCellIdentity = null; + return result; + } + + private NetworkRegistrationState copy() { + Parcel p = Parcel.obtain(); + this.writeToParcel(p, 0); + p.setDataPosition(0); + NetworkRegistrationState result = new NetworkRegistrationState(p); + p.recycle(); + return result; + } } diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java index 2c9ba1dfff7b..3ce646cb400b 100644 --- a/telephony/java/android/telephony/PhoneStateListener.java +++ b/telephony/java/android/telephony/PhoneStateListener.java @@ -46,7 +46,7 @@ import java.util.concurrent.Executor; * Override the methods for the state that you wish to receive updates for, and * pass your PhoneStateListener object, along with bitwise-or of the LISTEN_ * flags to {@link TelephonyManager#listen TelephonyManager.listen()}. Methods are - * called when the state changes, os well as once on initial registration. + * called when the state changes, as well as once on initial registration. * <p> * Note that access to some telephony information is * permission-protected. Your application won't receive updates for protected diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 33178766f3a3..a1aee6d8217f 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -36,6 +36,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; /** * Contains phone state and service related information. @@ -887,6 +888,24 @@ public class ServiceState implements Parcelable { } /** + * Convert roaming type to string + * + * @param roamingType roaming type + * @return The roaming type in string format + * + * @hide + */ + public static String roamingTypeToString(@RoamingType int roamingType) { + switch (roamingType) { + case ROAMING_TYPE_NOT_ROAMING: return "NOT_ROAMING"; + case ROAMING_TYPE_UNKNOWN: return "UNKNOWN"; + case ROAMING_TYPE_DOMESTIC: return "DOMESTIC"; + case ROAMING_TYPE_INTERNATIONAL: return "INTERNATIONAL"; + } + return "Unknown roaming type " + roamingType; + } + + /** * Convert radio technology to String * * @param rt radioTechnology @@ -1867,4 +1886,29 @@ public class ServiceState implements Parcelable { ? range1 : range2; } + + /** + * Returns a copy of self with location-identifying information removed. + * Always clears the NetworkRegistrationState's CellIdentity fields, but if removeCoarseLocation + * is true, clears other info as well. + * @hide + */ + public ServiceState sanitizeLocationInfo(boolean removeCoarseLocation) { + ServiceState state = new ServiceState(this); + if (state.mNetworkRegistrationStates != null) { + state.mNetworkRegistrationStates = state.mNetworkRegistrationStates.stream() + .map(NetworkRegistrationState::sanitizeLocationInfo) + .collect(Collectors.toList()); + } + if (!removeCoarseLocation) return state; + + state.mDataOperatorAlphaLong = null; + state.mDataOperatorAlphaShort = null; + state.mDataOperatorNumeric = null; + state.mVoiceOperatorAlphaLong = null; + state.mVoiceOperatorAlphaShort = null; + state.mVoiceOperatorNumeric = null; + + return state; + } } diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 313146d5538b..836a50bf05c7 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -59,6 +59,7 @@ import android.util.DisplayMetrics; import android.util.Log; import com.android.internal.telephony.IOnSubscriptionsChangedListener; +import com.android.internal.telephony.ISetOpportunisticDataCallback; import com.android.internal.telephony.ISub; import com.android.internal.telephony.ITelephonyRegistry; import com.android.internal.telephony.PhoneConstants; @@ -72,6 +73,7 @@ import java.util.List; import java.util.Locale; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import java.util.stream.Collectors; /** @@ -864,7 +866,8 @@ public class SubscriptionManager { } /** - * Callback invoked when there is any change to any SubscriptionInfo. Typically + * Callback invoked when there is any change to any SubscriptionInfo, as well as once on + * registering for changes with {@link #addOnSubscriptionsChangedListener}. Typically * this method would invoke {@link #getActiveSubscriptionInfoList} */ public void onSubscriptionsChanged() { @@ -916,7 +919,9 @@ public class SubscriptionManager { /** * Register for changes to the list of active {@link SubscriptionInfo} records or to the * individual records themselves. When a change occurs the onSubscriptionsChanged method of - * the listener will be invoked immediately if there has been a notification. + * the listener will be invoked immediately if there has been a notification. The + * onSubscriptionChanged method will also be triggered once initially when calling this + * function. * * @param listener an instance of {@link OnSubscriptionsChangedListener} with * onSubscriptionsChanged overridden. @@ -1859,7 +1864,7 @@ public class SubscriptionManager { iSub.setDefaultSmsSubId(subscriptionId); } } catch (RemoteException ex) { - // ignore it + ex.rethrowFromSystemServer(); } } @@ -2573,17 +2578,35 @@ public class SubscriptionManager { * {@link SubscriptionManager#DEFAULT_SUBSCRIPTION_ID}, it means * it's unset and {@link SubscriptionManager#getDefaultDataSubscriptionId()} * is used to determine which modem is preferred. + * @param needValidation whether validation is needed before switch happens. + * @param executor The executor of where the callback will execute. + * @param callback Callback will be triggered once it succeeds or failed. + * See {@link TelephonyManager.SetOpportunisticSubscriptionResult} + * for more details. Pass null if don't care about the result. + * * @hide * */ + @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - public void setPreferredDataSubscriptionId(int subId) { + public void setPreferredDataSubscriptionId(int subId, boolean needValidation, + @NonNull @CallbackExecutor Executor executor, Consumer<Integer> callback) { if (VDBG) logd("[setPreferredDataSubscriptionId]+ subId:" + subId); try { ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); - if (iSub != null) { - iSub.setPreferredDataSubscriptionId(subId); - } + if (iSub == null) return; + + ISetOpportunisticDataCallback callbackStub = new ISetOpportunisticDataCallback.Stub() { + @Override + public void onComplete(int result) { + Binder.withCleanCallingIdentity(() -> executor.execute(() -> { + if (callback != null) { + callback.accept(result); + } + })); + } + }; + iSub.setPreferredDataSubscriptionId(subId, needValidation, callbackStub); } catch (RemoteException ex) { // ignore it } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 2a03924fd58c..ced4f4ab6573 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -229,10 +229,19 @@ public class TelephonyManager { public static final int SRVCC_STATE_HANDOVER_CANCELED = 3; /** - * An invalid UICC card identifier. See {@link #getCardIdForDefaultEuicc()} and - * {@link UiccCardInfo#getCardId()}. + * A UICC card identifier used if the device does not support the operation. + * For example, {@link #getCardIdForDefaultEuicc()} returns this value if the device has no + * eUICC, or the eUICC cannot be read. */ - public static final int INVALID_CARD_ID = -1; + public static final int UNSUPPORTED_CARD_ID = -1; + + /** + * A UICC card identifier used before the UICC card is loaded. See + * {@link #getCardIdForDefaultEuicc()} and {@link UiccCardInfo#getCardId()}. + * <p> + * Note that once the UICC card is loaded, the card ID may become {@link #UNSUPPORTED_CARD_ID}. + */ + public static final int UNINITIALIZED_CARD_ID = -2; /** @hide */ @Retention(RetentionPolicy.SOURCE) @@ -1689,10 +1698,7 @@ public class TelephonyManager { * @deprecated use {@link #getAllCellInfo} instead, which returns a superset of this API. */ @Deprecated - @RequiresPermission(anyOf = { - android.Manifest.permission.ACCESS_COARSE_LOCATION, - android.Manifest.permission.ACCESS_FINE_LOCATION - }) + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public CellLocation getCellLocation() { try { ITelephony telephony = getITelephony(); @@ -3178,24 +3184,25 @@ public class TelephonyManager { } /** - * Get the card ID of the default eUICC card. If there is no eUICC, returns - * {@link #INVALID_CARD_ID}. + * Get the card ID of the default eUICC card. If the eUICCs have not yet been loaded, returns + * {@link #UNINITIALIZED_CARD_ID}. If there is no eUICC or the device does not support card IDs + * for eUICCs, returns {@link #UNSUPPORTED_CARD_ID}. * * <p>The card ID is a unique identifier associated with a UICC or eUICC card. Card IDs are * unique to a device, and always refer to the same UICC or eUICC card unless the device goes * through a factory reset. * - * @return card ID of the default eUICC card. + * @return card ID of the default eUICC card, if loaded. */ public int getCardIdForDefaultEuicc() { try { ITelephony telephony = getITelephony(); if (telephony == null) { - return INVALID_CARD_ID; + return UNINITIALIZED_CARD_ID; } return telephony.getCardIdForDefaultEuicc(mSubId, mContext.getOpPackageName()); } catch (RemoteException e) { - return INVALID_CARD_ID; + return UNINITIALIZED_CARD_ID; } } @@ -4941,7 +4948,7 @@ public class TelephonyManager { * @return List of {@link android.telephony.CellInfo}; null if cell * information is unavailable. */ - @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public List<CellInfo> getAllCellInfo() { try { ITelephony telephony = getITelephony(); @@ -5018,7 +5025,7 @@ public class TelephonyManager { * @param executor the executor on which callback will be invoked. * @param callback a callback to receive CellInfo. */ - @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) + @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestCellInfoUpdate( @NonNull @CallbackExecutor Executor executor, @NonNull CellInfoCallback callback) { try { @@ -5057,7 +5064,7 @@ public class TelephonyManager { * @hide */ @SystemApi - @RequiresPermission(allOf = {android.Manifest.permission.ACCESS_COARSE_LOCATION, + @RequiresPermission(allOf = {android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull WorkSource workSource, @NonNull @CallbackExecutor Executor executor, @NonNull CellInfoCallback callback) { @@ -6647,9 +6654,10 @@ public class TelephonyManager { * * <p> Note that this scan can take a long time (sometimes minutes) to happen. * - * <p>Requires Permission: + * <p>Requires Permissions: * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or that the calling app has carrier * privileges (see {@link #hasCarrierPrivileges}) + * and {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}. * * @return {@link CellNetworkScanResult} with the status * {@link CellNetworkScanResult#STATUS_SUCCESS} and a list of @@ -6658,12 +6666,15 @@ public class TelephonyManager { * * @hide */ - @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + @RequiresPermission(allOf = { + android.Manifest.permission.MODIFY_PHONE_STATE, + Manifest.permission.ACCESS_COARSE_LOCATION + }) public CellNetworkScanResult getAvailableNetworks() { try { ITelephony telephony = getITelephony(); if (telephony != null) { - return telephony.getCellNetworkScanResults(getSubId()); + return telephony.getCellNetworkScanResults(getSubId(), getOpPackageName()); } } catch (RemoteException ex) { Rlog.e(TAG, "getAvailableNetworks RemoteException", ex); @@ -6682,7 +6693,8 @@ public class TelephonyManager { * * <p>Requires Permission: * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling - * app has carrier privileges (see {@link #hasCarrierPrivileges}). + * app has carrier privileges (see {@link #hasCarrierPrivileges}) + * and {@link android.Manifest.permission#ACCESS_FINE_LOCATION}. * * @param request Contains all the RAT with bands/channels that need to be scanned. * @param executor The executor through which the callback should be invoked. Since the scan @@ -6693,7 +6705,10 @@ public class TelephonyManager { * @return A NetworkScan obj which contains a callback which can be used to stop the scan. */ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges - @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + @RequiresPermission(allOf = { + android.Manifest.permission.MODIFY_PHONE_STATE, + Manifest.permission.ACCESS_FINE_LOCATION + }) public NetworkScan requestNetworkScan( NetworkScanRequest request, Executor executor, TelephonyScanManager.NetworkScanCallback callback) { @@ -6702,7 +6717,8 @@ public class TelephonyManager { mTelephonyScanManager = new TelephonyScanManager(); } } - return mTelephonyScanManager.requestNetworkScan(getSubId(), request, executor, callback); + return mTelephonyScanManager.requestNetworkScan(getSubId(), request, executor, callback, + getOpPackageName()); } /** @@ -6712,7 +6728,10 @@ public class TelephonyManager { * @removed */ @Deprecated - @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + @RequiresPermission(allOf = { + android.Manifest.permission.MODIFY_PHONE_STATE, + Manifest.permission.ACCESS_FINE_LOCATION + }) public NetworkScan requestNetworkScan( NetworkScanRequest request, TelephonyScanManager.NetworkScanCallback callback) { return requestNetworkScan(request, AsyncTask.SERIAL_EXECUTOR, callback); @@ -8713,10 +8732,14 @@ public class TelephonyManager { * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()} * * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} - * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}) + * and {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}. */ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges - @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + @RequiresPermission(allOf = { + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_COARSE_LOCATION + }) public ServiceState getServiceState() { return getServiceStateForSubscriber(getSubId()); } @@ -10094,6 +10117,29 @@ public class TelephonyManager { return false; } + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"SET_OPPORTUNISTIC_SUB"}, value = { + SET_OPPORTUNISTIC_SUB_SUCCESS, + SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED, + SET_OPPORTUNISTIC_SUB_INVALID_PARAMETER}) + public @interface SetOpportunisticSubscriptionResult {} + + /** + * No error. Operation succeeded. + */ + public static final int SET_OPPORTUNISTIC_SUB_SUCCESS = 0; + + /** + * Validation failed when trying to switch to preferred subscription. + */ + public static final int SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED = 1; + + /** + * The parameter passed in is invalid. + */ + public static final int SET_OPPORTUNISTIC_SUB_INVALID_PARAMETER = 2; + /** * Set preferred opportunistic data subscription id. * diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java index 96ff33255b53..91f74b867fa0 100644 --- a/telephony/java/android/telephony/TelephonyScanManager.java +++ b/telephony/java/android/telephony/TelephonyScanManager.java @@ -29,14 +29,14 @@ import android.os.Messenger; import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; -import android.util.Log; import android.util.SparseArray; + +import com.android.internal.telephony.ITelephony; + import java.util.Arrays; import java.util.List; import java.util.concurrent.Executor; -import com.android.internal.telephony.ITelephony; - /** * Manages the radio access network scan requests and callbacks. */ @@ -183,6 +183,7 @@ public final class TelephonyScanManager { * * <p> * Requires Permission: + * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} and * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} * Or the calling app has carrier privileges. @see #hasCarrierPrivileges * @@ -192,11 +193,13 @@ public final class TelephonyScanManager { * @hide */ public NetworkScan requestNetworkScan(int subId, - NetworkScanRequest request, Executor executor, NetworkScanCallback callback) { + NetworkScanRequest request, Executor executor, NetworkScanCallback callback, + String callingPackage) { try { ITelephony telephony = getITelephony(); if (telephony != null) { - int scanId = telephony.requestNetworkScan(subId, request, mMessenger, new Binder()); + int scanId = telephony.requestNetworkScan( + subId, request, mMessenger, new Binder(), callingPackage); saveScanInfo(scanId, request, executor, callback); return new NetworkScan(scanId, subId); } diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index 1bbf9f4c4d25..ad343498f9e1 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -460,7 +460,7 @@ public class EuiccManager { */ @Nullable public String getEid() { - if (!refreshCardIdIfInvalid()) { + if (!refreshCardIdIfUninitialized()) { return null; } try { @@ -483,7 +483,7 @@ public class EuiccManager { @SystemApi @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public int getOtaStatus() { - if (!refreshCardIdIfInvalid()) { + if (!refreshCardIdIfUninitialized()) { return EUICC_OTA_STATUS_UNAVAILABLE; } try { @@ -518,7 +518,7 @@ public class EuiccManager { @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void downloadSubscription(DownloadableSubscription subscription, boolean switchAfterDownload, PendingIntent callbackIntent) { - if (!refreshCardIdIfInvalid()) { + if (!refreshCardIdIfUninitialized()) { sendUnavailableError(callbackIntent); return; } @@ -580,7 +580,7 @@ public class EuiccManager { @SystemApi @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void continueOperation(Intent resolutionIntent, Bundle resolutionExtras) { - if (!refreshCardIdIfInvalid()) { + if (!refreshCardIdIfUninitialized()) { PendingIntent callbackIntent = resolutionIntent.getParcelableExtra( EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_CALLBACK_INTENT); @@ -617,7 +617,7 @@ public class EuiccManager { @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDownloadableSubscriptionMetadata( DownloadableSubscription subscription, PendingIntent callbackIntent) { - if (!refreshCardIdIfInvalid()) { + if (!refreshCardIdIfUninitialized()) { sendUnavailableError(callbackIntent); return; } @@ -647,7 +647,7 @@ public class EuiccManager { @SystemApi @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDefaultDownloadableSubscriptionList(PendingIntent callbackIntent) { - if (!refreshCardIdIfInvalid()) { + if (!refreshCardIdIfUninitialized()) { sendUnavailableError(callbackIntent); return; } @@ -666,7 +666,7 @@ public class EuiccManager { */ @Nullable public EuiccInfo getEuiccInfo() { - if (!refreshCardIdIfInvalid()) { + if (!refreshCardIdIfUninitialized()) { return null; } try { @@ -691,7 +691,7 @@ public class EuiccManager { */ @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void deleteSubscription(int subscriptionId, PendingIntent callbackIntent) { - if (!refreshCardIdIfInvalid()) { + if (!refreshCardIdIfUninitialized()) { sendUnavailableError(callbackIntent); return; } @@ -731,7 +731,7 @@ public class EuiccManager { */ @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void switchToSubscription(int subscriptionId, PendingIntent callbackIntent) { - if (!refreshCardIdIfInvalid()) { + if (!refreshCardIdIfUninitialized()) { sendUnavailableError(callbackIntent); return; } @@ -757,7 +757,7 @@ public class EuiccManager { @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void updateSubscriptionNickname( int subscriptionId, String nickname, PendingIntent callbackIntent) { - if (!refreshCardIdIfInvalid()) { + if (!refreshCardIdIfUninitialized()) { sendUnavailableError(callbackIntent); return; } @@ -781,7 +781,7 @@ public class EuiccManager { @SystemApi @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void eraseSubscriptions(PendingIntent callbackIntent) { - if (!refreshCardIdIfInvalid()) { + if (!refreshCardIdIfUninitialized()) { sendUnavailableError(callbackIntent); return; } @@ -811,7 +811,7 @@ public class EuiccManager { * @hide */ public void retainSubscriptionsForFactoryReset(PendingIntent callbackIntent) { - if (!refreshCardIdIfInvalid()) { + if (!refreshCardIdIfUninitialized()) { sendUnavailableError(callbackIntent); return; } @@ -822,16 +822,24 @@ public class EuiccManager { } } - private boolean refreshCardIdIfInvalid() { - if (!isEnabled()) { - return false; - } - // Refresh mCardId if it's invalid. - if (mCardId == TelephonyManager.INVALID_CARD_ID) { + /** + * Refreshes the cardId if its uninitialized, and returns whether we should continue the + * operation. + * <p> + * Note that after a successful refresh, the mCardId may be TelephonyManager.UNSUPPORTED_CARD_ID + * on older HALs. For backwards compatability, we continue to the LPA and let it decide which + * card to use. + */ + private boolean refreshCardIdIfUninitialized() { + // Refresh mCardId if its UNINITIALIZED_CARD_ID + if (mCardId == TelephonyManager.UNINITIALIZED_CARD_ID) { TelephonyManager tm = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); mCardId = tm.getCardIdForDefaultEuicc(); } + if (mCardId == TelephonyManager.UNINITIALIZED_CARD_ID) { + return false; + } return true; } diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java index 59167b7d5bba..73f055649b3f 100644 --- a/telephony/java/android/telephony/ims/ImsCallProfile.java +++ b/telephony/java/android/telephony/ims/ImsCallProfile.java @@ -350,6 +350,9 @@ public final class ImsCallProfile implements Parcelable { /** Indicates if the call is for testing purpose */ private boolean mEmergencyCallTesting = false; + /** Indicates if we have known the intent of the user for the call is emergency */ + private boolean mHasKnownUserIntentEmergency = false; + /** * Extras associated with this {@link ImsCallProfile}. * <p> @@ -789,12 +792,13 @@ public final class ImsCallProfile implements Parcelable { * * @hide */ - public void setEmergencyCallInfo(EmergencyNumber num) { + public void setEmergencyCallInfo(EmergencyNumber num, boolean hasKnownUserIntentEmergency) { setEmergencyServiceCategories(num.getEmergencyServiceCategoryBitmaskInternalDial()); setEmergencyUrns(num.getEmergencyUrns()); setEmergencyCallRouting(num.getEmergencyCallRouting()); setEmergencyCallTesting(num.getEmergencyNumberSourceBitmask() == EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST); + setHasKnownUserIntentEmergency(hasKnownUserIntentEmergency); } /** @@ -860,6 +864,19 @@ public final class ImsCallProfile implements Parcelable { } /** + * Set if we have known the user intent of the call is emergency. + * + * This is only used to specify when the dialed number is ambiguous when it can be identified + * as both emergency number and any other non-emergency number; e.g. in some situation, 611 + * could be both an emergency number in a country and a non-emergency number of a carrier's + * customer service hotline. + */ + @VisibleForTesting + public void setHasKnownUserIntentEmergency(boolean hasKnownUserIntentEmergency) { + mHasKnownUserIntentEmergency = hasKnownUserIntentEmergency; + } + + /** * Get the emergency service categories, only valid if {@link #getServiceType} returns * {@link #SERVICE_TYPE_EMERGENCY} * @@ -916,4 +933,16 @@ public final class ImsCallProfile implements Parcelable { public boolean isEmergencyCallTesting() { return mEmergencyCallTesting; } + + /** + * Checks if we have known the user intent of the call is emergency. + * + * This is only used to specify when the dialed number is ambiguous when it can be identified + * as both emergency number and any other non-emergency number; e.g. in some situation, 611 + * could be both an emergency number in a country and a non-emergency number of a carrier's + * customer service hotline. + */ + public boolean hasKnownUserIntentEmergency() { + return mHasKnownUserIntentEmergency; + } } diff --git a/telephony/java/com/android/internal/telephony/ISetOpportunisticDataCallback.aidl b/telephony/java/com/android/internal/telephony/ISetOpportunisticDataCallback.aidl new file mode 100644 index 000000000000..7a78f3454aac --- /dev/null +++ b/telephony/java/com/android/internal/telephony/ISetOpportunisticDataCallback.aidl @@ -0,0 +1,25 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony; + +/** + * Callback to provide asynchronous result of setPreferredOpportunisticData. + * @hide + */ +oneway interface ISetOpportunisticDataCallback { + void onComplete(int result); +} diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl index 6ce9de4ca677..75a4d8227e23 100755 --- a/telephony/java/com/android/internal/telephony/ISub.aidl +++ b/telephony/java/com/android/internal/telephony/ISub.aidl @@ -17,6 +17,7 @@ package com.android.internal.telephony; import android.telephony.SubscriptionInfo; +import com.android.internal.telephony.ISetOpportunisticDataCallback; interface ISub { /** @@ -217,10 +218,14 @@ interface ISub { * designed to overwrite default data subscription temporarily. * * @param subId which subscription is preferred to for cellular data. + * @param needValidation whether validation is needed before switching. + * @param callback callback upon request completion. + * * @hide * */ - void setPreferredDataSubscriptionId(int subId); + void setPreferredDataSubscriptionId(int subId, boolean needValidation, + ISetOpportunisticDataCallback callback); /** * Get which subscription is preferred for cellular data. diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index caa367fbaafe..7089ee5671c2 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -765,7 +765,7 @@ interface ITelephony { * @param subId the id of the subscription. * @return CellNetworkScanResult containing status of scan and networks. */ - CellNetworkScanResult getCellNetworkScanResults(int subId); + CellNetworkScanResult getCellNetworkScanResults(int subId, String callingPackage); /** * Perform a radio network scan and return the id of this scan. @@ -774,10 +774,11 @@ interface ITelephony { * @param request Defines all the configs for network scan. * @param messenger Callback messages will be sent using this messenger. * @param binder the binder object instantiated in TelephonyManager. + * @param callingPackage the calling package * @return An id for this scan. */ int requestNetworkScan(int subId, in NetworkScanRequest request, in Messenger messenger, - in IBinder binder); + in IBinder binder, in String callingPackage); /** * Stop an existing radio network scan. diff --git a/telephony/java/com/android/internal/telephony/cdma/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/BearerData.java index a4cd56b9e3e2..694cc69c2b3f 100644 --- a/telephony/java/com/android/internal/telephony/cdma/BearerData.java +++ b/telephony/java/com/android/internal/telephony/cdma/BearerData.java @@ -596,6 +596,45 @@ public final class BearerData { System.arraycopy(payload, 0, uData.payload, udhBytes, payload.length); } + private static void encode7bitAsciiEms(UserData uData, byte[] udhData, boolean force) + throws CodingException + { + try { + Rlog.d(LOG_TAG, "encode7bitAsciiEms"); + int udhBytes = udhData.length + 1; // Add length octet. + int udhSeptets = ((udhBytes * 8) + 6) / 7; + int paddingBits = (udhSeptets * 7) - (udhBytes * 8); + String msg = uData.payloadStr; + byte[] payload ; + int msgLen = msg.length(); + BitwiseOutputStream outStream = new BitwiseOutputStream(msgLen + + (paddingBits > 0 ? 1 : 0)); + outStream.write(paddingBits, 0); + for (int i = 0; i < msgLen; i++) { + int charCode = UserData.charToAscii.get(msg.charAt(i), -1); + if (charCode == -1) { + if (force) { + outStream.write(7, UserData.UNENCODABLE_7_BIT_CHAR); + } else { + throw new CodingException("cannot ASCII encode (" + msg.charAt(i) + ")"); + } + } else { + outStream.write(7, charCode); + } + } + payload = outStream.toByteArray(); + uData.msgEncoding = UserData.ENCODING_7BIT_ASCII; + uData.msgEncodingSet = true; + uData.numFields = udhSeptets + uData.payloadStr.length(); + uData.payload = new byte[udhBytes + payload.length]; + uData.payload[0] = (byte)udhData.length; + System.arraycopy(udhData, 0, uData.payload, 1, udhData.length); + System.arraycopy(payload, 0, uData.payload, udhBytes, payload.length); + } catch (BitwiseOutputStream.AccessException ex) { + throw new CodingException("7bit ASCII encode failed: " + ex); + } + } + private static void encodeEmsUserDataPayload(UserData uData) throws CodingException { @@ -605,6 +644,8 @@ public final class BearerData { encode7bitEms(uData, headerData, true); } else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) { encode16bitEms(uData, headerData); + } else if (uData.msgEncoding == UserData.ENCODING_7BIT_ASCII) { + encode7bitAsciiEms(uData, headerData, true); } else { throw new CodingException("unsupported EMS user data encoding (" + uData.msgEncoding + ")"); @@ -1056,15 +1097,18 @@ public final class BearerData { throws CodingException { try { - offset *= 8; + int offsetBits = offset * 8; + int offsetSeptets = (offsetBits + 6) / 7; + numFields -= offsetSeptets; + StringBuffer strBuf = new StringBuffer(numFields); BitwiseInputStream inStream = new BitwiseInputStream(data); - int wantedBits = (offset * 8) + (numFields * 7); + int wantedBits = (offsetSeptets * 7) + (numFields * 7); if (inStream.available() < wantedBits) { throw new CodingException("insufficient data (wanted " + wantedBits + " bits, but only have " + inStream.available() + ")"); } - inStream.skip(offset); + inStream.skip(offsetSeptets * 7); for (int i = 0; i < numFields; i++) { int charCode = inStream.read(7); if ((charCode >= UserData.ASCII_MAP_BASE_INDEX) && diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java index 9080e23eb88f..1da5eac27002 100644 --- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java @@ -864,8 +864,9 @@ public class SmsMessage extends SmsMessageBase { Rlog.d(LOG_TAG, "MO raw BearerData = '" + HexDump.toHexString(encodedBearerData) + "'"); } - int teleservice = bearerData.hasUserDataHeader ? - SmsEnvelope.TELESERVICE_WEMT : SmsEnvelope.TELESERVICE_WMT; + int teleservice = (bearerData.hasUserDataHeader + && userData.msgEncoding != UserData.ENCODING_7BIT_ASCII) + ? SmsEnvelope.TELESERVICE_WEMT : SmsEnvelope.TELESERVICE_WMT; SmsEnvelope envelope = new SmsEnvelope(); envelope.messageType = SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT; diff --git a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java index d5549cc1355e..07b3a97817a3 100644 --- a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java +++ b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java @@ -439,6 +439,14 @@ public class ActivityTestMain extends Activity { return true; } }); + menu.add("Require unknown permission").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override public boolean onMenuItemClick(MenuItem item) { + final Intent intent = new Intent(SLOW_RECEIVER_ACTION); + intent.putExtra(SLOW_RECEIVER_EXTRA, 5038); + sendOrderedBroadcast(intent, "com.google.android.test.activity.permission.UNDEFINED"); + return true; + } + }); menu.add("Stack Doc").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { ActivityManager.AppTask task = findDocTask(); diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index a7c95c78d05d..e57d838bcf92 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -3817,11 +3817,14 @@ public class ConnectivityServiceTest { } @Test - public void testNattSocketKeepalives() throws Exception { + public void testNattSocketKeepalives_SingleThreadExecutor() throws Exception { final ExecutorService executorSingleThread = Executors.newSingleThreadExecutor(); doTestNattSocketKeepalivesWithExecutor(executorSingleThread); executorSingleThread.shutdown(); + } + @Test + public void testNattSocketKeepalives_InlineExecutor() throws Exception { final Executor executorInline = (Runnable r) -> r.run(); doTestNattSocketKeepalivesWithExecutor(executorInline); } @@ -3963,6 +3966,7 @@ public class ConnectivityServiceTest { testSocket2.close(); mWiFiNetworkAgent.disconnect(); + waitFor(mWiFiNetworkAgent.getDisconnectedCV()); } @Test diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto index 73b568e77689..9a1d94288363 100644 --- a/tools/aapt2/Resources.proto +++ b/tools/aapt2/Resources.proto @@ -151,10 +151,12 @@ message Overlayable { // Represents an overlayable <item> declaration within an <overlayable> tag. message OverlayableItem { enum Policy { - PUBLIC = 0; - SYSTEM = 1; - VENDOR = 2; - PRODUCT = 3; + NONE = 0; + PUBLIC = 1; + SYSTEM = 2; + VENDOR = 3; + PRODUCT = 4; + SIGNATURE = 5; } // The location of the <item> declaration in source. diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java index 488de8789178..af9fdfbd364d 100644 --- a/wifi/java/android/net/wifi/WifiInfo.java +++ b/wifi/java/android/net/wifi/WifiInfo.java @@ -276,14 +276,19 @@ public class WifiInfo implements Parcelable { /** * Returns the service set identifier (SSID) of the current 802.11 network. + * <p> * If the SSID can be decoded as UTF-8, it will be returned surrounded by double - * quotation marks. Otherwise, it is returned as a string of hex digits. The - * SSID may be <unknown ssid> if there is no network currently connected, - * or if the caller has insufficient permissions to access the SSID. - * + * quotation marks. Otherwise, it is returned as a string of hex digits. + * The SSID may be + * <lt><unknown ssid>, if there is no network currently connected or if the caller has + * insufficient permissions to access the SSID.<lt> + * </p> + * <p> * Prior to {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}, this method * always returned the SSID with no quotes around it. - * @return the SSID + * </p> + * + * @return the SSID. */ public String getSSID() { if (mWifiSsid != null) { @@ -312,7 +317,13 @@ public class WifiInfo implements Parcelable { /** * Return the basic service set identifier (BSSID) of the current access point. - * The BSSID may be {@code null} if there is no network currently connected. + * <p> + * The BSSID may be + * <lt>{@code null}, if there is no network currently connected.</lt> + * <lt>{@code "02:00:00:00:00:00"}, if the caller has insufficient permissions to access the + * BSSID.<lt> + * </p> + * * @return the BSSID, in the form of a six-byte MAC address: {@code XX:XX:XX:XX:XX:XX} */ public String getBSSID() { @@ -511,9 +522,13 @@ public class WifiInfo implements Parcelable { /** * Each configured network has a unique small integer ID, used to identify - * the network when performing operations on the supplicant. This method - * returns the ID for the currently connected network. - * @return the network ID, or -1 if there is no currently connected network + * the network. This method returns the ID for the currently connected network. + * <p> + * The networkId may be {@code -1} if there is no currently connected network or if the caller + * has insufficient permissions to access the network ID. + * </p> + * + * @return the network ID. */ public int getNetworkId() { return mNetworkId; diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 2cc1d8313225..5e5a59566ce5 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -1149,9 +1149,6 @@ public class WifiManager { * Return a list of all the networks configured for the current foreground * user. * - * Requires the same permissions as {@link #getScanResults}. - * If such access is not allowed, this API will always return an empty list. - * * Not all fields of WifiConfiguration are returned. Only the following * fields are filled in: * <ul> @@ -1176,8 +1173,12 @@ public class WifiManager { * when auto-connecting to wifi. * <b>Compatibility Note:</b> For applications targeting * {@link android.os.Build.VERSION_CODES#Q} or above, this API will return an empty list, - * except to callers with Carrier privilege which will receive a restricted list only - * containing configurations which they created. + * except for: + * <ul> + * <li>Device Owner (DO) & Profile Owner (PO) apps will have access to the full list. + * <li>Callers with Carrier privilege will receive a restricted list only containing + * configurations which they created. + * </ul> */ @Deprecated @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, ACCESS_WIFI_STATE}) @@ -4721,7 +4722,6 @@ public class WifiManager { * * @hide */ - @SystemApi private static class EasyConnectCallbackProxy extends IDppCallback.Stub { private final Executor mExecutor; private final EasyConnectStatusCallback mEasyConnectStatusCallback; |