Merge "count receivers in hasTooManyComponents"
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 1898e49..1147e07 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -622,7 +622,7 @@
public static final long DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS =
Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS);
public static final long DEFAULT_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS =
- Math.max(Long.MAX_VALUE, DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS);
+ Math.min(Long.MAX_VALUE, DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS);
static final boolean DEFAULT_PERSIST_IN_SPLIT_FILES = true;
private static final boolean DEFAULT_USE_TARE_POLICY = false;
@@ -3174,10 +3174,9 @@
/** Returns the minimum amount of time we should let this job run before timing out. */
public long getMinJobExecutionGuaranteeMs(JobStatus job) {
synchronized (mLock) {
- final boolean shouldTreatAsDataTransfer = job.getJob().isDataTransfer()
- && checkRunLongJobsPermission(job.getSourceUid(), job.getSourcePackageName());
- if (job.shouldTreatAsUserInitiatedJob()) {
- if (shouldTreatAsDataTransfer) {
+ if (job.shouldTreatAsUserInitiatedJob()
+ && checkRunLongJobsPermission(job.getSourceUid(), job.getSourcePackageName())) {
+ if (job.getJob().isDataTransfer()) {
final long estimatedTransferTimeMs =
mConnectivityController.getEstimatedTransferTimeMs(job);
if (estimatedTransferTimeMs == ConnectivityController.UNKNOWN_TIME) {
@@ -3194,7 +3193,7 @@
));
}
return mConstants.RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS;
- } else if (shouldTreatAsDataTransfer) {
+ } else if (job.getJob().isDataTransfer()) {
// For now, don't increase a bg data transfer's minimum guarantee.
return mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS;
} else if (job.shouldTreatAsExpeditedJob()) {
@@ -3213,23 +3212,18 @@
/** Returns the maximum amount of time this job could run for. */
public long getMaxJobExecutionTimeMs(JobStatus job) {
synchronized (mLock) {
- final boolean allowLongerJob;
- final boolean isDataTransfer = job.getJob().isDataTransfer();
- if (isDataTransfer || job.shouldTreatAsUserInitiatedJob()) {
- allowLongerJob =
- checkRunLongJobsPermission(job.getSourceUid(), job.getSourcePackageName());
- } else {
- allowLongerJob = false;
+ final boolean allowLongerJob = job.shouldTreatAsUserInitiatedJob()
+ && checkRunLongJobsPermission(job.getSourceUid(), job.getSourcePackageName());
+ if (job.getJob().isDataTransfer() && allowLongerJob) { // UI+DT
+ return mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS;
+ }
+ if (allowLongerJob) { // UI with LRJ permission
+ return mConstants.RUNTIME_USER_INITIATED_LIMIT_MS;
}
if (job.shouldTreatAsUserInitiatedJob()) {
- if (isDataTransfer && allowLongerJob) {
- return mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS;
- }
- if (allowLongerJob) {
- return mConstants.RUNTIME_USER_INITIATED_LIMIT_MS;
- }
return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
- } else if (isDataTransfer && allowLongerJob) {
+ }
+ if (job.getJob().isDataTransfer()) {
return mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS;
}
return Math.min(mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
diff --git a/core/api/current.txt b/core/api/current.txt
index 2f8cdf0..9da95c0 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -3094,6 +3094,7 @@
public abstract class AccessibilityService extends android.app.Service {
ctor public AccessibilityService();
method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl);
+ method public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl);
method public boolean clearCache();
method public boolean clearCachedSubtree(@NonNull android.view.accessibility.AccessibilityNodeInfo);
method public final void disableSelf();
@@ -4297,7 +4298,7 @@
method @Deprecated public final void removeDialog(int);
method public void reportFullyDrawn();
method public android.view.DragAndDropPermissions requestDragAndDropPermissions(android.view.DragEvent);
- method public void requestFullscreenMode(@NonNull int, @Nullable android.os.OutcomeReceiver<java.lang.Void,java.lang.Throwable>);
+ method public void requestFullscreenMode(int, @Nullable android.os.OutcomeReceiver<java.lang.Void,java.lang.Throwable>);
method public final void requestPermissions(@NonNull String[], int);
method public final void requestShowKeyboardShortcuts();
method @Deprecated public boolean requestVisibleBehind(boolean);
@@ -5873,10 +5874,15 @@
method @Deprecated public android.view.Window startActivity(String, android.content.Intent);
}
- public class LocaleConfig {
+ public class LocaleConfig implements android.os.Parcelable {
ctor public LocaleConfig(@NonNull android.content.Context);
+ ctor public LocaleConfig(@NonNull android.os.LocaleList);
+ method public int describeContents();
+ method @NonNull public static android.app.LocaleConfig fromResources(@NonNull android.content.Context);
method public int getStatus();
method @Nullable public android.os.LocaleList getSupportedLocales();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.LocaleConfig> CREATOR;
field public static final int STATUS_NOT_SPECIFIED = 1; // 0x1
field public static final int STATUS_PARSING_FAILED = 2; // 0x2
field public static final int STATUS_SUCCESS = 0; // 0x0
@@ -5887,8 +5893,10 @@
public class LocaleManager {
method @NonNull public android.os.LocaleList getApplicationLocales();
method @NonNull @RequiresPermission(value="android.permission.READ_APP_SPECIFIC_LOCALES", conditional=true) public android.os.LocaleList getApplicationLocales(@NonNull String);
+ method @Nullable public android.app.LocaleConfig getOverrideLocaleConfig();
method @NonNull public android.os.LocaleList getSystemLocales();
method public void setApplicationLocales(@NonNull android.os.LocaleList);
+ method public void setOverrideLocaleConfig(@Nullable android.app.LocaleConfig);
}
public class MediaRouteActionProvider extends android.view.ActionProvider {
@@ -15048,6 +15056,7 @@
field public static final int FLEX_RGB_888 = 41; // 0x29
field public static final int HEIC = 1212500294; // 0x48454946
field public static final int JPEG = 256; // 0x100
+ field public static final int JPEG_R = 4101; // 0x1005
field public static final int NV16 = 16; // 0x10
field public static final int NV21 = 17; // 0x11
field public static final int PRIVATE = 34; // 0x22
@@ -17229,6 +17238,7 @@
field public static final int DATASPACE_DYNAMIC_DEPTH = 4098; // 0x1002
field public static final int DATASPACE_HEIF = 4100; // 0x1004
field public static final int DATASPACE_JFIF = 146931712; // 0x8c20000
+ field public static final int DATASPACE_JPEG_R = 4101; // 0x1005
field public static final int DATASPACE_SCRGB = 411107328; // 0x18810000
field public static final int DATASPACE_SCRGB_LINEAR = 406913024; // 0x18410000
field public static final int DATASPACE_SRGB = 142671872; // 0x8810000
@@ -19687,6 +19697,7 @@
public final class GnssCapabilities implements android.os.Parcelable {
method public int describeContents();
method @NonNull public java.util.List<android.location.GnssSignalType> getGnssSignalTypes();
+ method public boolean hasAccumulatedDeltaRange();
method public boolean hasAntennaInfo();
method public boolean hasGeofencing();
method @Deprecated public boolean hasGnssAntennaInfo();
@@ -19721,6 +19732,7 @@
ctor public GnssCapabilities.Builder(@NonNull android.location.GnssCapabilities);
method @NonNull public android.location.GnssCapabilities build();
method @NonNull public android.location.GnssCapabilities.Builder setGnssSignalTypes(@NonNull java.util.List<android.location.GnssSignalType>);
+ method @NonNull public android.location.GnssCapabilities.Builder setHasAccumulatedDeltaRange(boolean);
method @NonNull public android.location.GnssCapabilities.Builder setHasAntennaInfo(boolean);
method @NonNull public android.location.GnssCapabilities.Builder setHasGeofencing(boolean);
method @NonNull public android.location.GnssCapabilities.Builder setHasLowPowerMode(boolean);
@@ -21197,6 +21209,7 @@
method @NonNull public android.media.AudioTrack.Builder setAudioAttributes(@NonNull android.media.AudioAttributes) throws java.lang.IllegalArgumentException;
method @NonNull public android.media.AudioTrack.Builder setAudioFormat(@NonNull android.media.AudioFormat) throws java.lang.IllegalArgumentException;
method @NonNull public android.media.AudioTrack.Builder setBufferSizeInBytes(@IntRange(from=0) int) throws java.lang.IllegalArgumentException;
+ method @NonNull public android.media.AudioTrack.Builder setContext(@NonNull android.content.Context);
method @NonNull public android.media.AudioTrack.Builder setEncapsulationMode(int);
method @NonNull public android.media.AudioTrack.Builder setOffloadedPlayback(boolean);
method @NonNull public android.media.AudioTrack.Builder setPerformanceMode(int);
@@ -23122,6 +23135,7 @@
public class MediaPlayer implements android.media.AudioRouting android.media.VolumeAutomation {
ctor public MediaPlayer();
+ ctor public MediaPlayer(@NonNull android.content.Context);
method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public void addTimedTextSource(String, String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void addTimedTextSource(android.content.Context, android.net.Uri, String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
@@ -32506,6 +32520,7 @@
public static class PerformanceHintManager.Session implements java.io.Closeable {
method public void close();
method public void reportActualWorkDuration(long);
+ method public void setThreads(@NonNull int[]);
method public void updateTargetWorkDuration(long);
}
@@ -40474,6 +40489,7 @@
public abstract class RecognitionService extends android.app.Service {
ctor public RecognitionService();
+ method public int getMaxConcurrentSessionsCount();
method public final android.os.IBinder onBind(android.content.Intent);
method protected abstract void onCancel(android.speech.RecognitionService.Callback);
method public void onCheckRecognitionSupport(@NonNull android.content.Intent, @NonNull android.speech.RecognitionService.SupportCallback);
@@ -41008,6 +41024,36 @@
field public static final int ROUTE_WIRED_OR_EARPIECE = 5; // 0x5
}
+ public final class CallEndpoint implements android.os.Parcelable {
+ ctor public CallEndpoint(@NonNull CharSequence, int, @NonNull android.os.ParcelUuid);
+ method public int describeContents();
+ method @NonNull public CharSequence getEndpointName();
+ method public int getEndpointType();
+ method @NonNull public android.os.ParcelUuid getIdentifier();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telecom.CallEndpoint> CREATOR;
+ field public static final int TYPE_BLUETOOTH = 2; // 0x2
+ field public static final int TYPE_EARPIECE = 1; // 0x1
+ field public static final int TYPE_SPEAKER = 4; // 0x4
+ field public static final int TYPE_STREAMING = 5; // 0x5
+ field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
+ field public static final int TYPE_WIRED_HEADSET = 3; // 0x3
+ }
+
+ public final class CallEndpointException extends java.lang.RuntimeException implements android.os.Parcelable {
+ ctor public CallEndpointException(@Nullable String);
+ ctor public CallEndpointException(@Nullable String, int);
+ ctor public CallEndpointException(@Nullable String, int, @Nullable Throwable);
+ method public int describeContents();
+ method public int getCode();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telecom.CallEndpointException> CREATOR;
+ field public static final int ERROR_ANOTHER_REQUEST = 3; // 0x3
+ field public static final int ERROR_ENDPOINT_DOES_NOT_EXIST = 1; // 0x1
+ field public static final int ERROR_REQUEST_TIME_OUT = 2; // 0x2
+ field public static final int ERROR_UNSPECIFIED = 4; // 0x4
+ }
+
public abstract class CallRedirectionService extends android.app.Service {
ctor public CallRedirectionService();
method public final void cancelCall();
@@ -41057,13 +41103,14 @@
method public final boolean addConnection(android.telecom.Connection);
method @NonNull public static android.telecom.Conference createFailedConference(@NonNull android.telecom.DisconnectCause, @NonNull android.telecom.PhoneAccountHandle);
method public final void destroy();
- method public final android.telecom.CallAudioState getCallAudioState();
+ method @Deprecated public final android.telecom.CallAudioState getCallAudioState();
method public final java.util.List<android.telecom.Connection> getConferenceableConnections();
method public final int getConnectionCapabilities();
method public final int getConnectionProperties();
method public final long getConnectionStartElapsedRealtimeMillis();
method @IntRange(from=0) public final long getConnectionTime();
method public final java.util.List<android.telecom.Connection> getConnections();
+ method @NonNull public final android.telecom.CallEndpoint getCurrentCallEndpoint();
method public final android.telecom.DisconnectCause getDisconnectCause();
method public final android.os.Bundle getExtras();
method public final android.telecom.PhoneAccountHandle getPhoneAccountHandle();
@@ -41074,13 +41121,16 @@
method public final boolean isRingbackRequested();
method public void onAddConferenceParticipants(@NonNull java.util.List<android.net.Uri>);
method public void onAnswer(int);
- method public void onCallAudioStateChanged(android.telecom.CallAudioState);
+ method public void onAvailableCallEndpointsChanged(@NonNull java.util.List<android.telecom.CallEndpoint>);
+ method @Deprecated public void onCallAudioStateChanged(android.telecom.CallAudioState);
+ method public void onCallEndpointChanged(@NonNull android.telecom.CallEndpoint);
method public void onConnectionAdded(android.telecom.Connection);
method public void onDisconnect();
method public void onExtrasChanged(android.os.Bundle);
method public void onHold();
method public void onMerge(android.telecom.Connection);
method public void onMerge();
+ method public void onMuteStateChanged(boolean);
method public void onPlayDtmfTone(char);
method public void onReject();
method public void onSeparate(android.telecom.Connection);
@@ -41123,7 +41173,7 @@
method public final android.net.Uri getAddress();
method public final int getAddressPresentation();
method public final boolean getAudioModeIsVoip();
- method public final android.telecom.CallAudioState getCallAudioState();
+ method @Deprecated public final android.telecom.CallAudioState getCallAudioState();
method public final String getCallerDisplayName();
method public final int getCallerDisplayNamePresentation();
method public final int getCallerNumberVerificationStatus();
@@ -41131,6 +41181,7 @@
method public final java.util.List<android.telecom.Conferenceable> getConferenceables();
method public final int getConnectionCapabilities();
method public final int getConnectionProperties();
+ method @NonNull public final android.telecom.CallEndpoint getCurrentCallEndpoint();
method public final android.telecom.DisconnectCause getDisconnectCause();
method public final android.os.Bundle getExtras();
method public final int getState();
@@ -41144,13 +41195,16 @@
method public void onAddConferenceParticipants(@NonNull java.util.List<android.net.Uri>);
method public void onAnswer(int);
method public void onAnswer();
- method public void onCallAudioStateChanged(android.telecom.CallAudioState);
+ method public void onAvailableCallEndpointsChanged(@NonNull java.util.List<android.telecom.CallEndpoint>);
+ method @Deprecated public void onCallAudioStateChanged(android.telecom.CallAudioState);
+ method public void onCallEndpointChanged(@NonNull android.telecom.CallEndpoint);
method public void onCallEvent(String, android.os.Bundle);
method public void onDeflect(android.net.Uri);
method public void onDisconnect();
method public void onExtrasChanged(android.os.Bundle);
method public void onHandoverComplete();
method public void onHold();
+ method public void onMuteStateChanged(boolean);
method public void onPlayDtmfTone(char);
method public void onPostDialContinue(boolean);
method public void onPullExternalCall();
@@ -41171,7 +41225,8 @@
method public final void putExtras(@NonNull android.os.Bundle);
method public final void removeExtras(java.util.List<java.lang.String>);
method public final void removeExtras(java.lang.String...);
- method public void requestBluetoothAudio(@NonNull android.bluetooth.BluetoothDevice);
+ method @Deprecated public void requestBluetoothAudio(@NonNull android.bluetooth.BluetoothDevice);
+ method public final void requestCallEndpointChange(@NonNull android.telecom.CallEndpoint, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallEndpointException>);
method public void sendConnectionEvent(String, android.os.Bundle);
method public final void sendRemoteRttRequest();
method public final void sendRttInitiationFailure(int);
@@ -41180,7 +41235,7 @@
method public final void setActive();
method public final void setAddress(android.net.Uri, int);
method public final void setAudioModeIsVoip(boolean);
- method public final void setAudioRoute(int);
+ method @Deprecated public final void setAudioRoute(int);
method public final void setCallerDisplayName(String, int);
method public final void setCallerNumberVerificationStatus(int);
method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
@@ -41432,18 +41487,23 @@
public abstract class InCallService extends android.app.Service {
ctor public InCallService();
method public final boolean canAddCall();
- method public final android.telecom.CallAudioState getCallAudioState();
+ method @Deprecated public final android.telecom.CallAudioState getCallAudioState();
method public final java.util.List<android.telecom.Call> getCalls();
+ method @NonNull public final android.telecom.CallEndpoint getCurrentCallEndpoint();
+ method public void onAvailableCallEndpointsChanged(@NonNull java.util.List<android.telecom.CallEndpoint>);
method public android.os.IBinder onBind(android.content.Intent);
method public void onBringToForeground(boolean);
method public void onCallAdded(android.telecom.Call);
- method public void onCallAudioStateChanged(android.telecom.CallAudioState);
+ method @Deprecated public void onCallAudioStateChanged(android.telecom.CallAudioState);
+ method public void onCallEndpointChanged(@NonNull android.telecom.CallEndpoint);
method public void onCallRemoved(android.telecom.Call);
method public void onCanAddCallChanged(boolean);
method public void onConnectionEvent(android.telecom.Call, String, android.os.Bundle);
+ method public void onMuteStateChanged(boolean);
method public void onSilenceRinger();
- method public final void requestBluetoothAudio(@NonNull android.bluetooth.BluetoothDevice);
- method public final void setAudioRoute(int);
+ method @Deprecated public final void requestBluetoothAudio(@NonNull android.bluetooth.BluetoothDevice);
+ method public final void requestCallEndpointChange(@NonNull android.telecom.CallEndpoint, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallEndpointException>);
+ method @Deprecated public final void setAudioRoute(int);
method public final void setMuted(boolean);
field public static final String SERVICE_INTERFACE = "android.telecom.InCallService";
}
@@ -42696,6 +42756,9 @@
field public static final int AUTHENTICATION_METHOD_CERT = 1; // 0x1
field public static final int AUTHENTICATION_METHOD_EAP_ONLY = 0; // 0x0
field public static final int EPDG_ADDRESS_CELLULAR_LOC = 3; // 0x3
+ field public static final int EPDG_ADDRESS_IPV4_ONLY = 2; // 0x2
+ field public static final int EPDG_ADDRESS_IPV4_PREFERRED = 0; // 0x0
+ field public static final int EPDG_ADDRESS_IPV6_PREFERRED = 1; // 0x1
field public static final int EPDG_ADDRESS_PCO = 2; // 0x2
field public static final int EPDG_ADDRESS_PLMN = 1; // 0x1
field public static final int EPDG_ADDRESS_STATIC = 0; // 0x0
@@ -42710,6 +42773,7 @@
field public static final String KEY_CHILD_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY = "iwlan.child_session_aes_ctr_key_size_int_array";
field public static final String KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY = "iwlan.diffie_hellman_groups_int_array";
field public static final String KEY_DPD_TIMER_SEC_INT = "iwlan.dpd_timer_sec_int";
+ field public static final String KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT = "iwlan.epdg_address_ip_type_preference_int";
field public static final String KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY = "iwlan.epdg_address_priority_int_array";
field public static final String KEY_EPDG_AUTHENTICATION_METHOD_INT = "iwlan.epdg_authentication_method_int";
field public static final String KEY_EPDG_PCO_ID_IPV4_INT = "iwlan.epdg_pco_id_ipv4_int";
@@ -43544,9 +43608,12 @@
method public int getDomain();
method @Nullable public String getRegisteredPlmn();
method public int getTransportType();
- method public boolean isRegistered();
- method public boolean isRoaming();
- method public boolean isSearching();
+ method public boolean isNetworkRegistered();
+ method public boolean isNetworkRoaming();
+ method public boolean isNetworkSearching();
+ method @Deprecated public boolean isRegistered();
+ method @Deprecated public boolean isRoaming();
+ method @Deprecated public boolean isSearching();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.NetworkRegistrationInfo> CREATOR;
field public static final int DOMAIN_CS = 1; // 0x1
@@ -43827,6 +43894,7 @@
public final class SignalThresholdInfo implements android.os.Parcelable {
method public int describeContents();
+ method public int getHysteresisDb();
method public static int getMaximumNumberOfThresholdsAllowed();
method public static int getMinimumNumberOfThresholdsAllowed();
method public int getRadioAccessNetworkType();
@@ -43849,6 +43917,7 @@
public static final class SignalThresholdInfo.Builder {
ctor public SignalThresholdInfo.Builder();
method @NonNull public android.telephony.SignalThresholdInfo build();
+ method @NonNull public android.telephony.SignalThresholdInfo.Builder setHysteresisDb(@IntRange(from=0) int);
method @NonNull public android.telephony.SignalThresholdInfo.Builder setRadioAccessNetworkType(int);
method @NonNull public android.telephony.SignalThresholdInfo.Builder setSignalMeasurementType(int);
method @NonNull public android.telephony.SignalThresholdInfo.Builder setThresholds(@NonNull int[]);
@@ -44117,6 +44186,7 @@
method public int getActiveSubscriptionInfoCountMax();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telephony.SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int);
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telephony.SubscriptionInfo> getActiveSubscriptionInfoList();
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, "carrier privileges"}) public java.util.List<android.telephony.SubscriptionInfo> getAllSubscriptionInfoList();
method @NonNull public java.util.List<android.telephony.SubscriptionInfo> getCompleteActiveSubscriptionInfoList();
method public static int getDefaultDataSubscriptionId();
method public static int getDefaultSmsSubscriptionId();
@@ -44128,7 +44198,8 @@
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_NUMBERS, "android.permission.READ_PRIVILEGED_PHONE_STATE", "carrier privileges"}) public String getPhoneNumber(int, int);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_NUMBERS, "android.permission.READ_PRIVILEGED_PHONE_STATE", "carrier privileges"}) public String getPhoneNumber(int);
method public static int getSlotIndex(int);
- method @Nullable public int[] getSubscriptionIds(int);
+ method public static int getSubscriptionId(int);
+ method @Deprecated @Nullable public int[] getSubscriptionIds(int);
method @NonNull public java.util.List<android.telephony.SubscriptionPlan> getSubscriptionPlans(int);
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telephony.SubscriptionInfo> getSubscriptionsInGroup(@NonNull android.os.ParcelUuid);
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isActiveSubscriptionId(int);
@@ -44312,6 +44383,7 @@
method public int describeContents();
method public int getNetworkType();
method public int getOverrideNetworkType();
+ method public boolean isRoaming();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.TelephonyDisplayInfo> CREATOR;
field public static final int OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO = 2; // 0x2
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 50c3c78..46f51c7 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3023,6 +3023,8 @@
method public int describeContents();
method @NonNull public java.util.Set<android.content.ComponentName> getAllowedActivities();
method @NonNull public java.util.Set<android.content.ComponentName> getAllowedCrossTaskNavigations();
+ method public int getAudioPlaybackSessionId();
+ method public int getAudioRecordingSessionId();
method @NonNull public java.util.Set<android.content.ComponentName> getBlockedActivities();
method @NonNull public java.util.Set<android.content.ComponentName> getBlockedCrossTaskNavigations();
method public int getDefaultActivityPolicy();
@@ -3043,6 +3045,7 @@
field public static final int LOCK_STATE_DEFAULT = 0; // 0x0
field public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0
field public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1
+ field public static final int POLICY_TYPE_AUDIO = 1; // 0x1
field public static final int POLICY_TYPE_SENSORS = 0; // 0x0
field public static final int RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS = 1; // 0x1
}
@@ -3053,6 +3056,8 @@
method @NonNull public android.companion.virtual.VirtualDeviceParams build();
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
+ method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAudioPlaybackSessionId(int);
+ method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAudioRecordingSessionId(int);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDefaultRecentsPolicy(int);
@@ -13192,10 +13197,18 @@
public final class DataSpecificRegistrationInfo implements android.os.Parcelable {
method public int describeContents();
+ method public int getLteAttachExtraInfo();
+ method public int getLteAttachResultType();
method @Deprecated @NonNull public android.telephony.LteVopsSupportInfo getLteVopsSupportInfo();
method @Nullable public android.telephony.VopsSupportInfo getVopsSupportInfo();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.DataSpecificRegistrationInfo> CREATOR;
+ field public static final int LTE_ATTACH_EXTRA_INFO_CSFB_NOT_PREFERRED = 1; // 0x1
+ field public static final int LTE_ATTACH_EXTRA_INFO_NONE = 0; // 0x0
+ field public static final int LTE_ATTACH_EXTRA_INFO_SMS_ONLY = 2; // 0x2
+ field public static final int LTE_ATTACH_TYPE_COMBINED = 2; // 0x2
+ field public static final int LTE_ATTACH_TYPE_EPS_ONLY = 1; // 0x1
+ field public static final int LTE_ATTACH_TYPE_UNKNOWN = 0; // 0x0
}
public final class DataThrottlingRequest implements android.os.Parcelable {
@@ -13285,12 +13298,14 @@
public final class NetworkRegistrationInfo implements android.os.Parcelable {
method @Nullable public android.telephony.DataSpecificRegistrationInfo getDataSpecificInfo();
- method public int getRegistrationState();
+ method public int getNetworkRegistrationState();
+ method @Deprecated public int getRegistrationState();
method public int getRejectCause();
method public int getRoamingType();
method public boolean isEmergencyEnabled();
method public void writeToParcel(android.os.Parcel, int);
field public static final int REGISTRATION_STATE_DENIED = 3; // 0x3
+ field public static final int REGISTRATION_STATE_EMERGENCY = 6; // 0x6
field public static final int REGISTRATION_STATE_HOME = 1; // 0x1
field public static final int REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING = 0; // 0x0
field public static final int REGISTRATION_STATE_NOT_REGISTERED_SEARCHING = 2; // 0x2
@@ -13785,6 +13800,7 @@
field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED = 28; // 0x1c
field @RequiresPermission(android.Manifest.permission.READ_CALL_LOG) public static final int EVENT_LEGACY_CALL_STATE_CHANGED = 36; // 0x24
field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_LINK_CAPACITY_ESTIMATE_CHANGED = 37; // 0x25
+ field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_MEDIA_QUALITY_STATUS_CHANGED = 39; // 0x27
field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_MESSAGE_WAITING_INDICATOR_CHANGED = 3; // 0x3
field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_OEM_HOOK_RAW = 15; // 0xf
field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int EVENT_OUTGOING_EMERGENCY_CALL = 29; // 0x1d
@@ -13820,6 +13836,10 @@
method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onLinkCapacityEstimateChanged(@NonNull java.util.List<android.telephony.LinkCapacityEstimate>);
}
+ public static interface TelephonyCallback.MediaQualityStatusChangedListener {
+ method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onMediaQualityStatusChanged(@NonNull android.telephony.ims.MediaQualityStatus);
+ }
+
public static interface TelephonyCallback.OutgoingEmergencyCallListener {
method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber, int);
}
@@ -15362,6 +15382,37 @@
method public void receiveSessionModifyResponse(int, android.telecom.VideoProfile, android.telecom.VideoProfile);
}
+ public final class MediaQualityStatus implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public String getCallSessionId();
+ method public int getMediaSessionType();
+ method public long getRtpInactivityMillis();
+ method public int getRtpJitterMillis();
+ method @IntRange(from=0, to=100) public int getRtpPacketLossRate();
+ method public int getTransportType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.MediaQualityStatus> CREATOR;
+ field public static final int MEDIA_SESSION_TYPE_AUDIO = 1; // 0x1
+ field public static final int MEDIA_SESSION_TYPE_VIDEO = 2; // 0x2
+ }
+
+ public static final class MediaQualityStatus.Builder {
+ ctor public MediaQualityStatus.Builder(@NonNull String, int, int);
+ method @NonNull public android.telephony.ims.MediaQualityStatus build();
+ method @NonNull public android.telephony.ims.MediaQualityStatus.Builder setRtpInactivityMillis(long);
+ method @NonNull public android.telephony.ims.MediaQualityStatus.Builder setRtpJitterMillis(int);
+ method @NonNull public android.telephony.ims.MediaQualityStatus.Builder setRtpPacketLossRate(@IntRange(from=0, to=100) int);
+ }
+
+ public final class MediaThreshold implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public long[] getThresholdsRtpInactivityTimeMillis();
+ method @NonNull public int[] getThresholdsRtpJitterMillis();
+ method @NonNull public int[] getThresholdsRtpPacketLossRate();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.MediaThreshold> CREATOR;
+ }
+
public class ProvisioningManager {
method @NonNull public static android.telephony.ims.ProvisioningManager createForSubscriptionId(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public int getProvisioningIntValue(int);
@@ -15813,6 +15864,7 @@
ctor public MmTelFeature(@NonNull java.util.concurrent.Executor);
method public void changeEnabledCapabilities(@NonNull android.telephony.ims.feature.CapabilityChangeRequest, @NonNull android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy);
method public void changeOfferedRtpHeaderExtensionTypes(@NonNull java.util.Set<android.telephony.ims.RtpHeaderExtensionType>);
+ method public void clearMediaThreshold(int);
method @Nullable public android.telephony.ims.ImsCallProfile createCallProfile(int, int);
method @Nullable public android.telephony.ims.stub.ImsCallSessionImplBase createCallSession(@NonNull android.telephony.ims.ImsCallProfile);
method @NonNull public android.telephony.ims.stub.ImsEcbmImplBase getEcbm();
@@ -15822,6 +15874,7 @@
method public final void notifyCapabilitiesStatusChanged(@NonNull android.telephony.ims.feature.MmTelFeature.MmTelCapabilities);
method @Deprecated public final void notifyIncomingCall(@NonNull android.telephony.ims.stub.ImsCallSessionImplBase, @NonNull android.os.Bundle);
method @Nullable public final android.telephony.ims.ImsCallSessionListener notifyIncomingCall(@NonNull android.telephony.ims.stub.ImsCallSessionImplBase, @NonNull String, @NonNull android.os.Bundle);
+ method public final void notifyMediaQualityStatusChanged(@NonNull android.telephony.ims.MediaQualityStatus);
method public final void notifyRejectedCall(@NonNull android.telephony.ims.ImsCallProfile, @NonNull android.telephony.ims.ImsReasonInfo);
method public void notifySrvccCanceled();
method public void notifySrvccCompleted();
@@ -15832,7 +15885,9 @@
method public void onFeatureRemoved();
method public boolean queryCapabilityConfiguration(int, int);
method @NonNull public final android.telephony.ims.feature.MmTelFeature.MmTelCapabilities queryCapabilityStatus();
+ method @Nullable public android.telephony.ims.MediaQualityStatus queryMediaQualityStatus(int);
method public final void setCallAudioHandler(int);
+ method public void setMediaThreshold(int, @NonNull android.telephony.ims.MediaThreshold);
method public void setTerminalBasedCallWaitingStatus(boolean);
method public void setUiTtyMode(int, @Nullable android.os.Message);
method public int shouldProcessCall(@NonNull String[]);
@@ -16030,6 +16085,7 @@
public class ImsSmsImplBase {
ctor public ImsSmsImplBase();
+ ctor public ImsSmsImplBase(@NonNull java.util.concurrent.Executor);
method public void acknowledgeSms(int, @IntRange(from=0, to=65535) int, int);
method public void acknowledgeSms(int, @IntRange(from=0, to=65535) int, int, @NonNull byte[]);
method public void acknowledgeSmsReport(int, @IntRange(from=0, to=65535) int, int);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 1cfd644..82cc3fd 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1861,6 +1861,7 @@
}
public static class PerformanceHintManager.Session implements java.io.Closeable {
+ method @Nullable public int[] getThreadIds();
method public void sendHint(int);
field public static final int CPU_LOAD_DOWN = 1; // 0x1
field public static final int CPU_LOAD_RESET = 2; // 0x2
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 968ed87..25483f2 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -3439,4 +3439,20 @@
throw new RuntimeException(re);
}
}
+
+ /**
+ * Attaches an accessibility overlay {@link android.view.SurfaceControl} to the specified
+ * window. This method should be used when you want the overlay to move and resize as the parent
+ * window moves and resizes. To remove this overlay and free the associated resources, use
+ * <code> new SurfaceControl.Transaction().reparent(sc, null).apply();</code>.
+ *
+ * @param accessibilityWindowId The window id, from {@link AccessibilityWindowInfo#getId()}.
+ * @param sc the SurfaceControl containing the overlay content
+ */
+ public void attachAccessibilityOverlayToWindow(
+ int accessibilityWindowId, @NonNull SurfaceControl sc) {
+ Preconditions.checkNotNull(sc, "SurfaceControl cannot be null");
+ AccessibilityInteractionClient.getInstance(this)
+ .attachAccessibilityOverlayToWindow(mConnectionId, accessibilityWindowId, sc);
+ }
}
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index da13e73..e99932d 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -24,6 +24,7 @@
import android.os.Bundle;
import android.os.RemoteCallback;
import android.view.MagnificationSpec;
+import android.view.SurfaceControl;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -157,4 +158,6 @@
List<AccessibilityServiceInfo> getInstalledAndEnabledServices();
void attachAccessibilityOverlayToDisplay(int displayId, in SurfaceControl sc);
-}
+
+ void attachAccessibilityOverlayToWindow(int accessibilityWindowId, in SurfaceControl sc);
+}
\ No newline at end of file
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index b9eb443..d1772e37 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -995,8 +995,13 @@
})
public @interface FullscreenModeRequest {}
+ /** Request type of {@link #requestFullscreenMode(int, OutcomeReceiver)}, to request exiting the
+ * requested fullscreen mode and restore to the previous multi-window mode.
+ */
public static final int FULLSCREEN_MODE_REQUEST_EXIT = 0;
-
+ /** Request type of {@link #requestFullscreenMode(int, OutcomeReceiver)}, to request enter
+ * fullscreen mode from multi-window mode.
+ */
public static final int FULLSCREEN_MODE_REQUEST_ENTER = 1;
private boolean mShouldDockBigOverlays;
@@ -3015,8 +3020,9 @@
/**
* Request to put the a freeform activity into fullscreen. This will only be allowed if the
* activity is on a freeform display, such as a desktop device. The requester has to be the
- * top-most activity and the request should be a response to a user input. When getting
- * fullscreen and receiving corresponding {@link #onConfigurationChanged(Configuration)} and
+ * top-most activity of the focused display, and the request should be a response to a user
+ * input. When getting fullscreen and receiving corresponding
+ * {@link #onConfigurationChanged(Configuration)} and
* {@link #onMultiWindowModeChanged(boolean, Configuration)}, the activity should relayout
* itself and the system bars' visibilities can be controlled as usual fullscreen apps.
*
@@ -3034,9 +3040,11 @@
* @param approvalCallback Optional callback, use {@code null} when not necessary. When the
* request is approved or rejected, the callback will be triggered. This
* will happen before any configuration change. The callback will be
- * dispatched on the main thread.
+ * dispatched on the main thread. If the request is rejected, the
+ * Throwable provided will be an {@link IllegalStateException} with a
+ * detailed message can be retrieved by {@link Throwable#getMessage()}.
*/
- public void requestFullscreenMode(@NonNull @FullscreenModeRequest int request,
+ public void requestFullscreenMode(@FullscreenModeRequest int request,
@Nullable OutcomeReceiver<Void, Throwable> approvalCallback) {
FullscreenRequestHandler.requestFullscreenMode(
request, approvalCallback, mCurrentConfig, getActivityToken());
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a4c9f8c..37749e6 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -502,7 +502,6 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
static volatile Handler sMainThreadHandler; // set once in main()
- private long mStartSeq; // Only accesssed from the main thread
Bundle mCoreSettings = null;
@@ -6739,12 +6738,6 @@
}
final IActivityManager mgr = ActivityManager.getService();
- try {
- mgr.finishAttachApplication(mStartSeq);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
-
final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
mConfigurationController.updateLocaleListFromAppContext(appContext);
@@ -6815,11 +6808,9 @@
// Wait for debugger after we have notified the system to finish attach application
if (data.debugMode != ApplicationThreadConstants.DEBUG_OFF) {
- // XXX should have option to change the port.
- Debug.changeDebugPort(8100);
if (data.debugMode == ApplicationThreadConstants.DEBUG_WAIT) {
Slog.w(TAG, "Application " + data.info.getPackageName()
- + " is waiting for the debugger on port 8100...");
+ + " is waiting for the debugger ...");
try {
mgr.showWaitingForDebugger(mAppThread, true);
@@ -6834,10 +6825,6 @@
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
-
- } else {
- Slog.w(TAG, "Application " + data.info.getPackageName()
- + " can be debugged on port 8100...");
}
}
@@ -7681,8 +7668,6 @@
sCurrentActivityThread = this;
mConfigurationController = new ConfigurationController(this);
mSystemThread = system;
- mStartSeq = startSeq;
-
if (!system) {
android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
UserHandle.myUserId());
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 938f1f6..b120ea7 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -147,7 +147,6 @@
oneway void finishReceiver(in IBinder who, int resultCode, in String resultData, in Bundle map,
boolean abortBroadcast, int flags);
void attachApplication(in IApplicationThread app, long startSeq);
- void finishAttachApplication(long startSeq);
List<ActivityManager.RunningTaskInfo> getTasks(int maxNum);
@UnsupportedAppUsage
void moveTaskToFront(in IApplicationThread caller, in String callingPackage, int task,
@@ -719,8 +718,8 @@
/**
* Control the app freezer state. Returns true in case of success, false if the operation
- * didn't succeed (for example, when the app freezer isn't supported).
- * Handling the freezer state via this method is reentrant, that is it can be
+ * didn't succeed (for example, when the app freezer isn't supported).
+ * Handling the freezer state via this method is reentrant, that is it can be
* disabled and re-enabled multiple times in parallel. As long as there's a 1:1 disable to
* enable match, the freezer is re-enabled at last enable only.
* @param enable set it to true to enable the app freezer, false to disable it.
diff --git a/core/java/android/app/ILocaleManager.aidl b/core/java/android/app/ILocaleManager.aidl
index c38b64f..e6d40f2 100644
--- a/core/java/android/app/ILocaleManager.aidl
+++ b/core/java/android/app/ILocaleManager.aidl
@@ -17,6 +17,7 @@
package android.app;
+import android.app.LocaleConfig;
import android.os.LocaleList;
/**
@@ -29,7 +30,6 @@
* @hide
*/
interface ILocaleManager {
-
/**
* Sets a specified app’s app-specific UI locales.
*/
@@ -45,4 +45,7 @@
*/
LocaleList getSystemLocales();
+ void setOverrideLocaleConfig(String packageName, int userId, in LocaleConfig localeConfig);
+
+ LocaleConfig getOverrideLocaleConfig(String packageName, int userId);
}
diff --git a/core/java/android/app/LocaleConfig.aidl b/core/java/android/app/LocaleConfig.aidl
new file mode 100644
index 0000000..37e0847
--- /dev/null
+++ b/core/java/android/app/LocaleConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.app;
+
+parcelable LocaleConfig;
\ No newline at end of file
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index bbe3ce3..5d50d29 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -25,6 +25,8 @@
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Slog;
import android.util.Xml;
@@ -37,23 +39,27 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.HashSet;
+import java.util.Locale;
import java.util.Set;
/**
* The LocaleConfig of an application.
- * Defined in an XML resource file with an {@code <locale-config>} element and
- * referenced in the manifest via {@code android:localeConfig} on
- * {@code <application>}.
+ * There are two sources. One is from an XML resource file with an {@code <locale-config>} element
+ * and referenced in the manifest via {@code android:localeConfig} on {@code <application>}. The
+ * other is that the application dynamically provides an override version which is persisted in
+ * {@link LocaleManager#setOverrideLocaleConfig(LocaleConfig)}.
*
- * <p>For more information, see
+ * <p>For more information about the LocaleConfig from an XML resource file, see
* <a href="https://developer.android.com/about/versions/13/features/app-languages#use-localeconfig">
* the section on per-app language preferences</a>.
*
* @attr ref android.R.styleable#LocaleConfig_Locale_name
* @attr ref android.R.styleable#AndroidManifestApplication_localeConfig
+ *
+ * <p>For more information about the LocaleConfig overridden by the application, see
+ * TODO(b/261528306): add link to guide
*/
-public class LocaleConfig {
-
+public class LocaleConfig implements Parcelable {
private static final String TAG = "LocaleConfig";
public static final String TAG_LOCALE_CONFIG = "locale-config";
public static final String TAG_LOCALE = "locale";
@@ -83,13 +89,46 @@
public @interface Status{}
/**
- * Returns the LocaleConfig for the provided application context
+ * Returns an override LocaleConfig if it has been set via
+ * {@link LocaleManager#setOverrideLocaleConfig(LocaleConfig)}. Otherwise, returns the
+ * LocaleConfig from the application resources.
*
- * @param context the context of the application
+ * @param context the context of the application.
*
* @see Context#createPackageContext(String, int).
*/
public LocaleConfig(@NonNull Context context) {
+ this(context, true);
+ }
+
+ /**
+ * Returns a LocaleConfig from the application resources regardless of whether any LocaleConfig
+ * is overridden via {@link LocaleManager#setOverrideLocaleConfig(LocaleConfig)}.
+ *
+ * @param context the context of the application.
+ *
+ * @see Context#createPackageContext(String, int).
+ */
+ @NonNull
+ public static LocaleConfig fromResources(@NonNull Context context) {
+ return new LocaleConfig(context, false);
+ }
+
+ private LocaleConfig(@NonNull Context context, boolean allowOverride) {
+ if (allowOverride) {
+ LocaleManager localeManager = context.getSystemService(LocaleManager.class);
+ if (localeManager == null) {
+ Slog.w(TAG, "LocaleManager is null, cannot get the override LocaleConfig");
+ return;
+ }
+ LocaleConfig localeConfig = localeManager.getOverrideLocaleConfig();
+ if (localeConfig != null) {
+ Slog.d(TAG, "Has the override LocaleConfig");
+ mStatus = localeConfig.getStatus();
+ mLocales = localeConfig.getSupportedLocales();
+ return;
+ }
+ }
int resId = 0;
Resources res = context.getResources();
try {
@@ -109,6 +148,38 @@
}
/**
+ * Return the LocaleConfig with any sequence of locales combined into a {@link LocaleList}.
+ *
+ * <p><b>Note:</b> Applications seeking to create an override LocaleConfig via
+ * {@link LocaleManager#setOverrideLocaleConfig(LocaleConfig)} should use this constructor to
+ * first create the LocaleConfig they intend the system to see as the override.
+ *
+ * <p><b>Note:</b> The creation of this LocaleConfig does not automatically mean it will
+ * become the override config for an application. Any LocaleConfig desired to be the override
+ * must be passed into the {@link LocaleManager#setOverrideLocaleConfig(LocaleConfig)},
+ * otherwise it will not persist or affect the system’s understanding of app-supported
+ * resources.
+ *
+ * @param locales the desired locales for a specified application
+ */
+ public LocaleConfig(@NonNull LocaleList locales) {
+ mStatus = STATUS_SUCCESS;
+ mLocales = locales;
+ }
+
+ /**
+ * Instantiate a new LocaleConfig from the data in a Parcel that was
+ * previously written with {@link #writeToParcel(Parcel, int)}.
+ *
+ * @param in The Parcel containing the previously written LocaleConfig,
+ * positioned at the location in the buffer where it was written.
+ */
+ private LocaleConfig(@NonNull Parcel in) {
+ mStatus = in.readInt();
+ mLocales = in.readTypedObject(LocaleList.CREATOR);
+ }
+
+ /**
* Parse the XML content and get the locales supported by the application
*/
private void parseLocaleConfig(XmlResourceParser parser, Resources res)
@@ -165,4 +236,52 @@
public @Status int getStatus() {
return mStatus;
}
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mStatus);
+ dest.writeTypedObject(mLocales, flags);
+ }
+
+ public static final @NonNull Parcelable.Creator<LocaleConfig> CREATOR =
+ new Parcelable.Creator<LocaleConfig>() {
+ @Override
+ public LocaleConfig createFromParcel(Parcel source) {
+ return new LocaleConfig(source);
+ }
+
+ @Override
+ public LocaleConfig[] newArray(int size) {
+ return new LocaleConfig[size];
+ }
+ };
+
+ /**
+ * Compare whether the locale is existed in the {@code mLocales} of the LocaleConfig.
+ *
+ * @param locale The {@link Locale} to compare for.
+ *
+ * @return true if the locale is existed in the {@code mLocales} of the LocaleConfig, false
+ * otherwise.
+ *
+ * @hide
+ */
+ public boolean containsLocale(Locale locale) {
+ if (mLocales == null) {
+ return false;
+ }
+
+ for (int i = 0; i < mLocales.size(); i++) {
+ if (LocaleList.matchesLanguageAndScript(mLocales.get(i), locale)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
}
diff --git a/core/java/android/app/LocaleManager.java b/core/java/android/app/LocaleManager.java
index 70c014f..b62f766 100644
--- a/core/java/android/app/LocaleManager.java
+++ b/core/java/android/app/LocaleManager.java
@@ -18,6 +18,7 @@
import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -29,8 +30,9 @@
import android.os.RemoteException;
/**
- * This class gives access to system locale services. These services allow applications to control
- * granular locale settings (such as per-app locales).
+ * This class gives access to system locale services. These services allow applications to
+ * control granular locale settings (such as per-app locales) or override their list of supported
+ * locales while running.
*
* <p> Third party applications should treat this as a write-side surface, and continue reading
* locales via their in-process {@link LocaleList}s.
@@ -106,8 +108,8 @@
private void setApplicationLocales(@NonNull String appPackageName, @NonNull LocaleList locales,
boolean fromDelegate) {
try {
- mService.setApplicationLocales(appPackageName, mContext.getUser().getIdentifier(),
- locales, fromDelegate);
+ mService.setApplicationLocales(appPackageName, mContext.getUserId(), locales,
+ fromDelegate);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -144,8 +146,7 @@
@NonNull
public LocaleList getApplicationLocales(@NonNull String appPackageName) {
try {
- return mService.getApplicationLocales(appPackageName, mContext.getUser()
- .getIdentifier());
+ return mService.getApplicationLocales(appPackageName, mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -187,4 +188,49 @@
}
}
+ /**
+ * Sets the override LocaleConfig for the calling app.
+ *
+ * <p><b>Note:</b> Only the app itself with the same user can override its own LocaleConfig.
+ *
+ * <p><b>Note:</b> This function takes in a {@link LocaleConfig} which is intended to
+ * override the original config in the application’s resources. This LocaleConfig will become
+ * the override config, and stored in a system file for future access.
+ *
+ * <p><b>Note:</b> Using this function, applications can update their list of supported
+ * locales while running, without an update of the application’s software. For more
+ * information, see TODO(b/261528306): add link to guide.
+ *
+ * <p>Applications can remove the override LocaleConfig with a {@code null} object.
+ *
+ * @param localeConfig the desired {@link LocaleConfig} for the calling app.
+ */
+ @UserHandleAware
+ public void setOverrideLocaleConfig(@Nullable LocaleConfig localeConfig) {
+ try {
+ // The permission android.Manifest.permission#SET_APP_SPECIFIC_LOCALECONFIG is
+ // required to set an override LocaleConfig of another packages
+ mService.setOverrideLocaleConfig(mContext.getPackageName(), mContext.getUserId(),
+ localeConfig);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the override LocaleConfig for the calling app.
+ *
+ * @return the override LocaleConfig, or {@code null} if the LocaleConfig isn't overridden.
+ */
+ @Nullable
+ @UserHandleAware
+ public LocaleConfig getOverrideLocaleConfig() {
+ try {
+ return mService.getOverrideLocaleConfig(mContext.getPackageName(),
+ mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
}
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 9c33729..512b5e0 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -57,6 +57,7 @@
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
+import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
@@ -427,7 +428,7 @@
+ " PendingIntent, use FLAG_NO_CREATE, however, to create a"
+ " new PendingIntent with an implicit Intent use"
+ " FLAG_IMMUTABLE.";
- Log.wtfStack(TAG, msg);
+ Slog.wtfStack(TAG, msg);
} else {
String msg = "New mutable implicit PendingIntent: pkg=" + packageName
+ ", action=" + intent.getAction()
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index 6e784b2..f0d23ac 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -72,4 +72,16 @@
int createVirtualDisplay(in VirtualDisplayConfig virtualDisplayConfig,
in IVirtualDisplayCallback callback, in IVirtualDevice virtualDevice,
String packageName);
+
+ /**
+ * Returns device-specific session id for playback, or AUDIO_SESSION_ID_GENERATE
+ * if there's none.
+ */
+ int getAudioPlaybackSessionId(int deviceId);
+
+ /**
+ * Returns device-specific session id for recording, or AUDIO_SESSION_ID_GENERATE
+ * if there's none.
+ */
+ int getAudioRecordingSessionId(int deviceId);
}
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index cc452e2..088ac06 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -16,6 +16,8 @@
package android.companion.virtual;
+import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
+
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.IntRange;
@@ -255,6 +257,52 @@
}
/**
+ * Returns device-specific audio session id for audio playback.
+ *
+ * @param deviceId - id of the virtual audio device
+ * @return Device specific session id to be used for audio playback (see
+ * {@link android.media.AudioManager.generateAudioSessionId}) if virtual device has
+ * {@link VirtualDeviceParams.POLICY_TYPE_AUDIO} set to
+ * {@link VirtualDeviceParams.DEVICE_POLICY_CUSTOM} and Virtual Audio Device
+ * is configured in context-aware mode.
+ * Otherwise {@link AUDIO_SESSION_ID_GENERATE} constant is returned.
+ * @hide
+ */
+ public int getAudioPlaybackSessionId(int deviceId) {
+ if (mService == null) {
+ return AUDIO_SESSION_ID_GENERATE;
+ }
+ try {
+ return mService.getAudioPlaybackSessionId(deviceId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns device-specific audio session id for audio recording.
+ *
+ * @param deviceId - id of the virtual audio device
+ * @return Device specific session id to be used for audio recording (see
+ * {@link android.media.AudioManager.generateAudioSessionId}) if virtual device has
+ * {@link VirtualDeviceParams.POLICY_TYPE_AUDIO} set to
+ * {@link VirtualDeviceParams.DEVICE_POLICY_CUSTOM} and Virtual Audio Device
+ * is configured in context-aware mode.
+ * Otherwise {@link AUDIO_SESSION_ID_GENERATE} constant is returned.
+ * @hide
+ */
+ public int getAudioRecordingSessionId(int deviceId) {
+ if (mService == null) {
+ return AUDIO_SESSION_ID_GENERATE;
+ }
+ try {
+ return mService.getAudioRecordingSessionId(deviceId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* A virtual device has its own virtual display, audio output, microphone, and camera etc. The
* creator of a virtual device can take the output from the virtual display and stream it over
* to another device, and inject input events that are received from the remote device.
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index be77f2b..597b0f5 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -17,6 +17,7 @@
package android.companion.virtual;
import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
+import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -129,7 +130,7 @@
* a given policy type.
* @hide
*/
- @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_SENSORS})
+ @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_SENSORS, POLICY_TYPE_AUDIO})
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface PolicyType {}
@@ -147,6 +148,21 @@
*/
public static final int POLICY_TYPE_SENSORS = 0;
+ /**
+ * Tells the audio framework whether to configure the players ({@link android.media.AudioTrack},
+ * {@link android.media.MediaPlayer}, {@link android.media.SoundPool} and recorders
+ * {@link android.media.AudioRecord}) to use specific session ids re-routed to
+ * VirtualAudioDevice.
+ *
+ * <ul>
+ * <li>{@link #DEVICE_POLICY_DEFAULT}: fall back to default session id handling.
+ * <li>{@link #DEVICE_POLICY_CUSTOM}: audio framework will assign device specific session
+ * ids to players and recorders constructed within device context. The session ids are
+ * used to re-route corresponding audio streams to VirtualAudioDevice.
+ * <ul/>
+ */
+ public static final int POLICY_TYPE_AUDIO = 1;
+
/** @hide */
@IntDef(flag = true, prefix = "RECENTS_POLICY_",
value = {RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS})
@@ -176,6 +192,8 @@
@NonNull private final List<VirtualSensorConfig> mVirtualSensorConfigs;
@RecentsPolicy
private final int mDefaultRecentsPolicy;
+ private final int mAudioPlaybackSessionId;
+ private final int mAudioRecordingSessionId;
private VirtualDeviceParams(
@LockState int lockState,
@@ -189,7 +207,9 @@
@Nullable String name,
@NonNull SparseIntArray devicePolicies,
@NonNull List<VirtualSensorConfig> virtualSensorConfigs,
- @RecentsPolicy int defaultRecentsPolicy) {
+ @RecentsPolicy int defaultRecentsPolicy,
+ int audioPlaybackSessionId,
+ int audioRecordingSessionId) {
mLockState = lockState;
mUsersWithMatchingAccounts =
new ArraySet<>(Objects.requireNonNull(usersWithMatchingAccounts));
@@ -205,6 +225,9 @@
mDevicePolicies = Objects.requireNonNull(devicePolicies);
mVirtualSensorConfigs = Objects.requireNonNull(virtualSensorConfigs);
mDefaultRecentsPolicy = defaultRecentsPolicy;
+ mAudioPlaybackSessionId = audioPlaybackSessionId;
+ mAudioRecordingSessionId = audioRecordingSessionId;
+
}
@SuppressWarnings("unchecked")
@@ -222,6 +245,8 @@
mVirtualSensorConfigs = new ArrayList<>();
parcel.readTypedList(mVirtualSensorConfigs, VirtualSensorConfig.CREATOR);
mDefaultRecentsPolicy = parcel.readInt();
+ mAudioPlaybackSessionId = parcel.readInt();
+ mAudioRecordingSessionId = parcel.readInt();
}
/**
@@ -356,6 +381,24 @@
return mDefaultRecentsPolicy;
}
+ /**
+ * Returns device-specific audio session id for playback.
+ *
+ * @see Builder#setAudioPlaybackSessionId(int)
+ */
+ public int getAudioPlaybackSessionId() {
+ return mAudioPlaybackSessionId;
+ }
+
+ /**
+ * Returns device-specific audio session id for recording.
+ *
+ * @see Builder#setAudioRecordingSessionId(int)
+ */
+ public int getAudioRecordingSessionId() {
+ return mAudioRecordingSessionId;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -375,6 +418,8 @@
dest.writeSparseIntArray(mDevicePolicies);
dest.writeTypedList(mVirtualSensorConfigs);
dest.writeInt(mDefaultRecentsPolicy);
+ dest.writeInt(mAudioPlaybackSessionId);
+ dest.writeInt(mAudioRecordingSessionId);
}
@Override
@@ -407,7 +452,9 @@
&& Objects.equals(mBlockedActivities, that.mBlockedActivities)
&& mDefaultActivityPolicy == that.mDefaultActivityPolicy
&& Objects.equals(mName, that.mName)
- && mDefaultRecentsPolicy == that.mDefaultRecentsPolicy;
+ && mDefaultRecentsPolicy == that.mDefaultRecentsPolicy
+ && mAudioPlaybackSessionId == that.mAudioPlaybackSessionId
+ && mAudioRecordingSessionId == that.mAudioRecordingSessionId;
}
@Override
@@ -416,7 +463,7 @@
mLockState, mUsersWithMatchingAccounts, mAllowedCrossTaskNavigations,
mBlockedCrossTaskNavigations, mDefaultNavigationPolicy, mAllowedActivities,
mBlockedActivities, mDefaultActivityPolicy, mName, mDevicePolicies,
- mDefaultRecentsPolicy);
+ mDefaultRecentsPolicy, mAudioPlaybackSessionId, mAudioRecordingSessionId);
for (int i = 0; i < mDevicePolicies.size(); i++) {
hashCode = 31 * hashCode + mDevicePolicies.keyAt(i);
hashCode = 31 * hashCode + mDevicePolicies.valueAt(i);
@@ -439,6 +486,8 @@
+ " mName=" + mName
+ " mDevicePolicies=" + mDevicePolicies
+ " mDefaultRecentsPolicy=" + mDefaultRecentsPolicy
+ + " mAudioPlaybackSessionId=" + mAudioPlaybackSessionId
+ + " mAudioRecordingSessionId=" + mAudioRecordingSessionId
+ ")";
}
@@ -475,6 +524,8 @@
@NonNull private SparseIntArray mDevicePolicies = new SparseIntArray();
@NonNull private List<VirtualSensorConfig> mVirtualSensorConfigs = new ArrayList<>();
private int mDefaultRecentsPolicy;
+ private int mAudioPlaybackSessionId = AUDIO_SESSION_ID_GENERATE;
+ private int mAudioRecordingSessionId = AUDIO_SESSION_ID_GENERATE;
/**
* Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY}
@@ -691,6 +742,54 @@
}
/**
+ * Sets audio playback session id specific for this virtual device.
+ *
+ * <p>Audio players constructed within context associated with this virtual device
+ * will be automatically assigned provided session id.
+ *
+ * <p>Requires {@link #DEVICE_POLICY_CUSTOM} to be set for {@link #POLICY_TYPE_AUDIO},
+ * otherwise {@link #build()} method will throw {@link IllegalArgumentException} if
+ * the playback session id is set to value other than
+ * {@link android.media.AudioManager.AUDIO_SESSION_ID_GENERATE}.
+ *
+ * @param playbackSessionId requested device-specific audio session id for playback
+ * @see android.media.AudioManager.generateAudioSessionId()
+ * @see android.media.AudioTrack.Builder.setContext(Context)
+ */
+ @NonNull
+ public Builder setAudioPlaybackSessionId(int playbackSessionId) {
+ if (playbackSessionId != AUDIO_SESSION_ID_GENERATE || playbackSessionId < 0) {
+ throw new IllegalArgumentException("Invalid playback audio session id");
+ }
+ mAudioPlaybackSessionId = playbackSessionId;
+ return this;
+ }
+
+ /**
+ * Sets audio recording session id specific for this virtual device.
+ *
+ * <p>{@link android.media.AudioRecord} constructed within context associated with this
+ * virtual device will be automatically assigned provided session id.
+ *
+ * <p>Requires {@link #DEVICE_POLICY_CUSTOM} to be set for {@link #POLICY_TYPE_AUDIO},
+ * otherwise {@link #build()} method will throw {@link IllegalArgumentException} if
+ * the recording session id is set to value other than
+ * {@link android.media.AudioManager.AUDIO_SESSION_ID_GENERATE}.
+ *
+ * @param recordingSessionId requested device-specific audio session id for playback
+ * @see android.media.AudioManager.generateAudioSessionId()
+ * @see android.media.AudioRecord.Builder.setContext(Context)
+ */
+ @NonNull
+ public Builder setAudioRecordingSessionId(int recordingSessionId) {
+ if (recordingSessionId != AUDIO_SESSION_ID_GENERATE || recordingSessionId < 0) {
+ throw new IllegalArgumentException("Invalid recording audio session id");
+ }
+ mAudioRecordingSessionId = recordingSessionId;
+ return this;
+ }
+
+ /**
* Builds the {@link VirtualDeviceParams} instance.
*
* @throws IllegalArgumentException if there's mismatch between policy definition and
@@ -706,6 +805,15 @@
"DEVICE_POLICY_CUSTOM for POLICY_TYPE_SENSORS is required for creating "
+ "virtual sensors.");
}
+
+ if ((mAudioPlaybackSessionId != AUDIO_SESSION_ID_GENERATE
+ || mAudioRecordingSessionId != AUDIO_SESSION_ID_GENERATE)
+ && mDevicePolicies.get(POLICY_TYPE_AUDIO, DEVICE_POLICY_DEFAULT)
+ != DEVICE_POLICY_CUSTOM) {
+ throw new IllegalArgumentException("DEVICE_POLICY_CUSTOM for POLICY_TYPE_AUDIO is "
+ + "required for configuration of device-specific audio session ids.");
+ }
+
SparseArray<Set<String>> sensorNameByType = new SparseArray();
for (int i = 0; i < mVirtualSensorConfigs.size(); ++i) {
VirtualSensorConfig config = mVirtualSensorConfigs.get(i);
@@ -729,7 +837,9 @@
mName,
mDevicePolicies,
mVirtualSensorConfigs,
- mDefaultRecentsPolicy);
+ mDefaultRecentsPolicy,
+ mAudioPlaybackSessionId,
+ mAudioRecordingSessionId);
}
}
}
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index ed1f6a2..7803cb8 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -20,6 +20,7 @@
import android.annotation.NonUiContext;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.compat.Compatibility;
@@ -28,7 +29,6 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
-import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -39,40 +39,21 @@
import java.util.List;
/**
- * Updates OverlayManager state; gets information about installed overlay packages.
+ * OverlayManager gives apps the ability to create an {@link OverlayManagerTransaction} to
+ * maintain the overlays and list the registered fabricated runtime resources overlays(FRROs).
*
- * <p>Users of this API must be actors of any overlays they desire to change the state of.</p>
+ * <p>OverlayManager returns the list of overlays to the app calling {@link
+ * #getOverlayInfosForTarget(String)}. The app starts an {@link OverlayManagerTransaction} to manage
+ * the overlays. The app can achieve the following by using {@link OverlayManagerTransaction}.
*
- * <p>An actor is a package responsible for managing the state of overlays targeting overlayables
- * that specify the actor. For example, an actor may enable or disable an overlay or otherwise
- * change its state.</p>
- *
- * <p>Actors are specified as part of the overlayable definition.
- *
- * <pre>{@code
- * <overlayable name="OverlayableResourcesName" actor="overlay://namespace/actorName">
- * }</pre></p>
- *
- * <p>Actors are defined through SystemConfig. Only system packages can be used.
- * The namespace "android" is reserved for use by AOSP and any "android" definitions must
- * have an implementation on device that fulfill their intended functionality.</p>
- *
- * <pre>{@code
- * <named-actor
- * namespace="namespace"
- * name="actorName"
- * package="com.example.pkg"
- * />
- * }</pre></p>
- *
- * <p>An actor can manipulate a particular overlay if any of the following is true:
* <ul>
- * <li>its UID is {@link Process#ROOT_UID}, {@link Process#SYSTEM_UID}</li>
- * <li>it is the target of the overlay package</li>
- * <li>it has the CHANGE_OVERLAY_PACKAGES permission and the target does not specify an actor</li>
- * <li>it is the actor specified by the overlayable</li>
- * </ul></p>
+ * <li>register overlays
+ * <li>unregister overlays
+ * <li>execute multiple operations in one commitment by calling {@link
+ * OverlayManagerTransaction#commit()}
+ * </ul>
*
+ * @see OverlayManagerTransaction
* @hide
*/
@SystemApi
@@ -85,7 +66,7 @@
/**
* Pre R a {@link java.lang.SecurityException} would only be thrown by setEnabled APIs (e
- * .g. {@link #setEnabled(String, boolean, UserHandle)}) for a permission error.
+ * .g. {@code #setEnabled(String, boolean, UserHandle)}) for a permission error.
* Since R this no longer holds true, and {@link java.lang.SecurityException} can be
* thrown for any number of reasons, none of which are exposed to the caller.
*
@@ -115,19 +96,56 @@
/**
* Creates a new instance.
*
+ * Updates OverlayManager state; gets information about installed overlay packages.
+ * <p>Users of this API must be actors of any overlays they desire to change the state of.
+ *
+ * <p>An actor is a package responsible for managing the state of overlays targeting
+ * overlayables that specify the actor. For example, an actor may enable or disable an overlay
+ * or otherwise change its state.
+ *
+ * <p>Actors are specified as part of the overlayable definition.
+ *
+ * <pre>{@code
+ * <overlayable name="OverlayableResourcesName" actor="overlay://namespace/actorName">
+ * }</pre></p>
+ *
+ * <p>Actors are defined through {@code com.android.server.SystemConfig}. Only system packages
+ * can be used. The namespace "android" is reserved for use by AOSP and any "android"
+ * definitions must have an implementation on device that fulfill their intended functionality.
+ *
+ * <pre>{@code
+ * <named-actor
+ * namespace="namespace"
+ * name="actorName"
+ * package="com.example.pkg"
+ * />
+ * }</pre></p>
+ *
+ * <p>An actor can manipulate a particular overlay if any of the following is true:
+ * <ul>
+ * <li>its UID is {@link android.os.Process#ROOT_UID}, {@link android.os.Process#SYSTEM_UID}
+ * </li>
+ * <li>it is the target of the overlay package</li>
+ * <li>it has the CHANGE_OVERLAY_PACKAGES permission and the target does not specify an actor
+ * </li>
+ * <li>it is the actor specified by the overlayable</li>
+ * </ul></p>
+ *
* @param context The current context in which to operate.
* @param service The backing system service.
*
* @hide
*/
- public OverlayManager(Context context, IOverlayManager service) {
+ @SuppressLint("ReferencesHidden")
+ public OverlayManager(@NonNull Context context, @Nullable IOverlayManager service) {
mContext = context;
mService = service;
mOverlayManagerImpl = new OverlayManagerImpl(context);
}
/** @hide */
- public OverlayManager(Context context) {
+ @SuppressLint("ReferencesHidden")
+ public OverlayManager(@NonNull Context context) {
this(context, IOverlayManager.Stub.asInterface(
ServiceManager.getService(Context.OVERLAY_SERVICE)));
}
@@ -137,7 +155,7 @@
* target package and category are disabled.
*
* If a set of overlay packages share the same category, single call to this method is
- * equivalent to multiple calls to {@link #setEnabled(String, boolean, UserHandle)}.
+ * equivalent to multiple calls to {@code #setEnabled(String, boolean, UserHandle)}.
*
* The caller must pass the actor requirements specified in the class comment.
*
@@ -352,17 +370,6 @@
}
/**
- * Get a OverlayManagerTransaction.Builder to build out a overlay manager transaction.
- *
- * @return a builder of the overlay manager transaction.
- * @hide
- */
- @NonNull
- public OverlayManagerTransaction.Builder beginTransaction() {
- return new OverlayManagerTransaction.Builder(this);
- }
-
- /**
* Commit the self-targeting transaction to register or unregister overlays.
*
* <p>Applications can request OverlayManager to register overlays and unregister the registered
diff --git a/core/java/android/content/om/OverlayManagerTransaction.java b/core/java/android/content/om/OverlayManagerTransaction.java
index 42b3ef3..c7c605d 100644
--- a/core/java/android/content/om/OverlayManagerTransaction.java
+++ b/core/java/android/content/om/OverlayManagerTransaction.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.NonUiContext;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Parcel;
@@ -38,29 +39,49 @@
import java.util.Objects;
/**
- * Container for a batch of requests to the OverlayManagerService.
+ * A container for a batch of requests to the OverlayManager.
*
- * Transactions are created using a builder interface. Example usage:
+ * <p>An app can get an {@link OverlayManagerTransaction} with the specified {@link OverlayManager}
+ * to handle the transaction. The app can register multiple overlays and unregister multiple
+ * registered overlays in one transaction commitment.
*
- * final OverlayManager om = ctx.getSystemService(OverlayManager.class);
- * final OverlayManagerTransaction t = new OverlayManagerTransaction.Builder()
- * .setEnabled(...)
- * .setEnabled(...)
- * .build();
- * om.commit(t);
+ * <p>The below example is registering a {@code updatingOverlay} and unregistering a {@code
+ * deprecatedOverlay} in one transaction commitment.
*
+ * <pre>{@code
+ * final OverlayManager overlayManager = ctx.getSystemService(OverlayManager.class);
+ * final OverlayManagerTransaction transaction = new OverlayManagerTransaction(overlayManager);
+ * transaction.registerFabricatedOverlay(updatingOverlay);
+ * transaction.unregisterFabricatedOverlay(deprecatedOverlay)
+ * transaction.commit();
+ * }</pre>
+ *
+ * @see OverlayManager
+ * @see FabricatedOverlay
* @hide
*/
-public class OverlayManagerTransaction
- implements Iterable<OverlayManagerTransaction.Request>, Parcelable {
+public final class OverlayManagerTransaction implements Parcelable {
// TODO: remove @hide from this class when OverlayManager is added to the
// SDK, but keep OverlayManagerTransaction.Request @hidden
private final List<Request> mRequests;
private final OverlayManager mOverlayManager;
- OverlayManagerTransaction(
+ /**
+ * Container for a batch of requests to the OverlayManagerService.
+ *
+ * <p>Transactions are created using a builder interface. Example usage:
+ * <pre>{@code
+ * final OverlayManager om = ctx.getSystemService(OverlayManager.class);
+ * final OverlayManagerTransaction t = new OverlayManagerTransaction.Builder()
+ * .setEnabled(...)
+ * .setEnabled(...)
+ * .build();
+ * om.commit(t);
+ * }</pre>
+ */
+ private OverlayManagerTransaction(
@NonNull final List<Request> requests, @Nullable OverlayManager overlayManager) {
- checkNotNull(requests);
+ Objects.requireNonNull(requests);
if (requests.contains(null)) {
throw new IllegalArgumentException("null request");
}
@@ -68,6 +89,16 @@
mOverlayManager = overlayManager;
}
+ /**
+ * Get an overlay manager transaction with the specified handler.
+ * @param overlayManager handles this transaction.
+ *
+ * @hide
+ */
+ public OverlayManagerTransaction(@NonNull OverlayManager overlayManager) {
+ this(new ArrayList<>(), Objects.requireNonNull(overlayManager));
+ }
+
private OverlayManagerTransaction(@NonNull final Parcel source) {
final int size = source.readInt();
mRequests = new ArrayList<>(size);
@@ -81,11 +112,23 @@
mOverlayManager = null;
}
- @Override
- public Iterator<Request> iterator() {
+ /**
+ * Get the iterator of requests
+ *
+ * @return the iterator of request
+ * @hide
+ */
+ @SuppressLint("ReferencesHidden")
+ @NonNull
+ public Iterator<Request> getRequests() {
return mRequests.iterator();
}
+ /**
+ * {@inheritDoc}
+ *
+ * @hide
+ */
@Override
public String toString() {
return String.format("OverlayManagerTransaction { mRequests = %s }", mRequests);
@@ -97,7 +140,7 @@
*
* @hide
*/
- public static class Request {
+ public static final class Request {
@IntDef(prefix = "TYPE_", value = {
TYPE_SET_ENABLED,
TYPE_SET_DISABLED,
@@ -117,6 +160,8 @@
@NonNull
public final OverlayIdentifier overlay;
public final int userId;
+
+ @SuppressLint("NullableCollection")
@Nullable
public final Bundle extras;
@@ -161,22 +206,8 @@
*
* @hide
*/
- public static class Builder {
+ public static final class Builder {
private final List<Request> mRequests = new ArrayList<>();
- @Nullable private final OverlayManager mOverlayManager;
-
- public Builder() {
- mOverlayManager = null;
- }
-
- /**
- * The transaction builder for self-targeting.
- *
- * @param overlayManager is not null if the transaction is for self-targeting.
- */
- Builder(@NonNull OverlayManager overlayManager) {
- mOverlayManager = Objects.requireNonNull(overlayManager);
- }
/**
* Request that an overlay package be enabled and change its loading
@@ -228,12 +259,7 @@
*/
@NonNull
public Builder registerFabricatedOverlay(@NonNull FabricatedOverlay overlay) {
- Objects.requireNonNull(overlay);
-
- final Bundle extras = new Bundle();
- extras.putParcelable(Request.BUNDLE_FABRICATED_OVERLAY, overlay.mOverlay);
- mRequests.add(new Request(Request.TYPE_REGISTER_FABRICATED, overlay.getIdentifier(),
- UserHandle.USER_ALL, extras));
+ mRequests.add(generateRegisterFabricatedOverlayRequest(overlay));
return this;
}
@@ -246,10 +272,7 @@
*/
@NonNull
public Builder unregisterFabricatedOverlay(@NonNull OverlayIdentifier overlay) {
- Objects.requireNonNull(overlay);
-
- mRequests.add(new Request(Request.TYPE_UNREGISTER_FABRICATED, overlay,
- UserHandle.USER_ALL));
+ mRequests.add(generateUnRegisterFabricatedOverlayRequest(overlay));
return this;
}
@@ -262,17 +285,27 @@
*/
@NonNull
public OverlayManagerTransaction build() {
- return new OverlayManagerTransaction(mRequests, mOverlayManager);
+ return new OverlayManagerTransaction(mRequests, null /* overlayManager */);
}
}
+ /**
+ * {@inheritDoc}
+ *
+ * @hide
+ */
@Override
public int describeContents() {
return 0;
}
+ /**
+ * {@inheritDoc}
+ *
+ * @hide
+ */
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
final int size = mRequests.size();
dest.writeInt(size);
for (int i = 0; i < size; i++) {
@@ -284,6 +317,7 @@
}
}
+ @NonNull
public static final Parcelable.Creator<OverlayManagerTransaction> CREATOR =
new Parcelable.Creator<OverlayManagerTransaction>() {
@@ -313,6 +347,55 @@
mOverlayManager.commitSelfTarget(this);
}
+ private static Request generateRegisterFabricatedOverlayRequest(
+ @NonNull FabricatedOverlay overlay) {
+ Objects.requireNonNull(overlay);
+
+ final Bundle extras = new Bundle();
+ extras.putParcelable(Request.BUNDLE_FABRICATED_OVERLAY, overlay.mOverlay);
+ return new Request(Request.TYPE_REGISTER_FABRICATED, overlay.getIdentifier(),
+ UserHandle.USER_ALL, extras);
+ }
+
+ private static Request generateUnRegisterFabricatedOverlayRequest(
+ @NonNull OverlayIdentifier overlayIdentifier) {
+ Objects.requireNonNull(overlayIdentifier);
+
+ return new Request(Request.TYPE_UNREGISTER_FABRICATED, overlayIdentifier,
+ UserHandle.USER_ALL);
+ }
+
+ /**
+ * Registers the fabricated overlays with the overlay manager so it can be used to overlay
+ * the app resources in runtime.
+ *
+ * <p>If an overlay is re-registered the existing overlay will be replaced by the newly
+ * registered overlay. The registered overlay will be left unchanged until the target
+ * package or target overlayable is changed.
+ *
+ * @param overlay the overlay to register with the overlay manager
+ *
+ * @hide
+ */
+ @NonNull
+ public void registerFabricatedOverlay(@NonNull FabricatedOverlay overlay) {
+ mRequests.add(generateRegisterFabricatedOverlayRequest(overlay));
+ }
+
+ /**
+ * Unregisters the registered overlays from the overlay manager.
+ *
+ * @param overlay the overlay to be unregistered
+ *
+ * @see OverlayManager#getOverlayInfosForTarget(String)
+ * @see OverlayInfo#getOverlayIdentifier()
+ * @hide
+ */
+ @NonNull
+ public void unregisterFabricatedOverlay(@NonNull OverlayIdentifier overlay) {
+ mRequests.add(generateUnRegisterFabricatedOverlayRequest(overlay));
+ }
+
boolean isSelfTargetingTransaction() {
return mOverlayManager != null;
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b6ca159..be19203 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1427,22 +1427,6 @@
*/
public static final int INSTALL_GRANT_RUNTIME_PERMISSIONS = 0x00000100;
- /**
- * Flag parameter for {@link #installPackage} to indicate that all restricted
- * permissions should be whitelisted. If {@link #INSTALL_ALL_USERS}
- * is set the restricted permissions will be whitelisted for all users, otherwise
- * only to the owner.
- *
- * <p>
- * <strong>Note: </strong>In retrospect it would have been preferred to use
- * more inclusive terminology when naming this API. Similar APIs added will
- * refrain from using the term "whitelist".
- * </p>
- *
- * @hide
- */
- public static final int INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS = 0x00400000;
-
/** {@hide} */
public static final int INSTALL_FORCE_VOLUME_UUID = 0x00000200;
@@ -1539,11 +1523,27 @@
public static final int INSTALL_STAGED = 0x00200000;
/**
+ * Flag parameter for {@link #installPackage} to indicate that all restricted
+ * permissions should be allowlisted. If {@link #INSTALL_ALL_USERS}
+ * is set the restricted permissions will be allowlisted for all users, otherwise
+ * only to the owner.
+ *
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
+ *
+ * @hide
+ */
+ public static final int INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS = 0x00400000;
+
+ /**
* Flag parameter for {@link #installPackage} to indicate that check whether given APEX can be
* updated should be disabled for this install.
* @hide
*/
- public static final int INSTALL_DISABLE_ALLOWED_APEX_UPDATE_CHECK = 0x00400000;
+ public static final int INSTALL_DISABLE_ALLOWED_APEX_UPDATE_CHECK = 0x00800000;
/** @hide */
@IntDef(flag = true, value = {
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 8b614d8..23bb22f 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -50,13 +50,19 @@
@SystemService(Context.CREDENTIAL_SERVICE)
public final class CredentialManager {
private static final String TAG = "CredentialManager";
- private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER =
- "enable_credential_manager";
private final Context mContext;
private final ICredentialManager mService;
/**
+ * Flag to enable and disable Credential Manager.
+ *
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER =
+ "enable_credential_manager";
+
+ /**
* @hide instantiated by ContextImpl.
*/
public CredentialManager(Context context, ICredentialManager service) {
diff --git a/core/java/android/hardware/DataSpace.java b/core/java/android/hardware/DataSpace.java
index 15eae09..0a14574 100644
--- a/core/java/android/hardware/DataSpace.java
+++ b/core/java/android/hardware/DataSpace.java
@@ -408,6 +408,19 @@
*/
public static final int DATASPACE_HEIF = 4100;
+ /**
+ * ISO/IEC TBD
+ *
+ * JPEG image with embedded recovery map following the Jpeg/R specification.
+ *
+ * <p>This value must always remain aligned with the public ImageFormat Jpeg/R definition and is
+ * valid with formats:
+ * HAL_PIXEL_FORMAT_BLOB: JPEG image encoded by Jpeg/R encoder according to ISO/IEC TBD.
+ * The image contains a standard SDR JPEG and a recovery map. Jpeg/R decoders can use the
+ * map to recover the input image.</p>
+ */
+ public static final int DATASPACE_JPEG_R = 4101;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {
@@ -626,6 +639,7 @@
DATASPACE_DEPTH,
DATASPACE_DYNAMIC_DEPTH,
DATASPACE_HEIF,
+ DATASPACE_JPEG_R,
DATASPACE_UNKNOWN,
DATASPACE_SCRGB_LINEAR,
DATASPACE_SRGB,
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 6e72b5f..a6f7e94 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -5562,6 +5562,115 @@
public static final Key<Integer> AUTOMOTIVE_LOCATION =
new Key<Integer>("android.automotive.location", int.class);
+ /**
+ * <p>The available Jpeg/R stream
+ * configurations that this camera device supports
+ * (i.e. format, width, height, output/input stream).</p>
+ * <p>The configurations are listed as <code>(format, width, height, input?)</code> tuples.</p>
+ * <p>If the camera device supports Jpeg/R, it will support the same stream combinations with
+ * Jpeg/R as it does with P010. The stream combinations with Jpeg/R (or P010) supported
+ * by the device is determined by the device's hardware level and capabilities.</p>
+ * <p>All the static, control, and dynamic metadata tags related to JPEG apply to Jpeg/R formats.
+ * Configuring JPEG and Jpeg/R streams at the same time is not supported.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfiguration[]> JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS =
+ new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.jpegr.availableJpegRStreamConfigurations", android.hardware.camera2.params.StreamConfiguration[].class);
+
+ /**
+ * <p>This lists the minimum frame duration for each
+ * format/size combination for Jpeg/R output formats.</p>
+ * <p>This should correspond to the frame duration when only that
+ * stream is active, with all processing (typically in android.*.mode)
+ * set to either OFF or FAST.</p>
+ * <p>When multiple streams are used in a request, the minimum frame
+ * duration will be max(individual stream min durations).</p>
+ * <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} and
+ * android.scaler.availableStallDurations for more details about
+ * calculating the max frame rate.</p>
+ * <p><b>Units</b>: (format, width, height, ns) x n</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> JPEGR_AVAILABLE_JPEG_R_MIN_FRAME_DURATIONS =
+ new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.jpegr.availableJpegRMinFrameDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+ /**
+ * <p>This lists the maximum stall duration for each
+ * output format/size combination for Jpeg/R streams.</p>
+ * <p>A stall duration is how much extra time would get added
+ * to the normal minimum frame duration for a repeating request
+ * that has streams with non-zero stall.</p>
+ * <p>This functions similarly to
+ * android.scaler.availableStallDurations for Jpeg/R
+ * streams.</p>
+ * <p>All Jpeg/R output stream formats may have a nonzero stall
+ * duration.</p>
+ * <p><b>Units</b>: (format, width, height, ns) x n</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS =
+ new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.jpegr.availableJpegRStallDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+ /**
+ * <p>The available Jpeg/R stream
+ * configurations that this camera device supports
+ * (i.e. format, width, height, output/input stream).</p>
+ * <p>Refer to android.jpegr.availableJpegRStreamConfigurations for details.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfiguration[]> JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION =
+ new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.jpegr.availableJpegRStreamConfigurationsMaximumResolution", android.hardware.camera2.params.StreamConfiguration[].class);
+
+ /**
+ * <p>This lists the minimum frame duration for each
+ * format/size combination for Jpeg/R output formats for CaptureRequests where
+ * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>Refer to android.jpegr.availableJpegRMinFrameDurations for details.</p>
+ * <p><b>Units</b>: (format, width, height, ns) x n</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> JPEGR_AVAILABLE_JPEG_R_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION =
+ new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.jpegr.availableJpegRMinFrameDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+ /**
+ * <p>This lists the maximum stall duration for each
+ * output format/size combination for Jpeg/R streams for CaptureRequests where
+ * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>Refer to android.jpegr.availableJpegRStallDurations for details.</p>
+ * <p><b>Units</b>: (format, width, height, ns) x n</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS_MAXIMUM_RESOLUTION =
+ new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.jpegr.availableJpegRStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
/*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
* End generated code
*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 10a7538..bf2e563 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -859,6 +859,28 @@
* format {@link android.graphics.ImageFormat#YUV_420_888} with a 10-bit profile
* will cause a capture session initialization failure.
* </p>
+ * <p>{@link android.graphics.ImageFormat#JPEG_R} may also be supported if advertised by
+ * {@link android.hardware.camera2.params.StreamConfigurationMap}. When initializing a capture
+ * session that includes a Jpeg/R camera output clients must consider the following items w.r.t.
+ * the 10-bit mandatory stream combination table:
+ *
+ * <ul>
+ * <li>To generate the compressed Jpeg/R image a single
+ * {@link android.graphics.ImageFormat#YCBCR_P010} output will be used internally by
+ * the camera device.</li>
+ * <li>On camera devices that are able to support concurrent 10 and 8-bit capture requests
+ * see {@link android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints}
+ * an extra {@link android.graphics.ImageFormat#JPEG} will also
+ * be configured internally to help speed up the encoding process.</li>
+ * </ul>
+ *
+ * Jpeg/R camera outputs will typically be able to support the MAXIMUM device resolution.
+ * Clients can also call {@link StreamConfigurationMap#getOutputSizes(int)} for a complete list
+ * supported sizes.
+ * Camera clients that register a Jpeg/R output within a stream combination that doesn't fit
+ * in the mandatory stream table above can call
+ * {@link CameraDevice#isSessionConfigurationSupported} to ensure that this particular
+ * configuration is supported.</p>
*
* <p>Devices with the STREAM_USE_CASE capability ({@link
* CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} includes {@link
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 012fad5..9a16474 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -1351,6 +1351,9 @@
/*heicconfiguration*/ null,
/*heicminduration*/ null,
/*heicstallduration*/ null,
+ /*jpegRconfiguration*/ null,
+ /*jpegRminduration*/ null,
+ /*jpegRstallduration*/ null,
/*highspeedvideoconfigurations*/ null,
/*inputoutputformatsmap*/ null, listHighResolution, supportsPrivate[i]);
break;
@@ -1365,6 +1368,9 @@
/*heicconfiguration*/ null,
/*heicminduration*/ null,
/*heicstallduration*/ null,
+ /*jpegRconfiguration*/ null,
+ /*jpegRminduration*/ null,
+ /*jpegRstallduration*/ null,
highSpeedVideoConfigurations,
/*inputoutputformatsmap*/ null, listHighResolution, supportsPrivate[i]);
break;
@@ -1379,6 +1385,9 @@
/*heicconfiguration*/ null,
/*heicminduration*/ null,
/*heicstallduration*/ null,
+ /*jpegRconfiguration*/ null,
+ /*jpegRminduration*/ null,
+ /*jpegRstallduration*/ null,
/*highSpeedVideoConfigurations*/ null,
inputOutputFormatsMap, listHighResolution, supportsPrivate[i]);
break;
@@ -1393,6 +1402,9 @@
/*heicconfiguration*/ null,
/*heicminduration*/ null,
/*heicstallduration*/ null,
+ /*jpegRconfiguration*/ null,
+ /*jpegRminduration*/ null,
+ /*jpegRstallduration*/ null,
/*highSpeedVideoConfigurations*/ null,
/*inputOutputFormatsMap*/ null, listHighResolution, supportsPrivate[i]);
}
@@ -1546,6 +1558,12 @@
CameraCharacteristics.HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS);
StreamConfigurationDuration[] heicStallDurations = getBase(
CameraCharacteristics.HEIC_AVAILABLE_HEIC_STALL_DURATIONS);
+ StreamConfiguration[] jpegRConfigurations = getBase(
+ CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS);
+ StreamConfigurationDuration[] jpegRMinFrameDurations = getBase(
+ CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_MIN_FRAME_DURATIONS);
+ StreamConfigurationDuration[] jpegRStallDurations = getBase(
+ CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS);
HighSpeedVideoConfiguration[] highSpeedVideoConfigurations = getBase(
CameraCharacteristics.CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS);
ReprocessFormatsMap inputOutputFormatsMap = getBase(
@@ -1557,6 +1575,7 @@
dynamicDepthConfigurations, dynamicDepthMinFrameDurations,
dynamicDepthStallDurations, heicConfigurations,
heicMinFrameDurations, heicStallDurations,
+ jpegRConfigurations, jpegRMinFrameDurations, jpegRStallDurations,
highSpeedVideoConfigurations, inputOutputFormatsMap,
listHighResolution);
}
@@ -1589,6 +1608,12 @@
CameraCharacteristics.HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION);
StreamConfigurationDuration[] heicStallDurations = getBase(
CameraCharacteristics.HEIC_AVAILABLE_HEIC_STALL_DURATIONS_MAXIMUM_RESOLUTION);
+ StreamConfiguration[] jpegRConfigurations = getBase(
+ CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION);
+ StreamConfigurationDuration[] jpegRMinFrameDurations = getBase(
+ CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION);
+ StreamConfigurationDuration[] jpegRStallDurations = getBase(
+ CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS_MAXIMUM_RESOLUTION);
HighSpeedVideoConfiguration[] highSpeedVideoConfigurations = getBase(
CameraCharacteristics.CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS_MAXIMUM_RESOLUTION);
ReprocessFormatsMap inputOutputFormatsMap = getBase(
@@ -1601,6 +1626,7 @@
dynamicDepthConfigurations, dynamicDepthMinFrameDurations,
dynamicDepthStallDurations, heicConfigurations,
heicMinFrameDurations, heicStallDurations,
+ jpegRConfigurations, jpegRMinFrameDurations, jpegRStallDurations,
highSpeedVideoConfigurations, inputOutputFormatsMap,
listHighResolution, false);
}
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index 5981d27..cb678b9 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -26,6 +26,7 @@
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.utils.HashCodeHelpers;
import android.hardware.camera2.utils.SurfaceUtils;
+import android.util.Log;
import android.util.Range;
import android.util.Size;
import android.util.SparseIntArray;
@@ -95,6 +96,11 @@
* {@link StreamConfigurationDuration}
* @param heicStallDurations a non-{@code null} array of heic
* {@link StreamConfigurationDuration}
+ * @param jpegRConfigurations a non-{@code null} array of Jpeg/R {@link StreamConfiguration}
+ * @param jpegRMinFrameDurations a non-{@code null} array of Jpeg/R
+ * {@link StreamConfigurationDuration}
+ * @param jpegRStallDurations a non-{@code null} array of Jpeg/R
+ * {@link StreamConfigurationDuration}
* @param highSpeedVideoConfigurations an array of {@link HighSpeedVideoConfiguration}, null if
* camera device does not support high speed video recording
* @param listHighResolution a flag indicating whether the device supports BURST_CAPTURE
@@ -117,6 +123,9 @@
StreamConfiguration[] heicConfigurations,
StreamConfigurationDuration[] heicMinFrameDurations,
StreamConfigurationDuration[] heicStallDurations,
+ StreamConfiguration[] jpegRConfigurations,
+ StreamConfigurationDuration[] jpegRMinFrameDurations,
+ StreamConfigurationDuration[] jpegRStallDurations,
HighSpeedVideoConfiguration[] highSpeedVideoConfigurations,
ReprocessFormatsMap inputOutputFormatsMap,
boolean listHighResolution) {
@@ -125,6 +134,7 @@
dynamicDepthConfigurations, dynamicDepthMinFrameDurations,
dynamicDepthStallDurations,
heicConfigurations, heicMinFrameDurations, heicStallDurations,
+ jpegRConfigurations, jpegRMinFrameDurations, jpegRStallDurations,
highSpeedVideoConfigurations, inputOutputFormatsMap, listHighResolution,
/*enforceImplementationDefined*/ true);
}
@@ -154,6 +164,11 @@
* {@link StreamConfigurationDuration}
* @param heicStallDurations a non-{@code null} array of heic
* {@link StreamConfigurationDuration}
+ * @param jpegRConfigurations a non-{@code null} array of Jpeg/R {@link StreamConfiguration}
+ * @param jpegRMinFrameDurations a non-{@code null} array of Jpeg/R
+ * {@link StreamConfigurationDuration}
+ * @param jpegRStallDurations a non-{@code null} array of Jpeg/R
+ * {@link StreamConfigurationDuration}
* @param highSpeedVideoConfigurations an array of {@link HighSpeedVideoConfiguration}, null if
* camera device does not support high speed video recording
* @param listHighResolution a flag indicating whether the device supports BURST_CAPTURE
@@ -178,6 +193,9 @@
StreamConfiguration[] heicConfigurations,
StreamConfigurationDuration[] heicMinFrameDurations,
StreamConfigurationDuration[] heicStallDurations,
+ StreamConfiguration[] jpegRConfigurations,
+ StreamConfigurationDuration[] jpegRMinFrameDurations,
+ StreamConfigurationDuration[] jpegRStallDurations,
HighSpeedVideoConfiguration[] highSpeedVideoConfigurations,
ReprocessFormatsMap inputOutputFormatsMap,
boolean listHighResolution,
@@ -242,6 +260,20 @@
"heicStallDurations");
}
+
+ if (jpegRConfigurations == null) {
+ mJpegRConfigurations = new StreamConfiguration[0];
+ mJpegRMinFrameDurations = new StreamConfigurationDuration[0];
+ mJpegRStallDurations = new StreamConfigurationDuration[0];
+ } else {
+ mJpegRConfigurations = checkArrayElementsNotNull(jpegRConfigurations,
+ "jpegRConfigurations");
+ mJpegRMinFrameDurations = checkArrayElementsNotNull(jpegRMinFrameDurations,
+ "jpegRFrameDurations");
+ mJpegRStallDurations = checkArrayElementsNotNull(jpegRStallDurations,
+ "jpegRStallDurations");
+ }
+
if (highSpeedVideoConfigurations == null) {
mHighSpeedVideoConfigurations = new HighSpeedVideoConfiguration[0];
} else {
@@ -305,6 +337,17 @@
mHeicOutputFormats.get(config.getFormat()) + 1);
}
+ // For each Jpeg/R format, track how many sizes there are available to configure
+ for (StreamConfiguration config : mJpegRConfigurations) {
+ if (!config.isOutput()) {
+ // Ignoring input Jpeg/R configs
+ continue;
+ }
+
+ mJpegROutputFormats.put(config.getFormat(),
+ mJpegROutputFormats.get(config.getFormat()) + 1);
+ }
+
if (configurations != null && enforceImplementationDefined &&
mOutputFormats.indexOfKey(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) < 0) {
throw new AssertionError(
@@ -447,6 +490,8 @@
return mDynamicDepthOutputFormats.indexOfKey(internalFormat) >= 0;
} else if (dataspace == HAL_DATASPACE_HEIF) {
return mHeicOutputFormats.indexOfKey(internalFormat) >= 0;
+ } else if (dataspace == HAL_DATASPACE_JPEG_R) {
+ return mJpegROutputFormats.indexOfKey(internalFormat) >= 0;
} else {
return getFormatsMap(/*output*/true).indexOfKey(internalFormat) >= 0;
}
@@ -561,6 +606,7 @@
surfaceDataspace == HAL_DATASPACE_DEPTH ? mDepthConfigurations :
surfaceDataspace == HAL_DATASPACE_DYNAMIC_DEPTH ? mDynamicDepthConfigurations :
surfaceDataspace == HAL_DATASPACE_HEIF ? mHeicConfigurations :
+ surfaceDataspace == HAL_DATASPACE_JPEG_R ? mJpegRConfigurations :
mConfigurations;
for (StreamConfiguration config : configs) {
if (config.getFormat() == surfaceFormat && config.isOutput()) {
@@ -597,6 +643,7 @@
dataspace == HAL_DATASPACE_DEPTH ? mDepthConfigurations :
dataspace == HAL_DATASPACE_DYNAMIC_DEPTH ? mDynamicDepthConfigurations :
dataspace == HAL_DATASPACE_HEIF ? mHeicConfigurations :
+ dataspace == HAL_DATASPACE_JPEG_R ? mJpegRConfigurations :
mConfigurations;
for (StreamConfiguration config : configs) {
if ((config.getFormat() == internalFormat) && config.isOutput() &&
@@ -1120,6 +1167,9 @@
Arrays.equals(mHeicConfigurations, other.mHeicConfigurations) &&
Arrays.equals(mHeicMinFrameDurations, other.mHeicMinFrameDurations) &&
Arrays.equals(mHeicStallDurations, other.mHeicStallDurations) &&
+ Arrays.equals(mJpegRConfigurations, other.mJpegRConfigurations) &&
+ Arrays.equals(mJpegRMinFrameDurations, other.mJpegRMinFrameDurations) &&
+ Arrays.equals(mJpegRStallDurations, other.mJpegRStallDurations) &&
Arrays.equals(mHighSpeedVideoConfigurations,
other.mHighSpeedVideoConfigurations);
}
@@ -1138,6 +1188,7 @@
mDynamicDepthConfigurations, mDynamicDepthMinFrameDurations,
mDynamicDepthStallDurations, mHeicConfigurations,
mHeicMinFrameDurations, mHeicStallDurations,
+ mJpegRConfigurations, mJpegRMinFrameDurations, mJpegRStallDurations,
mHighSpeedVideoConfigurations);
}
@@ -1161,6 +1212,10 @@
if (mHeicOutputFormats.indexOfKey(internalFormat) >= 0) {
return format;
}
+ } else if (internalDataspace == HAL_DATASPACE_JPEG_R) {
+ if (mJpegROutputFormats.indexOfKey(internalFormat) >= 0) {
+ return format;
+ }
} else {
if (mAllOutputFormats.indexOfKey(internalFormat) >= 0) {
return format;
@@ -1365,6 +1420,7 @@
* <li>ImageFormat.DEPTH_POINT_CLOUD => HAL_PIXEL_FORMAT_BLOB
* <li>ImageFormat.DEPTH_JPEG => HAL_PIXEL_FORMAT_BLOB
* <li>ImageFormat.HEIC => HAL_PIXEL_FORMAT_BLOB
+ * <li>ImageFormat.JPEG_R => HAL_PIXEL_FORMAT_BLOB
* <li>ImageFormat.DEPTH16 => HAL_PIXEL_FORMAT_Y16
* </ul>
* </p>
@@ -1391,6 +1447,7 @@
case ImageFormat.DEPTH_POINT_CLOUD:
case ImageFormat.DEPTH_JPEG:
case ImageFormat.HEIC:
+ case ImageFormat.JPEG_R:
return HAL_PIXEL_FORMAT_BLOB;
case ImageFormat.DEPTH16:
return HAL_PIXEL_FORMAT_Y16;
@@ -1414,6 +1471,7 @@
* <li>ImageFormat.DEPTH16 => HAL_DATASPACE_DEPTH
* <li>ImageFormat.DEPTH_JPEG => HAL_DATASPACE_DYNAMIC_DEPTH
* <li>ImageFormat.HEIC => HAL_DATASPACE_HEIF
+ * <li>ImageFormat.JPEG_R => HAL_DATASPACE_JPEG_R
* <li>others => HAL_DATASPACE_UNKNOWN
* </ul>
* </p>
@@ -1448,6 +1506,8 @@
return HAL_DATASPACE_DYNAMIC_DEPTH;
case ImageFormat.HEIC:
return HAL_DATASPACE_HEIF;
+ case ImageFormat.JPEG_R:
+ return HAL_DATASPACE_JPEG_R;
default:
return HAL_DATASPACE_UNKNOWN;
}
@@ -1500,14 +1560,15 @@
dataspace == HAL_DATASPACE_DEPTH ? mDepthOutputFormats :
dataspace == HAL_DATASPACE_DYNAMIC_DEPTH ? mDynamicDepthOutputFormats :
dataspace == HAL_DATASPACE_HEIF ? mHeicOutputFormats :
+ dataspace == HAL_DATASPACE_JPEG_R ? mJpegROutputFormats :
highRes ? mHighResOutputFormats :
mOutputFormats;
int sizesCount = formatsMap.get(format);
- if ( ((!output || (dataspace == HAL_DATASPACE_DEPTH ||
+ if ( ((!output || (dataspace == HAL_DATASPACE_DEPTH || dataspace == HAL_DATASPACE_JPEG_R ||
dataspace == HAL_DATASPACE_DYNAMIC_DEPTH ||
dataspace == HAL_DATASPACE_HEIF)) && sizesCount == 0) ||
- (output && (dataspace != HAL_DATASPACE_DEPTH &&
+ (output && (dataspace != HAL_DATASPACE_DEPTH && dataspace != HAL_DATASPACE_JPEG_R &&
dataspace != HAL_DATASPACE_DYNAMIC_DEPTH &&
dataspace != HAL_DATASPACE_HEIF) &&
mAllOutputFormats.get(format) == 0)) {
@@ -1521,11 +1582,13 @@
(dataspace == HAL_DATASPACE_DEPTH) ? mDepthConfigurations :
(dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthConfigurations :
(dataspace == HAL_DATASPACE_HEIF) ? mHeicConfigurations :
+ (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRConfigurations :
mConfigurations;
StreamConfigurationDuration[] minFrameDurations =
(dataspace == HAL_DATASPACE_DEPTH) ? mDepthMinFrameDurations :
(dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthMinFrameDurations :
(dataspace == HAL_DATASPACE_HEIF) ? mHeicMinFrameDurations :
+ (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRMinFrameDurations :
mMinFrameDurations;
for (StreamConfiguration config : configurations) {
@@ -1555,7 +1618,7 @@
// Dynamic depth streams can have both fast and also high res modes.
if ((sizeIndex != sizesCount) && (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH ||
- dataspace == HAL_DATASPACE_HEIF)) {
+ dataspace == HAL_DATASPACE_HEIF) || (dataspace == HAL_DATASPACE_JPEG_R)) {
if (sizeIndex > sizesCount) {
throw new AssertionError(
@@ -1598,6 +1661,9 @@
if (mHeicOutputFormats.size() > 0) {
formats[i++] = ImageFormat.HEIC;
}
+ if (mJpegROutputFormats.size() > 0) {
+ formats[i++] = ImageFormat.JPEG_R;
+ }
}
if (formats.length != i) {
throw new AssertionError("Too few formats " + i + ", expected " + formats.length);
@@ -1644,12 +1710,14 @@
(dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ?
mDynamicDepthMinFrameDurations :
(dataspace == HAL_DATASPACE_HEIF) ? mHeicMinFrameDurations :
+ (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRMinFrameDurations :
mMinFrameDurations;
case DURATION_STALL:
return (dataspace == HAL_DATASPACE_DEPTH) ? mDepthStallDurations :
(dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthStallDurations :
(dataspace == HAL_DATASPACE_HEIF) ? mHeicStallDurations :
+ (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRStallDurations :
mStallDurations;
default:
throw new IllegalArgumentException("duration was invalid");
@@ -1664,6 +1732,7 @@
size += mDepthOutputFormats.size();
size += mDynamicDepthOutputFormats.size();
size += mHeicOutputFormats.size();
+ size += mJpegROutputFormats.size();
}
return size;
@@ -1688,6 +1757,7 @@
(dataspace == HAL_DATASPACE_DEPTH) ? mDepthConfigurations :
(dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthConfigurations :
(dataspace == HAL_DATASPACE_HEIF) ? mHeicConfigurations :
+ (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRConfigurations :
mConfigurations;
for (int i = 0; i < configurations.length; i++) {
@@ -1908,6 +1978,8 @@
return "PRIVATE";
case ImageFormat.HEIC:
return "HEIC";
+ case ImageFormat.JPEG_R:
+ return "JPEG/R";
default:
return "UNKNOWN";
}
@@ -1948,6 +2020,10 @@
* @hide
*/
public static final int HAL_DATASPACE_HEIF = 0x1003;
+ /**
+ * @hide
+ */
+ public static final int HAL_DATASPACE_JPEG_R = 0x1005;
private static final long DURATION_20FPS_NS = 50000000L;
/**
* @see #getDurations(int, int)
@@ -1971,6 +2047,10 @@
private final StreamConfigurationDuration[] mHeicMinFrameDurations;
private final StreamConfigurationDuration[] mHeicStallDurations;
+ private final StreamConfiguration[] mJpegRConfigurations;
+ private final StreamConfigurationDuration[] mJpegRMinFrameDurations;
+ private final StreamConfigurationDuration[] mJpegRStallDurations;
+
private final HighSpeedVideoConfiguration[] mHighSpeedVideoConfigurations;
private final ReprocessFormatsMap mInputOutputFormatsMap;
@@ -1992,6 +2072,8 @@
private final SparseIntArray mDynamicDepthOutputFormats = new SparseIntArray();
/** internal format -> num heic output sizes mapping, for HAL_DATASPACE_HEIF */
private final SparseIntArray mHeicOutputFormats = new SparseIntArray();
+ /** internal format -> num Jpeg/R output sizes mapping, for HAL_DATASPACE_JPEG_R */
+ private final SparseIntArray mJpegROutputFormats = new SparseIntArray();
/** High speed video Size -> FPS range count mapping*/
private final HashMap</*HighSpeedVideoSize*/Size, /*Count*/Integer> mHighSpeedVideoSizeMap =
diff --git a/core/java/android/os/IHintManager.aidl b/core/java/android/os/IHintManager.aidl
index 661b95a..d97ea54 100644
--- a/core/java/android/os/IHintManager.aidl
+++ b/core/java/android/os/IHintManager.aidl
@@ -24,10 +24,13 @@
/**
* Creates a {@link Session} for the given set of threads and associates to a binder token.
*/
- IHintSession createHintSession(in IBinder token, in int[] tids, long durationNanos);
+ IHintSession createHintSession(in IBinder token, in int[] tids, long durationNanos);
/**
* Get preferred rate limit in nano second.
*/
- long getHintSessionPreferredRate();
+ long getHintSessionPreferredRate();
+
+ void setHintSessionThreads(in IHintSession hintSession, in int[] tids);
+ int[] getHintSessionThreadIds(in IHintSession hintSession);
}
diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java
index 85d6d83..f79d6e6 100644
--- a/core/java/android/os/PerformanceHintManager.java
+++ b/core/java/android/os/PerformanceHintManager.java
@@ -222,16 +222,50 @@
Reference.reachabilityFence(this);
}
}
+
+ /**
+ * Set a list of threads to the performance hint session. This operation will replace
+ * the current list of threads with the given list of threads.
+ * Note that this is not an oneway method.
+ *
+ * @param tids The list of threads to be associated with this session. They must be
+ * part of this app's thread group.
+ *
+ * @throws IllegalStateException if the hint session is not in the foreground.
+ * @throws IllegalArgumentException if the thread id list is empty.
+ * @throws SecurityException if any thread id doesn't belong to the application.
+ */
+ public void setThreads(@NonNull int[] tids) {
+ if (mNativeSessionPtr == 0) {
+ return;
+ }
+ if (tids.length == 0) {
+ throw new IllegalArgumentException("Thread id list can't be empty.");
+ }
+ nativeSetThreads(mNativeSessionPtr, tids);
+ }
+
+ /**
+ * Returns the list of thread ids.
+ *
+ * @hide
+ */
+ @TestApi
+ public @Nullable int[] getThreadIds() {
+ return nativeGetThreadIds(mNativeSessionPtr);
+ }
}
private static native long nativeAcquireManager();
private static native long nativeGetPreferredUpdateRateNanos(long nativeManagerPtr);
private static native long nativeCreateSession(long nativeManagerPtr,
int[] tids, long initialTargetWorkDurationNanos);
+ private static native int[] nativeGetThreadIds(long nativeSessionPtr);
private static native void nativeUpdateTargetWorkDuration(long nativeSessionPtr,
long targetDurationNanos);
private static native void nativeReportActualWorkDuration(long nativeSessionPtr,
long actualDurationNanos);
private static native void nativeCloseSession(long nativeSessionPtr);
private static native void nativeSendHint(long nativeSessionPtr, int hint);
+ private static native void nativeSetThreads(long nativeSessionPtr, int[] tids);
}
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index 394927e..b210c46 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -292,8 +292,10 @@
* If the service is not running, servicemanager will attempt to start it, and this function
* will wait for it to be ready.
*
- * @return {@code null} if the service is not declared in the manifest, or if there are
- * permission problems, or if there are fatal errors.
+ * @throws SecurityException if the process does not have the permissions to check
+ * isDeclared() for the service.
+ * @return {@code null} if the service is not declared in the manifest, or if there
+ * are fatal errors.
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 3cb5c60..0b6a99b 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -73,6 +73,8 @@
private static final int ZYGOTE_CONNECT_TIMEOUT_MS = 20000;
+ private static final int APPLICATION_ZYGOTE_READ_TIMEOUT_MS = 5000;
+
/**
* Use a relatively short delay, because for app zygote, this is in the critical path of
* service launch.
@@ -1109,6 +1111,9 @@
state.mZygoteOutputWriter.flush();
+ // The system_server should not be blocked by a defective or bad application zygote.
+ state.mZygoteSessionSocket.setSoTimeout(APPLICATION_ZYGOTE_READ_TIMEOUT_MS);
+
return (state.mZygoteInputStream.readInt() == 0);
}
}
diff --git a/core/java/android/security/rkp/IRemoteProvisioning.aidl b/core/java/android/security/rkp/IRemoteProvisioning.aidl
index 23d8159..0efaa89 100644
--- a/core/java/android/security/rkp/IRemoteProvisioning.aidl
+++ b/core/java/android/security/rkp/IRemoteProvisioning.aidl
@@ -58,11 +58,4 @@
*
*/
void getRegistration(String irpcName, IGetRegistrationCallback callback);
-
- /**
- * Cancel any active {@link getRegistration} call associated with the given
- * callback. If no getRegistration call is currently active, this function is
- * a noop.
- */
- void cancelGetRegistration(IGetRegistrationCallback callback);
}
diff --git a/core/java/android/service/credentials/CredentialProviderErrors.java b/core/java/android/service/credentials/CredentialProviderErrors.java
new file mode 100644
index 0000000..e9dc35b
--- /dev/null
+++ b/core/java/android/service/credentials/CredentialProviderErrors.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.service.credentials;
+
+/**
+ * Contains custom error codes to be used internally for various credential
+ * provider error states.
+ *
+ * @hide
+ */
+public class CredentialProviderErrors {
+ public static final int ERROR_UNKNOWN = 0;
+
+ /**
+ * For internal use only.
+ * Error code to be used when the provider request times out.
+ *
+ * @hide
+ */
+ public static final int ERROR_TIMEOUT = 1;
+
+ /**
+ * For internal use only.
+ * Error code to be used when the async task is canceled internally.
+ *
+ * @hide
+ */
+ public static final int ERROR_TASK_CANCELED = 2;
+
+ /**
+ * For internal use only.
+ * Error code to be used when an exception is received from the provider.
+ *
+ * @hide
+ */
+ public static final int ERROR_PROVIDER_FAILURE = 3;
+}
diff --git a/core/java/android/service/credentials/CredentialProviderException.java b/core/java/android/service/credentials/CredentialProviderException.java
deleted file mode 100644
index 969bcb5..0000000
--- a/core/java/android/service/credentials/CredentialProviderException.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.service.credentials;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Contains custom exceptions to be used by credential providers on failure.
- *
- * @hide
- */
-public class CredentialProviderException extends Exception {
- public static final int ERROR_UNKNOWN = 0;
-
- /**
- * For internal use only.
- * Error code to be used when the provider request times out.
- *
- * @hide
- */
- public static final int ERROR_TIMEOUT = 1;
-
- /**
- * For internal use only.
- * Error code to be used when the async task is canceled internally.
- *
- * @hide
- */
- public static final int ERROR_TASK_CANCELED = 2;
-
- /**
- * For internal use only.
- * Error code to be used when the provider encounters a failure while processing the request.
- *
- * @hide
- */
- public static final int ERROR_PROVIDER_FAILURE = 3;
-
- private final int mErrorCode;
-
- /**
- * @hide
- */
- @IntDef(prefix = {"ERROR_"}, value = {
- ERROR_UNKNOWN,
- ERROR_TIMEOUT,
- ERROR_TASK_CANCELED
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface CredentialProviderError { }
-
- public CredentialProviderException(@CredentialProviderError int errorCode,
- @NonNull String message, @NonNull Throwable cause) {
- super(message, cause);
- mErrorCode = errorCode;
- }
-
- public CredentialProviderException(@CredentialProviderError int errorCode,
- @NonNull String message) {
- super(message);
- mErrorCode = errorCode;
- }
-
- public CredentialProviderException(@CredentialProviderError int errorCode,
- @NonNull Throwable cause) {
- super(cause);
- mErrorCode = errorCode;
- }
-
- public CredentialProviderException(@CredentialProviderError int errorCode) {
- super();
- mErrorCode = errorCode;
- }
-
- public @CredentialProviderError int getErrorCode() {
- return mErrorCode;
- }
-}
diff --git a/core/java/android/service/quicksettings/Tile.java b/core/java/android/service/quicksettings/Tile.java
index d60b225..289b0e0 100644
--- a/core/java/android/service/quicksettings/Tile.java
+++ b/core/java/android/service/quicksettings/Tile.java
@@ -40,8 +40,8 @@
/**
* An unavailable state indicates that for some reason this tile is not currently
- * available to the user for some reason, and will have no click action. The tile's
- * icon will be tinted differently to reflect this state.
+ * available to the user, and will have no click action. The tile's icon will be
+ * tinted differently to reflect this state.
*/
public static final int STATE_UNAVAILABLE = 0;
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index a41401b..4c51be0 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -42,7 +42,7 @@
@UnsupportedAppUsage
oneway void destroy();
oneway void setZoomOut(float scale);
- oneway void scalePreview(in Rect positionInWindow);
+ oneway void resizePreview(in Rect positionInWindow);
oneway void removeLocalColorsAreas(in List<RectF> regions);
oneway void addLocalColorsAreas(in List<RectF> regions);
SurfaceControl mirrorSurfaceControl();
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 84a233f..de5d395 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -163,7 +163,7 @@
private static final int MSG_TOUCH_EVENT = 10040;
private static final int MSG_REQUEST_WALLPAPER_COLORS = 10050;
private static final int MSG_ZOOM = 10100;
- private static final int MSG_SCALE_PREVIEW = 10110;
+ private static final int MSG_RESIZE_PREVIEW = 10110;
private static final int MSG_REPORT_SHOWN = 10150;
private static final int MSG_UPDATE_DIMMING = 10200;
private static final int MSG_WALLPAPER_FLAGS_CHANGED = 10210;
@@ -322,7 +322,7 @@
@Override
public void setFixedSize(int width, int height) {
- if (!mFixedSizeAllowed) {
+ if (!mFixedSizeAllowed && !mIWallpaperEngine.mIsPreview) {
// Regular apps can't do this. It can only work for
// certain designs of window animations, so you can't
// rely on it.
@@ -1406,16 +1406,9 @@
}
}
- private void scalePreview(Rect position) {
- if (isPreview() && mPreviewSurfacePosition == null && position != null
- || mPreviewSurfacePosition != null
- && !mPreviewSurfacePosition.equals(position)) {
- mPreviewSurfacePosition = position;
- if (mSurfaceControl.isValid()) {
- reposition();
- } else {
- updateSurface(false, false, false);
- }
+ private void resizePreview(Rect position) {
+ if (position != null) {
+ mSurfaceHolder.setFixedSize(position.width(), position.height());
}
}
@@ -2351,8 +2344,8 @@
mCaller.sendMessage(msg);
}
- public void scalePreview(Rect position) {
- Message msg = mCaller.obtainMessageO(MSG_SCALE_PREVIEW, position);
+ public void resizePreview(Rect position) {
+ Message msg = mCaller.obtainMessageO(MSG_RESIZE_PREVIEW, position);
mCaller.sendMessage(msg);
}
@@ -2439,8 +2432,8 @@
case MSG_UPDATE_DIMMING:
mEngine.updateWallpaperDimming(Float.intBitsToFloat(message.arg1));
break;
- case MSG_SCALE_PREVIEW:
- mEngine.scalePreview((Rect) message.obj);
+ case MSG_RESIZE_PREVIEW:
+ mEngine.resizePreview((Rect) message.obj);
break;
case MSG_VISIBILITY_CHANGED:
if (DEBUG) Log.v(TAG, "Visibility change in " + mEngine
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
index 6b59f54..1cc772a 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -42,6 +42,8 @@
import com.android.internal.util.function.pooled.PooledLambda;
import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Objects;
/**
@@ -55,7 +57,7 @@
*/
@SdkConstant(SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE = "android.speech.RecognitionService";
-
+
/**
* Name under which a RecognitionService component publishes information about itself.
* This meta-data should reference an XML resource containing a
@@ -71,17 +73,12 @@
/** Debugging flag */
private static final boolean DBG = false;
+ private static final int DEFAULT_MAX_CONCURRENT_SESSIONS_COUNT = 1;
+
+ private final Map<IBinder, SessionState> mSessions = new HashMap<>();
+
/** Binder of the recognition service */
- private RecognitionServiceBinder mBinder = new RecognitionServiceBinder(this);
-
- /**
- * The current callback of an application that invoked the
- *
- * {@link RecognitionService#onStartListening(Intent, Callback)} method
- */
- private Callback mCurrentCallback = null;
-
- private boolean mStartedDataDelivery;
+ private final RecognitionServiceBinder mBinder = new RecognitionServiceBinder(this);
private static final int MSG_START_LISTENING = 1;
@@ -110,7 +107,7 @@
dispatchCancel((IRecognitionListener) msg.obj);
break;
case MSG_RESET:
- dispatchClearCallback();
+ dispatchClearCallback((IRecognitionListener) msg.obj);
break;
case MSG_CHECK_RECOGNITION_SUPPORT:
Pair<Intent, IRecognitionSupportCallback> intentAndListener =
@@ -127,71 +124,90 @@
private void dispatchStartListening(Intent intent, final IRecognitionListener listener,
@NonNull AttributionSource attributionSource) {
+ Callback currentCallback = null;
+ SessionState sessionState = mSessions.get(listener.asBinder());
+
try {
- if (mCurrentCallback == null) {
- boolean preflightPermissionCheckPassed =
- intent.hasExtra(RecognizerIntent.EXTRA_AUDIO_SOURCE)
- || checkPermissionForPreflightNotHardDenied(attributionSource);
- if (preflightPermissionCheckPassed) {
- if (DBG) {
- Log.d(TAG, "created new mCurrentCallback, listener = "
- + listener.asBinder());
- }
- mCurrentCallback = new Callback(listener, attributionSource);
- RecognitionService.this.onStartListening(intent, mCurrentCallback);
+ if (sessionState == null) {
+ if (mSessions.size() >= getMaxConcurrentSessionsCount()) {
+ listener.onError(SpeechRecognizer.ERROR_RECOGNIZER_BUSY);
+ Log.i(TAG, "#startListening received "
+ + "when the service's capacity is full - ignoring this call.");
+ return;
}
- if (!preflightPermissionCheckPassed || !checkPermissionAndStartDataDelivery()) {
+ boolean preflightPermissionCheckPassed =
+ intent.hasExtra(RecognizerIntent.EXTRA_AUDIO_SOURCE)
+ || checkPermissionForPreflightNotHardDenied(attributionSource);
+ if (preflightPermissionCheckPassed) {
+ currentCallback = new Callback(listener, attributionSource);
+ sessionState = new SessionState(currentCallback);
+ RecognitionService.this.onStartListening(intent, currentCallback);
+ }
+
+ if (!preflightPermissionCheckPassed
+ || !checkPermissionAndStartDataDelivery(sessionState)) {
listener.onError(SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS);
if (preflightPermissionCheckPassed) {
- // If we attempted to start listening, cancel the callback
- RecognitionService.this.onCancel(mCurrentCallback);
- dispatchClearCallback();
+ // If start listening was attempted, cancel the callback.
+ RecognitionService.this.onCancel(currentCallback);
+ finishDataDelivery(sessionState);
+ sessionState.reset();
}
- Log.i(TAG, "caller doesn't have permission:"
- + Manifest.permission.RECORD_AUDIO);
+ Log.i(TAG, "#startListening received from a caller "
+ + "without permission " + Manifest.permission.RECORD_AUDIO + ".");
+ } else {
+ if (DBG) {
+ Log.d(TAG, "Added a new session to the map.");
+ }
+ mSessions.put(listener.asBinder(), sessionState);
}
} else {
- listener.onError(SpeechRecognizer.ERROR_RECOGNIZER_BUSY);
- Log.i(TAG, "concurrent startListening received - ignoring this call");
+ listener.onError(SpeechRecognizer.ERROR_CLIENT);
+ Log.i(TAG, "#startListening received "
+ + "for a listener which is already in session - ignoring this call.");
}
} catch (RemoteException e) {
- Log.d(TAG, "onError call from startListening failed");
+ Log.d(TAG, "#onError call from #startListening failed.");
}
}
private void dispatchStopListening(IRecognitionListener listener) {
- try {
- if (mCurrentCallback == null) {
+ SessionState sessionState = mSessions.get(listener.asBinder());
+ if (sessionState == null) {
+ try {
listener.onError(SpeechRecognizer.ERROR_CLIENT);
- Log.w(TAG, "stopListening called with no preceding startListening - ignoring");
- } else if (mCurrentCallback.mListener.asBinder() != listener.asBinder()) {
- listener.onError(SpeechRecognizer.ERROR_RECOGNIZER_BUSY);
- Log.w(TAG, "stopListening called by other caller than startListening - ignoring");
- } else { // the correct state
- RecognitionService.this.onStopListening(mCurrentCallback);
+ } catch (RemoteException e) {
+ Log.d(TAG, "#onError call from #stopListening failed.");
}
- } catch (RemoteException e) { // occurs if onError fails
- Log.d(TAG, "onError call from stopListening failed");
+ Log.w(TAG, "#stopListening received for a listener "
+ + "which has not started a session - ignoring this call.");
+ } else {
+ RecognitionService.this.onStopListening(sessionState.mCallback);
}
}
private void dispatchCancel(IRecognitionListener listener) {
- if (mCurrentCallback == null) {
- if (DBG) Log.d(TAG, "cancel called with no preceding startListening - ignoring");
- } else if (mCurrentCallback.mListener.asBinder() != listener.asBinder()) {
- Log.w(TAG, "cancel called by client who did not call startListening - ignoring");
- } else { // the correct state
- RecognitionService.this.onCancel(mCurrentCallback);
- dispatchClearCallback();
- if (DBG) Log.d(TAG, "canceling - setting mCurrentCallback to null");
+ SessionState sessionState = mSessions.get(listener.asBinder());
+ if (sessionState == null) {
+ Log.w(TAG, "#cancel received for a listener which has not started a session "
+ + "- ignoring this call.");
+ } else {
+ RecognitionService.this.onCancel(sessionState.mCallback);
+ dispatchClearCallback(listener);
}
}
- private void dispatchClearCallback() {
- finishDataDelivery();
- mCurrentCallback = null;
- mStartedDataDelivery = false;
+ private void dispatchClearCallback(IRecognitionListener listener) {
+ SessionState sessionState = mSessions.remove(listener.asBinder());
+ if (sessionState != null) {
+ if (DBG) {
+ Log.d(TAG, "Removed session from the map for listener = "
+ + listener.asBinder() + ".");
+ }
+ finishDataDelivery(sessionState);
+ sessionState.reset();
+ }
}
private void dispatchCheckRecognitionSupport(
@@ -203,11 +219,11 @@
RecognitionService.this.onTriggerModelDownload(intent);
}
- private class StartListeningArgs {
+ private static class StartListeningArgs {
public final Intent mIntent;
public final IRecognitionListener mListener;
- public final @NonNull AttributionSource mAttributionSource;
+ @NonNull public final AttributionSource mAttributionSource;
public StartListeningArgs(Intent intent, IRecognitionListener listener,
@NonNull AttributionSource attributionSource) {
@@ -306,28 +322,43 @@
}
private void handleAttributionContextCreation(@NonNull AttributionSource attributionSource) {
- if (mCurrentCallback != null
- && mCurrentCallback.mCallingAttributionSource.equals(attributionSource)) {
- mCurrentCallback.mAttributionContextCreated = true;
+ for (SessionState sessionState : mSessions.values()) {
+ Callback currentCallback = sessionState.mCallback;
+ if (currentCallback != null
+ && currentCallback.mCallingAttributionSource.equals(attributionSource)) {
+ currentCallback.mAttributionContextCreated = true;
+ }
}
}
@Override
public final IBinder onBind(final Intent intent) {
- if (DBG) Log.d(TAG, "onBind, intent=" + intent);
+ if (DBG) Log.d(TAG, "#onBind, intent=" + intent);
return mBinder;
}
@Override
public void onDestroy() {
- if (DBG) Log.d(TAG, "onDestroy");
- finishDataDelivery();
- mCurrentCallback = null;
+ if (DBG) Log.d(TAG, "#onDestroy");
+ for (SessionState sessionState : mSessions.values()) {
+ finishDataDelivery(sessionState);
+ sessionState.reset();
+ }
+ mSessions.clear();
mBinder.clearReference();
super.onDestroy();
}
/**
+ * Returns the maximal number of recognition sessions ongoing at the same time.
+ * <p>
+ * The default value is 1, meaning concurrency should be enabled by overriding this method.
+ */
+ public int getMaxConcurrentSessionsCount() {
+ return DEFAULT_MAX_CONCURRENT_SESSIONS_COUNT;
+ }
+
+ /**
* This class receives callbacks from the speech recognition service and forwards them to the
* user. An instance of this class is passed to the
* {@link RecognitionService#onStartListening(Intent, Callback)} method. Recognizers may call
@@ -335,8 +366,8 @@
*/
public class Callback {
private final IRecognitionListener mListener;
- private final @NonNull AttributionSource mCallingAttributionSource;
- private @Nullable Context mAttributionContext;
+ @NonNull private final AttributionSource mCallingAttributionSource;
+ @Nullable private Context mAttributionContext;
private boolean mAttributionContextCreated;
private Callback(IRecognitionListener listener,
@@ -355,7 +386,7 @@
/**
* The service should call this method when sound has been received. The purpose of this
* function is to allow giving feedback to the user regarding the captured audio.
- *
+ *
* @param buffer a buffer containing a sequence of big-endian 16-bit integers representing a
* single channel audio stream. The sample rate is implementation dependent.
*/
@@ -372,11 +403,11 @@
/**
* The service should call this method when a network or recognition error occurred.
- *
+ *
* @param error code is defined in {@link SpeechRecognizer}
*/
public void error(@SpeechRecognizer.RecognitionError int error) throws RemoteException {
- Message.obtain(mHandler, MSG_RESET).sendToTarget();
+ Message.obtain(mHandler, MSG_RESET, mListener).sendToTarget();
mListener.onError(error);
}
@@ -386,7 +417,7 @@
* {@link #results(Bundle)} when partial results are ready. This method may be called zero,
* one or multiple times for each call to {@link SpeechRecognizer#startListening(Intent)},
* depending on the speech recognition service implementation.
- *
+ *
* @param partialResults the returned results. To retrieve the results in
* ArrayList<String> format use {@link Bundle#getStringArrayList(String)} with
* {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter
@@ -398,7 +429,7 @@
/**
* The service should call this method when the endpointer is ready for the user to start
* speaking.
- *
+ *
* @param params parameters set by the recognition service. Reserved for future use.
*/
public void readyForSpeech(Bundle params) throws RemoteException {
@@ -407,20 +438,20 @@
/**
* The service should call this method when recognition results are ready.
- *
+ *
* @param results the recognition results. To retrieve the results in {@code
* ArrayList<String>} format use {@link Bundle#getStringArrayList(String)} with
* {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter
*/
public void results(Bundle results) throws RemoteException {
- Message.obtain(mHandler, MSG_RESET).sendToTarget();
+ Message.obtain(mHandler, MSG_RESET, mListener).sendToTarget();
mListener.onResults(results);
}
/**
* The service should call this method when the sound level in the audio stream has changed.
* There is no guarantee that this method will be called.
- *
+ *
* @param rmsdB the new RMS dB value
*/
public void rmsChanged(float rmsdB) throws RemoteException {
@@ -444,7 +475,7 @@
*/
@SuppressLint({"CallbackMethodName", "RethrowRemoteException"})
public void endOfSegmentedSession() throws RemoteException {
- Message.obtain(mHandler, MSG_RESET).sendToTarget();
+ Message.obtain(mHandler, MSG_RESET, mListener).sendToTarget();
mListener.onEndOfSegmentedSession();
}
@@ -469,7 +500,8 @@
* AttributionSource)
*/
@SuppressLint("CallbackMethodName")
- public @NonNull AttributionSource getCallingAttributionSource() {
+ @NonNull
+ public AttributionSource getCallingAttributionSource() {
return mCallingAttributionSource;
}
@@ -490,7 +522,6 @@
* these methods on any thread.
*/
public static class SupportCallback {
-
private final IRecognitionSupportCallback mCallback;
private SupportCallback(IRecognitionSupportCallback callback) {
@@ -521,7 +552,7 @@
}
}
-/** Binder of the recognition service */
+ /** Binder of the recognition service. */
private static final class RecognitionServiceBinder extends IRecognitionService.Stub {
private final WeakReference<RecognitionService> mServiceRef;
@@ -538,7 +569,7 @@
final RecognitionService service = mServiceRef.get();
if (service != null) {
service.mHandler.sendMessage(Message.obtain(service.mHandler,
- MSG_START_LISTENING, service.new StartListeningArgs(
+ MSG_START_LISTENING, new StartListeningArgs(
recognizerIntent, listener, attributionSource)));
}
}
@@ -589,17 +620,21 @@
}
}
- private boolean checkPermissionAndStartDataDelivery() {
- if (mCurrentCallback.mAttributionContextCreated) {
+ private boolean checkPermissionAndStartDataDelivery(SessionState sessionState) {
+ if (sessionState.mCallback.mAttributionContextCreated) {
return true;
}
+
if (PermissionChecker.checkPermissionAndStartDataDelivery(
- RecognitionService.this, Manifest.permission.RECORD_AUDIO,
- mCurrentCallback.getAttributionContextForCaller().getAttributionSource(),
- /*message*/ null) == PermissionChecker.PERMISSION_GRANTED) {
- mStartedDataDelivery = true;
+ RecognitionService.this,
+ Manifest.permission.RECORD_AUDIO,
+ sessionState.mCallback.getAttributionContextForCaller().getAttributionSource(),
+ /* message */ null)
+ == PermissionChecker.PERMISSION_GRANTED) {
+ sessionState.mStartedDataDelivery = true;
}
- return mStartedDataDelivery;
+
+ return sessionState.mStartedDataDelivery;
}
private boolean checkPermissionForPreflightNotHardDenied(AttributionSource attributionSource) {
@@ -609,12 +644,39 @@
|| result == PermissionChecker.PERMISSION_SOFT_DENIED;
}
- void finishDataDelivery() {
- if (mStartedDataDelivery) {
- mStartedDataDelivery = false;
+ void finishDataDelivery(SessionState sessionState) {
+ if (sessionState.mStartedDataDelivery) {
+ sessionState.mStartedDataDelivery = false;
final String op = AppOpsManager.permissionToOp(Manifest.permission.RECORD_AUDIO);
PermissionChecker.finishDataDelivery(RecognitionService.this, op,
- mCurrentCallback.getAttributionContextForCaller().getAttributionSource());
+ sessionState.mCallback.getAttributionContextForCaller().getAttributionSource());
+ }
+ }
+
+ /**
+ * Data class containing information about an ongoing session:
+ * <ul>
+ * <li> {@link SessionState#mCallback} - callback of the client that invoked the
+ * {@link RecognitionService#onStartListening(Intent, Callback)} method;
+ * <li> {@link SessionState#mStartedDataDelivery} - flag denoting if data
+ * is being delivered to the client.
+ */
+ private static class SessionState {
+ private Callback mCallback;
+ private boolean mStartedDataDelivery;
+
+ SessionState(Callback callback, boolean startedDataDelivery) {
+ mCallback = callback;
+ mStartedDataDelivery = startedDataDelivery;
+ }
+
+ SessionState(Callback currentCallback) {
+ this(currentCallback, false);
+ }
+
+ void reset() {
+ mCallback = null;
+ mStartedDataDelivery = false;
}
}
}
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index dded76c..18fdb83 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -34,6 +34,7 @@
import android.telephony.TelephonyManager.DataEnabledReason;
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.MediaQualityStatus;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IPhoneStateListener;
@@ -1664,6 +1665,10 @@
List<LinkCapacityEstimate> linkCapacityEstimateList) {
// default implementation empty
}
+
+ public final void onMediaQualityStatusChanged(MediaQualityStatus mediaQualityStatus) {
+ // not support. Can't override. Use TelephonyCallback.
+ }
}
private void log(String s) {
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index e90ae75..a50e6db 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -27,6 +27,8 @@
import android.os.Build;
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.MediaQualityStatus;
+import android.telephony.ims.MediaThreshold;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -592,6 +594,19 @@
public static final int EVENT_TRIGGER_NOTIFY_ANBR = 38;
/**
+ * Event for changes to the media quality status
+ *
+ * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}
+ *
+ * @see MediaQualityStatusChangedListener#onMediaQualityStatusChanged
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
+ public static final int EVENT_MEDIA_QUALITY_STATUS_CHANGED = 39;
+
+ /**
* @hide
*/
@IntDef(prefix = {"EVENT_"}, value = {
@@ -632,7 +647,8 @@
EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED,
EVENT_LEGACY_CALL_STATE_CHANGED,
EVENT_LINK_CAPACITY_ESTIMATE_CHANGED,
- EVENT_TRIGGER_NOTIFY_ANBR
+ EVENT_TRIGGER_NOTIFY_ANBR,
+ EVENT_MEDIA_QUALITY_STATUS_CHANGED
})
@Retention(RetentionPolicy.SOURCE)
public @interface TelephonyEvent {
@@ -1517,6 +1533,30 @@
}
/**
+ * Interface for media quality status changed listener.
+ *
+ * @hide
+ */
+ @SystemApi
+ public interface MediaQualityStatusChangedListener {
+ /**
+ * Callback invoked when the media quality status of IMS call changes. This call back
+ * means current media quality status crosses at least one of threshold values in {@link
+ * MediaThreshold}. Listener needs to get quality information & check whether it crossed
+ * listener's threshold.
+ *
+ * <p/> Currently thresholds for this indication can be configurable by CARRIER_CONFIG
+ * {@link CarrierConfigManager#KEY_VOICE_RTP_THRESHOLDS_PACKET_LOSS_RATE_INT}
+ * {@link CarrierConfigManager#KEY_VOICE_RTP_THRESHOLDS_INACTIVITY_TIME_IN_MILLIS_INT}
+ * {@link CarrierConfigManager#KEY_VOICE_RTP_THRESHOLDS_JITTER_INT}
+ *
+ * @param mediaQualityStatus The media quality status currently measured.
+ */
+ @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
+ void onMediaQualityStatusChanged(@NonNull MediaQualityStatus mediaQualityStatus);
+ }
+
+ /**
* The callback methods need to be called on the handler thread where
* this object was created. If the binder did that for us it'd be nice.
* <p>
@@ -1873,5 +1913,16 @@
() -> mExecutor.execute(() -> listener.onLinkCapacityEstimateChanged(
linkCapacityEstimateList)));
}
+
+ public void onMediaQualityStatusChanged(
+ MediaQualityStatus mediaQualityStatus) {
+ MediaQualityStatusChangedListener listener =
+ (MediaQualityStatusChangedListener) mTelephonyCallbackWeakRef.get();
+ if (listener == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> listener.onMediaQualityStatusChanged(
+ mediaQualityStatus)));
+ }
}
}
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 519647d..8b24e07 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -40,6 +40,7 @@
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsCallSession;
import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.MediaQualityStatus;
import android.util.ArraySet;
import android.util.Log;
@@ -545,6 +546,27 @@
}
/**
+ * Notify change of media quality status {@link MediaQualityStatus} crosses media quality
+ * threshold
+ * <p/>
+ * Currently thresholds for this indication can be configurable by CARRIER_CONFIG
+ * {@link CarrierConfigManager#KEY_VOICE_RTP_THRESHOLDS_PACKET_LOSS_RATE_INT}
+ * {@link CarrierConfigManager#KEY_VOICE_RTP_THRESHOLDS_INACTIVITY_TIME_IN_MILLIS_INT}
+ * {@link CarrierConfigManager#KEY_VOICE_RTP_THRESHOLDS_JITTER_INT}
+ *
+ * @param status media quality status
+ */
+ public void notifyMediaQualityStatusChanged(
+ int slotIndex, int subId, @NonNull MediaQualityStatus status) {
+ try {
+ sRegistry.notifyMediaQualityStatusChanged(slotIndex, subId, status);
+ } catch (RemoteException ex) {
+ // system server crash
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Notify emergency number list changed on certain subscription.
*
* @param slotIndex for which emergency number list changed. Can be derived from subId except
@@ -1087,6 +1109,10 @@
eventList.add(TelephonyCallback.EVENT_LINK_CAPACITY_ESTIMATE_CHANGED);
}
+ if (telephonyCallback instanceof TelephonyCallback.MediaQualityStatusChangedListener) {
+ eventList.add(TelephonyCallback.EVENT_MEDIA_QUALITY_STATUS_CHANGED);
+ }
+
return eventList;
}
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 44168ca..f299238 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -21,6 +21,8 @@
import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_REQUESTED_KEY;
import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
import android.accessibilityservice.AccessibilityService;
import android.annotation.NonNull;
import android.graphics.Matrix;
@@ -1961,4 +1963,23 @@
}
}
}
+
+ /** Attaches an accessibility overlay to the specified window. */
+ public void attachAccessibilityOverlayToWindowClientThread(SurfaceControl sc) {
+ mHandler.sendMessage(
+ obtainMessage(
+ AccessibilityInteractionController
+ ::attachAccessibilityOverlayToWindowUiThread,
+ this,
+ sc));
+ }
+
+ private void attachAccessibilityOverlayToWindowUiThread(SurfaceControl sc) {
+ SurfaceControl parent = mViewRootImpl.getSurfaceControl();
+ if (parent.isValid()) {
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.reparent(sc, parent).apply();
+ t.close();
+ }
+ }
}
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index 83a7b3f..712d1d6 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -23,6 +23,7 @@
import static android.view.DisplayCutoutProto.BOUND_RIGHT;
import static android.view.DisplayCutoutProto.BOUND_TOP;
import static android.view.DisplayCutoutProto.INSETS;
+import static android.view.DisplayCutoutProto.WATERFALL_INSETS;
import static android.view.Surface.ROTATION_0;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
@@ -830,7 +831,7 @@
mBounds.getRect(BOUNDS_POSITION_TOP).dumpDebug(proto, BOUND_TOP);
mBounds.getRect(BOUNDS_POSITION_RIGHT).dumpDebug(proto, BOUND_RIGHT);
mBounds.getRect(BOUNDS_POSITION_BOTTOM).dumpDebug(proto, BOUND_BOTTOM);
- mWaterfallInsets.toRect().dumpDebug(proto, INSETS);
+ mWaterfallInsets.toRect().dumpDebug(proto, WATERFALL_INSETS);
proto.end(token);
}
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 85cb517..c2b6bc5 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -18,6 +18,7 @@
import static android.view.DisplayInfoProto.APP_HEIGHT;
import static android.view.DisplayInfoProto.APP_WIDTH;
+import static android.view.DisplayInfoProto.CUTOUT;
import static android.view.DisplayInfoProto.FLAGS;
import static android.view.DisplayInfoProto.LOGICAL_HEIGHT;
import static android.view.DisplayInfoProto.LOGICAL_WIDTH;
@@ -861,6 +862,9 @@
protoOutputStream.write(APP_HEIGHT, appHeight);
protoOutputStream.write(NAME, name);
protoOutputStream.write(FLAGS, flags);
+ if (displayCutout != null) {
+ displayCutout.dumpDebug(protoOutputStream, CUTOUT);
+ }
protoOutputStream.end(token);
}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 03ccb47..e4d878a 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -299,7 +299,8 @@
*/
void grantInputChannel(int displayId, in SurfaceControl surface, in IWindow window,
in IBinder hostInputToken, int flags, int privateFlags, int type,
- in IBinder focusGrantToken, String inputHandleName, out InputChannel outInputChannel);
+ in IBinder windowToken, in IBinder focusGrantToken, String inputHandleName,
+ out InputChannel outInputChannel);
/**
* Update the flags on an input channel associated with a particular surface.
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index a24c1f9..492c938 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -77,7 +77,7 @@
mInputChannel = inputChannel;
mMessageQueue = looper.getQueue();
mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
- inputChannel, mMessageQueue);
+ mInputChannel, mMessageQueue);
mCloseGuard.open("InputEventReceiver.dispose");
}
diff --git a/core/java/android/view/InputEventSender.java b/core/java/android/view/InputEventSender.java
index 9035f3f..64f62c7 100644
--- a/core/java/android/view/InputEventSender.java
+++ b/core/java/android/view/InputEventSender.java
@@ -65,7 +65,7 @@
mInputChannel = inputChannel;
mMessageQueue = looper.getQueue();
mSenderPtr = nativeInit(new WeakReference<InputEventSender>(this),
- inputChannel, mMessageQueue);
+ mInputChannel, mMessageQueue);
mCloseGuard.open("InputEventSender.dispose");
}
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index e775969..1648659 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -217,7 +217,7 @@
public void updateSurfacePosition(SparseArray<InsetsSourceControl> controls) {
for (int i = controls.size() - 1; i >= 0; i--) {
final InsetsSourceControl control = controls.valueAt(i);
- final InsetsSourceControl c = mControls.get(control.getType());
+ final InsetsSourceControl c = mControls.get(control.getId());
if (c == null) {
continue;
}
@@ -395,7 +395,7 @@
// control may be null if it got revoked.
continue;
}
- state.getSource(control.getType()).setVisible(shown);
+ state.getSource(control.getId()).setVisible(shown);
}
return getInsetsFromState(state, frame, typeSideMap);
}
@@ -413,7 +413,7 @@
// control may be null if it got revoked.
continue;
}
- if (state == null || state.getSource(control.getType()).isVisible()) {
+ if (state == null || state.getSource(control.getId()).isVisible()) {
insets = Insets.max(insets, control.getInsetsHint());
}
}
@@ -443,7 +443,7 @@
// TODO: Implement behavior when inset spans over multiple types
for (int i = controls.size() - 1; i >= 0; i--) {
final InsetsSourceControl control = controls.valueAt(i);
- final InsetsSource source = mInitialInsetsState.getSource(control.getType());
+ final InsetsSource source = mInitialInsetsState.getSource(control.getId());
final SurfaceControl leash = control.getLeash();
mTmpMatrix.setTranslate(control.getSurfacePosition().x, control.getSurfacePosition().y);
@@ -455,8 +455,8 @@
: inset != 0;
if (outState != null) {
- outState.getSource(source.getType()).setVisible(visible);
- outState.getSource(source.getType()).setFrame(mTmpFrame);
+ outState.getSource(source.getId()).setVisible(visible);
+ outState.getSource(source.getId()).setFrame(mTmpFrame);
}
// If the system is controlling the insets source, the leash can be null.
@@ -521,7 +521,7 @@
continue;
}
@InternalInsetsSide int side = InsetsState.getInsetSide(control.getInsetsHint());
- if (side == ISIDE_FLOATING && control.getType() == ITYPE_IME) {
+ if (side == ISIDE_FLOATING && control.getType() == WindowInsets.Type.ime()) {
side = ISIDE_BOTTOM;
}
sideControlsMap.add(side, control);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index fbd8226..421efed8 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -835,7 +835,7 @@
&& !fromSource.getFrame().equals(toSource.getFrame())
&& (Rect.intersects(mFrame, fromSource.getFrame())
|| Rect.intersects(mFrame, toSource.getFrame()))) {
- types |= InsetsState.toPublicType(toSource.getType());
+ types |= toSource.getType();
if (toState == null) {
toState = new InsetsState();
}
@@ -888,7 +888,7 @@
for (InsetsSourceControl activeControl : activeControls) {
if (activeControl != null) {
// TODO(b/122982984): Figure out why it can be null.
- mTmpControlArray.put(activeControl.getType(), activeControl);
+ mTmpControlArray.put(activeControl.getId(), activeControl);
}
}
}
@@ -910,10 +910,9 @@
// Ensure to create source consumers if not available yet.
for (int i = mTmpControlArray.size() - 1; i >= 0; i--) {
final InsetsSourceControl control = mTmpControlArray.valueAt(i);
- final @InternalInsetsType int type = control.getType();
- final InsetsSourceConsumer consumer = getSourceConsumer(type);
+ final InsetsSourceConsumer consumer = getSourceConsumer(control.getId());
consumer.setControl(control, showTypes, hideTypes);
- controllableTypes |= InsetsState.toPublicType(type);
+ controllableTypes |= control.getType();
}
if (mTmpControlArray.size() > 0) {
@@ -1265,7 +1264,7 @@
}
final InsetsSourceControl control = consumer.getControl();
if (control != null && control.getLeash() != null) {
- controls.put(control.getType(), new InsetsSourceControl(control));
+ controls.put(control.getId(), new InsetsSourceControl(control));
typesReady |= consumer.getType();
} else if (animationType == ANIMATION_TYPE_SHOW) {
if (DEBUG) Log.d(TAG, "collectSourceControls no control for show(). fromIme: "
@@ -1422,14 +1421,14 @@
}
@VisibleForTesting
- public @NonNull InsetsSourceConsumer getSourceConsumer(@InternalInsetsType int type) {
- InsetsSourceConsumer controller = mSourceConsumers.get(type);
- if (controller != null) {
- return controller;
+ public @NonNull InsetsSourceConsumer getSourceConsumer(int id) {
+ InsetsSourceConsumer consumer = mSourceConsumers.get(id);
+ if (consumer != null) {
+ return consumer;
}
- controller = mConsumerCreator.apply(this, type);
- mSourceConsumers.put(type, controller);
- return controller;
+ consumer = mConsumerCreator.apply(this, id);
+ mSourceConsumers.put(id, consumer);
+ return consumer;
}
@VisibleForTesting
diff --git a/core/java/android/view/InsetsResizeAnimationRunner.java b/core/java/android/view/InsetsResizeAnimationRunner.java
index 778c677..cf64eedf 100644
--- a/core/java/android/view/InsetsResizeAnimationRunner.java
+++ b/core/java/android/view/InsetsResizeAnimationRunner.java
@@ -155,7 +155,7 @@
(int) (fromFrame.top + fraction * (toFrame.top - fromFrame.top)),
(int) (fromFrame.right + fraction * (toFrame.right - fromFrame.right)),
(int) (fromFrame.bottom + fraction * (toFrame.bottom - fromFrame.bottom)));
- final InsetsSource source = new InsetsSource(type);
+ final InsetsSource source = new InsetsSource(type, fromSource.getType());
source.setFrame(frame);
source.setVisible(toSource.isVisible());
outState.addSource(source);
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index c8c941a..f6b063d 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -20,8 +20,6 @@
import static android.view.InsetsSourceProto.TYPE;
import static android.view.InsetsSourceProto.VISIBLE;
import static android.view.InsetsSourceProto.VISIBLE_FRAME;
-import static android.view.InsetsState.ITYPE_CAPTION_BAR;
-import static android.view.InsetsState.ITYPE_IME;
import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
import android.annotation.NonNull;
@@ -31,34 +29,42 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.util.proto.ProtoOutputStream;
-import android.view.InsetsState.InternalInsetsType;
+import android.view.WindowInsets.Type.InsetsType;
import java.io.PrintWriter;
import java.util.Objects;
/**
- * Represents the state of a single window generating insets for clients.
+ * Represents the state of a single entity generating insets for clients.
* @hide
*/
public class InsetsSource implements Parcelable {
- private final @InternalInsetsType int mType;
+ /**
+ * An unique integer to identify this source across processes.
+ */
+ private final int mId;
+
+ private final @InsetsType int mType;
/** Frame of the source in screen coordinate space */
private final Rect mFrame;
private @Nullable Rect mVisibleFrame;
+
private boolean mVisible;
private boolean mInsetsRoundedCornerFrame;
private final Rect mTmpFrame = new Rect();
- public InsetsSource(@InternalInsetsType int type) {
+ public InsetsSource(int id, @InsetsType int type) {
+ mId = id;
mType = type;
mFrame = new Rect();
- mVisible = InsetsState.getDefaultVisibility(type);
+ mVisible = (WindowInsets.Type.defaultVisible() & type) != 0;
}
public InsetsSource(InsetsSource other) {
+ mId = other.mId;
mType = other.mType;
mFrame = new Rect(other.mFrame);
mVisible = other.mVisible;
@@ -86,14 +92,18 @@
}
public void setVisibleFrame(@Nullable Rect visibleFrame) {
- mVisibleFrame = visibleFrame != null ? new Rect(visibleFrame) : visibleFrame;
+ mVisibleFrame = visibleFrame != null ? new Rect(visibleFrame) : null;
}
public void setVisible(boolean visible) {
mVisible = visible;
}
- public @InternalInsetsType int getType() {
+ public int getId() {
+ return mId;
+ }
+
+ public @InsetsType int getType() {
return mType;
}
@@ -149,7 +159,7 @@
// During drag-move and drag-resizing, the caption insets position may not get updated
// before the app frame get updated. To layout the app content correctly during drag events,
// we always return the insets with the corresponding height covering the top.
- if (!CAPTION_ON_SHELL && getType() == ITYPE_CAPTION_BAR) {
+ if (!CAPTION_ON_SHELL && getType() == WindowInsets.Type.captionBar()) {
return Insets.of(0, frame.height(), 0, 0);
}
// Checks for whether there is shared edge with insets for 0-width/height window.
@@ -162,7 +172,7 @@
// TODO: Currently, non-floating IME always intersects at bottom due to issues with cutout.
// However, we should let the policy decide from the server.
- if (getType() == ITYPE_IME) {
+ if (getType() == WindowInsets.Type.ime()) {
return Insets.of(0, 0, 0, mTmpFrame.height());
}
@@ -220,7 +230,7 @@
*/
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- proto.write(TYPE, InsetsState.typeToString(mType));
+ proto.write(TYPE, WindowInsets.Type.toString(mType));
mFrame.dumpDebug(proto, FRAME);
if (mVisibleFrame != null) {
mVisibleFrame.dumpDebug(proto, VISIBLE_FRAME);
@@ -231,7 +241,8 @@
public void dump(String prefix, PrintWriter pw) {
pw.print(prefix);
- pw.print("InsetsSource type="); pw.print(InsetsState.typeToString(mType));
+ pw.print("InsetsSource id="); pw.print(mId);
+ pw.print(" type="); pw.print(WindowInsets.Type.toString(mType));
pw.print(" frame="); pw.print(mFrame.toShortString());
if (mVisibleFrame != null) {
pw.print(" visibleFrame="); pw.print(mVisibleFrame.toShortString());
@@ -256,9 +267,10 @@
InsetsSource that = (InsetsSource) o;
+ if (mId != that.mId) return false;
if (mType != that.mType) return false;
if (mVisible != that.mVisible) return false;
- if (excludeInvisibleImeFrames && !mVisible && mType == ITYPE_IME) return true;
+ if (excludeInvisibleImeFrames && !mVisible && mType == WindowInsets.Type.ime()) return true;
if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false;
if (mInsetsRoundedCornerFrame != that.mInsetsRoundedCornerFrame) return false;
return mFrame.equals(that.mFrame);
@@ -266,15 +278,11 @@
@Override
public int hashCode() {
- int result = mType;
- result = 31 * result + mFrame.hashCode();
- result = 31 * result + (mVisibleFrame != null ? mVisibleFrame.hashCode() : 0);
- result = 31 * result + (mVisible ? 1 : 0);
- result = 31 * result + (mInsetsRoundedCornerFrame ? 1 : 0);
- return result;
+ return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mInsetsRoundedCornerFrame);
}
public InsetsSource(Parcel in) {
+ mId = in.readInt();
mType = in.readInt();
mFrame = Rect.CREATOR.createFromParcel(in);
if (in.readInt() != 0) {
@@ -293,6 +301,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mId);
dest.writeInt(mType);
mFrame.writeToParcel(dest, 0);
if (mVisibleFrame != null) {
@@ -308,14 +317,15 @@
@Override
public String toString() {
return "InsetsSource: {"
- + "mType=" + InsetsState.typeToString(mType)
- + ", mFrame=" + mFrame.toShortString()
- + ", mVisible=" + mVisible
- + ", mInsetsRoundedCornerFrame=" + mInsetsRoundedCornerFrame
+ + "mId=" + mId
+ + " mType=" + WindowInsets.Type.toString(mType)
+ + " mFrame=" + mFrame.toShortString()
+ + " mVisible=" + mVisible
+ + (mInsetsRoundedCornerFrame ? " insetsRoundedCornerFrame" : "")
+ "}";
}
- public static final @android.annotation.NonNull Creator<InsetsSource> CREATOR = new Creator<InsetsSource>() {
+ public static final @NonNull Creator<InsetsSource> CREATOR = new Creator<>() {
public InsetsSource createFromParcel(Parcel in) {
return new InsetsSource(in);
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 21c0395..b8c4eaa 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -132,7 +132,7 @@
mSourceControl = control;
if (control != null) {
if (DEBUG) Log.d(TAG, String.format("setControl -> %s on %s",
- InsetsState.typeToString(control.getType()),
+ WindowInsets.Type.toString(control.getType()),
mController.getHost().getRootViewTitle()));
}
if (mSourceControl == null) {
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 5f1cbba..610cfe4 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -22,13 +22,14 @@
import static android.view.InsetsSourceControlProto.POSITION;
import static android.view.InsetsSourceControlProto.TYPE;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Insets;
import android.graphics.Point;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.proto.ProtoOutputStream;
-import android.view.InsetsState.InternalInsetsType;
+import android.view.WindowInsets.Type.InsetsType;
import java.io.PrintWriter;
import java.util.Objects;
@@ -40,7 +41,8 @@
*/
public class InsetsSourceControl implements Parcelable {
- private final @InternalInsetsType int mType;
+ private final int mId;
+ private final @InsetsType int mType;
private final @Nullable SurfaceControl mLeash;
private final boolean mInitiallyVisible;
private final Point mSurfacePosition;
@@ -52,8 +54,9 @@
private boolean mSkipAnimationOnce;
private int mParcelableFlags;
- public InsetsSourceControl(@InternalInsetsType int type, @Nullable SurfaceControl leash,
+ public InsetsSourceControl(int id, @InsetsType int type, @Nullable SurfaceControl leash,
boolean initiallyVisible, Point surfacePosition, Insets insetsHint) {
+ mId = id;
mType = type;
mLeash = leash;
mInitiallyVisible = initiallyVisible;
@@ -62,6 +65,7 @@
}
public InsetsSourceControl(InsetsSourceControl other) {
+ mId = other.mId;
mType = other.mType;
if (other.mLeash != null) {
mLeash = new SurfaceControl(other.mLeash, "InsetsSourceControl");
@@ -75,6 +79,7 @@
}
public InsetsSourceControl(Parcel in) {
+ mId = in.readInt();
mType = in.readInt();
mLeash = in.readTypedObject(SurfaceControl.CREATOR);
mInitiallyVisible = in.readBoolean();
@@ -83,6 +88,10 @@
mSkipAnimationOnce = in.readBoolean();
}
+ public int getId() {
+ return mId;
+ }
+
public int getType() {
return mType;
}
@@ -153,6 +162,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mId);
dest.writeInt(mType);
dest.writeTypedObject(mLeash, mParcelableFlags);
dest.writeBoolean(mInitiallyVisible);
@@ -177,7 +187,8 @@
}
final InsetsSourceControl that = (InsetsSourceControl) o;
final SurfaceControl thatLeash = that.mLeash;
- return mType == that.mType
+ return mId == that.mId
+ && mType == that.mType
&& ((mLeash == thatLeash)
|| (mLeash != null && thatLeash != null && mLeash.isSameSurface(thatLeash)))
&& mInitiallyVisible == that.mInitiallyVisible
@@ -188,22 +199,26 @@
@Override
public int hashCode() {
- return Objects.hash(mType, mLeash, mInitiallyVisible, mSurfacePosition, mInsetsHint,
+ return Objects.hash(mId, mType, mLeash, mInitiallyVisible, mSurfacePosition, mInsetsHint,
mSkipAnimationOnce);
}
@Override
public String toString() {
return "InsetsSourceControl: {"
- + "type=" + InsetsState.typeToString(mType)
- + ", mSurfacePosition=" + mSurfacePosition
- + ", mInsetsHint=" + mInsetsHint
+ + "mId=" + mId
+ + " mType=" + WindowInsets.Type.toString(mType)
+ + (mInitiallyVisible ? " initiallyVisible" : "")
+ + " mSurfacePosition=" + mSurfacePosition
+ + " mInsetsHint=" + mInsetsHint
+ + (mSkipAnimationOnce ? " skipAnimationOnce" : "")
+ "}";
}
public void dump(String prefix, PrintWriter pw) {
pw.print(prefix);
- pw.print("InsetsSourceControl type="); pw.print(InsetsState.typeToString(mType));
+ pw.print("InsetsSourceControl mId="); pw.print(mId);
+ pw.print(" mType="); pw.print(WindowInsets.Type.toString(mType));
pw.print(" mLeash="); pw.print(mLeash);
pw.print(" mInitiallyVisible="); pw.print(mInitiallyVisible);
pw.print(" mSurfacePosition="); pw.print(mSurfacePosition);
@@ -212,8 +227,7 @@
pw.println();
}
- public static final @android.annotation.NonNull Creator<InsetsSourceControl> CREATOR
- = new Creator<InsetsSourceControl>() {
+ public static final @NonNull Creator<InsetsSourceControl> CREATOR = new Creator<>() {
public InsetsSourceControl createFromParcel(Parcel in) {
return new InsetsSourceControl(in);
}
@@ -231,7 +245,7 @@
*/
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- proto.write(TYPE, InsetsState.typeToString(mType));
+ proto.write(TYPE, WindowInsets.Type.toString(mType));
final long surfaceToken = proto.start(POSITION);
proto.write(X, mSurfacePosition.x);
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index c56d618..054d177 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -245,7 +245,7 @@
// IME won't be reported in max insets as the size depends on the EditorInfo of the IME
// target.
- if (source.getType() != ITYPE_IME) {
+ if (source.getType() != WindowInsets.Type.ime()) {
InsetsSource ignoringVisibilitySource = ignoringVisibilityState != null
? ignoringVisibilityState.getSource(type)
: source;
@@ -442,7 +442,7 @@
@Nullable boolean[] typeVisibilityMap) {
Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility);
- int type = toPublicType(source.getType());
+ final int type = source.getType();
processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
insets, type);
@@ -486,7 +486,7 @@
if (typeSideMap != null) {
@InternalInsetsSide int insetSide = getInsetSide(insets);
if (insetSide != ISIDE_UNKNOWN) {
- typeSideMap.put(source.getType(), insetSide);
+ typeSideMap.put(source.getId(), insetSide);
}
}
}
@@ -519,7 +519,7 @@
if (source != null) {
return source;
}
- source = new InsetsSource(type);
+ source = new InsetsSource(type, toPublicType(type));
mSources[type] = source;
return source;
}
@@ -708,7 +708,7 @@
}
public void addSource(InsetsSource source) {
- mSources[source.getType()] = source;
+ mSources[source.getId()] = source;
}
public static boolean clearsCompatInsets(int windowType, int windowFlags, int windowingMode) {
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 0a134be..50ce7b6 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -376,6 +376,7 @@
public void setView(@NonNull View view, @NonNull WindowManager.LayoutParams attrs) {
Objects.requireNonNull(view);
attrs.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+ addWindowToken(attrs);
view.setLayoutParams(attrs);
mViewRoot.setView(view, attrs, null);
}
@@ -453,4 +454,11 @@
public IBinder getFocusGrantToken() {
return mWm.getFocusGrantToken();
}
+
+ private void addWindowToken(WindowManager.LayoutParams attrs) {
+ final WindowManagerImpl wm =
+ (WindowManagerImpl) mViewRoot.mContext.getSystemService(Context.WINDOW_SERVICE);
+ attrs.token = wm.getDefaultToken();
+ }
}
+
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b2973ef..ff4ea81 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -10810,6 +10810,15 @@
}
}
}
+
+ public void attachAccessibilityOverlayToWindow(SurfaceControl sc) {
+ ViewRootImpl viewRootImpl = mViewRootImpl.get();
+ if (viewRootImpl != null) {
+ viewRootImpl
+ .getAccessibilityInteractionController()
+ .attachAccessibilityOverlayToWindowClientThread(sc);
+ }
+ }
}
/**
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 2228b9f..19d49d9 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -408,4 +408,8 @@
}
return null;
}
+
+ IBinder getDefaultToken() {
+ return mDefaultToken;
+ }
}
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 69340aa..a9fea017 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -166,11 +166,12 @@
if (mRealWm instanceof IWindowSession.Stub) {
mRealWm.grantInputChannel(displayId,
new SurfaceControl(sc, "WindowlessWindowManager.addToDisplay"),
- window, mHostInputToken, attrs.flags, attrs.privateFlags, attrs.type,
+ window, mHostInputToken,
+ attrs.flags, attrs.privateFlags, attrs.type, attrs.token,
mFocusGrantToken, attrs.getTitle().toString(), outInputChannel);
} else {
mRealWm.grantInputChannel(displayId, sc, window, mHostInputToken, attrs.flags,
- attrs.privateFlags, attrs.type, mFocusGrantToken,
+ attrs.privateFlags, attrs.type, attrs.token, mFocusGrantToken,
attrs.getTitle().toString(), outInputChannel);
}
} catch (RemoteException e) {
@@ -508,8 +509,9 @@
@Override
public void grantInputChannel(int displayId, SurfaceControl surface, IWindow window,
- IBinder hostInputToken, int flags, int privateFlags, int type, IBinder focusGrantToken,
- String inputHandleName, InputChannel outInputChannel) {
+ IBinder hostInputToken, int flags, int privateFlags, int type,
+ IBinder windowToken, IBinder focusGrantToken, String inputHandleName,
+ InputChannel outInputChannel) {
}
@Override
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 06a6de9..52eda0a 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -46,6 +46,7 @@
import android.util.SparseArray;
import android.util.SparseLongArray;
import android.view.Display;
+import android.view.SurfaceControl;
import android.view.ViewConfiguration;
import android.window.ScreenCapture;
@@ -1626,4 +1627,21 @@
new HashSet<String>(Arrays.asList("getStackTrace", "logTraceClient")),
FLAGS_ACCESSIBILITY_INTERACTION_CLIENT);
}
+
+ /** Attaches an accessibility overlay to the specified window. */
+ public void attachAccessibilityOverlayToWindow(
+ int connectionId, int accessibilityWindowId, SurfaceControl sc) {
+ synchronized (mInstanceLock) {
+ try {
+ IAccessibilityServiceConnection connection = getConnection(connectionId);
+ if (connection == null) {
+ Log.e(LOG_TAG, "Error while getting service connection.");
+ return;
+ }
+ connection.attachAccessibilityOverlayToWindow(accessibilityWindowId, sc);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while calling remote attachAccessibilityOverlayToWindow", re);
+ }
+ }
+ }
}
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
index fb01921..0eeba7c 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
@@ -20,6 +20,7 @@
import android.graphics.Point;
import android.os.Bundle;
import android.view.MagnificationSpec;
+import android.view.SurfaceControl;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
import android.window.ScreenCapture;
@@ -65,4 +66,5 @@
void takeScreenshotOfWindow(int interactionId,
in ScreenCapture.ScreenCaptureListener listener,
IAccessibilityInteractionConnectionCallback callback);
+ void attachAccessibilityOverlayToWindow(in SurfaceControl sc);
}
diff --git a/core/java/com/android/internal/content/om/OverlayManagerImpl.java b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
index 260d1a2..c462449 100644
--- a/core/java/com/android/internal/content/om/OverlayManagerImpl.java
+++ b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
@@ -31,6 +31,7 @@
import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.content.om.OverlayManagerTransaction;
+import android.content.om.OverlayManagerTransaction.Request;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.parsing.FrameworkParsingPackageUtils;
@@ -295,9 +296,8 @@
throws PackageManager.NameNotFoundException, IOException {
Objects.requireNonNull(transaction);
- for (Iterator<OverlayManagerTransaction.Request> it = transaction.iterator();
- it.hasNext(); ) {
- final OverlayManagerTransaction.Request request = it.next();
+ for (Iterator<Request> it = transaction.getRequests(); it.hasNext(); ) {
+ final Request request = it.next();
if (request.type == TYPE_REGISTER_FABRICATED) {
final FabricatedOverlayInternal fabricatedOverlayInternal =
Objects.requireNonNull(
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 9cb2e68..3e9f1cb 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -31,6 +31,7 @@
import android.telephony.SignalStrength;
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.MediaQualityStatus;
/**
* {@hide}
@@ -76,4 +77,5 @@
void onDataEnabledChanged(boolean enabled, int reason);
void onAllowedNetworkTypesChanged(in int reason, in long allowedNetworkType);
void onLinkCapacityEstimateChanged(in List<LinkCapacityEstimate> linkCapacityEstimateList);
+ void onMediaQualityStatusChanged(in MediaQualityStatus mediaQualityStatus);
}
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 54936c6..fd9239d 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -32,6 +32,8 @@
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.emergency.EmergencyNumber;
+import android.telephony.ims.MediaQualityStatus;
+
import com.android.internal.telephony.ICarrierConfigChangeListener;
import com.android.internal.telephony.ICarrierPrivilegesCallback;
import com.android.internal.telephony.IPhoneStateListener;
@@ -92,6 +94,7 @@
in EmergencyNumber emergencyNumber);
void notifyCallQualityChanged(in CallQuality callQuality, int phoneId, int subId,
int callNetworkType);
+ void notifyMediaQualityStatusChanged(int phoneId, int subId, in MediaQualityStatus status);
void notifyImsDisconnectCause(int subId, in ImsReasonInfo imsReasonInfo);
void notifyRegistrationFailed(int slotIndex, int subId, in CellIdentity cellIdentity,
String chosenPlmn, int domain, int causeCode, int additionalCauseCode);
diff --git a/core/jni/android_os_PerformanceHintManager.cpp b/core/jni/android_os_PerformanceHintManager.cpp
index ac1401d..0223b96 100644
--- a/core/jni/android_os_PerformanceHintManager.cpp
+++ b/core/jni/android_os_PerformanceHintManager.cpp
@@ -41,6 +41,8 @@
typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t);
typedef void (*APH_closeSession)(APerformanceHintSession* session);
typedef void (*APH_sendHint)(APerformanceHintSession*, int32_t);
+typedef void (*APH_setThreads)(APerformanceHintSession*, const int32_t*, size_t);
+typedef void (*APH_getThreadIds)(APerformanceHintSession*, int32_t* const, size_t* const);
bool gAPerformanceHintBindingInitialized = false;
APH_getManager gAPH_getManagerFn = nullptr;
@@ -50,6 +52,8 @@
APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr;
APH_closeSession gAPH_closeSessionFn = nullptr;
APH_sendHint gAPH_sendHintFn = nullptr;
+APH_setThreads gAPH_setThreadsFn = nullptr;
+APH_getThreadIds gAPH_getThreadIdsFn = nullptr;
void ensureAPerformanceHintBindingInitialized() {
if (gAPerformanceHintBindingInitialized) return;
@@ -95,6 +99,14 @@
"Failed to find required symbol "
"APerformanceHint_sendHint!");
+ gAPH_setThreadsFn = (APH_setThreads)dlsym(handle_, "APerformanceHint_setThreads");
+ LOG_ALWAYS_FATAL_IF(gAPH_setThreadsFn == nullptr,
+ "Failed to find required symbol APerformanceHint_setThreads!");
+
+ gAPH_getThreadIdsFn = (APH_getThreadIds)dlsym(handle_, "APerformanceHint_getThreadIds");
+ LOG_ALWAYS_FATAL_IF(gAPH_getThreadIdsFn == nullptr,
+ "Failed to find required symbol APerformanceHint_getThreadIds!");
+
gAPerformanceHintBindingInitialized = true;
}
@@ -150,6 +162,50 @@
gAPH_sendHintFn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr), hint);
}
+static void nativeSetThreads(JNIEnv* env, jclass clazz, jlong nativeSessionPtr, jintArray tids) {
+ ensureAPerformanceHintBindingInitialized();
+
+ if (tids == nullptr) {
+ return;
+ }
+ ScopedIntArrayRO tidsArray(env, tids);
+ std::vector<int32_t> tidsVector;
+ tidsVector.reserve(tidsArray.size());
+ for (size_t i = 0; i < tidsArray.size(); ++i) {
+ tidsVector.push_back(static_cast<int32_t>(tidsArray[i]));
+ }
+ gAPH_setThreadsFn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr),
+ tidsVector.data(), tidsVector.size());
+}
+
+// This call should only be used for validation in tests only. This call will initiate two IPC
+// calls, the first one is used to determined the size of the thread ids list, the second one
+// is used to return the actual list.
+static jintArray nativeGetThreadIds(JNIEnv* env, jclass clazz, jlong nativeSessionPtr) {
+ ensureAPerformanceHintBindingInitialized();
+ size_t size = 0;
+ gAPH_getThreadIdsFn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr), nullptr,
+ &size);
+ if (size == 0) {
+ jintArray jintArr = env->NewIntArray(0);
+ return jintArr;
+ }
+ std::vector<int32_t> tidsVector(size);
+ gAPH_getThreadIdsFn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr),
+ tidsVector.data(), &size);
+ jintArray jintArr = env->NewIntArray(size);
+ if (jintArr == nullptr) {
+ jniThrowException(env, "java/lang/OutOfMemoryError", nullptr);
+ return nullptr;
+ }
+ jint* threadIds = env->GetIntArrayElements(jintArr, 0);
+ for (int i = 0; i < size; ++i) {
+ threadIds[i] = tidsVector[i];
+ }
+ env->ReleaseIntArrayElements(jintArr, threadIds, 0);
+ return jintArr;
+}
+
static const JNINativeMethod gPerformanceHintMethods[] = {
{"nativeAcquireManager", "()J", (void*)nativeAcquireManager},
{"nativeGetPreferredUpdateRateNanos", "(J)J", (void*)nativeGetPreferredUpdateRateNanos},
@@ -158,6 +214,8 @@
{"nativeReportActualWorkDuration", "(JJ)V", (void*)nativeReportActualWorkDuration},
{"nativeCloseSession", "(J)V", (void*)nativeCloseSession},
{"nativeSendHint", "(JI)V", (void*)nativeSendHint},
+ {"nativeSetThreads", "(J[I)V", (void*)nativeSetThreads},
+ {"nativeGetThreadIds", "(J)[I", (void*)nativeGetThreadIds},
};
int register_android_os_PerformanceHintManager(JNIEnv* env) {
diff --git a/core/proto/android/view/displaycutout.proto b/core/proto/android/view/displaycutout.proto
index ff98e99..72d5303 100644
--- a/core/proto/android/view/displaycutout.proto
+++ b/core/proto/android/view/displaycutout.proto
@@ -31,4 +31,5 @@
optional .android.graphics.RectProto bound_top = 4;
optional .android.graphics.RectProto bound_right = 5;
optional .android.graphics.RectProto bound_bottom = 6;
+ optional .android.graphics.RectProto waterfall_insets = 7;
}
diff --git a/core/proto/android/view/displayinfo.proto b/core/proto/android/view/displayinfo.proto
index 984be2a..f936ed1 100644
--- a/core/proto/android/view/displayinfo.proto
+++ b/core/proto/android/view/displayinfo.proto
@@ -18,6 +18,7 @@
package android.view;
import "frameworks/base/core/proto/android/privacy.proto";
+import "frameworks/base/core/proto/android/view/displaycutout.proto";
option java_multiple_files = true;
@@ -34,4 +35,5 @@
// Eg: "Built-in Screen"
optional string name = 5;
optional int32 flags = 6;
+ optional DisplayCutoutProto cutout = 7;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7a6066e..78a6a66 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -315,6 +315,7 @@
<protected-broadcast android:name="android.media.MASTER_BALANCE_CHANGED_ACTION" />
<protected-broadcast android:name="android.media.SCO_AUDIO_STATE_CHANGED" />
<protected-broadcast android:name="android.media.ACTION_SCO_AUDIO_STATE_UPDATED" />
+ <protected-broadcast android:name="com.android.server.audio.action.CHECK_MUSIC_ACTIVE" />
<protected-broadcast android:name="android.intent.action.MEDIA_REMOVED" />
<protected-broadcast android:name="android.intent.action.MEDIA_UNMOUNTED" />
@@ -3422,6 +3423,8 @@
<!-- Allows an app to prevent non-system-overlay windows from being drawn on top of it -->
<permission android:name="android.permission.HIDE_OVERLAY_WINDOWS"
+ android:label="@string/permlab_hideOverlayWindows"
+ android:description="@string/permdesc_hideOverlayWindows"
android:protectionLevel="normal" />
<!-- ================================== -->
@@ -3624,6 +3627,11 @@
<permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES"
android:protectionLevel="signature|installer" />
+ <!-- @hide Allows applications to set an application-specific {@link LocaleConfig}.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.SET_APP_SPECIFIC_LOCALECONFIG"
+ android:protectionLevel="signature" />
+
<!-- @hide Allows an application to monitor {@link android.provider.Settings.Config} access.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS"
diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml
index a5608af..7aef82a 100644
--- a/core/res/res/layout/notification_material_action_list.xml
+++ b/core/res/res/layout/notification_material_action_list.xml
@@ -27,6 +27,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
+ android:layout_gravity="bottom"
android:orientation="horizontal"
android:background="@color/notification_action_list_background_color"
>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f995a6e..fb10d3d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4240,9 +4240,22 @@
If no service with the specified name exists on the device, on device search wil be
disabled.
Example: "com.android.intelliegence/.SearchUiService"
--->
+ -->
<string name="config_defaultSearchUiService" translatable="false"></string>
+ <!-- The component name, flattened to a string, for the system's credential manager
+ hybrid service. This service allows credential retrieval and storage from, and to
+ remote devices.
+
+ This service must be trusted, as it can be activated without explicit consent of the user.
+ If no service with the specified name exists on the device, credential manager
+ will still work, but remote credential retrieval and storage will not be offered to
+ the user.
+
+ See android.credentials.CredentialManager
+ -->
+ <string name="config_defaultCredentialManagerHybridService" translatable="false"></string>
+
<!-- The package name for the system's smartspace service.
This service returns smartspace results.
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d4aa47c..87298d5 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1122,6 +1122,11 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_systemAlertWindow">This app can appear on top of other apps or other parts of the screen. This may interfere with normal app usage and change the way that other apps appear.</string>
+ <!-- Title of hide overlay windows permission, which allows an app to hide overlay windows from other apps. -->
+ <string name="permlab_hideOverlayWindows">hide other apps overlays</string>
+ <!-- Description of hide overlay windows permission, which allows an app to prevent overlays from being drawn on top of it. [CHAR LIMIT=NONE] -->
+ <string name="permdesc_hideOverlayWindows">This app can request that the system hides overlays originating from apps from being shown on top of it.</string>
+
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_runInBackground">run in the background</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6d393f6..6fa5534 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3688,6 +3688,7 @@
<java-symbol type="string" name="config_defaultTranslationService" />
<java-symbol type="string" name="config_defaultAppPredictionService" />
<java-symbol type="string" name="config_defaultContentSuggestionsService" />
+ <java-symbol type="string" name="config_defaultCredentialManagerHybridService" />
<java-symbol type="string" name="config_defaultSearchUiService" />
<java-symbol type="string" name="config_defaultSmartspaceService" />
<java-symbol type="string" name="config_defaultWallpaperEffectsGenerationService" />
diff --git a/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java b/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java
index 372bca4..b1991c2 100644
--- a/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java
+++ b/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java
@@ -61,7 +61,7 @@
public void timeReadWriteInsetsState(int reps) {
final InsetsState insetsState = new InsetsState();
for (int i = 0; i < InsetsState.SIZE; i++) {
- insetsState.addSource(new InsetsSource(i));
+ insetsState.addSource(new InsetsSource(i, InsetsState.toPublicType(i)));
}
for (int i = 0; i < reps; i++) {
insetsState.writeToParcel(mParcel, 0);
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
index 44923b6..7eefbbc 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -137,4 +137,13 @@
assumeNotNull(s);
s.close();
}
+
+ @Test
+ public void testSetThreadsWithIllegalArgument() {
+ Session session = createSession();
+ assumeNotNull(session);
+ assertThrows(IllegalArgumentException.class, () -> {
+ session.setThreads(new int[] { });
+ });
+ }
}
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index 0bf133f..9b8a0e9 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -92,8 +92,8 @@
@Test
public void testImeVisibility() {
- final InsetsSourceControl ime =
- new InsetsSourceControl(ITYPE_IME, mLeash, false, new Point(), Insets.NONE);
+ final InsetsSourceControl ime = new InsetsSourceControl(ITYPE_IME, WindowInsets.Type.ime(),
+ mLeash, false, new Point(), Insets.NONE);
mController.onControlsChanged(new InsetsSourceControl[] { ime });
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
@@ -121,8 +121,8 @@
mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
// set control and verify visibility is applied.
- InsetsSourceControl control =
- new InsetsSourceControl(ITYPE_IME, mLeash, false, new Point(), Insets.NONE);
+ InsetsSourceControl control = new InsetsSourceControl(ITYPE_IME,
+ WindowInsets.Type.ime(), mLeash, false, new Point(), Insets.NONE);
mController.onControlsChanged(new InsetsSourceControl[] { control });
// IME show animation should be triggered when control becomes available.
verify(mController).applyAnimation(
@@ -161,8 +161,8 @@
}
// set control and verify visibility is applied.
- InsetsSourceControl control = Mockito.spy(
- new InsetsSourceControl(ITYPE_IME, mLeash, false, new Point(), Insets.NONE));
+ InsetsSourceControl control = Mockito.spy(new InsetsSourceControl(ITYPE_IME,
+ WindowInsets.Type.ime(), mLeash, false, new Point(), Insets.NONE));
// Simulate IME source control set this flag when the target has starting window.
control.setSkipAnimationOnce(true);
@@ -173,7 +173,7 @@
verify(control).getAndClearSkipAnimationOnce();
verify(mController).applyAnimation(eq(WindowInsets.Type.ime()),
eq(true) /* show */, eq(false) /* fromIme */,
- eq(expectSkipAnim) /* skipAnim */, null /* statsToken */);
+ eq(expectSkipAnim) /* skipAnim */, eq(null) /* statsToken */);
}
// If previously hasViewFocus is false, verify when requesting the IME visible next
@@ -187,7 +187,7 @@
verify(control).getAndClearSkipAnimationOnce();
verify(mController).applyAnimation(eq(WindowInsets.Type.ime()),
eq(true) /* show */, eq(true) /* fromIme */,
- eq(false) /* skipAnim */, null /* statsToken */);
+ eq(false) /* skipAnim */, eq(null) /* statsToken */);
}
});
}
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index c88255e..19ff598 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -69,7 +69,7 @@
private InsetsAnimationControlImpl mController;
private SurfaceSession mSession = new SurfaceSession();
- private SurfaceControl mTopLeash;
+ private SurfaceControl mStatusLeash;
private SurfaceControl mNavLeash;
private InsetsState mInsetsState;
@@ -80,7 +80,7 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mTopLeash = new SurfaceControl.Builder(mSession)
+ mStatusLeash = new SurfaceControl.Builder(mSession)
.setName("testSurface")
.build();
mNavLeash = new SurfaceControl.Builder(mSession)
@@ -92,15 +92,16 @@
InsetsSourceConsumer topConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR, mInsetsState,
() -> mMockTransaction, mMockController);
topConsumer.setControl(
- new InsetsSourceControl(
- ITYPE_STATUS_BAR, mTopLeash, true, new Point(0, 0),
- Insets.of(0, 100, 0, 0)),
+ new InsetsSourceControl(ITYPE_STATUS_BAR, WindowInsets.Type.statusBars(),
+ mStatusLeash, true, new Point(0, 0), Insets.of(0, 100, 0, 0)),
new int[1], new int[1]);
InsetsSourceConsumer navConsumer = new InsetsSourceConsumer(ITYPE_NAVIGATION_BAR,
mInsetsState, () -> mMockTransaction, mMockController);
- navConsumer.setControl(new InsetsSourceControl(ITYPE_NAVIGATION_BAR, mNavLeash, true,
- new Point(400, 0), Insets.of(0, 0, 100, 0)), new int[1], new int[1]);
+ navConsumer.setControl(
+ new InsetsSourceControl(ITYPE_NAVIGATION_BAR, WindowInsets.Type.navigationBars(),
+ mNavLeash, true, new Point(400, 0), Insets.of(0, 0, 100, 0)),
+ new int[1], new int[1]);
navConsumer.hide();
SparseArray<InsetsSourceControl> controls = new SparseArray<>();
@@ -143,7 +144,7 @@
assertEquals(2, params.size());
SurfaceParams first = params.get(0);
SurfaceParams second = params.get(1);
- SurfaceParams topParams = first.surface == mTopLeash ? first : second;
+ SurfaceParams topParams = first.surface == mStatusLeash ? first : second;
SurfaceParams navParams = first.surface == mNavLeash ? first : second;
assertPosition(topParams.matrix, new Rect(0, 0, 500, 100), new Rect(0, -70, 500, 30));
assertPosition(navParams.matrix, new Rect(400, 0, 500, 500), new Rect(460, 0, 560, 500));
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index c6fa778..fad9a5ca 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -174,7 +174,7 @@
@Test
public void testControlsChanged() {
- mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR));
+ mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
assertNotNull(mController.getSourceConsumer(ITYPE_STATUS_BAR).getControl().getLeash());
mController.addOnControllableInsetsChangedListener(
((controller, typeMask) -> assertEquals(statusBars(), typeMask)));
@@ -185,7 +185,7 @@
OnControllableInsetsChangedListener listener
= mock(OnControllableInsetsChangedListener.class);
mController.addOnControllableInsetsChangedListener(listener);
- mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR));
+ mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
mController.onControlsChanged(new InsetsSourceControl[0]);
assertNull(mController.getSourceConsumer(ITYPE_STATUS_BAR).getControl());
InOrder inOrder = Mockito.inOrder(listener);
@@ -197,7 +197,7 @@
@Test
public void testControlsRevoked_duringAnim() {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR));
+ mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
ArgumentCaptor<WindowInsetsAnimationController> animationController =
ArgumentCaptor.forClass(WindowInsetsAnimationController.class);
@@ -226,7 +226,8 @@
InsetsSourceControl control =
new InsetsSourceControl(
- ITYPE_STATUS_BAR, mLeash, true, new Point(), Insets.of(0, 10, 0, 0));
+ ITYPE_STATUS_BAR, statusBars(), mLeash, true, new Point(),
+ Insets.of(0, 10, 0, 0));
mController.onControlsChanged(new InsetsSourceControl[]{control});
mController.controlWindowInsetsAnimation(0, 0 /* durationMs */,
new LinearInterpolator(),
@@ -278,7 +279,7 @@
@Test
public void testApplyImeVisibility() {
- InsetsSourceControl ime = createControl(ITYPE_IME);
+ InsetsSourceControl ime = createControl(ITYPE_IME, ime());
mController.onControlsChanged(new InsetsSourceControl[] { ime });
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
@@ -417,7 +418,7 @@
@Test
public void testRestoreStartsAnimation() {
- mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR));
+ mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.hide(statusBars());
@@ -434,7 +435,7 @@
assertTrue(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible());
// Gaining control
- mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR));
+ mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(statusBars()));
mController.cancelExistingAnimations();
assertFalse(isRequestedVisible(mController, statusBars()));
@@ -455,7 +456,7 @@
mController.show(ime(), true /* fromIme */, null /* statsToken */);
// Gaining control shortly after
- mController.onControlsChanged(createSingletonControl(ITYPE_IME));
+ mController.onControlsChanged(createSingletonControl(ITYPE_IME, ime()));
assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime()));
mController.cancelExistingAnimations();
@@ -473,7 +474,7 @@
assertFalse(mController.getState().getSource(ITYPE_IME).isVisible());
// Gaining control shortly after
- mController.onControlsChanged(createSingletonControl(ITYPE_IME));
+ mController.onControlsChanged(createSingletonControl(ITYPE_IME, ime()));
// Pretend IME is calling
mController.show(ime(), true /* fromIme */, null /* statsToken */);
@@ -488,7 +489,7 @@
@Test
public void testAnimationEndState_controller() throws Exception {
- mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR));
+ mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
WindowInsetsAnimationControlListener mockListener =
@@ -514,7 +515,7 @@
@Test
public void testCancellation_afterGainingControl() throws Exception {
- mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR));
+ mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
WindowInsetsAnimationControlListener mockListener =
@@ -635,7 +636,7 @@
public void testFrameUpdateDuringAnimation() {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- mController.onControlsChanged(createSingletonControl(ITYPE_IME));
+ mController.onControlsChanged(createSingletonControl(ITYPE_IME, ime()));
// Pretend IME is calling
mController.show(ime(), true /* fromIme */, null /* statsToken */);
@@ -926,23 +927,23 @@
latch.await();
}
- private InsetsSourceControl createControl(@InternalInsetsType int type) {
+ private InsetsSourceControl createControl(int id, @InsetsType int type) {
// Simulate binder behavior by copying SurfaceControl. Otherwise, InsetsController will
// attempt to release mLeash directly.
SurfaceControl copy = new SurfaceControl(mLeash, "InsetsControllerTest.createControl");
- return new InsetsSourceControl(type, copy, InsetsState.getDefaultVisibility(type),
- new Point(), Insets.NONE);
+ return new InsetsSourceControl(id, type, copy,
+ (type & WindowInsets.Type.defaultVisible()) != 0, new Point(), Insets.NONE);
}
- private InsetsSourceControl[] createSingletonControl(@InternalInsetsType int type) {
- return new InsetsSourceControl[] { createControl(type) };
+ private InsetsSourceControl[] createSingletonControl(int id, @InsetsType int type) {
+ return new InsetsSourceControl[] { createControl(id, type) };
}
private InsetsSourceControl[] prepareControls() {
- final InsetsSourceControl navBar = createControl(ITYPE_NAVIGATION_BAR);
- final InsetsSourceControl statusBar = createControl(ITYPE_STATUS_BAR);
- final InsetsSourceControl ime = createControl(ITYPE_IME);
+ final InsetsSourceControl navBar = createControl(ITYPE_NAVIGATION_BAR, navigationBars());
+ final InsetsSourceControl statusBar = createControl(ITYPE_STATUS_BAR, statusBars());
+ final InsetsSourceControl ime = createControl(ITYPE_IME, ime());
InsetsSourceControl[] controls = new InsetsSourceControl[3];
controls[0] = navBar;
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 1253278..521b65e 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -97,7 +97,7 @@
// activity isn't running, lets ignore BadTokenException.
}
mState = new InsetsState();
- mSpyInsetsSource = Mockito.spy(new InsetsSource(ITYPE_STATUS_BAR));
+ mSpyInsetsSource = Mockito.spy(new InsetsSource(ITYPE_STATUS_BAR, statusBars()));
mState.addSource(mSpyInsetsSource);
mController = new InsetsController(new ViewRootInsetsControllerHost(mViewRoot));
@@ -113,8 +113,8 @@
instrumentation.waitForIdleSync();
mConsumer.setControl(
- new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, true /* initialVisible */,
- new Point(), Insets.NONE),
+ new InsetsSourceControl(ITYPE_STATUS_BAR, statusBars(), mLeash,
+ true /* initialVisible */, new Point(), Insets.NONE),
new int[1], new int[1]);
}
@@ -146,7 +146,7 @@
InsetsSourceConsumer consumer = new InsetsSourceConsumer(
ITYPE_IME, state, null, controller);
- InsetsSource source = new InsetsSource(ITYPE_IME);
+ InsetsSource source = new InsetsSource(ITYPE_IME, ime());
source.setFrame(0, 1, 2, 3);
consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_NONE);
@@ -182,8 +182,8 @@
verifyZeroInteractions(mMockTransaction);
int[] hideTypes = new int[1];
mConsumer.setControl(
- new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, true /* initialVisible */,
- new Point(), Insets.NONE),
+ new InsetsSourceControl(ITYPE_STATUS_BAR, statusBars(), mLeash,
+ true /* initialVisible */, new Point(), Insets.NONE),
new int[1], hideTypes);
assertEquals(statusBars(), hideTypes[0]);
assertFalse(mRemoveSurfaceCalled);
@@ -200,8 +200,8 @@
mRemoveSurfaceCalled = false;
int[] hideTypes = new int[1];
mConsumer.setControl(
- new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, false /* initialVisible */,
- new Point(), Insets.NONE),
+ new InsetsSourceControl(ITYPE_STATUS_BAR, statusBars(), mLeash,
+ false /* initialVisible */, new Point(), Insets.NONE),
new int[1], hideTypes);
assertTrue(mRemoveSurfaceCalled);
assertEquals(0, hideTypes[0]);
@@ -230,7 +230,7 @@
InsetsSourceConsumer imeConsumer = insetsController.getSourceConsumer(ITYPE_IME);
// Initial IME insets source control with its leash.
- imeConsumer.setControl(new InsetsSourceControl(ITYPE_IME, mLeash,
+ imeConsumer.setControl(new InsetsSourceControl(ITYPE_IME, ime(), mLeash,
false /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
reset(mMockTransaction);
@@ -239,7 +239,7 @@
insetsController.controlWindowInsetsAnimation(ime(), 0L,
null /* interpolator */, null /* cancellationSignal */, null /* listener */);
assertEquals(ANIMATION_TYPE_USER, insetsController.getAnimationType(ime()));
- imeConsumer.setControl(new InsetsSourceControl(ITYPE_IME, mLeash,
+ imeConsumer.setControl(new InsetsSourceControl(ITYPE_IME, ime(), mLeash,
true /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
verify(mMockTransaction, never()).show(mLeash);
});
diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java
index 2106b4b..e01440c 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java
@@ -16,9 +16,9 @@
package android.view;
-import static android.view.InsetsState.ITYPE_CAPTION_BAR;
-import static android.view.InsetsState.ITYPE_IME;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.WindowInsets.Type.captionBar;
+import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.navigationBars;
import static org.junit.Assert.assertEquals;
@@ -45,9 +45,9 @@
@RunWith(AndroidJUnit4.class)
public class InsetsSourceTest {
- private InsetsSource mSource = new InsetsSource(ITYPE_NAVIGATION_BAR);
- private InsetsSource mImeSource = new InsetsSource(ITYPE_IME);
- private InsetsSource mCaptionSource = new InsetsSource(ITYPE_CAPTION_BAR);
+ private final InsetsSource mSource = new InsetsSource(0 /* id */, navigationBars());
+ private final InsetsSource mImeSource = new InsetsSource(1 /* id */, ime());
+ private final InsetsSource mCaptionSource = new InsetsSource(2 /* id */, captionBar());
@Before
public void setUp() {
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 6443ac18..756888f 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -226,4 +226,7 @@
@Override
public void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc) {}
+
+ @Override
+ public void attachAccessibilityOverlayToWindow(int accessibilityWindowId, SurfaceControl sc) {}
}
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index 68f2927..88373e8 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -60,7 +60,8 @@
RAW_DEPTH,
RAW_DEPTH10,
PRIVATE,
- HEIC
+ HEIC,
+ JPEG_R
})
public @interface Format {
}
@@ -258,6 +259,14 @@
public static final int DEPTH_JPEG = 0x69656963;
/**
+ * Compressed JPEG format that includes an embedded recovery map.
+ *
+ * <p>JPEG compressed main image along with XMP embedded recovery map
+ * following ISO TBD.</p>
+ */
+ public static final int JPEG_R = 0x1005;
+
+ /**
* <p>Multi-plane Android YUV 420 format</p>
*
* <p>This format is a generic YCbCr format, capable of describing any 4:2:0
@@ -886,6 +895,7 @@
case Y8:
case DEPTH_JPEG:
case HEIC:
+ case JPEG_R:
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 04d62f6..abe42ee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -2922,8 +2922,10 @@
.withEndActions(() -> {
View child = mManageMenu.getChildAt(0);
child.requestAccessibilityFocus();
- // Update the AV's obscured touchable region for the new visibility state.
- mExpandedBubble.getExpandedView().updateObscuredTouchableRegion();
+ if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+ // Update the AV's obscured touchable region for the new state.
+ mExpandedBubble.getExpandedView().updateObscuredTouchableRegion();
+ }
})
.start();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index d9b4f47..7aae633 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -261,7 +261,7 @@
if (activeControl == null) {
continue;
}
- if (activeControl.getType() == InsetsState.ITYPE_IME) {
+ if (activeControl.getType() == WindowInsets.Type.ime()) {
imeSourceControl = activeControl;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
index 1f7a7fc..4a06d84 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
@@ -24,6 +24,7 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.view.MagnificationSpec;
+import android.view.SurfaceControl;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
@@ -380,5 +381,8 @@
public void notifyOutsideTouch() throws RemoteException {
// Do nothing
}
- }
+
+ @Override
+ public void attachAccessibilityOverlayToWindow(SurfaceControl sc) {}
}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
index a4b7033..31490e4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -22,7 +22,6 @@
import static android.view.KeyEvent.KEYCODE_DPAD_UP;
import static com.android.wm.shell.pip.tv.TvPipBoundsState.ORIENTATION_HORIZONTAL;
-import static com.android.wm.shell.pip.tv.TvPipBoundsState.ORIENTATION_UNDETERMINED;
import static com.android.wm.shell.pip.tv.TvPipBoundsState.ORIENTATION_VERTICAL;
import android.content.Context;
@@ -63,7 +62,8 @@
@NonNull TvPipBoundsState tvPipBoundsState,
@NonNull PipSnapAlgorithm pipSnapAlgorithm) {
super(context, tvPipBoundsState, pipSnapAlgorithm,
- new PipKeepClearAlgorithm() {});
+ new PipKeepClearAlgorithm() {
+ });
this.mTvPipBoundsState = tvPipBoundsState;
this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm();
reloadResources(context);
@@ -98,7 +98,7 @@
&& mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0
&& !mTvPipBoundsState.isTvPipManuallyCollapsed();
if (isPipExpanded) {
- updateGravityOnExpandToggled(Gravity.NO_GRAVITY, true);
+ updateGravityOnExpansionToggled(/* expanding= */ true);
}
mTvPipBoundsState.setTvPipExpanded(isPipExpanded);
return adjustBoundsForTemporaryDecor(getTvPipPlacement().getBounds());
@@ -172,135 +172,85 @@
return placement;
}
- /**
- * @return previous gravity if it is to be saved, or {@link Gravity#NO_GRAVITY} if not.
- */
- int updateGravityOnExpandToggled(int previousGravity, boolean expanding) {
+ void updateGravityOnExpansionToggled(boolean expanding) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: updateGravityOnExpandToggled(), expanding: %b"
- + ", mOrientation: %d, previous gravity: %s",
- TAG, expanding, mTvPipBoundsState.getTvFixedPipOrientation(),
- Gravity.toString(previousGravity));
+ "%s: updateGravity, expanding: %b, fixedExpandedOrientation: %d",
+ TAG, expanding, mTvPipBoundsState.getTvFixedPipOrientation());
- if (!mTvPipBoundsState.isTvExpandedPipSupported()) {
- return Gravity.NO_GRAVITY;
- }
+ int currentX = mTvPipBoundsState.getTvPipGravity() & Gravity.HORIZONTAL_GRAVITY_MASK;
+ int currentY = mTvPipBoundsState.getTvPipGravity() & Gravity.VERTICAL_GRAVITY_MASK;
+ int previousCollapsedX = mTvPipBoundsState.getTvPipPreviousCollapsedGravity()
+ & Gravity.HORIZONTAL_GRAVITY_MASK;
+ int previousCollapsedY = mTvPipBoundsState.getTvPipPreviousCollapsedGravity()
+ & Gravity.VERTICAL_GRAVITY_MASK;
- if (expanding && mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_UNDETERMINED) {
- float expandedRatio = mTvPipBoundsState.getDesiredTvExpandedAspectRatio();
- if (expandedRatio == 0) {
- return Gravity.NO_GRAVITY;
- }
- if (expandedRatio < 1) {
- mTvPipBoundsState.setTvFixedPipOrientation(ORIENTATION_VERTICAL);
- } else {
- mTvPipBoundsState.setTvFixedPipOrientation(ORIENTATION_HORIZONTAL);
- }
- }
-
- int gravityToSave = Gravity.NO_GRAVITY;
- int currentGravity = mTvPipBoundsState.getTvPipGravity();
int updatedGravity;
-
if (expanding) {
- // save collapsed gravity
- gravityToSave = mTvPipBoundsState.getTvPipGravity();
+ // Save collapsed gravity.
+ mTvPipBoundsState.setTvPipPreviousCollapsedGravity(mTvPipBoundsState.getTvPipGravity());
if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
- updatedGravity =
- Gravity.CENTER_HORIZONTAL | (currentGravity
- & Gravity.VERTICAL_GRAVITY_MASK);
+ updatedGravity = Gravity.CENTER_HORIZONTAL | currentY;
} else {
- updatedGravity =
- Gravity.CENTER_VERTICAL | (currentGravity
- & Gravity.HORIZONTAL_GRAVITY_MASK);
+ updatedGravity = currentX | Gravity.CENTER_VERTICAL;
}
} else {
- if (previousGravity != Gravity.NO_GRAVITY) {
- // The pip hasn't been moved since expanding,
- // go back to previous collapsed position.
- updatedGravity = previousGravity;
+ // Collapse to the edge that the user moved to before.
+ if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
+ updatedGravity = previousCollapsedX | currentY;
} else {
- if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
- updatedGravity =
- Gravity.RIGHT | (currentGravity & Gravity.VERTICAL_GRAVITY_MASK);
- } else {
- updatedGravity =
- Gravity.BOTTOM | (currentGravity & Gravity.HORIZONTAL_GRAVITY_MASK);
- }
+ updatedGravity = currentX | previousCollapsedY;
}
}
mTvPipBoundsState.setTvPipGravity(updatedGravity);
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: new gravity: %s", TAG, Gravity.toString(updatedGravity));
-
- return gravityToSave;
}
/**
- * @return true if gravity changed
+ * @return true if the gravity changed
*/
boolean updateGravity(int keycode) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: updateGravity, keycode: %d", TAG, keycode);
- // Check if position change is valid
+ // Check if position change is valid.
if (mTvPipBoundsState.isTvPipExpanded()) {
- int mOrientation = mTvPipBoundsState.getTvFixedPipOrientation();
- if (mOrientation == ORIENTATION_VERTICAL
+ int fixedOrientation = mTvPipBoundsState.getTvFixedPipOrientation();
+ if (fixedOrientation == ORIENTATION_VERTICAL
&& (keycode == KEYCODE_DPAD_UP || keycode == KEYCODE_DPAD_DOWN)
- || mOrientation == ORIENTATION_HORIZONTAL
+ || fixedOrientation == ORIENTATION_HORIZONTAL
&& (keycode == KEYCODE_DPAD_RIGHT || keycode == KEYCODE_DPAD_LEFT)) {
return false;
}
}
- int currentGravity = mTvPipBoundsState.getTvPipGravity();
- int updatedGravity;
- // First axis
+ int updatedX = mTvPipBoundsState.getTvPipGravity() & Gravity.HORIZONTAL_GRAVITY_MASK;
+ int updatedY = mTvPipBoundsState.getTvPipGravity() & Gravity.VERTICAL_GRAVITY_MASK;
+
switch (keycode) {
case KEYCODE_DPAD_UP:
- updatedGravity = Gravity.TOP;
+ updatedY = Gravity.TOP;
break;
case KEYCODE_DPAD_DOWN:
- updatedGravity = Gravity.BOTTOM;
+ updatedY = Gravity.BOTTOM;
break;
case KEYCODE_DPAD_LEFT:
- updatedGravity = Gravity.LEFT;
+ updatedX = Gravity.LEFT;
break;
case KEYCODE_DPAD_RIGHT:
- updatedGravity = Gravity.RIGHT;
+ updatedX = Gravity.RIGHT;
break;
default:
- updatedGravity = currentGravity;
+ // NOOP - unsupported keycode
}
- // Second axis
- switch (keycode) {
- case KEYCODE_DPAD_UP:
- case KEYCODE_DPAD_DOWN:
- if (mTvPipBoundsState.isTvPipExpanded()) {
- updatedGravity |= Gravity.CENTER_HORIZONTAL;
- } else {
- updatedGravity |= (currentGravity & Gravity.HORIZONTAL_GRAVITY_MASK);
- }
- break;
- case KEYCODE_DPAD_LEFT:
- case KEYCODE_DPAD_RIGHT:
- if (mTvPipBoundsState.isTvPipExpanded()) {
- updatedGravity |= Gravity.CENTER_VERTICAL;
- } else {
- updatedGravity |= (currentGravity & Gravity.VERTICAL_GRAVITY_MASK);
- }
- break;
- default:
- break;
- }
+ int updatedGravity = updatedX | updatedY;
- if (updatedGravity != currentGravity) {
+ if (updatedGravity != mTvPipBoundsState.getTvPipGravity()) {
mTvPipBoundsState.setTvPipGravity(updatedGravity);
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: new gravity: %s", TAG, Gravity.toString(updatedGravity));
+ "%s: updateGravity, new gravity: %s", TAG, Gravity.toString(updatedGravity));
return true;
}
return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index 1651f89..9c7c0ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -27,6 +27,7 @@
import android.graphics.Insets;
import android.util.Size;
import android.view.Gravity;
+import android.view.View;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
@@ -52,24 +53,61 @@
public @interface Orientation {
}
- public static final int DEFAULT_TV_GRAVITY = Gravity.BOTTOM | Gravity.RIGHT;
+ private final Context mContext;
+
+ private int mDefaultGravity;
+ private int mTvPipGravity;
+ private int mPreviousCollapsedGravity;
+ private boolean mIsRtl;
private final boolean mIsTvExpandedPipSupported;
private boolean mIsTvPipExpanded;
private boolean mTvPipManuallyCollapsed;
private float mDesiredTvExpandedAspectRatio;
- private @Orientation int mTvFixedPipOrientation;
- private int mTvPipGravity;
- private @Nullable Size mTvExpandedSize;
- private @NonNull Insets mPipMenuPermanentDecorInsets = Insets.NONE;
- private @NonNull Insets mPipMenuTemporaryDecorInsets = Insets.NONE;
+ @Orientation
+ private int mTvFixedPipOrientation;
+ @Nullable
+ private Size mTvExpandedSize;
+ @NonNull
+ private Insets mPipMenuPermanentDecorInsets = Insets.NONE;
+ @NonNull
+ private Insets mPipMenuTemporaryDecorInsets = Insets.NONE;
public TvPipBoundsState(@NonNull Context context) {
super(context);
+ mContext = context;
+ updateDefaultGravity();
+ mPreviousCollapsedGravity = mDefaultGravity;
mIsTvExpandedPipSupported = context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE);
}
+ public int getDefaultGravity() {
+ return mDefaultGravity;
+ }
+
+ private void updateDefaultGravity() {
+ boolean isRtl = mContext.getResources().getConfiguration().getLayoutDirection()
+ == View.LAYOUT_DIRECTION_RTL;
+ mDefaultGravity = Gravity.BOTTOM | (isRtl ? Gravity.LEFT : Gravity.RIGHT);
+
+ if (mIsRtl != isRtl) {
+ int prevGravityX = mPreviousCollapsedGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+ int prevGravityY = mPreviousCollapsedGravity & Gravity.VERTICAL_GRAVITY_MASK;
+ if ((prevGravityX & Gravity.RIGHT) == Gravity.RIGHT) {
+ mPreviousCollapsedGravity = Gravity.LEFT | prevGravityY;
+ } else if ((prevGravityX & Gravity.LEFT) == Gravity.LEFT) {
+ mPreviousCollapsedGravity = Gravity.RIGHT | prevGravityY;
+ }
+ }
+ mIsRtl = isRtl;
+ }
+
+ @Override
+ public void onConfigurationChanged() {
+ updateDefaultGravity();
+ }
+
/**
* Initialize states when first entering PiP.
*/
@@ -86,7 +124,8 @@
/** Resets the TV PiP state for a new activity. */
public void resetTvPipState() {
mTvFixedPipOrientation = ORIENTATION_UNDETERMINED;
- mTvPipGravity = DEFAULT_TV_GRAVITY;
+ mTvPipGravity = mDefaultGravity;
+ mPreviousCollapsedGravity = mDefaultGravity;
mTvPipManuallyCollapsed = false;
}
@@ -102,16 +141,23 @@
}
/** Set the PiP aspect ratio for the expanded PiP (TV) that is desired by the app. */
- public void setDesiredTvExpandedAspectRatio(float aspectRatio, boolean override) {
+ public void setDesiredTvExpandedAspectRatio(float expandedAspectRatio, boolean override) {
if (override || mTvFixedPipOrientation == ORIENTATION_UNDETERMINED) {
- mDesiredTvExpandedAspectRatio = aspectRatio;
resetTvPipState();
+ mDesiredTvExpandedAspectRatio = expandedAspectRatio;
+ if (expandedAspectRatio != 0) {
+ if (expandedAspectRatio > 1) {
+ mTvFixedPipOrientation = ORIENTATION_HORIZONTAL;
+ } else {
+ mTvFixedPipOrientation = ORIENTATION_VERTICAL;
+ }
+ }
return;
}
- if ((aspectRatio > 1 && mTvFixedPipOrientation == ORIENTATION_HORIZONTAL)
- || (aspectRatio <= 1 && mTvFixedPipOrientation == ORIENTATION_VERTICAL)
- || aspectRatio == 0) {
- mDesiredTvExpandedAspectRatio = aspectRatio;
+ if ((expandedAspectRatio > 1 && mTvFixedPipOrientation == ORIENTATION_HORIZONTAL)
+ || (expandedAspectRatio <= 1 && mTvFixedPipOrientation == ORIENTATION_VERTICAL)
+ || expandedAspectRatio == 0) {
+ mDesiredTvExpandedAspectRatio = expandedAspectRatio;
}
}
@@ -123,11 +169,6 @@
return mDesiredTvExpandedAspectRatio;
}
- /** Sets the orientation the expanded TV PiP activity has been fixed to. */
- public void setTvFixedPipOrientation(@Orientation int orientation) {
- mTvFixedPipOrientation = orientation;
- }
-
/** Returns the fixed orientation of the expanded PiP on TV. */
@Orientation
public int getTvFixedPipOrientation() {
@@ -144,6 +185,14 @@
return mTvPipGravity;
}
+ public void setTvPipPreviousCollapsedGravity(int gravity) {
+ mPreviousCollapsedGravity = gravity;
+ }
+
+ public int getTvPipPreviousCollapsedGravity() {
+ return mPreviousCollapsedGravity;
+ }
+
/** Sets whether the TV PiP is currently expanded. */
public void setTvPipExpanded(boolean expanded) {
mIsTvPipExpanded = expanded;
@@ -173,7 +222,8 @@
mPipMenuPermanentDecorInsets = permanentInsets;
}
- public @NonNull Insets getPipMenuPermanentDecorInsets() {
+ @NonNull
+ public Insets getPipMenuPermanentDecorInsets() {
return mPipMenuPermanentDecorInsets;
}
@@ -181,7 +231,8 @@
mPipMenuTemporaryDecorInsets = temporaryDecorInsets;
}
- public @NonNull Insets getPipMenuTemporaryDecorInsets() {
+ @NonNull
+ public Insets getPipMenuTemporaryDecorInsets() {
return mPipMenuTemporaryDecorInsets;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 7671081..fd4fcff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -18,6 +18,8 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.KeyEvent.KEYCODE_DPAD_LEFT;
+import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT;
import android.annotation.IntDef;
import android.app.ActivityManager;
@@ -137,7 +139,6 @@
@State
private int mState = STATE_NO_PIP;
- private int mPreviousGravity = TvPipBoundsState.DEFAULT_TV_GRAVITY;
private int mPinnedTaskId = NONEXISTENT_TASK_ID;
// How long the shell will wait for the app to close the PiP if a custom action is set.
@@ -214,6 +215,7 @@
mTvPipBoundsState = tvPipBoundsState;
mTvPipBoundsState.setDisplayId(context.getDisplayId());
mTvPipBoundsState.setDisplayLayout(new DisplayLayout(context, context.getDisplay()));
+
mTvPipBoundsAlgorithm = tvPipBoundsAlgorithm;
mTvPipBoundsController = tvPipBoundsController;
mTvPipBoundsController.setListener(this);
@@ -243,7 +245,7 @@
private void onInit() {
mPipTransitionController.registerPipTransitionCallback(this);
- loadConfigurations();
+ reloadResources();
registerPipParamsChangedListener(mPipParamsChangedForwarder);
registerTaskStackListenerCallback(mTaskStackListener);
@@ -266,9 +268,38 @@
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onConfigurationChanged(), state=%s", TAG, stateToName(mState));
- loadConfigurations();
+ int previousDefaultGravityX = mTvPipBoundsState.getDefaultGravity()
+ & Gravity.HORIZONTAL_GRAVITY_MASK;
+
+ reloadResources();
+
mPipNotificationController.onConfigurationChanged();
mTvPipBoundsAlgorithm.onConfigurationChanged(mContext);
+ mTvPipBoundsState.onConfigurationChanged();
+
+ int defaultGravityX = mTvPipBoundsState.getDefaultGravity()
+ & Gravity.HORIZONTAL_GRAVITY_MASK;
+ if (isPipShown() && previousDefaultGravityX != defaultGravityX) {
+ movePipToOppositeSide();
+ }
+ }
+
+ private void reloadResources() {
+ final Resources res = mContext.getResources();
+ mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration);
+ mPipForceCloseDelay = res.getInteger(R.integer.config_pipForceCloseDelay);
+ mEduTextWindowExitAnimationDuration =
+ res.getInteger(R.integer.pip_edu_text_window_exit_animation_duration);
+ }
+
+ private void movePipToOppositeSide() {
+ ProtoLog.i(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: movePipToOppositeSide", TAG);
+ if ((mTvPipBoundsState.getTvPipGravity() & Gravity.RIGHT) == Gravity.RIGHT) {
+ movePip(KEYCODE_DPAD_LEFT);
+ } else if ((mTvPipBoundsState.getTvPipGravity() & Gravity.LEFT) == Gravity.LEFT) {
+ movePip(KEYCODE_DPAD_RIGHT);
+ }
}
/**
@@ -332,11 +363,7 @@
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: togglePipExpansion()", TAG);
boolean expanding = !mTvPipBoundsState.isTvPipExpanded();
- int saveGravity = mTvPipBoundsAlgorithm
- .updateGravityOnExpandToggled(mPreviousGravity, expanding);
- if (saveGravity != Gravity.NO_GRAVITY) {
- mPreviousGravity = saveGravity;
- }
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(expanding);
mTvPipBoundsState.setTvPipManuallyCollapsed(!expanding);
mTvPipBoundsState.setTvPipExpanded(expanding);
@@ -347,7 +374,6 @@
public void movePip(int keycode) {
if (mTvPipBoundsAlgorithm.updateGravity(keycode)) {
mTvPipMenuController.updateGravity(mTvPipBoundsState.getTvPipGravity());
- mPreviousGravity = Gravity.NO_GRAVITY;
updatePinnedStackBounds();
} else {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -356,15 +382,6 @@
}
@Override
- public int getPipGravity() {
- return mTvPipBoundsState.getTvPipGravity();
- }
-
- public int getOrientation() {
- return mTvPipBoundsState.getTvFixedPipOrientation();
- }
-
- @Override
public void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
Set<Rect> unrestricted) {
if (mTvPipBoundsState.getDisplayId() == displayId) {
@@ -516,14 +533,6 @@
mState = state;
}
- private void loadConfigurations() {
- final Resources res = mContext.getResources();
- mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration);
- mPipForceCloseDelay = res.getInteger(R.integer.config_pipForceCloseDelay);
- mEduTextWindowExitAnimationDuration =
- res.getInteger(R.integer.pip_edu_text_window_exit_animation_duration);
- }
-
private void registerTaskStackListenerCallback(TaskStackListenerImpl taskStackListener) {
taskStackListener.addListener(new TaskStackListenerCallback() {
@Override
@@ -596,11 +605,7 @@
// 2) PiP is expanded, but expanded PiP was disabled
// --> collapse PiP
if (mTvPipBoundsState.isTvPipExpanded() && ratio == 0) {
- int saveGravity = mTvPipBoundsAlgorithm
- .updateGravityOnExpandToggled(mPreviousGravity, false);
- if (saveGravity != Gravity.NO_GRAVITY) {
- mPreviousGravity = saveGravity;
- }
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(/* expanding= */ false);
mTvPipBoundsState.setTvPipExpanded(false);
updatePinnedStackBounds();
}
@@ -610,11 +615,7 @@
if (!mTvPipBoundsState.isTvPipExpanded() && ratio != 0
&& !mTvPipBoundsState.isTvPipManuallyCollapsed()) {
mTvPipBoundsAlgorithm.updateExpandedPipSize();
- int saveGravity = mTvPipBoundsAlgorithm
- .updateGravityOnExpandToggled(mPreviousGravity, true);
- if (saveGravity != Gravity.NO_GRAVITY) {
- mPreviousGravity = saveGravity;
- }
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(/* expanding= */ true);
mTvPipBoundsState.setTvPipExpanded(true);
updatePinnedStackBounds();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index 00e4f47..8895fca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -199,7 +199,7 @@
"%s: showMovementMenuOnly()", TAG);
setInMoveMode(true);
if (mMenuIsOpen) {
- mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
+ mPipMenuView.showMoveMenu(mTvPipBoundsState.getTvPipGravity());
} else {
mCloseAfterExitMoveMenu = true;
showMenuInternal();
@@ -222,7 +222,7 @@
mMenuIsOpen = true;
grantPipMenuFocus(true);
if (mInMoveMode) {
- mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
+ mPipMenuView.showMoveMenu(mTvPipBoundsState.getTvPipGravity());
} else {
mPipMenuView.showButtonsMenu(/* exitingMoveMode= */ false);
}
@@ -236,7 +236,9 @@
}
void updateGravity(int gravity) {
- mPipMenuView.showMovementHints(gravity);
+ if (mInMoveMode) {
+ mPipMenuView.showMovementHints(gravity);
+ }
}
private Rect calculateMenuSurfaceBounds(Rect pipBounds) {
@@ -500,8 +502,6 @@
void onInMoveModeChanged();
- int getPipGravity();
-
void onMenuClosed();
void closeEduText();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index d3f1332..bb67145 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -105,6 +105,7 @@
FLAG_NOT_FOCUSABLE,
PRIVATE_FLAG_TRUSTED_OVERLAY,
TYPE_APPLICATION,
+ null /* windowToken */,
mFocusGrantToken,
TAG + " of " + decorationSurface.toString(),
mInputChannel);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 40f2e88..22df362 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -127,13 +127,14 @@
private InsetsSourceControl[] insetsSourceControl() {
return new InsetsSourceControl[]{
new InsetsSourceControl(
- ITYPE_IME, mock(SurfaceControl.class), false, new Point(0, 0), Insets.NONE)
+ ITYPE_IME, ime(), mock(SurfaceControl.class), false, new Point(0, 0),
+ Insets.NONE)
};
}
private InsetsState insetsStateWithIme(boolean visible) {
InsetsState state = new InsetsState();
- state.addSource(new InsetsSource(ITYPE_IME));
+ state.addSource(new InsetsSource(ITYPE_IME, ime()));
state.setSourceVisible(ITYPE_IME, visible);
return state;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 2fc0914..d4408d7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -19,6 +19,7 @@
import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
+import static android.view.WindowInsets.Type.navigationBars;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -291,7 +292,7 @@
mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
InsetsState insetsState = new InsetsState();
- InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR);
+ InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR, navigationBars());
insetsSource.setFrame(0, 0, 1000, 1000);
insetsState.addSource(insetsSource);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index e79b803..c4d78bb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -21,6 +21,7 @@
import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
+import static android.view.WindowInsets.Type.navigationBars;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -328,7 +329,7 @@
// Update if the insets change on the existing display layout
clearInvocations(mWindowManager);
InsetsState insetsState = new InsetsState();
- InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR);
+ InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR, navigationBars());
insetsSource.setFrame(0, 0, 1000, 1000);
insetsState.addSource(insetsSource);
displayLayout.setInsets(mContext.getResources(), insetsState);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
new file mode 100644
index 0000000..6cf6e82
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.wm.shell.pip.tv;
+
+import static android.view.KeyEvent.KEYCODE_DPAD_DOWN;
+import static android.view.KeyEvent.KEYCODE_DPAD_LEFT;
+import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT;
+import static android.view.KeyEvent.KEYCODE_DPAD_UP;
+
+import static org.junit.Assert.assertEquals;
+
+import android.view.Gravity;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.pip.PipSnapAlgorithm;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Locale;
+
+public class TvPipGravityTest extends ShellTestCase {
+
+ private static final float VERTICAL_EXPANDED_ASPECT_RATIO = 1f / 3;
+ private static final float HORIZONTAL_EXPANDED_ASPECT_RATIO = 3f;
+
+ @Mock
+ private PipSnapAlgorithm mMockPipSnapAlgorithm;
+
+ private TvPipBoundsState mTvPipBoundsState;
+ private TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mTvPipBoundsState = new TvPipBoundsState(mContext);
+ mTvPipBoundsAlgorithm = new TvPipBoundsAlgorithm(mContext, mTvPipBoundsState,
+ mMockPipSnapAlgorithm);
+
+ setRTL(false);
+ }
+
+ private void checkGravity(int gravityActual, int gravityExpected) {
+ assertEquals(gravityExpected, gravityActual);
+ }
+
+ private void setRTL(boolean isRtl) {
+ mContext.getResources().getConfiguration().setLayoutDirection(
+ isRtl ? new Locale("ar") : Locale.ENGLISH);
+ mTvPipBoundsState.onConfigurationChanged();
+ mTvPipBoundsAlgorithm.onConfigurationChanged(mContext);
+ }
+
+ private void assertGravityAfterExpansion(int gravityFrom, int gravityTo) {
+ mTvPipBoundsState.setTvPipExpanded(false);
+ mTvPipBoundsState.setTvPipGravity(gravityFrom);
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(true);
+ checkGravity(mTvPipBoundsState.getTvPipGravity(), gravityTo);
+ }
+
+ private void assertGravityAfterCollapse(int gravityFrom, int gravityTo) {
+ mTvPipBoundsState.setTvPipExpanded(true);
+ mTvPipBoundsState.setTvPipGravity(gravityFrom);
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(false);
+ checkGravity(mTvPipBoundsState.getTvPipGravity(), gravityTo);
+ }
+
+ private void assertGravityAfterExpandAndCollapse(int gravityStartAndEnd) {
+ mTvPipBoundsState.setTvPipGravity(gravityStartAndEnd);
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(true);
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(false);
+ checkGravity(mTvPipBoundsState.getTvPipGravity(), gravityStartAndEnd);
+ }
+
+ @Test
+ public void regularPip_defaultGravity() {
+ checkGravity(mTvPipBoundsState.getDefaultGravity(), Gravity.RIGHT | Gravity.BOTTOM);
+ }
+
+ @Test
+ public void regularPip_defaultGravity_RTL() {
+ setRTL(true);
+ checkGravity(mTvPipBoundsState.getDefaultGravity(), Gravity.LEFT | Gravity.BOTTOM);
+ }
+
+ @Test
+ public void updateGravity_expand_vertical() {
+ // Vertical expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
+
+ assertGravityAfterExpansion(Gravity.BOTTOM | Gravity.RIGHT,
+ Gravity.CENTER_VERTICAL | Gravity.RIGHT);
+ assertGravityAfterExpansion(Gravity.TOP | Gravity.RIGHT,
+ Gravity.CENTER_VERTICAL | Gravity.RIGHT);
+ assertGravityAfterExpansion(Gravity.BOTTOM | Gravity.LEFT,
+ Gravity.CENTER_VERTICAL | Gravity.LEFT);
+ assertGravityAfterExpansion(Gravity.TOP | Gravity.LEFT,
+ Gravity.CENTER_VERTICAL | Gravity.LEFT);
+ }
+
+ @Test
+ public void updateGravity_expand_horizontal() {
+ // Horizontal expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
+
+ assertGravityAfterExpansion(Gravity.BOTTOM | Gravity.RIGHT,
+ Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
+ assertGravityAfterExpansion(Gravity.TOP | Gravity.RIGHT,
+ Gravity.TOP | Gravity.CENTER_HORIZONTAL);
+ assertGravityAfterExpansion(Gravity.BOTTOM | Gravity.LEFT,
+ Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
+ assertGravityAfterExpansion(Gravity.TOP | Gravity.LEFT,
+ Gravity.TOP | Gravity.CENTER_HORIZONTAL);
+ }
+
+ @Test
+ public void updateGravity_collapse() {
+ // Vertical expansion
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
+ assertGravityAfterCollapse(Gravity.CENTER_VERTICAL | Gravity.RIGHT,
+ Gravity.BOTTOM | Gravity.RIGHT);
+ assertGravityAfterCollapse(Gravity.CENTER_VERTICAL | Gravity.LEFT,
+ Gravity.BOTTOM | Gravity.LEFT);
+
+ // Horizontal expansion
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
+ assertGravityAfterCollapse(Gravity.TOP | Gravity.CENTER_HORIZONTAL,
+ Gravity.TOP | Gravity.RIGHT);
+ assertGravityAfterCollapse(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
+ Gravity.BOTTOM | Gravity.RIGHT);
+ }
+
+ @Test
+ public void updateGravity_collapse_RTL() {
+ setRTL(true);
+
+ // Horizontal expansion
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
+ assertGravityAfterCollapse(Gravity.TOP | Gravity.CENTER_HORIZONTAL,
+ Gravity.TOP | Gravity.LEFT);
+ assertGravityAfterCollapse(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
+ Gravity.BOTTOM | Gravity.LEFT);
+ }
+
+ @Test
+ public void updateGravity_expand_collapse() {
+ // Vertical expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
+
+ assertGravityAfterExpandAndCollapse(Gravity.BOTTOM | Gravity.RIGHT);
+ assertGravityAfterExpandAndCollapse(Gravity.BOTTOM | Gravity.LEFT);
+ assertGravityAfterExpandAndCollapse(Gravity.TOP | Gravity.LEFT);
+ assertGravityAfterExpandAndCollapse(Gravity.TOP | Gravity.RIGHT);
+
+ // Horizontal expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
+
+ assertGravityAfterExpandAndCollapse(Gravity.BOTTOM | Gravity.RIGHT);
+ assertGravityAfterExpandAndCollapse(Gravity.BOTTOM | Gravity.LEFT);
+ assertGravityAfterExpandAndCollapse(Gravity.TOP | Gravity.LEFT);
+ assertGravityAfterExpandAndCollapse(Gravity.TOP | Gravity.RIGHT);
+ }
+
+ @Test
+ public void updateGravity_expand_move_collapse() {
+ // Vertical expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
+ expandMoveCollapseCheck(Gravity.TOP | Gravity.RIGHT, KEYCODE_DPAD_LEFT,
+ Gravity.TOP | Gravity.LEFT);
+
+ // Horizontal expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
+ expandMoveCollapseCheck(Gravity.BOTTOM | Gravity.LEFT, KEYCODE_DPAD_UP,
+ Gravity.TOP | Gravity.LEFT);
+ }
+
+ private void expandMoveCollapseCheck(int gravityFrom, int keycode, int gravityTo) {
+ // Expand
+ mTvPipBoundsState.setTvPipExpanded(false);
+ mTvPipBoundsState.setTvPipGravity(gravityFrom);
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(true);
+ // Move
+ mTvPipBoundsAlgorithm.updateGravity(keycode);
+ // Collapse
+ mTvPipBoundsState.setTvPipExpanded(true);
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(false);
+
+ checkGravity(mTvPipBoundsState.getTvPipGravity(), gravityTo);
+ }
+
+ private void moveAndCheckGravity(int keycode, int gravityEnd, boolean expectChange) {
+ assertEquals(expectChange, mTvPipBoundsAlgorithm.updateGravity(keycode));
+ checkGravity(mTvPipBoundsState.getTvPipGravity(), gravityEnd);
+ }
+
+ @Test
+ public void updateGravity_move_regular_valid() {
+ mTvPipBoundsState.setTvPipGravity(Gravity.BOTTOM | Gravity.RIGHT);
+ // clockwise
+ moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.BOTTOM | Gravity.LEFT, true);
+ moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.TOP | Gravity.LEFT, true);
+ moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.TOP | Gravity.RIGHT, true);
+ moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.BOTTOM | Gravity.RIGHT, true);
+ // anti-clockwise
+ moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.TOP | Gravity.RIGHT, true);
+ moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.TOP | Gravity.LEFT, true);
+ moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.BOTTOM | Gravity.LEFT, true);
+ moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.BOTTOM | Gravity.RIGHT, true);
+ }
+
+ @Test
+ public void updateGravity_move_expanded_valid() {
+ mTvPipBoundsState.setTvPipExpanded(true);
+
+ // Vertical expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
+ mTvPipBoundsState.setTvPipGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT);
+ moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.CENTER_VERTICAL | Gravity.LEFT, true);
+ moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.CENTER_VERTICAL | Gravity.RIGHT, true);
+
+ // Horizontal expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
+ mTvPipBoundsState.setTvPipGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
+ moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.TOP | Gravity.CENTER_HORIZONTAL, true);
+ moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, true);
+ }
+
+ @Test
+ public void updateGravity_move_regular_invalid() {
+ int gravity = Gravity.BOTTOM | Gravity.RIGHT;
+ mTvPipBoundsState.setTvPipGravity(gravity);
+ moveAndCheckGravity(KEYCODE_DPAD_DOWN, gravity, false);
+ moveAndCheckGravity(KEYCODE_DPAD_RIGHT, gravity, false);
+
+ gravity = Gravity.BOTTOM | Gravity.LEFT;
+ mTvPipBoundsState.setTvPipGravity(gravity);
+ moveAndCheckGravity(KEYCODE_DPAD_DOWN, gravity, false);
+ moveAndCheckGravity(KEYCODE_DPAD_LEFT, gravity, false);
+
+ gravity = Gravity.TOP | Gravity.LEFT;
+ mTvPipBoundsState.setTvPipGravity(gravity);
+ moveAndCheckGravity(KEYCODE_DPAD_UP, gravity, false);
+ moveAndCheckGravity(KEYCODE_DPAD_LEFT, gravity, false);
+
+ gravity = Gravity.TOP | Gravity.RIGHT;
+ mTvPipBoundsState.setTvPipGravity(gravity);
+ moveAndCheckGravity(KEYCODE_DPAD_UP, gravity, false);
+ moveAndCheckGravity(KEYCODE_DPAD_RIGHT, gravity, false);
+ }
+
+ @Test
+ public void updateGravity_move_expanded_invalid() {
+ mTvPipBoundsState.setTvPipExpanded(true);
+
+ // Vertical expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
+ mTvPipBoundsState.setTvPipGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT);
+ moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.CENTER_VERTICAL | Gravity.RIGHT, false);
+ moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.CENTER_VERTICAL | Gravity.RIGHT, false);
+ moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.CENTER_VERTICAL | Gravity.RIGHT, false);
+
+ mTvPipBoundsState.setTvPipGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
+ moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.CENTER_VERTICAL | Gravity.LEFT, false);
+ moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.CENTER_VERTICAL | Gravity.LEFT, false);
+ moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.CENTER_VERTICAL | Gravity.LEFT, false);
+
+ // Horizontal expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
+ mTvPipBoundsState.setTvPipGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
+ moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, false);
+ moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, false);
+ moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, false);
+
+ mTvPipBoundsState.setTvPipGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL);
+ moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.TOP | Gravity.CENTER_HORIZONTAL, false);
+ moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.TOP | Gravity.CENTER_HORIZONTAL, false);
+ moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.TOP | Gravity.CENTER_HORIZONTAL, false);
+ }
+
+ @Test
+ public void previousCollapsedGravity_defaultValue() {
+ assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
+ mTvPipBoundsState.getDefaultGravity());
+ setRTL(true);
+ assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
+ mTvPipBoundsState.getDefaultGravity());
+ }
+
+ @Test
+ public void previousCollapsedGravity_changes_on_RTL() {
+ mTvPipBoundsState.setTvPipPreviousCollapsedGravity(Gravity.TOP | Gravity.LEFT);
+ setRTL(true);
+ assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
+ Gravity.TOP | Gravity.RIGHT);
+ setRTL(false);
+ assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
+ Gravity.TOP | Gravity.LEFT);
+
+ mTvPipBoundsState.setTvPipPreviousCollapsedGravity(Gravity.BOTTOM | Gravity.RIGHT);
+ setRTL(true);
+ assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
+ Gravity.BOTTOM | Gravity.LEFT);
+ setRTL(false);
+ assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
+ Gravity.BOTTOM | Gravity.RIGHT);
+ }
+
+}
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 31516dc..1fed206 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -6890,7 +6890,8 @@
const uint32_t typeSize = dtohl(type->header.size);
const size_t newEntryCount = dtohl(type->entryCount);
-
+ const size_t entrySize = type->flags & ResTable_type::FLAG_OFFSET16 ?
+ sizeof(uint16_t) : sizeof(uint32_t);
if (kDebugLoadTableNoisy) {
printf("Type off %p: type=0x%x, headerSize=0x%x, size=%u\n",
(void*)(base-(const uint8_t*)chunk),
@@ -6898,9 +6899,9 @@
dtohs(type->header.headerSize),
typeSize);
}
- if (dtohs(type->header.headerSize)+(sizeof(uint32_t)*newEntryCount) > typeSize) {
+ if (dtohs(type->header.headerSize)+(entrySize*newEntryCount) > typeSize) {
ALOGW("ResTable_type entry index to %p extends beyond chunk end 0x%x.",
- (void*)(dtohs(type->header.headerSize) + (sizeof(uint32_t)*newEntryCount)),
+ (void*)(dtohs(type->header.headerSize) + (entrySize*newEntryCount)),
typeSize);
return (mError=BAD_TYPE);
}
diff --git a/location/java/android/location/GnssCapabilities.java b/location/java/android/location/GnssCapabilities.java
index a6da0a3..f9f9fa4 100644
--- a/location/java/android/location/GnssCapabilities.java
+++ b/location/java/android/location/GnssCapabilities.java
@@ -39,33 +39,35 @@
/** @hide */
public static final int TOP_HAL_CAPABILITY_SCHEDULING = 1;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_MSB = 2;
+ public static final int TOP_HAL_CAPABILITY_MSB = 1 << 1;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_MSA = 4;
+ public static final int TOP_HAL_CAPABILITY_MSA = 1 << 2;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_SINGLE_SHOT = 8;
+ public static final int TOP_HAL_CAPABILITY_SINGLE_SHOT = 1 << 3;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_ON_DEMAND_TIME = 16;
+ public static final int TOP_HAL_CAPABILITY_ON_DEMAND_TIME = 1 << 4;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_GEOFENCING = 32;
+ public static final int TOP_HAL_CAPABILITY_GEOFENCING = 1 << 5;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_MEASUREMENTS = 64;
+ public static final int TOP_HAL_CAPABILITY_MEASUREMENTS = 1 << 6;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_NAV_MESSAGES = 128;
+ public static final int TOP_HAL_CAPABILITY_NAV_MESSAGES = 1 << 7;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_LOW_POWER_MODE = 256;
+ public static final int TOP_HAL_CAPABILITY_LOW_POWER_MODE = 1 << 8;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_SATELLITE_BLOCKLIST = 512;
+ public static final int TOP_HAL_CAPABILITY_SATELLITE_BLOCKLIST = 1 << 9;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS = 1024;
+ public static final int TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS = 1 << 10;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_ANTENNA_INFO = 2048;
+ public static final int TOP_HAL_CAPABILITY_ANTENNA_INFO = 1 << 11;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_CORRELATION_VECTOR = 4096;
+ public static final int TOP_HAL_CAPABILITY_CORRELATION_VECTOR = 1 << 12;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_SATELLITE_PVT = 8192;
+ public static final int TOP_HAL_CAPABILITY_SATELLITE_PVT = 1 << 13;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS_FOR_DRIVING = 16384;
+ public static final int TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS_FOR_DRIVING = 1 << 14;
+ /** @hide */
+ public static final int TOP_HAL_CAPABILITY_ACCUMULATED_DELTA_RANGE = 1 << 15;
/** @hide */
@IntDef(flag = true, prefix = {"TOP_HAL_CAPABILITY_"}, value = {TOP_HAL_CAPABILITY_SCHEDULING,
@@ -75,7 +77,8 @@
TOP_HAL_CAPABILITY_LOW_POWER_MODE, TOP_HAL_CAPABILITY_SATELLITE_BLOCKLIST,
TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS, TOP_HAL_CAPABILITY_ANTENNA_INFO,
TOP_HAL_CAPABILITY_CORRELATION_VECTOR, TOP_HAL_CAPABILITY_SATELLITE_PVT,
- TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS_FOR_DRIVING})
+ TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS_FOR_DRIVING,
+ TOP_HAL_CAPABILITY_ACCUMULATED_DELTA_RANGE})
@Retention(RetentionPolicy.SOURCE)
public @interface TopHalCapabilityFlags {}
@@ -362,6 +365,19 @@
}
/**
+ * Returns {@code true} if GNSS chipset supports accumulated delta range, {@code false}
+ * otherwise.
+ *
+ * <p>The accumulated delta range information can be queried in
+ * {@link android.location.GnssMeasurement#getAccumulatedDeltaRangeState()},
+ * {@link android.location.GnssMeasurement#getAccumulatedDeltaRangeMeters()}, and
+ * {@link android.location.GnssMeasurement#getAccumulatedDeltaRangeUncertaintyMeters()}.
+ */
+ public boolean hasAccumulatedDeltaRange() {
+ return (mTopFlags & TOP_HAL_CAPABILITY_ACCUMULATED_DELTA_RANGE) != 0;
+ }
+
+ /**
* Returns {@code true} if GNSS chipset supports line-of-sight satellite identification
* measurement corrections, {@code false} otherwise.
*/
@@ -554,6 +570,9 @@
if (hasMeasurementCorrectionsForDriving()) {
builder.append("MEASUREMENT_CORRECTIONS_FOR_DRIVING ");
}
+ if (hasAccumulatedDeltaRange()) {
+ builder.append("ACCUMULATED_DELTA_RANGE ");
+ }
if (hasMeasurementCorrectionsLosSats()) {
builder.append("LOS_SATS ");
}
@@ -742,6 +761,15 @@
}
/**
+ * Sets accumulated delta range capability.
+ */
+ public @NonNull Builder setHasAccumulatedDeltaRange(boolean capable) {
+ mTopFlags = setFlag(mTopFlags, TOP_HAL_CAPABILITY_ACCUMULATED_DELTA_RANGE,
+ capable);
+ return this;
+ }
+
+ /**
* Sets measurement corrections line-of-sight satellites capability.
*/
public @NonNull Builder setHasMeasurementCorrectionsLosSats(boolean capable) {
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 8be1bcd..6e3829a 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -16,6 +16,11 @@
package android.media;
+import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
+
import android.annotation.CallbackExecutor;
import android.annotation.FloatRange;
import android.annotation.IntDef;
@@ -26,6 +31,7 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.ActivityThread;
+import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.AttributionSource;
import android.content.AttributionSource.ScopedParcelState;
@@ -455,7 +461,7 @@
int[] sampleRate = new int[] {mSampleRate};
int[] session = new int[1];
- session[0] = sessionId;
+ session[0] = resolveSessionId(context, sessionId);
//TODO: update native initialization when information about hardware init failure
// due to capture device already open is available.
@@ -624,15 +630,15 @@
/**
* Sets the context the record belongs to. This context will be used to pull information,
- * such as {@link android.content.AttributionSource}, which will be associated with
- * the AudioRecord. However, the context itself will not be retained by the AudioRecord.
+ * such as {@link android.content.AttributionSource} and device specific session ids,
+ * which will be associated with the {@link AudioRecord} the AudioRecord.
+ * However, the context itself will not be retained by the AudioRecord.
* @param context a non-null {@link Context} instance
* @return the same Builder instance.
*/
public @NonNull Builder setContext(@NonNull Context context) {
- Objects.requireNonNull(context);
// keep reference, we only copy the data when building
- mContext = context;
+ mContext = Objects.requireNonNull(context);
return this;
}
@@ -746,6 +752,9 @@
/**
* @hide
* To be only used by system components.
+ *
+ * Note, that if there's a device specific session id asociated with the context, explicitly
+ * setting a session id using this method will override it.
* @param sessionId ID of audio session the AudioRecord must be attached to, or
* {@link AudioManager#AUDIO_SESSION_ID_GENERATE} if the session isn't known at
* construction time.
@@ -983,6 +992,45 @@
}
}
+ /**
+ * Helper method to resolve session id to be used for AudioRecord initialization.
+ *
+ * This method will assign session id in following way:
+ * 1. Explicitly requested session id has the highest priority, if there is one,
+ * it will be used.
+ * 2. If there's device-specific session id asociated with the provided context,
+ * it will be used.
+ * 3. Otherwise {@link AUDIO_SESSION_ID_GENERATE} is returned.
+ *
+ * @param context {@link Context} to use for extraction of device specific session id.
+ * @param requestedSessionId explicitly requested session id or AUDIO_SESSION_ID_GENERATE.
+ * @return session id to be passed to AudioService for the {@link AudioRecord} instance given
+ * provided {@link Context} instance and explicitly requested session id.
+ */
+ private static int resolveSessionId(@Nullable Context context, int requestedSessionId) {
+ if (requestedSessionId != AUDIO_SESSION_ID_GENERATE) {
+ // Use explicitly requested session id.
+ return requestedSessionId;
+ }
+
+ if (context == null) {
+ return AUDIO_SESSION_ID_GENERATE;
+ }
+
+ int deviceId = context.getDeviceId();
+ if (deviceId == DEVICE_ID_DEFAULT) {
+ return AUDIO_SESSION_ID_GENERATE;
+ }
+
+ VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
+ if (vdm == null || vdm.getDevicePolicy(deviceId, POLICY_TYPE_AUDIO)
+ == DEVICE_POLICY_DEFAULT) {
+ return AUDIO_SESSION_ID_GENERATE;
+ }
+
+ return vdm.getAudioRecordingSessionId(deviceId);
+ }
+
// Convenience method for the constructor's parameter checks.
// This, getChannelMaskFromLegacyConfig and audioBuffSizeCheck are where constructor
// IllegalArgumentException-s are thrown
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index c2c752e..50749e7 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -16,6 +16,8 @@
package android.media;
+import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
+
import android.annotation.CallbackExecutor;
import android.annotation.FloatRange;
import android.annotation.IntDef;
@@ -26,6 +28,7 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
import android.media.audiopolicy.AudioMix;
import android.media.audiopolicy.AudioMixingRule;
import android.media.audiopolicy.AudioPolicy;
@@ -545,7 +548,7 @@
/**
* Audio session ID
*/
- private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
+ private int mSessionId = AUDIO_SESSION_ID_GENERATE;
/**
* HW_AV_SYNC track AV Sync Header
*/
@@ -645,7 +648,7 @@
int bufferSizeInBytes, int mode)
throws IllegalArgumentException {
this(streamType, sampleRateInHz, channelConfig, audioFormat,
- bufferSizeInBytes, mode, AudioManager.AUDIO_SESSION_ID_GENERATE);
+ bufferSizeInBytes, mode, AUDIO_SESSION_ID_GENERATE);
}
/**
@@ -749,12 +752,12 @@
public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
int mode, int sessionId)
throws IllegalArgumentException {
- this(attributes, format, bufferSizeInBytes, mode, sessionId, false /*offload*/,
- ENCAPSULATION_MODE_NONE, null /* tunerConfiguration */);
+ this(null /* context */, attributes, format, bufferSizeInBytes, mode, sessionId,
+ false /*offload*/, ENCAPSULATION_MODE_NONE, null /* tunerConfiguration */);
}
- private AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
- int mode, int sessionId, boolean offload, int encapsulationMode,
+ private AudioTrack(@Nullable Context context, AudioAttributes attributes, AudioFormat format,
+ int bufferSizeInBytes, int mode, int sessionId, boolean offload, int encapsulationMode,
@Nullable TunerConfiguration tunerConfiguration)
throws IllegalArgumentException {
super(attributes, AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK);
@@ -817,7 +820,8 @@
int[] sampleRate = new int[] {mSampleRate};
int[] session = new int[1];
- session[0] = sessionId;
+ session[0] = resolvePlaybackSessionId(context, sessionId);
+
// native initialization
int initResult = native_setup(new WeakReference<AudioTrack>(this), mAttributes,
sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat,
@@ -1028,11 +1032,12 @@
* <br>Offload is false by default.
*/
public static class Builder {
+ private Context mContext;
private AudioAttributes mAttributes;
private AudioFormat mFormat;
private int mBufferSizeInBytes;
private int mEncapsulationMode = ENCAPSULATION_MODE_NONE;
- private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
+ private int mSessionId = AUDIO_SESSION_ID_GENERATE;
private int mMode = MODE_STREAM;
private int mPerformanceMode = PERFORMANCE_MODE_NONE;
private boolean mOffload = false;
@@ -1046,6 +1051,19 @@
}
/**
+ * Sets the context the track belongs to. This context will be used to pull information,
+ * such as {@link android.content.AttributionSource} and device specific audio session ids,
+ * which will be associated with the {@link AudioTrack}. However, the context itself will
+ * not be retained by the {@link AudioTrack}.
+ * @param context a non-null {@link Context} instance
+ * @return the same Builder instance.
+ */
+ public @NonNull Builder setContext(@NonNull Context context) {
+ mContext = Objects.requireNonNull(context);
+ return this;
+ }
+
+ /**
* Sets the {@link AudioAttributes}.
* @param attributes a non-null {@link AudioAttributes} instance that describes the audio
* data to be played.
@@ -1152,6 +1170,10 @@
/**
* Sets the session ID the {@link AudioTrack} will be attached to.
+ *
+ * Note, that if there's a device specific session id asociated with the context, explicitly
+ * setting a session id using this method will override it
+ * (see {@link Builder#setContext(Context)}).
* @param sessionId a strictly positive ID number retrieved from another
* <code>AudioTrack</code> via {@link AudioTrack#getAudioSessionId()} or allocated by
* {@link AudioManager} via {@link AudioManager#generateAudioSessionId()}, or
@@ -1161,7 +1183,7 @@
*/
public @NonNull Builder setSessionId(@IntRange(from = 1) int sessionId)
throws IllegalArgumentException {
- if ((sessionId != AudioManager.AUDIO_SESSION_ID_GENERATE) && (sessionId < 1)) {
+ if ((sessionId != AUDIO_SESSION_ID_GENERATE) && (sessionId < 1)) {
throw new IllegalArgumentException("Invalid audio session ID " + sessionId);
}
mSessionId = sessionId;
@@ -1371,8 +1393,8 @@
try {
final AudioTrack track = new AudioTrack(
- mAttributes, mFormat, mBufferSizeInBytes, mMode, mSessionId, mOffload,
- mEncapsulationMode, mTunerConfiguration);
+ mContext, mAttributes, mFormat, mBufferSizeInBytes, mMode, mSessionId,
+ mOffload, mEncapsulationMode, mTunerConfiguration);
if (track.getState() == STATE_UNINITIALIZED) {
// release is not necessary
throw new UnsupportedOperationException("Cannot create AudioTrack");
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index f223bfd..72aaa35 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -1199,6 +1199,7 @@
case ImageFormat.RAW_PRIVATE:
case ImageFormat.DEPTH_JPEG:
case ImageFormat.HEIC:
+ case ImageFormat.JPEG_R:
width = ImageReader.this.getWidth();
break;
default:
@@ -1217,6 +1218,7 @@
case ImageFormat.RAW_PRIVATE:
case ImageFormat.DEPTH_JPEG:
case ImageFormat.HEIC:
+ case ImageFormat.JPEG_R:
height = ImageReader.this.getHeight();
break;
default:
diff --git a/media/java/android/media/ImageUtils.java b/media/java/android/media/ImageUtils.java
index 2f1a36c..8f7019d 100644
--- a/media/java/android/media/ImageUtils.java
+++ b/media/java/android/media/ImageUtils.java
@@ -68,6 +68,7 @@
case ImageFormat.RAW_DEPTH10:
case ImageFormat.DEPTH_JPEG:
case ImageFormat.HEIC:
+ case ImageFormat.JPEG_R:
return 1;
case ImageFormat.PRIVATE:
return 0;
@@ -231,6 +232,7 @@
case ImageFormat.DEPTH_POINT_CLOUD:
case ImageFormat.DEPTH_JPEG:
case ImageFormat.HEIC:
+ case ImageFormat.JPEG_R:
estimatedBytePerPixel = 0.3;
break;
case ImageFormat.Y8:
@@ -304,6 +306,7 @@
case ImageFormat.RAW_DEPTH:
case ImageFormat.RAW_DEPTH10:
case ImageFormat.HEIC:
+ case ImageFormat.JPEG_R:
return new Size(image.getWidth(), image.getHeight());
case ImageFormat.PRIVATE:
return new Size(0, 0);
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 79a5902..273c7af 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -661,10 +661,26 @@
* Doing so frees any resources you have previously acquired.
*/
public MediaPlayer() {
- this(AudioSystem.AUDIO_SESSION_ALLOCATE);
+ this(/*context=*/null, AudioSystem.AUDIO_SESSION_ALLOCATE);
}
- private MediaPlayer(int sessionId) {
+
+ /**
+ * Default constructor with context.
+ *
+ * <p>Consider using one of the create() methods for synchronously instantiating a
+ * MediaPlayer from a Uri or resource.
+ *
+ * @param context non-null context. This context will be used to pull information,
+ * such as {@link android.content.AttributionSource} and device specific session ids, which
+ * will be associated with the {@link MediaPlayer}.
+ * However, the context itself will not be retained by the MediaPlayer.
+ */
+ public MediaPlayer(@NonNull Context context) {
+ this(Objects.requireNonNull(context), AudioSystem.AUDIO_SESSION_ALLOCATE);
+ }
+
+ private MediaPlayer(Context context, int sessionId) {
super(new AudioAttributes.Builder().build(),
AudioPlaybackConfiguration.PLAYER_TYPE_JAM_MEDIAPLAYER);
@@ -680,7 +696,9 @@
mTimeProvider = new TimeProvider(this);
mOpenSubtitleSources = new Vector<InputStream>();
- AttributionSource attributionSource = AttributionSource.myAttributionSource();
+ AttributionSource attributionSource =
+ context == null ? AttributionSource.myAttributionSource()
+ : context.getAttributionSource();
// set the package name to empty if it was null
if (attributionSource.getPackageName() == null) {
attributionSource = attributionSource.withPackageName("");
@@ -693,7 +711,9 @@
native_setup(new WeakReference<MediaPlayer>(this), attributionSourceState.getParcel());
}
- baseRegisterPlayer(sessionId);
+ int effectiveSessionId = resolvePlaybackSessionId(context, sessionId);
+ baseRegisterPlayer(effectiveSessionId);
+ native_setAudioSessionId(effectiveSessionId);
}
private Parcel createPlayerIIdParcel() {
@@ -932,11 +952,10 @@
AudioAttributes audioAttributes, int audioSessionId) {
try {
- MediaPlayer mp = new MediaPlayer(audioSessionId);
+ MediaPlayer mp = new MediaPlayer(context, audioSessionId);
final AudioAttributes aa = audioAttributes != null ? audioAttributes :
new AudioAttributes.Builder().build();
mp.setAudioAttributes(aa);
- mp.native_setAudioSessionId(audioSessionId);
mp.setDataSource(context, uri);
if (holder != null) {
mp.setDisplay(holder);
@@ -998,7 +1017,7 @@
AssetFileDescriptor afd = context.getResources().openRawResourceFd(resid);
if (afd == null) return null;
- MediaPlayer mp = new MediaPlayer(audioSessionId);
+ MediaPlayer mp = new MediaPlayer(context, audioSessionId);
final AudioAttributes aa = audioAttributes != null ? audioAttributes :
new AudioAttributes.Builder().build();
@@ -2402,6 +2421,9 @@
* However, it is possible to force this player to be part of an already existing audio session
* by calling this method.
* This method must be called before one of the overloaded <code> setDataSource </code> methods.
+ * Note that session id set using this method will override device-specific audio session id,
+ * if the {@link MediaPlayer} was instantiated using device-specific {@link Context} -
+ * see {@link MediaPlayer#MediaPlayer(Context)}.
* @throws IllegalStateException if it is called in an invalid state
*/
public void setAudioSessionId(int sessionId)
diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java
index 72ee00f..38115e1 100644
--- a/media/java/android/media/PlayerBase.java
+++ b/media/java/android/media/PlayerBase.java
@@ -16,9 +16,15 @@
package android.media;
+import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
+import android.companion.virtual.VirtualDeviceManager;
import android.content.Context;
import android.os.IBinder;
import android.os.Parcel;
@@ -535,4 +541,44 @@
protected String getCurrentOpPackageName() {
return TextUtils.emptyIfNull(ActivityThread.currentOpPackageName());
}
+
+ /**
+ * Helper method to resolve which session id should be used for player initialization.
+ *
+ * This method will assign session id in following way:
+ * 1. Explicitly requested session id has the highest priority, if there is one,
+ * it will be used.
+ * 2. If there's device-specific session id associated with the provided context,
+ * it will be used.
+ * 3. Otherwise {@link AUDIO_SESSION_ID_GENERATE} is returned.
+ *
+ * @param context {@link Context} to use for extraction of device specific session id.
+ * @param requestedSessionId explicitly requested session id or AUDIO_SESSION_ID_GENERATE.
+ * @return session id to be passed to AudioService for the player initialization given
+ * provided {@link Context} instance and explicitly requested session id.
+ */
+ protected static int resolvePlaybackSessionId(@Nullable Context context,
+ int requestedSessionId) {
+ if (requestedSessionId != AUDIO_SESSION_ID_GENERATE) {
+ // Use explicitly requested session id.
+ return requestedSessionId;
+ }
+
+ if (context == null) {
+ return AUDIO_SESSION_ID_GENERATE;
+ }
+
+ int deviceId = context.getDeviceId();
+ if (deviceId == DEVICE_ID_DEFAULT) {
+ return AUDIO_SESSION_ID_GENERATE;
+ }
+
+ VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
+ if (vdm == null || vdm.getDevicePolicy(deviceId, POLICY_TYPE_AUDIO)
+ == DEVICE_POLICY_DEFAULT) {
+ return AUDIO_SESSION_ID_GENERATE;
+ }
+
+ return vdm.getAudioPlaybackSessionId(deviceId);
+ }
}
diff --git a/media/tests/MediaFrameworkTest/Android.bp b/media/tests/MediaFrameworkTest/Android.bp
index 48d56d8..06ec949e 100644
--- a/media/tests/MediaFrameworkTest/Android.bp
+++ b/media/tests/MediaFrameworkTest/Android.bp
@@ -13,13 +13,18 @@
libs: [
"android.test.runner",
"android.test.base",
+ "android.test.mock",
],
static_libs: [
- "mockito-target-minus-junit4",
+ "mockito-target-inline-minus-junit4",
"androidx.test.ext.junit",
"androidx.test.rules",
"android-ex-camera2",
- "testng"
+ "testng",
+ ],
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
],
platform_apis: true,
}
diff --git a/media/tests/MediaFrameworkTest/AndroidManifest.xml b/media/tests/MediaFrameworkTest/AndroidManifest.xml
index 9211a7e..41ac285 100644
--- a/media/tests/MediaFrameworkTest/AndroidManifest.xml
+++ b/media/tests/MediaFrameworkTest/AndroidManifest.xml
@@ -24,7 +24,8 @@
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
- <application android:networkSecurityConfig="@xml/network_security_config">
+ <application android:networkSecurityConfig="@xml/network_security_config"
+ android:debuggable="true">
<uses-library android:name="android.test.runner"/>
<activity android:label="@string/app_name"
android:name="MediaFrameworkTest"
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioRecordUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioRecordUnitTest.java
new file mode 100644
index 0000000..9c813c2
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioRecordUnitTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.mediaframeworktest.unit;
+
+import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.companion.virtual.VirtualDeviceManager;
+import android.content.Context;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.MediaRecorder;
+import android.test.mock.MockContext;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AudioRecordUnitTest {
+ private static final int TEST_SAMPLE_RATE = 44000;
+ private static final int TEST_VIRTUAL_DEVICE_ID = 42;
+ private static final AudioFormat TEST_AUDIO_FORMAT = new AudioFormat.Builder().setSampleRate(
+ TEST_SAMPLE_RATE).setEncoding(AudioFormat.ENCODING_PCM_16BIT).setChannelMask(
+ AudioFormat.CHANNEL_IN_MONO).build();
+ private static final int TEST_BUFFER_SIZE = AudioRecord.getMinBufferSize(
+ TEST_AUDIO_FORMAT.getSampleRate(),
+ TEST_AUDIO_FORMAT.getChannelMask(), TEST_AUDIO_FORMAT.getEncoding());
+
+ @Test
+ public void testBuilderConstructionWithContext_defaultDeviceExplicitSessionId() {
+ Context mockDefaultDeviceContext = getVirtualDeviceMockContext(
+ DEVICE_ID_DEFAULT, /*vdm=*/null);
+ int sessionId = getContext().getSystemService(AudioManager.class).generateAudioSessionId();
+
+ AudioRecord audioRecord = new AudioRecord.Builder().setContext(
+ mockDefaultDeviceContext).setAudioSource(
+ MediaRecorder.AudioSource.DEFAULT).setAudioFormat(
+ TEST_AUDIO_FORMAT).setBufferSizeInBytes(TEST_BUFFER_SIZE).setSessionId(
+ sessionId).build();
+
+ assertEquals(AudioRecord.STATE_INITIALIZED, audioRecord.getState());
+ assertEquals(sessionId, audioRecord.getAudioSessionId());
+ }
+
+ @Test
+ public void testBuilderConstructionWithContext_virtualDeviceDefaultAudioPolicy() {
+ int vdmPlaybackSessionId = getContext().getSystemService(
+ AudioManager.class).generateAudioSessionId();
+ VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+ vdmPlaybackSessionId, DEVICE_POLICY_DEFAULT);
+ Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);
+
+ AudioRecord audioRecord = new AudioRecord.Builder().setContext(
+ virtualDeviceContext).setAudioSource(
+ MediaRecorder.AudioSource.DEFAULT).setAudioFormat(
+ TEST_AUDIO_FORMAT).setBufferSizeInBytes(TEST_BUFFER_SIZE).build();
+
+ assertEquals(AudioRecord.STATE_INITIALIZED, audioRecord.getState());
+ assertNotEquals(vdmPlaybackSessionId, audioRecord.getAudioSessionId());
+ }
+
+ @Test
+ public void testBuilderConstructionWithContext_virtualDeviceCustomAudioPolicy() {
+ int vdmRecordingSessionId = getContext().getSystemService(
+ AudioManager.class).generateAudioSessionId();
+ VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+ vdmRecordingSessionId, DEVICE_POLICY_CUSTOM);
+ Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);
+
+ AudioRecord audioRecord = new AudioRecord.Builder().setContext(
+ virtualDeviceContext).setAudioSource(
+ MediaRecorder.AudioSource.DEFAULT).setAudioFormat(
+ TEST_AUDIO_FORMAT).setBufferSizeInBytes(TEST_BUFFER_SIZE).build();
+
+ assertEquals(AudioRecord.STATE_INITIALIZED, audioRecord.getState());
+ assertEquals(vdmRecordingSessionId, audioRecord.getAudioSessionId());
+ }
+
+ @Test
+ public void testBuilderConstructionWithContext_virtualDeviceSetSessionIdOverridesContext() {
+ int vdmRecordingSessionId = getContext().getSystemService(
+ AudioManager.class).generateAudioSessionId();
+ int anotherSessionId = getContext().getSystemService(
+ AudioManager.class).generateAudioSessionId();
+ VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+ vdmRecordingSessionId, DEVICE_POLICY_CUSTOM);
+ Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);
+
+ AudioRecord audioRecord = new AudioRecord.Builder().setContext(
+ virtualDeviceContext).setAudioSource(
+ MediaRecorder.AudioSource.DEFAULT).setAudioSource(
+ MediaRecorder.AudioSource.DEFAULT).setBufferSizeInBytes(
+ TEST_BUFFER_SIZE).setSessionId(
+ anotherSessionId).build();
+
+ assertEquals(AudioRecord.STATE_INITIALIZED, audioRecord.getState());
+ assertEquals(anotherSessionId, audioRecord.getAudioSessionId());
+ }
+
+ private Context getContext() {
+ return InstrumentationRegistry.getInstrumentation().getContext();
+ }
+
+ private Context getVirtualDeviceMockContext(int deviceId, VirtualDeviceManager vdm) {
+ MockContext mockContext = mock(MockContext.class);
+ when(mockContext.getDeviceId()).thenReturn(deviceId);
+ when(mockContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm);
+ when(mockContext.getAttributionSource()).thenReturn(getContext().getAttributionSource());
+ return mockContext;
+ }
+
+ private static VirtualDeviceManager getMockVirtualDeviceManager(int deviceId,
+ int recordingSessionId, int audioDevicePolicy) {
+ VirtualDeviceManager vdmMock = mock(VirtualDeviceManager.class);
+ when(vdmMock.getAudioRecordingSessionId(anyInt())).thenReturn(AUDIO_SESSION_ID_GENERATE);
+ when(vdmMock.getAudioRecordingSessionId(deviceId)).thenReturn(recordingSessionId);
+ when(vdmMock.getDevicePolicy(anyInt(), anyInt())).thenReturn(DEVICE_POLICY_DEFAULT);
+ when(vdmMock.getDevicePolicy(deviceId, POLICY_TYPE_AUDIO)).thenReturn(audioDevicePolicy);
+ return vdmMock;
+ }
+
+}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioTrackUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioTrackUnitTest.java
new file mode 100644
index 0000000..ffed39a
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioTrackUnitTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.mediaframeworktest.unit;
+
+import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.companion.virtual.VirtualDeviceManager;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+import android.test.mock.MockContext;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AudioTrackUnitTest {
+ private static final int TEST_SAMPLE_RATE = 44000;
+ private static final int TEST_VIRTUAL_DEVICE_ID = 42;
+ private static final AudioAttributes TEST_AUDIO_ATTRIBUTES =
+ new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build();
+ private static final AudioFormat TEST_AUDIO_FORMAT = new AudioFormat.Builder().setSampleRate(
+ TEST_SAMPLE_RATE).setEncoding(AudioFormat.ENCODING_PCM_16BIT).setChannelMask(
+ AudioFormat.CHANNEL_OUT_STEREO).build();
+ private static final int TEST_BUFFER_SIZE = AudioTrack.getMinBufferSize(
+ TEST_AUDIO_FORMAT.getSampleRate(),
+ TEST_AUDIO_FORMAT.getChannelMask(), TEST_AUDIO_FORMAT.getEncoding());
+
+ @Test
+ public void testBuilderConstructionWithContext_defaultDeviceExplicitSessionId() {
+ Context mockDefaultDeviceContext = getVirtualDeviceMockContext(
+ DEVICE_ID_DEFAULT, /*vdm=*/null);
+ int sessionId = getContext().getSystemService(AudioManager.class).generateAudioSessionId();
+
+ AudioTrack audioTrack = new AudioTrack.Builder().setContext(
+ mockDefaultDeviceContext).setAudioAttributes(TEST_AUDIO_ATTRIBUTES).setAudioFormat(
+ TEST_AUDIO_FORMAT).setBufferSizeInBytes(TEST_BUFFER_SIZE).setSessionId(
+ sessionId).build();
+
+ assertEquals(AudioTrack.STATE_INITIALIZED, audioTrack.getState());
+ assertEquals(sessionId, audioTrack.getAudioSessionId());
+ }
+
+ @Test
+ public void testBuilderConstructionWithContext_virtualDeviceDefaultAudioPolicy() {
+ int vdmPlaybackSessionId = getContext().getSystemService(
+ AudioManager.class).generateAudioSessionId();
+ VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+ vdmPlaybackSessionId, DEVICE_POLICY_DEFAULT);
+ Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);
+
+ AudioTrack audioTrack = new AudioTrack.Builder().setContext(
+ virtualDeviceContext).setAudioAttributes(TEST_AUDIO_ATTRIBUTES).setAudioFormat(
+ TEST_AUDIO_FORMAT).setBufferSizeInBytes(TEST_BUFFER_SIZE).build();
+
+ assertEquals(AudioTrack.STATE_INITIALIZED, audioTrack.getState());
+ assertNotEquals(vdmPlaybackSessionId, audioTrack.getAudioSessionId());
+ }
+
+ @Test
+ public void testBuilderConstructionWithContext_virtualDeviceCustomAudioPolicy() {
+ int vdmPlaybackSessionId = getContext().getSystemService(
+ AudioManager.class).generateAudioSessionId();
+ VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+ vdmPlaybackSessionId, DEVICE_POLICY_CUSTOM);
+ Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);
+
+ AudioTrack audioTrack = new AudioTrack.Builder().setContext(
+ virtualDeviceContext).setAudioAttributes(TEST_AUDIO_ATTRIBUTES).setAudioFormat(
+ TEST_AUDIO_FORMAT).setBufferSizeInBytes(TEST_BUFFER_SIZE).build();
+
+ assertEquals(AudioTrack.STATE_INITIALIZED, audioTrack.getState());
+ assertEquals(vdmPlaybackSessionId, audioTrack.getAudioSessionId());
+ }
+
+ @Test
+ public void testBuilderConstructionWithContext_virtualDeviceSetSessionIdOverridesContext() {
+ int vdmPlaybackSessionId = getContext().getSystemService(
+ AudioManager.class).generateAudioSessionId();
+ int anotherSessionId = getContext().getSystemService(
+ AudioManager.class).generateAudioSessionId();
+ VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+ vdmPlaybackSessionId, DEVICE_POLICY_CUSTOM);
+ Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);
+
+ AudioTrack audioTrack = new AudioTrack.Builder().setContext(
+ virtualDeviceContext).setAudioAttributes(
+ TEST_AUDIO_ATTRIBUTES).setAudioFormat(
+ TEST_AUDIO_FORMAT).setBufferSizeInBytes(TEST_BUFFER_SIZE).setSessionId(
+ anotherSessionId).build();
+
+ assertEquals(AudioTrack.STATE_INITIALIZED, audioTrack.getState());
+ assertEquals(anotherSessionId, audioTrack.getAudioSessionId());
+ }
+
+ private Context getContext() {
+ return InstrumentationRegistry.getInstrumentation().getContext();
+ }
+
+ private static Context getVirtualDeviceMockContext(int deviceId, VirtualDeviceManager vdm) {
+ MockContext mockContext = mock(MockContext.class);
+ when(mockContext.getDeviceId()).thenReturn(deviceId);
+ when(mockContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm);
+ return mockContext;
+ }
+
+ private static VirtualDeviceManager getMockVirtualDeviceManager(int deviceId,
+ int playbackSessionId, int audioDevicePolicy) {
+ VirtualDeviceManager vdmMock = mock(VirtualDeviceManager.class);
+ when(vdmMock.getAudioPlaybackSessionId(anyInt())).thenReturn(AUDIO_SESSION_ID_GENERATE);
+ when(vdmMock.getAudioPlaybackSessionId(deviceId)).thenReturn(playbackSessionId);
+ when(vdmMock.getDevicePolicy(anyInt(), anyInt())).thenReturn(DEVICE_POLICY_DEFAULT);
+ when(vdmMock.getDevicePolicy(deviceId, POLICY_TYPE_AUDIO)).thenReturn(audioDevicePolicy);
+ return vdmMock;
+ }
+}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerUnitTest.java
new file mode 100644
index 0000000..f480566
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerUnitTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.mediaframeworktest.unit;
+
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.companion.virtual.VirtualDeviceManager;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.test.mock.MockContext;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+@RunWith(AndroidJUnit4.class)
+public class MediaPlayerUnitTest {
+
+ private static final int TEST_VIRTUAL_DEVICE_ID = 42;
+
+ @Test
+ public void testConstructionWithContext_virtualDeviceDefaultAudioPolicy() {
+ int vdmPlaybackSessionId = getContext().getSystemService(
+ AudioManager.class).generateAudioSessionId();
+ VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+ vdmPlaybackSessionId, DEVICE_POLICY_DEFAULT);
+ Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);
+
+ MediaPlayer mediaPlayer = new MediaPlayer(virtualDeviceContext);
+
+ assertNotEquals(vdmPlaybackSessionId, mediaPlayer.getAudioSessionId());
+ }
+
+ @Test
+ public void testConstructionWithContext_virtualDeviceCustomAudioPolicy() {
+ int vdmPlaybackSessionId = getContext().getSystemService(
+ AudioManager.class).generateAudioSessionId();
+ VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+ vdmPlaybackSessionId, DEVICE_POLICY_CUSTOM);
+ Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);
+
+ MediaPlayer mediaPlayer = new MediaPlayer(virtualDeviceContext);
+
+ assertEquals(vdmPlaybackSessionId, mediaPlayer.getAudioSessionId());
+ }
+
+ @Test
+ public void testConstructionWithContext_virtualSetSessionIdOverridesContext() {
+ int vdmPlaybackSessionId = getContext().getSystemService(
+ AudioManager.class).generateAudioSessionId();
+ int anotherSessionId = getContext().getSystemService(
+ AudioManager.class).generateAudioSessionId();
+ VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+ vdmPlaybackSessionId, DEVICE_POLICY_CUSTOM);
+ Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);
+
+ MediaPlayer mediaPlayer = new MediaPlayer(virtualDeviceContext);
+ mediaPlayer.setAudioSessionId(anotherSessionId);
+
+ assertEquals(anotherSessionId, mediaPlayer.getAudioSessionId());
+ }
+
+ private Context getContext() {
+ return InstrumentationRegistry.getInstrumentation().getContext();
+ }
+
+ private Context getVirtualDeviceMockContext(int deviceId, VirtualDeviceManager vdm) {
+ MockContext mockContext = mock(MockContext.class);
+ when(mockContext.getDeviceId()).thenReturn(deviceId);
+ when(mockContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm);
+ when(mockContext.getAttributionSource()).thenReturn(getContext().getAttributionSource());
+ return mockContext;
+ }
+
+ private static VirtualDeviceManager getMockVirtualDeviceManager(int deviceId,
+ int playbackSessionId, int audioDevicePolicy) {
+ VirtualDeviceManager vdmMock = mock(VirtualDeviceManager.class);
+ when(vdmMock.getAudioPlaybackSessionId(anyInt())).thenReturn(AUDIO_SESSION_ID_GENERATE);
+ when(vdmMock.getAudioPlaybackSessionId(deviceId)).thenReturn(playbackSessionId);
+ when(vdmMock.getDevicePolicy(anyInt(), anyInt())).thenReturn(DEVICE_POLICY_DEFAULT);
+ when(vdmMock.getDevicePolicy(deviceId, POLICY_TYPE_AUDIO)).thenReturn(audioDevicePolicy);
+ return vdmMock;
+ }
+}
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 4e6a0c5..e89c8c9 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -330,6 +330,7 @@
APerformanceHint_updateTargetWorkDuration; # introduced=Tiramisu
APerformanceHint_reportActualWorkDuration; # introduced=Tiramisu
APerformanceHint_closeSession; # introduced=Tiramisu
+ APerformanceHint_setThreads; # introduced=UpsideDownCake
local:
*;
};
@@ -338,6 +339,7 @@
global:
APerformanceHint_setIHintManagerForTesting;
APerformanceHint_sendHint;
+ APerformanceHint_getThreadIds;
extern "C++" {
ASurfaceControl_registerSurfaceStatsListener*;
ASurfaceControl_unregisterSurfaceStatsListener*;
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 43b3d2e..dfbd7b5 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -62,18 +62,21 @@
struct APerformanceHintSession {
public:
- APerformanceHintSession(sp<IHintSession> session, int64_t preferredRateNanos,
- int64_t targetDurationNanos);
+ APerformanceHintSession(sp<IHintManager> hintManager, sp<IHintSession> session,
+ int64_t preferredRateNanos, int64_t targetDurationNanos);
APerformanceHintSession() = delete;
~APerformanceHintSession();
int updateTargetWorkDuration(int64_t targetDurationNanos);
int reportActualWorkDuration(int64_t actualDurationNanos);
int sendHint(int32_t hint);
+ int setThreads(const int32_t* threadIds, size_t size);
+ int getThreadIds(int32_t* const threadIds, size_t* size);
private:
friend struct APerformanceHintManager;
+ sp<IHintManager> mHintManager;
sp<IHintSession> mHintSession;
// HAL preferred update rate
const int64_t mPreferredRateNanos;
@@ -140,7 +143,7 @@
if (!ret.isOk() || !session) {
return nullptr;
}
- return new APerformanceHintSession(std::move(session), mPreferredRateNanos,
+ return new APerformanceHintSession(mHintManager, std::move(session), mPreferredRateNanos,
initialTargetWorkDurationNanos);
}
@@ -150,10 +153,12 @@
// ===================================== APerformanceHintSession implementation
-APerformanceHintSession::APerformanceHintSession(sp<IHintSession> session,
+APerformanceHintSession::APerformanceHintSession(sp<IHintManager> hintManager,
+ sp<IHintSession> session,
int64_t preferredRateNanos,
int64_t targetDurationNanos)
- : mHintSession(std::move(session)),
+ : mHintManager(hintManager),
+ mHintSession(std::move(session)),
mPreferredRateNanos(preferredRateNanos),
mTargetDurationNanos(targetDurationNanos),
mFirstTargetMetTimestamp(0),
@@ -260,6 +265,47 @@
return 0;
}
+int APerformanceHintSession::setThreads(const int32_t* threadIds, size_t size) {
+ if (size == 0) {
+ ALOGE("%s: the list of thread ids must not be empty.", __FUNCTION__);
+ return EINVAL;
+ }
+ std::vector<int32_t> tids(threadIds, threadIds + size);
+ binder::Status ret = mHintManager->setHintSessionThreads(mHintSession, tids);
+ if (!ret.isOk()) {
+ ALOGE("%s: failed: %s", __FUNCTION__, ret.exceptionMessage().c_str());
+ if (ret.exceptionCode() == binder::Status::Exception::EX_SECURITY ||
+ ret.exceptionCode() == binder::Status::Exception::EX_ILLEGAL_ARGUMENT) {
+ return EINVAL;
+ }
+ return EPIPE;
+ }
+ return 0;
+}
+
+int APerformanceHintSession::getThreadIds(int32_t* const threadIds, size_t* size) {
+ std::vector<int32_t> tids;
+ binder::Status ret = mHintManager->getHintSessionThreadIds(mHintSession, &tids);
+ if (!ret.isOk()) {
+ ALOGE("%s: failed: %s", __FUNCTION__, ret.exceptionMessage().c_str());
+ return EPIPE;
+ }
+
+ // When threadIds is nullptr, this is the first call to determine the size
+ // of the thread ids list.
+ if (threadIds == nullptr) {
+ *size = tids.size();
+ return 0;
+ }
+
+ // Second call to return the actual list of thread ids.
+ *size = tids.size();
+ for (size_t i = 0; i < *size; ++i) {
+ threadIds[i] = tids[i];
+ }
+ return 0;
+}
+
// ===================================== C API
APerformanceHintManager* APerformanceHint_getManager() {
return APerformanceHintManager::getInstance();
@@ -293,6 +339,23 @@
return reinterpret_cast<APerformanceHintSession*>(session)->sendHint(hint);
}
+int APerformanceHint_setThreads(APerformanceHintSession* session, const int32_t* threadIds,
+ size_t size) {
+ if (session == nullptr) {
+ return EINVAL;
+ }
+ return session->setThreads(threadIds, size);
+}
+
+int APerformanceHint_getThreadIds(void* aPerformanceHintSession, int32_t* const threadIds,
+ size_t* const size) {
+ if (aPerformanceHintSession == nullptr) {
+ return EINVAL;
+ }
+ return static_cast<APerformanceHintSession*>(aPerformanceHintSession)
+ ->getThreadIds(threadIds, size);
+}
+
void APerformanceHint_setIHintManagerForTesting(void* iManager) {
delete gHintManagerForTesting;
gHintManagerForTesting = nullptr;
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index 0c2d3b6..321a7dd 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -37,10 +37,15 @@
class MockIHintManager : public IHintManager {
public:
MOCK_METHOD(Status, createHintSession,
- (const ::android::sp<::android::IBinder>& token, const ::std::vector<int32_t>& tids,
- int64_t durationNanos, ::android::sp<::android::os::IHintSession>* _aidl_return),
+ (const sp<IBinder>& token, const ::std::vector<int32_t>& tids,
+ int64_t durationNanos, ::android::sp<IHintSession>* _aidl_return),
(override));
MOCK_METHOD(Status, getHintSessionPreferredRate, (int64_t * _aidl_return), (override));
+ MOCK_METHOD(Status, setHintSessionThreads,
+ (const sp<IHintSession>& hintSession, const ::std::vector<int32_t>& tids),
+ (override));
+ MOCK_METHOD(Status, getHintSessionThreadIds,
+ (const sp<IHintSession>& hintSession, ::std::vector<int32_t>* tids), (override));
MOCK_METHOD(IBinder*, onAsBinder, (), (override));
};
@@ -141,3 +146,36 @@
EXPECT_CALL(*iSession, close()).Times(Exactly(1));
APerformanceHint_closeSession(session);
}
+
+TEST_F(PerformanceHintTest, SetThreads) {
+ APerformanceHintManager* manager = createManager();
+
+ std::vector<int32_t> tids;
+ tids.push_back(1);
+ tids.push_back(2);
+ int64_t targetDuration = 56789L;
+
+ StrictMock<MockIHintSession>* iSession = new StrictMock<MockIHintSession>();
+ sp<IHintSession> session_sp(iSession);
+
+ EXPECT_CALL(*mMockIHintManager, createHintSession(_, Eq(tids), Eq(targetDuration), _))
+ .Times(Exactly(1))
+ .WillRepeatedly(DoAll(SetArgPointee<3>(std::move(session_sp)), Return(Status())));
+
+ APerformanceHintSession* session =
+ APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration);
+ ASSERT_TRUE(session);
+
+ std::vector<int32_t> emptyTids;
+ int result = APerformanceHint_setThreads(session, emptyTids.data(), emptyTids.size());
+ EXPECT_EQ(EINVAL, result);
+
+ std::vector<int32_t> newTids;
+ newTids.push_back(1);
+ newTids.push_back(3);
+ EXPECT_CALL(*mMockIHintManager, setHintSessionThreads(_, Eq(newTids)))
+ .Times(Exactly(1))
+ .WillOnce(Return(Status()));
+ result = APerformanceHint_setThreads(session, newTids.data(), newTids.size());
+ EXPECT_EQ(0, result);
+}
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index c4bb17c..3f86aba 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -77,6 +77,7 @@
<receiver android:name="com.android.carrierdefaultapp.SlicePurchaseBroadcastReceiver"
android:exported="true">
<intent-filter>
+ <action android:name="android.intent.action.LOCALE_CHANGED" />
<action android:name="com.android.phone.slice.action.START_SLICE_PURCHASE_APP" />
<action android:name="com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_TIMEOUT" />
<action android:name="com.android.phone.slice.action.NOTIFICATION_CANCELED" />
diff --git a/packages/CarrierDefaultApp/res/drawable/ic_network_boost.xml b/packages/CarrierDefaultApp/res/drawable/ic_performance_boost.xml
similarity index 100%
rename from packages/CarrierDefaultApp/res/drawable/ic_network_boost.xml
rename to packages/CarrierDefaultApp/res/drawable/ic_performance_boost.xml
diff --git a/packages/CarrierDefaultApp/res/values/strings.xml b/packages/CarrierDefaultApp/res/values/strings.xml
index 3dcdf00..df4705b 100644
--- a/packages/CarrierDefaultApp/res/values/strings.xml
+++ b/packages/CarrierDefaultApp/res/values/strings.xml
@@ -14,17 +14,17 @@
<string name="ssl_error_example">For example, the login page may not belong to the organization shown.</string>
<string name="ssl_error_continue">Continue anyway via browser</string>
- <!-- Telephony notification channel name for network boost notifications. -->
- <string name="network_boost_notification_channel">Network boost</string>
- <!-- Notification title text for the network boost notification. -->
- <string name="network_boost_notification_title">%s recommends a data boost</string>
- <!-- Notification detail text for the network boost notification. -->
- <string name="network_boost_notification_detail">Buy a network boost for better performance</string>
- <!-- Notification button text to cancel the network boost notification. -->
- <string name="network_boost_notification_button_not_now">Not now</string>
- <!-- Notification button text to manage the network boost notification. -->
- <string name="network_boost_notification_button_manage">Manage</string>
+ <!-- Telephony notification channel name for performance boost notifications. -->
+ <string name="performance_boost_notification_channel">Performance boost</string>
+ <!-- Notification title text for the performance boost notification. -->
+ <string name="performance_boost_notification_title">%s recommends a performance boost</string>
+ <!-- Notification detail text for the performance boost notification. -->
+ <string name="performance_boost_notification_detail">Buy a performance boost for better network performance</string>
+ <!-- Notification button text to cancel the performance boost notification. -->
+ <string name="performance_boost_notification_button_not_now">Not now</string>
+ <!-- Notification button text to manage the performance boost notification. -->
+ <string name="performance_boost_notification_button_manage">Manage</string>
<!-- Label to display when the slice purchase application opens. -->
- <string name="slice_purchase_app_label">Purchase a network boost.</string>
+ <string name="slice_purchase_app_label">Purchase a performance boost.</string>
</resources>
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
index c524037..5f067e9 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
@@ -19,26 +19,22 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
-import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
-import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.KeyEvent;
-import android.webkit.URLUtil;
import android.webkit.WebView;
import com.android.phone.slice.SlicePurchaseController;
-import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.TimeUnit;
/**
- * Activity that launches when the user clicks on the network boost notification.
+ * Activity that launches when the user clicks on the performance boost notification.
* This will open a {@link WebView} for the carrier website to allow the user to complete the
* premium capability purchase.
* The carrier website can get the requested premium capability using the JavaScript interface
@@ -46,7 +42,7 @@
* If the purchase is successful, the carrier website shall notify the slice purchase application
* using the JavaScript interface method
* {@code SlicePurchaseWebInterface.notifyPurchaseSuccessful(duration)}, where {@code duration} is
- * the optional duration of the network boost.
+ * the optional duration of the performance boost.
* If the purchase was not successful, the carrier website shall notify the slice purchase
* application using the JavaScript interface method
* {@code SlicePurchaseWebInterface.notifyPurchaseFailed(code, reason)}, where {@code code} is the
@@ -62,38 +58,29 @@
@NonNull private WebView mWebView;
@NonNull private Context mApplicationContext;
@NonNull private Intent mIntent;
- @Nullable private URL mUrl;
- private int mSubId;
+ @NonNull private URL mUrl;
@TelephonyManager.PremiumCapability protected int mCapability;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mIntent = getIntent();
- mSubId = mIntent.getIntExtra(SlicePurchaseController.EXTRA_SUB_ID,
+ int subId = mIntent.getIntExtra(SlicePurchaseController.EXTRA_SUB_ID,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
mCapability = mIntent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
+ String url = mIntent.getStringExtra(SlicePurchaseController.EXTRA_PURCHASE_URL);
mApplicationContext = getApplicationContext();
- mUrl = getUrl();
- logd("onCreate: subId=" + mSubId + ", capability="
- + TelephonyManager.convertPremiumCapabilityToString(mCapability)
- + ", url=" + mUrl);
+ logd("onCreate: subId=" + subId + ", capability="
+ + TelephonyManager.convertPremiumCapabilityToString(mCapability) + ", url=" + url);
- // Cancel network boost notification
- mApplicationContext.getSystemService(NotificationManager.class)
- .cancel(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG, mCapability);
+ // Cancel performance boost notification
+ SlicePurchaseBroadcastReceiver.cancelNotification(mApplicationContext, mCapability);
- // Verify intent and values are valid
- if (!SlicePurchaseBroadcastReceiver.isIntentValid(mIntent)) {
- loge("Not starting SlicePurchaseActivity with an invalid Intent: " + mIntent);
- SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
- mIntent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
- finishAndRemoveTask();
- return;
- }
+ // Verify purchase URL is valid
+ mUrl = SlicePurchaseBroadcastReceiver.getPurchaseUrl(url);
if (mUrl == null) {
- String error = "Unable to create a URL from carrier configs.";
+ String error = "Unable to create a purchase URL.";
loge(error);
Intent data = new Intent();
data.putExtra(SlicePurchaseController.EXTRA_FAILURE_CODE,
@@ -104,18 +91,26 @@
finishAndRemoveTask();
return;
}
- if (mSubId != SubscriptionManager.getDefaultSubscriptionId()) {
+
+ // Verify intent is valid
+ if (!SlicePurchaseBroadcastReceiver.isIntentValid(mIntent)) {
+ loge("Not starting SlicePurchaseActivity with an invalid Intent: " + mIntent);
+ SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
+ mIntent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
+ finishAndRemoveTask();
+ return;
+ }
+
+ // Verify sub ID is valid
+ if (subId != SubscriptionManager.getDefaultSubscriptionId()) {
loge("Unable to start the slice purchase application on the non-default data "
- + "subscription: " + mSubId);
+ + "subscription: " + subId);
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
mIntent, SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION);
finishAndRemoveTask();
return;
}
- // Create a reference to this activity in SlicePurchaseBroadcastReceiver
- SlicePurchaseBroadcastReceiver.updateSlicePurchaseActivity(mCapability, this);
-
// Create and configure WebView
setupWebView();
}
@@ -161,28 +156,9 @@
logd("onDestroy: User canceled the purchase by closing the application.");
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
mIntent, SlicePurchaseController.EXTRA_INTENT_CANCELED);
- SlicePurchaseBroadcastReceiver.removeSlicePurchaseActivity(mCapability);
super.onDestroy();
}
- @Nullable private URL getUrl() {
- String url = mApplicationContext.getSystemService(CarrierConfigManager.class)
- .getConfigForSubId(mSubId).getString(
- CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING);
- boolean isUrlValid = URLUtil.isValidUrl(url);
- if (URLUtil.isAssetUrl(url)) {
- isUrlValid = url.equals(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
- }
- if (isUrlValid) {
- try {
- return new URL(url);
- } catch (MalformedURLException ignored) {
- }
- }
- loge("Invalid URL: " + url);
- return null;
- }
-
private void setupWebView() {
// Create WebView
mWebView = new WebView(this);
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
index 367ae06..1b02c2b 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
@@ -16,6 +16,7 @@
package com.android.carrierdefaultapp;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -24,29 +25,37 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.drawable.Icon;
+import android.os.LocaleList;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.telephony.AnomalyReporter;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
+import android.webkit.URLUtil;
import android.webkit.WebView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.phone.slice.SlicePurchaseController;
-import java.lang.ref.WeakReference;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
import java.util.UUID;
/**
* The SlicePurchaseBroadcastReceiver listens for
* {@link SlicePurchaseController#ACTION_START_SLICE_PURCHASE_APP} from the SlicePurchaseController
- * in the phone process to start the slice purchase application. It displays the network boost
+ * in the phone process to start the slice purchase application. It displays the performance boost
* notification to the user and will start the {@link SlicePurchaseActivity} to display the
- * {@link WebView} to purchase network boosts from the user's carrier.
+ * {@link WebView} to purchase performance boosts from the user's carrier.
*/
public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{
private static final String TAG = "SlicePurchaseBroadcastReceiver";
@@ -57,40 +66,39 @@
*/
private static final String UUID_BAD_PENDING_INTENT = "c360246e-95dc-4abf-9dc1-929a76cd7e53";
- /** Weak references to {@link SlicePurchaseActivity} for each capability, if it exists. */
- private static final Map<Integer, WeakReference<SlicePurchaseActivity>>
- sSlicePurchaseActivities = new HashMap<>();
-
- /** Channel ID for the network boost notification. */
- private static final String NETWORK_BOOST_NOTIFICATION_CHANNEL_ID = "network_boost";
- /** Tag for the network boost notification. */
- public static final String NETWORK_BOOST_NOTIFICATION_TAG = "SlicePurchaseApp.Notification";
- /** Action for when the user clicks the "Not now" button on the network boost notification. */
+ /** Channel ID for the performance boost notification. */
+ private static final String PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID = "performance_boost";
+ /** Tag for the performance boost notification. */
+ public static final String PERFORMANCE_BOOST_NOTIFICATION_TAG = "SlicePurchaseApp.Notification";
+ /**
+ * Action for when the user clicks the "Not now" button on the performance boost notification.
+ */
private static final String ACTION_NOTIFICATION_CANCELED =
"com.android.phone.slice.action.NOTIFICATION_CANCELED";
/**
- * Create a weak reference to {@link SlicePurchaseActivity}. The reference will be removed when
- * {@link SlicePurchaseActivity#onDestroy()} is called.
- *
- * @param capability The premium capability requested.
- * @param slicePurchaseActivity The instance of SlicePurchaseActivity.
+ * A map of Intents sent by {@link SlicePurchaseController} for each capability.
+ * If this map contains an Intent for a given capability, the performance boost notification to
+ * purchase the capability is visible to the user.
+ * If this map does not contain an Intent for a given capability, either the capability was
+ * never requested or the {@link SlicePurchaseActivity} is visible to the user.
+ * An Intent is added to this map when the performance boost notification is displayed to the
+ * user and removed from the map when the notification is canceled.
*/
- public static void updateSlicePurchaseActivity(
- @TelephonyManager.PremiumCapability int capability,
- @NonNull SlicePurchaseActivity slicePurchaseActivity) {
- sSlicePurchaseActivities.put(capability, new WeakReference<>(slicePurchaseActivity));
- }
+ private static final Map<Integer, Intent> sIntents = new HashMap<>();
/**
- * Remove the weak reference to {@link SlicePurchaseActivity} when
- * {@link SlicePurchaseActivity#onDestroy()} is called.
+ * Cancel the performance boost notification for the given capability and
+ * remove the corresponding notification intent from the map.
*
- * @param capability The premium capability requested.
+ * @param context The context to cancel the notification in.
+ * @param capability The premium capability to cancel the notification for.
*/
- public static void removeSlicePurchaseActivity(
+ public static void cancelNotification(@NonNull Context context,
@TelephonyManager.PremiumCapability int capability) {
- sSlicePurchaseActivities.remove(capability);
+ context.getSystemService(NotificationManager.class).cancelAsUser(
+ PERFORMANCE_BOOST_NOTIFICATION_TAG, capability, UserHandle.ALL);
+ sIntents.remove(capability);
}
/**
@@ -139,7 +147,7 @@
* Check whether the Intent is valid and can be used to complete purchases in the slice purchase
* application. This checks that all necessary extras exist and that the values are valid.
*
- * @param intent The intent to check
+ * @param intent The intent to check.
* @return {@code true} if the intent is valid and {@code false} otherwise.
*/
public static boolean isIntentValid(@NonNull Intent intent) {
@@ -164,6 +172,12 @@
return false;
}
+ String purchaseUrl = intent.getStringExtra(SlicePurchaseController.EXTRA_PURCHASE_URL);
+ if (getPurchaseUrl(purchaseUrl) == null) {
+ loge("isIntentValid: invalid purchase URL: " + purchaseUrl);
+ return false;
+ }
+
String appName = intent.getStringExtra(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME);
if (TextUtils.isEmpty(appName)) {
loge("isIntentValid: empty requesting application name: " + appName);
@@ -180,6 +194,30 @@
SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
}
+ /**
+ * Get the {@link URL} from the given purchase URL String, if it is valid.
+ *
+ * @param purchaseUrl The purchase URL String to use to create the URL.
+ * @return The purchase URL from the given String or {@code null} if it is invalid.
+ */
+ @Nullable public static URL getPurchaseUrl(@Nullable String purchaseUrl) {
+ if (!URLUtil.isValidUrl(purchaseUrl)) {
+ return null;
+ }
+ if (URLUtil.isAssetUrl(purchaseUrl)
+ && !purchaseUrl.equals(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE)) {
+ return null;
+ }
+ URL url = null;
+ try {
+ url = new URL(purchaseUrl);
+ url.toURI();
+ } catch (MalformedURLException | URISyntaxException e) {
+ loge("Invalid purchase URL: " + purchaseUrl + ", " + e);
+ }
+ return url;
+ }
+
private static boolean isPendingIntentValid(@NonNull Intent intent, @NonNull String extra) {
String intentType = getPendingIntentType(extra);
PendingIntent pendingIntent = intent.getParcelableExtra(extra, PendingIntent.class);
@@ -223,8 +261,11 @@
public void onReceive(@NonNull Context context, @NonNull Intent intent) {
logd("onReceive intent: " + intent.getAction());
switch (intent.getAction()) {
+ case Intent.ACTION_LOCALE_CHANGED:
+ onLocaleChanged(context);
+ break;
case SlicePurchaseController.ACTION_START_SLICE_PURCHASE_APP:
- onDisplayNetworkBoostNotification(context, intent);
+ onDisplayPerformanceBoostNotification(context, intent, false);
break;
case SlicePurchaseController.ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT:
onTimeout(context, intent);
@@ -237,17 +278,31 @@
}
}
- private void onDisplayNetworkBoostNotification(@NonNull Context context,
- @NonNull Intent intent) {
- if (!isIntentValid(intent)) {
+ private void onLocaleChanged(@NonNull Context context) {
+ if (sIntents.isEmpty()) return;
+
+ for (int capability : new int[]{TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY}) {
+ if (sIntents.get(capability) != null) {
+ // Notification is active -- update notification for new locale
+ context.getSystemService(NotificationManager.class).cancelAsUser(
+ PERFORMANCE_BOOST_NOTIFICATION_TAG, capability, UserHandle.ALL);
+ onDisplayPerformanceBoostNotification(context, sIntents.get(capability), true);
+ }
+ }
+ }
+
+ private void onDisplayPerformanceBoostNotification(@NonNull Context context,
+ @NonNull Intent intent, boolean repeat) {
+ if (!repeat && !isIntentValid(intent)) {
sendSlicePurchaseAppResponse(intent,
SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
return;
}
+ Resources res = getResources(context);
NotificationChannel channel = new NotificationChannel(
- NETWORK_BOOST_NOTIFICATION_CHANNEL_ID,
- context.getResources().getString(R.string.network_boost_notification_channel),
+ PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID,
+ res.getString(R.string.performance_boost_notification_channel),
NotificationManager.IMPORTANCE_DEFAULT);
// CarrierDefaultApp notifications are unblockable by default. Make this channel blockable
// to allow users to disable notifications posted to this channel without affecting other
@@ -256,45 +311,77 @@
context.getSystemService(NotificationManager.class).createNotificationChannel(channel);
Notification notification =
- new Notification.Builder(context, NETWORK_BOOST_NOTIFICATION_CHANNEL_ID)
- .setContentTitle(String.format(context.getResources().getString(
- R.string.network_boost_notification_title),
+ new Notification.Builder(context, PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID)
+ .setContentTitle(String.format(res.getString(
+ R.string.performance_boost_notification_title),
intent.getStringExtra(
SlicePurchaseController.EXTRA_REQUESTING_APP_NAME)))
- .setContentText(context.getResources().getString(
- R.string.network_boost_notification_detail))
- .setSmallIcon(R.drawable.ic_network_boost)
+ .setContentText(res.getString(
+ R.string.performance_boost_notification_detail))
+ .setSmallIcon(R.drawable.ic_performance_boost)
.setContentIntent(createContentIntent(context, intent, 1))
.setDeleteIntent(intent.getParcelableExtra(
SlicePurchaseController.EXTRA_INTENT_CANCELED, PendingIntent.class))
// Add an action for the "Not now" button, which has the same behavior as
// the user canceling or closing the notification.
.addAction(new Notification.Action.Builder(
- Icon.createWithResource(context, R.drawable.ic_network_boost),
- context.getResources().getString(
- R.string.network_boost_notification_button_not_now),
+ Icon.createWithResource(context, R.drawable.ic_performance_boost),
+ res.getString(
+ R.string.performance_boost_notification_button_not_now),
createCanceledIntent(context, intent)).build())
// Add an action for the "Manage" button, which has the same behavior as
// the user clicking on the notification.
.addAction(new Notification.Action.Builder(
- Icon.createWithResource(context, R.drawable.ic_network_boost),
- context.getResources().getString(
- R.string.network_boost_notification_button_manage),
+ Icon.createWithResource(context, R.drawable.ic_performance_boost),
+ res.getString(
+ R.string.performance_boost_notification_button_manage),
createContentIntent(context, intent, 2)).build())
.build();
int capability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
- logd("Display the network boost notification for capability "
+ logd((repeat ? "Update" : "Display") + " the performance boost notification for capability "
+ TelephonyManager.convertPremiumCapabilityToString(capability));
context.getSystemService(NotificationManager.class).notifyAsUser(
- NETWORK_BOOST_NOTIFICATION_TAG, capability, notification, UserHandle.ALL);
- sendSlicePurchaseAppResponse(intent,
- SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
+ PERFORMANCE_BOOST_NOTIFICATION_TAG, capability, notification, UserHandle.ALL);
+ if (!repeat) {
+ sIntents.put(capability, intent);
+ sendSlicePurchaseAppResponse(intent,
+ SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
+ }
}
/**
- * Create the intent for when the user clicks on the "Manage" button on the network boost
+ * Get the {@link Resources} for the current locale.
+ *
+ * @param context The context to get the resources in.
+ *
+ * @return The resources in the current locale.
+ */
+ @VisibleForTesting
+ @NonNull public Resources getResources(@NonNull Context context) {
+ Resources resources = context.getResources();
+ Configuration config = resources.getConfiguration();
+ config.setLocale(getCurrentLocale());
+ return new Resources(resources.getAssets(), resources.getDisplayMetrics(), config);
+ }
+
+ /**
+ * Get the current {@link Locale} from the system property {@code persist.sys.locale}.
+ *
+ * @return The user's default/preferred language.
+ */
+ @VisibleForTesting
+ @NonNull public Locale getCurrentLocale() {
+ String languageTag = SystemProperties.get("persist.sys.locale");
+ if (TextUtils.isEmpty(languageTag)) {
+ return LocaleList.getAdjustedDefault().get(0);
+ }
+ return Locale.forLanguageTag(languageTag);
+ }
+
+ /**
+ * Create the intent for when the user clicks on the "Manage" button on the performance boost
* notification or the notification itself. This will open {@link SlicePurchaseActivity}.
*
* @param context The Context to create the intent for.
@@ -318,9 +405,9 @@
}
/**
- * Create the canceled intent for when the user clicks the "Not now" button on the network boost
- * notification. This will send {@link #ACTION_NOTIFICATION_CANCELED} and has the same function
- * as if the user had canceled or removed the notification.
+ * Create the canceled intent for when the user clicks the "Not now" button on the performance
+ * boost notification. This will send {@link #ACTION_NOTIFICATION_CANCELED} and has the same
+ * behavior as if the user had canceled or removed the notification.
*
* @param context The Context to create the intent for.
* @param intent The source Intent used to launch the slice purchase application.
@@ -343,16 +430,13 @@
SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
logd("Purchase capability " + TelephonyManager.convertPremiumCapabilityToString(capability)
+ " timed out.");
- if (sSlicePurchaseActivities.get(capability) == null) {
- // Notification is still active
- logd("Closing network boost notification since the user did not respond in time.");
- context.getSystemService(NotificationManager.class).cancelAsUser(
- NETWORK_BOOST_NOTIFICATION_TAG, capability, UserHandle.ALL);
+ if (sIntents.get(capability) != null) {
+ // Notification is still active -- cancel pending notification
+ logd("Closing performance boost notification since the user did not respond in time.");
+ cancelNotification(context, capability);
} else {
- // Notification was dismissed but SlicePurchaseActivity is still active
- logd("Closing slice purchase application WebView since the user did not complete the "
- + "purchase in time.");
- sSlicePurchaseActivities.get(capability).get().finishAndRemoveTask();
+ // SlicePurchaseActivity is still active -- ignore timer
+ logd("Ignoring timeout since the SlicePurchaseActivity is still active.");
}
}
@@ -360,8 +444,7 @@
int capability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
logd("onUserCanceled: " + TelephonyManager.convertPremiumCapabilityToString(capability));
- context.getSystemService(NotificationManager.class)
- .cancelAsUser(NETWORK_BOOST_NOTIFICATION_TAG, capability, UserHandle.ALL);
+ cancelNotification(context, capability);
sendSlicePurchaseAppResponse(intent, SlicePurchaseController.EXTRA_INTENT_CANCELED);
}
diff --git a/packages/CarrierDefaultApp/tests/unit/Android.bp b/packages/CarrierDefaultApp/tests/unit/Android.bp
index cdf7957..0d08ec6 100644
--- a/packages/CarrierDefaultApp/tests/unit/Android.bp
+++ b/packages/CarrierDefaultApp/tests/unit/Android.bp
@@ -37,5 +37,6 @@
// Include all test java files.
srcs: ["src/**/*.java"],
platform_apis: true,
+ use_embedded_native_libs: false,
instrumentation_for: "CarrierDefaultApp",
}
diff --git a/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml b/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml
index 7a26d95..995170a 100644
--- a/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml
@@ -17,7 +17,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.carrierdefaultapp.tests.unit">
<uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" />
- <application>
+ <application android:extractNativeLibs="true">
<uses-library android:name="android.test.runner" />
</application>
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
index cecc86d..1bf644e 100644
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
@@ -39,7 +39,6 @@
import com.android.phone.slice.SlicePurchaseController;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -94,6 +93,8 @@
SubscriptionManager.getDefaultDataSubscriptionId());
intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
+ intent.putExtra(SlicePurchaseController.EXTRA_PURCHASE_URL,
+ SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
intent.putExtra(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME, TAG);
Intent spiedIntent = spy(intent);
@@ -110,12 +111,6 @@
mSlicePurchaseActivity = startActivity(spiedIntent, null, null);
}
- @After
- public void tearDown() throws Exception {
- mSlicePurchaseActivity.onDestroy();
- super.tearDown();
- }
-
@Test
public void testOnPurchaseSuccessful() throws Exception {
int duration = 5 * 60 * 1000; // 5 minutes
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
index ab99a76..568d63c 100644
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
@@ -18,10 +18,14 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
@@ -35,11 +39,11 @@
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.UserHandle;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
-import android.util.DisplayMetrics;
import androidx.test.runner.AndroidJUnit4;
@@ -52,6 +56,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.net.URL;
+import java.util.Locale;
+
@RunWith(AndroidJUnit4.class)
public class SlicePurchaseBroadcastReceiverTest {
private static final int PHONE_ID = 0;
@@ -67,24 +74,26 @@
@Mock PendingIntent mNotificationShownIntent;
@Mock Context mContext;
@Mock Resources mResources;
+ @Mock Configuration mConfiguration;
@Mock NotificationManager mNotificationManager;
@Mock ApplicationInfo mApplicationInfo;
@Mock PackageManager mPackageManager;
- @Mock DisplayMetrics mDisplayMetrics;
- @Mock SlicePurchaseActivity mSlicePurchaseActivity;
private SlicePurchaseBroadcastReceiver mSlicePurchaseBroadcastReceiver;
- private ArgumentCaptor<Intent> mIntentCaptor;
- private ArgumentCaptor<Notification> mNotificationCaptor;
+ private Resources mSpiedResources;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mSpiedResources = spy(Resources.getSystem());
+
+ doReturn("").when(mResources).getString(anyInt());
doReturn(mNotificationManager).when(mContext)
.getSystemService(eq(NotificationManager.class));
+ doReturn(mApplicationInfo).when(mContext).getApplicationInfo();
+ doReturn(mPackageManager).when(mContext).getPackageManager();
+ doReturn(mSpiedResources).when(mContext).getResources();
- mIntentCaptor = ArgumentCaptor.forClass(Intent.class);
- mNotificationCaptor = ArgumentCaptor.forClass(Notification.class);
mSlicePurchaseBroadcastReceiver = spy(new SlicePurchaseBroadcastReceiver());
}
@@ -109,8 +118,9 @@
eq(EXTRA), eq(PendingIntent.class));
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(
mContext, mIntent, EXTRA, mDataIntent);
- verify(mPendingIntent).send(eq(mContext), eq(0), mIntentCaptor.capture());
- assertEquals(mDataIntent, mIntentCaptor.getValue());
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mPendingIntent).send(eq(mContext), eq(0), captor.capture());
+ assertEquals(mDataIntent, captor.getValue());
}
@Test
@@ -124,6 +134,8 @@
eq(SlicePurchaseController.EXTRA_SUB_ID), anyInt());
doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+ doReturn(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE).when(mIntent).getStringExtra(
+ eq(SlicePurchaseController.EXTRA_PURCHASE_URL));
doReturn(TAG).when(mIntent).getStringExtra(
eq(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME));
assertFalse(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent));
@@ -137,18 +149,53 @@
}
@Test
- public void testDisplayNetworkBoostNotification() throws Exception {
- // set up intent
- doReturn(SlicePurchaseController.ACTION_START_SLICE_PURCHASE_APP).when(mIntent).getAction();
- doReturn(PHONE_ID).when(mIntent).getIntExtra(
- eq(SlicePurchaseController.EXTRA_PHONE_ID), anyInt());
- doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mIntent).getIntExtra(
- eq(SlicePurchaseController.EXTRA_SUB_ID), anyInt());
- doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
- eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
- doReturn(TAG).when(mIntent).getStringExtra(
- eq(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME));
+ public void testGetPurchaseUrl() {
+ String[] invalidUrls = new String[] {
+ null,
+ "",
+ "www.google.com",
+ "htt://www.google.com",
+ "http//www.google.com",
+ "http:/www.google.com",
+ "file:///android_asset/",
+ "file:///android_asset/slice_store_test.html"
+ };
+ for (String url : invalidUrls) {
+ URL purchaseUrl = SlicePurchaseBroadcastReceiver.getPurchaseUrl(url);
+ assertNull(purchaseUrl);
+ }
+
+ assertEquals(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE,
+ SlicePurchaseBroadcastReceiver.getPurchaseUrl(
+ SlicePurchaseController.SLICE_PURCHASE_TEST_FILE).toString());
+ }
+
+ @Test
+ public void testDisplayPerformanceBoostNotification() throws Exception {
+ displayPerformanceBoostNotification();
+
+ // verify performance boost notification was shown
+ ArgumentCaptor<Notification> captor = ArgumentCaptor.forClass(Notification.class);
+ verify(mNotificationManager).notifyAsUser(
+ eq(SlicePurchaseBroadcastReceiver.PERFORMANCE_BOOST_NOTIFICATION_TAG),
+ eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
+ captor.capture(),
+ eq(UserHandle.ALL));
+
+ // verify notification fields
+ Notification notification = captor.getValue();
+ assertEquals(mContentIntent1, notification.contentIntent);
+ assertEquals(mPendingIntent, notification.deleteIntent);
+ assertEquals(2, notification.actions.length);
+ assertEquals(mCanceledIntent, notification.actions[0].actionIntent);
+ assertEquals(mContentIntent2, notification.actions[1].actionIntent);
+
+ // verify SlicePurchaseController was notified
+ verify(mNotificationShownIntent).send();
+ }
+
+ private void displayPerformanceBoostNotification() {
// set up pending intents
doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mPendingIntent).getCreatorPackage();
doReturn(true).when(mPendingIntent).isBroadcast();
@@ -161,14 +208,7 @@
eq(SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN),
eq(PendingIntent.class));
- // set up notification
- doReturn(mResources).when(mContext).getResources();
- doReturn(mDisplayMetrics).when(mResources).getDisplayMetrics();
- doReturn("").when(mResources).getString(anyInt());
- doReturn(mApplicationInfo).when(mContext).getApplicationInfo();
- doReturn(mPackageManager).when(mContext).getPackageManager();
-
- // set up intents created by broadcast receiver
+ // spy notification intents to prevent PendingIntent issues
doReturn(mContentIntent1).when(mSlicePurchaseBroadcastReceiver).createContentIntent(
eq(mContext), eq(mIntent), eq(1));
doReturn(mContentIntent2).when(mSlicePurchaseBroadcastReceiver).createContentIntent(
@@ -176,84 +216,104 @@
doReturn(mCanceledIntent).when(mSlicePurchaseBroadcastReceiver).createCanceledIntent(
eq(mContext), eq(mIntent));
+ // spy resources to prevent resource not found issues
+ doReturn(mResources).when(mSlicePurchaseBroadcastReceiver).getResources(eq(mContext));
+
// send ACTION_START_SLICE_PURCHASE_APP
+ doReturn(SlicePurchaseController.ACTION_START_SLICE_PURCHASE_APP).when(mIntent).getAction();
+ doReturn(PHONE_ID).when(mIntent).getIntExtra(
+ eq(SlicePurchaseController.EXTRA_PHONE_ID), anyInt());
+ doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mIntent).getIntExtra(
+ eq(SlicePurchaseController.EXTRA_SUB_ID), anyInt());
+ doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
+ eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+ doReturn(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE).when(mIntent).getStringExtra(
+ eq(SlicePurchaseController.EXTRA_PURCHASE_URL));
+ doReturn(TAG).when(mIntent).getStringExtra(
+ eq(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME));
mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
-
- // verify network boost notification was shown
- verify(mNotificationManager).notifyAsUser(
- eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG),
- eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
- mNotificationCaptor.capture(),
- eq(UserHandle.ALL));
-
- Notification notification = mNotificationCaptor.getValue();
- assertEquals(mContentIntent1, notification.contentIntent);
- assertEquals(mPendingIntent, notification.deleteIntent);
- assertEquals(2, notification.actions.length);
- assertEquals(mCanceledIntent, notification.actions[0].actionIntent);
- assertEquals(mContentIntent2, notification.actions[1].actionIntent);
-
- // verify SlicePurchaseController was notified
- verify(mNotificationShownIntent).send();
}
@Test
public void testNotificationCanceled() {
- // set up intent
+ // send ACTION_NOTIFICATION_CANCELED
doReturn("com.android.phone.slice.action.NOTIFICATION_CANCELED").when(mIntent).getAction();
doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
-
- // send ACTION_NOTIFICATION_CANCELED
mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
// verify notification was canceled
verify(mNotificationManager).cancelAsUser(
- eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG),
+ eq(SlicePurchaseBroadcastReceiver.PERFORMANCE_BOOST_NOTIFICATION_TAG),
eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
eq(UserHandle.ALL));
}
@Test
- public void testNotificationTimeout() {
- // set up intent
+ public void testNotificationTimeout() throws Exception {
+ displayPerformanceBoostNotification();
+
+ // send ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT
doReturn(SlicePurchaseController.ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT).when(mIntent)
.getAction();
doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
-
- // send ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT
mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
// verify notification was canceled
verify(mNotificationManager).cancelAsUser(
- eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG),
+ eq(SlicePurchaseBroadcastReceiver.PERFORMANCE_BOOST_NOTIFICATION_TAG),
eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
eq(UserHandle.ALL));
}
@Test
- // TODO: WebView/Activity should not close on timeout.
- // This test should be removed once implementation is fixed.
- public void testActivityTimeout() {
- // create and track activity
- SlicePurchaseBroadcastReceiver.updateSlicePurchaseActivity(
- TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, mSlicePurchaseActivity);
+ public void testLocaleChanged() throws Exception {
+ // get previous locale
+ doReturn(mConfiguration).when(mSpiedResources).getConfiguration();
+ Locale before = getLocale();
- // set up intent
- doReturn(SlicePurchaseController.ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT).when(mIntent)
- .getAction();
- doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
- eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+ // display notification
+ displayPerformanceBoostNotification();
+ clearInvocations(mNotificationManager);
+ clearInvocations(mNotificationShownIntent);
- // send ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT
+ // change current locale from previous value
+ Locale newLocale = Locale.forLanguageTag("en-US");
+ if (before.equals(newLocale)) {
+ newLocale = Locale.forLanguageTag("ko-KR");
+ }
+ doReturn(newLocale).when(mSlicePurchaseBroadcastReceiver).getCurrentLocale();
+
+ // send ACTION_LOCALE_CHANGED
+ doReturn(Intent.ACTION_LOCALE_CHANGED).when(mIntent).getAction();
mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
- // verify activity was canceled
- verify(mSlicePurchaseActivity).finishAndRemoveTask();
+ // verify notification was updated and SlicePurchaseController was not notified
+ verify(mNotificationManager).cancelAsUser(
+ eq(SlicePurchaseBroadcastReceiver.PERFORMANCE_BOOST_NOTIFICATION_TAG),
+ eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
+ eq(UserHandle.ALL));
+ verify(mNotificationManager).notifyAsUser(
+ eq(SlicePurchaseBroadcastReceiver.PERFORMANCE_BOOST_NOTIFICATION_TAG),
+ eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
+ any(Notification.class),
+ eq(UserHandle.ALL));
+ verify(mNotificationShownIntent, never()).send();
- // untrack activity
- SlicePurchaseBroadcastReceiver.removeSlicePurchaseActivity(
- TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
+ // verify locale was changed successfully
+ doCallRealMethod().when(mSlicePurchaseBroadcastReceiver).getResources(eq(mContext));
+ assertEquals(newLocale, getLocale());
+ }
+
+ private Locale getLocale() {
+ try {
+ mSlicePurchaseBroadcastReceiver.getResources(mContext);
+ fail("getLocale should not have completed successfully.");
+ } catch (NullPointerException expected) { }
+ ArgumentCaptor<Locale> captor = ArgumentCaptor.forClass(Locale.class);
+ verify(mConfiguration).setLocale(captor.capture());
+ clearInvocations(mConfiguration);
+ return captor.getValue();
}
}
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 3b21541..0764d90 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -43,7 +43,7 @@
<string name="profile_name_glasses">glasses</string>
<!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile (type) [CHAR LIMIT=NONE] -->
- <string name="summary_glasses">This app is needed to manage your <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string>
+ <string name="summary_glasses">This app is needed to manage your <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string>
<!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile for singleDevice(type) [CHAR LIMIT=NONE] -->
<string name="summary_glasses_single_device">The app is needed to manage your <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with these permissions:</string>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 49a63345..8d0a2c0 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -592,13 +592,13 @@
setupPermissionList();
} else if (deviceProfile.equals(DEVICE_PROFILE_GLASSES)) {
profileName = getString(R.string.profile_name_glasses);
- title = getHtmlFromResources(this, R.string.confirmation_title, appLabel, profileName);
+ title = getHtmlFromResources(this, R.string.confirmation_title, appLabel, deviceName);
summary = getHtmlFromResources(
this, R.string.summary_glasses_single_device, profileName, appLabel);
profileIcon = getIcon(this, R.drawable.ic_glasses);
mPermissionTypes.addAll(Arrays.asList(
- PERMISSION_PHONE, PERMISSION_SMS, PERMISSION_CONTACTS,
+ PERMISSION_NOTIFICATION, PERMISSION_PHONE, PERMISSION_SMS, PERMISSION_CONTACTS,
PERMISSION_MICROPHONE, PERMISSION_NEARBY_DEVICES));
setupPermissionList();
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
index cb0bfc6..69c6131 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
@@ -21,11 +21,8 @@
import android.content.pm.PackageInfo
import android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED
import android.content.pm.PackageManager
-import android.util.Log
import com.android.settingslib.spa.framework.util.asyncFilter
-private const val TAG = "PackageManagers"
-
interface IPackageManagers {
fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo?
fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo?
@@ -94,12 +91,7 @@
}.toSet()
override fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo? =
- try {
- packageManagerWrapper.getPackageInfoAsUserCached(packageName, flags.toLong(), userId)
- } catch (e: PackageManager.NameNotFoundException) {
- Log.w(TAG, "getPackageInfoAsUserCached() failed", e)
- null
- }
+ packageManagerWrapper.getPackageInfoAsUserCached(packageName, flags.toLong(), userId)
private fun Int.hasFlag(flag: Int) = (this and flag) > 0
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index c4f2df2..26174c2 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -17,13 +17,17 @@
package com.android.settingslib.spaprivileged.model.app
import android.content.Context
+import android.content.pm.ActivityInfo
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ApplicationInfoFlags
import android.content.pm.PackageManager.ResolveInfoFlags
+import android.content.pm.ResolveInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
@@ -51,50 +55,162 @@
private lateinit var repository: AppListRepository
- private val normalApp = ApplicationInfo().apply {
- packageName = "normal"
- enabled = true
- }
-
- private val instantApp = ApplicationInfo().apply {
- packageName = "instant"
- enabled = true
- privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT
- }
-
@Before
fun setUp() {
whenever(context.packageManager).thenReturn(packageManager)
whenever(packageManager.getInstalledModules(anyInt())).thenReturn(emptyList())
whenever(
- packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(USER_ID))
- ).thenReturn(listOf(normalApp, instantApp))
- whenever(
packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID))
).thenReturn(emptyList())
repository = AppListRepositoryImpl(context)
}
+ private fun mockInstalledApplications(apps: List<ApplicationInfo>) {
+ whenever(
+ packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(USER_ID))
+ ).thenReturn(apps)
+ }
+
@Test
- fun notShowInstantApps() = runTest {
+ fun loadApps_notShowInstantApps() = runTest {
+ mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP))
val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
val appListFlow = repository.loadApps(appListConfig)
- assertThat(appListFlow).containsExactly(normalApp)
+ assertThat(appListFlow).containsExactly(NORMAL_APP)
}
@Test
- fun showInstantApps() = runTest {
+ fun loadApps_showInstantApps() = runTest {
+ mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP))
val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = true)
val appListFlow = repository.loadApps(appListConfig)
- assertThat(appListFlow).containsExactly(normalApp, instantApp)
+ assertThat(appListFlow).containsExactly(NORMAL_APP, INSTANT_APP)
}
+ @Test
+ fun loadApps_isDisabledUntilUsed() = runTest {
+ val app = ApplicationInfo().apply {
+ packageName = "is.disabled.until.used"
+ enabled = false
+ enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
+ }
+ mockInstalledApplications(listOf(app))
+ val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
+
+ val appListFlow = repository.loadApps(appListConfig)
+
+ assertThat(appListFlow).containsExactly(app)
+ }
+
+ @Test
+ fun loadApps_disabled() = runTest {
+ val app = ApplicationInfo().apply {
+ packageName = "disabled"
+ enabled = false
+ }
+ mockInstalledApplications(listOf(app))
+ val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
+
+ val appListFlow = repository.loadApps(appListConfig)
+
+ assertThat(appListFlow).isEmpty()
+ }
+
+ @Test
+ fun showSystemPredicate_showSystem() = runTest {
+ val app = ApplicationInfo().apply {
+ flags = ApplicationInfo.FLAG_SYSTEM
+ }
+
+ val showSystemPredicate = getShowSystemPredicate(showSystem = true)
+
+ assertThat(showSystemPredicate(app)).isTrue()
+ }
+
+ @Test
+ fun showSystemPredicate_notShowSystemAndIsSystemApp() = runTest {
+ val app = ApplicationInfo().apply {
+ flags = ApplicationInfo.FLAG_SYSTEM
+ }
+
+ val showSystemPredicate = getShowSystemPredicate(showSystem = false)
+
+ assertThat(showSystemPredicate(app)).isFalse()
+ }
+
+ @Test
+ fun showSystemPredicate_isUpdatedSystemApp() = runTest {
+ val app = ApplicationInfo().apply {
+ flags = ApplicationInfo.FLAG_SYSTEM or ApplicationInfo.FLAG_UPDATED_SYSTEM_APP
+ }
+
+ val showSystemPredicate = getShowSystemPredicate(showSystem = false)
+
+ assertThat(showSystemPredicate(app)).isTrue()
+ }
+
+ @Test
+ fun showSystemPredicate_isHome() = runTest {
+ val app = ApplicationInfo().apply {
+ flags = ApplicationInfo.FLAG_SYSTEM
+ packageName = "home.app"
+ }
+ whenever(packageManager.getHomeActivities(any())).thenAnswer {
+ @Suppress("UNCHECKED_CAST")
+ val resolveInfos = it.arguments[0] as MutableList<ResolveInfo>
+ resolveInfos.add(resolveInfoOf(packageName = app.packageName))
+ null
+ }
+
+ val showSystemPredicate = getShowSystemPredicate(showSystem = false)
+
+ assertThat(showSystemPredicate(app)).isTrue()
+ }
+
+ @Test
+ fun showSystemPredicate_appInLauncher() = runTest {
+ val app = ApplicationInfo().apply {
+ flags = ApplicationInfo.FLAG_SYSTEM
+ packageName = "app.in.launcher"
+ }
+ whenever(
+ packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID))
+ ).thenReturn(listOf(resolveInfoOf(packageName = app.packageName)))
+
+ val showSystemPredicate = getShowSystemPredicate(showSystem = false)
+
+ assertThat(showSystemPredicate(app)).isTrue()
+ }
+
+ private suspend fun getShowSystemPredicate(showSystem: Boolean) =
+ repository.showSystemPredicate(
+ userIdFlow = flowOf(USER_ID),
+ showSystemFlow = flowOf(showSystem),
+ ).first()
+
private companion object {
const val USER_ID = 0
+
+ val NORMAL_APP = ApplicationInfo().apply {
+ packageName = "normal"
+ enabled = true
+ }
+
+ val INSTANT_APP = ApplicationInfo().apply {
+ packageName = "instant"
+ enabled = true
+ privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT
+ }
+
+ fun resolveInfoOf(packageName: String) = ResolveInfo().apply {
+ activityInfo = ActivityInfo().apply {
+ this.packageName = packageName
+ }
+ }
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfosTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfosTest.kt
new file mode 100644
index 0000000..a1b2df3
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfosTest.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.model.app
+
+import android.app.admin.DevicePolicyManager
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.os.UserManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
+import com.android.settingslib.spaprivileged.framework.common.userManager
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class ApplicationInfosTest {
+ @get:Rule
+ val mockito: MockitoRule = MockitoJUnit.rule()
+
+ @Spy
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Mock
+ private lateinit var userManager: UserManager
+
+ @Mock
+ private lateinit var devicePolicyManager: DevicePolicyManager
+
+ @Before
+ fun setUp() {
+ whenever(context.userManager).thenReturn(userManager)
+ whenever(context.devicePolicyManager).thenReturn(devicePolicyManager)
+ }
+
+ @Test
+ fun userId() {
+ val app = ApplicationInfo().apply {
+ uid = 123
+ }
+
+ val userId = app.userId
+
+ assertThat(userId).isEqualTo(0)
+ }
+
+ @Test
+ fun userHandle() {
+ val app = ApplicationInfo().apply {
+ uid = 123
+ }
+
+ val userHandle = app.userHandle
+
+ assertThat(userHandle.identifier).isEqualTo(0)
+ }
+
+ @Test
+ fun hasFlag() {
+ val app = ApplicationInfo().apply {
+ flags = ApplicationInfo.FLAG_INSTALLED
+ }
+
+ val hasFlag = app.hasFlag(ApplicationInfo.FLAG_INSTALLED)
+
+ assertThat(hasFlag).isTrue()
+ }
+
+ @Test
+ fun installed() {
+ val app = ApplicationInfo().apply {
+ flags = ApplicationInfo.FLAG_INSTALLED
+ }
+
+ val installed = app.installed
+
+ assertThat(installed).isTrue()
+ }
+
+ @Test
+ fun isDisabledUntilUsed() {
+ val app = ApplicationInfo().apply {
+ enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
+ }
+
+ val isDisabledUntilUsed = app.isDisabledUntilUsed
+
+ assertThat(isDisabledUntilUsed).isTrue()
+ }
+
+ @Test
+ fun isDisallowControl() {
+ val app = ApplicationInfo().apply {
+ uid = 123
+ }
+ whenever(
+ userManager.hasBaseUserRestriction(UserManager.DISALLOW_APPS_CONTROL, app.userHandle)
+ ).thenReturn(true)
+
+ val isDisallowControl = app.isDisallowControl(context)
+
+ assertThat(isDisallowControl).isTrue()
+ }
+
+ @Test
+ fun isActiveAdmin() {
+ val app = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ uid = 123
+ }
+ whenever(devicePolicyManager.packageHasActiveAdmins(PACKAGE_NAME, app.userId))
+ .thenReturn(true)
+
+ val isActiveAdmin = app.isActiveAdmin(context)
+
+ assertThat(isActiveAdmin).isTrue()
+ }
+
+ @Test
+ fun toRoute() {
+ val app = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ uid = 123
+ }
+
+ val route = app.toRoute()
+
+ assertThat(route).isEqualTo("package.name/0")
+ }
+
+ private companion object {
+ const val PACKAGE_NAME = "package.name"
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt
index 6c31f4b..7c92838 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt
@@ -31,6 +31,74 @@
private val packageManagersImpl = PackageManagersImpl(fakePackageManagerWrapper)
@Test
+ fun getPackageInfoAsUser_notFound() {
+ fakePackageManagerWrapper.fakePackageInfo = null
+
+ val packageInfo = packageManagersImpl.getPackageInfoAsUser(PACKAGE_NAME, 0)
+
+ assertThat(packageInfo).isNull()
+ }
+
+ @Test
+ fun getPackageInfoAsUser_found() {
+ fakePackageManagerWrapper.fakePackageInfo = PackageInfo()
+
+ val packageInfo = packageManagersImpl.getPackageInfoAsUser(PACKAGE_NAME, 0)
+
+ assertThat(packageInfo).isSameInstanceAs(fakePackageManagerWrapper.fakePackageInfo)
+ }
+
+ @Test
+ fun hasRequestPermission_packageInfoIsNull_returnFalse() {
+ fakePackageManagerWrapper.fakePackageInfo = null
+
+ val hasRequestPermission = with(packageManagersImpl) {
+ APP.hasRequestPermission(PERMISSION_A)
+ }
+
+ assertThat(hasRequestPermission).isFalse()
+ }
+
+ @Test
+ fun hasRequestPermission_requestedPermissionsIsNull_returnFalse() {
+ fakePackageManagerWrapper.fakePackageInfo = PackageInfo().apply {
+ requestedPermissions = null
+ }
+
+ val hasRequestPermission = with(packageManagersImpl) {
+ APP.hasRequestPermission(PERMISSION_A)
+ }
+
+ assertThat(hasRequestPermission).isFalse()
+ }
+
+ @Test
+ fun hasRequestPermission_notRequested_returnFalse() {
+ fakePackageManagerWrapper.fakePackageInfo = PackageInfo().apply {
+ requestedPermissions = emptyArray()
+ }
+
+ val hasRequestPermission = with(packageManagersImpl) {
+ APP.hasRequestPermission(PERMISSION_A)
+ }
+
+ assertThat(hasRequestPermission).isFalse()
+ }
+
+ @Test
+ fun hasRequestPermission_requested_returnTrue() {
+ fakePackageManagerWrapper.fakePackageInfo = PackageInfo().apply {
+ requestedPermissions = arrayOf(PERMISSION_A)
+ }
+
+ val hasRequestPermission = with(packageManagersImpl) {
+ APP.hasRequestPermission(PERMISSION_A)
+ }
+
+ assertThat(hasRequestPermission).isTrue()
+ }
+
+ @Test
fun hasGrantPermission_packageInfoIsNull_returnFalse() {
fakePackageManagerWrapper.fakePackageInfo = null
@@ -43,7 +111,9 @@
@Test
fun hasGrantPermission_requestedPermissionsIsNull_returnFalse() {
- fakePackageManagerWrapper.fakePackageInfo = PackageInfo()
+ fakePackageManagerWrapper.fakePackageInfo = PackageInfo().apply {
+ requestedPermissions = null
+ }
val hasGrantPermission = with(packageManagersImpl) {
APP.hasGrantPermission(PERMISSION_A)
@@ -94,18 +164,8 @@
assertThat(hasGrantPermission).isTrue()
}
- private inner class FakePackageManagerWrapper : PackageManagerWrapper {
- var fakePackageInfo: PackageInfo? = null
-
- override fun getPackageInfoAsUserCached(
- packageName: String,
- flags: Long,
- userId: Int,
- ): PackageInfo? = fakePackageInfo
- }
-
private companion object {
- const val PACKAGE_NAME = "packageName"
+ const val PACKAGE_NAME = "package.name"
const val PERMISSION_A = "permission.A"
const val PERMISSION_B = "permission.B"
const val UID = 123
@@ -115,3 +175,10 @@
}
}
}
+
+private class FakePackageManagerWrapper : PackageManagerWrapper {
+ var fakePackageInfo: PackageInfo? = null
+
+ override fun getPackageInfoAsUserCached(packageName: String, flags: Long, userId: Int) =
+ fakePackageInfo
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
index f1d9abe..cd9c048 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
@@ -21,7 +21,7 @@
import android.content.pm.ApplicationInfo
import androidx.compose.runtime.State
import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.lifecycle.liveData
+import androidx.lifecycle.MutableLiveData
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
@@ -254,7 +254,7 @@
private class FakeAppOpsController(private val fakeMode: Int) : IAppOpsController {
var setAllowedCalledWith: Boolean? = null
- override val mode = liveData { emit(fakeMode) }
+ override val mode = MutableLiveData(fakeMode)
override fun setAllowed(allowed: Boolean) {
setAllowedCalledWith = allowed
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index a78256a..8a30a3b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -23,6 +23,9 @@
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.drawable.Drawable;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbPort;
+import android.hardware.usb.UsbPortStatus;
import android.location.LocationManager;
import android.media.AudioManager;
import android.net.NetworkCapabilities;
@@ -40,6 +43,7 @@
import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
@@ -55,6 +59,7 @@
import com.android.settingslib.utils.BuildCompatUtils;
import java.text.NumberFormat;
+import java.util.List;
public class Utils {
@@ -308,8 +313,16 @@
@ColorInt
public static int getColorAttrDefaultColor(Context context, int attr) {
+ return getColorAttrDefaultColor(context, attr, 0);
+ }
+
+ /**
+ * Get color styled attribute {@code attr}, default to {@code defValue} if not found.
+ */
+ @ColorInt
+ public static int getColorAttrDefaultColor(Context context, int attr, @ColorInt int defValue) {
TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
- @ColorInt int colorAccent = ta.getColor(0, 0);
+ @ColorInt int colorAccent = ta.getColor(0, defValue);
ta.recycle();
return colorAccent;
}
@@ -630,4 +643,31 @@
(VcnTransportInfo) networkCapabilities.getTransportInfo();
return vcnTransportInfo.getWifiInfo();
}
+
+ /** Whether there is any incompatible chargers in the current UsbPort? */
+ public static boolean containsIncompatibleChargers(Context context, String tag) {
+ final List<UsbPort> usbPortList =
+ context.getSystemService(UsbManager.class).getPorts();
+ if (usbPortList == null || usbPortList.isEmpty()) {
+ return false;
+ }
+ for (UsbPort usbPort : usbPortList) {
+ Log.d(tag, "usbPort: " + usbPort);
+ final UsbPortStatus usbStatus = usbPort.getStatus();
+ if (usbStatus == null || !usbStatus.isConnected()) {
+ continue;
+ }
+ final int[] complianceWarnings = usbStatus.getComplianceWarnings();
+ if (complianceWarnings == null || complianceWarnings.length == 0) {
+ continue;
+ }
+ for (int complianceWarningType : complianceWarnings) {
+ if (complianceWarningType != 0) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
index 7f65837..cac3103 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
@@ -32,6 +32,7 @@
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.UserManager;
import android.text.TextUtils;
import android.util.Log;
@@ -306,4 +307,17 @@
}
}
}
+
+ /**
+ * Returns clone user profile id if present. Returns -1 if not present.
+ */
+ public static int getCloneUserId(Context context) {
+ UserManager userManager = context.getSystemService(UserManager.class);
+ for (UserHandle userHandle : userManager.getUserProfiles()) {
+ if (userManager.getUserInfo(userHandle.getIdentifier()).isCloneProfile()) {
+ return userHandle.getIdentifier();
+ }
+ }
+ return -1;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index ca5f57d..21e7d81 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -123,6 +123,7 @@
final int mAdminRetrieveFlags;
final int mRetrieveFlags;
PackageIntentReceiver mPackageIntentReceiver;
+ PackageIntentReceiver mClonePackageIntentReceiver;
boolean mResumed;
boolean mHaveDisabledApps;
@@ -265,6 +266,15 @@
mPackageIntentReceiver.registerReceiver();
}
+ // Listen to any package additions in clone user to refresh the app list.
+ if (mClonePackageIntentReceiver == null) {
+ int cloneUserId = AppUtils.getCloneUserId(mContext);
+ if (cloneUserId != -1) {
+ mClonePackageIntentReceiver = new PackageIntentReceiver();
+ mClonePackageIntentReceiver.registerReceiverForClone(cloneUserId);
+ }
+ }
+
final List<ApplicationInfo> prevApplications = mApplications;
mApplications = new ArrayList<>();
for (UserInfo user : mUm.getProfiles(UserHandle.myUserId())) {
@@ -456,6 +466,10 @@
mPackageIntentReceiver.unregisterReceiver();
mPackageIntentReceiver = null;
}
+ if (mClonePackageIntentReceiver != null) {
+ mClonePackageIntentReceiver.unregisterReceiver();
+ mClonePackageIntentReceiver = null;
+ }
}
public AppEntry getEntry(String packageName, int userId) {
@@ -1526,6 +1540,12 @@
removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL));
}
}
+
+ public void registerReceiverForClone(int cloneId) {
+ IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+ filter.addDataScheme("package");
+ mContext.registerReceiverAsUser(this, UserHandle.of(cloneId), filter, null, null);
+ }
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
index c941954..840c936 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
@@ -251,5 +251,22 @@
}
return config;
}
+
+ /**
+ * Returns true if this config and the other config are semantically equal.
+ *
+ * Does not override isEquals because existing clients may be relying on the currently
+ * defined equals behavior.
+ */
+ public boolean areEqual(Config other) {
+ return showAtLeast3G == other.showAtLeast3G
+ && show4gFor3g == other.show4gFor3g
+ && alwaysShowCdmaRssi == other.alwaysShowCdmaRssi
+ && show4gForLte == other.show4gForLte
+ && show4glteForLte == other.show4glteForLte
+ && hideLtePlus == other.hideLtePlus
+ && hspaDataDistinguishable == other.hspaDataDistinguishable
+ && alwaysShowDataRatIcon == other.alwaysShowDataRatIcon;
+ }
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
index 39b4b8e..35e3dd3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
@@ -231,7 +231,7 @@
public SignalStrength signalStrength;
public TelephonyDisplayInfo telephonyDisplayInfo =
new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
- TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false);
/**
* Empty constructor
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 291f6a3..68a1e19 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -29,6 +29,9 @@
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbPort;
+import android.hardware.usb.UsbPortStatus;
import android.location.LocationManager;
import android.media.AudioManager;
import android.os.BatteryManager;
@@ -53,7 +56,9 @@
import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowSettings;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
@RunWith(RobolectricTestRunner.class)
@@ -74,12 +79,19 @@
private ServiceState mServiceState;
@Mock
private NetworkRegistrationInfo mNetworkRegistrationInfo;
+ @Mock
+ private UsbPort mUsbPort;
+ @Mock
+ private UsbManager mUsbManager;
+ @Mock
+ private UsbPortStatus mUsbPortStatus;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
when(mContext.getSystemService(Context.LOCATION_SERVICE)).thenReturn(mLocationManager);
+ when(mContext.getSystemService(UsbManager.class)).thenReturn(mUsbManager);
ShadowSecure.reset();
mAudioManager = mContext.getSystemService(AudioManager.class);
}
@@ -411,4 +423,50 @@
assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
resources.getString(R.string.battery_info_status_charging));
}
+
+ @Test
+ public void containsIncompatibleChargers_nullPorts_returnFalse() {
+ when(mUsbManager.getPorts()).thenReturn(null);
+ assertThat(Utils.containsIncompatibleChargers(mContext, "tag")).isFalse();
+ }
+
+ @Test
+ public void containsIncompatibleChargers_emptyPorts_returnFalse() {
+ when(mUsbManager.getPorts()).thenReturn(new ArrayList<>());
+ assertThat(Utils.containsIncompatibleChargers(mContext, "tag")).isFalse();
+ }
+
+ @Test
+ public void containsIncompatibleChargers_nullPortStatus_returnFalse() {
+ final List<UsbPort> usbPorts = new ArrayList<>();
+ usbPorts.add(mUsbPort);
+ when(mUsbManager.getPorts()).thenReturn(usbPorts);
+ when(mUsbPort.getStatus()).thenReturn(null);
+
+ assertThat(Utils.containsIncompatibleChargers(mContext, "tag")).isFalse();
+ }
+
+ @Test
+ public void containsIncompatibleChargers_returnTrue() {
+ final List<UsbPort> usbPorts = new ArrayList<>();
+ usbPorts.add(mUsbPort);
+ when(mUsbManager.getPorts()).thenReturn(usbPorts);
+ when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
+ when(mUsbPortStatus.isConnected()).thenReturn(true);
+ when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[]{1});
+
+ assertThat(Utils.containsIncompatibleChargers(mContext, "tag")).isTrue();
+ }
+
+ @Test
+ public void containsIncompatibleChargers_emptyComplianceWarnings_returnFalse() {
+ final List<UsbPort> usbPorts = new ArrayList<>();
+ usbPorts.add(mUsbPort);
+ when(mUsbManager.getPorts()).thenReturn(usbPorts);
+ when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
+ when(mUsbPortStatus.isConnected()).thenReturn(true);
+ when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[1]);
+
+ assertThat(Utils.containsIncompatibleChargers(mContext, "tag")).isFalse();
+ }
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index c7db275..b8ac384 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -515,6 +515,9 @@
<!-- Permissions needed for CTS test - CtsLocaleManagerTestCases -->
<uses-permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES" />
+ <!-- Permissions needed for CTS test - CtsLocaleManagerTestCases -->
+ <uses-permission android:name="android.permission.SET_APP_SPECIFIC_LOCALECONFIG" />
+
<!-- Permissions used for manual testing of time detection behavior. -->
<uses-permission android:name="android.permission.SUGGEST_MANUAL_TIME" />
<uses-permission android:name="android.permission.SUGGEST_TELEPHONY_TIME" />
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 87354c7..e1000e0 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -222,6 +222,7 @@
"WindowManager-Shell",
"LowLightDreamLib",
"motion_tool_lib",
+ "androidx.core_core-animation-testing-nodeps",
],
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt
index 923b99f..e197752 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt
@@ -145,6 +145,9 @@
const val TABLE_NAME = "flags"
val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build()
+ /** Flag denoting whether the Wallpaper Picker should use the new, revamped UI. */
+ const val FLAG_NAME_REVAMPED_WALLPAPER_UI = "revamped_wallpaper_ui"
+
/**
* Flag denoting whether the customizable lock screen quick affordances feature is enabled.
*/
diff --git a/packages/SystemUI/res-keyguard/layout/fsi_chrome_view.xml b/packages/SystemUI/res-keyguard/layout/fsi_chrome_view.xml
new file mode 100644
index 0000000..4ff2967
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/fsi_chrome_view.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.systemui.statusbar.notification.fsi.FsiChromeView android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="50dp"
+ android:orientation="vertical"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:id="@+id/fsi_chrome"
+ android:layout_height="50dp"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/fsi_app_icon"
+ android:layout_width="50dp"
+ android:layout_height="match_parent"
+ android:contentDescription="@null" />
+
+ <TextView
+ android:id="@+id/fsi_app_name"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:padding="10dp"
+ android:textSize="22dp"
+ android:gravity="center"
+ android:textColor="#FFFFFF"
+ android:text="AppName" />
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+
+ <Button
+ android:id="@+id/fsi_fullscreen_button"
+ android:layout_width="100dp"
+ android:layout_height="match_parent"
+ android:text="fullscreen" />
+
+ <Button
+ android:id="@+id/fsi_dismiss_button"
+ android:layout_width="100dp"
+ android:layout_height="match_parent"
+ android:text="dismiss" />
+
+ </LinearLayout>
+
+</com.android.systemui.statusbar.notification.fsi.FsiChromeView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index 13c9a5e..95aefab 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -186,7 +186,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:barrierDirection="end"
app:constraint_referenced_ids="actionPrev,media_scrubbing_elapsed_time,media_progress_bar,actionNext,media_scrubbing_total_time,action0,action1,action2,action3,action4"
- app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintRight_toRightOf="@id/actionPlayPause"
/>
<!-- This barrier is used in expanded view to constrain the bottom row of actions -->
diff --git a/packages/SystemUI/res/layout/remote_input.xml b/packages/SystemUI/res/layout/remote_input.xml
index a5b2f80..f4b0a45 100644
--- a/packages/SystemUI/res/layout/remote_input.xml
+++ b/packages/SystemUI/res/layout/remote_input.xml
@@ -20,6 +20,7 @@
<com.android.systemui.statusbar.policy.RemoteInputView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/remote_input"
+ android:forceHasOverlappingRendering="false"
android:layout_height="match_parent"
android:layout_width="match_parent">
<LinearLayout
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index 65983b7..efd683f 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -166,6 +166,7 @@
android:paddingEnd="4dp"
android:src="@drawable/ic_work_app_badge"
app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/screenshot_message_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
@@ -177,7 +178,24 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/screenshot_message_icon"
- app:layout_constraintEnd_toEndOf="parent"/>
+ app:layout_constraintEnd_toStartOf="@id/message_dismiss_button"/>
+
+ <FrameLayout
+ android:id="@+id/message_dismiss_button"
+ android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
+ android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
+ app:layout_constraintStart_toEndOf="@id/screenshot_message_content"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ android:contentDescription="@string/screenshot_dismiss_work_profile">
+ <ImageView
+ android:id="@+id/screenshot_dismiss_image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="@dimen/overlay_dismiss_button_margin"
+ android:src="@drawable/overlay_cancel"/>
+ </FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.android.systemui.screenshot.DraggableConstraintLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 227c0dd..6a9149e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -218,6 +218,9 @@
<!-- Radius for notifications corners with adjacent notifications -->
<dimen name="notification_corner_radius_small">4dp</dimen>
+ <!-- Vertical padding of the FSI container -->
+ <dimen name="fsi_chrome_vertical_padding">80dp</dimen>
+
<!-- the padding of the shelf icon container -->
<dimen name="shelf_icon_container_padding">13dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4f64a4b..ce3084c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -227,6 +227,8 @@
<string name="screenshot_scroll_label">Capture more</string>
<!-- Content description indicating that tapping a button will dismiss the screenshots UI [CHAR LIMIT=NONE] -->
<string name="screenshot_dismiss_description">Dismiss screenshot</string>
+ <!-- Content description indicating that tapping a button will dismiss the work profile first run dialog [CHAR LIMIT=NONE] -->
+ <string name="screenshot_dismiss_work_profile">Dismiss work profile message</string>
<!-- Content description indicating that the view is a preview of the screenshot that was just taken [CHAR LIMIT=NONE] -->
<string name="screenshot_preview_description">Screenshot preview</string>
<!-- Content description for the top boundary of the screenshot being cropped, with the current position as a percentage. [CHAR LIMIT=NONE] -->
@@ -2510,6 +2512,8 @@
<string name="media_output_dialog_accessibility_seekbar">Volume</string>
<!-- Summary for media output volume of a device in percentage [CHAR LIMIT=NONE] -->
<string name="media_output_dialog_volume_percentage"><xliff:g id="percentage" example="10">%1$d</xliff:g>%%</string>
+ <!-- Title for Speakers and Displays group. [CHAR LIMIT=NONE] -->
+ <string name="media_output_group_title_speakers_and_displays">Speakers & Displays</string>
<!-- Media Output Broadcast Dialog -->
<!-- Title for Broadcast First Notify Dialog [CHAR LIMIT=60] -->
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index c0b0571..96707f4 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -43,6 +43,7 @@
import com.android.systemui.shortcut.ShortcutKeyDispatcher
import com.android.systemui.statusbar.notification.fsi.FsiChromeRepo
import com.android.systemui.statusbar.notification.InstantAppNotifier
+import com.android.systemui.statusbar.notification.fsi.FsiChromeViewModelFactory
import com.android.systemui.statusbar.phone.KeyguardLiftController
import com.android.systemui.stylus.StylusUsiPowerStartable
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
@@ -87,6 +88,12 @@
@ClassKey(FsiChromeRepo::class)
abstract fun bindFSIChromeRepo(sysui: FsiChromeRepo): CoreStartable
+ /** Inject into FsiChromeWindowViewModel. */
+ @Binds
+ @IntoMap
+ @ClassKey(FsiChromeViewModelFactory::class)
+ abstract fun bindFSIChromeWindowViewModel(sysui: FsiChromeViewModelFactory): CoreStartable
+
/** Inject into GarbageMonitor.Service. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 6800e54..25fa915 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -103,6 +103,11 @@
// TODO(b/257315550): Tracking Bug
val NO_HUN_FOR_OLD_WHEN = unreleasedFlag(118, "no_hun_for_old_when", teamfood = true)
+ // TODO(b/260335638): Tracking Bug
+ @JvmField
+ val NOTIFICATION_INLINE_REPLY_ANIMATION =
+ unreleasedFlag(174148361, "notification_inline_reply_animation", teamfood = true)
+
val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD =
unreleasedFlag(254647461, "filter_unseen_notifs_on_keyguard", teamfood = true)
@@ -183,6 +188,10 @@
// TODO(b/260619425): Tracking Bug
@JvmField val MODERN_ALTERNATE_BOUNCER = unreleasedFlag(219, "modern_alternate_bouncer")
+ // TODO(b/262780002): Tracking Bug
+ @JvmField
+ val REVAMPED_WALLPAPER_UI = unreleasedFlag(222, "revamped_wallpaper_ui", teamfood = false)
+
/** Flag to control the migration of face auth to modern architecture. */
// TODO(b/262838215): Tracking bug
@JvmField val FACE_AUTH_REFACTOR = unreleasedFlag(220, "face_auth_refactor")
@@ -280,7 +289,7 @@
// 900 - media
// TODO(b/254512697): Tracking Bug
- val MEDIA_TAP_TO_TRANSFER = releasedFlag(900, "media_tap_to_transfer")
+ val MEDIA_TAP_TO_TRANSFER = unreleasedFlag(900, "media_tap_to_transfer", teamfood = true)
// TODO(b/254512502): Tracking Bug
val MEDIA_SESSION_ACTIONS = unreleasedFlag(901, "media_session_actions")
@@ -413,7 +422,7 @@
// TODO(b/261979569): Tracking Bug
val QUICK_TAP_FLOW_FRAMEWORK =
- unreleasedFlag(1401, "quick_tap_flow_framework", teamfood = false)
+ unreleasedFlag(1401, "quick_tap_flow_framework", teamfood = false)
// 1500 - chooser
// TODO(b/254512507): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index c0d6cc9..4a85fa8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -321,6 +321,7 @@
// and unlock the device as well as hiding the surface.
if (surfaceBehindAlpha == 0f) {
Log.d(TAG, "surfaceBehindAlphaAnimator#onAnimationEnd")
+ surfaceBehindRemoteAnimationTargets = null
keyguardViewMediator.get().finishSurfaceBehindRemoteAnimation(
false /* cancelled */)
} else {
@@ -820,13 +821,13 @@
// Make sure we made the surface behind fully visible, just in case. It should already be
// fully visible. The exit animation is finished, and we should not hold the leash anymore,
// so forcing it to 1f.
- surfaceBehindAlphaAnimator.cancel()
- surfaceBehindEntryAnimator.cancel()
surfaceBehindAlpha = 1f
setSurfaceBehindAppearAmount(1f)
+ surfaceBehindAlphaAnimator.cancel()
+ surfaceBehindEntryAnimator.cancel()
try {
launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */)
- } catch (e: RemoteException) {
+ } catch (e: RemoteException) {
Log.e(TAG, "Remote exception in notifyFinishedKeyguardExitAnimation", e)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
index 0b04fb4..6063ce2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
@@ -17,6 +17,7 @@
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
+import com.android.systemui.util.traceSection
import javax.inject.Inject
import javax.inject.Singleton
@@ -39,14 +40,22 @@
}
override fun onScreenTurnedOn() {
- listeners.forEach(ScreenListener::onScreenTurnedOn)
+ traceSection("$TRACE_TAG#onScreenTurnedOn") {
+ listeners.forEach(ScreenListener::onScreenTurnedOn)
+ }
}
override fun onScreenTurningOff() {
- listeners.forEach(ScreenListener::onScreenTurningOff)
+ traceSection("$TRACE_TAG#onScreenTurningOff") {
+ listeners.forEach(ScreenListener::onScreenTurningOff)
+ }
}
override fun onScreenTurningOn() {
- listeners.forEach(ScreenListener::onScreenTurningOn)
+ traceSection("$TRACE_TAG#onScreenTurningOn") {
+ listeners.forEach(ScreenListener::onScreenTurningOn)
+ }
}
}
+
+private const val TRACE_TAG = "LifecycleScreenStatusProvider"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 8eace76..9772cb9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -40,13 +40,13 @@
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.KeyguardStateController
import dagger.Lazy
+import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
-import javax.inject.Inject
@SysUISingleton
class KeyguardQuickAffordanceInteractor
@@ -294,10 +294,7 @@
SystemUIDialog.setShowForAllUsers(dialog, true)
SystemUIDialog.registerDismissListener(dialog)
SystemUIDialog.setDialogSize(dialog)
- launchAnimator.show(
- dialog,
- controller
- )
+ launchAnimator.show(dialog, controller)
}
}
@@ -355,6 +352,10 @@
fun getPickerFlags(): List<KeyguardPickerFlag> {
return listOf(
KeyguardPickerFlag(
+ name = Contract.FlagsTable.FLAG_NAME_REVAMPED_WALLPAPER_UI,
+ value = featureFlags.isEnabled(Flags.REVAMPED_WALLPAPER_UI),
+ ),
+ KeyguardPickerFlag(
name = Contract.FlagsTable.FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED,
value = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES),
),
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index f2e8e42..7beb5b8 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -102,6 +102,15 @@
return factory.create("ShadeLog", 500, false);
}
+ /** Provides a logging buffer for Shade height messages. */
+ @Provides
+ @SysUISingleton
+ @ShadeHeightLog
+ public static LogBuffer provideShadeHeightLogBuffer(LogBufferFactory factory) {
+ return factory.create("ShadeHeightLog", 500 /* maxSize */);
+ }
+
+
/** Provides a logging buffer for all logs related to managing notification sections. */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeHeightLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeHeightLog.java
new file mode 100644
index 0000000..6fe8686
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeHeightLog.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.plugins.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for Shade height changes. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface ShadeHeightLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index fb47d97..51b5a3d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -70,6 +70,13 @@
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
if (mController.isAdvancedLayoutSupported()) {
+ if (position >= mController.getMediaItemList().size()) {
+ if (DEBUG) {
+ Log.d(TAG, "Incorrect position: " + position + " list size: "
+ + mController.getMediaItemList().size());
+ }
+ return;
+ }
MediaItem currentMediaItem = mController.getMediaItemList().get(position);
switch (currentMediaItem.getMediaItemType()) {
case MediaItem.MediaItemType.TYPE_GROUP_DIVIDER:
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 3b1d861..4e08050 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -84,8 +84,7 @@
int viewType) {
mContext = viewGroup.getContext();
mHolderView = LayoutInflater.from(mContext).inflate(
- mController.isAdvancedLayoutSupported() ? MediaItem.getMediaLayoutId(
- viewType) /*R.layout.media_output_list_item_advanced*/
+ mController.isAdvancedLayoutSupported() ? MediaItem.getMediaLayoutId(viewType)
: R.layout.media_output_list_item, viewGroup, false);
return null;
@@ -308,9 +307,10 @@
updateTitleIcon(currentVolume == 0 ? R.drawable.media_output_icon_volume_off
: R.drawable.media_output_icon_volume,
mController.getColorItemContent());
+ } else {
+ animateCornerAndVolume(mSeekBar.getProgress(),
+ MediaOutputSeekbar.scaleVolumeToProgress(currentVolume));
}
- animateCornerAndVolume(mSeekBar.getProgress(),
- MediaOutputSeekbar.scaleVolumeToProgress(currentVolume));
} else {
if (!mVolumeAnimator.isStarted()) {
if (mController.isAdvancedLayoutSupported()) {
@@ -396,23 +396,18 @@
}
void updateMutedVolumeIcon() {
+ mIconAreaLayout.setBackground(
+ mContext.getDrawable(R.drawable.media_output_item_background_active));
updateTitleIcon(R.drawable.media_output_icon_volume_off,
mController.getColorItemContent());
- final GradientDrawable iconAreaBackgroundDrawable =
- (GradientDrawable) mIconAreaLayout.getBackground();
- iconAreaBackgroundDrawable.setCornerRadius(mController.getActiveRadius());
}
void updateUnmutedVolumeIcon() {
+ mIconAreaLayout.setBackground(
+ mContext.getDrawable(R.drawable.media_output_title_icon_area)
+ );
updateTitleIcon(R.drawable.media_output_icon_volume,
mController.getColorItemContent());
- final GradientDrawable iconAreaBackgroundDrawable =
- (GradientDrawable) mIconAreaLayout.getBackground();
- iconAreaBackgroundDrawable.setCornerRadii(new float[]{
- mController.getActiveRadius(),
- mController.getActiveRadius(),
- 0, 0, 0, 0, mController.getActiveRadius(), mController.getActiveRadius()
- });
}
void updateTitleIcon(@DrawableRes int id, int color) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index b436562..8eb25c4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -87,9 +87,11 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
@@ -275,7 +277,9 @@
@Override
public void onDeviceListUpdate(List<MediaDevice> devices) {
- if (mMediaDevices.isEmpty() || !mIsRefreshing) {
+ boolean isListEmpty =
+ isAdvancedLayoutSupported() ? mMediaItemList.isEmpty() : mMediaDevices.isEmpty();
+ if (isListEmpty || !mIsRefreshing) {
buildMediaDevices(devices);
mCallback.onDeviceListChanged();
} else {
@@ -646,16 +650,19 @@
for (MediaDevice device : devices) {
if (device.isMutingExpectedDevice()) {
mMediaItemList.add(0, new MediaItem(device));
+ mMediaItemList.add(1, new MediaItem(mContext.getString(
+ R.string.media_output_group_title_speakers_and_displays),
+ MediaItem.MediaItemType.TYPE_GROUP_DIVIDER));
} else {
mMediaItemList.add(new MediaItem(device));
}
}
+ mMediaItemList.add(new MediaItem());
} else {
mMediaItemList.addAll(
devices.stream().map(MediaItem::new).collect(Collectors.toList()));
+ categorizeMediaItems(null);
}
-
- categorizeMediaItems();
return;
}
// selected device exist
@@ -666,11 +673,12 @@
mMediaItemList.add(new MediaItem(device));
}
}
- categorizeMediaItems();
+ categorizeMediaItems(connectedMediaDevice);
return;
}
// To keep the same list order
final List<MediaDevice> targetMediaDevices = new ArrayList<>();
+ final Map<Integer, MediaItem> dividerItems = new HashMap<>();
for (MediaItem originalMediaItem : mMediaItemList) {
for (MediaDevice newDevice : devices) {
if (originalMediaItem.getMediaDevice().isPresent()
@@ -680,6 +688,10 @@
break;
}
}
+ if (originalMediaItem.getMediaItemType()
+ == MediaItem.MediaItemType.TYPE_GROUP_DIVIDER) {
+ dividerItems.put(mMediaItemList.indexOf(originalMediaItem), originalMediaItem);
+ }
}
if (targetMediaDevices.size() != devices.size()) {
devices.removeAll(targetMediaDevices);
@@ -688,13 +700,34 @@
mMediaItemList.clear();
mMediaItemList.addAll(
targetMediaDevices.stream().map(MediaItem::new).collect(Collectors.toList()));
- categorizeMediaItems();
+ dividerItems.forEach((key, item) -> {
+ mMediaItemList.add(key, item);
+ });
+ mMediaItemList.add(new MediaItem());
}
}
- private void categorizeMediaItems() {
+ private void categorizeMediaItems(MediaDevice connectedMediaDevice) {
synchronized (mMediaDevicesLock) {
- //TODO(255124239): do the categorization here
+ Set<String> selectedDevicesIds = getSelectedMediaDevice().stream().map(
+ MediaDevice::getId).collect(Collectors.toSet());
+ if (connectedMediaDevice != null) {
+ selectedDevicesIds.add(connectedMediaDevice.getId());
+ }
+ int latestSelected = 1;
+ for (MediaItem item : mMediaItemList) {
+ if (item.getMediaDevice().isPresent()) {
+ MediaDevice device = item.getMediaDevice().get();
+ if (selectedDevicesIds.contains(device.getId())) {
+ latestSelected = mMediaItemList.indexOf(item) + 1;
+ } else {
+ mMediaItemList.add(latestSelected, new MediaItem(mContext.getString(
+ R.string.media_output_group_title_speakers_and_displays),
+ MediaItem.MediaItemType.TYPE_GROUP_DIVIDER));
+ break;
+ }
+ }
+ }
mMediaItemList.add(new MediaItem());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index a4ce6b3..534155c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -144,7 +144,7 @@
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final TelephonyDisplayInfo DEFAULT_TELEPHONY_DISPLAY_INFO =
new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
- TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false);
static final int MAX_WIFI_ENTRY_COUNT = 3;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 9c7718d..e8ceb52 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -289,6 +289,9 @@
mDismissButton.getBoundsOnScreen(tmpRect);
swipeRegion.op(tmpRect, Region.Op.UNION);
+ mMessageContainer.findViewById(R.id.message_dismiss_button).getBoundsOnScreen(tmpRect);
+ swipeRegion.op(tmpRect, Region.Op.UNION);
+
return swipeRegion;
}
@@ -353,6 +356,9 @@
mMessageContent.setText(
mContext.getString(R.string.screenshot_work_profile_notification, appName));
mMessageContainer.setVisibility(VISIBLE);
+ mMessageContainer.findViewById(R.id.message_dismiss_button).setOnClickListener((v) -> {
+ mMessageContainer.setVisibility(View.GONE);
+ });
}
@Override // View
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index eabd276..1709043 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -634,6 +634,7 @@
private final KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
private final KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
private float mMinExpandHeight;
+ private ShadeHeightLogger mShadeHeightLogger;
private boolean mPanelUpdateWhenAnimatorEnds;
private boolean mHasVibratedOnOpen = false;
private int mFixedDuration = NO_FIXED_DURATION;
@@ -713,6 +714,7 @@
KeyguardUpdateMonitor keyguardUpdateMonitor,
MetricsLogger metricsLogger,
ShadeLogger shadeLogger,
+ ShadeHeightLogger shadeHeightLogger,
ConfigurationController configurationController,
Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilder,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
@@ -774,6 +776,7 @@
mLockscreenGestureLogger = lockscreenGestureLogger;
mShadeExpansionStateManager = shadeExpansionStateManager;
mShadeLog = shadeLogger;
+ mShadeHeightLogger = shadeHeightLogger;
mGutsManager = gutsManager;
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
@@ -1819,6 +1822,7 @@
waiting = true;
} else {
resetViews(false /* animate */);
+ mShadeHeightLogger.logFunctionCall("collapsePanel");
setExpandedFraction(0); // just in case
}
if (!waiting) {
@@ -3695,6 +3699,7 @@
beginJankMonitoring();
fling(0 /* expand */);
} else {
+ mShadeHeightLogger.logFunctionCall("expand");
setExpandedFraction(1f);
}
mInstantExpanding = false;
@@ -4759,6 +4764,7 @@
mInitialTouchFromKeyguard = mKeyguardStateController.isShowing();
if (startTracking) {
mTouchSlopExceeded = true;
+ mShadeHeightLogger.logFunctionCall("startExpandMotion");
setExpandedHeight(mInitialOffsetOnTouch);
onTrackingStarted();
}
@@ -4904,6 +4910,7 @@
@VisibleForTesting
void setExpandedHeight(float height) {
debugLog("setExpandedHeight(%.1f)", height);
+ mShadeHeightLogger.logFunctionCall("setExpandedHeight");
setExpandedHeightInternal(height);
}
@@ -4927,10 +4934,13 @@
return;
}
+ mShadeHeightLogger.logFunctionCall("updateExpandedHeightToMaxHeight");
setExpandedHeight(currentMaxPanelHeight);
}
private void setExpandedHeightInternal(float h) {
+ mShadeHeightLogger.logSetExpandedHeightInternal(h, mSystemClock.currentTimeMillis());
+
if (isNaN(h)) {
Log.wtf(TAG, "ExpandedHeight set to NaN");
}
@@ -4987,7 +4997,9 @@
/** Sets the expanded height relative to a number from 0 to 1. */
public void setExpandedFraction(float frac) {
- setExpandedHeight(getMaxPanelTransitionDistance() * frac);
+ final int maxDist = getMaxPanelTransitionDistance();
+ mShadeHeightLogger.logFunctionCall("setExpandedFraction");
+ setExpandedHeight(maxDist * frac);
}
float getExpandedHeight() {
@@ -5049,6 +5061,7 @@
/** Collapses the shade instantly without animation. */
public void instantCollapse() {
abortAnimations();
+ mShadeHeightLogger.logFunctionCall("instantCollapse");
setExpandedFraction(0f);
if (mExpanding) {
notifyExpandingFinished();
@@ -5165,6 +5178,7 @@
animator.getAnimatedFraction()));
setOverExpansionInternal(expansion, false /* isFromGesture */);
}
+ mShadeHeightLogger.logFunctionCall("height animator update");
setExpandedHeightInternal((float) animation.getAnimatedValue());
});
return animator;
@@ -5639,6 +5653,7 @@
mStatusBarStateController.setUpcomingState(KEYGUARD);
mStatusBarStateListener.onStateChanged(KEYGUARD);
mStatusBarStateListener.onDozeAmountChanged(1f, 1f);
+ mShadeHeightLogger.logFunctionCall("showAodUi");
setExpandedFraction(1f);
}
@@ -6185,6 +6200,7 @@
// otherwise {@link NotificationStackScrollLayout}
// wrongly enables stack height updates at the start of lockscreen swipe-up
mAmbientState.setSwipingUp(h <= 0);
+ mShadeHeightLogger.logFunctionCall("ACTION_MOVE");
setExpandedHeightInternal(newHeight);
}
break;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeightLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeightLogger.kt
new file mode 100644
index 0000000..e610b98
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeightLogger.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT 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.shade
+
+import com.android.systemui.log.dagger.ShadeHeightLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import java.text.SimpleDateFormat
+import javax.inject.Inject
+
+private const val TAG = "ShadeHeightLogger"
+
+/**
+ * Log the call stack for [NotificationPanelViewController] setExpandedHeightInternal.
+ *
+ * Tracking bug: b/261593829
+ */
+class ShadeHeightLogger
+@Inject constructor(
+ @ShadeHeightLog private val buffer: LogBuffer,
+) {
+
+ private val dateFormat = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS")
+
+ fun logFunctionCall(functionName: String) {
+ buffer.log(TAG, DEBUG, {
+ str1 = functionName
+ }, {
+ "$str1"
+ })
+ }
+
+ fun logSetExpandedHeightInternal(h: Float, time: Long) {
+ buffer.log(TAG, DEBUG, {
+ double1 = h.toDouble()
+ long1 = time
+ }, {
+ "setExpandedHeightInternal=$double1 time=${dateFormat.format(long1)}"
+ })
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index f668528..3516037 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -463,7 +463,11 @@
riv.getController().setRemoteInput(input);
riv.getController().setRemoteInputs(inputs);
riv.getController().setEditedSuggestionInfo(editedSuggestionInfo);
- riv.focusAnimated();
+ ViewGroup parent = view.getParent() != null ? (ViewGroup) view.getParent() : null;
+ if (parent != null) {
+ riv.setDefocusTargetHeight(parent.getHeight());
+ }
+ riv.focusAnimated(parent);
if (userMessageContent != null) {
riv.setEditTextContent(userMessageContent);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
index 1fb6a98..c37b01f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
@@ -49,7 +49,7 @@
) : ConnectivityState() {
@JvmField var telephonyDisplayInfo = TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
- TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE)
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false)
@JvmField var serviceState: ServiceState? = null
@JvmField var signalStrength: SignalStrength? = null
@@ -131,7 +131,7 @@
}
fun isRoaming(): Boolean {
- return serviceState != null && serviceState!!.roaming
+ return telephonyDisplayInfo != null && telephonyDisplayInfo.isRoaming
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 47cdde4..56eb4b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.collection.inflation;
+import static com.android.systemui.flags.Flags.NOTIFICATION_INLINE_REPLY_ANIMATION;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
@@ -30,6 +31,7 @@
import com.android.internal.util.NotificationMessagingUtil;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -71,6 +73,7 @@
private NotificationListContainer mListContainer;
private BindRowCallback mBindRowCallback;
private NotificationClicker mNotificationClicker;
+ private FeatureFlags mFeatureFlags;
@Inject
public NotificationRowBinderImpl(
@@ -82,7 +85,8 @@
RowContentBindStage rowContentBindStage,
Provider<RowInflaterTask> rowInflaterTaskProvider,
ExpandableNotificationRowComponent.Builder expandableNotificationRowComponentBuilder,
- IconManager iconManager) {
+ IconManager iconManager,
+ FeatureFlags featureFlags) {
mContext = context;
mNotifBindPipeline = notifBindPipeline;
mRowContentBindStage = rowContentBindStage;
@@ -92,6 +96,7 @@
mRowInflaterTaskProvider = rowInflaterTaskProvider;
mExpandableNotificationRowComponentBuilder = expandableNotificationRowComponentBuilder;
mIconManager = iconManager;
+ mFeatureFlags = featureFlags;
}
/**
@@ -176,6 +181,8 @@
entry.setRow(row);
mNotifBindPipeline.manageRow(entry, row);
mBindRowCallback.onBindRow(row);
+ row.setInlineReplyAnimationFlagEnabled(
+ mFeatureFlags.isEnabled(NOTIFICATION_INLINE_REPLY_ANIMATION));
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeView.kt
new file mode 100644
index 0000000..6e5fcf4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeView.kt
@@ -0,0 +1,83 @@
+package com.android.systemui.statusbar.notification.fsi
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.Color.DKGRAY
+import android.graphics.Outline
+import android.util.AttributeSet
+import android.view.View
+import android.view.ViewOutlineProvider
+import android.widget.Button
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.fsi.FsiDebug.Companion.log
+
+@SysUISingleton
+class FsiChromeView
+@JvmOverloads
+constructor(
+ context: Context?,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0
+) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
+
+ companion object {
+ private const val classTag = "FsiChromeView"
+ }
+
+ lateinit var chromeContainer: LinearLayout
+ lateinit var appIconImageView: ImageView
+ lateinit var appNameTextView: TextView
+ lateinit var dismissButton: Button
+ lateinit var fullscreenButton: Button
+
+ private val cornerRadius: Float =
+ resources.getDimensionPixelSize(R.dimen.notification_corner_radius).toFloat()
+ private val vertPadding: Int =
+ resources.getDimensionPixelSize(R.dimen.fsi_chrome_vertical_padding)
+ private val sidePadding: Int =
+ resources.getDimensionPixelSize(R.dimen.notification_side_paddings)
+
+ init {
+ log("$classTag init")
+ }
+
+ override fun onFinishInflate() {
+ log("$classTag onFinishInflate")
+ super.onFinishInflate()
+
+ setBackgroundColor(Color.TRANSPARENT)
+ setPadding(
+ sidePadding,
+ vertPadding,
+ sidePadding,
+ vertPadding
+ ) // Make smaller than fullscreen.
+
+ chromeContainer = findViewById(R.id.fsi_chrome)
+ chromeContainer.setBackgroundColor(DKGRAY)
+
+ appIconImageView = findViewById(R.id.fsi_app_icon)
+ appNameTextView = findViewById(R.id.fsi_app_name)
+ dismissButton = findViewById(R.id.fsi_dismiss_button)
+ fullscreenButton = findViewById(R.id.fsi_fullscreen_button)
+
+ outlineProvider =
+ object : ViewOutlineProvider() {
+ override fun getOutline(view: View, outline: Outline) {
+ outline.setRoundRect(
+ /* left */ sidePadding,
+ /* top */ vertPadding,
+ /* right */ view.width - sidePadding,
+ /* bottom */ view.height - vertPadding,
+ cornerRadius
+ )
+ }
+ }
+ clipToOutline = true
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewModelFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewModelFactory.kt
new file mode 100644
index 0000000..1ca698b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewModelFactory.kt
@@ -0,0 +1,87 @@
+package com.android.systemui.statusbar.notification.fsi
+
+import android.annotation.UiContext
+import android.app.PendingIntent
+import android.content.Context
+import android.graphics.drawable.Drawable
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.notification.fsi.FsiDebug.Companion.log
+import com.android.wm.shell.TaskView
+import com.android.wm.shell.TaskViewFactory
+import java.util.Optional
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlin.coroutines.resume
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+/**
+ * Handle view-related data for fullscreen intent container on lockscreen. Wraps FsiChromeRepo,
+ * transforms events/state into view-relevant representation for FsiChromeView. Alive for lifetime
+ * of SystemUI.
+ */
+@SysUISingleton
+class FsiChromeViewModelFactory
+@Inject
+constructor(
+ val repo: FsiChromeRepo,
+ val taskViewFactory: Optional<TaskViewFactory>,
+ @UiContext val context: Context,
+ @Main val mainExecutor: Executor,
+) : CoreStartable {
+
+ companion object {
+ private const val classTag = "FsiChromeViewModelFactory"
+ }
+
+ val viewModelFlow: Flow<FsiChromeViewModel?> =
+ repo.infoFlow.mapLatest { fsiInfo ->
+ fsiInfo?.let {
+ log("$classTag viewModelFlow got new fsiInfo")
+
+ // mapLatest emits null when FSIInfo is null
+ FsiChromeViewModel(
+ fsiInfo.appName,
+ fsiInfo.appIcon,
+ createTaskView(),
+ fsiInfo.fullscreenIntent,
+ repo
+ )
+ }
+ }
+
+ override fun start() {
+ log("$classTag start")
+ }
+
+ private suspend fun createTaskView(): TaskView = suspendCancellableCoroutine { k ->
+ log("$classTag createTaskView")
+
+ taskViewFactory.get().create(context, mainExecutor) { taskView -> k.resume(taskView) }
+ }
+}
+
+// Alive for lifetime of FSI.
+data class FsiChromeViewModel(
+ val appName: String,
+ val appIcon: Drawable,
+ val taskView: TaskView,
+ val fsi: PendingIntent,
+ val repo: FsiChromeRepo
+) {
+ companion object {
+ private const val classTag = "FsiChromeViewModel"
+ }
+
+ fun onDismiss() {
+ log("$classTag onDismiss")
+ repo.dismiss()
+ }
+ fun onFullscreen() {
+ log("$classTag onFullscreen")
+ repo.onFullscreen()
+ }
+}
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 44a231d..c1173e4 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
@@ -284,6 +284,7 @@
private View.OnClickListener mOnAppClickListener;
private View.OnClickListener mOnFeedbackClickListener;
private Path mExpandingClipPath;
+ private boolean mIsInlineReplyAnimationFlagEnabled = false;
// Listener will be called when receiving a long click event.
// Use #setLongPressPosition to optionally assign positional data with the long press.
@@ -3079,6 +3080,10 @@
return 0;
}
+ public void setInlineReplyAnimationFlagEnabled(boolean isEnabled) {
+ mIsInlineReplyAnimationFlagEnabled = isEnabled;
+ }
+
@Override
public void setActualHeight(int height, boolean notifyListeners) {
boolean changed = height != getActualHeight();
@@ -3098,7 +3103,11 @@
}
int contentHeight = Math.max(getMinHeight(), height);
for (NotificationContentView l : mLayouts) {
- l.setContentHeight(contentHeight);
+ if (mIsInlineReplyAnimationFlagEnabled) {
+ l.setContentHeight(height);
+ } else {
+ l.setContentHeight(contentHeight);
+ }
}
if (mIsSummaryWithChildren) {
mChildrenContainer.setActualHeight(height);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 277ad8e..e46bf52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -70,7 +70,7 @@
/**
* A frame layout containing the actual payload of the notification, including the contracted,
- * expanded and heads up layout. This class is responsible for clipping the content and and
+ * expanded and heads up layout. This class is responsible for clipping the content and
* switching between the expanded, contracted and the heads up view depending on its clipped size.
*/
public class NotificationContentView extends FrameLayout implements NotificationFadeAware {
@@ -627,6 +627,13 @@
int hint;
if (mHeadsUpChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_HEADSUP)) {
hint = getViewHeight(VISIBLE_TYPE_HEADSUP);
+ if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isAnimatingAppearance()
+ && mHeadsUpRemoteInputController.isFocusAnimationFlagActive()) {
+ // While the RemoteInputView is animating its appearance, it should be allowed
+ // to overlap the hint, therefore no space is reserved for the hint during the
+ // appearance animation of the RemoteInputView
+ hint = 0;
+ }
} else if (mExpandedChild != null) {
hint = getViewHeight(VISIBLE_TYPE_EXPANDED);
} else if (mContractedChild != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index e70c81d..85590fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -11,6 +11,7 @@
import android.view.View;
import android.widget.FrameLayout;
+import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.collection.ArrayMap;
@@ -62,6 +63,8 @@
public static final String HIGH_PRIORITY = "high_priority";
private static final long AOD_ICONS_APPEAR_DURATION = 200;
+ @ColorInt
+ private static final int DEFAULT_AOD_ICON_COLOR = 0xffffffff;
private final ContrastColorUtil mContrastColorUtil;
private final Runnable mUpdateStatusBarIcons = this::updateStatusBarIcons;
@@ -84,7 +87,7 @@
private NotificationIconContainer mShelfIcons;
private NotificationIconContainer mAodIcons;
private final ArrayList<Rect> mTintAreas = new ArrayList<>();
- private Context mContext;
+ private final Context mContext;
private final DemoModeController mDemoModeController;
@@ -567,7 +570,7 @@
private void reloadAodColor() {
mAodIconTint = Utils.getColorAttrDefaultColor(mContext,
- R.attr.wallpaperTextColor);
+ R.attr.wallpaperTextColor, DEFAULT_AOD_ICON_COLOR);
}
private void updateAodIconColors() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
index aea85eb..498c0b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -17,8 +17,11 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
import android.provider.Settings
+import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.MobileMappings
+import com.android.settingslib.mobile.MobileMappings.Config
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import kotlinx.coroutines.flow.Flow
@@ -47,6 +50,18 @@
/** Observe changes to the [Settings.Global.MOBILE_DATA] setting */
val globalMobileDataSettingChangedEvent: Flow<Unit>
+ /**
+ * [Config] is an object that tracks relevant configuration flags for a given subscription ID.
+ * In the case of [MobileMappings], it's hard-coded to check the default data subscription's
+ * config, so this will apply to every icon that we care about.
+ *
+ * Relevant bits in the config are things like
+ * [CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL]
+ *
+ * This flow will produce whenever the default data subscription or the carrier config changes.
+ */
+ val defaultDataSubRatConfig: StateFlow<Config>
+
/** The icon mapping from network type to [MobileIconGroup] for the default subscription */
val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
index d8e0e81..db9d24f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -19,6 +19,7 @@
import android.os.Bundle
import androidx.annotation.VisibleForTesting
import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.MobileMappings
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.demomode.DemoMode
@@ -123,6 +124,15 @@
realRepository.activeMobileDataSubscriptionId.value
)
+ override val defaultDataSubRatConfig: StateFlow<MobileMappings.Config> =
+ activeRepo
+ .flatMapLatest { it.defaultDataSubRatConfig }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ realRepository.defaultDataSubRatConfig.value
+ )
+
override val defaultMobileIconMapping: Flow<Map<String, SignalIcon.MobileIconGroup>> =
activeRepo.flatMapLatest { it.defaultMobileIconMapping }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index 1e7fae7..1c08525 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -106,7 +106,8 @@
)
/** Demo mode doesn't currently support modifications to the mobile mappings */
- val defaultDataSubRatConfig = MutableStateFlow(MobileMappings.Config.readConfig(context))
+ override val defaultDataSubRatConfig =
+ MutableStateFlow(MobileMappings.Config.readConfig(context))
override val defaultMobileIconGroup = flowOf(TelephonyIcons.THREE_G)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index f27a9c9..483df47 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -37,7 +37,6 @@
import androidx.annotation.VisibleForTesting
import com.android.internal.telephony.PhoneConstants
import com.android.settingslib.SignalIcon.MobileIconGroup
-import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.MobileMappings.Config
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -152,17 +151,7 @@
IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
)
- /**
- * [Config] is an object that tracks relevant configuration flags for a given subscription ID.
- * In the case of [MobileMappings], it's hard-coded to check the default data subscription's
- * config, so this will apply to every icon that we care about.
- *
- * Relevant bits in the config are things like
- * [CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL]
- *
- * This flow will produce whenever the default data subscription or the carrier config changes.
- */
- private val defaultDataSubRatConfig: StateFlow<Config> =
+ override val defaultDataSubRatConfig: StateFlow<Config> =
merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent)
.mapLatest { Config.readConfig(context) }
.stateIn(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 8e1197c..a26f28a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -45,6 +45,9 @@
/** Observable for the data enabled state of this connection */
val isDataEnabled: StateFlow<Boolean>
+ /** True if the RAT icon should always be displayed and false otherwise. */
+ val alwaysShowDataRatIcon: StateFlow<Boolean>
+
/** Observable for RAT type (network type) indicator */
val networkTypeIconGroup: StateFlow<MobileIconGroup>
@@ -64,6 +67,7 @@
class MobileIconInteractorImpl(
@Application scope: CoroutineScope,
defaultSubscriptionHasDataEnabled: StateFlow<Boolean>,
+ override val alwaysShowDataRatIcon: StateFlow<Boolean>,
defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>,
defaultMobileIconGroup: StateFlow<MobileIconGroup>,
override val isDefaultConnectionFailed: StateFlow<Boolean>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index 6f8fb2e..21f6d8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -55,6 +55,8 @@
val filteredSubscriptions: Flow<List<SubscriptionModel>>
/** True if the active mobile data subscription has data enabled */
val activeDataConnectionHasDataEnabled: StateFlow<Boolean>
+ /** True if the RAT icon should always be displayed and false otherwise. */
+ val alwaysShowDataRatIcon: StateFlow<Boolean>
/** The icon mapping from network type to [MobileIconGroup] for the default subscription */
val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>
/** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
@@ -158,6 +160,11 @@
initialValue = mapOf()
)
+ override val alwaysShowDataRatIcon: StateFlow<Boolean> =
+ mobileConnectionsRepo.defaultDataSubRatConfig
+ .mapLatest { it.alwaysShowDataRatIcon }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
/** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */
override val defaultMobileIconGroup: StateFlow<MobileIconGroup> =
mobileConnectionsRepo.defaultMobileIconGroup.stateIn(
@@ -188,6 +195,7 @@
MobileIconInteractorImpl(
scope,
activeDataConnectionHasDataEnabled,
+ alwaysShowDataRatIcon,
defaultMobileIconMapping,
defaultMobileIconGroup,
isDefaultConnectionFailed,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 7869021..8ebd718 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -71,15 +71,19 @@
iconInteractor.isDataConnected,
iconInteractor.isDataEnabled,
iconInteractor.isDefaultConnectionFailed,
- ) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection ->
- if (!dataConnected || !dataEnabled || failedConnection) {
- null
- } else {
- val desc =
- if (networkTypeIconGroup.dataContentDescription != 0)
- ContentDescription.Resource(networkTypeIconGroup.dataContentDescription)
- else null
- Icon.Resource(networkTypeIconGroup.dataType, desc)
+ iconInteractor.alwaysShowDataRatIcon,
+ ) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection, alwaysShow ->
+ val desc =
+ if (networkTypeIconGroup.dataContentDescription != 0)
+ ContentDescription.Resource(networkTypeIconGroup.dataContentDescription)
+ else null
+ val icon = Icon.Resource(networkTypeIconGroup.dataType, desc)
+ return@combine when {
+ alwaysShow -> icon
+ !dataConnected -> null
+ !dataEnabled -> null
+ failedConnection -> null
+ else -> icon
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index dd400b3..d8a8c5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -18,8 +18,8 @@
import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
+import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_STANDARD;
+
import android.app.ActivityManager;
import android.app.Notification;
import android.content.Context;
@@ -57,6 +57,7 @@
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
+import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -67,6 +68,11 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.core.animation.Animator;
+import androidx.core.animation.AnimatorListenerAdapter;
+import androidx.core.animation.AnimatorSet;
+import androidx.core.animation.ObjectAnimator;
+import androidx.core.animation.ValueAnimator;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
@@ -74,6 +80,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.animation.InterpolatorsAndroidX;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
@@ -97,6 +104,12 @@
// A marker object that let's us easily find views of this class.
public static final Object VIEW_TAG = new Object();
+ private static final long FOCUS_ANIMATION_TOTAL_DURATION = ANIMATION_DURATION_STANDARD;
+ private static final long FOCUS_ANIMATION_CROSSFADE_DURATION = 50;
+ private static final long FOCUS_ANIMATION_FADE_IN_DELAY = 33;
+ private static final long FOCUS_ANIMATION_FADE_IN_DURATION = 83;
+ private static final float FOCUS_ANIMATION_MIN_SCALE = 0.5f;
+
public final Object mToken = new Object();
private final SendButtonTextWatcher mTextWatcher;
@@ -108,6 +121,7 @@
private RemoteEditText mEditText;
private ImageButton mSendButton;
+ private LinearLayout mContentView;
private GradientDrawable mContentBackground;
private ProgressBar mProgressBar;
private ImageView mDelete;
@@ -115,7 +129,10 @@
private boolean mColorized;
private int mTint;
private boolean mResetting;
- @Nullable private RevealParams mRevealParams;
+ @Nullable
+ private RevealParams mRevealParams;
+ private Rect mContentBackgroundBounds;
+ private boolean mIsFocusAnimationFlagActive;
// TODO(b/193539698): move these to a Controller
private RemoteInputController mController;
@@ -125,6 +142,10 @@
private boolean mSending;
private NotificationViewWrapper mWrapper;
+ private Integer mDefocusTargetHeight = null;
+ private boolean mIsAnimatingAppearance = false;
+
+
// TODO(b/193539698): remove this; views shouldn't have access to their controller, and places
// that need the controller shouldn't have access to the view
private RemoteInputViewController mViewController;
@@ -255,8 +276,8 @@
mDeleteBg.setImageTintBlendMode(BlendMode.SRC_IN);
mDelete.setImageTintBlendMode(BlendMode.SRC_IN);
mDelete.setOnClickListener(v -> setAttachment(null));
- LinearLayout contentView = findViewById(R.id.remote_input_content);
- contentView.setBackground(mContentBackground);
+ mContentView = findViewById(R.id.remote_input_content);
+ mContentView.setBackground(mContentBackground);
mEditText = findViewById(R.id.remote_input_text);
mEditText.setInnerFocusable(false);
// TextView initializes the spell checked when the view is attached to a window.
@@ -398,20 +419,74 @@
return true;
}
- private void onDefocus(boolean animate, boolean logClose) {
+ public boolean isAnimatingAppearance() {
+ return mIsAnimatingAppearance;
+ }
+
+ /**
+ * View will ensure to use at most the provided defocusTargetHeight, when defocusing animated.
+ * This is to ensure that the parent can resize itself to the targetHeight while the defocus
+ * animation of the RemoteInputView is running.
+ *
+ * @param defocusTargetHeight The target height the parent will resize itself to. If null, the
+ * RemoteInputView will not resize itself.
+ */
+ public void setDefocusTargetHeight(Integer defocusTargetHeight) {
+ mDefocusTargetHeight = defocusTargetHeight;
+ }
+
+ @VisibleForTesting
+ void onDefocus(boolean animate, boolean logClose) {
mController.removeRemoteInput(mEntry, mToken);
mEntry.remoteInputText = mEditText.getText();
// During removal, we get reattached and lose focus. Not hiding in that
// case to prevent flicker.
if (!mRemoved) {
- if (animate && mRevealParams != null && mRevealParams.radius > 0) {
- Animator reveal = mRevealParams.createCircularHideAnimator(this);
- reveal.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
- reveal.setDuration(StackStateAnimator.ANIMATION_DURATION_CLOSE_REMOTE_INPUT);
- reveal.addListener(new AnimatorListenerAdapter() {
+ if (animate && mIsFocusAnimationFlagActive) {
+ Animator animator = getDefocusAnimator();
+
+ // When defocusing, the notification needs to shrink. Therefore, we need to free
+ // up the space that is needed for the RemoteInputView. This is done by setting
+ // a negative top margin of the height difference of the RemoteInputView and its
+ // sibling (the actions_container_layout containing the Reply button)
+ if (mDefocusTargetHeight != null && mDefocusTargetHeight < getHeight()
+ && mDefocusTargetHeight >= 0
+ && getLayoutParams() instanceof FrameLayout.LayoutParams) {
+ int heightToShrink = getHeight() - mDefocusTargetHeight;
+ FrameLayout.LayoutParams layoutParams =
+ (FrameLayout.LayoutParams) getLayoutParams();
+ layoutParams.topMargin = -heightToShrink;
+ setLayoutParams(layoutParams);
+ ((ViewGroup) getParent().getParent()).setClipChildren(false);
+ }
+
+ animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
+ //reset top margin after the animation
+ if (getLayoutParams() instanceof FrameLayout.LayoutParams) {
+ FrameLayout.LayoutParams layoutParams =
+ (FrameLayout.LayoutParams) getLayoutParams();
+ layoutParams.topMargin = 0;
+ setLayoutParams(layoutParams);
+ ((ViewGroup) getParent().getParent()).setClipChildren(true);
+ }
+ setVisibility(GONE);
+ if (mWrapper != null) {
+ mWrapper.setRemoteInputVisible(false);
+ }
+ }
+ });
+ animator.start();
+
+ } else if (animate && mRevealParams != null && mRevealParams.radius > 0) {
+ android.animation.Animator reveal = mRevealParams.createCircularHideAnimator(this);
+ reveal.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+ reveal.setDuration(StackStateAnimator.ANIMATION_DURATION_CLOSE_REMOTE_INPUT);
+ reveal.addListener(new android.animation.AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(android.animation.Animator animation) {
setVisibility(GONE);
if (mWrapper != null) {
mWrapper.setRemoteInputVisible(false);
@@ -533,12 +608,37 @@
mEditText.setText(editTextContent);
}
- public void focusAnimated() {
- if (getVisibility() != VISIBLE && mRevealParams != null) {
- Animator animator = mRevealParams.createCircularRevealAnimator(this);
+ /**
+ * Sets whether the feature flag for the updated inline reply animation is active or not.
+ * @param active
+ */
+ public void setIsFocusAnimationFlagActive(boolean active) {
+ mIsFocusAnimationFlagActive = active;
+ }
+
+ /**
+ * Focuses the RemoteInputView and animates its appearance
+ *
+ * @param crossFadeView view that will be crossfaded during the appearance animation
+ */
+ public void focusAnimated(View crossFadeView) {
+ if (!mIsFocusAnimationFlagActive && getVisibility() != VISIBLE
+ && mRevealParams != null) {
+ android.animation.Animator animator = mRevealParams.createCircularRevealAnimator(this);
animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
animator.start();
+ } else if (mIsFocusAnimationFlagActive && getVisibility() != VISIBLE) {
+ mIsAnimatingAppearance = true;
+ setAlpha(0f);
+ Animator focusAnimator = getFocusAnimator(crossFadeView);
+ focusAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation, boolean isReverse) {
+ mIsAnimatingAppearance = false;
+ }
+ });
+ focusAnimator.start();
}
focus();
}
@@ -737,6 +837,81 @@
mOnSendListeners.remove(listener);
}
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ if (mIsFocusAnimationFlagActive) setPivotY(getMeasuredHeight());
+ if (mContentBackgroundBounds != null) {
+ mContentBackground.setBounds(mContentBackgroundBounds);
+ }
+ }
+
+ private Animator getFocusAnimator(View crossFadeView) {
+ final Animator alphaAnimator = ObjectAnimator.ofFloat(this, View.ALPHA, 0f, 1f);
+ alphaAnimator.setStartDelay(FOCUS_ANIMATION_FADE_IN_DELAY);
+ alphaAnimator.setDuration(FOCUS_ANIMATION_FADE_IN_DURATION);
+ alphaAnimator.setInterpolator(InterpolatorsAndroidX.LINEAR);
+
+ ValueAnimator scaleAnimator = ValueAnimator.ofFloat(FOCUS_ANIMATION_MIN_SCALE, 1f);
+ scaleAnimator.addUpdateListener(valueAnimator -> {
+ setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue());
+ });
+ scaleAnimator.setDuration(FOCUS_ANIMATION_TOTAL_DURATION);
+ scaleAnimator.setInterpolator(InterpolatorsAndroidX.FAST_OUT_SLOW_IN);
+
+ final Animator crossFadeViewAlphaAnimator =
+ ObjectAnimator.ofFloat(crossFadeView, View.ALPHA, 1f, 0f);
+ crossFadeViewAlphaAnimator.setDuration(FOCUS_ANIMATION_CROSSFADE_DURATION);
+ crossFadeViewAlphaAnimator.setInterpolator(InterpolatorsAndroidX.LINEAR);
+ alphaAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation, boolean isReverse) {
+ crossFadeView.setAlpha(1f);
+ }
+ });
+
+ final AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(alphaAnimator, scaleAnimator, crossFadeViewAlphaAnimator);
+ return animatorSet;
+ }
+
+ private Animator getDefocusAnimator() {
+ final Animator alphaAnimator = ObjectAnimator.ofFloat(this, View.ALPHA, 1f, 0f);
+ alphaAnimator.setDuration(FOCUS_ANIMATION_CROSSFADE_DURATION);
+ alphaAnimator.setInterpolator(InterpolatorsAndroidX.LINEAR);
+
+ ValueAnimator scaleAnimator = ValueAnimator.ofFloat(1f, FOCUS_ANIMATION_MIN_SCALE);
+ scaleAnimator.addUpdateListener(valueAnimator -> {
+ setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue());
+ });
+ scaleAnimator.setDuration(FOCUS_ANIMATION_TOTAL_DURATION);
+ scaleAnimator.setInterpolator(InterpolatorsAndroidX.FAST_OUT_SLOW_IN);
+ scaleAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation, boolean isReverse) {
+ setFocusAnimationScaleY(1f);
+ }
+ });
+
+ final AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(alphaAnimator, scaleAnimator);
+ return animatorSet;
+ }
+
+ /**
+ * Sets affected view properties for a vertical scale animation
+ *
+ * @param scaleY desired vertical view scale
+ */
+ private void setFocusAnimationScaleY(float scaleY) {
+ int verticalBoundOffset = (int) ((1f - scaleY) * 0.5f * mContentView.getHeight());
+ mContentBackgroundBounds = new Rect(0, verticalBoundOffset, mContentView.getWidth(),
+ mContentView.getHeight() - verticalBoundOffset);
+ mContentBackground.setBounds(mContentBackgroundBounds);
+ mContentView.setBackground(mContentBackground);
+ setTranslationY(verticalBoundOffset);
+ }
+
/** Handler for button click on send action in IME. */
private class EditorActionHandler implements TextView.OnEditorActionListener {
@@ -991,11 +1166,11 @@
this.radius = radius;
}
- Animator createCircularHideAnimator(View view) {
+ android.animation.Animator createCircularHideAnimator(View view) {
return ViewAnimationUtils.createCircularReveal(view, centerX, centerY, radius, 0);
}
- Animator createCircularRevealAnimator(View view) {
+ android.animation.Animator createCircularRevealAnimator(View view) {
return ViewAnimationUtils.createCircularReveal(view, centerX, centerY, 0, radius);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
index f845101..22b4c9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
@@ -30,6 +30,8 @@
import android.view.View
import com.android.internal.logging.UiEventLogger
import com.android.systemui.R
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.NOTIFICATION_INLINE_REPLY_ANIMATION
import com.android.systemui.statusbar.NotificationRemoteInputManager
import com.android.systemui.statusbar.RemoteInputController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -61,6 +63,8 @@
var revealParams: RevealParams?
+ val isFocusAnimationFlagActive: Boolean
+
/**
* Sets the smart reply that should be inserted in the remote input, or `null` if the user is
* not editing a smart reply.
@@ -117,7 +121,8 @@
private val remoteInputQuickSettingsDisabler: RemoteInputQuickSettingsDisabler,
private val remoteInputController: RemoteInputController,
private val shortcutManager: ShortcutManager,
- private val uiEventLogger: UiEventLogger
+ private val uiEventLogger: UiEventLogger,
+ private val mFlags: FeatureFlags
) : RemoteInputViewController {
private val onSendListeners = ArraySet<OnSendRemoteInputListener>()
@@ -149,6 +154,9 @@
override val isActive: Boolean get() = view.isActive
+ override val isFocusAnimationFlagActive: Boolean
+ get() = mFlags.isEnabled(NOTIFICATION_INLINE_REPLY_ANIMATION)
+
override fun bind() {
if (isBound) return
isBound = true
@@ -159,6 +167,7 @@
view.setSupportedMimeTypes(it.allowedDataTypes)
}
view.setRevealParameters(revealParams)
+ view.setIsFocusAnimationFlagActive(isFocusAnimationFlagActive)
view.addOnEditTextFocusChangedListener(onFocusChangeListener)
view.addOnSendRemoteInputListener(onSendRemoteInputListener)
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
index 79b42b8..9269df3 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
@@ -16,12 +16,18 @@
package com.android.systemui.unfold
+import android.content.ContentResolver
import android.content.Context
import android.hardware.devicestate.DeviceStateManager
+import android.util.Log
import com.android.internal.util.LatencyTracker
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.keyguard.ScreenLifecycle
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled
+import com.android.systemui.util.Compile
+import java.util.Optional
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -41,17 +47,19 @@
constructor(
private val latencyTracker: LatencyTracker,
private val deviceStateManager: DeviceStateManager,
+ private val transitionProgressProvider: Optional<UnfoldTransitionProgressProvider>,
@UiBackground private val uiBgExecutor: Executor,
private val context: Context,
+ private val contentResolver: ContentResolver,
private val screenLifecycle: ScreenLifecycle
-) : ScreenLifecycle.Observer {
+) : ScreenLifecycle.Observer, TransitionProgressListener {
private var folded: Boolean? = null
+ private var isTransitionEnabled: Boolean? = null
private val foldStateListener = FoldStateListener(context)
private val isFoldable: Boolean
get() =
- context
- .resources
+ context.resources
.getIntArray(com.android.internal.R.array.config_foldedDeviceStates)
.isNotEmpty()
@@ -62,6 +70,11 @@
}
deviceStateManager.registerCallback(uiBgExecutor, foldStateListener)
screenLifecycle.addObserver(this)
+ if (transitionProgressProvider.isPresent) {
+ // Might not be present if the device is not a foldable device or unfold transition
+ // is disabled in the device configuration
+ transitionProgressProvider.get().addCallback(this)
+ }
}
/**
@@ -71,16 +84,72 @@
* end action event only if we previously received a fold state.
*/
override fun onScreenTurnedOn() {
- if (folded == false) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "onScreenTurnedOn: folded = $folded, isTransitionEnabled = $isTransitionEnabled"
+ )
+ }
+
+ // We use onScreenTurnedOn event to finish tracking only if we are not playing
+ // the unfold animation (e.g. it could be disabled because of battery saver).
+ // When animation is enabled finishing of the tracking will be done in onTransitionStarted.
+ if (folded == false && isTransitionEnabled == false) {
latencyTracker.onActionEnd(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+
+ if (DEBUG) {
+ Log.d(TAG, "onScreenTurnedOn: ending ACTION_SWITCH_DISPLAY_UNFOLD")
+ }
+ }
+ }
+
+ /**
+ * This callback is used to end the metric when the unfold animation is enabled because it could
+ * add an additional delay to synchronize with launcher.
+ */
+ override fun onTransitionStarted() {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "onTransitionStarted: folded = $folded, isTransitionEnabled = $isTransitionEnabled"
+ )
+ }
+
+ if (folded == false && isTransitionEnabled == true) {
+ latencyTracker.onActionEnd(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+
+ if (DEBUG) {
+ Log.d(TAG, "onTransitionStarted: ending ACTION_SWITCH_DISPLAY_UNFOLD")
+ }
}
}
private fun onFoldEvent(folded: Boolean) {
- if (this.folded != folded) {
+ val oldFolded = this.folded
+
+ if (oldFolded != folded) {
this.folded = folded
- if (!folded) { // unfolding started
+
+ if (DEBUG) {
+ Log.d(TAG, "Received onFoldEvent = $folded")
+ }
+
+ // Do not start tracking when oldFolded is null, this means that this is the first
+ // onFoldEvent after booting the device or starting SystemUI and not actual folding or
+ // unfolding the device.
+ if (oldFolded != null && !folded) {
+ // Unfolding started
latencyTracker.onActionStart(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+ isTransitionEnabled =
+ transitionProgressProvider.isPresent && contentResolver.areAnimationsEnabled()
+
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "Starting ACTION_SWITCH_DISPLAY_UNFOLD, " +
+ "isTransitionEnabled = $isTransitionEnabled"
+ )
+ }
}
}
}
@@ -88,3 +157,6 @@
private inner class FoldStateListener(context: Context) :
DeviceStateManager.FoldStateListener(context, { onFoldEvent(it) })
}
+
+private const val TAG = "UnfoldLatencyTracker"
+private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
index cf9c91a..5e4a43f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
@@ -170,6 +170,7 @@
FakeFeatureFlags().apply {
set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
set(Flags.LOCKSCREEN_CUSTOM_CLOCKS, true)
+ set(Flags.REVAMPED_WALLPAPER_UI, true)
},
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 71c300c..b16a39f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -102,6 +102,8 @@
private MediaOutputController.Callback mCb = mock(MediaOutputController.Callback.class);
private MediaDevice mMediaDevice1 = mock(MediaDevice.class);
private MediaDevice mMediaDevice2 = mock(MediaDevice.class);
+ private MediaItem mMediaItem1 = mock(MediaItem.class);
+ private MediaItem mMediaItem2 = mock(MediaItem.class);
private NearbyDevice mNearbyDevice1 = mock(NearbyDevice.class);
private NearbyDevice mNearbyDevice2 = mock(NearbyDevice.class);
private MediaMetadata mMediaMetadata = mock(MediaMetadata.class);
@@ -125,6 +127,7 @@
private LocalMediaManager mLocalMediaManager;
private List<MediaController> mMediaControllers = new ArrayList<>();
private List<MediaDevice> mMediaDevices = new ArrayList<>();
+ private List<MediaItem> mMediaItemList = new ArrayList<>();
private List<NearbyDevice> mNearbyDevices = new ArrayList<>();
private MediaDescription mMediaDescription;
private List<RoutingSessionInfo> mRoutingSessionInfos = new ArrayList<>();
@@ -157,6 +160,11 @@
when(mMediaDevice2.getId()).thenReturn(TEST_DEVICE_2_ID);
mMediaDevices.add(mMediaDevice1);
mMediaDevices.add(mMediaDevice2);
+ when(mMediaItem1.getMediaDevice()).thenReturn(Optional.of(mMediaDevice1));
+ when(mMediaItem2.getMediaDevice()).thenReturn(Optional.of(mMediaDevice2));
+ mMediaItemList.add(mMediaItem1);
+ mMediaItemList.add(mMediaItem2);
+
when(mNearbyDevice1.getMediaRoute2Id()).thenReturn(TEST_DEVICE_1_ID);
when(mNearbyDevice1.getRangeZone()).thenReturn(NearbyDevice.RANGE_CLOSE);
@@ -314,6 +322,18 @@
}
@Test
+ public void advanced_onDeviceListUpdate_isRefreshing_updatesNeedRefreshToTrue() {
+ when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT)).thenReturn(true);
+ mMediaOutputController.start(mCb);
+ reset(mCb);
+ mMediaOutputController.mIsRefreshing = true;
+
+ mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+
+ assertThat(mMediaOutputController.mNeedRefresh).isTrue();
+ }
+
+ @Test
public void cancelMuteAwaitConnection_cancelsWithMediaManager() {
when(mAudioManager.getMutingExpectedDevice()).thenReturn(mock(AudioDeviceAttributes.class));
mMediaOutputController.start(mCb);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index e3a6b29..4843c76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -220,6 +220,7 @@
@Mock private KeyguardStateController mKeyguardStateController;
@Mock private DozeLog mDozeLog;
@Mock private ShadeLogger mShadeLog;
+ @Mock private ShadeHeightLogger mShadeHeightLogger;
@Mock private CommandQueue mCommandQueue;
@Mock private VibratorHelper mVibratorHelper;
@Mock private LatencyTracker mLatencyTracker;
@@ -457,6 +458,7 @@
mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
mMetricsLogger,
mShadeLog,
+ mShadeHeightLogger,
mConfigurationController,
() -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
mConversationNotificationManager, mMediaHierarchyManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewModelFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewModelFactoryTest.kt
new file mode 100644
index 0000000..5cee9e3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewModelFactoryTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.fsi
+
+import android.app.PendingIntent
+import android.graphics.drawable.Drawable
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.wm.shell.TaskView
+import com.android.wm.shell.TaskViewFactory
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import java.util.function.Consumer
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+class FsiChromeViewModelFactoryTest : SysuiTestCase() {
+ @Mock private lateinit var taskViewFactoryOptional: Optional<TaskViewFactory>
+ @Mock private lateinit var taskViewFactory: TaskViewFactory
+ @Mock lateinit var taskView: TaskView
+
+ @Main var mainExecutor = FakeExecutor(FakeSystemClock())
+ lateinit var viewModelFactory: FsiChromeViewModelFactory
+
+ private val fakeInfoFlow = MutableStateFlow<FsiChromeRepo.FSIInfo?>(null)
+ private var fsiChromeRepo: FsiChromeRepo =
+ mock<FsiChromeRepo>().apply { whenever(infoFlow).thenReturn(fakeInfoFlow) }
+
+ private val appName = "appName"
+ private val appIcon: Drawable = context.getDrawable(com.android.systemui.R.drawable.ic_android)
+ private val fsi: PendingIntent = Mockito.mock(PendingIntent::class.java)
+ private val fsiInfo = FsiChromeRepo.FSIInfo(appName, appIcon, fsi)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(taskViewFactoryOptional.get()).thenReturn(taskViewFactory)
+
+ viewModelFactory =
+ FsiChromeViewModelFactory(fsiChromeRepo, taskViewFactoryOptional, context, mainExecutor)
+ }
+
+ @Test
+ fun testViewModelFlow_update_createsTaskView() {
+ runTest {
+ val latestViewModel =
+ viewModelFactory.viewModelFlow
+ .onStart { FsiDebug.log("viewModelFactory.viewModelFlow.onStart") }
+ .stateIn(
+ backgroundScope, // stateIn runs forever, don't count it as test coroutine
+ SharingStarted.Eagerly,
+ null
+ )
+ runCurrent() // Drain queued backgroundScope operations
+
+ // Test: emit the fake FSIInfo
+ fakeInfoFlow.emit(fsiInfo)
+ runCurrent()
+
+ val taskViewFactoryCallback: Consumer<TaskView> = withArgCaptor {
+ verify(taskViewFactory).create(any(), any(), capture())
+ }
+ taskViewFactoryCallback.accept(taskView) // this will call k.resume
+ runCurrent()
+
+ // Verify that the factory has produced a new ViewModel
+ // containing the relevant data from FsiInfo
+ val expectedViewModel =
+ FsiChromeViewModel(appName, appIcon, taskView, fsi, fsiChromeRepo)
+
+ assertThat(latestViewModel.value).isEqualTo(expectedViewModel)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index d6af0e6..04d3cdd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -20,6 +20,7 @@
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyManager
import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -68,6 +69,8 @@
private val _globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
override val globalMobileDataSettingChangedEvent = _globalMobileDataSettingChangedEvent
+ override val defaultDataSubRatConfig = MutableStateFlow(MobileMappings.Config())
+
private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
override val defaultMobileIconMapping = _defaultMobileIconMapping
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 4b82b39..6d80acb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -23,6 +23,7 @@
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.provider.Settings
+import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback
@@ -30,6 +31,8 @@
import android.telephony.TelephonyManager
import androidx.test.filters.SmallTest
import com.android.internal.telephony.PhoneConstants
+import com.android.settingslib.R
+import com.android.settingslib.mobile.MobileMappings
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -50,6 +53,7 @@
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assert.assertThrows
+import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers.anyInt
@@ -63,6 +67,7 @@
class MobileConnectionsRepositoryTest : SysuiTestCase() {
private lateinit var underTest: MobileConnectionsRepositoryImpl
+ private lateinit var connectionFactory: MobileConnectionRepositoryImpl.Factory
@Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var subscriptionManager: SubscriptionManager
@Mock private lateinit var telephonyManager: TelephonyManager
@@ -84,7 +89,7 @@
}
}
- val connectionFactory: MobileConnectionRepositoryImpl.Factory =
+ connectionFactory =
MobileConnectionRepositoryImpl.Factory(
context = context,
telephonyManager = telephonyManager,
@@ -385,6 +390,92 @@
job.cancel()
}
+ @Test
+ fun config_initiallyFromContext() =
+ runBlocking(IMMEDIATE) {
+ overrideResource(R.bool.config_showMin3G, true)
+ val configFromContext = MobileMappings.Config.readConfig(context)
+ assertThat(configFromContext.showAtLeast3G).isTrue()
+
+ // The initial value will be fetched when the repo is created, so we need to override
+ // the resources and then re-create the repo.
+ underTest =
+ MobileConnectionsRepositoryImpl(
+ connectivityManager,
+ subscriptionManager,
+ telephonyManager,
+ logger,
+ mobileMappings,
+ fakeBroadcastDispatcher,
+ globalSettings,
+ context,
+ IMMEDIATE,
+ scope,
+ connectionFactory,
+ )
+
+ var latest: MobileMappings.Config? = null
+ val job = underTest.defaultDataSubRatConfig.onEach { latest = it }.launchIn(this)
+
+ assertTrue(latest!!.areEqual(configFromContext))
+ assertTrue(latest!!.showAtLeast3G)
+
+ job.cancel()
+ }
+
+ @Test
+ fun config_subIdChangeEvent_updated() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileMappings.Config? = null
+ val job = underTest.defaultDataSubRatConfig.onEach { latest = it }.launchIn(this)
+ assertThat(latest!!.showAtLeast3G).isFalse()
+
+ overrideResource(R.bool.config_showMin3G, true)
+ val configFromContext = MobileMappings.Config.readConfig(context)
+ assertThat(configFromContext.showAtLeast3G).isTrue()
+
+ // WHEN the change event is fired
+ fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+ receiver.onReceive(
+ context,
+ Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID)
+ )
+ }
+
+ // THEN the config is updated
+ assertTrue(latest!!.areEqual(configFromContext))
+ assertTrue(latest!!.showAtLeast3G)
+
+ job.cancel()
+ }
+
+ @Test
+ fun config_carrierConfigChangeEvent_updated() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileMappings.Config? = null
+ val job = underTest.defaultDataSubRatConfig.onEach { latest = it }.launchIn(this)
+ assertThat(latest!!.showAtLeast3G).isFalse()
+
+ overrideResource(R.bool.config_showMin3G, true)
+ val configFromContext = MobileMappings.Config.readConfig(context)
+ assertThat(configFromContext.showAtLeast3G).isTrue()
+
+ // WHEN the change event is fired
+ fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+ receiver.onReceive(
+ context,
+ Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
+ )
+ }
+
+ // THEN the config is updated
+ assertThat(latest!!.areEqual(configFromContext)).isTrue()
+ assertThat(latest!!.showAtLeast3G).isTrue()
+
+ job.cancel()
+ }
+
private fun createCapabilities(connected: Boolean, validated: Boolean): NetworkCapabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(connected)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index 3ae7d3c..1ff1636a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -22,6 +22,8 @@
import kotlinx.coroutines.flow.MutableStateFlow
class FakeMobileIconInteractor : MobileIconInteractor {
+ override val alwaysShowDataRatIcon = MutableStateFlow(false)
+
private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.THREE_G)
override val networkTypeIconGroup = _iconGroup
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index 0d4044d..9f300e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -54,6 +54,8 @@
private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled
+ override val alwaysShowDataRatIcon = MutableStateFlow(false)
+
private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
override val defaultMobileIconMapping = _defaultMobileIconMapping
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index fd41b5b..2281e89b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -59,6 +59,7 @@
MobileIconInteractorImpl(
scope,
mobileIconsInteractor.activeDataConnectionHasDataEnabled,
+ mobileIconsInteractor.alwaysShowDataRatIcon,
mobileIconsInteractor.defaultMobileIconMapping,
mobileIconsInteractor.defaultMobileIconGroup,
mobileIconsInteractor.isDefaultConnectionFailed,
@@ -223,6 +224,21 @@
}
@Test
+ fun alwaysShowDataRatIcon_matchesParent() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this)
+
+ mobileIconsInteractor.alwaysShowDataRatIcon.value = true
+ assertThat(latest).isTrue()
+
+ mobileIconsInteractor.alwaysShowDataRatIcon.value = false
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
fun test_isDefaultDataEnabled_matchesParent() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 58e57e2..8557894 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -18,6 +18,7 @@
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.test.filters.SmallTest
+import com.android.settingslib.mobile.MobileMappings
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -255,6 +256,38 @@
job.cancel()
}
+ @Test
+ fun alwaysShowDataRatIcon_configHasTrue() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this)
+
+ val config = MobileMappings.Config()
+ config.alwaysShowDataRatIcon = true
+ connectionsRepository.defaultDataSubRatConfig.value = config
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun alwaysShowDataRatIcon_configHasFalse() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this)
+
+ val config = MobileMappings.Config()
+ config.alwaysShowDataRatIcon = false
+ connectionsRepository.defaultDataSubRatConfig.value = config
+ yield()
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index d4c2c3f..f2533a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -174,6 +174,66 @@
job.cancel()
}
+ @Test
+ fun networkType_alwaysShow_shownEvenWhenDisabled() =
+ runBlocking(IMMEDIATE) {
+ interactor.setIconGroup(THREE_G)
+ interactor.setIsDataEnabled(true)
+ interactor.alwaysShowDataRatIcon.value = true
+
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ val expected =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription)
+ )
+ assertThat(latest).isEqualTo(expected)
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkType_alwaysShow_shownEvenWhenDisconnected() =
+ runBlocking(IMMEDIATE) {
+ interactor.setIconGroup(THREE_G)
+ interactor.isDataConnected.value = false
+ interactor.alwaysShowDataRatIcon.value = true
+
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ val expected =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription)
+ )
+ assertThat(latest).isEqualTo(expected)
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkType_alwaysShow_shownEvenWhenFailedConnection() =
+ runBlocking(IMMEDIATE) {
+ interactor.setIconGroup(THREE_G)
+ interactor.setIsFailedConnection(true)
+ interactor.alwaysShowDataRatIcon.value = true
+
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ val expected =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription)
+ )
+ assertThat(latest).isEqualTo(expected)
+
+ job.cancel()
+ }
+
/** Convenience constructor for these tests */
private fun defaultSignal(
level: Int = 1,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 915e999..2c47204 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -16,10 +16,14 @@
import static android.view.ContentInfo.SOURCE_CLIPBOARD;
+import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_STANDARD;
+
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -57,6 +61,7 @@
import android.window.WindowOnBackInvokedDispatcher;
import androidx.annotation.NonNull;
+import androidx.core.animation.AnimatorTestRule;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
@@ -64,15 +69,19 @@
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.LightBarController;
import org.junit.After;
import org.junit.Before;
+import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -99,6 +108,9 @@
private BlockingQueueIntentReceiver mReceiver;
private final UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
+ @ClassRule
+ public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+
@Before
public void setUp() throws Exception {
allowTestableLooperAsMainThread();
@@ -294,6 +306,9 @@
/* invoke the captured callback */
onBackInvokedCallbackCaptor.getValue().onBackInvoked();
+ /* wait for RemoteInputView disappear animation to finish */
+ mAnimatorTestRule.advanceTimeBy(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+
/* verify that the RemoteInputView goes away */
assertEquals(view.getVisibility(), View.GONE);
}
@@ -363,19 +378,73 @@
mUiEventLoggerFake.eventId(1));
}
+ @Test
+ public void testFocusAnimation() throws Exception {
+ NotificationTestHelper helper = new NotificationTestHelper(
+ mContext,
+ mDependency,
+ TestableLooper.get(this));
+ ExpandableNotificationRow row = helper.createRow();
+ RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
+ bindController(view, row.getEntry());
+ view.setVisibility(View.GONE);
+
+ View crossFadeView = new View(mContext);
+
+ // Start focus animation
+ view.focusAnimated(crossFadeView);
+
+ assertTrue(view.isAnimatingAppearance());
+
+ // fast forward to end of animation
+ mAnimatorTestRule.advanceTimeBy(ANIMATION_DURATION_STANDARD);
+
+ // assert that crossFadeView's alpha is reset to 1f after the animation (hidden behind
+ // RemoteInputView)
+ assertEquals(1f, crossFadeView.getAlpha());
+ assertFalse(view.isAnimatingAppearance());
+ assertEquals(View.VISIBLE, view.getVisibility());
+ assertEquals(1f, view.getAlpha());
+ }
+
+ @Test
+ public void testDefocusAnimation() throws Exception {
+ NotificationTestHelper helper = new NotificationTestHelper(
+ mContext,
+ mDependency,
+ TestableLooper.get(this));
+ ExpandableNotificationRow row = helper.createRow();
+ RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
+ bindController(view, row.getEntry());
+
+ // Start defocus animation
+ view.onDefocus(true, false);
+ assertEquals(View.VISIBLE, view.getVisibility());
+
+ // fast forward to end of animation
+ mAnimatorTestRule.advanceTimeBy(ANIMATION_DURATION_STANDARD);
+
+ // assert that RemoteInputView is no longer visible
+ assertEquals(View.GONE, view.getVisibility());
+ }
+
// NOTE: because we're refactoring the RemoteInputView and moving logic into the
- // RemoteInputViewController, it's easiest to just test the system of the two classes together.
+ // RemoteInputViewController, it's easiest to just test the system of the two classes together.
@NonNull
private RemoteInputViewController bindController(
RemoteInputView view,
NotificationEntry entry) {
+ FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags();
+ fakeFeatureFlags.set(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION, true);
RemoteInputViewControllerImpl viewController = new RemoteInputViewControllerImpl(
view,
entry,
mRemoteInputQuickSettingsDisabler,
mController,
mShortcutManager,
- mUiEventLoggerFake);
+ mUiEventLoggerFake,
+ fakeFeatureFlags
+ );
viewController.bind();
return viewController;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
index 4e2736c7..c7dc0ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
@@ -18,6 +18,7 @@
import android.hardware.devicestate.DeviceStateManager
import android.hardware.devicestate.DeviceStateManager.FoldStateListener
+import android.provider.Settings
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
@@ -32,9 +33,11 @@
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
+import java.util.Optional
@RunWith(AndroidTestingRunner::class)
@SmallTest
@@ -59,14 +62,18 @@
private lateinit var unfoldLatencyTracker: UnfoldLatencyTracker
+ private val transitionProgressProvider = TestUnfoldTransitionProvider()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
unfoldLatencyTracker = UnfoldLatencyTracker(
latencyTracker,
deviceStateManager,
+ Optional.of(transitionProgressProvider),
context.mainExecutor,
context,
+ context.contentResolver,
screenLifecycle
).apply { init() }
deviceStates = FoldableTestUtils.findDeviceStates(context)
@@ -76,8 +83,11 @@
}
@Test
- fun unfold_eventPropagated() {
+ fun unfold_startedFolded_animationsDisabled_eventPropagatedOnScreenTurnedOnEvent() {
+ setAnimationsEnabled(false)
+ sendFoldEvent(folded = true)
sendFoldEvent(folded = false)
+
sendScreenTurnedOnEvent()
verify(latencyTracker).onActionStart(any())
@@ -85,14 +95,77 @@
}
@Test
- fun fold_eventNotPropagated() {
+ fun unfold_startedFolded_animationsEnabledOnScreenTurnedOn_eventNotFinished() {
+ setAnimationsEnabled(true)
sendFoldEvent(folded = true)
+ sendFoldEvent(folded = false)
+
+ sendScreenTurnedOnEvent()
+
+ verify(latencyTracker).onActionStart(any())
+ verify(latencyTracker, never()).onActionEnd(any())
+ }
+
+ @Test
+ fun unfold_firstFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventNotPropagated() {
+ setAnimationsEnabled(true)
+ sendFoldEvent(folded = false)
+
+ sendScreenTurnedOnEvent()
+ transitionProgressProvider.onTransitionStarted()
+
+ verifyNoMoreInteractions(latencyTracker)
+ }
+
+ @Test
+ fun unfold_secondFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() {
+ setAnimationsEnabled(true)
+ sendFoldEvent(folded = true)
+ sendFoldEvent(folded = false)
+
+ sendScreenTurnedOnEvent()
+ transitionProgressProvider.onTransitionStarted()
+
+ verify(latencyTracker).onActionStart(any())
+ verify(latencyTracker).onActionEnd(any())
+ }
+
+ @Test
+ fun unfold_unfoldFoldUnfoldAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() {
+ setAnimationsEnabled(true)
+ sendFoldEvent(folded = false)
+ sendFoldEvent(folded = true)
+ sendFoldEvent(folded = false)
+
+ sendScreenTurnedOnEvent()
+ transitionProgressProvider.onTransitionStarted()
+
+ verify(latencyTracker).onActionStart(any())
+ verify(latencyTracker).onActionEnd(any())
+ }
+
+ @Test
+ fun fold_animationsDisabled_screenTurnedOn_eventNotPropagated() {
+ setAnimationsEnabled(false)
+ sendFoldEvent(folded = true)
+
sendScreenTurnedOnEvent() // outer display on.
verifyNoMoreInteractions(latencyTracker)
}
@Test
+ fun fold_animationsEnabled_screenTurnedOn_eventNotPropagated() {
+ setAnimationsEnabled(true)
+ sendFoldEvent(folded = true)
+
+ sendScreenTurnedOnEvent() // outer display on.
+ transitionProgressProvider.onTransitionStarted()
+
+ verifyNoMoreInteractions(latencyTracker)
+ }
+
+ @Test
fun onScreenTurnedOn_stateNeverSet_eventNotPropagated() {
sendScreenTurnedOnEvent()
@@ -107,4 +180,20 @@
private fun sendScreenTurnedOnEvent() {
screenLifecycleCaptor.value.onScreenTurnedOn()
}
+
+ private fun setAnimationsEnabled(enabled: Boolean) {
+ val durationScale =
+ if (enabled) {
+ 1f
+ } else {
+ 0f
+ }
+
+ // It uses [TestableSettingsProvider] and it will be cleared after the test
+ Settings.Global.putString(
+ context.contentResolver,
+ Settings.Global.ANIMATOR_DURATION_SCALE,
+ durationScale.toString()
+ )
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 2aed831..351e3bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -77,7 +77,6 @@
import android.view.ViewTreeObserver;
import android.view.WindowManager;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import com.android.internal.colorextraction.ColorExtractor;
@@ -142,6 +141,7 @@
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@@ -152,13 +152,13 @@
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
-@FlakyTest
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -411,6 +411,15 @@
.addCallback(mKeyguardStateControllerCallbackCaptor.capture());
}
+ @After
+ public void tearDown() {
+ ArrayList<Bubble> bubbles = new ArrayList<>(mBubbleData.getBubbles());
+ for (int i = 0; i < bubbles.size(); i++) {
+ mBubbleController.removeBubble(bubbles.get(i).getKey(),
+ Bubbles.DISMISS_NO_LONGER_BUBBLE);
+ }
+ }
+
@Test
public void dreamingHidesBubbles() throws RemoteException {
mBubbleController.updateBubble(mBubbleEntry);
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index ecc029d..074b1e1 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -16,6 +16,7 @@
package com.android.systemui.unfold.progress
import android.os.Trace
+import android.os.Trace.TRACE_TAG_APP
import android.util.Log
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.FloatPropertyCompat
@@ -157,7 +158,10 @@
}
private fun onStartTransition() {
+ Trace.beginSection( "$TAG#onStartTransition")
listeners.forEach { it.onTransitionStarted() }
+ Trace.endSection()
+
isTransitionRunning = true
if (DEBUG) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index e92150b..f0b5959 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -2504,4 +2504,20 @@
public void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc) {
mSystemSupport.attachAccessibilityOverlayToDisplay(displayId, sc);
}
-}
+
+ @Override
+ public void attachAccessibilityOverlayToWindow(int accessibilityWindowId, SurfaceControl sc)
+ throws RemoteException {
+ synchronized (mLock) {
+ RemoteAccessibilityConnection connection =
+ mA11yWindowManager.getConnectionLocked(
+ mSystemSupport.getCurrentUserIdLocked(),
+ resolveAccessibilityWindowIdLocked(accessibilityWindowId));
+ if (connection == null) {
+ Slog.e(LOG_TAG, "unable to get remote accessibility connection.");
+ return;
+ }
+ connection.getRemote().attachAccessibilityOverlayToWindow(sc);
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index b6cd160..195fee1 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -253,6 +253,16 @@
return mParams.getDevicePolicy(policyType);
}
+ /** Returns device-specific audio session id for playback. */
+ public int getAudioPlaybackSessionId() {
+ return mParams.getAudioPlaybackSessionId();
+ }
+
+ /** Returns device-specific audio session id for recording. */
+ public int getAudioRecordingSessionId() {
+ return mParams.getAudioRecordingSessionId();
+ }
+
/** Returns the unique device ID of this device. */
@Override // Binder call
public int getDeviceId() {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index da2c516..6373971 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -16,6 +16,8 @@
package com.android.server.companion.virtual;
+import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
+
import static com.android.server.wm.ActivityInterceptorCallback.VIRTUAL_DEVICE_SERVICE_ORDERED_ID;
import android.annotation.NonNull;
@@ -388,6 +390,24 @@
return VirtualDeviceManager.DEVICE_ID_DEFAULT;
}
+ @Override // Binder call
+ public int getAudioPlaybackSessionId(int deviceId) {
+ synchronized (mVirtualDeviceManagerLock) {
+ VirtualDeviceImpl virtualDevice = mVirtualDevices.get(deviceId);
+ return virtualDevice != null
+ ? virtualDevice.getAudioPlaybackSessionId() : AUDIO_SESSION_ID_GENERATE;
+ }
+ }
+
+ @Override // Binder call
+ public int getAudioRecordingSessionId(int deviceId) {
+ synchronized (mVirtualDeviceManagerLock) {
+ VirtualDeviceImpl virtualDevice = mVirtualDevices.get(deviceId);
+ return virtualDevice != null
+ ? virtualDevice.getAudioRecordingSessionId() : AUDIO_SESSION_ID_GENERATE;
+ }
+ }
+
@Nullable
private AssociationInfo getAssociationInfo(String packageName, int associationId) {
final int callingUserId = getCallingUserHandle().getIdentifier();
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 1e1d610..5c00452 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -161,6 +161,7 @@
"android.hardware.tv.cec-V1-java",
"android.hardware.tv.hdmi-V1-java",
"android.hardware.weaver-V1.0-java",
+ "android.hardware.weaver-V2-java",
"android.hardware.biometrics.face-V1.0-java",
"android.hardware.biometrics.fingerprint-V2.3-java",
"android.hardware.oemlock-V1.0-java",
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index ee922f9..9922818 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -84,11 +84,13 @@
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsCallSession;
import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.MediaQualityStatus;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.LocalLog;
import android.util.Pair;
+import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
@@ -357,6 +359,8 @@
private CallQuality[] mCallQuality;
+ private List<SparseArray<MediaQualityStatus>> mMediaQualityStatus;
+
private ArrayList<List<CallState>> mCallStateLists;
// network type of the call associated with the mCallStateLists and mCallQuality
@@ -483,6 +487,8 @@
TelephonyCallback.EVENT_DATA_ENABLED_CHANGED);
REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(
TelephonyCallback.EVENT_LINK_CAPACITY_ESTIMATE_CHANGED);
+ REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(
+ TelephonyCallback.EVENT_MEDIA_QUALITY_STATUS_CHANGED);
}
private boolean isLocationPermissionRequired(Set<Integer> events) {
@@ -715,6 +721,7 @@
cutListToSize(mCarrierPrivilegeStates, mNumPhones);
cutListToSize(mCarrierServiceStates, mNumPhones);
cutListToSize(mCallStateLists, mNumPhones);
+ cutListToSize(mMediaQualityStatus, mNumPhones);
return;
}
@@ -738,6 +745,7 @@
mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
mCallQuality[i] = createCallQuality();
+ mMediaQualityStatus.add(i, new SparseArray<>());
mCallStateLists.add(i, new ArrayList<>());
mCallNetworkType[i] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
mPreciseCallState[i] = createPreciseCallState();
@@ -805,6 +813,7 @@
mCallDisconnectCause = new int[numPhones];
mCallPreciseDisconnectCause = new int[numPhones];
mCallQuality = new CallQuality[numPhones];
+ mMediaQualityStatus = new ArrayList<>();
mCallNetworkType = new int[numPhones];
mCallStateLists = new ArrayList<>();
mPreciseDataConnectionStates = new ArrayList<>();
@@ -844,6 +853,7 @@
mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
mCallQuality[i] = createCallQuality();
+ mMediaQualityStatus.add(i, new SparseArray<>());
mCallStateLists.add(i, new ArrayList<>());
mCallNetworkType[i] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
mPreciseCallState[i] = createPreciseCallState();
@@ -1392,6 +1402,32 @@
remove(r.binder);
}
}
+ if (events.contains(TelephonyCallback.EVENT_MEDIA_QUALITY_STATUS_CHANGED)) {
+ CallState callState = null;
+ for (CallState cs : mCallStateLists.get(r.phoneId)) {
+ if (cs.getCallState() == PreciseCallState.PRECISE_CALL_STATE_ACTIVE) {
+ callState = cs;
+ break;
+ }
+ }
+ if (callState != null) {
+ String callId = callState.getImsCallSessionId();
+ try {
+ MediaQualityStatus status = mMediaQualityStatus.get(r.phoneId).get(
+ MediaQualityStatus.MEDIA_SESSION_TYPE_AUDIO);
+ if (status != null && status.getCallSessionId().equals(callId)) {
+ r.callback.onMediaQualityStatusChanged(status);
+ }
+ status = mMediaQualityStatus.get(r.phoneId).get(
+ MediaQualityStatus.MEDIA_SESSION_TYPE_VIDEO);
+ if (status != null && status.getCallSessionId().equals(callId)) {
+ r.callback.onMediaQualityStatusChanged(status);
+ }
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
+ }
}
}
}
@@ -1956,7 +1992,8 @@
&& overrideNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED) {
overrideNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE;
}
- return new TelephonyDisplayInfo(networkType, overrideNetworkType);
+ boolean isRoaming = telephonyDisplayInfo.isRoaming();
+ return new TelephonyDisplayInfo(networkType, overrideNetworkType, isRoaming);
}
public void notifyCallForwardingChanged(boolean cfi) {
@@ -2283,6 +2320,17 @@
}
mCallStateLists.get(phoneId).add(builder.build());
}
+ boolean hasOngoingCall = false;
+ for (CallState cs : mCallStateLists.get(phoneId)) {
+ if (cs.getCallState() != PreciseCallState.PRECISE_CALL_STATE_DISCONNECTED) {
+ hasOngoingCall = true;
+ break;
+ }
+ }
+ if (!hasOngoingCall) {
+ //no ongoing call. clear media quality status cached.
+ mMediaQualityStatus.get(phoneId).clear();
+ }
}
for (Record r : mRecords) {
@@ -2626,7 +2674,6 @@
}
}
}
-
handleRemoveListLocked();
}
}
@@ -3127,6 +3174,55 @@
}
}
+ @Override
+ public void notifyMediaQualityStatusChanged(int phoneId, int subId, MediaQualityStatus status) {
+ if (!checkNotifyPermission("notifyMediaQualityStatusChanged()")) {
+ return;
+ }
+
+ synchronized (mRecords) {
+ if (validatePhoneId(phoneId)) {
+ if (mCallStateLists.get(phoneId).size() > 0) {
+ CallState callState = null;
+ for (CallState cs : mCallStateLists.get(phoneId)) {
+ if (cs.getCallState() == PreciseCallState.PRECISE_CALL_STATE_ACTIVE) {
+ callState = cs;
+ break;
+ }
+ }
+ if (callState != null) {
+ String callSessionId = callState.getImsCallSessionId();
+ if (callSessionId != null
+ && callSessionId.equals(status.getCallSessionId())) {
+ mMediaQualityStatus.get(phoneId)
+ .put(status.getMediaSessionType(), status);
+ } else {
+ log("SessionId mismatch active call:" + callSessionId
+ + " media quality:" + status.getCallSessionId());
+ return;
+ }
+ } else {
+ log("There is no active call to report CallQaulity");
+ return;
+ }
+ }
+
+ for (Record r : mRecords) {
+ if (r.matchTelephonyCallbackEvent(
+ TelephonyCallback.EVENT_MEDIA_QUALITY_STATUS_CHANGED)
+ && idMatch(r, subId, phoneId)) {
+ try {
+ r.callback.onMediaQualityStatusChanged(status);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
@NeverCompile // Avoid size overhead of debugging code.
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ccaf353..8a6b600 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -87,7 +87,6 @@
import static android.os.Process.isSdkSandboxUid;
import static android.os.Process.isThreadInProcess;
import static android.os.Process.killProcess;
-import static android.os.Process.killProcessGroup;
import static android.os.Process.killProcessQuiet;
import static android.os.Process.myPid;
import static android.os.Process.myUid;
@@ -961,6 +960,13 @@
}
return false;
}
+
+ boolean doRemoveIfNoThreadInternal(int pid, ProcessRecord app) {
+ if (app == null || app.getThread() != null) {
+ return false;
+ }
+ return doRemoveInternal(pid, app);
+ }
}
private final PendingStartActivityUids mPendingStartActivityUids;
@@ -992,7 +998,7 @@
* method.
*/
@GuardedBy("this")
- boolean removePidLocked(int pid, ProcessRecord app) {
+ void removePidLocked(int pid, ProcessRecord app) {
final boolean removed;
synchronized (mPidsSelfLocked) {
removed = mPidsSelfLocked.doRemoveInternal(pid, app);
@@ -1003,6 +1009,26 @@
}
mAtmInternal.onProcessUnMapped(pid);
}
+ }
+
+ /**
+ * Removes the process record from the map if it doesn't have a thread.
+ * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this
+ * method.
+ */
+ @GuardedBy("this")
+ private boolean removePidIfNoThreadLocked(ProcessRecord app) {
+ final boolean removed;
+ final int pid = app.getPid();
+ synchronized (mPidsSelfLocked) {
+ removed = mPidsSelfLocked.doRemoveIfNoThreadInternal(pid, app);
+ }
+ if (removed) {
+ synchronized (sActiveProcessInfoSelfLocked) {
+ sActiveProcessInfoSelfLocked.remove(pid);
+ }
+ mAtmInternal.onProcessUnMapped(pid);
+ }
return removed;
}
@@ -2350,7 +2376,7 @@
mAppErrors = null;
mPackageWatchdog = null;
mAppOpsService = mInjector.getAppOpsService(null /* file */, null /* handler */);
- mBatteryStatsService = mInjector.getBatteryStatsService();
+ mBatteryStatsService = null;
mHandler = new MainHandler(handlerThread.getLooper());
mHandlerThread = handlerThread;
mConstants = new ActivityManagerConstants(mContext, this, mHandler);
@@ -2365,7 +2391,7 @@
mIntentFirewall = null;
mProcessStats = new ProcessStatsService(this, mContext.getCacheDir());
mCpHelper = new ContentProviderHelper(this, false);
- mServices = mInjector.getActiveServices(this);
+ mServices = null;
mSystemThread = null;
mUiHandler = injector.getUiHandler(null /* service */);
mUidObserverController = new UidObserverController(mUiHandler);
@@ -4757,7 +4783,7 @@
@GuardedBy("this")
void handleProcessStartOrKillTimeoutLocked(ProcessRecord app, boolean isKillTimeout) {
final int pid = app.getPid();
- boolean gone = isKillTimeout || removePidLocked(pid, app);
+ boolean gone = isKillTimeout || removePidIfNoThreadLocked(app);
if (gone) {
if (isKillTimeout) {
@@ -4838,7 +4864,7 @@
}
@GuardedBy("this")
- private void attachApplicationLocked(@NonNull IApplicationThread thread,
+ private boolean attachApplicationLocked(@NonNull IApplicationThread thread,
int pid, int callingUid, long startSeq) {
// Find the application record that is being attached... either via
@@ -4903,7 +4929,7 @@
// Ignore exceptions.
}
}
- return;
+ return false;
}
// If this application record is still attached to a previous
@@ -4928,7 +4954,7 @@
mProcessList.startProcessLocked(app,
new HostingRecord(HostingRecord.HOSTING_TYPE_LINK_FAIL, processName),
ZYGOTE_POLICY_FLAG_EMPTY);
- return;
+ return false;
}
EventLogTags.writeAmProcBound(app.userId, pid, app.processName);
@@ -4951,6 +4977,8 @@
app.setUnlocked(StorageManager.isUserKeyUnlocked(app.userId));
}
+ mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
+
boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
List<ProviderInfo> providers = normalMode
? mCpHelper.generateApplicationProvidersLocked(app)
@@ -5116,7 +5144,7 @@
app.killLocked("error during bind", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
true);
handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */);
- return;
+ return false;
}
// Remove this record from the list of starting applications.
@@ -5124,6 +5152,91 @@
if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG_PROCESSES,
"Attach application locked removing on hold: " + app);
mProcessesOnHold.remove(app);
+
+ boolean badApp = false;
+ boolean didSomething = false;
+
+ // See if the top visible activity is waiting to run in this process...
+ if (normalMode) {
+ try {
+ didSomething = mAtmInternal.attachApplication(app.getWindowProcessController());
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
+ badApp = true;
+ }
+ }
+
+ // Find any services that should be running in this process...
+ if (!badApp) {
+ try {
+ didSomething |= mServices.attachApplicationLocked(app, processName);
+ checkTime(startTime, "attachApplicationLocked: after mServices.attachApplicationLocked");
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Exception thrown starting services in " + app, e);
+ badApp = true;
+ }
+ }
+
+ // Check if a next-broadcast receiver is in this process...
+ if (!badApp) {
+ try {
+ for (BroadcastQueue queue : mBroadcastQueues) {
+ didSomething |= queue.onApplicationAttachedLocked(app);
+ }
+ checkTime(startTime, "attachApplicationLocked: after dispatching broadcasts");
+ } catch (Exception e) {
+ // If the app died trying to launch the receiver we declare it 'bad'
+ Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e);
+ badApp = true;
+ }
+ }
+
+ // Check whether the next backup agent is in this process...
+ if (!badApp && backupTarget != null && backupTarget.app == app) {
+ if (DEBUG_BACKUP) Slog.v(TAG_BACKUP,
+ "New app is backup target, launching agent for " + app);
+ notifyPackageUse(backupTarget.appInfo.packageName,
+ PackageManager.NOTIFY_PACKAGE_USE_BACKUP);
+ try {
+ thread.scheduleCreateBackupAgent(backupTarget.appInfo,
+ backupTarget.backupMode, backupTarget.userId,
+ backupTarget.backupDestination);
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Exception thrown creating backup agent in " + app, e);
+ badApp = true;
+ }
+ }
+
+ if (badApp) {
+ app.killLocked("error during init", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
+ true);
+ handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */);
+ return false;
+ }
+
+ if (!didSomething) {
+ updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
+ checkTime(startTime, "attachApplicationLocked: after updateOomAdjLocked");
+ }
+
+
+ final HostingRecord hostingRecord = app.getHostingRecord();
+ String shortAction = getShortAction(hostingRecord.getAction());
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.PROCESS_START_TIME,
+ app.info.uid,
+ pid,
+ app.info.packageName,
+ FrameworkStatsLog.PROCESS_START_TIME__TYPE__COLD,
+ app.getStartElapsedTime(),
+ (int) (bindApplicationTimeMillis - app.getStartUptime()),
+ (int) (SystemClock.uptimeMillis() - app.getStartUptime()),
+ hostingRecord.getType(),
+ hostingRecord.getName(),
+ shortAction,
+ HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()),
+ HostingRecord.getTriggerTypeForStatsd(hostingRecord.getTriggerType()));
+ return true;
}
@Override
@@ -5140,145 +5253,6 @@
}
}
- private void finishAttachApplicationInner(long startSeq, int uid, int pid) {
- final long startTime = SystemClock.uptimeMillis();
- // Find the application record that is being attached... either via
- // the pid if we are running in multiple processes, or just pull the
- // next app record if we are emulating process with anonymous threads.
- final ProcessRecord app;
- synchronized (mPidsSelfLocked) {
- app = mPidsSelfLocked.get(pid);
- }
-
- if (app != null && app.getStartUid() == uid && app.getStartSeq() == startSeq) {
- mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
- } else {
- Slog.wtf(TAG, "Mismatched or missing ProcessRecord: " + app + ". Pid: " + pid
- + ". Uid: " + uid);
- killProcess(pid);
- killProcessGroup(uid, pid);
- mProcessList.noteAppKill(pid, uid,
- ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
- ApplicationExitInfo.SUBREASON_UNKNOWN,
- "wrong startSeq");
- synchronized (this) {
- app.killLocked("unexpected process record",
- ApplicationExitInfo.REASON_OTHER, true);
- }
- return;
- }
-
- synchronized (this) {
- final boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
- final String processName = app.processName;
- boolean badApp = false;
- boolean didSomething = false;
-
- // See if the top visible activity is waiting to run in this process...
- if (normalMode) {
- try {
- didSomething = mAtmInternal.attachApplication(app.getWindowProcessController());
- } catch (Exception e) {
- Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
- badApp = true;
- }
- }
-
- // Find any services that should be running in this process...
- if (!badApp) {
- try {
- didSomething |= mServices.attachApplicationLocked(app, processName);
- checkTime(startTime, "finishAttachApplicationInner: "
- + "after mServices.attachApplicationLocked");
- } catch (Exception e) {
- Slog.wtf(TAG, "Exception thrown starting services in " + app, e);
- badApp = true;
- }
- }
-
- // Check if a next-broadcast receiver is in this process...
- if (!badApp) {
- try {
- for (BroadcastQueue queue : mBroadcastQueues) {
- didSomething |= queue.onApplicationAttachedLocked(app);
- }
- checkTime(startTime, "finishAttachApplicationInner: "
- + "after dispatching broadcasts");
- } catch (Exception e) {
- // If the app died trying to launch the receiver we declare it 'bad'
- Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e);
- badApp = true;
- }
- }
-
- // Check whether the next backup agent is in this process...
- final BackupRecord backupTarget = mBackupTargets.get(app.userId);
- if (!badApp && backupTarget != null && backupTarget.app == app) {
- if (DEBUG_BACKUP) {
- Slog.v(TAG_BACKUP,
- "New app is backup target, launching agent for " + app);
- }
-
- notifyPackageUse(backupTarget.appInfo.packageName,
- PackageManager.NOTIFY_PACKAGE_USE_BACKUP);
- try {
- app.getThread().scheduleCreateBackupAgent(backupTarget.appInfo,
- backupTarget.backupMode, backupTarget.userId,
- backupTarget.backupDestination);
- } catch (Exception e) {
- Slog.wtf(TAG, "Exception thrown creating backup agent in " + app, e);
- badApp = true;
- }
- }
-
- if (badApp) {
- app.killLocked("error during init",
- ApplicationExitInfo.REASON_INITIALIZATION_FAILURE, true);
- handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */);
- return;
- }
-
- if (!didSomething) {
- updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
- checkTime(startTime, "finishAttachApplicationInner: after updateOomAdjLocked");
- }
-
- final HostingRecord hostingRecord = app.getHostingRecord();
- final String shortAction = getShortAction(hostingRecord.getAction());
- FrameworkStatsLog.write(
- FrameworkStatsLog.PROCESS_START_TIME,
- app.info.uid,
- pid,
- app.info.packageName,
- FrameworkStatsLog.PROCESS_START_TIME__TYPE__COLD,
- app.getStartElapsedTime(),
- (int) (app.getBindApplicationTime() - app.getStartUptime()),
- (int) (SystemClock.uptimeMillis() - app.getStartUptime()),
- hostingRecord.getType(),
- hostingRecord.getName(),
- shortAction,
- HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()),
- HostingRecord.getTriggerTypeForStatsd(hostingRecord.getTriggerType()));
- }
- }
-
- @Override
- public final void finishAttachApplication(long startSeq) {
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
-
- if (pid == MY_PID && uid == SYSTEM_UID) {
- return;
- }
-
- final long origId = Binder.clearCallingIdentity();
- try {
- finishAttachApplicationInner(startSeq, uid, pid);
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
-
/**
* @return The last part of the string of an intent's action.
*/
@@ -18919,21 +18893,6 @@
return new ProcessList();
}
- /**
- * Returns the {@link BatteryStatsService} instance
- */
- public BatteryStatsService getBatteryStatsService() {
- return new BatteryStatsService(mContext, SystemServiceManager.ensureSystemDir(),
- BackgroundThread.get().getHandler());
- }
-
- /**
- * Returns the {@link ActiveServices} instance
- */
- public ActiveServices getActiveServices(ActivityManagerService service) {
- return new ActiveServices(service);
- }
-
private boolean ensureHasNetworkManagementInternal() {
if (mNmi == null) {
mNmi = LocalServices.getService(NetworkManagementInternal.class);
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 4a6e5a3..7f2e5fb 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -1350,6 +1350,9 @@
final InputStream mInput;
final String mGdbPort;
final boolean mMonkey;
+ final boolean mSimpleMode;
+ final String mTarget;
+ final boolean mAlwaysContinue;
static final int STATE_NORMAL = 0;
static final int STATE_CRASHED = 1;
@@ -1377,16 +1380,30 @@
boolean mGotGdbPrint;
MyActivityController(IActivityManager iam, PrintWriter pw, InputStream input,
- String gdbPort, boolean monkey) {
+ String gdbPort, boolean monkey, boolean simpleMode, String target,
+ boolean alwaysContinue) {
mInterface = iam;
mPw = pw;
mInput = input;
mGdbPort = gdbPort;
mMonkey = monkey;
+ mSimpleMode = simpleMode;
+ mTarget = target;
+ mAlwaysContinue = alwaysContinue;
+ }
+
+ private boolean shouldHandlePackageOrProcess(String packageOrProcess) {
+ if (mTarget == null) {
+ return true; // Always handle all packages / processes.
+ }
+ return mTarget.equals(packageOrProcess);
}
@Override
public boolean activityResuming(String pkg) {
+ if (!shouldHandlePackageOrProcess(pkg)) {
+ return true;
+ }
synchronized (this) {
mPw.println("** Activity resuming: " + pkg);
mPw.flush();
@@ -1396,6 +1413,9 @@
@Override
public boolean activityStarting(Intent intent, String pkg) {
+ if (!shouldHandlePackageOrProcess(pkg)) {
+ return true;
+ }
synchronized (this) {
mPw.println("** Activity starting: " + pkg);
mPw.flush();
@@ -1406,18 +1426,28 @@
@Override
public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg,
long timeMillis, String stackTrace) {
+ if (!shouldHandlePackageOrProcess(processName)) {
+ return true; // Don't kill
+ }
synchronized (this) {
- mPw.println("** ERROR: PROCESS CRASHED");
- mPw.println("processName: " + processName);
- mPw.println("processPid: " + pid);
- mPw.println("shortMsg: " + shortMsg);
- mPw.println("longMsg: " + longMsg);
- mPw.println("timeMillis: " + timeMillis);
- mPw.println("uptime: " + SystemClock.uptimeMillis());
- mPw.println("stack:");
- mPw.print(stackTrace);
- mPw.println("#");
+ if (mSimpleMode) {
+ mPw.println("** PROCESS CRASHED: " + processName);
+ } else {
+ mPw.println("** ERROR: PROCESS CRASHED");
+ mPw.println("processName: " + processName);
+ mPw.println("processPid: " + pid);
+ mPw.println("shortMsg: " + shortMsg);
+ mPw.println("longMsg: " + longMsg);
+ mPw.println("timeMillis: " + timeMillis);
+ mPw.println("uptime: " + SystemClock.uptimeMillis());
+ mPw.println("stack:");
+ mPw.print(stackTrace);
+ mPw.println("#");
+ }
mPw.flush();
+ if (mAlwaysContinue) {
+ return true;
+ }
int result = waitControllerLocked(pid, STATE_CRASHED);
return result == RESULT_CRASH_KILL ? false : true;
}
@@ -1425,13 +1455,23 @@
@Override
public int appEarlyNotResponding(String processName, int pid, String annotation) {
+ if (!shouldHandlePackageOrProcess(processName)) {
+ return 0; // Continue
+ }
synchronized (this) {
- mPw.println("** ERROR: EARLY PROCESS NOT RESPONDING");
- mPw.println("processName: " + processName);
- mPw.println("processPid: " + pid);
- mPw.println("annotation: " + annotation);
- mPw.println("uptime: " + SystemClock.uptimeMillis());
+ if (mSimpleMode) {
+ mPw.println("** EARLY PROCESS NOT RESPONDING: " + processName);
+ } else {
+ mPw.println("** ERROR: EARLY PROCESS NOT RESPONDING");
+ mPw.println("processName: " + processName);
+ mPw.println("processPid: " + pid);
+ mPw.println("annotation: " + annotation);
+ mPw.println("uptime: " + SystemClock.uptimeMillis());
+ }
mPw.flush();
+ if (mAlwaysContinue) {
+ return 0;
+ }
int result = waitControllerLocked(pid, STATE_EARLY_ANR);
if (result == RESULT_EARLY_ANR_KILL) return -1;
return 0;
@@ -1440,15 +1480,25 @@
@Override
public int appNotResponding(String processName, int pid, String processStats) {
+ if (!shouldHandlePackageOrProcess(processName)) {
+ return 0; // Default == show dialog
+ }
synchronized (this) {
- mPw.println("** ERROR: PROCESS NOT RESPONDING");
- mPw.println("processName: " + processName);
- mPw.println("processPid: " + pid);
- mPw.println("uptime: " + SystemClock.uptimeMillis());
- mPw.println("processStats:");
- mPw.print(processStats);
- mPw.println("#");
+ if (mSimpleMode) {
+ mPw.println("** PROCESS NOT RESPONDING: " + processName);
+ } else {
+ mPw.println("** ERROR: PROCESS NOT RESPONDING");
+ mPw.println("processName: " + processName);
+ mPw.println("processPid: " + pid);
+ mPw.println("uptime: " + SystemClock.uptimeMillis());
+ mPw.println("processStats:");
+ mPw.print(processStats);
+ mPw.println("#");
+ }
mPw.flush();
+ if (mAlwaysContinue) {
+ return 0;
+ }
int result = waitControllerLocked(pid, STATE_ANR);
if (result == RESULT_ANR_KILL) return -1;
if (result == RESULT_ANR_WAIT) return 1;
@@ -1458,11 +1508,16 @@
@Override
public int systemNotResponding(String message) {
+ if (mTarget != null) {
+ return -1; // If any target is set, just return.
+ }
synchronized (this) {
mPw.println("** ERROR: PROCESS NOT RESPONDING");
- mPw.println("message: " + message);
- mPw.println("#");
- mPw.println("Allowing system to die.");
+ if (!mSimpleMode) {
+ mPw.println("message: " + message);
+ mPw.println("#");
+ mPw.println("Allowing system to die.");
+ }
mPw.flush();
return -1;
}
@@ -1568,6 +1623,9 @@
}
void printMessageForState() {
+ if (mAlwaysContinue && mSimpleMode) {
+ return; // In the simplest mode, we don't need to show anything.
+ }
switch (mState) {
case STATE_NORMAL:
mPw.println("Monitoring activity manager... available commands:");
@@ -1663,11 +1721,21 @@
String opt;
String gdbPort = null;
boolean monkey = false;
+ boolean simpleMode = false;
+ boolean alwaysContinue = false;
+ String target = null;
+
while ((opt=getNextOption()) != null) {
if (opt.equals("--gdb")) {
gdbPort = getNextArgRequired();
+ } else if (opt.equals("-p")) {
+ target = getNextArgRequired();
} else if (opt.equals("-m")) {
monkey = true;
+ } else if (opt.equals("-s")) {
+ simpleMode = true;
+ } else if (opt.equals("-c")) {
+ alwaysContinue = true;
} else {
getErrPrintWriter().println("Error: Unknown option: " + opt);
return -1;
@@ -1675,7 +1743,7 @@
}
MyActivityController controller = new MyActivityController(mInterface, pw,
- getRawInputStream(), gdbPort, monkey);
+ getRawInputStream(), gdbPort, monkey, simpleMode, target, alwaysContinue);
controller.run();
return 0;
}
@@ -3896,9 +3964,12 @@
pw.println(" make-uid-idle [--user <USER_ID> | all | current] <PACKAGE>");
pw.println(" If the given application's uid is in the background and waiting to");
pw.println(" become idle (not allowing background services), do that now.");
- pw.println(" monitor [--gdb <port>]");
+ pw.println(" monitor [--gdb <port>] [-p <TARGET>] [-s] [-c]");
pw.println(" Start monitoring for crashes or ANRs.");
pw.println(" --gdb: start gdbserv on the given port at crash/ANR");
+ pw.println(" -p: only show events related to a specific process / package");
+ pw.println(" -s: simple mode, only show a summary line for each event");
+ pw.println(" -c: assume the input is always [c]ontinue");
pw.println(" watch-uids [--oom <uid>]");
pw.println(" Start watching for and reporting uid state changes.");
pw.println(" --oom: specify a uid for which to report detailed change messages.");
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 4d559b0..81e249c 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2508,7 +2508,7 @@
}
@GuardedBy("mService")
- String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) {
+ private String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) {
StringBuilder sb = null;
if (app.isKilledByAm()) {
if (sb == null) sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 33d7f9d..cf91429 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -200,11 +200,6 @@
private volatile long mStartElapsedTime;
/**
- * When the process was sent the bindApplication request
- */
- private volatile long mBindApplicationTime;
-
- /**
* This will be same as {@link #uid} usually except for some apps used during factory testing.
*/
private volatile int mStartUid;
@@ -744,10 +739,6 @@
return mStartElapsedTime;
}
- long getBindApplicationTime() {
- return mBindApplicationTime;
- }
-
int getStartUid() {
return mStartUid;
}
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 5fe9ada..6279a4d 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -66,7 +66,7 @@
private static final String TAG = "AS.SoundDoseHelper";
/*package*/ static final String ACTION_CHECK_MUSIC_ACTIVE =
- AudioService.class.getSimpleName() + ".CHECK_MUSIC_ACTIVE";
+ "com.android.server.audio.action.CHECK_MUSIC_ACTIVE";
/** Flag to enable/disable the sound dose computation. */
private static final boolean USE_CSD_FOR_SAFE_HEARING = false;
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 197c64e..4f00835 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -249,7 +249,7 @@
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
- int ambientLightHorizonLong) {
+ int ambientLightHorizonLong, float userLux, float userBrightness) {
this(new Injector(), callbacks, looper, sensorManager, lightSensor,
interactiveModeBrightnessMapper,
lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor,
@@ -258,7 +258,7 @@
ambientBrightnessThresholds, screenBrightnessThresholds,
ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, context,
hbmController, brightnessThrottler, idleModeBrightnessMapper,
- ambientLightHorizonShort, ambientLightHorizonLong
+ ambientLightHorizonShort, ambientLightHorizonLong, userLux, userBrightness
);
}
@@ -275,7 +275,7 @@
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
- int ambientLightHorizonLong) {
+ int ambientLightHorizonLong, float userLux, float userBrightness) {
mInjector = injector;
mClock = injector.createClock();
mContext = context;
@@ -322,6 +322,12 @@
mIdleModeBrightnessMapper = idleModeBrightnessMapper;
// Initialize to active (normal) screen brightness mode
switchToInteractiveScreenBrightnessMode();
+
+ if (userLux != BrightnessMappingStrategy.NO_USER_LUX
+ && userBrightness != BrightnessMappingStrategy.NO_USER_BRIGHTNESS) {
+ // Use the given short-term model
+ setScreenBrightnessByUser(userLux, userBrightness);
+ }
}
/**
@@ -384,7 +390,8 @@
public void configure(int state, @Nullable BrightnessConfiguration configuration,
float brightness, boolean userChangedBrightness, float adjustment,
- boolean userChangedAutoBrightnessAdjustment, int displayPolicy) {
+ boolean userChangedAutoBrightnessAdjustment, int displayPolicy,
+ boolean shouldResetShortTermModel) {
mState = state;
mHbmController.setAutoBrightnessEnabled(mState);
// While dozing, the application processor may be suspended which will prevent us from
@@ -393,7 +400,7 @@
// and hold onto the last computed screen auto brightness. We save the dozing flag for
// debugging purposes.
boolean dozing = (displayPolicy == DisplayPowerRequest.POLICY_DOZE);
- boolean changed = setBrightnessConfiguration(configuration);
+ boolean changed = setBrightnessConfiguration(configuration, shouldResetShortTermModel);
changed |= setDisplayPolicy(displayPolicy);
if (userChangedAutoBrightnessAdjustment) {
changed |= setAutoBrightnessAdjustment(adjustment);
@@ -492,9 +499,13 @@
// and we can't use this data to add a new control point to the short-term model.
return false;
}
- mCurrentBrightnessMapper.addUserDataPoint(mAmbientLux, brightness);
+ return setScreenBrightnessByUser(mAmbientLux, brightness);
+ }
+
+ private boolean setScreenBrightnessByUser(float lux, float brightness) {
+ mCurrentBrightnessMapper.addUserDataPoint(lux, brightness);
mShortTermModelValid = true;
- mShortTermModelAnchor = mAmbientLux;
+ mShortTermModelAnchor = lux;
if (mLoggingEnabled) {
Slog.d(TAG, "ShortTermModel: anchor=" + mShortTermModelAnchor);
}
@@ -514,9 +525,10 @@
mShortTermModelValid = false;
}
- public boolean setBrightnessConfiguration(BrightnessConfiguration configuration) {
+ public boolean setBrightnessConfiguration(BrightnessConfiguration configuration,
+ boolean shouldResetShortTermModel) {
if (mInteractiveModeBrightnessMapper.setBrightnessConfiguration(configuration)) {
- if (!isInIdleMode()) {
+ if (!isInIdleMode() && shouldResetShortTermModel) {
resetShortTermModel();
}
return true;
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index 25d0752..3fc50c4 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -51,6 +51,9 @@
public abstract class BrightnessMappingStrategy {
private static final String TAG = "BrightnessMappingStrategy";
+ public static final float NO_USER_LUX = -1;
+ public static final float NO_USER_BRIGHTNESS = -1;
+
private static final float LUX_GRAD_SMOOTHING = 0.25f;
private static final float MAX_GRAD = 1.0f;
private static final float SHORT_TERM_MODEL_THRESHOLD_RATIO = 0.6f;
@@ -68,6 +71,7 @@
* Creates a BrightnessMappingStrategy for active (normal) mode.
* @param resources
* @param displayDeviceConfig
+ * @param displayWhiteBalanceController
* @return the BrightnessMappingStrategy
*/
@Nullable
@@ -82,6 +86,7 @@
* Creates a BrightnessMappingStrategy for idle screen brightness mode.
* @param resources
* @param displayDeviceConfig
+ * @param displayWhiteBalanceController
* @return the BrightnessMappingStrategy
*/
@Nullable
@@ -100,6 +105,7 @@
* @param displayDeviceConfig
* @param isForIdleMode determines whether the configurations loaded are for idle screen
* brightness mode or active screen brightness mode.
+ * @param displayWhiteBalanceController
* @return the BrightnessMappingStrategy
*/
@Nullable
@@ -370,6 +376,10 @@
*/
public abstract boolean isForIdleMode();
+ abstract float getUserLux();
+
+ abstract float getUserBrightness();
+
/**
* Check if the short term model should be reset given the anchor lux the last
* brightness change was made at and the current ambient lux.
@@ -604,8 +614,8 @@
mMaxGamma = maxGamma;
mAutoBrightnessAdjustment = 0;
- mUserLux = -1;
- mUserBrightness = -1;
+ mUserLux = NO_USER_LUX;
+ mUserBrightness = NO_USER_BRIGHTNESS;
if (mLoggingEnabled) {
PLOG.start("simple mapping strategy");
}
@@ -732,6 +742,16 @@
return false;
}
+ @Override
+ float getUserLux() {
+ return mUserLux;
+ }
+
+ @Override
+ float getUserBrightness() {
+ return mUserBrightness;
+ }
+
private void computeSpline() {
Pair<float[], float[]> curve = getAdjustedCurve(mLux, mBrightness, mUserLux,
mUserBrightness, mAutoBrightnessAdjustment, mMaxGamma);
@@ -799,8 +819,8 @@
mIsForIdleMode = isForIdleMode;
mMaxGamma = maxGamma;
mAutoBrightnessAdjustment = 0;
- mUserLux = -1;
- mUserBrightness = -1;
+ mUserLux = NO_USER_LUX;
+ mUserBrightness = NO_USER_BRIGHTNESS;
mDisplayWhiteBalanceController = displayWhiteBalanceController;
mNits = nits;
@@ -972,6 +992,16 @@
return mIsForIdleMode;
}
+ @Override
+ float getUserLux() {
+ return mUserLux;
+ }
+
+ @Override
+ float getUserBrightness() {
+ return mUserBrightness;
+ }
+
/**
* Prints out the default curve and how it differs from the long-term curve
* and the current curve (in case the current curve includes short-term adjustments).
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 329e3ca..5e9f0e7 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -594,7 +594,7 @@
getBrightnessConfigForDisplayWithPdsFallbackLocked(
logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(),
userSerial);
- dpc.setBrightnessConfiguration(config);
+ dpc.setBrightnessConfiguration(config, /* shouldResetShortTermModel= */ true);
}
dpc.onSwitchUser(newUserId);
});
@@ -1934,7 +1934,7 @@
}
DisplayPowerControllerInterface dpc = getDpcFromUniqueIdLocked(uniqueId);
if (dpc != null) {
- dpc.setBrightnessConfiguration(c);
+ dpc.setBrightnessConfiguration(c, /* shouldResetShortTermModel= */ true);
}
}
}
@@ -1983,7 +1983,8 @@
final DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(
logicalDisplay.getDisplayIdLocked());
if (dpc != null) {
- dpc.setBrightnessConfiguration(config);
+ dpc.setBrightnessConfiguration(config,
+ /* shouldResetShortTermModel= */ false);
}
}
});
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index e4fcdbd..f4eed2b 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -225,6 +225,9 @@
// True if should use light sensor to automatically determine doze screen brightness.
private final boolean mAllowAutoBrightnessWhileDozingConfig;
+ // True if the brightness config has changed and the short-term model needs to be reset
+ private boolean mShouldResetShortTermModel;
+
// Whether or not the color fade on screen on / off is enabled.
private final boolean mColorFadeEnabled;
@@ -951,6 +954,13 @@
return;
}
+ float userLux = BrightnessMappingStrategy.NO_USER_LUX;
+ float userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
+ if (mInteractiveModeBrightnessMapper != null) {
+ userLux = mInteractiveModeBrightnessMapper.getUserLux();
+ userBrightness = mInteractiveModeBrightnessMapper.getUserBrightness();
+ }
+
final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
R.bool.config_enableIdleScreenBrightnessMode);
mInteractiveModeBrightnessMapper = BrightnessMappingStrategy.create(resources,
@@ -1078,7 +1088,7 @@
ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext,
mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper,
mDisplayDeviceConfig.getAmbientHorizonShort(),
- mDisplayDeviceConfig.getAmbientHorizonLong());
+ mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userBrightness);
mBrightnessEventRingBuffer =
new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_MAX);
@@ -1428,7 +1438,9 @@
mBrightnessConfiguration,
mLastUserSetScreenBrightness,
userSetBrightnessChanged, autoBrightnessAdjustment,
- autoBrightnessAdjustmentChanged, mPowerRequest.policy);
+ autoBrightnessAdjustmentChanged, mPowerRequest.policy,
+ mShouldResetShortTermModel);
+ mShouldResetShortTermModel = false;
}
if (mBrightnessTracker != null) {
@@ -1840,8 +1852,10 @@
}
@Override
- public void setBrightnessConfiguration(BrightnessConfiguration c) {
- Message msg = mHandler.obtainMessage(MSG_CONFIGURE_BRIGHTNESS, c);
+ public void setBrightnessConfiguration(BrightnessConfiguration c,
+ boolean shouldResetShortTermModel) {
+ Message msg = mHandler.obtainMessage(MSG_CONFIGURE_BRIGHTNESS,
+ shouldResetShortTermModel ? 1 : 0, /* unused */ 0, c);
msg.sendToTarget();
}
@@ -2892,6 +2906,7 @@
break;
case MSG_CONFIGURE_BRIGHTNESS:
mBrightnessConfiguration = (BrightnessConfiguration) msg.obj;
+ mShouldResetShortTermModel = msg.arg1 == 1;
updatePowerState();
break;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 81011dc..09136b0 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -202,6 +202,9 @@
// True if auto-brightness should be used.
private boolean mUseSoftwareAutoBrightnessConfig;
+ // True if the brightness config has changed and the short-term model needs to be reset
+ private boolean mShouldResetShortTermModel;
+
// Whether or not the color fade on screen on / off is enabled.
private final boolean mColorFadeEnabled;
@@ -868,6 +871,13 @@
return;
}
+ float userLux = BrightnessMappingStrategy.NO_USER_LUX;
+ float userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
+ if (mInteractiveModeBrightnessMapper != null) {
+ userLux = mInteractiveModeBrightnessMapper.getUserLux();
+ userBrightness = mInteractiveModeBrightnessMapper.getUserBrightness();
+ }
+
final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
R.bool.config_enableIdleScreenBrightnessMode);
mInteractiveModeBrightnessMapper = BrightnessMappingStrategy.create(resources,
@@ -995,7 +1005,7 @@
ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext,
mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper,
mDisplayDeviceConfig.getAmbientHorizonShort(),
- mDisplayDeviceConfig.getAmbientHorizonLong());
+ mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userBrightness);
mBrightnessEventRingBuffer =
new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_MAX);
@@ -1220,7 +1230,9 @@
mBrightnessConfiguration,
mLastUserSetScreenBrightness,
userSetBrightnessChanged, autoBrightnessAdjustment,
- autoBrightnessAdjustmentChanged, mPowerRequest.policy);
+ autoBrightnessAdjustmentChanged, mPowerRequest.policy,
+ mShouldResetShortTermModel);
+ mShouldResetShortTermModel = false;
}
if (mBrightnessTracker != null) {
@@ -1626,8 +1638,10 @@
}
@Override
- public void setBrightnessConfiguration(BrightnessConfiguration c) {
- Message msg = mHandler.obtainMessage(MSG_CONFIGURE_BRIGHTNESS, c);
+ public void setBrightnessConfiguration(BrightnessConfiguration c,
+ boolean shouldResetShortTermModel) {
+ Message msg = mHandler.obtainMessage(MSG_CONFIGURE_BRIGHTNESS,
+ shouldResetShortTermModel ? 1 : 0, /* unused */ 0, c);
msg.sendToTarget();
}
@@ -2485,6 +2499,7 @@
break;
case MSG_CONFIGURE_BRIGHTNESS:
mBrightnessConfiguration = (BrightnessConfiguration) msg.obj;
+ mShouldResetShortTermModel = msg.arg1 == 1;
updatePowerState();
break;
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index 46f1343..e750ee2 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -48,7 +48,8 @@
* Used to update the display's BrightnessConfiguration
* @param config The new BrightnessConfiguration
*/
- void setBrightnessConfiguration(BrightnessConfiguration config);
+ void setBrightnessConfiguration(BrightnessConfiguration config,
+ boolean shouldResetShortTermModel);
/**
* Used to set the ambient color temperature of the Display
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 783a6ae..66c01dc 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -25,6 +25,7 @@
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ILocaleManager;
+import android.app.LocaleConfig;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -32,6 +33,7 @@
import android.content.pm.PackageManager.PackageInfoFlags;
import android.content.res.Configuration;
import android.os.Binder;
+import android.os.Environment;
import android.os.HandlerThread;
import android.os.LocaleList;
import android.os.Process;
@@ -42,21 +44,40 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.AtomicFile;
import android.util.Slog;
+import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.wm.ActivityTaskManagerInternal;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
/**
* The implementation of ILocaleManager.aidl.
*
- * <p>This service is API entry point for storing app-specific UI locales
+ * <p>This service is API entry point for storing app-specific UI locales and an override
+ * {@link LocaleConfig} for a specified app.
*/
public class LocaleManagerService extends SystemService {
private static final String TAG = "LocaleManagerService";
@@ -64,6 +85,13 @@
// app.
private static final String PROP_ALLOW_IME_QUERY_APP_LOCALE =
"i18n.feature.allow_ime_query_app_locale";
+ // The feature flag control that the application can dynamically override the LocaleConfig.
+ private static final String PROP_DYNAMIC_LOCALES_CHANGE =
+ "i18n.feature.dynamic_locales_change";
+ private static final String LOCALE_CONFIGS = "locale_configs";
+ private static final String SUFFIX_FILE_NAME = ".xml";
+ private static final String ATTR_NAME = "name";
+
final Context mContext;
private final LocaleManagerService.LocaleManagerBinderService mBinderService;
private ActivityTaskManagerInternal mActivityTaskManagerInternal;
@@ -74,6 +102,8 @@
private final PackageMonitor mPackageMonitor;
+ private final Object mWriteLock = new Object();
+
public static final boolean DEBUG = false;
public LocaleManagerService(Context context) {
@@ -103,7 +133,7 @@
new AppUpdateTracker(mContext, this, mBackupHelper);
mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper,
- systemAppUpdateTracker, appUpdateTracker);
+ systemAppUpdateTracker, appUpdateTracker, this);
mPackageMonitor.register(context, broadcastHandlerThread.getLooper(),
UserHandle.ALL,
true);
@@ -173,6 +203,19 @@
}
@Override
+ public void setOverrideLocaleConfig(@NonNull String appPackageName, @UserIdInt int userId,
+ @Nullable LocaleConfig localeConfig) throws RemoteException {
+ LocaleManagerService.this.setOverrideLocaleConfig(appPackageName, userId, localeConfig);
+ }
+
+ @Override
+ @Nullable
+ public LocaleConfig getOverrideLocaleConfig(@NonNull String appPackageName,
+ @UserIdInt int userId) {
+ return LocaleManagerService.this.getOverrideLocaleConfig(appPackageName, userId);
+ }
+
+ @Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
@@ -211,7 +254,6 @@
if (!isCallerOwner) {
enforceChangeConfigurationPermission(atomRecordForMetrics);
}
-
mBackupHelper.persistLocalesModificationInfo(userId, appPackageName, fromDelegate,
locales.isEmpty());
final long token = Binder.clearCallingIdentity();
@@ -516,4 +558,242 @@
atomRecordForMetrics.mPrevLocales,
atomRecordForMetrics.mStatus);
}
+
+ /**
+ * Storing an override {@link LocaleConfig} for a specified app.
+ */
+ public void setOverrideLocaleConfig(@NonNull String appPackageName, @UserIdInt int userId,
+ @Nullable LocaleConfig localeConfig) throws IllegalArgumentException {
+ // TODO(b/262713398): Remove when stable
+ if (!SystemProperties.getBoolean(PROP_DYNAMIC_LOCALES_CHANGE, true)) {
+ return;
+ }
+
+ requireNonNull(appPackageName);
+
+ //Allow apps with INTERACT_ACROSS_USERS permission to set locales for different user.
+ userId = mActivityManagerInternal.handleIncomingUser(
+ Binder.getCallingPid(), Binder.getCallingUid(), userId,
+ false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL,
+ "setOverrideLocaleConfig", /* callerPackage= */ null);
+
+ // This function handles two types of set operations:
+ // 1.) A normal, an app overrides its own LocaleConfig.
+ // 2.) A privileged system application or service is granted the necessary permission to
+ // override a LocaleConfig of another package.
+ if (!isPackageOwnedByCaller(appPackageName, userId)) {
+ enforceSetAppSpecificLocaleConfigPermission();
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ setOverrideLocaleConfigUnchecked(appPackageName, userId, localeConfig);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ //TODO: Add metrics to monitor the usage by applications
+ }
+ private void setOverrideLocaleConfigUnchecked(@NonNull String appPackageName,
+ @UserIdInt int userId, @Nullable LocaleConfig overridelocaleConfig) {
+ synchronized (mWriteLock) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "set the override LocaleConfig for package " + appPackageName + " and user "
+ + userId);
+ }
+ final File file = getXmlFileNameForUser(appPackageName, userId);
+
+ if (overridelocaleConfig == null) {
+ if (file.exists()) {
+ Slog.d(TAG, "remove the override LocaleConfig");
+ file.delete();
+ }
+ return;
+ } else {
+ LocaleList localeList = overridelocaleConfig.getSupportedLocales();
+ // Normally the LocaleList object should not be null. However we reassign it as the
+ // empty list in case it happens.
+ if (localeList == null) {
+ localeList = LocaleList.getEmptyLocaleList();
+ }
+ if (DEBUG) {
+ Slog.d(TAG,
+ "setOverrideLocaleConfig, localeList: " + localeList.toLanguageTags());
+ }
+
+ // Store the override LocaleConfig to the file storage.
+ final AtomicFile atomicFile = new AtomicFile(file);
+ FileOutputStream stream = null;
+ try {
+ stream = atomicFile.startWrite();
+ stream.write(toXmlByteArray(localeList));
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to write file " + atomicFile, e);
+ if (stream != null) {
+ atomicFile.failWrite(stream);
+ }
+ return;
+ }
+ atomicFile.finishWrite(stream);
+ // Clear per-app locales if they are not in the override LocaleConfig.
+ removeUnsupportedAppLocales(appPackageName, userId, overridelocaleConfig);
+ if (DEBUG) {
+ Slog.i(TAG, "Successfully written to " + atomicFile);
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks if the per-app locales are in the new override LocaleConfig. Per-app locales
+ * missing from the new LocaleConfig will be removed.
+ */
+ private void removeUnsupportedAppLocales(String appPackageName, int userId,
+ LocaleConfig localeConfig) {
+ LocaleList appLocales = getApplicationLocalesUnchecked(appPackageName, userId);
+ // Remove the app locale from the locale list if it doesn't exist in the override
+ // LocaleConfig.
+ boolean resetAppLocales = false;
+ List<Locale> newAppLocales = new ArrayList<Locale>();
+ for (int i = 0; i < appLocales.size(); i++) {
+ if (!localeConfig.containsLocale(appLocales.get(i))) {
+ Slog.i(TAG, "reset the app locales");
+ resetAppLocales = true;
+ continue;
+ }
+ newAppLocales.add(appLocales.get(i));
+ }
+
+ if (resetAppLocales) {
+ // Reset the app locales
+ Locale[] locales = new Locale[newAppLocales.size()];
+ try {
+ setApplicationLocales(appPackageName, userId,
+ new LocaleList(newAppLocales.toArray(locales)), false);
+ } catch (RemoteException | IllegalArgumentException e) {
+ Slog.e(TAG, "Could not set locales for " + appPackageName, e);
+ }
+ }
+ }
+
+ private void enforceSetAppSpecificLocaleConfigPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.SET_APP_SPECIFIC_LOCALECONFIG,
+ "setOverrideLocaleConfig");
+ }
+
+ /**
+ * Returns the override LocaleConfig for a specified app.
+ */
+ @Nullable
+ public LocaleConfig getOverrideLocaleConfig(@NonNull String appPackageName,
+ @UserIdInt int userId) {
+ // TODO(b/262713398): Remove when stable
+ if (!SystemProperties.getBoolean(PROP_DYNAMIC_LOCALES_CHANGE, true)) {
+ return null;
+ }
+
+ requireNonNull(appPackageName);
+
+ // Allow apps with INTERACT_ACROSS_USERS permission to query the override LocaleConfig for
+ // different user.
+ userId = mActivityManagerInternal.handleIncomingUser(
+ Binder.getCallingPid(), Binder.getCallingUid(), userId,
+ false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL,
+ "getOverrideLocaleConfig", /* callerPackage= */ null);
+
+ final File file = getXmlFileNameForUser(appPackageName, userId);
+ if (!file.exists()) {
+ if (DEBUG) {
+ Slog.i(TAG, "getOverrideLocaleConfig, the file is not existed.");
+ }
+ return null;
+ }
+
+ try (InputStream in = new FileInputStream(file)) {
+ final TypedXmlPullParser parser = Xml.resolvePullParser(in);
+ List<String> overrideLocales = loadFromXml(parser);
+ if (DEBUG) {
+ Slog.i(TAG, "getOverrideLocaleConfig, Loaded locales: " + overrideLocales);
+ }
+ LocaleConfig storedLocaleConfig = new LocaleConfig(
+ LocaleList.forLanguageTags(String.join(",", overrideLocales)));
+
+ return storedLocaleConfig;
+ } catch (IOException | XmlPullParserException e) {
+ Slog.e(TAG, "Failed to parse XML configuration from " + file, e);
+ }
+
+ return null;
+ }
+
+ /**
+ * Delete an override {@link LocaleConfig} for a specified app from the file storage.
+ *
+ * <p>Clear the override LocaleConfig from the storage when the app is uninstalled.
+ */
+ void deleteOverrideLocaleConfig(@NonNull String appPackageName, @UserIdInt int userId) {
+ final File file = getXmlFileNameForUser(appPackageName, userId);
+
+ if (file.exists()) {
+ Slog.d(TAG, "Delete the override LocaleConfig.");
+ file.delete();
+ }
+ }
+
+ private byte[] toXmlByteArray(LocaleList localeList) {
+ try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
+ TypedXmlSerializer out = Xml.newFastSerializer();
+ out.setOutput(os, StandardCharsets.UTF_8.name());
+ out.startDocument(/* encoding= */ null, /* standalone= */ true);
+ out.startTag(/* namespace= */ null, LocaleConfig.TAG_LOCALE_CONFIG);
+
+ List<String> locales = new ArrayList<String>(
+ Arrays.asList(localeList.toLanguageTags().split(",")));
+ for (String locale : locales) {
+ out.startTag(null, LocaleConfig.TAG_LOCALE);
+ out.attribute(null, ATTR_NAME, locale);
+ out.endTag(null, LocaleConfig.TAG_LOCALE);
+ }
+
+ out.endTag(/* namespace= */ null, LocaleConfig.TAG_LOCALE_CONFIG);
+ out.endDocument();
+
+ if (DEBUG) {
+ Slog.d(TAG, "setOverrideLocaleConfig toXmlByteArray, output: " + os.toString());
+ }
+ return os.toByteArray();
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ @NonNull
+ private List<String> loadFromXml(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ List<String> localeList = new ArrayList<>();
+
+ XmlUtils.beginDocument(parser, LocaleConfig.TAG_LOCALE_CONFIG);
+ int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ final String tagName = parser.getName();
+ if (LocaleConfig.TAG_LOCALE.equals(tagName)) {
+ String locale = parser.getAttributeValue(/* namespace= */ null, ATTR_NAME);
+ localeList.add(locale);
+ } else {
+ Slog.w(TAG, "Unexpected tag name: " + tagName);
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ return localeList;
+ }
+
+ @NonNull
+ private File getXmlFileNameForUser(@NonNull String appPackageName, @UserIdInt int userId) {
+ // TODO(b/262752965): use per-package data directory
+ final File dir = new File(Environment.getDataSystemDeDirectory(userId), LOCALE_CONFIGS);
+ return new File(dir, appPackageName + SUFFIX_FILE_NAME);
+ }
}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
index 1a38f0c..771e1b0 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
@@ -16,6 +16,9 @@
package com.android.server.locales;
+import android.annotation.NonNull;
+import android.os.UserHandle;
+
import com.android.internal.content.PackageMonitor;
/**
@@ -35,12 +38,16 @@
private LocaleManagerBackupHelper mBackupHelper;
private SystemAppUpdateTracker mSystemAppUpdateTracker;
private AppUpdateTracker mAppUpdateTracker;
+ private LocaleManagerService mLocaleManagerService;
- LocaleManagerServicePackageMonitor(LocaleManagerBackupHelper localeManagerBackupHelper,
- SystemAppUpdateTracker systemAppUpdateTracker, AppUpdateTracker appUpdateTracker) {
+ LocaleManagerServicePackageMonitor(@NonNull LocaleManagerBackupHelper localeManagerBackupHelper,
+ @NonNull SystemAppUpdateTracker systemAppUpdateTracker,
+ @NonNull AppUpdateTracker appUpdateTracker,
+ @NonNull LocaleManagerService localeManagerService) {
mBackupHelper = localeManagerBackupHelper;
mSystemAppUpdateTracker = systemAppUpdateTracker;
mAppUpdateTracker = appUpdateTracker;
+ mLocaleManagerService = localeManagerService;
}
@Override
@@ -56,6 +63,7 @@
@Override
public void onPackageRemoved(String packageName, int uid) {
mBackupHelper.onPackageRemoved(packageName, uid);
+ mLocaleManagerService.deleteOverrideLocaleConfig(packageName, UserHandle.getUserId(uid));
}
@Override
diff --git a/services/core/java/com/android/server/locales/LocaleManagerShellCommand.java b/services/core/java/com/android/server/locales/LocaleManagerShellCommand.java
index c5069e5..09f2ffa 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerShellCommand.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerShellCommand.java
@@ -18,6 +18,7 @@
import android.app.ActivityManager;
import android.app.ILocaleManager;
+import android.app.LocaleConfig;
import android.os.LocaleList;
import android.os.RemoteException;
import android.os.ShellCommand;
@@ -44,6 +45,10 @@
return runSetAppLocales();
case "get-app-locales":
return runGetAppLocales();
+ case "set-app-localeconfig":
+ return runSetAppOverrideLocaleConfig();
+ case "get-app-localeconfig":
+ return runGetAppOverrideLocaleConfig();
default: {
return handleDefaultCommands(cmd);
}
@@ -62,15 +67,30 @@
pw.println(" --user <USER_ID>: apply for the given user, "
+ "the current user is used when unspecified.");
pw.println(" --locales <LOCALE_INFO>: The language tags of locale to be included "
- + "as a single String separated by commas");
- pw.println(" Empty locale list is used when unspecified.");
+ + "as a single String separated by commas.");
pw.println(" eg. en,en-US,hi ");
+ pw.println(" Empty locale list is used when unspecified.");
pw.println(" --delegate <FROM_DELEGATE>: The locales are set from a delegate, "
+ "the value could be true or false. false is the default when unspecified.");
pw.println(" get-app-locales <PACKAGE_NAME> [--user <USER_ID>]");
pw.println(" Get the locales for the specified app.");
pw.println(" --user <USER_ID>: get for the given user, "
+ "the current user is used when unspecified.");
+ pw.println(
+ " set-app-localeconfig <PACKAGE_NAME> [--user <USER_ID>] [--locales "
+ + "<LOCALE_INFO>]");
+ pw.println(" Set the override LocaleConfig for the specified app.");
+ pw.println(" --user <USER_ID>: apply for the given user, "
+ + "the current user is used when unspecified.");
+ pw.println(" --locales <LOCALE_INFO>: The language tags of locale to be included "
+ + "as a single String separated by commas.");
+ pw.println(" eg. en,en-US,hi ");
+ pw.println(" Empty locale list is used when typing a 'empty' word");
+ pw.println(" NULL is used when unspecified.");
+ pw.println(" get-app-localeconfig <PACKAGE_NAME> [--user <USER_ID>]");
+ pw.println(" Get the locales within the override LocaleConfig for the specified app.");
+ pw.println(" --user <USER_ID>: get for the given user, "
+ + "the current user is used when unspecified.");
}
private int runSetAppLocales() {
@@ -155,6 +175,106 @@
return 0;
}
+ private int runSetAppOverrideLocaleConfig() {
+ String packageName = getNextArg();
+
+ if (packageName != null) {
+ int userId = ActivityManager.getCurrentUser();
+ LocaleList locales = null;
+ do {
+ String option = getNextOption();
+ if (option == null) {
+ break;
+ }
+ switch (option) {
+ case "--user": {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ break;
+ }
+ case "--locales": {
+ locales = parseOverrideLocales();
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException("Unknown option: " + option);
+ }
+ }
+ } while (true);
+
+ try {
+ LocaleConfig localeConfig = locales == null ? null : new LocaleConfig(locales);
+ mBinderService.setOverrideLocaleConfig(packageName, userId, localeConfig);
+ } catch (RemoteException e) {
+ getOutPrintWriter().println("Remote Exception: " + e);
+ }
+ } else {
+ final PrintWriter err = getErrPrintWriter();
+ err.println("Error: no package specified");
+ return -1;
+ }
+ return 0;
+ }
+
+ private int runGetAppOverrideLocaleConfig() {
+ String packageName = getNextArg();
+
+ if (packageName != null) {
+ int userId = ActivityManager.getCurrentUser();
+ do {
+ String option = getNextOption();
+ if (option == null) {
+ break;
+ }
+ if ("--user".equals(option)) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ break;
+ } else {
+ throw new IllegalArgumentException("Unknown option: " + option);
+ }
+ } while (true);
+ try {
+ LocaleConfig localeConfig = mBinderService.getOverrideLocaleConfig(packageName,
+ userId);
+ if (localeConfig == null) {
+ getOutPrintWriter().println("LocaleConfig for " + packageName
+ + " for user " + userId + " is null");
+ } else {
+ LocaleList locales = localeConfig.getSupportedLocales();
+ if (locales == null) {
+ getOutPrintWriter().println(
+ "Locales within the LocaleConfig for " + packageName + " for user "
+ + userId + " are null");
+ } else {
+ getOutPrintWriter().println(
+ "Locales within the LocaleConfig for " + packageName + " for user "
+ + userId + " are [" + locales.toLanguageTags() + "]");
+ }
+ }
+ } catch (RemoteException e) {
+ getOutPrintWriter().println("Remote Exception: " + e);
+ }
+ } else {
+ final PrintWriter err = getErrPrintWriter();
+ err.println("Error: no package specified");
+ return -1;
+ }
+ return 0;
+ }
+
+ private LocaleList parseOverrideLocales() {
+ String locales = getNextArg();
+ if (locales == null) {
+ return null;
+ } else if (locales.equals("empty")) {
+ return LocaleList.getEmptyLocaleList();
+ } else {
+ if (locales.startsWith("-")) {
+ throw new IllegalArgumentException("Unknown locales: " + locales);
+ }
+ return LocaleList.forLanguageTags(locales);
+ }
+ }
+
private LocaleList parseLocales() {
String locales = getNextArg();
if (locales == null) {
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 5f39a52..90f2a6a 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -883,7 +883,6 @@
* Migrate the credential for the FRP credential owner user if the following are satisfied:
* - the user has a secure credential
* - the FRP credential is not set up
- * - the credential is based on a synthetic password.
*/
private void migrateFrpCredential() {
if (mStorage.readPersistentDataBlock() != PersistentData.NONE) {
@@ -892,15 +891,13 @@
for (UserInfo userInfo : mUserManager.getUsers()) {
if (userOwnsFrpCredential(mContext, userInfo) && isUserSecure(userInfo.id)) {
synchronized (mSpManager) {
- if (isSyntheticPasswordBasedCredentialLocked(userInfo.id)) {
- int actualQuality = (int) getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userInfo.id);
+ int actualQuality = (int) getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userInfo.id);
- mSpManager.migrateFrpPasswordLocked(
- getCurrentLskfBasedProtectorId(userInfo.id),
- userInfo,
- redactActualQualityToMostLenientEquivalentQuality(actualQuality));
- }
+ mSpManager.migrateFrpPasswordLocked(
+ getCurrentLskfBasedProtectorId(userInfo.id),
+ userInfo,
+ redactActualQualityToMostLenientEquivalentQuality(actualQuality));
}
return;
}
@@ -941,13 +938,9 @@
int serialNumber = mEarlyCreatedUsers.valueAt(i);
removeStateForReusedUserIdIfNecessary(userId, serialNumber);
- synchronized (mSpManager) {
- if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
- Slogf.i(TAG, "Creating locksettings state for user %d now that boot "
- + "is complete", userId);
- initializeSyntheticPassword(userId);
- }
- }
+ Slogf.i(TAG, "Creating locksettings state for user %d now that boot is complete",
+ userId);
+ initializeSyntheticPassword(userId);
}
mEarlyCreatedUsers = null; // no longer needed
@@ -1234,16 +1227,17 @@
return getFrpCredentialType();
}
synchronized (mSpManager) {
- if (isSyntheticPasswordBasedCredentialLocked(userId)) {
- final long protectorId = getCurrentLskfBasedProtectorId(userId);
- int rawType = mSpManager.getCredentialType(protectorId, userId);
- if (rawType != CREDENTIAL_TYPE_PASSWORD_OR_PIN) {
- return rawType;
- }
- return pinOrPasswordQualityToCredentialType(getKeyguardStoredQuality(userId));
+ final long protectorId = getCurrentLskfBasedProtectorId(userId);
+ if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
+ // Only possible for new users during early boot (before onThirdPartyAppsStarted())
+ return CREDENTIAL_TYPE_NONE;
}
+ int rawType = mSpManager.getCredentialType(protectorId, userId);
+ if (rawType != CREDENTIAL_TYPE_PASSWORD_OR_PIN) {
+ return rawType;
+ }
+ return pinOrPasswordQualityToCredentialType(getKeyguardStoredQuality(userId));
}
- return CREDENTIAL_TYPE_NONE;
}
private int getFrpCredentialType() {
@@ -2167,10 +2161,6 @@
VerifyCredentialResponse response;
synchronized (mSpManager) {
- if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
- Slog.wtf(TAG, "Unexpected credential type, should be SP based.");
- return VerifyCredentialResponse.ERROR;
- }
if (userId == USER_FRP) {
return mSpManager.verifyFrpCredential(getGateKeeperService(), credential,
progressCallback);
@@ -2424,16 +2414,20 @@
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
enforceShell();
- final int origPid = Binder.getCallingPid();
- final int origUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
- Slog.e(TAG, "Caller pid " + origPid + " Caller uid " + origUid);
+ // Don't log arguments other than the first one (the command name), since they might contain
+ // secrets that must not be written to the log.
+ Slogf.i(TAG, "Executing shell command '%s'; callingPid=%d, callingUid=%d",
+ ArrayUtils.isEmpty(args) ? "" : args[0], callingPid, callingUid);
+
// The original identity is an opaque integer.
final long origId = Binder.clearCallingIdentity();
try {
final LockSettingsShellCommand command =
- new LockSettingsShellCommand(new LockPatternUtils(mContext), mContext, origPid,
- origUid);
+ new LockSettingsShellCommand(new LockPatternUtils(mContext), mContext,
+ callingPid, callingUid);
command.exec(this, in, out, err, args, callback, resultReceiver);
} finally {
Binder.restoreCallingIdentity(origId);
@@ -2672,15 +2666,6 @@
setLong(LSKF_LAST_CHANGED_TIME_KEY, System.currentTimeMillis(), userId);
}
- private boolean isSyntheticPasswordBasedCredentialLocked(int userId) {
- if (userId == USER_FRP) {
- final int type = mStorage.readPersistentDataBlock().type;
- return type == PersistentData.TYPE_SP || type == PersistentData.TYPE_SP_WEAVER;
- }
- long protectorId = getCurrentLskfBasedProtectorId(userId);
- return protectorId != SyntheticPasswordManager.NULL_PROTECTOR_ID;
- }
-
/**
* Stores the gatekeeper password temporarily.
* @param gatekeeperPassword unlocked upon successful Synthetic Password
@@ -2888,10 +2873,6 @@
}
}
synchronized (mSpManager) {
- if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
- Slog.w(TAG, "Synthetic password not enabled");
- return null;
- }
long protectorId = getCurrentLskfBasedProtectorId(userId);
AuthenticationResult auth = mSpManager.unlockLskfBasedProtector(
getGateKeeperService(), protectorId, currentCredential, userId, null);
@@ -3218,9 +3199,7 @@
// Disable escrow token permanently on all other device/user types.
Slog.i(TAG, "Disabling escrow token on user " + userId);
- if (isSyntheticPasswordBasedCredentialLocked(userId)) {
- mSpManager.destroyEscrowData(userId);
- }
+ mSpManager.destroyEscrowData(userId);
}
/**
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index acd7cc1..66ce429 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -24,13 +24,14 @@
import android.app.admin.PasswordMetrics;
import android.content.Context;
import android.content.pm.UserInfo;
-import android.hardware.weaver.V1_0.IWeaver;
-import android.hardware.weaver.V1_0.WeaverConfig;
-import android.hardware.weaver.V1_0.WeaverReadResponse;
-import android.hardware.weaver.V1_0.WeaverReadStatus;
-import android.hardware.weaver.V1_0.WeaverStatus;
+import android.hardware.weaver.IWeaver;
+import android.hardware.weaver.WeaverConfig;
+import android.hardware.weaver.WeaverReadResponse;
+import android.hardware.weaver.WeaverReadStatus;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
import android.os.UserManager;
import android.provider.Settings;
import android.security.GateKeeper;
@@ -60,7 +61,6 @@
import java.nio.ByteBuffer;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
@@ -464,37 +464,67 @@
}
@VisibleForTesting
- protected IWeaver getWeaverService() throws RemoteException {
+ protected android.hardware.weaver.V1_0.IWeaver getWeaverHidlService() throws RemoteException {
try {
- return IWeaver.getService(/* retry */ true);
+ return android.hardware.weaver.V1_0.IWeaver.getService(/* retry */ true);
} catch (NoSuchElementException e) {
- Slog.i(TAG, "Device does not support weaver");
return null;
}
}
+ private IWeaver getWeaverService() {
+ // Try to get the AIDL service first
+ try {
+ IWeaver aidlWeaver = IWeaver.Stub.asInterface(
+ ServiceManager.waitForDeclaredService(IWeaver.DESCRIPTOR + "/default"));
+ if (aidlWeaver != null) {
+ Slog.i(TAG, "Using AIDL weaver service");
+ return aidlWeaver;
+ }
+ } catch (SecurityException e) {
+ Slog.w(TAG, "Does not have permissions to get AIDL weaver service");
+ }
+
+ // If the AIDL service can't be found, look for the HIDL service
+ try {
+ android.hardware.weaver.V1_0.IWeaver hidlWeaver = getWeaverHidlService();
+ if (hidlWeaver != null) {
+ Slog.i(TAG, "Using HIDL weaver service");
+ return new WeaverHidlWrapper(hidlWeaver);
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to get HIDL weaver service.", e);
+ }
+ Slog.w(TAG, "Device does not support weaver");
+ return null;
+ }
+
public synchronized void initWeaverService() {
if (mWeaver != null) {
return;
}
- try {
- mWeaverConfig = null;
- mWeaver = getWeaverService();
- if (mWeaver != null) {
- mWeaver.getConfig((int status, WeaverConfig config) -> {
- if (status == WeaverStatus.OK && config.slots > 0) {
- mWeaverConfig = config;
- } else {
- Slog.e(TAG, "Failed to get weaver config, status " + status
- + " slots: " + config.slots);
- mWeaver = null;
- }
- });
- mPasswordSlotManager.refreshActiveSlots(getUsedWeaverSlots());
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to get weaver service", e);
+
+ IWeaver weaver = getWeaverService();
+ if (weaver == null) {
+ return;
}
+
+ // Get the config
+ WeaverConfig weaverConfig = null;
+ try {
+ weaverConfig = weaver.getConfig();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get weaver config", e);
+ }
+ if (weaverConfig == null || weaverConfig.slots <= 0) {
+ Slog.e(TAG, "Failed to initialize weaver config");
+ return;
+ }
+
+ mWeaver = weaver;
+ mWeaverConfig = weaverConfig;
+ mPasswordSlotManager.refreshActiveSlots(getUsedWeaverSlots());
+ Slog.i(TAG, "Weaver service initialized");
}
private synchronized boolean isWeaverAvailable() {
@@ -525,19 +555,31 @@
value = secureRandom(mWeaverConfig.valueSize);
}
try {
- int writeStatus = mWeaver.write(slot, toByteArrayList(key), toByteArrayList(value));
- if (writeStatus != WeaverStatus.OK) {
- Slog.e(TAG, "weaver write failed, slot: " + slot + " status: " + writeStatus);
- return null;
- }
+ mWeaver.write(slot, key, value);
} catch (RemoteException e) {
- Slog.e(TAG, "weaver write failed", e);
+ Slog.e(TAG, "weaver write binder call failed, slot: " + slot, e);
+ return null;
+ } catch (ServiceSpecificException e) {
+ Slog.e(TAG, "weaver write failed, slot: " + slot, e);
return null;
}
return value;
}
/**
+ * Create a VerifyCredentialResponse from a timeout base on the WeaverReadResponse.
+ * This checks the received timeout(long) to make sure it sure it fits in an int before
+ * using it. If it doesn't fit, we use Integer.MAX_VALUE.
+ */
+ private static VerifyCredentialResponse responseFromTimeout(WeaverReadResponse response) {
+ int timeout =
+ response.timeout > Integer.MAX_VALUE || response.timeout < 0
+ ? Integer.MAX_VALUE
+ : (int) response.timeout;
+ return VerifyCredentialResponse.fromTimeout(timeout);
+ }
+
+ /**
* Verify the supplied key against a weaver slot, returning a response indicating whether
* the verification is successful, throttled or failed. If successful, the bound secret
* is also returned.
@@ -551,46 +593,39 @@
} else if (key.length != mWeaverConfig.keySize) {
throw new IllegalArgumentException("Invalid key size for weaver");
}
- final VerifyCredentialResponse[] response = new VerifyCredentialResponse[1];
+ final WeaverReadResponse readResponse;
try {
- mWeaver.read(slot, toByteArrayList(key),
- (int status, WeaverReadResponse readResponse) -> {
- switch (status) {
- case WeaverReadStatus.OK:
- response[0] = new VerifyCredentialResponse.Builder().setGatekeeperHAT(
- fromByteArrayList(readResponse.value)).build();
- break;
- case WeaverReadStatus.THROTTLE:
- response[0] = VerifyCredentialResponse
- .fromTimeout(readResponse.timeout);
- Slog.e(TAG, "weaver read failed (THROTTLE), slot: " + slot);
- break;
- case WeaverReadStatus.INCORRECT_KEY:
- if (readResponse.timeout == 0) {
- response[0] = VerifyCredentialResponse.ERROR;
- Slog.e(TAG, "weaver read failed (INCORRECT_KEY), slot: " + slot);
- } else {
- response[0] = VerifyCredentialResponse
- .fromTimeout(readResponse.timeout);
- Slog.e(TAG, "weaver read failed (INCORRECT_KEY/THROTTLE), slot: "
- + slot);
- }
- break;
- case WeaverReadStatus.FAILED:
- response[0] = VerifyCredentialResponse.ERROR;
- Slog.e(TAG, "weaver read failed (FAILED), slot: " + slot);
- break;
- default:
- response[0] = VerifyCredentialResponse.ERROR;
- Slog.e(TAG, "weaver read unknown status " + status + ", slot: " + slot);
- break;
- }
- });
+ readResponse = mWeaver.read(slot, key);
} catch (RemoteException e) {
- response[0] = VerifyCredentialResponse.ERROR;
Slog.e(TAG, "weaver read failed, slot: " + slot, e);
+ return VerifyCredentialResponse.ERROR;
}
- return response[0];
+
+ switch (readResponse.status) {
+ case WeaverReadStatus.OK:
+ return new VerifyCredentialResponse.Builder()
+ .setGatekeeperHAT(readResponse.value)
+ .build();
+ case WeaverReadStatus.THROTTLE:
+ Slog.e(TAG, "weaver read failed (THROTTLE), slot: " + slot);
+ return responseFromTimeout(readResponse);
+ case WeaverReadStatus.INCORRECT_KEY:
+ if (readResponse.timeout == 0) {
+ Slog.e(TAG, "weaver read failed (INCORRECT_KEY), slot: " + slot);
+ return VerifyCredentialResponse.ERROR;
+ } else {
+ Slog.e(TAG, "weaver read failed (INCORRECT_KEY/THROTTLE), slot: " + slot);
+ return responseFromTimeout(readResponse);
+ }
+ case WeaverReadStatus.FAILED:
+ Slog.e(TAG, "weaver read failed (FAILED), slot: " + slot);
+ return VerifyCredentialResponse.ERROR;
+ default:
+ Slog.e(TAG,
+ "weaver read unknown status " + readResponse.status
+ + ", slot: " + slot);
+ return VerifyCredentialResponse.ERROR;
+ }
}
public void removeUser(IGateKeeperService gatekeeper, int userId) {
@@ -1660,22 +1695,6 @@
native long nativeSidFromPasswordHandle(byte[] handle);
- protected static ArrayList<Byte> toByteArrayList(byte[] data) {
- ArrayList<Byte> result = new ArrayList<Byte>(data.length);
- for (int i = 0; i < data.length; i++) {
- result.add(data[i]);
- }
- return result;
- }
-
- protected static byte[] fromByteArrayList(ArrayList<Byte> data) {
- byte[] result = new byte[data.size()];
- for (int i = 0; i < data.size(); i++) {
- result[i] = data.get(i);
- }
- return result;
- }
-
@VisibleForTesting
static byte[] bytesToHex(byte[] bytes) {
return HexEncoding.encodeToString(bytes).getBytes();
diff --git a/services/core/java/com/android/server/locksettings/WeaverHidlWrapper.java b/services/core/java/com/android/server/locksettings/WeaverHidlWrapper.java
new file mode 100644
index 0000000..9d93c3d
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/WeaverHidlWrapper.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings;
+
+import android.hardware.weaver.V1_0.IWeaver;
+import android.hardware.weaver.V1_0.WeaverConfig;
+import android.hardware.weaver.V1_0.WeaverReadResponse;
+import android.hardware.weaver.V1_0.WeaverReadStatus;
+import android.hardware.weaver.V1_0.WeaverStatus;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Slog;
+
+import java.util.ArrayList;
+
+/**
+ * Implement the AIDL IWeaver interface wrapping the HIDL implementation
+ */
+class WeaverHidlWrapper implements android.hardware.weaver.IWeaver {
+ private static final String TAG = "WeaverHidlWrapper";
+ private final IWeaver mImpl;
+
+ WeaverHidlWrapper(IWeaver impl) {
+ mImpl = impl;
+ }
+
+ private static ArrayList<Byte> toByteArrayList(byte[] data) {
+ ArrayList<Byte> result = new ArrayList<Byte>(data.length);
+ for (int i = 0; i < data.length; i++) {
+ result.add(data[i]);
+ }
+ return result;
+ }
+
+ private static byte[] fromByteArrayList(ArrayList<Byte> data) {
+ byte[] result = new byte[data.size()];
+ for (int i = 0; i < data.size(); i++) {
+ result[i] = data.get(i);
+ }
+ return result;
+ }
+
+ @Override
+ public String getInterfaceHash() {
+ // We do not require the interface hash as the client.
+ throw new UnsupportedOperationException(
+ "WeaverHidlWrapper does not support getInterfaceHash");
+ }
+ @Override
+ public int getInterfaceVersion() {
+ // Supports only V2 which is at feature parity.
+ return 2;
+ }
+ @Override
+ public android.os.IBinder asBinder() {
+ // There is no IHwBinder to IBinder. Not required as the client.
+ throw new UnsupportedOperationException("WeaverHidlWrapper does not support asBinder");
+ }
+
+ @Override
+ public android.hardware.weaver.WeaverConfig getConfig() throws RemoteException {
+ final WeaverConfig[] res = new WeaverConfig[1];
+ mImpl.getConfig((int status, WeaverConfig config) -> {
+ if (status == WeaverStatus.OK && config.slots > 0) {
+ res[0] = config;
+ } else {
+ res[0] = null;
+ Slog.e(TAG,
+ "Failed to get HIDL weaver config. status: " + status
+ + ", slots: " + config.slots);
+ }
+ });
+
+ if (res[0] == null) {
+ return null;
+ }
+ android.hardware.weaver.WeaverConfig config = new android.hardware.weaver.WeaverConfig();
+ config.slots = res[0].slots;
+ config.keySize = res[0].keySize;
+ config.valueSize = res[0].valueSize;
+ return config;
+ }
+
+ @Override
+ public android.hardware.weaver.WeaverReadResponse read(int slotId, byte[] key)
+ throws RemoteException {
+ final WeaverReadResponse[] res = new WeaverReadResponse[1];
+ final int[] status = new int[1];
+ mImpl.read(
+ slotId, toByteArrayList(key), (int inStatus, WeaverReadResponse readResponse) -> {
+ status[0] = inStatus;
+ res[0] = readResponse;
+ });
+
+ android.hardware.weaver.WeaverReadResponse aidlRes =
+ new android.hardware.weaver.WeaverReadResponse();
+ switch (status[0]) {
+ case WeaverReadStatus.OK:
+ aidlRes.status = android.hardware.weaver.WeaverReadStatus.OK;
+ break;
+ case WeaverReadStatus.THROTTLE:
+ aidlRes.status = android.hardware.weaver.WeaverReadStatus.THROTTLE;
+ break;
+ case WeaverReadStatus.INCORRECT_KEY:
+ aidlRes.status = android.hardware.weaver.WeaverReadStatus.INCORRECT_KEY;
+ break;
+ case WeaverReadStatus.FAILED:
+ aidlRes.status = android.hardware.weaver.WeaverReadStatus.FAILED;
+ break;
+ default:
+ aidlRes.status = android.hardware.weaver.WeaverReadStatus.FAILED;
+ break;
+ }
+ if (res[0] != null) {
+ aidlRes.timeout = res[0].timeout;
+ aidlRes.value = fromByteArrayList(res[0].value);
+ }
+ return aidlRes;
+ }
+
+ @Override
+ public void write(int slotId, byte[] key, byte[] value) throws RemoteException {
+ int writeStatus = mImpl.write(slotId, toByteArrayList(key), toByteArrayList(value));
+ if (writeStatus != WeaverStatus.OK) {
+ throw new ServiceSpecificException(
+ android.hardware.weaver.IWeaver.STATUS_FAILED, "Failed IWeaver.write call");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/logcat/TEST_MAPPING b/services/core/java/com/android/server/logcat/TEST_MAPPING
new file mode 100644
index 0000000..f4b13a0
--- /dev/null
+++ b/services/core/java/com/android/server/logcat/TEST_MAPPING
@@ -0,0 +1,21 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {"include-filter": "com.android.server.logcat"},
+ {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {"include-filter": "com.android.server.logcat"}
+ ]
+ }
+ ]
+}
+
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 79f2b3f..9022a885 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -51,6 +51,7 @@
import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.content.om.OverlayManagerTransaction;
+import android.content.om.OverlayManagerTransaction.Request;
import android.content.om.OverlayableInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManagerInternal;
@@ -107,6 +108,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -978,7 +980,8 @@
synchronized (mLock) {
// execute the requests (as calling user)
Set<UserPackage> affectedPackagesToUpdate = null;
- for (final OverlayManagerTransaction.Request request : transaction) {
+ for (Iterator<Request> it = transaction.getRequests(); it.hasNext(); ) {
+ Request request = it.next();
affectedPackagesToUpdate = CollectionUtils.addAll(affectedPackagesToUpdate,
executeRequest(request));
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 77925c0..bf6bb22 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1597,8 +1597,6 @@
ServiceManager.addService("package", iPackageManager);
final PackageManagerNative pmn = new PackageManagerNative(m);
ServiceManager.addService("package_native", pmn);
- LocalManagerRegistry.addManager(PackageManagerLocal.class,
- new PackageManagerLocalImpl(m));
return m;
}
@@ -1797,6 +1795,8 @@
// Expose private service for system components to use.
LocalServices.addService(PackageManagerInternal.class, new PackageManagerInternalImpl());
+ LocalManagerRegistry.addManager(PackageManagerLocal.class,
+ new PackageManagerLocalImpl(this));
LocalServices.addService(TestUtilityService.class, this);
mTestUtilityService = LocalServices.getService(TestUtilityService.class);
mUserManager = injector.getUserManagerService();
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index f9c0deb..d2d3c3c 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -683,7 +683,7 @@
if (bp == null) {
return;
}
- if (bp.isDynamic()) {
+ if (!bp.isDynamic()) {
// TODO: switch this back to SecurityException
Slog.wtf(TAG, "Not allowed to modify non-dynamic permission "
+ permName);
@@ -1299,13 +1299,13 @@
}
}
}
+ }
- if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) {
- if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
- throw new SecurityException("Modifying installer allowlist requires"
- + " being installer on record or "
- + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
- }
+ if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) {
+ if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
+ throw new SecurityException("Modifying installer allowlist requires"
+ + " being installer on record or "
+ + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
}
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 458bfeb..884d5d6 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -4251,8 +4251,8 @@
}
private boolean forceSuspendInternal(int uid) {
- try {
- synchronized (mLock) {
+ synchronized (mLock) {
+ try {
mForceSuspendActive = true;
// Place the system in an non-interactive state
for (int idx = 0; idx < mPowerGroups.size(); idx++) {
@@ -4262,16 +4262,14 @@
// Disable all the partial wake locks as well
updateWakeLockDisabledStatesLocked();
- }
- Slog.i(TAG, "Force-Suspending (uid " + uid + ")...");
- boolean success = mNativeWrapper.nativeForceSuspend();
- if (!success) {
- Slog.i(TAG, "Force-Suspending failed in native.");
- }
- return success;
- } finally {
- synchronized (mLock) {
+ Slog.i(TAG, "Force-Suspending (uid " + uid + ")...");
+ boolean success = mNativeWrapper.nativeForceSuspend();
+ if (!success) {
+ Slog.i(TAG, "Force-Suspending failed in native.");
+ }
+ return success;
+ } finally {
mForceSuspendActive = false;
// Re-enable wake locks once again.
updateWakeLockDisabledStatesLocked();
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 952fcdc..a9a1d5e 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -189,6 +189,8 @@
private static native void nativeSendHint(long halPtr, int hint);
+ private static native void nativeSetThreads(long halPtr, int[] tids);
+
private static native long nativeGetHintSessionPreferredRate();
/** Wrapper for HintManager.nativeInit */
@@ -237,6 +239,11 @@
public long halGetHintSessionPreferredRate() {
return nativeGetHintSessionPreferredRate();
}
+
+ /** Wrapper for HintManager.nativeSetThreads */
+ public void halSetThreads(long halPtr, int[] tids) {
+ nativeSetThreads(halPtr, tids);
+ }
}
@VisibleForTesting
@@ -400,6 +407,18 @@
}
@Override
+ public void setHintSessionThreads(@NonNull IHintSession hintSession, @NonNull int[] tids) {
+ AppHintSession appHintSession = (AppHintSession) hintSession;
+ appHintSession.setThreads(tids);
+ }
+
+ @Override
+ public int[] getHintSessionThreadIds(@NonNull IHintSession hintSession) {
+ AppHintSession appHintSession = (AppHintSession) hintSession;
+ return appHintSession.getThreadIds();
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
return;
@@ -434,11 +453,12 @@
final class AppHintSession extends IHintSession.Stub implements IBinder.DeathRecipient {
protected final int mUid;
protected final int mPid;
- protected final int[] mThreadIds;
+ protected int[] mThreadIds;
protected final IBinder mToken;
protected long mHalSessionPtr;
protected long mTargetDurationNanos;
protected boolean mUpdateAllowed;
+ protected int[] mNewThreadIds;
protected AppHintSession(
int uid, int pid, int[] threadIds, IBinder token,
@@ -541,6 +561,38 @@
}
}
+ public void setThreads(@NonNull int[] tids) {
+ synchronized (mLock) {
+ if (mHalSessionPtr == 0) {
+ return;
+ }
+ if (tids.length == 0) {
+ throw new IllegalArgumentException("Thread id list can't be empty.");
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid());
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (!checkTidValid(callingUid, callingTgid, tids)) {
+ throw new SecurityException("Some tid doesn't belong to the application.");
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ if (!updateHintAllowed()) {
+ Slogf.v(TAG, "update hint not allowed, storing tids.");
+ mNewThreadIds = tids;
+ return;
+ }
+ mNativeWrapper.halSetThreads(mHalSessionPtr, tids);
+ mThreadIds = tids;
+ }
+ }
+
+ public int[] getThreadIds() {
+ return mThreadIds;
+ }
+
private void onProcStateChanged() {
updateHintAllowed();
}
@@ -556,6 +608,11 @@
synchronized (mLock) {
if (mHalSessionPtr == 0) return;
mNativeWrapper.halResumeHintSession(mHalSessionPtr);
+ if (mNewThreadIds != null) {
+ mNativeWrapper.halSetThreads(mHalSessionPtr, mNewThreadIds);
+ mThreadIds = mNewThreadIds;
+ mNewThreadIds = null;
+ }
}
}
diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java
new file mode 100644
index 0000000..868f34b
--- /dev/null
+++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.rkp;
+
+import android.os.CancellationSignal;
+import android.os.OperationCanceledException;
+import android.os.OutcomeReceiver;
+import android.security.rkp.IGetKeyCallback;
+import android.security.rkp.IRegistration;
+import android.security.rkp.service.RegistrationProxy;
+import android.security.rkp.service.RemotelyProvisionedKey;
+import android.util.Log;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+
+/**
+ * Implements android.security.rkp.IRegistration as a thin wrapper around the java code
+ * exported by com.android.rkp.
+ *
+ * @hide
+ */
+final class RemoteProvisioningRegistration extends IRegistration.Stub {
+ static final String TAG = RemoteProvisioningService.TAG;
+ private final ConcurrentHashMap<IGetKeyCallback, CancellationSignal> mOperations =
+ new ConcurrentHashMap<>();
+ private final RegistrationProxy mRegistration;
+ private final Executor mExecutor;
+
+ private class GetKeyReceiver implements OutcomeReceiver<RemotelyProvisionedKey, Exception> {
+ IGetKeyCallback mCallback;
+ GetKeyReceiver(IGetKeyCallback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void onResult(RemotelyProvisionedKey result) {
+ mOperations.remove(mCallback);
+ Log.i(TAG, "Successfully fetched key for client " + mCallback.hashCode());
+ android.security.rkp.RemotelyProvisionedKey parcelable =
+ new android.security.rkp.RemotelyProvisionedKey();
+ parcelable.keyBlob = result.getKeyBlob();
+ parcelable.encodedCertChain = result.getEncodedCertChain();
+ wrapCallback(() -> mCallback.onSuccess(parcelable));
+ }
+
+ @Override
+ public void onError(Exception e) {
+ mOperations.remove(mCallback);
+ if (e instanceof OperationCanceledException) {
+ Log.i(TAG, "Operation cancelled for client " + mCallback.hashCode());
+ wrapCallback(mCallback::onCancel);
+ } else {
+ Log.e(TAG, "Error fetching key for client " + mCallback.hashCode(), e);
+ wrapCallback(() -> mCallback.onError(e.getMessage()));
+ }
+ }
+ }
+
+ RemoteProvisioningRegistration(RegistrationProxy registration, Executor executor) {
+ mRegistration = registration;
+ mExecutor = executor;
+ }
+
+ @Override
+ public void getKey(int keyId, IGetKeyCallback callback) {
+ CancellationSignal cancellationSignal = new CancellationSignal();
+ if (mOperations.putIfAbsent(callback, cancellationSignal) != null) {
+ Log.e(TAG, "Client can only request one call at a time " + callback.hashCode());
+ throw new IllegalArgumentException(
+ "Callback is already associated with an existing operation: "
+ + callback.hashCode());
+ }
+
+ try {
+ Log.i(TAG, "Fetching key " + keyId + " for client " + callback.hashCode());
+ mRegistration.getKeyAsync(keyId, cancellationSignal, mExecutor,
+ new GetKeyReceiver(callback));
+ } catch (Exception e) {
+ Log.e(TAG, "getKeyAsync threw an exception for client " + callback.hashCode(), e);
+ mOperations.remove(callback);
+ wrapCallback(() -> callback.onError(e.getMessage()));
+ }
+ }
+
+ @Override
+ public void cancelGetKey(IGetKeyCallback callback) {
+ CancellationSignal cancellationSignal = mOperations.remove(callback);
+ if (cancellationSignal == null) {
+ throw new IllegalArgumentException(
+ "Invalid client in cancelGetKey: " + callback.hashCode());
+ }
+
+ Log.i(TAG, "Requesting cancellation for client " + callback.hashCode());
+ cancellationSignal.cancel();
+ }
+
+ @Override
+ public void storeUpgradedKey(byte[] oldKeyBlob, byte[] newKeyBlob) {
+ // TODO(b/262748535)
+ Log.e(TAG, "RegistrationBinder.storeUpgradedKey NOT YET IMPLEMENTED");
+ }
+
+ interface CallbackRunner {
+ void run() throws Exception;
+ }
+
+ private void wrapCallback(CallbackRunner callback) {
+ // Exceptions resulting from notifications to IGetKeyCallback objects can only be logged,
+ // since getKey execution is asynchronous, and there's no way for an exception to be
+ // properly handled up the stack.
+ try {
+ callback.run();
+ } catch (Exception e) {
+ Log.e(TAG, "Error invoking callback on client binder", e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java
index 65a4b38..cd1a968 100644
--- a/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java
+++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java
@@ -20,9 +20,7 @@
import android.os.Binder;
import android.os.OutcomeReceiver;
import android.os.RemoteException;
-import android.security.rkp.IGetKeyCallback;
import android.security.rkp.IGetRegistrationCallback;
-import android.security.rkp.IRegistration;
import android.security.rkp.IRemoteProvisioning;
import android.security.rkp.service.RegistrationProxy;
import android.util.Log;
@@ -30,6 +28,7 @@
import com.android.server.SystemService;
import java.time.Duration;
+import java.util.concurrent.Executor;
/**
* Implements the remote provisioning system service. This service is backed by a mainline
@@ -43,6 +42,35 @@
private static final Duration CREATE_REGISTRATION_TIMEOUT = Duration.ofSeconds(10);
private final RemoteProvisioningImpl mBinderImpl = new RemoteProvisioningImpl();
+ private static class RegistrationReceiver implements
+ OutcomeReceiver<RegistrationProxy, Exception> {
+ private final Executor mExecutor;
+ private final IGetRegistrationCallback mCallback;
+
+ RegistrationReceiver(Executor executor, IGetRegistrationCallback callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onResult(RegistrationProxy registration) {
+ try {
+ mCallback.onSuccess(new RemoteProvisioningRegistration(registration, mExecutor));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling success callback " + mCallback.hashCode(), e);
+ }
+ }
+
+ @Override
+ public void onError(Exception error) {
+ try {
+ mCallback.onError(error.toString());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling error callback " + mCallback.hashCode(), e);
+ }
+ }
+ }
+
/** @hide */
public RemoteProvisioningService(Context context) {
super(context);
@@ -54,73 +82,20 @@
}
private final class RemoteProvisioningImpl extends IRemoteProvisioning.Stub {
-
- final class RegistrationBinder extends IRegistration.Stub {
- static final String TAG = RemoteProvisioningService.TAG;
- private final RegistrationProxy mRegistration;
-
- RegistrationBinder(RegistrationProxy registration) {
- mRegistration = registration;
- }
-
- @Override
- public void getKey(int keyId, IGetKeyCallback callback) {
- Log.e(TAG, "RegistrationBinder.getKey NOT YET IMPLEMENTED");
- }
-
- @Override
- public void cancelGetKey(IGetKeyCallback callback) {
- Log.e(TAG, "RegistrationBinder.cancelGetKey NOT YET IMPLEMENTED");
- }
-
- @Override
- public void storeUpgradedKey(byte[] oldKeyBlob, byte[] newKeyBlob) {
- Log.e(TAG, "RegistrationBinder.storeUpgradedKey NOT YET IMPLEMENTED");
- }
- }
-
@Override
public void getRegistration(String irpcName, IGetRegistrationCallback callback)
throws RemoteException {
final int callerUid = Binder.getCallingUidOrThrow();
final long callingIdentity = Binder.clearCallingIdentity();
+ final Executor executor = getContext().getMainExecutor();
try {
Log.i(TAG, "getRegistration(" + irpcName + ")");
- RegistrationProxy.createAsync(
- getContext(),
- callerUid,
- irpcName,
- CREATE_REGISTRATION_TIMEOUT,
- getContext().getMainExecutor(),
- new OutcomeReceiver<>() {
- @Override
- public void onResult(RegistrationProxy registration) {
- try {
- callback.onSuccess(new RegistrationBinder(registration));
- } catch (RemoteException e) {
- Log.e(TAG, "Error calling success callback", e);
- }
- }
-
- @Override
- public void onError(Exception error) {
- try {
- callback.onError(error.toString());
- } catch (RemoteException e) {
- Log.e(TAG, "Error calling error callback", e);
- }
- }
- });
+ RegistrationProxy.createAsync(getContext(), callerUid, irpcName,
+ CREATE_REGISTRATION_TIMEOUT, executor,
+ new RegistrationReceiver(executor, callback));
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
}
-
- @Override
- public void cancelGetRegistration(IGetRegistrationCallback callback)
- throws RemoteException {
- Log.i(TAG, "cancelGetRegistration()");
- callback.onError("cancelGetRegistration not yet implemented");
- }
}
}
diff --git a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
index f4b335e..411b2fa 100644
--- a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
+++ b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
@@ -19,46 +19,49 @@
import static com.android.internal.infra.AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.RemoteException;
import android.speech.IRecognitionListener;
import android.speech.IRecognitionService;
import android.speech.IRecognitionSupportCallback;
import android.speech.RecognitionService;
import android.speech.SpeechRecognizer;
+import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.infra.ServiceConnector;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecognitionService> {
private static final String TAG = RemoteSpeechRecognitionService.class.getSimpleName();
private static final boolean DEBUG = false;
+ /** Maximum number of clients connected to this object at the same time. */
+ private static final int MAX_CONCURRENT_CLIENTS = 100;
+
private final Object mLock = new Object();
private boolean mConnected = false;
- @Nullable
- private IRecognitionListener mListener;
-
- @Nullable
+ /** Map containing info about connected clients indexed by the their listeners. */
@GuardedBy("mLock")
- private DelegatingListener mDelegatingListener;
+ private final Map<IBinder, ClientState> mClients = new HashMap<>();
- // Makes sure we can block startListening() if session is still in progress.
+ /** List of pairs associating clients' binder tokens with corresponding listeners. */
@GuardedBy("mLock")
- private boolean mSessionInProgress = false;
-
- // Makes sure we call startProxyOp / finishProxyOp at right times and only once per session.
- @GuardedBy("mLock")
- private boolean mRecordingInProgress = false;
+ private final List<Pair<IBinder, IRecognitionListener>> mClientListeners = new ArrayList<>();
private final int mCallingUid;
private final ComponentName mComponentName;
@@ -78,7 +81,7 @@
mComponentName = serviceName;
if (DEBUG) {
- Slog.i(TAG, "Bound to recognition service at: " + serviceName.flattenToString());
+ Slog.i(TAG, "Bound to recognition service at: " + serviceName.flattenToString() + ".");
}
}
@@ -89,13 +92,14 @@
void startListening(Intent recognizerIntent, IRecognitionListener listener,
@NonNull AttributionSource attributionSource) {
if (DEBUG) {
- Slog.i(TAG, String.format("#startListening for package: %s, feature=%s, callingUid=%d",
+ Slog.i(TAG, TextUtils.formatSimple("#startListening for package: "
+ + "%s, feature=%s, callingUid=%d.",
attributionSource.getPackageName(), attributionSource.getAttributionTag(),
mCallingUid));
}
if (listener == null) {
- Log.w(TAG, "#startListening called with no preceding #setListening - ignoring");
+ Slog.w(TAG, "#startListening called with no preceding #setListening - ignoring.");
return;
}
@@ -105,29 +109,52 @@
}
synchronized (mLock) {
- if (mSessionInProgress) {
- Slog.i(TAG, "#startListening called while listening is in progress.");
- tryRespondWithError(listener, SpeechRecognizer.ERROR_RECOGNIZER_BUSY);
- return;
+ ClientState clientState = mClients.get(listener.asBinder());
+
+ if (clientState == null) {
+ if (mClients.size() >= MAX_CONCURRENT_CLIENTS) {
+ tryRespondWithError(listener, SpeechRecognizer.ERROR_RECOGNIZER_BUSY);
+ Log.i(TAG, "#startListening received "
+ + "when the recognizer's capacity is full - ignoring this call.");
+ return;
+ }
+
+ final ClientState newClientState = new ClientState();
+ newClientState.mDelegatingListener = new DelegatingListener(listener,
+ () -> {
+ // To be invoked in terminal calls on success.
+ if (DEBUG) {
+ Slog.i(TAG, "Recognition session completed successfully.");
+ }
+ synchronized (mLock) {
+ newClientState.mRecordingInProgress = false;
+ }
+ },
+ () -> {
+ // To be invoked in terminal calls on failure.
+ if (DEBUG) {
+ Slog.i(TAG, "Recognition session failed.");
+ }
+ removeClient(listener);
+ });
+
+ if (DEBUG) {
+ Log.d(TAG, "Added a new client to the map.");
+ }
+ mClients.put(listener.asBinder(), newClientState);
+ clientState = newClientState;
+ } else {
+ if (clientState.mRecordingInProgress) {
+ Slog.i(TAG, "#startListening called "
+ + "while listening is in progress for this caller.");
+ tryRespondWithError(listener, SpeechRecognizer.ERROR_CLIENT);
+ return;
+ }
+ clientState.mRecordingInProgress = true;
}
- mSessionInProgress = true;
- mRecordingInProgress = true;
-
- mListener = listener;
- mDelegatingListener = new DelegatingListener(listener, () -> {
- // To be invoked in terminal calls of the callback: results() or error()
- if (DEBUG) {
- Slog.i(TAG, "Recognition session complete");
- }
-
- synchronized (mLock) {
- resetStateLocked();
- }
- });
-
- // Eager local evaluation to avoid reading a different or null value at closure-run-time
- final DelegatingListener listenerToStart = this.mDelegatingListener;
+ // Eager local evaluation to avoid reading a different or null value at closure runtime.
+ final DelegatingListener listenerToStart = clientState.mDelegatingListener;
run(service ->
service.startListening(
recognizerIntent,
@@ -147,26 +174,22 @@
}
synchronized (mLock) {
- if (mListener == null) {
- Log.w(TAG, "#stopListening called with no preceding #startListening - ignoring");
+ ClientState clientState = mClients.get(listener.asBinder());
+
+ if (clientState == null) {
+ Slog.w(TAG, "#stopListening called with no preceding #startListening - ignoring.");
tryRespondWithError(listener, SpeechRecognizer.ERROR_CLIENT);
return;
}
-
- if (mListener.asBinder() != listener.asBinder()) {
- Log.w(TAG, "#stopListening called with an unexpected listener");
+ if (!clientState.mRecordingInProgress) {
tryRespondWithError(listener, SpeechRecognizer.ERROR_CLIENT);
+ Slog.i(TAG, "#stopListening called while listening isn't in progress - ignoring.");
return;
}
+ clientState.mRecordingInProgress = false;
- if (!mRecordingInProgress) {
- Slog.i(TAG, "#stopListening called while listening isn't in progress, ignoring.");
- return;
- }
- mRecordingInProgress = false;
-
- // Eager local evaluation to avoid reading a different or null value at closure-run-time
- final DelegatingListener listenerToStop = this.mDelegatingListener;
+ // Eager local evaluation to avoid reading a different or null value at closure runtime.
+ final DelegatingListener listenerToStop = clientState.mDelegatingListener;
run(service -> service.stopListening(listenerToStop));
}
}
@@ -181,33 +204,30 @@
}
synchronized (mLock) {
- if (mListener == null) {
+ ClientState clientState = mClients.get(listener.asBinder());
+
+ if (clientState == null) {
if (DEBUG) {
- Log.w(TAG, "#cancel called with no preceding #startListening - ignoring");
+ Slog.w(TAG, "#cancel called with no preceding #startListening - ignoring.");
}
return;
}
-
- if (mListener.asBinder() != listener.asBinder()) {
- Log.w(TAG, "#cancel called with an unexpected listener");
- tryRespondWithError(listener, SpeechRecognizer.ERROR_CLIENT);
- return;
- }
+ clientState.mRecordingInProgress = false;
// Temporary reference to allow for resetting the hard link mDelegatingListener to null.
- IRecognitionListener delegatingListener = mDelegatingListener;
-
+ final IRecognitionListener delegatingListener = clientState.mDelegatingListener;
run(service -> service.cancel(delegatingListener, isShutdown));
- mRecordingInProgress = false;
- mSessionInProgress = false;
-
- mDelegatingListener = null;
- mListener = null;
-
- // Schedule to unbind after cancel is delivered.
+ // If shutdown, remove the client info from the map. Unbind if that was the last client.
if (isShutdown) {
- run(service -> unbind());
+ removeClient(listener);
+
+ if (mClients.isEmpty()) {
+ if (DEBUG) {
+ Slog.d(TAG, "Unbinding from the recognition service.");
+ }
+ run(service -> unbind());
+ }
}
}
}
@@ -215,7 +235,6 @@
void checkRecognitionSupport(
Intent recognizerIntent,
IRecognitionSupportCallback callback) {
-
if (!mConnected) {
try {
callback.onError(SpeechRecognizer.ERROR_SERVER_DISCONNECTED);
@@ -236,18 +255,14 @@
run(service -> service.triggerModelDownload(recognizerIntent));
}
- void shutdown() {
+ void shutdown(IBinder clientToken) {
synchronized (mLock) {
- if (this.mListener == null) {
- if (DEBUG) {
- Slog.i(TAG, "Package died, but session wasn't initialized. "
- + "Not invoking #cancel");
+ for (Pair<IBinder, IRecognitionListener> clientListener : mClientListeners) {
+ if (clientListener.first == clientToken) {
+ cancel(clientListener.second, /* isShutdown */ true);
}
- return;
}
}
-
- cancel(mListener, true /* isShutdown */);
}
@Override // from ServiceConnector.Impl
@@ -265,15 +280,18 @@
synchronized (mLock) {
if (!connected) {
- if (mListener == null) {
+ if (mClients.isEmpty()) {
Slog.i(TAG, "Connection to speech recognition service lost, but no "
+ "#startListening has been invoked yet.");
return;
}
- tryRespondWithError(mListener, SpeechRecognizer.ERROR_SERVER_DISCONNECTED);
-
- resetStateLocked();
+ for (ClientState clientState : mClients.values()) {
+ tryRespondWithError(
+ clientState.mDelegatingListener.mRemoteListener,
+ SpeechRecognizer.ERROR_SERVER_DISCONNECTED);
+ removeClient(clientState.mDelegatingListener.mRemoteListener);
+ }
}
}
}
@@ -283,11 +301,18 @@
return PERMANENT_BOUND_TIMEOUT_MS;
}
- private void resetStateLocked() {
- mListener = null;
- mDelegatingListener = null;
- mSessionInProgress = false;
- mRecordingInProgress = false;
+ private void removeClient(IRecognitionListener listener) {
+ synchronized (mLock) {
+ ClientState clientState = mClients.remove(listener.asBinder());
+ if (clientState != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "Removed a client from the map with listener = "
+ + listener.asBinder() + ".");
+ }
+ clientState.reset();
+ }
+ mClientListeners.removeIf(clientListener -> clientListener.second == listener);
+ }
}
private static void tryRespondWithError(IRecognitionListener listener, int errorCode) {
@@ -301,19 +326,35 @@
}
} catch (RemoteException e) {
Slog.w(TAG,
- String.format("Failed to respond with an error %d to the client", errorCode),
- e);
+ TextUtils.formatSimple("Failed to respond with an error %d to the client",
+ errorCode), e);
+ }
+ }
+
+ boolean hasActiveSessions() {
+ synchronized (mLock) {
+ return !mClients.isEmpty();
+ }
+ }
+
+ void associateClientWithActiveListener(IBinder clientToken, IRecognitionListener listener) {
+ synchronized (mLock) {
+ if (mClients.containsKey(listener.asBinder())) {
+ mClientListeners.add(new Pair<>(clientToken, listener));
+ }
}
}
private static class DelegatingListener extends IRecognitionListener.Stub {
-
private final IRecognitionListener mRemoteListener;
- private final Runnable mOnSessionComplete;
+ private final Runnable mOnSessionSuccess;
+ private final Runnable mOnSessionFailure;
- DelegatingListener(IRecognitionListener listener, Runnable onSessionComplete) {
+ DelegatingListener(IRecognitionListener listener,
+ Runnable onSessionSuccess, Runnable onSessionFailure) {
mRemoteListener = listener;
- mOnSessionComplete = onSessionComplete;
+ mOnSessionSuccess = onSessionSuccess;
+ mOnSessionFailure = onSessionFailure;
}
@Override
@@ -344,18 +385,18 @@
@Override
public void onError(int error) throws RemoteException {
if (DEBUG) {
- Slog.i(TAG, String.format("Error %d during recognition session", error));
+ Slog.i(TAG, TextUtils.formatSimple("Error %d during recognition session.", error));
}
- mOnSessionComplete.run();
+ mOnSessionFailure.run();
mRemoteListener.onError(error);
}
@Override
public void onResults(Bundle results) throws RemoteException {
if (DEBUG) {
- Slog.i(TAG, "#onResults invoked for a recognition session");
+ Slog.i(TAG, "#onResults invoked for a recognition session.");
}
- mOnSessionComplete.run();
+ mOnSessionSuccess.run();
mRemoteListener.onResults(results);
}
@@ -372,9 +413,9 @@
@Override
public void onEndOfSegmentedSession() throws RemoteException {
if (DEBUG) {
- Slog.i(TAG, "#onEndOfSegmentedSession invoked for a recognition session");
+ Slog.i(TAG, "#onEndOfSegmentedSession invoked for a recognition session.");
}
- mOnSessionComplete.run();
+ mOnSessionSuccess.run();
mRemoteListener.onEndOfSegmentedSession();
}
@@ -383,4 +424,35 @@
mRemoteListener.onEvent(eventType, params);
}
}
+
+ /**
+ * Data class holding info about a connected client:
+ * <ul>
+ * <li> {@link ClientState#mDelegatingListener}
+ * - object holding callbacks to be invoked after the session is complete;
+ * <li> {@link ClientState#mRecordingInProgress}
+ * - flag denoting if the client is currently recording.
+ */
+ static class ClientState {
+ DelegatingListener mDelegatingListener;
+ boolean mRecordingInProgress;
+
+ ClientState(DelegatingListener delegatingListener, boolean recordingInProgress) {
+ mDelegatingListener = delegatingListener;
+ mRecordingInProgress = recordingInProgress;
+ }
+
+ ClientState(DelegatingListener delegatingListener) {
+ this(delegatingListener, true);
+ }
+
+ ClientState() {
+ this(null, true);
+ }
+
+ void reset() {
+ mDelegatingListener = null;
+ mRecordingInProgress = false;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
index d5b73b7..e245c08 100644
--- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
@@ -51,7 +51,7 @@
final class SpeechRecognitionManagerServiceImpl extends
AbstractPerUserSystemService<SpeechRecognitionManagerServiceImpl,
- SpeechRecognitionManagerService> {
+ SpeechRecognitionManagerService> {
private static final String TAG = SpeechRecognitionManagerServiceImpl.class.getSimpleName();
private static final int MAX_CONCURRENT_CONNECTIONS_BY_CLIENT = 10;
@@ -127,13 +127,14 @@
}
IBinder.DeathRecipient deathRecipient =
- () -> handleClientDeath(creatorCallingUid, service, true /* invoke #cancel */);
+ () -> handleClientDeath(
+ clientToken, creatorCallingUid, service, true /* invoke #cancel */);
try {
clientToken.linkToDeath(deathRecipient, 0);
} catch (RemoteException e) {
// RemoteException == binder already died, schedule disconnect anyway.
- handleClientDeath(creatorCallingUid, service, true /* invoke #cancel */);
+ handleClientDeath(clientToken, creatorCallingUid, service, true /* invoke #cancel */);
return;
}
@@ -146,7 +147,7 @@
Intent recognizerIntent,
IRecognitionListener listener,
@NonNull AttributionSource attributionSource)
- throws RemoteException {
+ throws RemoteException {
attributionSource.enforceCallingUid();
if (!attributionSource.isTrusted(mMaster.getContext())) {
attributionSource = mMaster.getContext()
@@ -154,6 +155,7 @@
.registerAttributionSource(attributionSource);
}
service.startListening(recognizerIntent, listener, attributionSource);
+ service.associateClientWithActiveListener(clientToken, listener);
}
@Override
@@ -166,11 +168,11 @@
public void cancel(
IRecognitionListener listener,
boolean isShutdown) throws RemoteException {
-
service.cancel(listener, isShutdown);
if (isShutdown) {
handleClientDeath(
+ clientToken,
creatorCallingUid,
service,
false /* invoke #cancel */);
@@ -201,12 +203,16 @@
}
private void handleClientDeath(
- int callingUid,
+ IBinder clientToken, int callingUid,
RemoteSpeechRecognitionService service, boolean invokeCancel) {
if (invokeCancel) {
- service.shutdown();
+ service.shutdown(clientToken);
}
- removeService(callingUid, service);
+ synchronized (mLock) {
+ if (!service.hasActiveSessions()) {
+ removeService(callingUid, service);
+ }
+ }
}
@GuardedBy("mLock")
@@ -245,7 +251,6 @@
service.getServiceComponentName().equals(serviceComponent))
.findFirst();
if (existingService.isPresent()) {
-
if (mMaster.debug) {
Slog.i(TAG, "Reused existing connection to " + serviceComponent);
}
diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotCache.java b/services/core/java/com/android/server/wm/AbsAppSnapshotCache.java
new file mode 100644
index 0000000..c8adc8f
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AbsAppSnapshotCache.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wm;
+
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.window.TaskSnapshot;
+
+import java.io.PrintWriter;
+
+/**
+ * Base class for an app snapshot cache
+ * @param <TYPE> The basic type, either Task or ActivityRecord
+ */
+abstract class AbsAppSnapshotCache<TYPE extends WindowContainer> {
+ protected final WindowManagerService mService;
+ protected final String mName;
+ protected final ArrayMap<ActivityRecord, Integer> mAppIdMap = new ArrayMap<>();
+ protected final ArrayMap<Integer, CacheEntry> mRunningCache = new ArrayMap<>();
+
+ AbsAppSnapshotCache(WindowManagerService service, String name) {
+ mService = service;
+ mName = name;
+ }
+
+ abstract void putSnapshot(TYPE window, TaskSnapshot snapshot);
+
+ void clearRunningCache() {
+ mRunningCache.clear();
+ }
+
+ @Nullable
+ final TaskSnapshot getSnapshot(Integer id) {
+ synchronized (mService.mGlobalLock) {
+ // Try the running cache.
+ final CacheEntry entry = mRunningCache.get(id);
+ if (entry != null) {
+ return entry.snapshot;
+ }
+ }
+ return null;
+ }
+
+ /** Called when an app token has been removed. */
+ void onAppRemoved(ActivityRecord activity) {
+ final Integer id = mAppIdMap.get(activity);
+ if (id != null) {
+ removeRunningEntry(id);
+ }
+ }
+
+ /** Called when an app window token's process died. */
+ void onAppDied(ActivityRecord activity) {
+ final Integer id = mAppIdMap.get(activity);
+ if (id != null) {
+ removeRunningEntry(id);
+ }
+ }
+
+ void onIdRemoved(Integer index) {
+ removeRunningEntry(index);
+ }
+
+ void removeRunningEntry(Integer id) {
+ final CacheEntry entry = mRunningCache.get(id);
+ if (entry != null) {
+ mAppIdMap.remove(entry.topApp);
+ mRunningCache.remove(id);
+ }
+ }
+
+ void dump(PrintWriter pw, String prefix) {
+ final String doublePrefix = prefix + " ";
+ final String triplePrefix = doublePrefix + " ";
+ pw.println(prefix + "SnapshotCache " + mName);
+ for (int i = mRunningCache.size() - 1; i >= 0; i--) {
+ final CacheEntry entry = mRunningCache.valueAt(i);
+ pw.println(doublePrefix + "Entry token=" + mRunningCache.keyAt(i));
+ pw.println(triplePrefix + "topApp=" + entry.topApp);
+ pw.println(triplePrefix + "snapshot=" + entry.snapshot);
+ }
+ }
+
+ static final class CacheEntry {
+ /** The snapshot. */
+ final TaskSnapshot snapshot;
+ /** The app token that was on top of the task when the snapshot was taken */
+ final ActivityRecord topApp;
+ CacheEntry(TaskSnapshot snapshot, ActivityRecord topApp) {
+ this.snapshot = snapshot;
+ this.topApp = topApp;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
new file mode 100644
index 0000000..5e89fa6
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
@@ -0,0 +1,457 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wm;
+
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.RecordingCanvas;
+import android.graphics.Rect;
+import android.graphics.RenderNode;
+import android.hardware.HardwareBuffer;
+import android.os.Trace;
+import android.util.Pair;
+import android.util.Slog;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.ThreadedRenderer;
+import android.view.WindowInsets;
+import android.view.WindowInsetsController;
+import android.view.WindowManager;
+import android.window.ScreenCapture;
+import android.window.SnapshotDrawerUtils;
+import android.window.TaskSnapshot;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.ColorUtils;
+import com.android.server.wm.utils.InsetUtils;
+
+import java.io.PrintWriter;
+
+/**
+ * Base class for a Snapshot controller
+ * @param <TYPE> The basic type, either Task or ActivityRecord
+ * @param <CACHE> The basic cache for either Task or ActivityRecord
+ */
+abstract class AbsAppSnapshotController<TYPE extends WindowContainer,
+ CACHE extends AbsAppSnapshotCache<TYPE>> {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "SnapshotController" : TAG_WM;
+ /**
+ * Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be
+ * used as the snapshot.
+ */
+ @VisibleForTesting
+ static final int SNAPSHOT_MODE_REAL = 0;
+ /**
+ * Return value for {@link #getSnapshotMode}: We are not allowed to take a real screenshot but
+ * we should try to use the app theme to create a fake representation of the app.
+ */
+ @VisibleForTesting
+ static final int SNAPSHOT_MODE_APP_THEME = 1;
+ /**
+ * Return value for {@link #getSnapshotMode}: We aren't allowed to take any snapshot.
+ */
+ @VisibleForTesting
+ static final int SNAPSHOT_MODE_NONE = 2;
+
+ protected final WindowManagerService mService;
+ protected final float mHighResTaskSnapshotScale;
+ private final Rect mTmpRect = new Rect();
+ /**
+ * Flag indicating whether we are running on an Android TV device.
+ */
+ protected final boolean mIsRunningOnTv;
+ /**
+ * Flag indicating whether we are running on an IoT device.
+ */
+ protected final boolean mIsRunningOnIoT;
+
+ protected CACHE mCache;
+ /**
+ * Flag indicating if task snapshot is enabled on this device.
+ */
+ private boolean mSnapshotEnabled;
+
+ AbsAppSnapshotController(WindowManagerService service) {
+ mService = service;
+ mIsRunningOnTv = mService.mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_LEANBACK);
+ mIsRunningOnIoT = mService.mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_EMBEDDED);
+ mHighResTaskSnapshotScale = initSnapshotScale();
+ }
+
+ protected float initSnapshotScale() {
+ return mService.mContext.getResources().getFloat(
+ com.android.internal.R.dimen.config_highResTaskSnapshotScale);
+ }
+
+ /**
+ * Set basic cache to the controller.
+ */
+ protected void initialize(CACHE cache) {
+ mCache = cache;
+ }
+
+ void setSnapshotEnabled(boolean enabled) {
+ mSnapshotEnabled = enabled;
+ }
+
+ boolean shouldDisableSnapshots() {
+ return mIsRunningOnTv || mIsRunningOnIoT || !mSnapshotEnabled;
+ }
+
+ abstract ActivityRecord getTopActivity(TYPE source);
+ abstract ActivityRecord getTopFullscreenActivity(TYPE source);
+ abstract ActivityManager.TaskDescription getTaskDescription(TYPE source);
+ /**
+ * Find the window for a given task to take a snapshot. Top child of the task is usually the one
+ * we're looking for, but during app transitions, trampoline activities can appear in the
+ * children, which should be ignored.
+ */
+ @Nullable
+ protected abstract ActivityRecord findAppTokenForSnapshot(TYPE source);
+ protected abstract boolean use16BitFormat();
+
+ /**
+ * This is different than {@link #recordSnapshotInner(TYPE, boolean)} because it doesn't store
+ * the snapshot to the cache and returns the TaskSnapshot immediately.
+ *
+ * This is only used for testing so the snapshot content can be verified.
+ */
+ @VisibleForTesting
+ TaskSnapshot captureSnapshot(TYPE source, boolean snapshotHome) {
+ final TaskSnapshot snapshot;
+ if (snapshotHome) {
+ snapshot = snapshot(source);
+ } else {
+ switch (getSnapshotMode(source)) {
+ case SNAPSHOT_MODE_NONE:
+ return null;
+ case SNAPSHOT_MODE_APP_THEME:
+ snapshot = drawAppThemeSnapshot(source);
+ break;
+ case SNAPSHOT_MODE_REAL:
+ snapshot = snapshot(source);
+ break;
+ default:
+ snapshot = null;
+ break;
+ }
+ }
+ return snapshot;
+ }
+
+ final TaskSnapshot recordSnapshotInner(TYPE source, boolean allowSnapshotHome) {
+ final boolean snapshotHome = allowSnapshotHome && source.isActivityTypeHome();
+ final TaskSnapshot snapshot = captureSnapshot(source, snapshotHome);
+ if (snapshot == null) {
+ return null;
+ }
+ final HardwareBuffer buffer = snapshot.getHardwareBuffer();
+ if (buffer.getWidth() == 0 || buffer.getHeight() == 0) {
+ buffer.close();
+ Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x"
+ + buffer.getHeight());
+ return null;
+ } else {
+ mCache.putSnapshot(source, snapshot);
+ return snapshot;
+ }
+ }
+
+ @VisibleForTesting
+ int getSnapshotMode(TYPE source) {
+ final ActivityRecord topChild = getTopActivity(source);
+ if (!source.isActivityTypeStandardOrUndefined() && !source.isActivityTypeAssistant()) {
+ return SNAPSHOT_MODE_NONE;
+ } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
+ return SNAPSHOT_MODE_APP_THEME;
+ } else {
+ return SNAPSHOT_MODE_REAL;
+ }
+ }
+
+ @Nullable
+ TaskSnapshot snapshot(TYPE source) {
+ return snapshot(source, PixelFormat.UNKNOWN);
+ }
+
+ @Nullable
+ TaskSnapshot snapshot(TYPE source, int pixelFormat) {
+ TaskSnapshot.Builder builder = new TaskSnapshot.Builder();
+ if (!prepareTaskSnapshot(source, pixelFormat, builder)) {
+ // Failed some pre-req. Has been logged.
+ return null;
+ }
+ final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
+ createSnapshot(source, builder);
+ if (screenshotBuffer == null) {
+ // Failed to acquire image. Has been logged.
+ return null;
+ }
+ builder.setSnapshot(screenshotBuffer.getHardwareBuffer());
+ builder.setColorSpace(screenshotBuffer.getColorSpace());
+ return builder.build();
+ }
+
+ @Nullable
+ ScreenCapture.ScreenshotHardwareBuffer createSnapshot(@NonNull TYPE source,
+ TaskSnapshot.Builder builder) {
+ Point taskSize = new Point();
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "createSnapshot");
+ final ScreenCapture.ScreenshotHardwareBuffer taskSnapshot = createSnapshot(source,
+ mHighResTaskSnapshotScale, builder.getPixelFormat(), taskSize, builder);
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ builder.setTaskSize(taskSize);
+ return taskSnapshot;
+ }
+
+ @Nullable
+ ScreenCapture.ScreenshotHardwareBuffer createSnapshot(@NonNull TYPE source,
+ float scaleFraction, int pixelFormat, Point outTaskSize, TaskSnapshot.Builder builder) {
+ if (source.getSurfaceControl() == null) {
+ if (DEBUG_SCREENSHOT) {
+ Slog.w(TAG_WM, "Failed to take screenshot. No surface control for " + source);
+ }
+ return null;
+ }
+ source.getBounds(mTmpRect);
+ mTmpRect.offsetTo(0, 0);
+ SurfaceControl[] excludeLayers;
+ final WindowState imeWindow = source.getDisplayContent().mInputMethodWindow;
+ // Exclude IME window snapshot when IME isn't proper to attach to app.
+ final boolean excludeIme = imeWindow != null && imeWindow.getSurfaceControl() != null
+ && !source.getDisplayContent().shouldImeAttachedToApp();
+ final WindowState navWindow =
+ source.getDisplayContent().getDisplayPolicy().getNavigationBar();
+ // If config_attachNavBarToAppDuringTransition is true, the nav bar will be reparent to the
+ // the swiped app when entering recent app, therefore the task will contain the navigation
+ // bar and we should exclude it from snapshot.
+ final boolean excludeNavBar = navWindow != null;
+ if (excludeIme && excludeNavBar) {
+ excludeLayers = new SurfaceControl[2];
+ excludeLayers[0] = imeWindow.getSurfaceControl();
+ excludeLayers[1] = navWindow.getSurfaceControl();
+ } else if (excludeIme || excludeNavBar) {
+ excludeLayers = new SurfaceControl[1];
+ excludeLayers[0] =
+ excludeIme ? imeWindow.getSurfaceControl() : navWindow.getSurfaceControl();
+ } else {
+ excludeLayers = new SurfaceControl[0];
+ }
+ builder.setHasImeSurface(!excludeIme && imeWindow != null && imeWindow.isVisible());
+ final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
+ ScreenCapture.captureLayersExcluding(
+ source.getSurfaceControl(), mTmpRect, scaleFraction,
+ pixelFormat, excludeLayers);
+ if (outTaskSize != null) {
+ outTaskSize.x = mTmpRect.width();
+ outTaskSize.y = mTmpRect.height();
+ }
+ final HardwareBuffer buffer = screenshotBuffer == null ? null
+ : screenshotBuffer.getHardwareBuffer();
+ if (isInvalidHardwareBuffer(buffer)) {
+ return null;
+ }
+ return screenshotBuffer;
+ }
+
+ static boolean isInvalidHardwareBuffer(HardwareBuffer buffer) {
+ return buffer == null || buffer.isClosed() // This must be checked before getting size.
+ || buffer.getWidth() <= 1 || buffer.getHeight() <= 1;
+ }
+
+ /**
+ * Validates the state of the Task is appropriate to capture a snapshot, collects
+ * information from the task and populates the builder.
+ *
+ * @param source the window to capture
+ * @param pixelFormat the desired pixel format, or {@link PixelFormat#UNKNOWN} to
+ * automatically select
+ * @param builder the snapshot builder to populate
+ *
+ * @return true if the state of the task is ok to proceed
+ */
+ @VisibleForTesting
+ boolean prepareTaskSnapshot(TYPE source, int pixelFormat, TaskSnapshot.Builder builder) {
+ final Pair<ActivityRecord, WindowState> result = checkIfReadyToSnapshot(source);
+ if (result == null) {
+ return false;
+ }
+ final ActivityRecord activity = result.first;
+ final WindowState mainWindow = result.second;
+ final Rect contentInsets = getSystemBarInsets(mainWindow.getFrame(),
+ mainWindow.getInsetsStateWithVisibilityOverride());
+ final Rect letterboxInsets = activity.getLetterboxInsets();
+ InsetUtils.addInsets(contentInsets, letterboxInsets);
+ builder.setIsRealSnapshot(true);
+ builder.setId(System.currentTimeMillis());
+ builder.setContentInsets(contentInsets);
+ builder.setLetterboxInsets(letterboxInsets);
+ final boolean isWindowTranslucent = mainWindow.getAttrs().format != PixelFormat.OPAQUE;
+ final boolean isShowWallpaper = mainWindow.hasWallpaper();
+ if (pixelFormat == PixelFormat.UNKNOWN) {
+ pixelFormat = use16BitFormat() && activity.fillsParent()
+ && !(isWindowTranslucent && isShowWallpaper)
+ ? PixelFormat.RGB_565
+ : PixelFormat.RGBA_8888;
+ }
+ final boolean isTranslucent = PixelFormat.formatHasAlpha(pixelFormat)
+ && (!activity.fillsParent() || isWindowTranslucent);
+ builder.setTopActivityComponent(activity.mActivityComponent);
+ builder.setPixelFormat(pixelFormat);
+ builder.setIsTranslucent(isTranslucent);
+ builder.setOrientation(activity.getTask().getConfiguration().orientation);
+ builder.setRotation(activity.getTask().getDisplayContent().getRotation());
+ builder.setWindowingMode(source.getWindowingMode());
+ builder.setAppearance(getAppearance(source));
+ return true;
+ }
+
+ /**
+ * Check if the state of the Task is appropriate to capture a snapshot, such like the task
+ * snapshot or the associated IME surface snapshot.
+ *
+ * @param source the target object to capture the snapshot
+ * @return Pair of (the top activity of the task, the main window of the task) if passed the
+ * state checking. Returns {@code null} if the task state isn't ready to snapshot.
+ */
+ Pair<ActivityRecord, WindowState> checkIfReadyToSnapshot(TYPE source) {
+ if (!mService.mPolicy.isScreenOn()) {
+ if (DEBUG_SCREENSHOT) {
+ Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
+ }
+ return null;
+ }
+ final ActivityRecord activity = findAppTokenForSnapshot(source);
+ if (activity == null) {
+ if (DEBUG_SCREENSHOT) {
+ Slog.w(TAG_WM, "Failed to take screenshot. No visible windows for " + source);
+ }
+ return null;
+ }
+ if (activity.hasCommittedReparentToAnimationLeash()) {
+ if (DEBUG_SCREENSHOT) {
+ Slog.w(TAG_WM, "Failed to take screenshot. App is animating " + activity);
+ }
+ return null;
+ }
+ final WindowState mainWindow = activity.findMainWindow();
+ if (mainWindow == null) {
+ Slog.w(TAG_WM, "Failed to take screenshot. No main window for " + source);
+ return null;
+ }
+ if (activity.hasFixedRotationTransform()) {
+ if (DEBUG_SCREENSHOT) {
+ Slog.i(TAG_WM, "Skip taking screenshot. App has fixed rotation " + activity);
+ }
+ // The activity is in a temporal state that it has different rotation than the task.
+ return null;
+ }
+ return new Pair<>(activity, mainWindow);
+ }
+
+ /**
+ * If we are not allowed to take a real screenshot, this attempts to represent the app as best
+ * as possible by using the theme's window background.
+ */
+ private TaskSnapshot drawAppThemeSnapshot(TYPE source) {
+ final ActivityRecord topActivity = getTopActivity(source);
+ if (topActivity == null) {
+ return null;
+ }
+ final WindowState mainWindow = topActivity.findMainWindow();
+ if (mainWindow == null) {
+ return null;
+ }
+ final ActivityManager.TaskDescription taskDescription = getTaskDescription(source);
+ final int color = ColorUtils.setAlphaComponent(
+ taskDescription.getBackgroundColor(), 255);
+ final WindowManager.LayoutParams attrs = mainWindow.getAttrs();
+ final Rect taskBounds = source.getBounds();
+ final InsetsState insetsState = mainWindow.getInsetsStateWithVisibilityOverride();
+ final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrame(), insetsState);
+ final SnapshotDrawerUtils.SystemBarBackgroundPainter
+ decorPainter = new SnapshotDrawerUtils.SystemBarBackgroundPainter(attrs.flags,
+ attrs.privateFlags, attrs.insetsFlags.appearance, taskDescription,
+ mHighResTaskSnapshotScale, mainWindow.getRequestedVisibleTypes());
+ final int taskWidth = taskBounds.width();
+ final int taskHeight = taskBounds.height();
+ final int width = (int) (taskWidth * mHighResTaskSnapshotScale);
+ final int height = (int) (taskHeight * mHighResTaskSnapshotScale);
+ final RenderNode node = RenderNode.create("SnapshotController", null);
+ node.setLeftTopRightBottom(0, 0, width, height);
+ node.setClipToBounds(false);
+ final RecordingCanvas c = node.start(width, height);
+ c.drawColor(color);
+ decorPainter.setInsets(systemBarInsets);
+ decorPainter.drawDecors(c /* statusBarExcludeFrame */, null /* alreadyDrawFrame */);
+ node.end(c);
+ final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
+ if (hwBitmap == null) {
+ return null;
+ }
+ final Rect contentInsets = new Rect(systemBarInsets);
+ final Rect letterboxInsets = topActivity.getLetterboxInsets();
+ InsetUtils.addInsets(contentInsets, letterboxInsets);
+ // Note, the app theme snapshot is never translucent because we enforce a non-translucent
+ // color above
+ return new TaskSnapshot(
+ System.currentTimeMillis() /* id */,
+ topActivity.mActivityComponent, hwBitmap.getHardwareBuffer(),
+ hwBitmap.getColorSpace(), mainWindow.getConfiguration().orientation,
+ mainWindow.getWindowConfiguration().getRotation(), new Point(taskWidth, taskHeight),
+ contentInsets, letterboxInsets, false /* isLowResolution */,
+ false /* isRealSnapshot */, source.getWindowingMode(),
+ getAppearance(source), false /* isTranslucent */, false /* hasImeSurface */);
+ }
+
+ static Rect getSystemBarInsets(Rect frame, InsetsState state) {
+ return state.calculateInsets(
+ frame, WindowInsets.Type.systemBars(), false /* ignoreVisibility */).toRect();
+ }
+
+ /**
+ * @return The {@link WindowInsetsController.Appearance} flags for the top fullscreen opaque
+ * window in the given {@param TYPE}.
+ */
+ @WindowInsetsController.Appearance
+ private int getAppearance(TYPE source) {
+ final ActivityRecord topFullscreenActivity = getTopFullscreenActivity(source);
+ final WindowState topFullscreenOpaqueWindow = topFullscreenActivity != null
+ ? topFullscreenActivity.getTopFullscreenOpaqueWindow()
+ : null;
+ if (topFullscreenOpaqueWindow != null) {
+ return topFullscreenOpaqueWindow.mAttrs.insetsFlags.appearance;
+ }
+ return 0;
+ }
+
+ void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "mHighResTaskSnapshotScale=" + mHighResTaskSnapshotScale);
+ pw.println(prefix + "mTaskSnapshotEnabled=" + mSnapshotEnabled);
+ mCache.dump(pw, prefix);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 6c49f47..fb1b591 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1674,7 +1674,7 @@
mRootWindowContainer.startPowerModeLaunchIfNeeded(
false /* forceSend */, mStartActivity);
- final boolean isTaskSwitch = startedTask != prevTopTask && !startedTask.isEmbedded();
+ final boolean isTaskSwitch = startedTask != prevTopTask;
mTargetRootTask.startActivityLocked(mStartActivity, topRootTask, newTask, isTaskSwitch,
mOptions, sourceRecord);
if (mDoResume) {
@@ -2753,8 +2753,7 @@
} else {
TaskFragment candidateTf = mAddingToTaskFragment != null ? mAddingToTaskFragment : null;
if (candidateTf == null) {
- final ActivityRecord top = task.topRunningActivity(false /* focusableOnly */,
- false /* includingEmbeddedTask */);
+ final ActivityRecord top = task.topRunningActivity(false /* focusableOnly */);
if (top != null) {
candidateTf = top.getTaskFragment();
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 937f2f1..5dd77ea 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3754,7 +3754,7 @@
Slog.w(TAG, "takeTaskSnapshot: taskId=" + taskId + " not found or not visible");
return null;
}
- return mWindowManager.mTaskSnapshotController.captureTaskSnapshot(
+ return mWindowManager.mTaskSnapshotController.captureSnapshot(
task, false /* snapshotHome */);
}
} finally {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java b/services/core/java/com/android/server/wm/AppSnapshotLoader.java
similarity index 88%
rename from services/core/java/com/android/server/wm/TaskSnapshotLoader.java
rename to services/core/java/com/android/server/wm/AppSnapshotLoader.java
index 9189e51..88c4752 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
+++ b/services/core/java/com/android/server/wm/AppSnapshotLoader.java
@@ -11,7 +11,7 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
package com.android.server.wm;
@@ -31,6 +31,7 @@
import android.util.Slog;
import android.window.TaskSnapshot;
+import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto;
import java.io.File;
@@ -44,14 +45,14 @@
* <p>
* Test class: {@link TaskSnapshotPersisterLoaderTest}
*/
-class TaskSnapshotLoader {
+class AppSnapshotLoader {
private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotLoader" : TAG_WM;
- private final TaskSnapshotPersister mPersister;
+ private final PersistInfoProvider mPersistInfoProvider;
- TaskSnapshotLoader(TaskSnapshotPersister persister) {
- mPersister = persister;
+ AppSnapshotLoader(PersistInfoProvider persistInfoProvider) {
+ mPersistInfoProvider = persistInfoProvider;
}
static class PreRLegacySnapshotConfig {
@@ -130,21 +131,22 @@
* Do not hold the window manager lock when calling this method, as we directly read data from
* disk here, which might be slow.
*
- * @param taskId The id of the task to load.
+ * @param id The id of the snapshot to load.
* @param userId The id of the user the task belonged to.
* @param loadLowResolutionBitmap Whether to load a low resolution resolution version of the
* snapshot.
* @return The loaded {@link TaskSnapshot} or {@code null} if it couldn't be loaded.
*/
- TaskSnapshot loadTask(int taskId, int userId, boolean loadLowResolutionBitmap) {
- final File protoFile = mPersister.getProtoFile(taskId, userId);
+ TaskSnapshot loadTask(int id, int userId, boolean loadLowResolutionBitmap) {
+ final File protoFile = mPersistInfoProvider.getProtoFile(id, userId);
if (!protoFile.exists()) {
return null;
}
try {
final byte[] bytes = Files.readAllBytes(protoFile.toPath());
final TaskSnapshotProto proto = TaskSnapshotProto.parseFrom(bytes);
- final File highResBitmap = mPersister.getHighResolutionBitmapFile(taskId, userId);
+ final File highResBitmap = mPersistInfoProvider
+ .getHighResolutionBitmapFile(id, userId);
PreRLegacySnapshotConfig legacyConfig = getLegacySnapshotConfig(proto.taskWidth,
proto.legacyScale, highResBitmap.exists(), loadLowResolutionBitmap);
@@ -152,16 +154,16 @@
boolean forceLoadReducedJpeg =
legacyConfig != null && legacyConfig.mForceLoadReducedJpeg;
File bitmapFile = (loadLowResolutionBitmap || forceLoadReducedJpeg)
- ? mPersister.getLowResolutionBitmapFile(taskId, userId) : highResBitmap;
+ ? mPersistInfoProvider.getLowResolutionBitmapFile(id, userId)
+ : highResBitmap;
if (!bitmapFile.exists()) {
return null;
}
final Options options = new Options();
- options.inPreferredConfig = mPersister.use16BitFormat() && !proto.isTranslucent
- ? Config.RGB_565
- : Config.ARGB_8888;
+ options.inPreferredConfig = mPersistInfoProvider.use16BitFormat()
+ && !proto.isTranslucent ? Config.RGB_565 : Config.ARGB_8888;
final Bitmap bitmap = BitmapFactory.decodeFile(bitmapFile.getPath(), options);
if (bitmap == null) {
Slog.w(TAG, "Failed to load bitmap: " + bitmapFile.getPath());
@@ -201,7 +203,7 @@
loadLowResolutionBitmap, proto.isRealSnapshot, proto.windowingMode,
proto.appearance, proto.isTranslucent, false /* hasImeSurface */);
} catch (IOException e) {
- Slog.w(TAG, "Unable to load task snapshot data for taskId=" + taskId);
+ Slog.w(TAG, "Unable to load task snapshot data for Id=" + id);
return null;
}
}
diff --git a/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java b/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java
new file mode 100644
index 0000000..d604402
--- /dev/null
+++ b/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.annotation.NonNull;
+import android.window.TaskSnapshot;
+
+import java.io.File;
+
+class BaseAppSnapshotPersister {
+ static final String LOW_RES_FILE_POSTFIX = "_reduced";
+ static final String PROTO_EXTENSION = ".proto";
+ static final String BITMAP_EXTENSION = ".jpg";
+
+ // Shared with SnapshotPersistQueue
+ protected final Object mLock;
+ protected final SnapshotPersistQueue mSnapshotPersistQueue;
+ protected final PersistInfoProvider mPersistInfoProvider;
+
+ BaseAppSnapshotPersister(SnapshotPersistQueue persistQueue,
+ PersistInfoProvider persistInfoProvider) {
+ mSnapshotPersistQueue = persistQueue;
+ mPersistInfoProvider = persistInfoProvider;
+ mLock = persistQueue.getLock();
+ }
+
+ /**
+ * Persists a snapshot of a task to disk.
+ *
+ * @param id The id of the object that needs to be persisted.
+ * @param userId The id of the user this tasks belongs to.
+ * @param snapshot The snapshot to persist.
+ */
+ void persistSnapshot(int id, int userId, TaskSnapshot snapshot) {
+ synchronized (mLock) {
+ mSnapshotPersistQueue.sendToQueueLocked(mSnapshotPersistQueue
+ .createStoreWriteQueueItem(id, userId, snapshot, mPersistInfoProvider));
+ }
+ }
+
+ /**
+ * Called to remove the persisted file
+ *
+ * @param id The id of task that has been removed.
+ * @param userId The id of the user the task belonged to.
+ */
+ void removeSnap(int id, int userId) {
+ synchronized (mLock) {
+ mSnapshotPersistQueue.sendToQueueLocked(mSnapshotPersistQueue
+ .createDeleteWriteQueueItem(id, userId, mPersistInfoProvider));
+ }
+ }
+
+ interface DirectoryResolver {
+ File getSystemDirectoryForUser(int userId);
+ }
+
+ /**
+ * Persist information provider, the snapshot persister and loader can know where the file is,
+ * and the scale of a snapshot, etc.
+ */
+ static class PersistInfoProvider {
+ protected final DirectoryResolver mDirectoryResolver;
+ private final String mDirName;
+ private final boolean mEnableLowResSnapshots;
+ private final float mLowResScaleFactor;
+ private final boolean mUse16BitFormat;
+
+ PersistInfoProvider(DirectoryResolver directoryResolver, String dirName,
+ boolean enableLowResSnapshots, float lowResScaleFactor, boolean use16BitFormat) {
+ mDirectoryResolver = directoryResolver;
+ mDirName = dirName;
+ mEnableLowResSnapshots = enableLowResSnapshots;
+ mLowResScaleFactor = lowResScaleFactor;
+ mUse16BitFormat = use16BitFormat;
+ }
+
+ @NonNull
+ File getDirectory(int userId) {
+ return new File(mDirectoryResolver.getSystemDirectoryForUser(userId), mDirName);
+ }
+
+ /**
+ * Return if task snapshots are stored in 16 bit pixel format.
+ *
+ * @return true if task snapshots are stored in 16 bit pixel format.
+ */
+ boolean use16BitFormat() {
+ return mUse16BitFormat;
+ }
+
+ boolean createDirectory(int userId) {
+ final File dir = getDirectory(userId);
+ return dir.exists() || dir.mkdir();
+ }
+
+ File getProtoFile(int index, int userId) {
+ return new File(getDirectory(userId), index + PROTO_EXTENSION);
+ }
+
+ File getLowResolutionBitmapFile(int index, int userId) {
+ return new File(getDirectory(userId), index + LOW_RES_FILE_POSTFIX + BITMAP_EXTENSION);
+ }
+
+ File getHighResolutionBitmapFile(int index, int userId) {
+ return new File(getDirectory(userId), index + BITMAP_EXTENSION);
+ }
+
+ boolean enableLowResSnapshots() {
+ return mEnableLowResSnapshots;
+ }
+
+ float lowResScaleFactor() {
+ return mLowResScaleFactor;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ffea07c..4f81ee4 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -132,11 +132,15 @@
import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA;
import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION;
import static com.android.server.wm.DisplayContentProto.SLEEP_TOKENS;
+import static com.android.server.wm.EventLogTags.IMF_REMOVE_IME_SCREENSHOT;
+import static com.android.server.wm.EventLogTags.IMF_SHOW_IME_SCREENSHOT;
+import static com.android.server.wm.EventLogTags.IMF_UPDATE_IME_PARENT;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowContainerChildProto.DISPLAY_CONTENT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_IME_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
@@ -196,6 +200,7 @@
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.DisplayUtils;
+import android.util.EventLog;
import android.util.IntArray;
import android.util.Pair;
import android.util.RotationUtils;
@@ -844,12 +849,7 @@
// activity cannot be focused unless it is on the same TaskFragment as the focusedApp's.
TaskFragment parent = activity.getTaskFragment();
if (parent != null && parent.isEmbedded()) {
- Task hostTask = focusedApp.getTask();
- if (hostTask.isEmbedded()) {
- // Use the hosting task if the current task is embedded.
- hostTask = hostTask.getParent().asTaskFragment().getTask();
- }
- if (activity.isDescendantOf(hostTask)
+ if (activity.getTask() == focusedApp.getTask()
&& activity.getTaskFragment() != focusedApp.getTaskFragment()) {
return false;
}
@@ -4276,6 +4276,7 @@
private WindowState mImeTarget;
private SurfaceControl.Builder mSurfaceBuilder;
private SurfaceControl mImeSurface;
+ private Point mImeSurfacePosition;
ImeScreenshot(SurfaceControl.Builder surfaceBuilder, @NonNull WindowState imeTarget) {
mSurfaceBuilder = surfaceBuilder;
@@ -4326,6 +4327,7 @@
mImeTarget.mAttrs.surfaceInsets.top);
t.setPosition(imeSurface, surfacePosition.x, surfacePosition.y);
}
+ mImeSurfacePosition = surfacePosition;
ProtoLog.i(WM_DEBUG_IME, "Set IME snapshot position: (%d, %d)", surfacePosition.x,
surfacePosition.y);
return imeSurface;
@@ -4337,6 +4339,9 @@
t.remove(mImeSurface);
mImeSurface = null;
}
+ if (DEBUG_IME_VISIBILITY) {
+ EventLog.writeEvent(IMF_REMOVE_IME_SCREENSHOT, mImeTarget.toString());
+ }
}
void attachAndShow(Transaction t) {
@@ -4366,6 +4371,10 @@
ProtoLog.i(WM_DEBUG_IME, "show IME snapshot, ime target=%s, callers=%s",
mImeTarget, Debug.getCallers(6));
t.show(mImeSurface);
+ if (DEBUG_IME_VISIBILITY) {
+ EventLog.writeEvent(IMF_SHOW_IME_SCREENSHOT, mImeTarget.toString(),
+ dc.mInputMethodWindow.mTransitFlags, mImeSurfacePosition.toString());
+ }
} else if (!isValidSnapshot) {
removeImeSurface(t);
}
@@ -4511,6 +4520,9 @@
if (newParent != null && newParent != mInputMethodSurfaceParent) {
mInputMethodSurfaceParent = newParent;
getSyncTransaction().reparent(mImeWindowsContainer.mSurfaceControl, newParent);
+ if (DEBUG_IME_VISIBILITY) {
+ EventLog.writeEvent(IMF_UPDATE_IME_PARENT, newParent.toString());
+ }
// When surface parent is removed, the relative layer will also be removed. We need to
// do a force update to make sure there is a layer set for the new parent.
assignRelativeLayerForIme(getSyncTransaction(), true /* forceUpdate */);
diff --git a/services/core/java/com/android/server/wm/EventLogTags.logtags b/services/core/java/com/android/server/wm/EventLogTags.logtags
index d94bf4b..b36f835 100644
--- a/services/core/java/com/android/server/wm/EventLogTags.logtags
+++ b/services/core/java/com/android/server/wm/EventLogTags.logtags
@@ -62,5 +62,13 @@
# bootanim finished:
31007 wm_boot_animation_done (time|2|3)
+
+# IME surface parent is updated.
+32003 imf_update_ime_parent (surface name|3)
+# IME snapshot is shown.
+32004 imf_show_ime_screenshot (target window|3),(transition|1),(surface position|3)
+# IME snapshot is hidden.
+32005 imf_remove_ime_screenshot (target window|3)
+
# Request surface flinger to show / hide the wallpaper surface.
33001 wm_wallpaper_surface (Display Id|1|5),(Visible|1),(Target|3)
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 7fd093f..90d0f16 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -35,7 +35,6 @@
import android.view.InsetsSource;
import android.view.InsetsSourceConsumer;
import android.view.InsetsSourceControl;
-import android.view.InsetsState;
import android.view.WindowInsets;
import android.view.inputmethod.ImeTracker;
import android.window.TaskSnapshot;
@@ -58,7 +57,7 @@
private Runnable mShowImeRunner;
private boolean mIsImeLayoutDrawn;
private boolean mImeShowing;
- private final InsetsSource mLastSource = new InsetsSource(ITYPE_IME);
+ private final InsetsSource mLastSource = new InsetsSource(ITYPE_IME, WindowInsets.Type.ime());
/** @see #setFrozen(boolean) */
private boolean mFrozen;
@@ -142,7 +141,7 @@
@Override
protected boolean updateClientVisibility(InsetsControlTarget caller) {
boolean changed = super.updateClientVisibility(caller);
- if (changed && caller.isRequestedVisible(InsetsState.toPublicType(mSource.getType()))) {
+ if (changed && caller.isRequestedVisible(mSource.getType())) {
reportImeDrawnForOrganizer(caller);
}
return changed;
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 67cab10..35e1fbb 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -26,11 +26,7 @@
import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
-import static android.view.InsetsState.ITYPE_CAPTION_BAR;
-import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
-import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_IME;
-import static android.view.InsetsState.ITYPE_INVALID;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.SyncRtSurfaceTransactionApplier.applyParams;
@@ -38,8 +34,6 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
-import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -66,6 +60,7 @@
import android.view.SyncRtSurfaceTransactionApplier;
import android.view.WindowInsets;
import android.view.WindowInsets.Type;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation;
import android.view.WindowInsetsAnimation.Bounds;
import android.view.WindowInsetsAnimationControlListener;
@@ -81,6 +76,10 @@
*/
class InsetsPolicy {
+ public static final int CONTROLLABLE_TYPES = WindowInsets.Type.statusBars()
+ | WindowInsets.Type.navigationBars()
+ | WindowInsets.Type.ime();
+
private final InsetsStateController mStateController;
private final DisplayContent mDisplayContent;
private final DisplayPolicy mPolicy;
@@ -98,8 +97,7 @@
return;
}
for (InsetsSourceControl control : controls) {
- final @InternalInsetsType int type = control.getType();
- if (mShowingTransientTypes.indexOf(type) != -1) {
+ if (mShowingTransientTypes.indexOf(control.getId()) != -1) {
// The visibilities of transient bars will be handled with animations.
continue;
}
@@ -109,8 +107,9 @@
// We use alpha to control the visibility here which aligns the logic at
// SurfaceAnimator.createAnimationLeash
- mDisplayContent.getPendingTransaction().setAlpha(
- leash, InsetsState.getDefaultVisibility(type) ? 1f : 0f);
+ final boolean visible =
+ (control.getType() & WindowInsets.Type.defaultVisible()) != 0;
+ mDisplayContent.getPendingTransaction().setAlpha(leash, visible ? 1f : 0f);
}
}
if (hasLeash) {
@@ -278,7 +277,6 @@
* @see WindowState#getInsetsState()
*/
InsetsState getInsetsForWindowMetrics(@NonNull WindowManager.LayoutParams attrs) {
- final @InternalInsetsType int type = getInsetsTypeForLayoutParams(attrs);
final WindowToken token = mDisplayContent.getWindowToken(attrs.token);
if (token != null) {
final InsetsState rotatedState = token.getFixedRotationTransformInsetsState();
@@ -289,109 +287,62 @@
final boolean alwaysOnTop = token != null && token.isAlwaysOnTop();
// Always use windowing mode fullscreen when get insets for window metrics to make sure it
// contains all insets types.
- final InsetsState originalState = mDisplayContent.getInsetsPolicy()
- .enforceInsetsPolicyForTarget(type, WINDOWING_MODE_FULLSCREEN, alwaysOnTop,
- attrs.type, mStateController.getRawInsetsState());
+ final InsetsState originalState = enforceInsetsPolicyForTarget(attrs,
+ WINDOWING_MODE_FULLSCREEN, alwaysOnTop, mStateController.getRawInsetsState());
InsetsState state = adjustVisibilityForTransientTypes(originalState);
return adjustInsetsForRoundedCorners(token, state, state == originalState);
}
/**
- * @param type the internal type of the insets.
- * @return {@code true} if the given type is controllable, {@code false} otherwise.
- */
- static boolean isInsetsTypeControllable(@InternalInsetsType int type) {
- switch (type) {
- case ITYPE_STATUS_BAR:
- case ITYPE_NAVIGATION_BAR:
- case ITYPE_IME:
- case ITYPE_CLIMATE_BAR:
- case ITYPE_EXTRA_NAVIGATION_BAR:
- return true;
- default:
- return false;
- }
- }
-
- private static @InternalInsetsType int getInsetsTypeForLayoutParams(
- WindowManager.LayoutParams attrs) {
- @WindowManager.LayoutParams.WindowType int type = attrs.type;
- switch (type) {
- case TYPE_STATUS_BAR:
- return ITYPE_STATUS_BAR;
- case TYPE_NAVIGATION_BAR:
- return ITYPE_NAVIGATION_BAR;
- case TYPE_INPUT_METHOD:
- return ITYPE_IME;
- }
-
- // If not one of the types above, check whether an internal inset mapping is specified.
- if (attrs.providedInsets != null) {
- for (InsetsFrameProvider provider : attrs.providedInsets) {
- switch (provider.type) {
- case ITYPE_STATUS_BAR:
- case ITYPE_NAVIGATION_BAR:
- case ITYPE_CLIMATE_BAR:
- case ITYPE_EXTRA_NAVIGATION_BAR:
- return provider.type;
- }
- }
- }
-
- return ITYPE_INVALID;
- }
-
-
- /**
- * Modifies the given {@code state} according to the {@code type} (Inset type) provided by
- * the target.
- * When performing layout of the target or dispatching insets to the target, we need to exclude
- * sources which should not be visible to the target. e.g., the source which represents the
- * target window itself, and the IME source when the target is above IME. We also need to
- * exclude certain types of insets source for client within specific windowing modes.
+ * Modifies the given {@code state} according to insets provided by the target. When performing
+ * layout of the target or dispatching insets to the target, we need to exclude sources which
+ * should not be received by the target. e.g., the visible (non-gesture-wise) source provided by
+ * the target window itself.
*
- * @param type the inset type provided by the target
+ * We also need to exclude certain types of insets source for client within specific windowing
+ * modes.
+ *
+ * @param attrs the LayoutParams of the target
* @param windowingMode the windowing mode of the target
* @param isAlwaysOnTop is the target always on top
- * @param windowType the type of the target
* @param state the input inset state containing all the sources
* @return The state stripped of the necessary information.
*/
- InsetsState enforceInsetsPolicyForTarget(@InternalInsetsType int type,
+ InsetsState enforceInsetsPolicyForTarget(WindowManager.LayoutParams attrs,
@WindowConfiguration.WindowingMode int windowingMode, boolean isAlwaysOnTop,
- int windowType, InsetsState state) {
- boolean stateCopied = false;
+ InsetsState state) {
+ final InsetsState originalState = state;
- if (type != ITYPE_INVALID) {
+ // The caller should not receive the visible insets provided by itself.
+ if (attrs.type == TYPE_INPUT_METHOD) {
state = new InsetsState(state);
- stateCopied = true;
- state.removeSource(type);
-
- // Navigation bar doesn't get influenced by anything else
- if (type == ITYPE_NAVIGATION_BAR || type == ITYPE_EXTRA_NAVIGATION_BAR) {
- state.removeSource(ITYPE_STATUS_BAR);
- state.removeSource(ITYPE_CLIMATE_BAR);
- state.removeSource(ITYPE_CAPTION_BAR);
- state.removeSource(ITYPE_NAVIGATION_BAR);
- state.removeSource(ITYPE_EXTRA_NAVIGATION_BAR);
- }
-
- // Status bar doesn't get influenced by caption bar
- if (type == ITYPE_STATUS_BAR || type == ITYPE_CLIMATE_BAR) {
- state.removeSource(ITYPE_CAPTION_BAR);
+ state.removeSource(ITYPE_IME);
+ } else if (attrs.providedInsets != null) {
+ for (InsetsFrameProvider provider : attrs.providedInsets) {
+ // TODO(b/234093736): Let InsetsFrameProvider return the public type and the ID.
+ final int sourceId = provider.type;
+ final @InsetsType int type = InsetsState.toPublicType(sourceId);
+ if ((type & WindowInsets.Type.systemBars()) == 0) {
+ continue;
+ }
+ if (state == originalState) {
+ state = new InsetsState(state);
+ }
+ state.removeSource(sourceId);
}
}
- ArrayMap<Integer, WindowContainerInsetsSourceProvider> providers = mStateController
+
+ final ArrayMap<Integer, WindowContainerInsetsSourceProvider> providers = mStateController
.getSourceProviders();
+ final int windowType = attrs.type;
for (int i = providers.size() - 1; i >= 0; i--) {
- WindowContainerInsetsSourceProvider otherProvider = providers.valueAt(i);
+ final WindowContainerInsetsSourceProvider otherProvider = providers.valueAt(i);
if (otherProvider.overridesFrame(windowType)) {
- if (!stateCopied) {
+ if (state == originalState) {
state = new InsetsState(state);
- stateCopied = true;
}
- InsetsSource override =
- new InsetsSource(state.getSource(otherProvider.getSource().getType()));
+ final InsetsSource override =
+ new InsetsSource(state.getSource(otherProvider.getSource().getId()));
override.setFrame(otherProvider.getOverriddenFrame(windowType));
state.addSource(override);
}
@@ -404,10 +355,9 @@
if (windowingMode != WINDOWING_MODE_PINNED) {
types |= WindowInsets.Type.ime();
}
- InsetsState newState = new InsetsState();
+ final InsetsState newState = new InsetsState();
newState.set(state, types);
state = newState;
- stateCopied = true;
}
return state;
@@ -628,7 +578,7 @@
return focusedWin;
}
- private boolean isShowingTransientTypes(@Type.InsetsType int types) {
+ private boolean isShowingTransientTypes(@InsetsType int types) {
final IntArray showingTransientTypes = mShowingTransientTypes;
for (int i = showingTransientTypes.size() - 1; i >= 0; i--) {
if ((InsetsState.toPublicType(showingTransientTypes.get(i)) & types) != 0) {
@@ -677,14 +627,15 @@
final SparseArray<InsetsSourceControl> controls = new SparseArray<>();
final IntArray showingTransientTypes = mShowingTransientTypes;
for (int i = showingTransientTypes.size() - 1; i >= 0; i--) {
- final @InternalInsetsType int type = showingTransientTypes.get(i);
- WindowContainerInsetsSourceProvider provider = mStateController.getSourceProvider(type);
- InsetsSourceControl control = provider.getControl(mDummyControlTarget);
+ final int sourceId = showingTransientTypes.get(i);
+ final WindowContainerInsetsSourceProvider provider =
+ mStateController.getSourceProvider(sourceId);
+ final InsetsSourceControl control = provider.getControl(mDummyControlTarget);
if (control == null || control.getLeash() == null) {
continue;
}
- typesReady |= InsetsState.toPublicType(type);
- controls.put(control.getType(), new InsetsSourceControl(control));
+ typesReady |= control.getType();
+ controls.put(sourceId, new InsetsSourceControl(control));
}
controlAnimationUnchecked(typesReady, controls, show, callback);
}
@@ -733,7 +684,7 @@
}
private void updateVisibility(@Nullable InsetsControlTarget controlTarget,
- @Type.InsetsType int type) {
+ @InsetsType int type) {
setVisible(controlTarget == null || controlTarget.isRequestedVisible(type));
}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 5b205f0..5171f5b 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -45,7 +45,6 @@
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
-import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.WindowInsets;
@@ -121,14 +120,14 @@
InsetsSourceProvider(InsetsSource source, InsetsStateController stateController,
DisplayContent displayContent) {
- mClientVisible = InsetsState.getDefaultVisibility(source.getType());
+ mClientVisible = (WindowInsets.Type.defaultVisible() & source.getType()) != 0;
mSource = source;
mDisplayContent = displayContent;
mStateController = stateController;
mFakeControl = new InsetsSourceControl(
- source.getType(), null /* leash */, false /* initialVisible */, new Point(),
- Insets.NONE);
- mControllable = InsetsPolicy.isInsetsTypeControllable(source.getType());
+ source.getId(), source.getType(), null /* leash */, false /* initialVisible */,
+ new Point(), Insets.NONE);
+ mControllable = (InsetsPolicy.CONTROLLABLE_TYPES & source.getType()) != 0;
}
InsetsSource getSource() {
@@ -166,11 +165,11 @@
// TODO: Ideally, we should wait for the animation to finish so previous window can
// animate-out as new one animates-in.
mWindowContainer.cancelAnimation();
- mWindowContainer.getProvidedInsetsSources().remove(mSource.getType());
+ mWindowContainer.getProvidedInsetsSources().remove(mSource.getId());
mSeamlessRotating = false;
}
ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "InsetsSource setWin %s for type %s",
- windowContainer, InsetsState.typeToString(mSource.getType()));
+ windowContainer, WindowInsets.Type.toString(mSource.getType()));
mWindowContainer = windowContainer;
// TODO: remove the frame provider for non-WindowState container.
mFrameProvider = frameProvider;
@@ -182,7 +181,7 @@
mSource.setInsetsRoundedCornerFrame(false);
mSourceFrame.setEmpty();
} else {
- mWindowContainer.getProvidedInsetsSources().put(mSource.getType(), mSource);
+ mWindowContainer.getProvidedInsetsSources().put(mSource.getId(), mSource);
if (mControllable) {
mWindowContainer.setControllableInsetProvider(this);
if (mPendingControlTarget != null) {
@@ -284,7 +283,7 @@
InsetsSource createSimulatedSource(DisplayFrames displayFrames, Rect frame) {
// Don't copy visible frame because it might not be calculated in the provided display
// frames and it is not significant for this usage.
- final InsetsSource source = new InsetsSource(mSource.getType());
+ final InsetsSource source = new InsetsSource(mSource.getId(), mSource.getType());
source.setVisible(mSource.isVisible());
mTmpRect.set(frame);
if (mFrameProvider != null) {
@@ -454,13 +453,12 @@
if (target == null) {
// Cancelling the animation will invoke onAnimationCancelled, resetting all the fields.
mWindowContainer.cancelAnimation();
- setClientVisible(InsetsState.getDefaultVisibility(mSource.getType()));
+ setClientVisible((WindowInsets.Type.defaultVisible() & mSource.getType()) != 0);
return;
}
final Point surfacePosition = getWindowFrameSurfacePosition();
mAdapter = new ControlAdapter(surfacePosition);
- final int type = getSource().getType();
- if (type == ITYPE_IME) {
+ if (mSource.getType() == WindowInsets.Type.ime()) {
setClientVisible(target.isRequestedVisible(WindowInsets.Type.ime()));
}
final Transaction t = mDisplayContent.getSyncTransaction();
@@ -474,8 +472,8 @@
final SurfaceControl leash = mAdapter.mCapturedLeash;
mControlTarget = target;
updateVisibility();
- mControl = new InsetsSourceControl(type, leash, mClientVisible, surfacePosition,
- mInsetsHint);
+ mControl = new InsetsSourceControl(mSource.getId(), mSource.getType(), leash,
+ mClientVisible, surfacePosition, mInsetsHint);
ProtoLog.d(WM_DEBUG_WINDOW_INSETS,
"InsetsSource Control %s for target %s", mControl, mControlTarget);
@@ -493,8 +491,7 @@
}
boolean updateClientVisibility(InsetsControlTarget caller) {
- final boolean requestedVisible =
- caller.isRequestedVisible(InsetsState.toPublicType(mSource.getType()));
+ final boolean requestedVisible = caller.isRequestedVisible(mSource.getType());
if (caller != mControlTarget || requestedVisible == mClientVisible) {
return false;
}
@@ -530,7 +527,7 @@
mSource.setVisible(mServerVisible && (isMirroredSource() || mClientVisible));
ProtoLog.d(WM_DEBUG_WINDOW_INSETS,
"InsetsSource updateVisibility for %s, serverVisible: %s clientVisible: %s",
- InsetsState.typeToString(mSource.getType()),
+ WindowInsets.Type.toString(mSource.getType()),
mServerVisible, mClientVisible);
}
@@ -560,9 +557,9 @@
// The surface transaction of preparing leash is not applied yet. We don't send it
// to the client in case that the client applies its transaction sooner than ours
// that we could unexpectedly overwrite the surface state.
- return new InsetsSourceControl(mControl.getType(), null /* leash */,
- mControl.isInitiallyVisible(), mControl.getSurfacePosition(),
- mControl.getInsetsHint());
+ return new InsetsSourceControl(mControl.getId(), mControl.getType(),
+ null /* leash */, mControl.isInitiallyVisible(),
+ mControl.getSurfacePosition(), mControl.getInsetsHint());
}
return mControl;
}
@@ -673,7 +670,7 @@
public void startAnimation(SurfaceControl animationLeash, Transaction t,
@AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) {
// TODO(b/166736352): Check if we still need to control the IME visibility here.
- if (mSource.getType() == ITYPE_IME) {
+ if (mSource.getType() == WindowInsets.Type.ime()) {
// TODO: use 0 alpha and remove t.hide() once b/138459974 is fixed.
t.setAlpha(animationLeash, 1 /* alpha */);
t.hide(animationLeash);
@@ -698,7 +695,7 @@
mControl = null;
mControlTarget = null;
mAdapter = null;
- setClientVisible(InsetsState.getDefaultVisibility(mSource.getType()));
+ setClientVisible((WindowInsets.Type.defaultVisible() & mSource.getType()) != 0);
ProtoLog.i(WM_DEBUG_WINDOW_INSETS,
"ControlAdapter onAnimationCancelled mSource: %s mControlTarget: %s",
mSource, mControlTarget);
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index ac1fbc3..455cd48 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -38,6 +38,7 @@
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.InsetsState.InternalInsetsType;
+import android.view.WindowInsets;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.inputmethod.InputMethodManagerInternal;
@@ -80,7 +81,7 @@
return;
}
for (InsetsSourceControl control : controls) {
- if (control.getType() == ITYPE_IME) {
+ if (control.getType() == WindowInsets.Type.ime()) {
mDisplayContent.mWmService.mH.post(() ->
InputMethodManagerInternal.get().removeImeSurface());
}
@@ -246,7 +247,7 @@
void notifyControlRevoked(@NonNull InsetsControlTarget previousControlTarget,
InsetsSourceProvider provider) {
- removeFromControlMaps(previousControlTarget, provider.getSource().getType(),
+ removeFromControlMaps(previousControlTarget, provider.getSource().getId(),
false /* fake */);
}
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index c827062..4be1c83 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -1348,8 +1348,7 @@
+ " activityType=" + task.getActivityType()
+ " windowingMode=" + task.getWindowingMode()
+ " isAlwaysOnTopWhenVisible=" + task.isAlwaysOnTopWhenVisible()
- + " intentFlags=" + task.getBaseIntent().getFlags()
- + " isEmbedded=" + task.isEmbedded());
+ + " intentFlags=" + task.getBaseIntent().getFlags());
}
switch (task.getActivityType()) {
@@ -1385,11 +1384,6 @@
return false;
}
- // Ignore the task if it is a embedded task
- if (task.isEmbedded()) {
- return false;
- }
-
// Ignore the task if it is started on a display which is not allow to show its tasks on
// Recents.
if (task.getDisplayContent() != null
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index bffab7a..57fca3ae 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -904,7 +904,7 @@
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
final TaskAnimationAdapter adapter = mPendingAnimations.get(i);
final Task task = adapter.mTask;
- snapshotController.recordTaskSnapshot(task, false /* allowSnapshotHome */);
+ snapshotController.recordSnapshot(task, false /* allowSnapshotHome */);
final TaskSnapshot snapshot = snapshotController.getSnapshot(task.mTaskId, task.mUserId,
false /* restoreFromDisk */, false /* isLowResolution */);
if (snapshot != null) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 749fa1f..ca9b9b3 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -350,25 +350,8 @@
return false;
}
- if (matchingCandidate(task)) {
- return true;
- }
-
- // Looking for the embedded tasks (if any)
- return !task.isLeafTaskFragment() && task.forAllLeafTaskFragments(
- this::matchingCandidate);
- }
-
- boolean matchingCandidate(TaskFragment taskFragment) {
- final Task task = taskFragment.asTask();
- if (task == null) {
- return false;
- }
-
// Overlays should not be considered as the task's logical top activity.
- // Activities of the tasks that embedded from this one should not be used.
- final ActivityRecord r = task.getTopNonFinishingActivity(false /* includeOverlays */,
- false /* includingEmbeddedTask */);
+ final ActivityRecord r = task.getTopNonFinishingActivity(false /* includeOverlays */);
if (r == null || r.finishing || r.mUserId != userId
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
@@ -2109,7 +2092,7 @@
// Record the snapshot now, it will be later fetched for content-pip animation.
// We do this early in the process to make sure the right snapshot is used for
// entering content-pip animation.
- mWindowManager.mTaskSnapshotController.recordTaskSnapshot(
+ mWindowManager.mTaskSnapshotController.recordSnapshot(
task, false /* allowSnapshotHome */);
rootTask.setBounds(r.pictureInPictureArgs.getSourceRectHint());
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index b482181..bf3edcf 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -856,23 +856,19 @@
@Override
public void grantInputChannel(int displayId, SurfaceControl surface,
IWindow window, IBinder hostInputToken, int flags, int privateFlags, int type,
- IBinder focusGrantToken, String inputHandleName, InputChannel outInputChannel) {
+ IBinder windowToken, IBinder focusGrantToken, String inputHandleName,
+ InputChannel outInputChannel) {
if (hostInputToken == null && !mCanAddInternalSystemWindow) {
// Callers without INTERNAL_SYSTEM_WINDOW permission cannot grant input channel to
// embedded windows without providing a host window input token
throw new SecurityException("Requires INTERNAL_SYSTEM_WINDOW permission");
}
- if (!mCanAddInternalSystemWindow && type != 0) {
- Slog.w(TAG_WM, "Requires INTERNAL_SYSTEM_WINDOW permission if assign type to"
- + " input");
- }
-
final long identity = Binder.clearCallingIdentity();
try {
mService.grantInputChannel(this, mUid, mPid, displayId, surface, window, hostInputToken,
flags, mCanAddInternalSystemWindow ? privateFlags : 0,
- mCanAddInternalSystemWindow ? type : 0, focusGrantToken, inputHandleName,
+ type, windowToken, focusGrantToken, inputHandleName,
outInputChannel);
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
new file mode 100644
index 0000000..fdc3616
--- /dev/null
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.graphics.Bitmap.CompressFormat.JPEG;
+
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.graphics.Bitmap;
+import android.os.Process;
+import android.os.SystemClock;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.window.TaskSnapshot;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
+import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayDeque;
+
+/**
+ * Singleton worker thread to queue up persist or delete tasks of {@link TaskSnapshot}s to disk.
+ */
+class SnapshotPersistQueue {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM;
+ private static final long DELAY_MS = 100;
+ private static final int MAX_STORE_QUEUE_DEPTH = 2;
+ private static final int COMPRESS_QUALITY = 95;
+
+ @GuardedBy("mLock")
+ private final ArrayDeque<WriteQueueItem> mWriteQueue = new ArrayDeque<>();
+ @GuardedBy("mLock")
+ private final ArrayDeque<StoreWriteQueueItem> mStoreQueueItems = new ArrayDeque<>();
+ @GuardedBy("mLock")
+ private boolean mQueueIdling;
+ @GuardedBy("mLock")
+ private boolean mPaused;
+ private boolean mStarted;
+ private final Object mLock = new Object();
+ private final UserManagerInternal mUserManagerInternal;
+
+ SnapshotPersistQueue() {
+ mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
+ }
+
+ Object getLock() {
+ return mLock;
+ }
+
+ void systemReady() {
+ start();
+ }
+
+ /**
+ * Starts persisting.
+ */
+ void start() {
+ if (!mStarted) {
+ mStarted = true;
+ mPersister.start();
+ }
+ }
+
+ /**
+ * Temporarily pauses/unpauses persisting of task snapshots.
+ *
+ * @param paused Whether task snapshot persisting should be paused.
+ */
+ void setPaused(boolean paused) {
+ synchronized (mLock) {
+ mPaused = paused;
+ if (!paused) {
+ mLock.notifyAll();
+ }
+ }
+ }
+
+ @TestApi
+ void waitForQueueEmpty() {
+ while (true) {
+ synchronized (mLock) {
+ if (mWriteQueue.isEmpty() && mQueueIdling) {
+ return;
+ }
+ }
+ SystemClock.sleep(DELAY_MS);
+ }
+ }
+
+ @GuardedBy("mLock")
+ void sendToQueueLocked(WriteQueueItem item) {
+ mWriteQueue.offer(item);
+ item.onQueuedLocked();
+ ensureStoreQueueDepthLocked();
+ if (!mPaused) {
+ mLock.notifyAll();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void ensureStoreQueueDepthLocked() {
+ while (mStoreQueueItems.size() > MAX_STORE_QUEUE_DEPTH) {
+ final StoreWriteQueueItem item = mStoreQueueItems.poll();
+ mWriteQueue.remove(item);
+ Slog.i(TAG, "Queue is too deep! Purged item with index=" + item.mId);
+ }
+ }
+
+ private void deleteSnapshot(int index, int userId, PersistInfoProvider provider) {
+ final File protoFile = provider.getProtoFile(index, userId);
+ final File bitmapLowResFile = provider.getLowResolutionBitmapFile(index, userId);
+ protoFile.delete();
+ if (bitmapLowResFile.exists()) {
+ bitmapLowResFile.delete();
+ }
+ final File bitmapFile = provider.getHighResolutionBitmapFile(index, userId);
+ if (bitmapFile.exists()) {
+ bitmapFile.delete();
+ }
+ }
+
+ private final Thread mPersister = new Thread("TaskSnapshotPersister") {
+ public void run() {
+ android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+ while (true) {
+ WriteQueueItem next;
+ boolean isReadyToWrite = false;
+ synchronized (mLock) {
+ if (mPaused) {
+ next = null;
+ } else {
+ next = mWriteQueue.poll();
+ if (next != null) {
+ if (next.isReady()) {
+ isReadyToWrite = true;
+ next.onDequeuedLocked();
+ } else {
+ mWriteQueue.addLast(next);
+ }
+ }
+ }
+ }
+ if (next != null) {
+ if (isReadyToWrite) {
+ next.write();
+ }
+ SystemClock.sleep(DELAY_MS);
+ }
+ synchronized (mLock) {
+ final boolean writeQueueEmpty = mWriteQueue.isEmpty();
+ if (!writeQueueEmpty && !mPaused) {
+ continue;
+ }
+ try {
+ mQueueIdling = writeQueueEmpty;
+ mLock.wait();
+ mQueueIdling = false;
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+ };
+
+ abstract static class WriteQueueItem {
+ protected final PersistInfoProvider mPersistInfoProvider;
+ WriteQueueItem(@NonNull PersistInfoProvider persistInfoProvider) {
+ mPersistInfoProvider = persistInfoProvider;
+ }
+ /**
+ * @return {@code true} if item is ready to have {@link WriteQueueItem#write} called
+ */
+ boolean isReady() {
+ return true;
+ }
+
+ abstract void write();
+
+ /**
+ * Called when this queue item has been put into the queue.
+ */
+ void onQueuedLocked() {
+ }
+
+ /**
+ * Called when this queue item has been taken out of the queue.
+ */
+ void onDequeuedLocked() {
+ }
+ }
+
+ StoreWriteQueueItem createStoreWriteQueueItem(int id, int userId, TaskSnapshot snapshot,
+ PersistInfoProvider provider) {
+ return new StoreWriteQueueItem(id, userId, snapshot, provider);
+ }
+
+ class StoreWriteQueueItem extends WriteQueueItem {
+ private final int mId;
+ private final int mUserId;
+ private final TaskSnapshot mSnapshot;
+
+ StoreWriteQueueItem(int id, int userId, TaskSnapshot snapshot,
+ PersistInfoProvider provider) {
+ super(provider);
+ mId = id;
+ mUserId = userId;
+ mSnapshot = snapshot;
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ void onQueuedLocked() {
+ mStoreQueueItems.offer(this);
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ void onDequeuedLocked() {
+ mStoreQueueItems.remove(this);
+ }
+
+ @Override
+ boolean isReady() {
+ return mUserManagerInternal.isUserUnlocked(mUserId);
+ }
+
+ @Override
+ void write() {
+ if (!mPersistInfoProvider.createDirectory(mUserId)) {
+ Slog.e(TAG, "Unable to create snapshot directory for user dir="
+ + mPersistInfoProvider.getDirectory(mUserId));
+ }
+ boolean failed = false;
+ if (!writeProto()) {
+ failed = true;
+ }
+ if (!writeBuffer()) {
+ failed = true;
+ }
+ if (failed) {
+ deleteSnapshot(mId, mUserId, mPersistInfoProvider);
+ }
+ }
+
+ boolean writeProto() {
+ final TaskSnapshotProto proto = new TaskSnapshotProto();
+ proto.orientation = mSnapshot.getOrientation();
+ proto.rotation = mSnapshot.getRotation();
+ proto.taskWidth = mSnapshot.getTaskSize().x;
+ proto.taskHeight = mSnapshot.getTaskSize().y;
+ proto.insetLeft = mSnapshot.getContentInsets().left;
+ proto.insetTop = mSnapshot.getContentInsets().top;
+ proto.insetRight = mSnapshot.getContentInsets().right;
+ proto.insetBottom = mSnapshot.getContentInsets().bottom;
+ proto.letterboxInsetLeft = mSnapshot.getLetterboxInsets().left;
+ proto.letterboxInsetTop = mSnapshot.getLetterboxInsets().top;
+ proto.letterboxInsetRight = mSnapshot.getLetterboxInsets().right;
+ proto.letterboxInsetBottom = mSnapshot.getLetterboxInsets().bottom;
+ proto.isRealSnapshot = mSnapshot.isRealSnapshot();
+ proto.windowingMode = mSnapshot.getWindowingMode();
+ proto.appearance = mSnapshot.getAppearance();
+ proto.isTranslucent = mSnapshot.isTranslucent();
+ proto.topActivityComponent = mSnapshot.getTopActivityComponent().flattenToString();
+ proto.id = mSnapshot.getId();
+ final byte[] bytes = TaskSnapshotProto.toByteArray(proto);
+ final File file = mPersistInfoProvider.getProtoFile(mId, mUserId);
+ final AtomicFile atomicFile = new AtomicFile(file);
+ FileOutputStream fos = null;
+ try {
+ fos = atomicFile.startWrite();
+ fos.write(bytes);
+ atomicFile.finishWrite(fos);
+ } catch (IOException e) {
+ atomicFile.failWrite(fos);
+ Slog.e(TAG, "Unable to open " + file + " for persisting. " + e);
+ return false;
+ }
+ return true;
+ }
+
+ boolean writeBuffer() {
+ if (AbsAppSnapshotController.isInvalidHardwareBuffer(mSnapshot.getHardwareBuffer())) {
+ Slog.e(TAG, "Invalid task snapshot hw buffer, taskId=" + mId);
+ return false;
+ }
+ final Bitmap bitmap = Bitmap.wrapHardwareBuffer(
+ mSnapshot.getHardwareBuffer(), mSnapshot.getColorSpace());
+ if (bitmap == null) {
+ Slog.e(TAG, "Invalid task snapshot hw bitmap");
+ return false;
+ }
+
+ final Bitmap swBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false /* isMutable */);
+
+ final File file = mPersistInfoProvider.getHighResolutionBitmapFile(mId, mUserId);
+ try {
+ FileOutputStream fos = new FileOutputStream(file);
+ swBitmap.compress(JPEG, COMPRESS_QUALITY, fos);
+ fos.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to open " + file + " for persisting.", e);
+ return false;
+ }
+
+ if (!mPersistInfoProvider.enableLowResSnapshots()) {
+ swBitmap.recycle();
+ return true;
+ }
+
+ final Bitmap lowResBitmap = Bitmap.createScaledBitmap(swBitmap,
+ (int) (bitmap.getWidth() * mPersistInfoProvider.lowResScaleFactor()),
+ (int) (bitmap.getHeight() * mPersistInfoProvider.lowResScaleFactor()),
+ true /* filter */);
+ swBitmap.recycle();
+
+ final File lowResFile = mPersistInfoProvider.getLowResolutionBitmapFile(mId, mUserId);
+ try {
+ FileOutputStream lowResFos = new FileOutputStream(lowResFile);
+ lowResBitmap.compress(JPEG, COMPRESS_QUALITY, lowResFos);
+ lowResFos.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to open " + lowResFile + " for persisting.", e);
+ return false;
+ }
+ lowResBitmap.recycle();
+
+ return true;
+ }
+ }
+
+ DeleteWriteQueueItem createDeleteWriteQueueItem(int id, int userId,
+ PersistInfoProvider provider) {
+ return new DeleteWriteQueueItem(id, userId, provider);
+ }
+
+ private class DeleteWriteQueueItem extends WriteQueueItem {
+ private final int mId;
+ private final int mUserId;
+
+ DeleteWriteQueueItem(int id, int userId, PersistInfoProvider provider) {
+ super(provider);
+ mId = id;
+ mUserId = userId;
+ }
+
+ @Override
+ void write() {
+ deleteSnapshot(mId, mUserId, mPersistInfoProvider);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 21f470d..fdb5f1f 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2980,9 +2980,8 @@
// Found it. This activity on top of the given activity on the same TaskFragment.
return true;
}
- if (isSelfOrNonEmbeddedTask(parent.asTask())) {
- // Found it. This activity is the direct child of a leaf Task without being
- // embedded.
+ if (parent != null && parent.asTask() != null) {
+ // Found it. This activity is the direct child of a leaf Task.
return true;
}
// The candidate activity is being embedded. Checking if the bounds of the containing
@@ -2993,7 +2992,7 @@
// Not occluding the grandparent.
break;
}
- if (isSelfOrNonEmbeddedTask(grandParent.asTask())) {
+ if (grandParent.asTask() != null) {
// Found it. The activity occludes its parent TaskFragment and the parent
// TaskFragment also occludes its parent all the way up.
return true;
@@ -3006,13 +3005,6 @@
return top != activity ? top : null;
}
- private boolean isSelfOrNonEmbeddedTask(Task task) {
- if (task == this) {
- return true;
- }
- return task != null && !task.isEmbedded();
- }
-
@Override
public SurfaceControl.Builder makeAnimationLeash() {
return super.makeAnimationLeash().setMetadata(METADATA_TASK_ID, mTaskId);
@@ -5091,14 +5083,6 @@
// window manager to keep the previous window it had previously
// created, if it still had one.
Task baseTask = r.getTask();
- if (baseTask.isEmbedded()) {
- // If the task is embedded in a task fragment, there may have an existing
- // starting window in the parent task. This allows the embedded activities
- // to share the starting window and make sure that the window can have top
- // z-order by transferring to the top activity.
- baseTask = baseTask.getParent().asTaskFragment().getTask();
- }
-
final ActivityRecord prev = baseTask.getActivity(
a -> a.mStartingData != null && a.showToCurrentUser());
mWmService.mStartingSurfaceController.showStartingWindow(r, prev, newTask,
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 66b7342..0457408 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1074,10 +1074,14 @@
// Use launch-adjacent-flag-root if launching with launch-adjacent flag.
if ((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0
&& mLaunchAdjacentFlagRootTask != null) {
- // If the adjacent launch is coming from the same root, launch to adjacent root instead.
- if (sourceTask != null && mLaunchAdjacentFlagRootTask.getAdjacentTaskFragment() != null
+ if (sourceTask != null && sourceTask == candidateTask) {
+ // Do nothing when task that is getting opened is same as the source.
+ } else if (sourceTask != null
+ && mLaunchAdjacentFlagRootTask.getAdjacentTaskFragment() != null
&& (sourceTask == mLaunchAdjacentFlagRootTask
|| sourceTask.isDescendantOf(mLaunchAdjacentFlagRootTask))) {
+ // If the adjacent launch is coming from the same root, launch to
+ // adjacent root instead.
return mLaunchAdjacentFlagRootTask.getAdjacentTaskFragment().asTask();
} else {
return mLaunchAdjacentFlagRootTask;
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 6219203..ae3b2f2 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -927,63 +927,36 @@
return getTopNonFinishingActivity(true /* includeOverlays */);
}
- ActivityRecord getTopNonFinishingActivity(boolean includeOverlays) {
- return getTopNonFinishingActivity(includeOverlays, true /* includingEmbeddedTask */);
- }
-
/**
* Returns the top-most non-finishing activity, even if the activity is NOT ok to show to
* the current user.
* @param includeOverlays whether the task overlay activity should be included.
- * @param includingEmbeddedTask whether the activity in a task that being embedded from this
- * one should be included.
- * @see #topRunningActivity(boolean, boolean)
+ * @see #topRunningActivity(boolean)
*/
- ActivityRecord getTopNonFinishingActivity(boolean includeOverlays,
- boolean includingEmbeddedTask) {
- // Split into 4 to avoid object creation due to variable capture.
+ ActivityRecord getTopNonFinishingActivity(boolean includeOverlays) {
+ // Split into 2 to avoid object creation due to variable capture.
if (includeOverlays) {
- if (includingEmbeddedTask) {
- return getActivity((r) -> !r.finishing);
- }
- return getActivity((r) -> !r.finishing && r.getTask() == this.getTask());
+ return getActivity((r) -> !r.finishing);
}
-
- if (includingEmbeddedTask) {
- return getActivity((r) -> !r.finishing && !r.isTaskOverlay());
- }
- return getActivity(
- (r) -> !r.finishing && !r.isTaskOverlay() && r.getTask() == this.getTask());
+ return getActivity((r) -> !r.finishing && !r.isTaskOverlay());
}
ActivityRecord topRunningActivity() {
return topRunningActivity(false /* focusableOnly */);
}
- ActivityRecord topRunningActivity(boolean focusableOnly) {
- return topRunningActivity(focusableOnly, true /* includingEmbeddedTask */);
- }
-
/**
* Returns the top-most running activity, which the activity is non-finishing and ok to show
* to the current user.
*
* @see ActivityRecord#canBeTopRunning()
*/
- ActivityRecord topRunningActivity(boolean focusableOnly, boolean includingEmbeddedTask) {
- // Split into 4 to avoid object creation due to variable capture.
+ ActivityRecord topRunningActivity(boolean focusableOnly) {
+ // Split into 2 to avoid object creation due to variable capture.
if (focusableOnly) {
- if (includingEmbeddedTask) {
- return getActivity((r) -> r.canBeTopRunning() && r.isFocusable());
- }
- return getActivity(
- (r) -> r.canBeTopRunning() && r.isFocusable() && r.getTask() == this.getTask());
+ return getActivity((r) -> r.canBeTopRunning() && r.isFocusable());
}
-
- if (includingEmbeddedTask) {
- return getActivity(ActivityRecord::canBeTopRunning);
- }
- return getActivity((r) -> r.canBeTopRunning() && r.getTask() == this.getTask());
+ return getActivity(ActivityRecord::canBeTopRunning);
}
int getNonFinishingActivityCount() {
@@ -2003,9 +1976,7 @@
}
final Task thisTask = asTask();
- // Embedded Task's configuration should go with parent TaskFragment, so we don't re-compute
- // configuration here.
- if (thisTask != null && !thisTask.isEmbedded()) {
+ if (thisTask != null) {
thisTask.resolveLeafTaskOnlyOverrideConfigs(newParentConfig,
mTmpBounds /* previousBounds */);
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotCache.java b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
index 3c437ba..55e863e 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotCache.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
@@ -18,38 +18,28 @@
import android.annotation.Nullable;
import android.window.TaskSnapshot;
-import android.util.ArrayMap;
-
-import java.io.PrintWriter;
/**
* Caches snapshots. See {@link TaskSnapshotController}.
* <p>
* Access to this class should be guarded by the global window manager lock.
*/
-class TaskSnapshotCache {
+class TaskSnapshotCache extends AbsAppSnapshotCache<Task> {
- private final WindowManagerService mService;
- private final TaskSnapshotLoader mLoader;
- private final ArrayMap<ActivityRecord, Integer> mAppTaskMap = new ArrayMap<>();
- private final ArrayMap<Integer, CacheEntry> mRunningCache = new ArrayMap<>();
+ private final AppSnapshotLoader mLoader;
- TaskSnapshotCache(WindowManagerService service, TaskSnapshotLoader loader) {
- mService = service;
+ TaskSnapshotCache(WindowManagerService service, AppSnapshotLoader loader) {
+ super(service, "Task");
mLoader = loader;
}
- void clearRunningCache() {
- mRunningCache.clear();
- }
-
void putSnapshot(Task task, TaskSnapshot snapshot) {
final CacheEntry entry = mRunningCache.get(task.mTaskId);
if (entry != null) {
- mAppTaskMap.remove(entry.topApp);
+ mAppIdMap.remove(entry.topApp);
}
final ActivityRecord top = task.getTopMostActivity();
- mAppTaskMap.put(top, task.mTaskId);
+ mAppIdMap.put(top, task.mTaskId);
mRunningCache.put(task.mTaskId, new CacheEntry(snapshot, top));
}
@@ -58,13 +48,9 @@
*/
@Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
boolean isLowResolution) {
-
- synchronized (mService.mGlobalLock) {
- // Try the running cache.
- final CacheEntry entry = mRunningCache.get(taskId);
- if (entry != null) {
- return entry.snapshot;
- }
+ final TaskSnapshot snapshot = getSnapshot(taskId);
+ if (snapshot != null) {
+ return snapshot;
}
// Try to restore from disk if asked.
@@ -78,68 +64,6 @@
* DO NOT HOLD THE WINDOW MANAGER LOCK WHEN CALLING THIS METHOD!
*/
private TaskSnapshot tryRestoreFromDisk(int taskId, int userId, boolean isLowResolution) {
- final TaskSnapshot snapshot = mLoader.loadTask(taskId, userId, isLowResolution);
- if (snapshot == null) {
- return null;
- }
- return snapshot;
- }
-
- /**
- * Called when an app token has been removed
- */
- void onAppRemoved(ActivityRecord activity) {
- final Integer taskId = mAppTaskMap.get(activity);
- if (taskId != null) {
- removeRunningEntry(taskId);
- }
- }
-
- /**
- * Callend when an app window token's process died.
- */
- void onAppDied(ActivityRecord activity) {
- final Integer taskId = mAppTaskMap.get(activity);
- if (taskId != null) {
- removeRunningEntry(taskId);
- }
- }
-
- void onTaskRemoved(int taskId) {
- removeRunningEntry(taskId);
- }
-
- void removeRunningEntry(int taskId) {
- final CacheEntry entry = mRunningCache.get(taskId);
- if (entry != null) {
- mAppTaskMap.remove(entry.topApp);
- mRunningCache.remove(taskId);
- }
- }
-
- void dump(PrintWriter pw, String prefix) {
- final String doublePrefix = prefix + " ";
- final String triplePrefix = doublePrefix + " ";
- pw.println(prefix + "SnapshotCache");
- for (int i = mRunningCache.size() - 1; i >= 0; i--) {
- final CacheEntry entry = mRunningCache.valueAt(i);
- pw.println(doublePrefix + "Entry taskId=" + mRunningCache.keyAt(i));
- pw.println(triplePrefix + "topApp=" + entry.topApp);
- pw.println(triplePrefix + "snapshot=" + entry.snapshot);
- }
- }
-
- private static final class CacheEntry {
-
- /** The snapshot. */
- final TaskSnapshot snapshot;
-
- /** The app token that was on top of the task when the snapshot was taken */
- final ActivityRecord topApp;
-
- CacheEntry(TaskSnapshot snapshot, ActivityRecord topApp) {
- this.snapshot = snapshot;
- this.topApp = topApp;
- }
+ return mLoader.loadTask(taskId, userId, isLowResolution);
}
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index c1b9e662..2037fdc 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -17,44 +17,27 @@
package com.android.server.wm;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.pm.PackageManager;
-import android.graphics.Bitmap;
+import android.app.ActivityManager;
import android.graphics.PixelFormat;
-import android.graphics.Point;
-import android.graphics.RecordingCanvas;
import android.graphics.Rect;
-import android.graphics.RenderNode;
-import android.hardware.HardwareBuffer;
import android.os.Environment;
import android.os.Handler;
-import android.os.Trace;
import android.util.ArraySet;
-import android.util.Pair;
import android.util.Slog;
import android.view.Display;
-import android.view.InsetsState;
-import android.view.SurfaceControl;
-import android.view.ThreadedRenderer;
-import android.view.WindowInsets.Type;
-import android.view.WindowInsetsController.Appearance;
-import android.view.WindowManager.LayoutParams;
import android.window.ScreenCapture;
-import android.window.SnapshotDrawerUtils;
import android.window.TaskSnapshot;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.ColorUtils;
import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
-import com.android.server.wm.utils.InsetUtils;
+import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
import com.google.android.collect.Sets;
-import java.io.PrintWriter;
import java.util.Set;
/**
@@ -70,75 +53,60 @@
* <p>
* To access this class, acquire the global window manager lock.
*/
-class TaskSnapshotController {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotController" : TAG_WM;
+class TaskSnapshotController extends AbsAppSnapshotController<Task, TaskSnapshotCache> {
+ static final String SNAPSHOTS_DIRNAME = "snapshots";
- /**
- * Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be
- * used as the snapshot.
- */
- @VisibleForTesting
- static final int SNAPSHOT_MODE_REAL = 0;
-
- /**
- * Return value for {@link #getSnapshotMode}: We are not allowed to take a real screenshot but
- * we should try to use the app theme to create a fake representation of the app.
- */
- @VisibleForTesting
- static final int SNAPSHOT_MODE_APP_THEME = 1;
-
- /**
- * Return value for {@link #getSnapshotMode}: We aren't allowed to take any snapshot.
- */
- @VisibleForTesting
- static final int SNAPSHOT_MODE_NONE = 2;
-
- private final WindowManagerService mService;
-
- private final TaskSnapshotCache mCache;
private final TaskSnapshotPersister mPersister;
- private final TaskSnapshotLoader mLoader;
private final ArraySet<Task> mSkipClosingAppSnapshotTasks = new ArraySet<>();
private final ArraySet<Task> mTmpTasks = new ArraySet<>();
private final Handler mHandler = new Handler();
- private final float mHighResTaskSnapshotScale;
- private final Rect mTmpRect = new Rect();
+ private final PersistInfoProvider mPersistInfoProvider;
- /**
- * Flag indicating whether we are running on an Android TV device.
- */
- private final boolean mIsRunningOnTv;
+ TaskSnapshotController(WindowManagerService service, SnapshotPersistQueue persistQueue) {
+ super(service);
+ mPersistInfoProvider = createPersistInfoProvider(service,
+ Environment::getDataSystemCeDirectory);
+ mPersister = new TaskSnapshotPersister(persistQueue, mPersistInfoProvider);
- /**
- * Flag indicating whether we are running on an IoT device.
- */
- private final boolean mIsRunningOnIoT;
-
- /**
- * Flag indicating if task snapshot is enabled on this device.
- */
- private boolean mTaskSnapshotEnabled;
-
- TaskSnapshotController(WindowManagerService service) {
- mService = service;
- mPersister = new TaskSnapshotPersister(mService, Environment::getDataSystemCeDirectory);
- mLoader = new TaskSnapshotLoader(mPersister);
- mCache = new TaskSnapshotCache(mService, mLoader);
- mIsRunningOnTv = mService.mContext.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_LEANBACK);
- mIsRunningOnIoT = mService.mContext.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_EMBEDDED);
- mHighResTaskSnapshotScale = mService.mContext.getResources().getFloat(
- com.android.internal.R.dimen.config_highResTaskSnapshotScale);
- mTaskSnapshotEnabled =
- !mService.mContext
+ initialize(new TaskSnapshotCache(service, new AppSnapshotLoader(mPersistInfoProvider)));
+ final boolean snapshotEnabled =
+ !service.mContext
.getResources()
.getBoolean(com.android.internal.R.bool.config_disableTaskSnapshots);
+ setSnapshotEnabled(snapshotEnabled);
}
- void systemReady() {
- mPersister.start();
+ static PersistInfoProvider createPersistInfoProvider(WindowManagerService service,
+ BaseAppSnapshotPersister.DirectoryResolver resolver) {
+ final float highResTaskSnapshotScale = service.mContext.getResources().getFloat(
+ com.android.internal.R.dimen.config_highResTaskSnapshotScale);
+ final float lowResTaskSnapshotScale = service.mContext.getResources().getFloat(
+ com.android.internal.R.dimen.config_lowResTaskSnapshotScale);
+
+ if (lowResTaskSnapshotScale < 0 || 1 <= lowResTaskSnapshotScale) {
+ throw new RuntimeException("Low-res scale must be between 0 and 1");
+ }
+ if (highResTaskSnapshotScale <= 0 || 1 < highResTaskSnapshotScale) {
+ throw new RuntimeException("High-res scale must be between 0 and 1");
+ }
+ if (highResTaskSnapshotScale <= lowResTaskSnapshotScale) {
+ throw new RuntimeException("High-res scale must be greater than low-res scale");
+ }
+
+ final float lowResScaleFactor;
+ final boolean enableLowResSnapshots;
+ if (lowResTaskSnapshotScale > 0) {
+ lowResScaleFactor = lowResTaskSnapshotScale / highResTaskSnapshotScale;
+ enableLowResSnapshots = true;
+ } else {
+ lowResScaleFactor = 0;
+ enableLowResSnapshots = false;
+ }
+ final boolean use16BitFormat = service.mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_use16BitTaskSnapshotPixelFormat);
+ return new PersistInfoProvider(resolver, SNAPSHOTS_DIRNAME,
+ enableLowResSnapshots, lowResScaleFactor, use16BitFormat);
}
void onTransitionStarting(DisplayContent displayContent) {
@@ -182,60 +150,18 @@
snapshotTasks(tasks, false /* allowSnapshotHome */);
}
- /**
- * This is different than {@link #recordTaskSnapshot(Task, boolean)} because it doesn't store
- * the snapshot to the cache and returns the TaskSnapshot immediately.
- *
- * This is only used for testing so the snapshot content can be verified.
- */
- @VisibleForTesting
- TaskSnapshot captureTaskSnapshot(Task task, boolean snapshotHome) {
- final TaskSnapshot snapshot;
- if (snapshotHome) {
- snapshot = snapshotTask(task);
- } else {
- switch (getSnapshotMode(task)) {
- case SNAPSHOT_MODE_NONE:
- return null;
- case SNAPSHOT_MODE_APP_THEME:
- snapshot = drawAppThemeSnapshot(task);
- break;
- case SNAPSHOT_MODE_REAL:
- snapshot = snapshotTask(task);
- break;
- default:
- snapshot = null;
- break;
- }
- }
- return snapshot;
- }
-
- void recordTaskSnapshot(Task task, boolean allowSnapshotHome) {
+ void recordSnapshot(Task task, boolean allowSnapshotHome) {
final boolean snapshotHome = allowSnapshotHome && task.isActivityTypeHome();
- final TaskSnapshot snapshot = captureTaskSnapshot(task, snapshotHome);
- if (snapshot == null) {
- return;
- }
-
- final HardwareBuffer buffer = snapshot.getHardwareBuffer();
- if (buffer.getWidth() == 0 || buffer.getHeight() == 0) {
- buffer.close();
- Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x"
- + buffer.getHeight());
- } else {
- mCache.putSnapshot(task, snapshot);
- // Don't persist or notify the change for the temporal snapshot.
- if (!snapshotHome) {
- mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
- task.onSnapshotChanged(snapshot);
- }
+ final TaskSnapshot snapshot = recordSnapshotInner(task, allowSnapshotHome);
+ if (!snapshotHome && snapshot != null) {
+ mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
+ task.onSnapshotChanged(snapshot);
}
}
private void snapshotTasks(ArraySet<Task> tasks, boolean allowSnapshotHome) {
for (int i = tasks.size() - 1; i >= 0; i--) {
- recordTaskSnapshot(tasks.valueAt(i), allowSnapshotHome);
+ recordSnapshot(tasks.valueAt(i), allowSnapshotHome);
}
}
@@ -247,7 +173,7 @@
TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
boolean isLowResolution) {
return mCache.getSnapshot(taskId, userId, restoreFromDisk, isLowResolution
- && mPersister.enableLowResSnapshots());
+ && mPersistInfoProvider.enableLowResSnapshots());
}
/**
@@ -262,7 +188,7 @@
* we're looking for, but during app transitions, trampoline activities can appear in the
* children, which should be ignored.
*/
- @Nullable private ActivityRecord findAppTokenForSnapshot(Task task) {
+ @Nullable protected ActivityRecord findAppTokenForSnapshot(Task task) {
return task.getActivity((r) -> {
if (r == null || !r.isSurfaceShowing() || r.findMainWindow() == null) {
return false;
@@ -277,112 +203,10 @@
});
}
- /**
- * Validates the state of the Task is appropriate to capture a snapshot, collects
- * information from the task and populates the builder.
- *
- * @param task the task to capture
- * @param pixelFormat the desired pixel format, or {@link PixelFormat#UNKNOWN} to
- * automatically select
- * @param builder the snapshot builder to populate
- *
- * @return true if the state of the task is ok to proceed
- */
- @VisibleForTesting
- boolean prepareTaskSnapshot(Task task, int pixelFormat, TaskSnapshot.Builder builder) {
- final Pair<ActivityRecord, WindowState> result = checkIfReadyToSnapshot(task);
- if (result == null) {
- return false;
- }
- final ActivityRecord activity = result.first;
- final WindowState mainWindow = result.second;
- final Rect contentInsets = getSystemBarInsets(mainWindow.getFrame(),
- mainWindow.getInsetsStateWithVisibilityOverride());
- final Rect letterboxInsets = activity.getLetterboxInsets();
- InsetUtils.addInsets(contentInsets, letterboxInsets);
- builder.setIsRealSnapshot(true);
- builder.setId(System.currentTimeMillis());
- builder.setContentInsets(contentInsets);
- builder.setLetterboxInsets(letterboxInsets);
-
- final boolean isWindowTranslucent = mainWindow.getAttrs().format != PixelFormat.OPAQUE;
- final boolean isShowWallpaper = mainWindow.hasWallpaper();
-
- if (pixelFormat == PixelFormat.UNKNOWN) {
- pixelFormat = mPersister.use16BitFormat() && activity.fillsParent()
- && !(isWindowTranslucent && isShowWallpaper)
- ? PixelFormat.RGB_565
- : PixelFormat.RGBA_8888;
- }
-
- final boolean isTranslucent = PixelFormat.formatHasAlpha(pixelFormat)
- && (!activity.fillsParent() || isWindowTranslucent);
-
- builder.setTopActivityComponent(activity.mActivityComponent);
- builder.setPixelFormat(pixelFormat);
- builder.setIsTranslucent(isTranslucent);
- builder.setOrientation(activity.getTask().getConfiguration().orientation);
- builder.setRotation(activity.getTask().getDisplayContent().getRotation());
- builder.setWindowingMode(task.getWindowingMode());
- builder.setAppearance(getAppearance(task));
- return true;
- }
-
- /**
- * Check if the state of the Task is appropriate to capture a snapshot, such like the task
- * snapshot or the associated IME surface snapshot.
- *
- * @param task the target task to capture the snapshot
- * @return Pair of (the top activity of the task, the main window of the task) if passed the
- * state checking. Returns {@code null} if the task state isn't ready to snapshot.
- */
- Pair<ActivityRecord, WindowState> checkIfReadyToSnapshot(Task task) {
- if (!mService.mPolicy.isScreenOn()) {
- if (DEBUG_SCREENSHOT) {
- Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
- }
- return null;
- }
- final ActivityRecord activity = findAppTokenForSnapshot(task);
- if (activity == null) {
- if (DEBUG_SCREENSHOT) {
- Slog.w(TAG_WM, "Failed to take screenshot. No visible windows for " + task);
- }
- return null;
- }
- if (activity.hasCommittedReparentToAnimationLeash()) {
- if (DEBUG_SCREENSHOT) {
- Slog.w(TAG_WM, "Failed to take screenshot. App is animating " + activity);
- }
- return null;
- }
-
- final WindowState mainWindow = activity.findMainWindow();
- if (mainWindow == null) {
- Slog.w(TAG_WM, "Failed to take screenshot. No main window for " + task);
- return null;
- }
- if (activity.hasFixedRotationTransform()) {
- if (DEBUG_SCREENSHOT) {
- Slog.i(TAG_WM, "Skip taking screenshot. App has fixed rotation " + activity);
- }
- // The activity is in a temporal state that it has different rotation than the task.
- return null;
- }
- return new Pair<>(activity, mainWindow);
- }
-
- @Nullable
- ScreenCapture.ScreenshotHardwareBuffer createTaskSnapshot(@NonNull Task task,
- TaskSnapshot.Builder builder) {
- Point taskSize = new Point();
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "createTaskSnapshot");
- final ScreenCapture.ScreenshotHardwareBuffer taskSnapshot = createTaskSnapshot(task,
- mHighResTaskSnapshotScale, builder.getPixelFormat(), taskSize, builder);
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
- builder.setTaskSize(taskSize);
- return taskSnapshot;
+ @Override
+ protected boolean use16BitFormat() {
+ return mPersistInfoProvider.use16BitFormat();
}
@Nullable
@@ -416,101 +240,25 @@
if (checkIfReadyToSnapshot(task) == null) {
return null;
}
- final int pixelFormat = mPersister.use16BitFormat()
+ final int pixelFormat = mPersistInfoProvider.use16BitFormat()
? PixelFormat.RGB_565
: PixelFormat.RGBA_8888;
return createImeSnapshot(task, pixelFormat);
}
- @Nullable
- ScreenCapture.ScreenshotHardwareBuffer createTaskSnapshot(@NonNull Task task,
- float scaleFraction, int pixelFormat, Point outTaskSize, TaskSnapshot.Builder builder) {
- if (task.getSurfaceControl() == null) {
- if (DEBUG_SCREENSHOT) {
- Slog.w(TAG_WM, "Failed to take screenshot. No surface control for " + task);
- }
- return null;
- }
- task.getBounds(mTmpRect);
- mTmpRect.offsetTo(0, 0);
-
- SurfaceControl[] excludeLayers;
- final WindowState imeWindow = task.getDisplayContent().mInputMethodWindow;
- // Exclude IME window snapshot when IME isn't proper to attach to app.
- final boolean excludeIme = imeWindow != null && imeWindow.getSurfaceControl() != null
- && !task.getDisplayContent().shouldImeAttachedToApp();
- final WindowState navWindow =
- task.getDisplayContent().getDisplayPolicy().getNavigationBar();
- // If config_attachNavBarToAppDuringTransition is true, the nav bar will be reparent to the
- // the swiped app when entering recent app, therefore the task will contain the navigation
- // bar and we should exclude it from snapshot.
- final boolean excludeNavBar = navWindow != null;
- if (excludeIme && excludeNavBar) {
- excludeLayers = new SurfaceControl[2];
- excludeLayers[0] = imeWindow.getSurfaceControl();
- excludeLayers[1] = navWindow.getSurfaceControl();
- } else if (excludeIme || excludeNavBar) {
- excludeLayers = new SurfaceControl[1];
- excludeLayers[0] =
- excludeIme ? imeWindow.getSurfaceControl() : navWindow.getSurfaceControl();
- } else {
- excludeLayers = new SurfaceControl[0];
- }
- builder.setHasImeSurface(!excludeIme && imeWindow != null && imeWindow.isVisible());
-
- final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
- ScreenCapture.captureLayersExcluding(
- task.getSurfaceControl(), mTmpRect, scaleFraction,
- pixelFormat, excludeLayers);
- if (outTaskSize != null) {
- outTaskSize.x = mTmpRect.width();
- outTaskSize.y = mTmpRect.height();
- }
- final HardwareBuffer buffer = screenshotBuffer == null ? null
- : screenshotBuffer.getHardwareBuffer();
- if (isInvalidHardwareBuffer(buffer)) {
- return null;
- }
- return screenshotBuffer;
+ @Override
+ ActivityRecord getTopActivity(Task source) {
+ return source.getTopMostActivity();
}
- static boolean isInvalidHardwareBuffer(HardwareBuffer buffer) {
- return buffer == null || buffer.isClosed() // This must be checked before getting size.
- || buffer.getWidth() <= 1 || buffer.getHeight() <= 1;
+ @Override
+ ActivityRecord getTopFullscreenActivity(Task source) {
+ return source.getTopFullscreenActivity();
}
- @Nullable
- TaskSnapshot snapshotTask(Task task) {
- return snapshotTask(task, PixelFormat.UNKNOWN);
- }
-
- @Nullable
- TaskSnapshot snapshotTask(Task task, int pixelFormat) {
- TaskSnapshot.Builder builder = new TaskSnapshot.Builder();
-
- if (!prepareTaskSnapshot(task, pixelFormat, builder)) {
- // Failed some pre-req. Has been logged.
- return null;
- }
-
- final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
- createTaskSnapshot(task, builder);
-
- if (screenshotBuffer == null) {
- // Failed to acquire image. Has been logged.
- return null;
- }
- builder.setSnapshot(screenshotBuffer.getHardwareBuffer());
- builder.setColorSpace(screenshotBuffer.getColorSpace());
- return builder.build();
- }
-
- void setTaskSnapshotEnabled(boolean enabled) {
- mTaskSnapshotEnabled = enabled;
- }
-
- boolean shouldDisableSnapshots() {
- return mIsRunningOnTv || mIsRunningOnIoT || !mTaskSnapshotEnabled;
+ @Override
+ ActivityManager.TaskDescription getTaskDescription(Task source) {
+ return source.getTaskDescription();
}
/**
@@ -538,74 +286,6 @@
}
}
- @VisibleForTesting
- int getSnapshotMode(Task task) {
- final ActivityRecord topChild = task.getTopMostActivity();
- if (!task.isActivityTypeStandardOrUndefined() && !task.isActivityTypeAssistant()) {
- return SNAPSHOT_MODE_NONE;
- } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
- return SNAPSHOT_MODE_APP_THEME;
- } else {
- return SNAPSHOT_MODE_REAL;
- }
- }
-
- /**
- * If we are not allowed to take a real screenshot, this attempts to represent the app as best
- * as possible by using the theme's window background.
- */
- private TaskSnapshot drawAppThemeSnapshot(Task task) {
- final ActivityRecord topChild = task.getTopMostActivity();
- if (topChild == null) {
- return null;
- }
- final WindowState mainWindow = topChild.findMainWindow();
- if (mainWindow == null) {
- return null;
- }
- final int color = ColorUtils.setAlphaComponent(
- task.getTaskDescription().getBackgroundColor(), 255);
- final LayoutParams attrs = mainWindow.getAttrs();
- final Rect taskBounds = task.getBounds();
- final InsetsState insetsState = mainWindow.getInsetsStateWithVisibilityOverride();
- final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrame(), insetsState);
- final SnapshotDrawerUtils.SystemBarBackgroundPainter decorPainter =
- new SnapshotDrawerUtils.SystemBarBackgroundPainter(attrs.flags,
- attrs.privateFlags, attrs.insetsFlags.appearance, task.getTaskDescription(),
- mHighResTaskSnapshotScale, mainWindow.getRequestedVisibleTypes());
- final int taskWidth = taskBounds.width();
- final int taskHeight = taskBounds.height();
- final int width = (int) (taskWidth * mHighResTaskSnapshotScale);
- final int height = (int) (taskHeight * mHighResTaskSnapshotScale);
-
- final RenderNode node = RenderNode.create("TaskSnapshotController", null);
- node.setLeftTopRightBottom(0, 0, width, height);
- node.setClipToBounds(false);
- final RecordingCanvas c = node.start(width, height);
- c.drawColor(color);
- decorPainter.setInsets(systemBarInsets);
- decorPainter.drawDecors(c /* statusBarExcludeFrame */, null /* alreadyDrawnFrame */);
- node.end(c);
- final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
- if (hwBitmap == null) {
- return null;
- }
- final Rect contentInsets = new Rect(systemBarInsets);
- final Rect letterboxInsets = topChild.getLetterboxInsets();
- InsetUtils.addInsets(contentInsets, letterboxInsets);
-
- // Note, the app theme snapshot is never translucent because we enforce a non-translucent
- // color above
- return new TaskSnapshot(
- System.currentTimeMillis() /* id */,
- topChild.mActivityComponent, hwBitmap.getHardwareBuffer(),
- hwBitmap.getColorSpace(), mainWindow.getConfiguration().orientation,
- mainWindow.getWindowConfiguration().getRotation(), new Point(taskWidth, taskHeight),
- contentInsets, letterboxInsets, false /* isLowResolution */,
- false /* isRealSnapshot */, task.getWindowingMode(),
- getAppearance(task), false /* isTranslucent */, false /* hasImeSurface */);
- }
-
/**
* Called when an {@link ActivityRecord} has been removed.
*/
@@ -621,7 +301,7 @@
}
void notifyTaskRemovedFromRecents(int taskId, int userId) {
- mCache.onTaskRemoved(taskId);
+ mCache.onIdRemoved(taskId);
mPersister.onTaskRemovedFromRecents(taskId, userId);
}
@@ -637,15 +317,6 @@
}
/**
- * Temporarily pauses/unpauses persisting of task snapshots.
- *
- * @param paused Whether task snapshot persisting should be paused.
- */
- void setPersisterPaused(boolean paused) {
- mPersister.setPaused(paused);
- }
-
- /**
* Called when screen is being turned off.
*/
void screenTurningOff(int displayId, ScreenOffListener listener) {
@@ -691,34 +362,8 @@
snapshotTasks(mTmpTasks, allowSnapshotHome);
}
- /**
- * @return The {@link Appearance} flags for the top fullscreen opaque window in the given
- * {@param task}.
- */
- private @Appearance int getAppearance(Task task) {
- final ActivityRecord topFullscreenActivity = task.getTopFullscreenActivity();
- final WindowState topFullscreenOpaqueWindow = topFullscreenActivity != null
- ? topFullscreenActivity.getTopFullscreenOpaqueWindow()
- : null;
- if (topFullscreenOpaqueWindow != null) {
- return topFullscreenOpaqueWindow.mAttrs.insetsFlags.appearance;
- }
- return 0;
- }
-
- static Rect getSystemBarInsets(Rect frame, InsetsState state) {
- return state.calculateInsets(
- frame, Type.systemBars(), false /* ignoreVisibility */).toRect();
- }
-
private boolean isAnimatingByRecents(@NonNull Task task) {
return task.isAnimatingByRecents()
|| mService.mAtmService.getTransitionController().inRecentsTransition(task);
}
-
- void dump(PrintWriter pw, String prefix) {
- pw.println(prefix + "mHighResTaskSnapshotScale=" + mHighResTaskSnapshotScale);
- pw.println(prefix + "mTaskSnapshotEnabled=" + mTaskSnapshotEnabled);
- mCache.dump(pw, prefix);
- }
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 03098e3..cd15119 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -16,32 +16,13 @@
package com.android.server.wm;
-import static android.graphics.Bitmap.CompressFormat.JPEG;
-
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-
-import android.annotation.NonNull;
-import android.annotation.TestApi;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.os.Process;
-import android.os.SystemClock;
import android.util.ArraySet;
-import android.util.AtomicFile;
-import android.util.Slog;
import android.window.TaskSnapshot;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
-import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto;
import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayDeque;
import java.util.Arrays;
/**
@@ -49,32 +30,7 @@
* <p>
* Test class: {@link TaskSnapshotPersisterLoaderTest}
*/
-class TaskSnapshotPersister {
-
- private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM;
- private static final String SNAPSHOTS_DIRNAME = "snapshots";
- private static final String LOW_RES_FILE_POSTFIX = "_reduced";
- private static final long DELAY_MS = 100;
- private static final int QUALITY = 95;
- private static final String PROTO_EXTENSION = ".proto";
- private static final String BITMAP_EXTENSION = ".jpg";
- private static final int MAX_STORE_QUEUE_DEPTH = 2;
-
- @GuardedBy("mLock")
- private final ArrayDeque<WriteQueueItem> mWriteQueue = new ArrayDeque<>();
- @GuardedBy("mLock")
- private final ArrayDeque<StoreWriteQueueItem> mStoreQueueItems = new ArrayDeque<>();
- @GuardedBy("mLock")
- private boolean mQueueIdling;
- @GuardedBy("mLock")
- private boolean mPaused;
- private boolean mStarted;
- private final Object mLock = new Object();
- private final DirectoryResolver mDirectoryResolver;
- private final float mLowResScaleFactor;
- private boolean mEnableLowResSnapshots;
- private final boolean mUse16BitFormat;
- private final UserManagerInternal mUserManagerInternal;
+class TaskSnapshotPersister extends BaseAppSnapshotPersister {
/**
* The list of ids of the tasks that have been persisted since {@link #removeObsoleteFiles} was
@@ -83,45 +39,9 @@
@GuardedBy("mLock")
private final ArraySet<Integer> mPersistedTaskIdsSinceLastRemoveObsolete = new ArraySet<>();
- TaskSnapshotPersister(WindowManagerService service, DirectoryResolver resolver) {
- mDirectoryResolver = resolver;
- mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
-
- final float highResTaskSnapshotScale = service.mContext.getResources().getFloat(
- com.android.internal.R.dimen.config_highResTaskSnapshotScale);
- final float lowResTaskSnapshotScale = service.mContext.getResources().getFloat(
- com.android.internal.R.dimen.config_lowResTaskSnapshotScale);
-
- if (lowResTaskSnapshotScale < 0 || 1 <= lowResTaskSnapshotScale) {
- throw new RuntimeException("Low-res scale must be between 0 and 1");
- }
- if (highResTaskSnapshotScale <= 0 || 1 < highResTaskSnapshotScale) {
- throw new RuntimeException("High-res scale must be between 0 and 1");
- }
- if (highResTaskSnapshotScale <= lowResTaskSnapshotScale) {
- throw new RuntimeException("High-res scale must be greater than low-res scale");
- }
-
- if (lowResTaskSnapshotScale > 0) {
- mLowResScaleFactor = lowResTaskSnapshotScale / highResTaskSnapshotScale;
- mEnableLowResSnapshots = true;
- } else {
- mLowResScaleFactor = 0;
- mEnableLowResSnapshots = false;
- }
-
- mUse16BitFormat = service.mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_use16BitTaskSnapshotPixelFormat);
- }
-
- /**
- * Starts persisting.
- */
- void start() {
- if (!mStarted) {
- mStarted = true;
- mPersister.start();
- }
+ TaskSnapshotPersister(SnapshotPersistQueue persistQueue,
+ PersistInfoProvider persistInfoProvider) {
+ super(persistQueue, persistInfoProvider);
}
/**
@@ -134,7 +54,7 @@
void persistSnapshot(int taskId, int userId, TaskSnapshot snapshot) {
synchronized (mLock) {
mPersistedTaskIdsSinceLastRemoveObsolete.add(taskId);
- sendToQueueLocked(new StoreWriteQueueItem(taskId, userId, snapshot));
+ super.persistSnapshot(taskId, userId, snapshot);
}
}
@@ -147,7 +67,7 @@
void onTaskRemovedFromRecents(int taskId, int userId) {
synchronized (mLock) {
mPersistedTaskIdsSinceLastRemoveObsolete.remove(taskId);
- sendToQueueLocked(new DeleteWriteQueueItem(taskId, userId));
+ super.removeSnap(taskId, userId);
}
}
@@ -162,322 +82,20 @@
void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
synchronized (mLock) {
mPersistedTaskIdsSinceLastRemoveObsolete.clear();
- sendToQueueLocked(new RemoveObsoleteFilesQueueItem(persistentTaskIds, runningUserIds));
- }
- }
-
- void setPaused(boolean paused) {
- synchronized (mLock) {
- mPaused = paused;
- if (!paused) {
- mLock.notifyAll();
- }
- }
- }
-
- boolean enableLowResSnapshots() {
- return mEnableLowResSnapshots;
- }
-
- /**
- * Return if task snapshots are stored in 16 bit pixel format.
- *
- * @return true if task snapshots are stored in 16 bit pixel format.
- */
- boolean use16BitFormat() {
- return mUse16BitFormat;
- }
-
- @TestApi
- void waitForQueueEmpty() {
- while (true) {
- synchronized (mLock) {
- if (mWriteQueue.isEmpty() && mQueueIdling) {
- return;
- }
- }
- SystemClock.sleep(DELAY_MS);
- }
- }
-
- @GuardedBy("mLock")
- private void sendToQueueLocked(WriteQueueItem item) {
- mWriteQueue.offer(item);
- item.onQueuedLocked();
- ensureStoreQueueDepthLocked();
- if (!mPaused) {
- mLock.notifyAll();
- }
- }
-
- @GuardedBy("mLock")
- private void ensureStoreQueueDepthLocked() {
- while (mStoreQueueItems.size() > MAX_STORE_QUEUE_DEPTH) {
- final StoreWriteQueueItem item = mStoreQueueItems.poll();
- mWriteQueue.remove(item);
- Slog.i(TAG, "Queue is too deep! Purged item with taskid=" + item.mTaskId);
- }
- }
-
- private File getDirectory(int userId) {
- return new File(mDirectoryResolver.getSystemDirectoryForUser(userId), SNAPSHOTS_DIRNAME);
- }
-
- File getProtoFile(int taskId, int userId) {
- return new File(getDirectory(userId), taskId + PROTO_EXTENSION);
- }
-
- File getHighResolutionBitmapFile(int taskId, int userId) {
- return new File(getDirectory(userId), taskId + BITMAP_EXTENSION);
- }
-
- @NonNull
- File getLowResolutionBitmapFile(int taskId, int userId) {
- return new File(getDirectory(userId), taskId + LOW_RES_FILE_POSTFIX + BITMAP_EXTENSION);
- }
-
- private boolean createDirectory(int userId) {
- final File dir = getDirectory(userId);
- return dir.exists() || dir.mkdir();
- }
-
- private void deleteSnapshot(int taskId, int userId) {
- final File protoFile = getProtoFile(taskId, userId);
- final File bitmapLowResFile = getLowResolutionBitmapFile(taskId, userId);
- protoFile.delete();
- if (bitmapLowResFile.exists()) {
- bitmapLowResFile.delete();
- }
- final File bitmapFile = getHighResolutionBitmapFile(taskId, userId);
- if (bitmapFile.exists()) {
- bitmapFile.delete();
- }
- }
-
- interface DirectoryResolver {
- File getSystemDirectoryForUser(int userId);
- }
-
- private Thread mPersister = new Thread("TaskSnapshotPersister") {
- public void run() {
- android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- while (true) {
- WriteQueueItem next;
- boolean isReadyToWrite = false;
- synchronized (mLock) {
- if (mPaused) {
- next = null;
- } else {
- next = mWriteQueue.poll();
- if (next != null) {
- if (next.isReady()) {
- isReadyToWrite = true;
- next.onDequeuedLocked();
- } else {
- mWriteQueue.addLast(next);
- }
- }
- }
- }
- if (next != null) {
- if (isReadyToWrite) {
- next.write();
- }
- SystemClock.sleep(DELAY_MS);
- }
- synchronized (mLock) {
- final boolean writeQueueEmpty = mWriteQueue.isEmpty();
- if (!writeQueueEmpty && !mPaused) {
- continue;
- }
- try {
- mQueueIdling = writeQueueEmpty;
- mLock.wait();
- mQueueIdling = false;
- } catch (InterruptedException e) {
- }
- }
- }
- }
- };
-
- private abstract class WriteQueueItem {
- /**
- * @return {@code true} if item is ready to have {@link WriteQueueItem#write} called
- */
- boolean isReady() {
- return true;
- }
-
- abstract void write();
-
- /**
- * Called when this queue item has been put into the queue.
- */
- void onQueuedLocked() {
- }
-
- /**
- * Called when this queue item has been taken out of the queue.
- */
- void onDequeuedLocked() {
- }
- }
-
- private class StoreWriteQueueItem extends WriteQueueItem {
- private final int mTaskId;
- private final int mUserId;
- private final TaskSnapshot mSnapshot;
-
- StoreWriteQueueItem(int taskId, int userId, TaskSnapshot snapshot) {
- mTaskId = taskId;
- mUserId = userId;
- mSnapshot = snapshot;
- }
-
- @GuardedBy("mLock")
- @Override
- void onQueuedLocked() {
- mStoreQueueItems.offer(this);
- }
-
- @GuardedBy("mLock")
- @Override
- void onDequeuedLocked() {
- mStoreQueueItems.remove(this);
- }
-
- @Override
- boolean isReady() {
- return mUserManagerInternal.isUserUnlocked(mUserId);
- }
-
- @Override
- void write() {
- if (!createDirectory(mUserId)) {
- Slog.e(TAG, "Unable to create snapshot directory for user dir="
- + getDirectory(mUserId));
- }
- boolean failed = false;
- if (!writeProto()) {
- failed = true;
- }
- if (!writeBuffer()) {
- failed = true;
- }
- if (failed) {
- deleteSnapshot(mTaskId, mUserId);
- }
- }
-
- boolean writeProto() {
- final TaskSnapshotProto proto = new TaskSnapshotProto();
- proto.orientation = mSnapshot.getOrientation();
- proto.rotation = mSnapshot.getRotation();
- proto.taskWidth = mSnapshot.getTaskSize().x;
- proto.taskHeight = mSnapshot.getTaskSize().y;
- proto.insetLeft = mSnapshot.getContentInsets().left;
- proto.insetTop = mSnapshot.getContentInsets().top;
- proto.insetRight = mSnapshot.getContentInsets().right;
- proto.insetBottom = mSnapshot.getContentInsets().bottom;
- proto.letterboxInsetLeft = mSnapshot.getLetterboxInsets().left;
- proto.letterboxInsetTop = mSnapshot.getLetterboxInsets().top;
- proto.letterboxInsetRight = mSnapshot.getLetterboxInsets().right;
- proto.letterboxInsetBottom = mSnapshot.getLetterboxInsets().bottom;
- proto.isRealSnapshot = mSnapshot.isRealSnapshot();
- proto.windowingMode = mSnapshot.getWindowingMode();
- proto.appearance = mSnapshot.getAppearance();
- proto.isTranslucent = mSnapshot.isTranslucent();
- proto.topActivityComponent = mSnapshot.getTopActivityComponent().flattenToString();
- proto.id = mSnapshot.getId();
- final byte[] bytes = TaskSnapshotProto.toByteArray(proto);
- final File file = getProtoFile(mTaskId, mUserId);
- final AtomicFile atomicFile = new AtomicFile(file);
- FileOutputStream fos = null;
- try {
- fos = atomicFile.startWrite();
- fos.write(bytes);
- atomicFile.finishWrite(fos);
- } catch (IOException e) {
- atomicFile.failWrite(fos);
- Slog.e(TAG, "Unable to open " + file + " for persisting. " + e);
- return false;
- }
- return true;
- }
-
- boolean writeBuffer() {
- if (TaskSnapshotController.isInvalidHardwareBuffer(mSnapshot.getHardwareBuffer())) {
- Slog.e(TAG, "Invalid task snapshot hw buffer, taskId=" + mTaskId);
- return false;
- }
- final Bitmap bitmap = Bitmap.wrapHardwareBuffer(
- mSnapshot.getHardwareBuffer(), mSnapshot.getColorSpace());
- if (bitmap == null) {
- Slog.e(TAG, "Invalid task snapshot hw bitmap");
- return false;
- }
-
- final Bitmap swBitmap = bitmap.copy(Config.ARGB_8888, false /* isMutable */);
-
- final File file = getHighResolutionBitmapFile(mTaskId, mUserId);
- try {
- FileOutputStream fos = new FileOutputStream(file);
- swBitmap.compress(JPEG, QUALITY, fos);
- fos.close();
- } catch (IOException e) {
- Slog.e(TAG, "Unable to open " + file + " for persisting.", e);
- return false;
- }
-
- if (!mEnableLowResSnapshots) {
- swBitmap.recycle();
- return true;
- }
-
- final Bitmap lowResBitmap = Bitmap.createScaledBitmap(swBitmap,
- (int) (bitmap.getWidth() * mLowResScaleFactor),
- (int) (bitmap.getHeight() * mLowResScaleFactor), true /* filter */);
- swBitmap.recycle();
-
- final File lowResFile = getLowResolutionBitmapFile(mTaskId, mUserId);
- try {
- FileOutputStream lowResFos = new FileOutputStream(lowResFile);
- lowResBitmap.compress(JPEG, QUALITY, lowResFos);
- lowResFos.close();
- } catch (IOException e) {
- Slog.e(TAG, "Unable to open " + lowResFile + " for persisting.", e);
- return false;
- }
- lowResBitmap.recycle();
-
- return true;
- }
- }
-
- private class DeleteWriteQueueItem extends WriteQueueItem {
- private final int mTaskId;
- private final int mUserId;
-
- DeleteWriteQueueItem(int taskId, int userId) {
- mTaskId = taskId;
- mUserId = userId;
- }
-
- @Override
- void write() {
- deleteSnapshot(mTaskId, mUserId);
+ mSnapshotPersistQueue.sendToQueueLocked(new RemoveObsoleteFilesQueueItem(
+ persistentTaskIds, runningUserIds, mPersistInfoProvider));
}
}
@VisibleForTesting
- class RemoveObsoleteFilesQueueItem extends WriteQueueItem {
+ class RemoveObsoleteFilesQueueItem extends SnapshotPersistQueue.WriteQueueItem {
private final ArraySet<Integer> mPersistentTaskIds;
private final int[] mRunningUserIds;
@VisibleForTesting
RemoveObsoleteFilesQueueItem(ArraySet<Integer> persistentTaskIds,
- int[] runningUserIds) {
+ int[] runningUserIds, PersistInfoProvider provider) {
+ super(provider);
mPersistentTaskIds = new ArraySet<>(persistentTaskIds);
mRunningUserIds = Arrays.copyOf(runningUserIds, runningUserIds.length);
}
@@ -489,7 +107,7 @@
newPersistedTaskIds = new ArraySet<>(mPersistedTaskIdsSinceLastRemoveObsolete);
}
for (int userId : mRunningUserIds) {
- final File dir = getDirectory(userId);
+ final File dir = mPersistInfoProvider.getDirectory(userId);
final String[] files = dir.list();
if (files == null) {
continue;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 0c20d03..302538f 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -171,7 +171,7 @@
final ArraySet<WindowContainer> mParticipants = new ArraySet<>();
/** The final animation targets derived from participants after promotion. */
- private ArrayList<WindowContainer> mTargets;
+ private ArrayList<ChangeInfo> mTargets;
/** The displays that this transition is running on. */
private final ArrayList<DisplayContent> mTargetDisplays = new ArrayList<>();
@@ -625,7 +625,7 @@
// usually only size 1
final ArraySet<DisplayContent> displays = new ArraySet<>();
for (int i = mTargets.size() - 1; i >= 0; --i) {
- final WindowContainer target = mTargets.get(i);
+ final WindowContainer target = mTargets.get(i).mContainer;
if (target.getParent() != null) {
final SurfaceControl targetLeash = getLeashSurface(target, null /* t */);
final SurfaceControl origParent = getOrigParentSurface(target);
@@ -786,7 +786,7 @@
&& mTransientLaunches != null) {
// If transition is transient, then snapshots are taken at end of
// transition.
- mController.mTaskSnapshotController.recordTaskSnapshot(
+ mController.mTaskSnapshotController.recordSnapshot(
task, false /* allowSnapshotHome */);
}
ar.commitVisibility(false /* visible */, false /* performLayout */,
@@ -858,7 +858,7 @@
for (int i = 0; i < mTargetDisplays.size(); ++i) {
final DisplayContent dc = mTargetDisplays.get(i);
final AsyncRotationController asyncRotationController = dc.getAsyncRotationController();
- if (asyncRotationController != null && mTargets.contains(dc)) {
+ if (asyncRotationController != null && containsChangeFor(dc, mTargets)) {
asyncRotationController.onTransitionFinished();
}
if (mTransientLaunches != null) {
@@ -925,6 +925,14 @@
change.mFlags |= ChangeInfo.FLAG_CHANGE_NO_ANIMATION;
}
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ static boolean containsChangeFor(WindowContainer wc, ArrayList<ChangeInfo> list) {
+ for (int i = list.size() - 1; i >= 0; --i) {
+ if (list.get(i).mContainer == wc) return true;
+ }
+ return false;
+ }
+
@Override
public void onTransactionReady(int syncId, SurfaceControl.Transaction transaction) {
if (syncId != mSyncId) {
@@ -962,8 +970,7 @@
.containsBackAnimationTargets(this);
// Resolve the animating targets from the participants
mTargets = calculateTargets(mParticipants, mChanges);
- final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, mChanges,
- transaction);
+ final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
if (markBackAnimated) {
mController.mAtm.mBackNavigationController.clearBackAnimations(mStartTransaction);
}
@@ -972,7 +979,7 @@
if (mOverrideOptions.getType() == ANIM_OPEN_CROSS_PROFILE_APPS) {
for (int i = 0; i < mTargets.size(); ++i) {
final TransitionInfo.Change c = info.getChanges().get(i);
- final ActivityRecord ar = mTargets.get(i).asActivityRecord();
+ final ActivityRecord ar = mTargets.get(i).mContainer.asActivityRecord();
if (ar == null || c.getMode() != TRANSIT_OPEN) continue;
int flags = c.getFlags();
flags |= ar.mUserId == ar.mWmService.mCurrentUserId
@@ -1016,7 +1023,7 @@
// already been reset by the original hiding-transition's finishTransaction (we can't
// show in the finishTransaction because by then the activity doesn't hide until
// surface placement).
- for (WindowContainer p = ar.getParent(); p != null && !mTargets.contains(p);
+ for (WindowContainer p = ar.getParent(); p != null && !containsChangeFor(p, mTargets);
p = p.getParent()) {
if (p.getSurfaceControl() != null) {
transaction.show(p.getSurfaceControl());
@@ -1044,7 +1051,7 @@
final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
if (ar == null || ar.isVisibleRequested() || ar.getTask() == null
|| ar.getTask().isVisibleRequested()) continue;
- mController.mTaskSnapshotController.recordTaskSnapshot(
+ mController.mTaskSnapshotController.recordSnapshot(
ar.getTask(), false /* allowSnapshotHome */);
}
}
@@ -1052,7 +1059,7 @@
// This is non-null only if display has changes. It handles the visible windows that don't
// need to be participated in the transition.
final AsyncRotationController controller = dc.getAsyncRotationController();
- if (controller != null && mTargets.contains(dc)) {
+ if (controller != null && containsChangeFor(dc, mTargets)) {
controller.setupStartTransaction(transaction);
}
buildFinishTransaction(mFinishTransaction, info.getRootLeash());
@@ -1225,7 +1232,7 @@
// Search for the home task. If it is supposed to be visible, then the navbar is not at
// the bottom of the screen, so we need to animate it.
for (int i = 0; i < mTargets.size(); ++i) {
- final Task task = mTargets.get(i).asTask();
+ final Task task = mTargets.get(i).mContainer.asTask();
if (task == null || !task.isActivityTypeHomeOrRecents()) continue;
animate = task.isVisibleRequested();
break;
@@ -1350,12 +1357,13 @@
*
* @return {@code true} if transition in target can be promoted to its parent.
*/
- private static boolean canPromote(WindowContainer<?> target, Targets targets,
+ private static boolean canPromote(ChangeInfo targetChange, Targets targets,
ArrayMap<WindowContainer, ChangeInfo> changes) {
+ final WindowContainer<?> target = targetChange.mContainer;
final WindowContainer<?> parent = target.getParent();
final ChangeInfo parentChange = changes.get(parent);
if (!parent.canCreateRemoteAnimationTarget()
- || parentChange == null || !parentChange.hasChanged(parent)) {
+ || parentChange == null || !parentChange.hasChanged()) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: %s",
"parent can't be target " + parent);
return false;
@@ -1365,21 +1373,20 @@
return false;
}
- final ChangeInfo change = changes.get(target);
- if (change.mStartParent != null && target.getParent() != change.mStartParent) {
+ if (targetChange.mStartParent != null && target.getParent() != targetChange.mStartParent) {
// When a window is reparented, the state change won't fit into any of the parents.
// Don't promote such change so that we can animate the reparent if needed.
return false;
}
- final @TransitionInfo.TransitionMode int mode = change.getTransitMode(target);
+ final @TransitionInfo.TransitionMode int mode = targetChange.getTransitMode(target);
for (int i = parent.getChildCount() - 1; i >= 0; --i) {
final WindowContainer<?> sibling = parent.getChildAt(i);
if (target == sibling) continue;
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " check sibling %s",
sibling);
final ChangeInfo siblingChange = changes.get(sibling);
- if (siblingChange == null || !targets.wasParticipated(sibling)) {
+ if (siblingChange == null || !targets.wasParticipated(siblingChange)) {
if (sibling.isVisibleRequested()) {
// Sibling is visible but not animating, so no promote.
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
@@ -1424,7 +1431,8 @@
WindowContainer<?> lastNonPromotableParent = null;
// Go through from the deepest target.
for (int i = targets.mArray.size() - 1; i >= 0; --i) {
- final WindowContainer<?> target = targets.mArray.valueAt(i);
+ final ChangeInfo targetChange = targets.mArray.valueAt(i);
+ final WindowContainer<?> target = targetChange.mContainer;
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " checking %s", target);
final WindowContainer<?> parent = target.getParent();
if (parent == lastNonPromotableParent) {
@@ -1432,7 +1440,7 @@
" SKIP: its sibling was rejected");
continue;
}
- if (!canPromote(target, targets, changes)) {
+ if (!canPromote(targetChange, targets, changes)) {
lastNonPromotableParent = parent;
continue;
}
@@ -1442,19 +1450,20 @@
} else {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
" remove from targets %s", target);
- targets.remove(i, target);
+ targets.remove(i);
}
- if (targets.mArray.indexOfValue(parent) < 0) {
+ final ChangeInfo parentChange = changes.get(parent);
+ if (targets.mArray.indexOfValue(parentChange) < 0) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
" CAN PROMOTE: promoting to parent %s", parent);
// The parent has lower depth, so it will be checked in the later iteration.
i++;
- targets.add(parent);
+ targets.add(parentChange);
}
- if ((changes.get(target).mFlags & ChangeInfo.FLAG_CHANGE_NO_ANIMATION) != 0) {
- changes.get(parent).mFlags |= ChangeInfo.FLAG_CHANGE_NO_ANIMATION;
+ if ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_NO_ANIMATION) != 0) {
+ parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_NO_ANIMATION;
} else {
- changes.get(parent).mFlags |= ChangeInfo.FLAG_CHANGE_YES_ANIMATION;
+ parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_YES_ANIMATION;
}
}
}
@@ -1465,7 +1474,7 @@
*/
@VisibleForTesting
@NonNull
- static ArrayList<WindowContainer> calculateTargets(ArraySet<WindowContainer> participants,
+ static ArrayList<ChangeInfo> calculateTargets(ArraySet<WindowContainer> participants,
ArrayMap<WindowContainer, ChangeInfo> changes) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Start calculating TransitionInfo based on participants: %s", participants);
@@ -1485,12 +1494,12 @@
final ChangeInfo changeInfo = changes.get(wc);
// Reject no-ops
- if (!changeInfo.hasChanged(wc)) {
+ if (!changeInfo.hasChanged()) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
" Rejecting as no-op: %s", wc);
continue;
}
- targets.add(wc);
+ targets.add(changeInfo);
}
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Initial targets: %s",
targets.mArray);
@@ -1499,7 +1508,7 @@
// Establish the relationship between the targets and their top changes.
populateParentChanges(targets, changes);
- final ArrayList<WindowContainer> targetList = targets.getListSortedByZ();
+ final ArrayList<ChangeInfo> targetList = targets.getListSortedByZ();
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Final targets: %s", targetList);
return targetList;
}
@@ -1507,14 +1516,15 @@
/** Populates parent to the change info and collects intermediate targets. */
private static void populateParentChanges(Targets targets,
ArrayMap<WindowContainer, ChangeInfo> changes) {
- final ArrayList<WindowContainer<?>> intermediates = new ArrayList<>();
+ final ArrayList<ChangeInfo> intermediates = new ArrayList<>();
// Make a copy to iterate because the original array may be modified.
- final ArrayList<WindowContainer<?>> targetList = new ArrayList<>(targets.mArray.size());
+ final ArrayList<ChangeInfo> targetList = new ArrayList<>(targets.mArray.size());
for (int i = targets.mArray.size() - 1; i >= 0; --i) {
targetList.add(targets.mArray.valueAt(i));
}
for (int i = targetList.size() - 1; i >= 0; --i) {
- final WindowContainer<?> wc = targetList.get(i);
+ final ChangeInfo targetChange = targetList.get(i);
+ final WindowContainer wc = targetChange.mContainer;
// Wallpaper must belong to the top (regardless of how nested it is in DisplayAreas).
final boolean skipIntermediateReports = isWallpaper(wc);
intermediates.clear();
@@ -1523,34 +1533,34 @@
for (WindowContainer<?> p = getAnimatableParent(wc); p != null;
p = getAnimatableParent(p)) {
final ChangeInfo parentChange = changes.get(p);
- if (parentChange == null || !parentChange.hasChanged(p)) break;
+ if (parentChange == null || !parentChange.hasChanged()) break;
if (p.mRemoteToken == null) {
// Intermediate parents must be those that has window to be managed by Shell.
continue;
}
if (parentChange.mEndParent != null && !skipIntermediateReports) {
- changes.get(wc).mEndParent = p;
+ targetChange.mEndParent = p;
// The chain above the parent was processed.
break;
}
- if (targetList.contains(p)) {
+ if (targetList.contains(parentChange)) {
if (skipIntermediateReports) {
- changes.get(wc).mEndParent = p;
+ targetChange.mEndParent = p;
} else {
- intermediates.add(p);
+ intermediates.add(parentChange);
}
foundParentInTargets = true;
break;
} else if (reportIfNotTop(p) && !skipIntermediateReports) {
- intermediates.add(p);
+ intermediates.add(parentChange);
}
}
if (!foundParentInTargets || intermediates.isEmpty()) continue;
// Add any always-report parents along the way.
- changes.get(wc).mEndParent = intermediates.get(0);
+ targetChange.mEndParent = intermediates.get(0).mContainer;
for (int j = 0; j < intermediates.size() - 1; j++) {
- final WindowContainer<?> intermediate = intermediates.get(j);
- changes.get(intermediate).mEndParent = intermediates.get(j + 1);
+ final ChangeInfo intermediate = intermediates.get(j);
+ intermediate.mEndParent = intermediates.get(j + 1).mContainer;
targets.add(intermediate);
}
}
@@ -1611,14 +1621,13 @@
@VisibleForTesting
@NonNull
static TransitionInfo calculateTransitionInfo(@TransitionType int type, int flags,
- ArrayList<WindowContainer> sortedTargets,
- ArrayMap<WindowContainer, ChangeInfo> changes,
+ ArrayList<ChangeInfo> sortedTargets,
@Nullable SurfaceControl.Transaction startT) {
final TransitionInfo out = new TransitionInfo(type, flags);
WindowContainer<?> topApp = null;
for (int i = 0; i < sortedTargets.size(); i++) {
- final WindowContainer<?> wc = sortedTargets.get(i);
+ final WindowContainer<?> wc = sortedTargets.get(i).mContainer;
if (!isWallpaper(wc)) {
topApp = wc;
break;
@@ -1629,7 +1638,7 @@
return out;
}
- WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, changes, topApp);
+ WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, topApp);
// Make leash based on highest (z-order) direct child of ancestor with a participant.
// TODO(b/261418859): Handle the case when the target contains window containers which
@@ -1647,8 +1656,8 @@
// Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order.
final int count = sortedTargets.size();
for (int i = 0; i < count; ++i) {
- final WindowContainer target = sortedTargets.get(i);
- final ChangeInfo info = changes.get(target);
+ final ChangeInfo info = sortedTargets.get(i);
+ final WindowContainer target = info.mContainer;
final TransitionInfo.Change change = new TransitionInfo.Change(
target.mRemoteToken != null ? target.mRemoteToken.toWindowContainerToken()
: null, getLeashSurface(target, startT));
@@ -1758,14 +1767,14 @@
*/
@NonNull
private static WindowContainer<?> findCommonAncestor(
- @NonNull ArrayList<WindowContainer> targets,
- @NonNull ArrayMap<WindowContainer, ChangeInfo> changes,
+ @NonNull ArrayList<ChangeInfo> targets,
@NonNull WindowContainer<?> topApp) {
WindowContainer<?> ancestor = topApp.getParent();
// Go up ancestor parent chain until all targets are descendants. Ancestor should never be
// null because all targets are attached.
for (int i = targets.size() - 1; i >= 0; i--) {
- final WindowContainer wc = targets.get(i);
+ final ChangeInfo change = targets.get(i);
+ final WindowContainer wc = change.mContainer;
if (isWallpaper(wc)) {
// Skip the non-app window.
continue;
@@ -1777,7 +1786,6 @@
// Make sure the previous parent is also a descendant to make sure the animation won't
// be covered by other windows below the previous parent. For example, when reparenting
// an activity from PiP Task to split screen Task.
- final ChangeInfo change = changes.get(wc);
final WindowContainer prevParent = change.mCommonAncestor;
if (prevParent == null || !prevParent.isAttached()) {
continue;
@@ -1790,11 +1798,13 @@
}
private static WindowManager.LayoutParams getLayoutParamsForAnimationsStyle(int type,
- ArrayList<WindowContainer> sortedTargets) {
+ ArrayList<ChangeInfo> sortedTargets) {
// Find the layout params of the top-most application window that is part of the
// transition, which is what will control the animation theme.
final ArraySet<Integer> activityTypes = new ArraySet<>();
- for (WindowContainer target : sortedTargets) {
+ final int targetCount = sortedTargets.size();
+ for (int i = 0; i < targetCount; ++i) {
+ final WindowContainer target = sortedTargets.get(i).mContainer;
if (target.asActivityRecord() != null) {
activityTypes.add(target.getActivityType());
} else if (target.asWindowToken() == null && target.asWindowState() == null) {
@@ -1818,7 +1828,7 @@
}
private static ActivityRecord findAnimLayoutParamsActivityRecord(
- List<WindowContainer> sortedTargets,
+ List<ChangeInfo> sortedTargets,
@TransitionType int transit, ArraySet<Integer> activityTypes) {
// Remote animations always win, but fullscreen windows override non-fullscreen windows.
ActivityRecord result = lookForTopWindowWithFilter(sortedTargets,
@@ -1835,9 +1845,11 @@
return lookForTopWindowWithFilter(sortedTargets, w -> w.findMainWindow() != null);
}
- private static ActivityRecord lookForTopWindowWithFilter(List<WindowContainer> sortedTargets,
+ private static ActivityRecord lookForTopWindowWithFilter(List<ChangeInfo> sortedTargets,
Predicate<ActivityRecord> filter) {
- for (WindowContainer target : sortedTargets) {
+ final int count = sortedTargets.size();
+ for (int i = 0; i < count; ++i) {
+ final WindowContainer target = sortedTargets.get(i).mContainer;
final ActivityRecord activityRecord = target.asTaskFragment() != null
? target.asTaskFragment().getTopNonFinishingActivity()
: target.asActivityRecord();
@@ -1871,7 +1883,7 @@
for (int i = mParticipants.size() - 1; i >= 0; --i) {
final WindowContainer<?> wc = mParticipants.valueAt(i);
final DisplayContent dc = wc.asDisplayContent();
- if (dc == null || !mChanges.get(dc).hasChanged(dc)) continue;
+ if (dc == null || !mChanges.get(dc).hasChanged()) continue;
dc.sendNewConfiguration();
changed = true;
}
@@ -1913,6 +1925,7 @@
@Retention(RetentionPolicy.SOURCE)
@interface Flag {}
+ @NonNull final WindowContainer mContainer;
/**
* "Parent" that is also included in the transition. When populating the parent changes, we
* may skip the intermediate parents, so this may not be the actual parent in the hierarchy.
@@ -1945,6 +1958,7 @@
float mSnapshotLuma;
ChangeInfo(@NonNull WindowContainer origState) {
+ mContainer = origState;
mVisible = origState.isVisibleRequested();
mWindowingMode = origState.getWindowingMode();
mAbsoluteBounds.set(origState.getBounds());
@@ -1954,13 +1968,19 @@
}
@VisibleForTesting
- ChangeInfo(boolean visible, boolean existChange) {
+ ChangeInfo(@NonNull WindowContainer container, boolean visible, boolean existChange) {
+ mContainer = container;
mVisible = visible;
mExistenceChanged = existChange;
mShowWallpaper = false;
}
- boolean hasChanged(@NonNull WindowContainer newState) {
+ @Override
+ public String toString() {
+ return mContainer.toString();
+ }
+
+ boolean hasChanged() {
// the task including transient launch must promote to root task
if ((mFlags & ChangeInfo.FLAG_TRANSIENT_LAUNCH) != 0
|| (mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0) {
@@ -1968,15 +1988,15 @@
}
// If it's invisible and hasn't changed visibility, always return false since even if
// something changed, it wouldn't be a visible change.
- final boolean currVisible = newState.isVisibleRequested();
+ final boolean currVisible = mContainer.isVisibleRequested();
if (currVisible == mVisible && !mVisible) return false;
return currVisible != mVisible
|| mKnownConfigChanges != 0
// if mWindowingMode is 0, this container wasn't attached at collect time, so
// assume no change in windowing-mode.
- || (mWindowingMode != 0 && newState.getWindowingMode() != mWindowingMode)
- || !newState.getBounds().equals(mAbsoluteBounds)
- || mRotation != newState.getWindowConfiguration().getRotation();
+ || (mWindowingMode != 0 && mContainer.getWindowingMode() != mWindowingMode)
+ || !mContainer.getBounds().equals(mAbsoluteBounds)
+ || mRotation != mContainer.getWindowConfiguration().getRotation();
}
@TransitionInfo.TransitionMode
@@ -2233,19 +2253,19 @@
*/
private static class Targets {
/** All targets. Its keys (depth) are sorted in ascending order naturally. */
- final SparseArray<WindowContainer<?>> mArray = new SparseArray<>();
+ final SparseArray<ChangeInfo> mArray = new SparseArray<>();
/** The targets which were represented by their parent. */
- private ArrayList<WindowContainer<?>> mRemovedTargets;
+ private ArrayList<ChangeInfo> mRemovedTargets;
private int mDepthFactor;
- void add(WindowContainer<?> target) {
+ void add(ChangeInfo target) {
// The number of slots per depth is larger than the total number of window container,
// so the depth score (key) won't have collision.
if (mDepthFactor == 0) {
- mDepthFactor = target.mWmService.mRoot.getTreeWeight() + 1;
+ mDepthFactor = target.mContainer.mWmService.mRoot.getTreeWeight() + 1;
}
- int score = target.getPrefixOrderIndex();
- WindowContainer<?> wc = target;
+ int score = target.mContainer.getPrefixOrderIndex();
+ WindowContainer<?> wc = target.mContainer;
while (wc != null) {
final WindowContainer<?> parent = wc.getParent();
if (parent != null) {
@@ -2256,7 +2276,8 @@
mArray.put(score, target);
}
- void remove(int index, WindowContainer<?> removingTarget) {
+ void remove(int index) {
+ final ChangeInfo removingTarget = mArray.valueAt(index);
mArray.removeAt(index);
if (mRemovedTargets == null) {
mRemovedTargets = new ArrayList<>();
@@ -2264,19 +2285,19 @@
mRemovedTargets.add(removingTarget);
}
- boolean wasParticipated(WindowContainer<?> wc) {
+ boolean wasParticipated(ChangeInfo wc) {
return mArray.indexOfValue(wc) >= 0
|| (mRemovedTargets != null && mRemovedTargets.contains(wc));
}
/** Returns the target list sorted by z-order in ascending order (index 0 is top). */
- ArrayList<WindowContainer> getListSortedByZ() {
- final SparseArray<WindowContainer<?>> arrayByZ = new SparseArray<>(mArray.size());
+ ArrayList<ChangeInfo> getListSortedByZ() {
+ final SparseArray<ChangeInfo> arrayByZ = new SparseArray<>(mArray.size());
for (int i = mArray.size() - 1; i >= 0; --i) {
final int zOrder = mArray.keyAt(i) % mDepthFactor;
arrayByZ.put(zOrder, mArray.valueAt(i));
}
- final ArrayList<WindowContainer> sortedTargets = new ArrayList<>(arrayByZ.size());
+ final ArrayList<ChangeInfo> sortedTargets = new ArrayList<>(arrayByZ.size());
for (int i = arrayByZ.size() - 1; i >= 0; --i) {
sortedTargets.add(arrayByZ.valueAt(i));
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 4c34912..1758102 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -631,12 +631,12 @@
mWakeT.apply();
// Usually transitions put quite a load onto the system already (with all the things
// happening in app), so pause task snapshot persisting to not increase the load.
- mAtm.mWindowManager.mTaskSnapshotController.setPersisterPaused(true);
+ mAtm.mWindowManager.mSnapshotPersistQueue.setPaused(true);
mTransitionPlayerProc.setRunningRemoteAnimation(true);
} else if (mPlayingTransitions.isEmpty()) {
mWakeT.setEarlyWakeupEnd();
mWakeT.apply();
- mAtm.mWindowManager.mTaskSnapshotController.setPersisterPaused(false);
+ mAtm.mWindowManager.mSnapshotPersistQueue.setPaused(false);
mTransitionPlayerProc.setRunningRemoteAnimation(false);
mRemotePlayer.clear();
return;
diff --git a/services/core/java/com/android/server/wm/TransitionTracer.java b/services/core/java/com/android/server/wm/TransitionTracer.java
index c1927d8..022c19b 100644
--- a/services/core/java/com/android/server/wm/TransitionTracer.java
+++ b/services/core/java/com/android/server/wm/TransitionTracer.java
@@ -109,7 +109,7 @@
final long changeEntryToken = outputStream.start(CHANGE);
final int transitMode = changeInfo.getTransitMode(window);
- final boolean hasChanged = changeInfo.hasChanged(window);
+ final boolean hasChanged = changeInfo.hasChanged();
final int changeFlags = changeInfo.getChangeFlags(window);
outputStream.write(TRANSIT_MODE, transitMode);
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 4a43f4f..c11391e 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -205,10 +205,10 @@
if (runningExpensiveAnimations && !mRunningExpensiveAnimations) {
// Usually app transitions put quite a load onto the system already (with all the things
// happening in app), so pause task snapshot persisting to not increase the load.
- mService.mTaskSnapshotController.setPersisterPaused(true);
+ mService.mSnapshotPersistQueue.setPaused(true);
mTransaction.setEarlyWakeupStart();
} else if (!runningExpensiveAnimations && mRunningExpensiveAnimations) {
- mService.mTaskSnapshotController.setPersisterPaused(false);
+ mService.mSnapshotPersistQueue.setPaused(false);
mTransaction.setEarlyWakeupEnd();
}
mRunningExpensiveAnimations = runningExpensiveAnimations;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index d676c17..ce41ae7 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -450,15 +450,17 @@
mLocalInsetsSourceProviders = new SparseArray<>();
}
for (int i = 0; i < insetsTypes.length; i++) {
+ final @InsetsState.InternalInsetsType int type = insetsTypes[i];
InsetsSourceProvider insetsSourceProvider =
- mLocalInsetsSourceProviders.get(insetsTypes[i]);
+ mLocalInsetsSourceProviders.get(type);
if (insetsSourceProvider != null) {
if (DEBUG) {
- Slog.d(TAG, "The local insets provider for this type " + insetsTypes[i]
+ Slog.d(TAG, "The local insets provider for this type " + type
+ " already exists. Overwriting");
}
}
- insetsSourceProvider = new RectInsetsSourceProvider(new InsetsSource(insetsTypes[i]),
+ insetsSourceProvider = new RectInsetsSourceProvider(
+ new InsetsSource(type, InsetsState.toPublicType(type)),
mDisplayContent.getInsetsStateController(), mDisplayContent);
mLocalInsetsSourceProviders.put(insetsTypes[i], insetsSourceProvider);
((RectInsetsSourceProvider) insetsSourceProvider).setRect(providerFrame);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d341ef9..3242c2c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -704,6 +704,7 @@
// changes the orientation.
private final PowerManager.WakeLock mScreenFrozenLock;
+ final SnapshotPersistQueue mSnapshotPersistQueue;
final TaskSnapshotController mTaskSnapshotController;
final BlurController mBlurController;
@@ -1210,7 +1211,8 @@
mSyncEngine = new BLASTSyncEngine(this);
mWindowPlacerLocked = new WindowSurfacePlacer(this);
- mTaskSnapshotController = new TaskSnapshotController(this);
+ mSnapshotPersistQueue = new SnapshotPersistQueue();
+ mTaskSnapshotController = new TaskSnapshotController(this, mSnapshotPersistQueue);
mWindowTracing = WindowTracing.createDefaultAndStartLooper(this,
Choreographer.getInstance());
@@ -5168,7 +5170,7 @@
mSystemReady = true;
mPolicy.systemReady();
mRoot.forAllDisplayPolicies(DisplayPolicy::systemReady);
- mTaskSnapshotController.systemReady();
+ mSnapshotPersistQueue.systemReady();
mHasWideColorGamutSupport = queryWideColorGamutSupport();
mHasHdrSupport = queryHdrSupport();
UiThread.getHandler().post(mSettingsObserver::loadSettings);
@@ -8657,16 +8659,17 @@
*/
void grantInputChannel(Session session, int callingUid, int callingPid, int displayId,
SurfaceControl surface, IWindow window, IBinder hostInputToken,
- int flags, int privateFlags, int type, IBinder focusGrantToken,
+ int flags, int privateFlags, int type, IBinder windowToken, IBinder focusGrantToken,
String inputHandleName, InputChannel outInputChannel) {
+ final int sanitizedType = sanitizeWindowType(session, displayId, windowToken, type);
final InputApplicationHandle applicationHandle;
final String name;
final InputChannel clientChannel;
synchronized (mGlobalLock) {
EmbeddedWindowController.EmbeddedWindow win =
new EmbeddedWindowController.EmbeddedWindow(session, this, window,
- mInputToWindowMap.get(hostInputToken), callingUid, callingPid, type,
- displayId, focusGrantToken, inputHandleName);
+ mInputToWindowMap.get(hostInputToken), callingUid, callingPid,
+ sanitizedType, displayId, focusGrantToken, inputHandleName);
clientChannel = win.openInputChannel();
mEmbeddedWindowController.add(clientChannel.getToken(), win);
applicationHandle = win.getApplicationHandle();
@@ -8674,7 +8677,8 @@
}
updateInputChannel(clientChannel.getToken(), callingUid, callingPid, displayId, surface,
- name, applicationHandle, flags, privateFlags, type, null /* region */, window);
+ name, applicationHandle, flags, privateFlags, sanitizedType,
+ null /* region */, window);
clientChannel.copyTo(outInputChannel);
}
@@ -9184,7 +9188,7 @@
@Override
public void setTaskSnapshotEnabled(boolean enabled) {
- mTaskSnapshotController.setTaskSnapshotEnabled(enabled);
+ mTaskSnapshotController.setSnapshotEnabled(enabled);
}
@Override
@@ -9251,7 +9255,7 @@
throw new IllegalArgumentException(
"Failed to find matching task for taskId=" + taskId);
}
- taskSnapshot = mTaskSnapshotController.captureTaskSnapshot(task, false);
+ taskSnapshot = mTaskSnapshotController.captureSnapshot(task, false);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -9360,4 +9364,36 @@
public boolean isGlobalKey(int keyCode) {
return mPolicy.isGlobalKey(keyCode);
}
+
+ private int sanitizeWindowType(Session session, int displayId, IBinder windowToken, int type) {
+ // Determine whether this window type is valid for this process.
+ final boolean isTypeValid;
+ if (type == TYPE_ACCESSIBILITY_OVERLAY && windowToken != null) {
+ // Only accessibility services can add accessibility overlays.
+ // Accessibility services will have a WindowToken with type
+ // TYPE_ACCESSIBILITY_OVERLAY.
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+ final WindowToken token = displayContent.getWindowToken(windowToken);
+ if (token == null) {
+ isTypeValid = false;
+ } else if (type == token.getWindowType()) {
+ isTypeValid = true;
+ } else {
+ isTypeValid = false;
+ }
+ } else if (!session.mCanAddInternalSystemWindow && type != 0) {
+ Slog.w(
+ TAG_WM,
+ "Requires INTERNAL_SYSTEM_WINDOW permission if assign type to"
+ + " input. New type will be 0.");
+ isTypeValid = false;
+ } else {
+ isTypeValid = true;
+ }
+
+ if (!isTypeValid) {
+ return 0;
+ }
+ return type;
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index bc382e0..3ca254d 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -27,8 +27,6 @@
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.PowerManager.DRAW_WAKE_LOCK;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.InsetsState.ITYPE_IME;
-import static android.view.InsetsState.ITYPE_INVALID;
import static android.view.SurfaceControl.Transaction;
import static android.view.SurfaceControl.getGlobalTransaction;
import static android.view.ViewRootImpl.LOCAL_LAYOUT;
@@ -916,7 +914,7 @@
// Skip performing seamless rotation when the controlled insets is IME with visible state.
if (mControllableInsetProvider != null
- && mControllableInsetProvider.getSource().getType() == ITYPE_IME) {
+ && mControllableInsetProvider.getSource().getType() == WindowInsets.Type.ime()) {
return;
}
@@ -1681,14 +1679,10 @@
if (rotatedState != null) {
return insetsPolicy.adjustInsetsForWindow(this, rotatedState);
}
- final InsetsSourceProvider provider = getControllableInsetProvider();
- final @InternalInsetsType int insetTypeProvidedByWindow = provider != null
- ? provider.getSource().getType() : ITYPE_INVALID;
final InsetsState rawInsetsState =
mFrozenInsetsState != null ? mFrozenInsetsState : getMergedInsetsState();
- final InsetsState insetsStateForWindow = insetsPolicy
- .enforceInsetsPolicyForTarget(insetTypeProvidedByWindow,
- getWindowingMode(), isAlwaysOnTop(), mAttrs.type, rawInsetsState);
+ final InsetsState insetsStateForWindow = insetsPolicy.enforceInsetsPolicyForTarget(
+ mAttrs, getWindowingMode(), isAlwaysOnTop(), rawInsetsState);
return insetsPolicy.adjustInsetsForWindow(this, insetsStateForWindow,
includeTransient);
}
diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp
index d975760..e322fa2 100644
--- a/services/core/jni/com_android_server_hint_HintManagerService.cpp
+++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp
@@ -87,6 +87,11 @@
appSession->sendHint(hint);
}
+static void setThreads(int64_t session_ptr, const std::vector<int32_t>& threadIds) {
+ sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ appSession->setThreads(threadIds);
+}
+
static int64_t getHintSessionPreferredRate() {
int64_t rate = -1;
auto result = gPowerHalController.getHintSessionPreferredRate();
@@ -149,6 +154,16 @@
sendHint(session_ptr, static_cast<SessionHint>(hint));
}
+static void nativeSetThreads(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jintArray tids) {
+ ScopedIntArrayRO arrayThreadIds(env, tids);
+
+ std::vector<int32_t> threadIds(arrayThreadIds.size());
+ for (size_t i = 0; i < arrayThreadIds.size(); i++) {
+ threadIds[i] = arrayThreadIds[i];
+ }
+ setThreads(session_ptr, threadIds);
+}
+
static jlong nativeGetHintSessionPreferredRate(JNIEnv* /* env */, jclass /* clazz */) {
return static_cast<jlong>(getHintSessionPreferredRate());
}
@@ -164,6 +179,7 @@
{"nativeUpdateTargetWorkDuration", "(JJ)V", (void*)nativeUpdateTargetWorkDuration},
{"nativeReportActualWorkDuration", "(J[J[J)V", (void*)nativeReportActualWorkDuration},
{"nativeSendHint", "(JI)V", (void*)nativeSendHint},
+ {"nativeSetThreads", "(J[I)V", (void*)nativeSetThreads},
{"nativeGetHintSessionPreferredRate", "()J", (void*)nativeGetHintSessionPreferredRate},
};
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 3d337b8..d9dc4ed 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -83,16 +83,6 @@
}
}
- private void respondToClientAndFinish(CreateCredentialResponse response) {
- Log.i(TAG, "respondToClientAndFinish");
- try {
- mClientCallback.onResponse(response);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- finishSession();
- }
-
@Override
public void onProviderStatusChanged(ProviderSession.Status status,
ComponentName componentName) {
@@ -101,10 +91,47 @@
@Override
public void onFinalResponseReceived(ComponentName componentName,
- CreateCredentialResponse response) {
+ @Nullable CreateCredentialResponse response) {
Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
if (response != null) {
- respondToClientAndFinish(response);
+ respondToClientWithResponseAndFinish(response);
+ } else {
+ // TODO("Replace with properly defined error type)
+ respondToClientWithErrorAndFinish("unknown_type",
+ "Invalid response");
}
}
+
+ @Override
+ public void onFinalErrorReceived(ComponentName componentName, String errorType,
+ String message) {
+ respondToClientWithErrorAndFinish(errorType, message);
+ }
+
+ @Override
+ public void onUiCancellation() {
+ // TODO("Replace with properly defined error type")
+ respondToClientWithErrorAndFinish("user_cancelled",
+ "User cancelled the selector");
+ }
+
+ private void respondToClientWithResponseAndFinish(CreateCredentialResponse response) {
+ Log.i(TAG, "respondToClientWithResponseAndFinish");
+ try {
+ mClientCallback.onResponse(response);
+ } catch (RemoteException e) {
+ Log.i(TAG, "Issue while responding to client: " + e.getMessage());
+ }
+ finishSession();
+ }
+
+ private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
+ Log.i(TAG, "respondToClientWithErrorAndFinish");
+ try {
+ mClientCallback.onError(errorType, errorMsg);
+ } catch (RemoteException e) {
+ Log.i(TAG, "Issue while responding to client: " + e.getMessage());
+ }
+ finishSession();
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index b4d7632..1f74e93 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -112,8 +112,9 @@
continue;
}
try {
- serviceList.add(
- new CredentialManagerServiceImpl(this, mLock, resolvedUserId, serviceName));
+ serviceList.add(new CredentialManagerServiceImpl(this, mLock,
+ resolvedUserId,
+ serviceName));
} catch (PackageManager.NameNotFoundException | SecurityException e) {
Log.i(TAG, "Unable to add serviceInfo : " + e.getMessage());
}
@@ -137,20 +138,21 @@
}
}
+ @SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
+ // to be guarded by 'service.mLock', which is the same as mLock.
private List<ProviderSession> initiateProviderSessions(
RequestSession session, List<String> requestOptions) {
List<ProviderSession> providerSessions = new ArrayList<>();
// Invoke all services of a user to initiate a provider session
- runForUser(
- (service) -> {
- if (service.isServiceCapable(requestOptions)) {
- ProviderSession providerSession =
- service.initiateProviderSessionForRequest(session);
- if (providerSession != null) {
- providerSessions.add(providerSession);
- }
- }
- });
+ runForUser((service) -> {
+ synchronized (mLock) {
+ ProviderSession providerSession = service
+ .initiateProviderSessionForRequestLocked(session, requestOptions);
+ if (providerSession != null) {
+ providerSessions.add(providerSession);
+ }
+ }
+ });
return providerSessions;
}
@@ -175,12 +177,20 @@
// Initiate all provider sessions
List<ProviderSession> providerSessions =
- initiateProviderSessions(
- session,
- request.getGetCredentialOptions().stream()
- .map(GetCredentialOption::getType)
- .collect(Collectors.toList()));
- // TODO : Return error when no providers available
+ initiateProviderSessions(session, request.getGetCredentialOptions()
+ .stream().map(GetCredentialOption::getType)
+ .collect(Collectors.toList()));
+
+ if (providerSessions.isEmpty()) {
+ try {
+ // TODO("Replace with properly defined error type")
+ callback.onError("unknown_type",
+ "No providers available to fulfill request.");
+ } catch (RemoteException e) {
+ Log.i(TAG, "Issue invoking onError on IGetCredentialCallback "
+ + "callback: " + e.getMessage());
+ }
+ }
// Iterate over all provider sessions and invoke the request
providerSessions.forEach(providerGetSession -> {
@@ -212,7 +222,17 @@
// Initiate all provider sessions
List<ProviderSession> providerSessions =
initiateProviderSessions(session, List.of(request.getType()));
- // TODO : Return error when no providers available
+
+ if (providerSessions.isEmpty()) {
+ try {
+ // TODO("Replace with properly defined error type")
+ callback.onError("unknown_type",
+ "No providers available to fulfill request.");
+ } catch (RemoteException e) {
+ Log.i(TAG, "Issue invoking onError on ICreateCredentialCallback "
+ + "callback: " + e.getMessage());
+ }
+ }
// Iterate over all provider sessions and invoke the request
providerSessions.forEach(
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
index 0c32304..08fdeed 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
@@ -22,8 +22,10 @@
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.service.credentials.CredentialProviderInfo;
+import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.server.infra.AbstractPerUserSystemService;
import java.util.List;
@@ -37,26 +39,33 @@
private static final String TAG = "CredManSysServiceImpl";
// TODO(b/210531) : Make final when update flow is fixed
- private ComponentName mRemoteServiceComponentName;
+ @GuardedBy("mLock")
private CredentialProviderInfo mInfo;
- public CredentialManagerServiceImpl(
+ CredentialManagerServiceImpl(
@NonNull CredentialManagerService master,
@NonNull Object lock, int userId, String serviceName)
throws PackageManager.NameNotFoundException {
super(master, lock, userId);
- Slog.i(TAG, "in CredentialManagerServiceImpl cons");
- // TODO : Replace with newServiceInfoLocked after confirming behavior
- mRemoteServiceComponentName = ComponentName.unflattenFromString(serviceName);
- mInfo = new CredentialProviderInfo(getContext(), mRemoteServiceComponentName, mUserId);
+ Log.i(TAG, "in CredentialManagerServiceImpl constructed with: " + serviceName);
+ synchronized (mLock) {
+ newServiceInfoLocked(ComponentName.unflattenFromString(serviceName));
+ }
}
@Override // from PerUserSystemService
+ @GuardedBy("mLock")
protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
throws PackageManager.NameNotFoundException {
// TODO : Test update flows with multiple providers
- Slog.i(TAG , "newServiceInfoLocked with : " + serviceComponent.getPackageName());
- mRemoteServiceComponentName = serviceComponent;
+ if (mInfo != null) {
+ Log.i(TAG, "newServiceInfoLocked with : "
+ + mInfo.getServiceInfo().getComponentName().flattenToString() + " , "
+ + serviceComponent.getPackageName());
+ } else {
+ Log.i(TAG, "newServiceInfoLocked with null mInfo , "
+ + serviceComponent.getPackageName());
+ }
mInfo = new CredentialProviderInfo(getContext(), serviceComponent, mUserId);
return mInfo.getServiceInfo();
}
@@ -64,8 +73,13 @@
/**
* Starts a provider session and associates it with the given request session. */
@Nullable
- public ProviderSession initiateProviderSessionForRequest(
- RequestSession requestSession) {
+ @GuardedBy("mLock")
+ public ProviderSession initiateProviderSessionForRequestLocked(
+ RequestSession requestSession, List<String> requestOptions) {
+ if (!isServiceCapableLocked(requestOptions)) {
+ Log.i(TAG, "Service is not capable");
+ return null;
+ }
Slog.i(TAG, "in initiateProviderSessionForRequest in CredManServiceImpl");
if (mInfo == null) {
Slog.i(TAG, "in initiateProviderSessionForRequest in CredManServiceImpl, "
@@ -80,7 +94,8 @@
}
/** Return true if at least one capability found. */
- boolean isServiceCapable(List<String> requestedOptions) {
+ @GuardedBy("mLock")
+ boolean isServiceCapableLocked(List<String> requestedOptions) {
if (mInfo == null) {
Slog.i(TAG, "in isServiceCapable, mInfo is null");
return false;
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 33c5ec9..64a5152 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -17,8 +17,11 @@
import android.annotation.NonNull;
import android.app.PendingIntent;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ServiceInfo;
+import android.credentials.ui.DisabledProviderData;
import android.credentials.ui.IntentFactory;
import android.credentials.ui.ProviderData;
import android.credentials.ui.RequestInfo;
@@ -27,11 +30,14 @@
import android.os.Handler;
import android.os.Looper;
import android.os.ResultReceiver;
+import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
import android.util.Slog;
import java.util.ArrayList;
+import java.util.Set;
import java.util.UUID;
+import java.util.stream.Collectors;
/** Initiates the Credential Manager UI and receives results. */
public class CredentialManagerUi {
@@ -89,8 +95,27 @@
public PendingIntent createPendingIntent(
RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) {
Log.i(TAG, "In createPendingIntent");
- Intent intent = IntentFactory.newIntent(requestInfo, providerDataList, new ArrayList<>(),
- mResultReceiver)
+
+ ArrayList<DisabledProviderData> disabledProviderDataList = new ArrayList<>();
+ Set<String> enabledProviders = providerDataList.stream()
+ .map(ProviderData::getProviderFlattenedComponentName)
+ .collect(Collectors.toUnmodifiableSet());
+ // TODO("Filter out non user configurable providers")
+ Set<String> allProviders =
+ CredentialProviderInfo.getAvailableServices(mContext, mUserId).stream()
+ .map(CredentialProviderInfo::getServiceInfo)
+ .map(ServiceInfo::getComponentName)
+ .map(ComponentName::flattenToString)
+ .collect(Collectors.toUnmodifiableSet());
+
+ for (String provider: allProviders) {
+ if (!enabledProviders.contains(provider)) {
+ disabledProviderDataList.add(new DisabledProviderData(provider));
+ }
+ }
+
+ Intent intent = IntentFactory.newIntent(requestInfo, providerDataList,
+ disabledProviderDataList, mResultReceiver)
.setAction(UUID.randomUUID().toString());
//TODO: Create unique pending intent using request code and cancel any pre-existing pending
// intents
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index c092b3a..6e070f9 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -88,15 +88,27 @@
@Override
public void onFinalResponseReceived(ComponentName componentName,
- GetCredentialResponse response) {
+ @Nullable GetCredentialResponse response) {
Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
if (response != null) {
- respondToClientAndFinish(response);
+ respondToClientWithResponseAndFinish(response);
+ } else {
+ // TODO("Replace with no credentials/unknown type when ready)
+ respondToClientWithErrorAndFinish("unknown_type",
+ "Invalid response from provider");
}
}
- private void respondToClientAndFinish(GetCredentialResponse response) {
- Log.i(TAG, "respondToClientAndFinish");
+ //TODO: Try moving the three error & response methods below to RequestSession to be shared
+ // between get & create.
+ @Override
+ public void onFinalErrorReceived(ComponentName componentName, String errorType,
+ String message) {
+ respondToClientWithErrorAndFinish(errorType, message);
+ }
+
+ private void respondToClientWithResponseAndFinish(GetCredentialResponse response) {
+ Log.i(TAG, "respondToClientWithResponseAndFinish");
try {
mClientCallback.onResponse(response);
} catch (RemoteException e) {
@@ -104,4 +116,21 @@
}
finishSession();
}
+
+ private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
+ Log.i(TAG, "respondToClientWithErrorAndFinish");
+ try {
+ mClientCallback.onError(errorType, errorMsg);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ finishSession();
+ }
+
+ @Override
+ public void onUiCancellation() {
+ // TODO("Replace with properly defined error type")
+ respondToClientWithErrorAndFinish("user_canceled",
+ "User cancelled the selector");
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java b/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
index 7f9e57a..8796314 100644
--- a/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
+++ b/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
@@ -18,7 +18,9 @@
import android.app.Activity;
import android.content.Intent;
+import android.credentials.CreateCredentialException;
import android.credentials.CreateCredentialResponse;
+import android.credentials.GetCredentialException;
import android.credentials.GetCredentialResponse;
import android.credentials.ui.ProviderPendingIntentResponse;
import android.service.credentials.CredentialProviderService;
@@ -31,7 +33,7 @@
*/
public class PendingIntentResultHandler {
/** Returns true if the result is successful and may contain result extras. */
- public static boolean isSuccessfulResponse(
+ public static boolean isValidResponse(
ProviderPendingIntentResponse pendingIntentResponse) {
//TODO: Differentiate based on extra_error in the resultData
return pendingIntentResponse.getResultCode() == Activity.RESULT_OK;
@@ -66,4 +68,28 @@
CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
GetCredentialResponse.class);
}
+
+ /** Extract the {@link CreateCredentialException} from the
+ * given pending intent . */
+ public static CreateCredentialException extractCreateCredentialException(
+ Intent resultData) {
+ if (resultData == null) {
+ return null;
+ }
+ return resultData.getParcelableExtra(
+ CredentialProviderService.EXTRA_CREATE_CREDENTIAL_EXCEPTION,
+ CreateCredentialException.class);
+ }
+
+ /** Extract the {@link GetCredentialException} from the
+ * given pending intent . */
+ public static GetCredentialException extractGetCredentialException(
+ Intent resultData) {
+ if (resultData == null) {
+ return null;
+ }
+ return resultData.getParcelableExtra(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION,
+ GetCredentialException.class);
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 32e85b0..855f274 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.Signature;
+import android.credentials.CreateCredentialException;
import android.credentials.ui.CreateCredentialProviderData;
import android.credentials.ui.Entry;
import android.credentials.ui.ProviderPendingIntentResponse;
@@ -58,6 +59,8 @@
/** The complete request to be used in the second round. */
private final CreateCredentialRequest mCompleteRequest;
+ private CreateCredentialException mProviderException;
+
/** Creates a new provider session to be used by the request session. */
@Nullable public static ProviderCreateSession createNewSession(
Context context,
@@ -124,8 +127,11 @@
/** Called when the provider response resulted in a failure. */
@Override
- public void onProviderResponseFailure(int errorCode, @Nullable String errorType,
- @Nullable CharSequence message) {
+ public void onProviderResponseFailure(int errorCode, @Nullable Exception exception) {
+ if (exception instanceof CreateCredentialException) {
+ // Store query phase exception for aggregation with final response
+ mProviderException = (CreateCredentialException) exception;
+ }
updateStatusAndInvokeCallback(toStatus(errorCode));
}
@@ -177,16 +183,20 @@
if (mUiSaveEntries.containsKey(entryKey)) {
onSaveEntrySelected(providerPendingIntentResponse);
} else {
- //TODO: Handle properly
Log.i(TAG, "Unexpected save entry key");
+ // TODO("Replace with no credentials error type");
+ invokeCallbackWithError("unknown_type",
+ "Issue while retrieving credential");
}
break;
case REMOTE_ENTRY_KEY:
if (mUiRemoteEntry.first.equals(entryKey)) {
onRemoteEntrySelected(providerPendingIntentResponse);
} else {
- //TODO: Handle properly
Log.i(TAG, "Unexpected remote entry key");
+ // TODO("Replace with unknown/no credentials exception")
+ invokeCallbackWithError("unknown_type",
+ "Issue while retrieving credential");
}
break;
default:
@@ -227,19 +237,53 @@
}
private void onSaveEntrySelected(ProviderPendingIntentResponse pendingIntentResponse) {
- if (pendingIntentResponse == null) {
+ CreateCredentialException exception = maybeGetPendingIntentException(
+ pendingIntentResponse);
+ if (exception != null) {
+ invokeCallbackWithError(
+ exception.errorType,
+ exception.getMessage());
return;
- //TODO: Handle failure if pending intent is null
}
- if (PendingIntentResultHandler.isSuccessfulResponse(pendingIntentResponse)) {
- android.credentials.CreateCredentialResponse credentialResponse =
- PendingIntentResultHandler.extractCreateCredentialResponse(
- pendingIntentResponse.getResultData());
- if (credentialResponse != null) {
- mCallbacks.onFinalResponseReceived(mComponentName, credentialResponse);
- return;
+ android.credentials.CreateCredentialResponse credentialResponse =
+ PendingIntentResultHandler.extractCreateCredentialResponse(
+ pendingIntentResponse.getResultData());
+ if (credentialResponse != null) {
+ mCallbacks.onFinalResponseReceived(mComponentName, credentialResponse);
+ return;
+ } else {
+ Log.i(TAG, "onSaveEntrySelected - no response or error found in pending "
+ + "intent response");
+ invokeCallbackWithError(
+ // TODO("Replace with unknown/no credentials exception")
+ "unknown",
+ "Issue encountered while retrieving the credential");
+ }
+ }
+
+ private void invokeCallbackWithError(String errorType, @Nullable String message) {
+ mCallbacks.onFinalErrorReceived(mComponentName, errorType, message);
+ }
+
+ @Nullable
+ private CreateCredentialException maybeGetPendingIntentException(
+ ProviderPendingIntentResponse pendingIntentResponse) {
+ if (pendingIntentResponse == null) {
+ Log.i(TAG, "pendingIntentResponse is null");
+ return null;
+ }
+ if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
+ CreateCredentialException exception = PendingIntentResultHandler
+ .extractCreateCredentialException(pendingIntentResponse.getResultData());
+ if (exception != null) {
+ Log.i(TAG, "Pending intent contains provider exception");
+ return exception;
}
+ } else {
+ Log.i(TAG, "Pending intent result code not Activity.RESULT_OK");
+ // TODO("Update with unknown exception when ready")
+ return new CreateCredentialException("unknown");
}
- //TODO: Handle failure case is pending intent response does not have a credential
+ return null;
}
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 218fc21..42f872d2 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -22,7 +22,7 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.Signature;
+import android.credentials.GetCredentialException;
import android.credentials.GetCredentialOption;
import android.credentials.GetCredentialResponse;
import android.credentials.ui.Entry;
@@ -80,6 +80,8 @@
/** The complete request to be used in the second round. */
private final GetCredentialRequest mCompleteRequest;
+ private GetCredentialException mProviderException;
+
/** Creates a new provider session to be used by the request session. */
@Nullable public static ProviderGetSession createNewSession(
Context context,
@@ -131,7 +133,7 @@
if (!filteredOptions.isEmpty()) {
return new GetCredentialRequest.Builder(
new CallingAppInfo(clientCallingPackage,
- new ArraySet<Signature>())).setGetCredentialOptions(
+ new ArraySet<>())).setGetCredentialOptions(
filteredOptions).build();
}
Log.i(TAG, "In createProviderRequest - returning null");
@@ -164,8 +166,10 @@
/** Called when the provider response resulted in a failure. */
@Override // Callback from the remote provider
- public void onProviderResponseFailure(int errorCode, @Nullable String errorType,
- @Nullable CharSequence message) {
+ public void onProviderResponseFailure(int errorCode, Exception exception) {
+ if (exception instanceof GetCredentialException) {
+ mProviderException = (GetCredentialException) exception;
+ }
updateStatusAndInvokeCallback(toStatus(errorCode));
}
@@ -187,8 +191,10 @@
case CREDENTIAL_ENTRY_KEY:
CredentialEntry credentialEntry = mUiCredentialEntries.get(entryKey);
if (credentialEntry == null) {
- Log.i(TAG, "Credential entry not found");
- //TODO: Handle properly
+ Log.i(TAG, "Unexpected credential entry key");
+ // TODO("Replace with no credentials/unknown exception")
+ invokeCallbackWithError("unknown_type",
+ "Issue while retrieving credential");
return;
}
onCredentialEntrySelected(credentialEntry, providerPendingIntentResponse);
@@ -196,8 +202,10 @@
case ACTION_ENTRY_KEY:
Action actionEntry = mUiActionsEntries.get(entryKey);
if (actionEntry == null) {
- Log.i(TAG, "Action entry not found");
- //TODO: Handle properly
+ Log.i(TAG, "Unexpected action entry key");
+ // TODO("Replace with no credentials/unknown exception")
+ invokeCallbackWithError("unknown_type",
+ "Issue while retrieving credential");
return;
}
onActionEntrySelected(providerPendingIntentResponse);
@@ -206,16 +214,20 @@
if (mUiAuthenticationAction.first.equals(entryKey)) {
onAuthenticationEntrySelected(providerPendingIntentResponse);
} else {
- //TODO: Handle properly
- Log.i(TAG, "Authentication entry not found");
+ Log.i(TAG, "Unexpected authentication entry key");
+ // TODO("Replace with no credentials/unknown exception")
+ invokeCallbackWithError("unknown_type",
+ "Issue while retrieving credential");
}
break;
case REMOTE_ENTRY_KEY:
if (mUiRemoteEntry.first.equals(entryKey)) {
onRemoteEntrySelected(providerPendingIntentResponse);
} else {
- //TODO: Handle properly
- Log.i(TAG, "Remote entry not found");
+ Log.i(TAG, "Unexpected remote entry key");
+ // TODO("Replace with no credentials/unknown exception")
+ invokeCallbackWithError("unknown_type",
+ "Issue while retrieving credential");
}
break;
default:
@@ -223,6 +235,11 @@
}
}
+ private void invokeCallbackWithError(String errorType, @Nullable String errorMessage) {
+ // TODO: Determine what the error message should be
+ mCallbacks.onFinalErrorReceived(mComponentName, errorType, errorMessage);
+ }
+
@Override // Call from request session to data to be shown on the UI
@Nullable protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
Log.i(TAG, "In prepareUiData");
@@ -284,10 +301,9 @@
mUiCredentialEntries.put(entryId, credentialEntry);
Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
if (credentialEntry.getPendingIntent() != null) {
- setUpFillInIntent(credentialEntry.getPendingIntent());
credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
credentialEntry.getSlice(), credentialEntry.getPendingIntent(),
- /*fillInIntent=*/null));
+ /*fillInIntent=*/setUpFillInIntent(credentialEntry.getPendingIntent())));
} else {
Log.i(TAG, "No pending intent. Should not happen.");
}
@@ -330,39 +346,61 @@
private void onCredentialEntrySelected(CredentialEntry credentialEntry,
ProviderPendingIntentResponse providerPendingIntentResponse) {
if (providerPendingIntentResponse != null) {
- if (PendingIntentResultHandler.isSuccessfulResponse(providerPendingIntentResponse)) {
- // TODO: Remove credential extraction when flow is fully transitioned
- GetCredentialResponse getCredentialResponse = PendingIntentResultHandler
- .extractGetCredentialResponse(
- providerPendingIntentResponse.getResultData());
- if (getCredentialResponse != null) {
- mCallbacks.onFinalResponseReceived(mComponentName, getCredentialResponse);
- return;
- }
+ // Check if pending intent has an error
+ GetCredentialException exception = maybeGetPendingIntentException(
+ providerPendingIntentResponse);
+ if (exception != null) {
+ invokeCallbackWithError(exception.errorType,
+ exception.getMessage());
+ return;
}
- // TODO: Handle other pending intent statuses
+
+ // Check if pending intent has a credential
+ GetCredentialResponse getCredentialResponse = PendingIntentResultHandler
+ .extractGetCredentialResponse(
+ providerPendingIntentResponse.getResultData());
+ if (getCredentialResponse != null) {
+ mCallbacks.onFinalResponseReceived(mComponentName,
+ getCredentialResponse);
+ return;
+ }
+
+ Log.i(TAG, "Pending intent response contains no credential, or error");
+ // TODO("Replace with no credentials/unknown error when ready)
+ invokeCallbackWithError("unknown_type",
+ "Issue while retrieving credential");
}
Log.i(TAG, "CredentialEntry does not have a credential or a pending intent result");
- // TODO: Propagate failure to client
+ // TODO("Replace with no credentials/unknown error when ready)
+ invokeCallbackWithError("unknown_type",
+ "Error encountered while retrieving the credential");
}
private void onAuthenticationEntrySelected(
@Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
- if (providerPendingIntentResponse != null) {
- if (PendingIntentResultHandler.isSuccessfulResponse(providerPendingIntentResponse)) {
- CredentialsResponseContent content = PendingIntentResultHandler
- .extractResponseContent(providerPendingIntentResponse
- .getResultData());
- if (content != null) {
- onUpdateResponse(
- BeginGetCredentialResponse.createWithResponseContent(content));
- return;
- }
- }
//TODO: Other provider intent statuses
+ // Check if pending intent has an error
+ GetCredentialException exception = maybeGetPendingIntentException(
+ providerPendingIntentResponse);
+ if (exception != null) {
+ invokeCallbackWithError(exception.errorType,
+ exception.getMessage());
+ return;
}
- Log.i(TAG, "Display content not present in pending intent result");
- // TODO: Propagate error to client
+
+ // Check if pending intent has the content
+ CredentialsResponseContent content = PendingIntentResultHandler
+ .extractResponseContent(providerPendingIntentResponse
+ .getResultData());
+ if (content != null) {
+ onUpdateResponse(BeginGetCredentialResponse.createWithResponseContent(content));
+ return;
+ }
+
+ Log.i(TAG, "No error or respond found in pending intent response");
+ // TODO("Replace with no credentials/unknown error when ready)
+ invokeCallbackWithError("unknown type", "Issue"
+ + " while retrieving credential");
}
private void onActionEntrySelected(ProviderPendingIntentResponse
@@ -383,4 +421,26 @@
updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
}
}
+
+ @Nullable
+ private GetCredentialException maybeGetPendingIntentException(
+ ProviderPendingIntentResponse pendingIntentResponse) {
+ if (pendingIntentResponse == null) {
+ Log.i(TAG, "pendingIntentResponse is null");
+ return null;
+ }
+ if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
+ GetCredentialException exception = PendingIntentResultHandler
+ .extractGetCredentialException(pendingIntentResponse.getResultData());
+ if (exception != null) {
+ Log.i(TAG, "Pending intent contains provider exception");
+ return exception;
+ }
+ } else {
+ Log.i(TAG, "Pending intent result code not Activity.RESULT_OK");
+ // TODO("Update with unknown exception when ready")
+ return new GetCredentialException("unknown");
+ }
+ return null;
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index ac360bd..7ecae9d 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -24,7 +24,6 @@
import android.credentials.ui.ProviderData;
import android.credentials.ui.ProviderPendingIntentResponse;
import android.service.credentials.CredentialEntry;
-import android.service.credentials.CredentialProviderException;
import android.service.credentials.CredentialProviderInfo;
import android.util.Pair;
@@ -37,6 +36,8 @@
*/
public abstract class ProviderSession<T, R>
implements RemoteCredentialService.ProviderCallbacks<R> {
+
+ private static final String TAG = "ProviderSession";
// Key to be used as an entry key for a remote entry
protected static final String REMOTE_ENTRY_KEY = "remote_entry_key";
@@ -52,6 +53,7 @@
@Nullable protected R mProviderResponse;
@Nullable protected Pair<String, CredentialEntry> mUiRemoteEntry;
+
/**
* Returns true if the given status reflects that the provider state is ready to be shown
* on the credMan UI.
@@ -95,8 +97,12 @@
/** Called when status changes. */
void onProviderStatusChanged(Status status, ComponentName componentName);
- /** Called when the final credential to be returned to the client has been received. */
+ /** Called when the final credential is received through an entry selection. */
void onFinalResponseReceived(ComponentName componentName, V response);
+
+ /** Called when an error is received through an entry selection. */
+ void onFinalErrorReceived(ComponentName componentName, String errorType,
+ @Nullable String message);
}
protected ProviderSession(@NonNull Context context, @NonNull CredentialProviderInfo info,
@@ -129,8 +135,7 @@
/** Converts exception to a provider session status. */
@NonNull
- public static Status toStatus(
- @CredentialProviderException.CredentialProviderError int errorCode) {
+ public static Status toStatus(int errorCode) {
// TODO : Add more mappings as more flows are supported
return Status.CANCELED;
}
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
index 307d96a..6049fd9 100644
--- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -30,7 +30,7 @@
import android.service.credentials.BeginCreateCredentialResponse;
import android.service.credentials.BeginGetCredentialRequest;
import android.service.credentials.BeginGetCredentialResponse;
-import android.service.credentials.CredentialProviderException;
+import android.service.credentials.CredentialProviderErrors;
import android.service.credentials.CredentialProviderService;
import android.service.credentials.IBeginCreateCredentialCallback;
import android.service.credentials.IBeginGetCredentialCallback;
@@ -72,8 +72,7 @@
/** Called when a successful response is received from the remote provider. */
void onProviderResponseSuccess(@Nullable T response);
/** Called when a failure response is received from the remote provider. */
- void onProviderResponseFailure(int errorCode, @Nullable String errorType,
- @Nullable CharSequence message);
+ void onProviderResponseFailure(int internalErrorCode, @Nullable Exception e);
/** Called when the remote provider service dies. */
void onProviderServiceDied(RemoteCredentialService service);
}
@@ -208,36 +207,31 @@
Log.i(TAG, "In RemoteCredentialService execute error is timeout");
dispatchCancellationSignal(cancellationSink.get());
callback.onProviderResponseFailure(
- CredentialProviderException.ERROR_TIMEOUT,
- null,
- error.getMessage());
+ CredentialProviderErrors.ERROR_TIMEOUT,
+ null);
} else if (error instanceof CancellationException) {
Log.i(TAG, "In RemoteCredentialService execute error is cancellation");
dispatchCancellationSignal(cancellationSink.get());
callback.onProviderResponseFailure(
- CredentialProviderException.ERROR_TASK_CANCELED,
- null,
- error.getMessage());
+ CredentialProviderErrors.ERROR_TASK_CANCELED,
+ null);
} else if (error instanceof GetCredentialException) {
Log.i(TAG, "In RemoteCredentialService execute error is provider get"
+ "error");
callback.onProviderResponseFailure(
- CredentialProviderException.ERROR_PROVIDER_FAILURE,
- ((GetCredentialException) error).errorType,
- error.getMessage());
+ CredentialProviderErrors.ERROR_PROVIDER_FAILURE,
+ (GetCredentialException) error);
} else if (error instanceof CreateCredentialException) {
Log.i(TAG, "In RemoteCredentialService execute error is provider create "
+ "error");
callback.onProviderResponseFailure(
- CredentialProviderException.ERROR_PROVIDER_FAILURE,
- ((CreateCredentialException) error).errorType,
- error.getMessage());
+ CredentialProviderErrors.ERROR_PROVIDER_FAILURE,
+ (CreateCredentialException) error);
} else {
Log.i(TAG, "In RemoteCredentialService execute error is unknown");
callback.onProviderResponseFailure(
- CredentialProviderException.ERROR_UNKNOWN,
- null,
- error.getMessage());
+ CredentialProviderErrors.ERROR_UNKNOWN,
+ (Exception) error);
}
}
}
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 71fc67c..937fac9 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -93,6 +93,7 @@
@Override // from CredentialManagerUiCallbacks
public void onUiCancellation() {
+ Log.i(TAG, "Ui canceled");
// User canceled the activity
finishSession();
}
@@ -133,10 +134,12 @@
}
protected void finishSession() {
+ Log.i(TAG, "finishing session");
clearProviderSessions();
}
protected void clearProviderSessions() {
+ Log.i(TAG, "Clearing sessions");
//TODO: Implement
mProviders.clear();
}
@@ -151,6 +154,9 @@
}
private void getProviderDataAndInitiateUi() {
+ Log.i(TAG, "In getProviderDataAndInitiateUi");
+ Log.i(TAG, "In getProviderDataAndInitiateUi providers size: " + mProviders.size());
+
ArrayList<ProviderData> providerDataList = new ArrayList<>();
for (ProviderSession session : mProviders.values()) {
Log.i(TAG, "preparing data for : " + session.getComponentName());
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c68eea8..f41d4ab 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -46,6 +46,7 @@
import android.content.pm.PackageManagerInternal;
import android.content.res.Configuration;
import android.content.res.Resources.Theme;
+import android.credentials.CredentialManager;
import android.database.sqlite.SQLiteCompatibilityWalFlags;
import android.database.sqlite.SQLiteGlobal;
import android.graphics.GraphicsStatsService;
@@ -2609,9 +2610,16 @@
}
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_CREDENTIALS)) {
- t.traceBegin("StartCredentialManagerService");
- mSystemServiceManager.startService(CREDENTIAL_MANAGER_SERVICE_CLASS);
- t.traceEnd();
+ boolean credentialManagerEnabled =
+ DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CREDENTIAL,
+ CredentialManager.DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER, true);
+ if (credentialManagerEnabled) {
+ t.traceBegin("StartCredentialManagerService");
+ mSystemServiceManager.startService(CREDENTIAL_MANAGER_SERVICE_CLASS);
+ t.traceEnd();
+ } else {
+ Slog.d(TAG, "CredentialManager disabled.");
+ }
}
// Translation manager service
diff --git a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
index 306970a..3b277f8 100644
--- a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
@@ -17,6 +17,10 @@
package com.android.server.permission.access
import android.content.Context
+import android.content.pm.PackageManager
+import android.content.pm.PackageManagerInternal
+import android.os.SystemProperties
+import android.os.UserHandle
import com.android.internal.annotations.Keep
import com.android.server.LocalManagerRegistry
import com.android.server.LocalServices
@@ -24,12 +28,16 @@
import com.android.server.SystemService
import com.android.server.appop.AppOpsCheckingServiceInterface
import com.android.server.permission.access.appop.AppOpService
-import com.android.server.permission.access.collection.IntSet
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
import com.android.server.permission.access.permission.PermissionService
+import com.android.server.pm.KnownPackages
import com.android.server.pm.PackageManagerLocal
import com.android.server.pm.UserManagerService
import com.android.server.pm.permission.PermissionManagerServiceInterface
import com.android.server.pm.pkg.PackageState
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
@Keep
class AccessCheckingService(context: Context) : SystemService(context) {
@@ -44,6 +52,7 @@
private lateinit var appOpService: AppOpService
private lateinit var permissionService: PermissionService
+ private lateinit var packageManagerInternal: PackageManagerInternal
private lateinit var packageManagerLocal: PackageManagerLocal
private lateinit var userManagerService: UserManagerService
private lateinit var systemConfig: SystemConfig
@@ -57,6 +66,7 @@
}
fun initialize() {
+ packageManagerInternal = LocalServices.getService(PackageManagerInternal::class.java)
packageManagerLocal =
LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal::class.java)
userManagerService = UserManagerService.getInstance()
@@ -64,19 +74,94 @@
val userIds = IntSet(userManagerService.userIdsIncludingPreCreated)
val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
+ val knownPackages = packageManagerInternal.knownPackages
+ val isLeanback = systemConfig.isLeanback
+ val configPermissions = systemConfig.permissions
+ val privilegedPermissionAllowlistPackages =
+ systemConfig.privilegedPermissionAllowlistPackages
val permissionAllowlist = systemConfig.permissionAllowlist
+ val implicitToSourcePermissions = systemConfig.implicitToSourcePermissions
val state = AccessState()
policy.initialize(
- state, userIds, packageStates, disabledSystemPackageStates, permissionAllowlist
+ state, userIds, packageStates, disabledSystemPackageStates, knownPackages, isLeanback,
+ configPermissions, privilegedPermissionAllowlistPackages, permissionAllowlist,
+ implicitToSourcePermissions
)
persistence.read(state)
this.state = state
+ mutateState {
+ with(policy) { onInitialized() }
+ }
+
appOpService.initialize()
permissionService.initialize()
}
+ private val PackageManagerInternal.knownPackages: IntMap<Array<String>>
+ get() = IntMap<Array<String>>().apply {
+ this[KnownPackages.PACKAGE_INSTALLER] = getKnownPackageNames(
+ KnownPackages.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM
+ )
+ this[KnownPackages.PACKAGE_PERMISSION_CONTROLLER] = getKnownPackageNames(
+ KnownPackages.PACKAGE_PERMISSION_CONTROLLER, UserHandle.USER_SYSTEM
+ )
+ this[KnownPackages.PACKAGE_VERIFIER] = getKnownPackageNames(
+ KnownPackages.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM
+ )
+ this[KnownPackages.PACKAGE_SETUP_WIZARD] = getKnownPackageNames(
+ KnownPackages.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM
+ )
+ this[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER] = getKnownPackageNames(
+ KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER, UserHandle.USER_SYSTEM
+ )
+ this[KnownPackages.PACKAGE_CONFIGURATOR] = getKnownPackageNames(
+ KnownPackages.PACKAGE_CONFIGURATOR, UserHandle.USER_SYSTEM
+ )
+ this[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER] = getKnownPackageNames(
+ KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER, UserHandle.USER_SYSTEM
+ )
+ this[KnownPackages.PACKAGE_APP_PREDICTOR] = getKnownPackageNames(
+ KnownPackages.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM
+ )
+ this[KnownPackages.PACKAGE_COMPANION] = getKnownPackageNames(
+ KnownPackages.PACKAGE_COMPANION, UserHandle.USER_SYSTEM
+ )
+ this[KnownPackages.PACKAGE_RETAIL_DEMO] = getKnownPackageNames(
+ KnownPackages.PACKAGE_RETAIL_DEMO, UserHandle.USER_SYSTEM
+ )
+ this[KnownPackages.PACKAGE_RECENTS] = getKnownPackageNames(
+ KnownPackages.PACKAGE_RECENTS, UserHandle.USER_SYSTEM
+ )
+ }
+
+ private val SystemConfig.isLeanback: Boolean
+ get() = PackageManager.FEATURE_LEANBACK in availableFeatures
+
+ private val SystemConfig.privilegedPermissionAllowlistPackages: IndexedListSet<String>
+ get() = IndexedListSet<String>().apply {
+ this += "android"
+ if (PackageManager.FEATURE_AUTOMOTIVE in availableFeatures) {
+ // Note that SystemProperties.get(String, String) forces returning an empty string
+ // even if we pass null for the def parameter.
+ val carServicePackage = SystemProperties.get("ro.android.car.carservice.package")
+ if (carServicePackage.isNotEmpty()) {
+ this += carServicePackage
+ }
+ }
+ }
+
+ private val SystemConfig.implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>
+ get() = IndexedMap<String, IndexedListSet<String>>().apply {
+ splitPermissions.forEach { splitPermissionInfo ->
+ val sourcePermissionName = splitPermissionInfo.splitPermission
+ splitPermissionInfo.newPermissions.forEach { implicitPermissionName ->
+ getOrPut(implicitPermissionName) { IndexedListSet() } += sourcePermissionName
+ }
+ }
+ }
+
fun getDecision(subject: AccessUri, `object`: AccessUri): Int =
getState {
with(policy) { getDecision(subject, `object`) }
@@ -151,10 +236,15 @@
Pair<Map<String, PackageState>, Map<String, PackageState>>
get() = withUnfilteredSnapshot().use { it.packageStates to it.disabledSystemPackageStates }
- internal inline fun <T> getState(action: GetStateScope.() -> T): T =
- GetStateScope(state).action()
+ @OptIn(ExperimentalContracts::class)
+ internal inline fun <T> getState(action: GetStateScope.() -> T): T {
+ contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) }
+ return GetStateScope(state).action()
+ }
+ @OptIn(ExperimentalContracts::class)
internal inline fun mutateState(crossinline action: MutateStateScope.() -> Unit) {
+ contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) }
synchronized(stateLock) {
val oldState = state
val newState = oldState.copy()
diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
index 8cf4aeec..2d83bfd 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
@@ -19,6 +19,7 @@
import android.util.Log
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
+import com.android.server.SystemConfig
import com.android.server.permission.access.appop.PackageAppOpPolicy
import com.android.server.permission.access.appop.UidAppOpPolicy
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
@@ -59,7 +60,12 @@
userIds: IntSet,
packageStates: Map<String, PackageState>,
disabledSystemPackageStates: Map<String, PackageState>,
- permissionAllowlist: PermissionAllowlist
+ knownPackages: IntMap<Array<String>>,
+ isLeanback: Boolean,
+ configPermissions: Map<String, SystemConfig.PermissionEntry>,
+ privilegedPermissionAllowlistPackages: IndexedListSet<String>,
+ permissionAllowlist: PermissionAllowlist,
+ implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>
) {
state.systemState.apply {
this.userIds += userIds
@@ -69,7 +75,12 @@
appIds.getOrPut(packageState.appId) { IndexedListSet() }
.add(packageState.packageName)
}
+ this.knownPackages = knownPackages
+ this.isLeanback = isLeanback
+ this.configPermissions = configPermissions
+ this.privilegedPermissionAllowlistPackages = privilegedPermissionAllowlistPackages
this.permissionAllowlist = permissionAllowlist
+ this.implicitToSourcePermissions = implicitToSourcePermissions
}
}
@@ -79,6 +90,12 @@
}
}
+ fun MutateStateScope.onInitialized() {
+ forEachSchemePolicy {
+ with(it) { onInitialized() }
+ }
+ }
+
fun MutateStateScope.onUserAdded(userId: Int) {
newState.systemState.userIds += userId
newState.userStates[userId] = UserState()
@@ -292,6 +309,8 @@
open fun GetStateScope.onStateMutated() {}
+ open fun MutateStateScope.onInitialized() {}
+
open fun MutateStateScope.onUserAdded(userId: Int) {}
open fun MutateStateScope.onUserRemoved(userId: Int) {}
diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt
index 6924d51..9616193 100644
--- a/services/permission/java/com/android/server/permission/access/AccessState.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessState.kt
@@ -17,6 +17,7 @@
package com.android.server.permission.access
import android.content.pm.PermissionGroupInfo
+import com.android.server.SystemConfig
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
import com.android.server.permission.access.permission.Permission
import com.android.server.pm.permission.PermissionAllowlist
@@ -42,15 +43,15 @@
var packageStates: Map<String, PackageState>,
var disabledSystemPackageStates: Map<String, PackageState>,
val appIds: IntMap<IndexedListSet<String>>,
- // A map of KnownPackagesInt to a set of known package names
- val knownPackages: IntMap<IndexedListSet<String>>,
- // A map of userId to packageName
- val deviceAndProfileOwners: IntMap<String>,
- // Whether the device supports leanback UI
+ // Mapping from KnownPackages keys to package names.
+ var knownPackages: IntMap<Array<String>>,
var isLeanback: Boolean,
- val privilegedPermissionAllowlistSourcePackageNames: IndexedListSet<String>,
+ var configPermissions: Map<String, SystemConfig.PermissionEntry>,
+ var privilegedPermissionAllowlistPackages: IndexedListSet<String>,
var permissionAllowlist: PermissionAllowlist,
- val implicitToSourcePermissions: Map<String, Set<String>>,
+ var implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>,
+ // Mapping from user ID to package name.
+ var deviceAndProfileOwners: IntMap<String>,
val permissionGroups: IndexedMap<String, PermissionGroupInfo>,
val permissionTrees: IndexedMap<String, Permission>,
val permissions: IndexedMap<String, Permission>
@@ -61,11 +62,12 @@
emptyMap(),
IntMap(),
IntMap(),
- IntMap(),
false,
+ emptyMap(),
IndexedListSet(),
PermissionAllowlist(),
IndexedMap(),
+ IntMap(),
IndexedMap(),
IndexedMap(),
IndexedMap()
@@ -77,12 +79,13 @@
packageStates,
disabledSystemPackageStates,
appIds.copy { it.copy() },
- knownPackages.copy { it.copy() },
- deviceAndProfileOwners.copy { it },
+ knownPackages,
isLeanback,
- privilegedPermissionAllowlistSourcePackageNames.copy(),
+ configPermissions,
+ privilegedPermissionAllowlistPackages,
permissionAllowlist,
implicitToSourcePermissions,
+ deviceAndProfileOwners,
permissionGroups.copy { it },
permissionTrees.copy { it },
permissions.copy { it }
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt
index 1e73be7..f4e362c 100644
--- a/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt
@@ -148,10 +148,21 @@
return isChanged
}
-inline fun <K, V, R> IndexedMap<K, V>.mapNotNullIndexed(transform: (K, V) -> R?): IndexedList<R> =
+inline fun <K, V, R> IndexedMap<K, V>.mapNotNullIndexed(
+ transform: (Int, K, V) -> R?
+): IndexedList<R> =
IndexedList<R>().also { destination ->
- forEachIndexed { _, key, value ->
- transform(key, value)?.let { destination += it }
+ forEachIndexed { index, key, value ->
+ transform(index, key, value)?.let { destination += it }
+ }
+ }
+
+inline fun <K, V, R> IndexedMap<K, V>.mapNotNullIndexedToSet(
+ transform: (Int, K, V) -> R?
+): IndexedSet<R> =
+ IndexedSet<R>().also { destination ->
+ forEachIndexed { index, key, value ->
+ transform(index, key, value)?.let { destination += it }
}
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/Permission.kt b/services/permission/java/com/android/server/permission/access/permission/Permission.kt
index de2df747..88989c4 100644
--- a/services/permission/java/com/android/server/permission/access/permission/Permission.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/Permission.kt
@@ -17,13 +17,18 @@
package com.android.server.permission.access.permission
import android.content.pm.PermissionInfo
+import android.os.UserHandle
import com.android.server.permission.access.util.hasBits
+import libcore.util.EmptyArray
data class Permission(
val permissionInfo: PermissionInfo,
val isReconciled: Boolean,
val type: Int,
- val appId: Int
+ val appId: Int,
+ @Suppress("ArrayInDataClass")
+ val gids: IntArray = EmptyArray.INT,
+ val areGidsPerUser: Boolean = false
) {
inline val name: String
get() = permissionInfo.name
@@ -37,36 +42,50 @@
inline val isDynamic: Boolean
get() = type == TYPE_DYNAMIC
+ inline val protectionLevel: Int
+ @Suppress("DEPRECATION")
+ get() = permissionInfo.protectionLevel
+
+ inline val isInternal: Boolean
+ get() = permissionInfo.protection == PermissionInfo.PROTECTION_INTERNAL
+
inline val isNormal: Boolean
get() = permissionInfo.protection == PermissionInfo.PROTECTION_NORMAL
inline val isRuntime: Boolean
get() = permissionInfo.protection == PermissionInfo.PROTECTION_DANGEROUS
- inline val isAppOp: Boolean
- get() = permissionInfo.protection == PermissionInfo.PROTECTION_FLAG_APPOP
-
- inline val isRemoved: Boolean
- get() = permissionInfo.flags.hasBits(PermissionInfo.FLAG_REMOVED)
-
- inline val isSoftRestricted: Boolean
- get() = permissionInfo.flags.hasBits(PermissionInfo.FLAG_SOFT_RESTRICTED)
-
- inline val isHardRestricted: Boolean
- get() = permissionInfo.flags.hasBits(PermissionInfo.FLAG_HARD_RESTRICTED)
-
inline val isSignature: Boolean
get() = permissionInfo.protection == PermissionInfo.PROTECTION_SIGNATURE
- inline val isInternal: Boolean
- get() = permissionInfo.protection == PermissionInfo.PROTECTION_INTERNAL
+ inline val isAppOp: Boolean
+ get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_APPOP)
+
+ inline val isAppPredictor: Boolean
+ get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR)
+
+ inline val isCompanion: Boolean
+ get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_COMPANION)
+
+ inline val isConfigurator: Boolean
+ get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_CONFIGURATOR)
inline val isDevelopment: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_DEVELOPMENT)
+ inline val isIncidentReportApprover: Boolean
+ get() = permissionInfo.protectionFlags
+ .hasBits(PermissionInfo.PROTECTION_FLAG_INCIDENT_REPORT_APPROVER)
+
inline val isInstaller: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_INSTALLER)
+ inline val isInstant: Boolean
+ get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_INSTANT)
+
+ inline val isKnownSigner: Boolean
+ get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER)
+
inline val isOem: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_OEM)
@@ -79,55 +98,54 @@
inline val isPrivileged: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_PRIVILEGED)
+ inline val isRecents: Boolean
+ get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RECENTS)
+
+ inline val isRetailDemo: Boolean
+ get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RETAIL_DEMO)
+
+ inline val isRole: Boolean
+ get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_ROLE)
+
+ inline val isRuntimeOnly: Boolean
+ get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY)
+
inline val isSetup: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_SETUP)
- inline val isVerifier: Boolean
- get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_VERIFIER)
-
- inline val isVendorPrivileged: Boolean
- get() = permissionInfo.protectionFlags
- .hasBits(PROTECTION_FLAG_VENDOR_PRIVILEGED)
-
inline val isSystemTextClassifier: Boolean
get() = permissionInfo.protectionFlags
.hasBits(PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER)
- inline val isConfigurator: Boolean
- get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_CONFIGURATOR)
-
- inline val isIncidentReportApprover: Boolean
+ inline val isVendorPrivileged: Boolean
get() = permissionInfo.protectionFlags
- .hasBits(PermissionInfo.PROTECTION_FLAG_INCIDENT_REPORT_APPROVER)
+ .hasBits(PermissionInfo.PROTECTION_FLAG_VENDOR_PRIVILEGED)
- inline val isAppPredictor: Boolean
- get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR)
+ inline val isVerifier: Boolean
+ get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_VERIFIER)
- inline val isCompanion: Boolean
- get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_COMPANION)
+ inline val isHardRestricted: Boolean
+ get() = permissionInfo.flags.hasBits(PermissionInfo.FLAG_HARD_RESTRICTED)
- inline val isRetailDemo: Boolean
- get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RETAIL_DEMO)
+ inline val isRemoved: Boolean
+ get() = permissionInfo.flags.hasBits(PermissionInfo.FLAG_REMOVED)
- inline val isRecents: Boolean
- get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RECENTS)
-
- inline val isRole: Boolean
- get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_ROLE)
-
- inline val isKnownSigner: Boolean
- get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER)
-
- inline val hasGids: Boolean
- get() = throw NotImplementedError()
-
- inline val protectionLevel: Int
- @Suppress("DEPRECATION")
- get() = permissionInfo.protectionLevel
+ inline val isSoftRestricted: Boolean
+ get() = permissionInfo.flags.hasBits(PermissionInfo.FLAG_SOFT_RESTRICTED)
inline val knownCerts: Set<String>
get() = permissionInfo.knownCerts
+ inline val hasGids: Boolean
+ get() = gids.isNotEmpty()
+
+ fun getGidsForUser(userId: Int): IntArray =
+ if (areGidsPerUser) {
+ IntArray(gids.size) { i -> UserHandle.getUid(userId, gids[i]) }
+ } else {
+ gids.clone()
+ }
+
companion object {
// The permission is defined in an application manifest.
const val TYPE_MANIFEST = 0
@@ -135,8 +153,5 @@
const val TYPE_CONFIG = 1
// The permission is defined dynamically.
const val TYPE_DYNAMIC = 2
-
- // TODO: PermissionInfo.PROTECTION_FLAG_VENDOR_PRIVILEGED is a testApi
- const val PROTECTION_FLAG_VENDOR_PRIVILEGED = 0x8000
}
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
index a4708c8..48658ff 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
@@ -317,172 +317,164 @@
*/
const val MASK_EXEMPT = INSTALLER_EXEMPT or SYSTEM_EXEMPT or UPGRADE_EXEMPT
- /**
- * Mask for all API permission flags about permission restriction.
- */
- private const val API_MASK_RESTRICTION =
- PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT or
- PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT or
- PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT or
- PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION
-
- /**
- * Mask for all permission flags about permission restriction.
- */
- private const val MASK_RESTRICTION = INSTALLER_EXEMPT or SYSTEM_EXEMPT or
- UPGRADE_EXEMPT or RESTRICTION_REVOKED or SOFT_RESTRICTED
-
- fun isPermissionGranted(policyFlags: Int): Boolean {
- if (policyFlags.hasBits(INSTALL_GRANTED)) {
+ fun isPermissionGranted(flags: Int): Boolean {
+ if (flags.hasBits(INSTALL_GRANTED)) {
return true
}
- if (policyFlags.hasBits(INSTALL_REVOKED)) {
+ if (flags.hasBits(INSTALL_REVOKED)) {
return false
}
- if (policyFlags.hasBits(PROTECTION_GRANTED)) {
+ if (flags.hasBits(PROTECTION_GRANTED)) {
return true
}
- if (policyFlags.hasBits(LEGACY_GRANTED) || policyFlags.hasBits(IMPLICIT_GRANTED)) {
+ if (flags.hasBits(LEGACY_GRANTED) || flags.hasBits(IMPLICIT_GRANTED)) {
return true
}
- if (policyFlags.hasBits(RESTRICTION_REVOKED)) {
+ if (flags.hasBits(RESTRICTION_REVOKED)) {
return false
}
- return policyFlags.hasBits(RUNTIME_GRANTED)
+ return flags.hasBits(RUNTIME_GRANTED)
}
- fun isAppOpGranted(policyFlags: Int): Boolean =
- isPermissionGranted(policyFlags) && !policyFlags.hasBits(APP_OP_REVOKED)
+ fun isAppOpGranted(flags: Int): Boolean =
+ isPermissionGranted(flags) && !flags.hasBits(APP_OP_REVOKED)
- fun isReviewRequired(policyFlags: Int): Boolean =
- policyFlags.hasBits(LEGACY_GRANTED) && policyFlags.hasBits(IMPLICIT)
-
- fun toApiFlags(policyFlags: Int): Int {
+ fun toApiFlags(flags: Int): Int {
var apiFlags = 0
- if (policyFlags.hasBits(USER_SET)) {
+ if (flags.hasBits(USER_SET)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_USER_SET
}
- if (policyFlags.hasBits(USER_FIXED)) {
+ if (flags.hasBits(USER_FIXED)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_USER_FIXED
}
- if (policyFlags.hasBits(POLICY_FIXED)) {
+ if (flags.hasBits(POLICY_FIXED)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_POLICY_FIXED
}
- if (policyFlags.hasBits(SYSTEM_FIXED)) {
+ if (flags.hasBits(SYSTEM_FIXED)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
}
- if (policyFlags.hasBits(PREGRANT)) {
+ if (flags.hasBits(PREGRANT)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT
}
- if (policyFlags.hasBits(IMPLICIT)) {
- apiFlags = apiFlags or if (policyFlags.hasBits(LEGACY_GRANTED)) {
+ if (flags.hasBits(IMPLICIT)) {
+ apiFlags = apiFlags or if (flags.hasBits(LEGACY_GRANTED)) {
PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
} else {
PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED
}
}
- if (policyFlags.hasBits(USER_SENSITIVE_WHEN_GRANTED)) {
+ if (flags.hasBits(USER_SENSITIVE_WHEN_GRANTED)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED
}
- if (policyFlags.hasBits(USER_SENSITIVE_WHEN_REVOKED)) {
+ if (flags.hasBits(USER_SENSITIVE_WHEN_REVOKED)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED
}
- if (policyFlags.hasBits(INSTALLER_EXEMPT)) {
+ if (flags.hasBits(INSTALLER_EXEMPT)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT
}
- if (policyFlags.hasBits(SYSTEM_EXEMPT)) {
+ if (flags.hasBits(SYSTEM_EXEMPT)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT
}
- if (policyFlags.hasBits(UPGRADE_EXEMPT)) {
+ if (flags.hasBits(UPGRADE_EXEMPT)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT
}
- if (policyFlags.hasBits(RESTRICTION_REVOKED) || policyFlags.hasBits(SOFT_RESTRICTED)) {
+ if (flags.hasBits(RESTRICTION_REVOKED) || flags.hasBits(SOFT_RESTRICTED)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION
}
- if (policyFlags.hasBits(ROLE)) {
+ if (flags.hasBits(ROLE)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE
}
- if (policyFlags.hasBits(APP_OP_REVOKED)) {
+ if (flags.hasBits(APP_OP_REVOKED)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_REVOKED_COMPAT
}
- if (policyFlags.hasBits(ONE_TIME)) {
+ if (flags.hasBits(ONE_TIME)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_ONE_TIME
}
- if (policyFlags.hasBits(HIBERNATION)) {
+ if (flags.hasBits(HIBERNATION)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_AUTO_REVOKED
}
- if (policyFlags.hasBits(USER_SELECTED)) {
+ if (flags.hasBits(USER_SELECTED)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY
}
return apiFlags
}
- fun setRuntimePermissionGranted(policyFlags: Int, isGranted: Boolean): Int =
- if (isGranted) policyFlags or RUNTIME_GRANTED else policyFlags andInv RUNTIME_GRANTED
+ fun updateRuntimePermissionGranted(flags: Int, isGranted: Boolean): Int =
+ if (isGranted) flags or RUNTIME_GRANTED else flags andInv RUNTIME_GRANTED
- fun updatePolicyFlags(policyFlags: Int, apiFlagMask: Int, apiFlagValues: Int): Int {
- check(!apiFlagMask.hasAnyBit(API_MASK_RESTRICTION)) {
- "Permission flags about permission restriction can only be directly mutated by the" +
- " policy"
- }
- val oldApiFlags = toApiFlags(policyFlags)
+ fun updateFlags(permission: Permission, flags: Int, apiFlagMask: Int, apiFlagValues: Int): Int {
+ val oldApiFlags = toApiFlags(flags)
val newApiFlags = (oldApiFlags andInv apiFlagMask) or (apiFlagValues and apiFlagMask)
- return toPolicyFlags(policyFlags, newApiFlags)
+ return fromApiFlags(newApiFlags, permission, flags)
}
- private fun toPolicyFlags(oldPolicyFlags: Int, apiFlags: Int): Int {
- var policyFlags = 0
- policyFlags = policyFlags or (oldPolicyFlags and INSTALL_GRANTED)
- policyFlags = policyFlags or (oldPolicyFlags and INSTALL_REVOKED)
- policyFlags = policyFlags or (oldPolicyFlags and PROTECTION_GRANTED)
+ private fun fromApiFlags(apiFlags: Int, permission: Permission, oldFlags: Int): Int {
+ var flags = 0
+ flags = flags or (oldFlags and INSTALL_GRANTED)
+ flags = flags or (oldFlags and INSTALL_REVOKED)
+ flags = flags or (oldFlags and PROTECTION_GRANTED)
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE)) {
- policyFlags = policyFlags or ROLE
+ flags = flags or ROLE
}
- policyFlags = policyFlags or (oldPolicyFlags and RUNTIME_GRANTED)
+ flags = flags or (oldFlags and RUNTIME_GRANTED)
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_USER_SET)) {
- policyFlags = policyFlags or USER_SET
+ flags = flags or USER_SET
}
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_USER_FIXED)) {
- policyFlags = policyFlags or USER_FIXED
+ flags = flags or USER_FIXED
}
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_POLICY_FIXED)) {
- policyFlags = policyFlags or POLICY_FIXED
+ flags = flags or POLICY_FIXED
}
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_SYSTEM_FIXED)) {
- policyFlags = policyFlags or SYSTEM_FIXED
+ flags = flags or SYSTEM_FIXED
}
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT)) {
- policyFlags = policyFlags or PREGRANT
+ flags = flags or PREGRANT
}
- policyFlags = policyFlags or (oldPolicyFlags and LEGACY_GRANTED)
- policyFlags = policyFlags or (oldPolicyFlags and IMPLICIT_GRANTED)
+ flags = flags or (oldFlags and LEGACY_GRANTED)
+ flags = flags or (oldFlags and IMPLICIT_GRANTED)
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) ||
apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED)) {
- policyFlags = policyFlags or IMPLICIT
+ flags = flags or IMPLICIT
}
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED)) {
- policyFlags = policyFlags or USER_SENSITIVE_WHEN_GRANTED
+ flags = flags or USER_SENSITIVE_WHEN_GRANTED
}
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED)) {
- policyFlags = policyFlags or USER_SENSITIVE_WHEN_REVOKED
+ flags = flags or USER_SENSITIVE_WHEN_REVOKED
}
- // FLAG_PERMISSION_APPLY_RESTRICTION can be either REVOKED_BY_RESTRICTION when the
- // permission is hard restricted, or SOFT_RESTRICTED when the permission is soft restricted.
- // However since we should never allow indirect mutation of restriction state, we can just
- // get the flags about restriction from the old policy flags.
- policyFlags = policyFlags or (oldPolicyFlags and MASK_RESTRICTION)
+ if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT)) {
+ flags = flags or INSTALLER_EXEMPT
+ }
+ if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT)) {
+ flags = flags or SYSTEM_EXEMPT
+ }
+ if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT)) {
+ flags = flags or UPGRADE_EXEMPT
+ }
+ // We ignore whether FLAG_PERMISSION_APPLY_RESTRICTION is set here because previously
+ // platform may be relying on the old restorePermissionState() to get it correct later.
+ if (!flags.hasAnyBit(MASK_EXEMPT)) {
+ if (permission.isHardRestricted) {
+ flags = flags or RESTRICTION_REVOKED
+ }
+ if (permission.isSoftRestricted) {
+ flags = flags or SOFT_RESTRICTED
+ }
+ }
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVOKED_COMPAT)) {
- policyFlags = policyFlags or APP_OP_REVOKED
+ flags = flags or APP_OP_REVOKED
}
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_ONE_TIME)) {
- policyFlags = policyFlags or ONE_TIME
+ flags = flags or ONE_TIME
}
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_AUTO_REVOKED)) {
- policyFlags = policyFlags or HIBERNATION
+ flags = flags or HIBERNATION
}
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY)) {
- policyFlags = policyFlags or USER_SELECTED
+ flags = flags or USER_SELECTED
}
- return policyFlags
+ return flags
}
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index ff7483f..7117501 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -26,6 +26,7 @@
import android.content.pm.PermissionGroupInfo
import android.content.pm.PermissionInfo
import android.content.pm.permission.SplitPermissionInfoParcelable
+import android.metrics.LogMaker
import android.os.Binder
import android.os.Build
import android.os.Handler
@@ -37,30 +38,46 @@
import android.os.RemoteException
import android.os.ServiceManager
import android.os.UserHandle
+import android.os.UserManager
import android.permission.IOnPermissionsChangeListener
import android.permission.PermissionManager
import android.provider.Settings
+import android.util.DebugUtils
+import android.util.IntArray as GrowingIntArray
import android.util.Log
import com.android.internal.compat.IPlatformCompat
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.nano.MetricsProto
+import com.android.internal.util.Preconditions
import com.android.server.FgThread
import com.android.server.LocalManagerRegistry
import com.android.server.LocalServices
import com.android.server.ServiceThread
import com.android.server.SystemConfig
import com.android.server.permission.access.AccessCheckingService
+import com.android.server.permission.access.GetStateScope
+import com.android.server.permission.access.MutateStateScope
import com.android.server.permission.access.PermissionUri
import com.android.server.permission.access.UidUri
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.util.andInv
import com.android.server.permission.access.util.hasAnyBit
import com.android.server.permission.access.util.hasBits
+import com.android.server.permission.access.util.withClearedCallingIdentity
+import com.android.server.pm.KnownPackages
import com.android.server.pm.PackageManagerLocal
+import com.android.server.pm.UserManagerInternal
import com.android.server.pm.UserManagerService
+import com.android.server.pm.parsing.pkg.AndroidPackageUtils
import com.android.server.pm.permission.LegacyPermission
import com.android.server.pm.permission.LegacyPermissionSettings
import com.android.server.pm.permission.LegacyPermissionState
import com.android.server.pm.permission.PermissionManagerServiceInterface
import com.android.server.pm.permission.PermissionManagerServiceInternal
import com.android.server.pm.pkg.AndroidPackage
+import com.android.server.pm.pkg.PackageState
+import com.android.server.policy.SoftRestrictedPermissionPolicy
+import libcore.util.EmptyArray
import java.io.FileDescriptor
import java.io.PrintWriter
@@ -74,39 +91,43 @@
service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as UidPermissionPolicy
private val context = service.context
+ private lateinit var metricsLogger: MetricsLogger
private lateinit var packageManagerInternal: PackageManagerInternal
private lateinit var packageManagerLocal: PackageManagerLocal
private lateinit var platformCompat: IPlatformCompat
+ private lateinit var systemConfig: SystemConfig
+ private lateinit var userManagerInternal: UserManagerInternal
private lateinit var userManagerService: UserManagerService
- private val mountedStorageVolumes = IndexedSet<String?>()
-
private lateinit var handlerThread: HandlerThread
private lateinit var handler: Handler
-
private lateinit var onPermissionsChangeListeners: OnPermissionsChangeListeners
private lateinit var onPermissionFlagsChangedListener: OnPermissionFlagsChangedListener
+ private val mountedStorageVolumes = IndexedSet<String?>()
+
fun initialize() {
+ metricsLogger = MetricsLogger()
packageManagerInternal = LocalServices.getService(PackageManagerInternal::class.java)
packageManagerLocal =
LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal::class.java)
platformCompat = IPlatformCompat.Stub.asInterface(
ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)
)
+ systemConfig = SystemConfig.getInstance()
+ userManagerInternal = LocalServices.getService(UserManagerInternal::class.java)
userManagerService = UserManagerService.getInstance()
handlerThread = ServiceThread(LOG_TAG, Process.THREAD_PRIORITY_BACKGROUND, true)
handler = Handler(handlerThread.looper)
-
onPermissionsChangeListeners = OnPermissionsChangeListeners(FgThread.get().looper)
onPermissionFlagsChangedListener = OnPermissionFlagsChangedListener()
policy.addOnPermissionFlagsChangedListener(onPermissionFlagsChangedListener)
}
override fun getAllPermissionGroups(flags: Int): List<PermissionGroupInfo> {
- val callingUid = Binder.getCallingUid()
packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
+ val callingUid = Binder.getCallingUid()
if (snapshot.isUidInstantApp(callingUid)) {
return emptyList()
}
@@ -115,7 +136,7 @@
with(policy) { getPermissionGroups() }
}
- return permissionGroups.mapNotNullIndexed { _, permissionGroup ->
+ return permissionGroups.mapNotNullIndexed { _, _, permissionGroup ->
if (snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) {
permissionGroup.generatePermissionGroupInfo(flags)
} else {
@@ -184,12 +205,10 @@
return null
}
- val callingAppId = UserHandle.getAppId(callingUid)
- val opPackage = snapshot.packageStates[opPackageName]?.androidPackage
+ val opPackage = snapshot.getPackageState(opPackageName)?.androidPackage
targetSdkVersion = when {
// System sees all flags.
- callingAppId == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID ||
- callingAppId == Process.SHELL_UID -> Build.VERSION_CODES.CUR_DEVELOPMENT
+ isRootOrSystemOrShell(callingUid) -> Build.VERSION_CODES.CUR_DEVELOPMENT
opPackage != null -> opPackage.targetSdkVersion
else -> Build.VERSION_CODES.CUR_DEVELOPMENT
}
@@ -223,29 +242,30 @@
permissionGroupName: String?,
flags: Int
): List<PermissionInfo>? {
- val callingUid = Binder.getCallingUid()
packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
+ val callingUid = Binder.getCallingUid()
if (snapshot.isUidInstantApp(callingUid)) {
return null
}
- if (permissionGroupName != null) {
- val permissionGroup = service.getState {
- with(policy) { getPermissionGroups()[permissionGroupName] }
- } ?: return null
+ val permissions: IndexedMap<String, Permission>
+ service.getState {
+ if (permissionGroupName != null) {
+ val permissionGroup =
+ with(policy) { getPermissionGroups()[permissionGroupName] } ?: return null
- if (!snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) {
- return null
+ if (!snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) {
+ return null
+ }
}
+
+ permissions = with(policy) { getPermissions() }
}
- val permissions = service.getState {
- with(policy) { getPermissions() }
- }
-
- return permissions.mapNotNullIndexed { _, permission ->
+ return permissions.mapNotNullIndexed { _, _, permission ->
if (permission.groupName == permissionGroupName &&
- snapshot.isPackageVisibleToUid(permission.packageName, callingUid)) {
+ snapshot.isPackageVisibleToUid(permission.packageName, callingUid)
+ ) {
permission.generatePermissionInfo(flags)
} else {
null
@@ -263,7 +283,10 @@
}
override fun getPermissionGids(permissionName: String, userId: Int): IntArray {
- TODO("Not yet implemented")
+ val permission = service.getState {
+ with(policy) { getPermissions()[permissionName] }
+ } ?: return EmptyArray.INT
+ return permission.getGidsForUser(userId)
}
override fun addPermission(permissionInfo: PermissionInfo, async: Boolean): Boolean {
@@ -274,20 +297,188 @@
TODO("Not yet implemented")
}
- override fun checkPermission(packageName: String, permissionName: String, userId: Int): Int {
- TODO("Not yet implemented")
+ override fun checkUidPermission(uid: Int, permissionName: String): Int {
+ val userId = UserHandle.getUserId(uid)
+ if (!userManagerInternal.exists(userId)) {
+ return PackageManager.PERMISSION_DENIED
+ }
+
+ // PackageManagerInternal.getPackage(int) already checks package visibility and enforces
+ // that instant apps can't see shared UIDs. Note that on the contrary,
+ // Note that PackageManagerInternal.getPackage(String) doesn't perform any checks.
+ val androidPackage = packageManagerInternal.getPackage(uid)
+ if (androidPackage != null) {
+ // Note that PackageManagerInternal.getPackageStateInternal() is not filtered.
+ val packageState =
+ packageManagerInternal.getPackageStateInternal(androidPackage.packageName)
+ if (packageState == null) {
+ Log.e(
+ LOG_TAG, "checkUidPermission: PackageState not found for AndroidPackage" +
+ " $androidPackage"
+ )
+ return PackageManager.PERMISSION_DENIED
+ }
+ val isPermissionGranted = service.getState {
+ isPermissionGranted(packageState, userId, permissionName)
+ }
+ return if (isPermissionGranted) {
+ PackageManager.PERMISSION_GRANTED
+ } else {
+ PackageManager.PERMISSION_DENIED
+ }
+ }
+
+ return if (isSystemUidPermissionGranted(uid, permissionName)) {
+ PackageManager.PERMISSION_GRANTED
+ } else {
+ PackageManager.PERMISSION_DENIED
+ }
}
- override fun checkUidPermission(uid: Int, permissionName: String): Int {
- TODO("Not yet implemented")
+ /**
+ * Internal implementation that should only be called by [checkUidPermission].
+ */
+ private fun isSystemUidPermissionGranted(uid: Int, permissionName: String): Boolean {
+ val uidPermissions = systemConfig.systemPermissions[uid] ?: return false
+ if (permissionName in uidPermissions) {
+ return true
+ }
+
+ val fullerPermissionName = FULLER_PERMISSIONS[permissionName]
+ if (fullerPermissionName != null && fullerPermissionName in uidPermissions) {
+ return true
+ }
+
+ return false
+ }
+
+ override fun checkPermission(packageName: String, permissionName: String, userId: Int): Int {
+ if (!userManagerInternal.exists(userId)) {
+ return PackageManager.PERMISSION_DENIED
+ }
+
+ val packageState = packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId)
+ .use { it.getPackageState(packageName) } ?: return PackageManager.PERMISSION_DENIED
+
+ val isPermissionGranted = service.getState {
+ isPermissionGranted(packageState, userId, permissionName)
+ }
+ return if (isPermissionGranted) {
+ PackageManager.PERMISSION_GRANTED
+ } else {
+ PackageManager.PERMISSION_DENIED
+ }
+ }
+
+ /**
+ * Check whether a permission is granted, without any validation on caller.
+ *
+ * This method should always be called for checking whether a permission is granted, instead of
+ * reading permission flags directly from the policy.
+ */
+ private fun GetStateScope.isPermissionGranted(
+ packageState: PackageState,
+ userId: Int,
+ permissionName: String
+ ): Boolean {
+ val appId = packageState.appId
+ // Note that instant apps can't have shared UIDs, so we only need to check the current
+ // package state.
+ val isInstantApp = packageState.getUserStateOrDefault(userId).isInstantApp
+ if (isSinglePermissionGranted(appId, userId, isInstantApp, permissionName)) {
+ return true
+ }
+
+ val fullerPermissionName = FULLER_PERMISSIONS[permissionName]
+ if (fullerPermissionName != null &&
+ isSinglePermissionGranted(appId, userId, isInstantApp, fullerPermissionName)) {
+ return true
+ }
+
+ return false
+ }
+
+ /**
+ * Internal implementation that should only be called by [isPermissionGranted].
+ */
+ private fun GetStateScope.isSinglePermissionGranted(
+ appId: Int,
+ userId: Int,
+ isInstantApp: Boolean,
+ permissionName: String
+ ): Boolean {
+ val flags = with(policy) { getPermissionFlags(appId, userId, permissionName) }
+ if (!PermissionFlags.isPermissionGranted(flags)) {
+ return false
+ }
+
+ if (isInstantApp) {
+ val permission = with(policy) { getPermissions()[permissionName] } ?: return false
+ if (!permission.isInstant) {
+ return false
+ }
+ }
+
+ return true
}
override fun getGrantedPermissions(packageName: String, userId: Int): Set<String> {
- TODO("Not yet implemented")
+ requireNotNull(packageName) { "packageName cannot be null" }
+ Preconditions.checkArgumentNonnegative(userId, "userId")
+
+ val packageState = packageManagerLocal.withUnfilteredSnapshot()
+ .use { it.getPackageState(packageName) }
+ if (packageState == null) {
+ Log.w(LOG_TAG, "getGrantedPermissions: Unknown package $packageName")
+ return emptySet()
+ }
+
+ service.getState {
+ val permissionFlags = with(policy) { getUidPermissionFlags(packageState.appId, userId) }
+ ?: return emptySet()
+
+ return permissionFlags.mapNotNullIndexedToSet { _, permissionName, _ ->
+ if (isPermissionGranted(packageState, userId, permissionName)) {
+ permissionName
+ } else {
+ null
+ }
+ }
+ }
+ }
+
+ override fun getGidsForUid(uid: Int): IntArray {
+ val appId = UserHandle.getAppId(uid)
+ val userId = UserHandle.getUserId(uid)
+ val globalGids = systemConfig.globalGids
+ service.getState {
+ // Different from the old implementation, which returns an empty array when the
+ // permission state is not found, now we always return at least global GIDs. This is
+ // more consistent with the pre-S-refactor behavior. This is also because we are now
+ // actively trimming the per-UID objects when empty.
+ val permissionFlags = with(policy) { getUidPermissionFlags(appId, userId) }
+ ?: return globalGids.clone()
+
+ val gids = GrowingIntArray.wrap(globalGids)
+ permissionFlags.forEachIndexed { _, permissionName, flags ->
+ if (!PermissionFlags.isPermissionGranted(flags)) {
+ return@forEachIndexed
+ }
+
+ val permission = with(policy) { getPermissions()[permissionName] }
+ ?: return@forEachIndexed
+ val permissionGids = permission.getGidsForUser(userId)
+ if (permissionGids.isEmpty()) {
+ return@forEachIndexed
+ }
+ gids.addAll(permissionGids)
+ }
+ return gids.toArray()
+ }
}
override fun grantRuntimePermission(packageName: String, permissionName: String, userId: Int) {
- TODO("Not yet implemented")
+ setRuntimePermissionGranted(packageName, userId, permissionName, isGranted = true)
}
override fun revokeRuntimePermission(
@@ -296,31 +487,301 @@
userId: Int,
reason: String?
) {
- TODO("Not yet implemented")
+ setRuntimePermissionGranted(
+ packageName, userId, permissionName, isGranted = false, revokeReason = reason
+ )
}
override fun revokePostNotificationPermissionWithoutKillForTest(
packageName: String,
userId: Int
) {
- TODO("Not yet implemented")
+ setRuntimePermissionGranted(
+ packageName, userId, Manifest.permission.POST_NOTIFICATIONS, isGranted = false,
+ skipKillUid = true
+ )
}
- override fun addOnPermissionsChangeListener(listener: IOnPermissionsChangeListener) {
- onPermissionsChangeListeners.addListener(listener)
+ /**
+ * Shared internal implementation that should only be called by [grantRuntimePermission],
+ * [revokeRuntimePermission] and [revokePostNotificationPermissionWithoutKillForTest].
+ */
+ private fun setRuntimePermissionGranted(
+ packageName: String,
+ userId: Int,
+ permissionName: String,
+ isGranted: Boolean,
+ skipKillUid: Boolean = false,
+ revokeReason: String? = null
+ ) {
+ val methodName = if (isGranted) "grantRuntimePermission" else "revokeRuntimePermission"
+ val callingUid = Binder.getCallingUid()
+ val isDebugEnabled = if (isGranted) {
+ PermissionManager.DEBUG_TRACE_GRANTS
+ } else {
+ PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES
+ }
+ if (isDebugEnabled &&
+ PermissionManager.shouldTraceGrant(packageName, permissionName, userId)) {
+ val callingUidName = packageManagerInternal.getNameForUid(callingUid)
+ Log.i(
+ LOG_TAG, "$methodName(packageName = $packageName," +
+ " permissionName = $permissionName" +
+ (if (isGranted) "" else "skipKillUid = $skipKillUid, reason = $revokeReason") +
+ ", userId = $userId," + " callingUid = $callingUidName ($callingUid))",
+ RuntimeException()
+ )
+ }
+
+ enforceCallingOrSelfCrossUserPermission(
+ userId, enforceFullPermission = true, enforceShellRestriction = true, methodName
+ )
+ val enforcedPermissionName = if (isGranted) {
+ Manifest.permission.GRANT_RUNTIME_PERMISSIONS
+ } else {
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
+ }
+ context.enforceCallingOrSelfPermission(enforcedPermissionName, methodName)
+
+ if (!userManagerInternal.exists(userId)) {
+ Log.w(LOG_TAG, "$methodName: Unknown user $userId")
+ return
+ }
+
+ val packageState: PackageState?
+ val permissionControllerPackageName = packageManagerInternal.getKnownPackageNames(
+ KnownPackages.PACKAGE_PERMISSION_CONTROLLER, UserHandle.USER_SYSTEM
+ ).first()
+ val permissionControllerPackageState: PackageState?
+ packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
+ packageState = snapshot.filtered(callingUid, userId)
+ .use { it.getPackageState(packageName) }
+ permissionControllerPackageState =
+ snapshot.getPackageState(permissionControllerPackageName)
+ }
+ val androidPackage = packageState?.androidPackage
+ // Different from the old implementation, which returns when package doesn't exist but
+ // throws when package exists but isn't visible, we now return in both cases to avoid
+ // leaking the package existence.
+ if (androidPackage == null) {
+ Log.w(LOG_TAG, "$methodName: Unknown package $packageName")
+ return
+ }
+
+ val canManageRolePermission = isRootOrSystem(callingUid) ||
+ UserHandle.getAppId(callingUid) == permissionControllerPackageState!!.appId
+ val overridePolicyFixed = context.checkCallingOrSelfPermission(
+ Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY
+ ) == PackageManager.PERMISSION_GRANTED
+
+ service.mutateState {
+ with(onPermissionFlagsChangedListener) {
+ if (skipKillUid) {
+ skipKillRuntimePermissionRevokedUids()
+ }
+ if (revokeReason != null) {
+ addKillRuntimePermissionRevokedUidsReason(revokeReason)
+ }
+ }
+
+ setRuntimePermissionGranted(
+ packageState, userId, permissionName, isGranted, canManageRolePermission,
+ overridePolicyFixed, reportError = true, methodName
+ )
+ }
}
- override fun removeOnPermissionsChangeListener(listener: IOnPermissionsChangeListener) {
- onPermissionsChangeListeners.removeListener(listener)
+ private fun grantRequestedRuntimePermissions(
+ packageState: PackageState,
+ userId: Int,
+ permissionNames: List<String>
+ ) {
+ service.mutateState {
+ permissionNames.forEachIndexed { _, permissionName ->
+ setRuntimePermissionGranted(
+ packageState, userId, permissionName, isGranted = true,
+ canManageRolePermission = false, overridePolicyFixed = false,
+ reportError = false, "grantRequestedRuntimePermissions"
+ )
+ }
+ }
+ }
+
+ /**
+ * Set whether a runtime permission is granted, without any validation on caller.
+ */
+ private fun MutateStateScope.setRuntimePermissionGranted(
+ packageState: PackageState,
+ userId: Int,
+ permissionName: String,
+ isGranted: Boolean,
+ canManageRolePermission: Boolean,
+ overridePolicyFixed: Boolean,
+ reportError: Boolean,
+ methodName: String
+ ) {
+ val permission = with(policy) { getPermissions()[permissionName] }
+ if (permission == null) {
+ if (reportError) {
+ throw IllegalArgumentException("Unknown permission $permissionName")
+ }
+ return
+ }
+
+ val androidPackage = packageState.androidPackage!!
+ val packageName = packageState.packageName
+ when {
+ permission.isDevelopment -> {}
+ permission.isRole -> {
+ if (!canManageRolePermission) {
+ if (reportError) {
+ throw SecurityException("Permission $permissionName is managed by role")
+ }
+ return
+ }
+ }
+ permission.isRuntime -> {
+ if (androidPackage.targetSdkVersion < Build.VERSION_CODES.M) {
+ // If a permission review is required for legacy apps we represent
+ // their permissions as always granted
+ return
+ }
+ if (isGranted && packageState.getUserStateOrDefault(userId).isInstantApp &&
+ !permission.isInstant) {
+ if (reportError) {
+ throw SecurityException(
+ "Cannot grant non-instant permission $permissionName to package" +
+ " $packageName"
+ )
+ }
+ return
+ }
+ }
+ else -> {
+ if (reportError) {
+ throw SecurityException(
+ "Permission $permissionName requested by package $packageName is not a" +
+ " changeable permission type"
+ )
+ }
+ return
+ }
+ }
+
+ val appId = packageState.appId
+ val oldFlags = with(policy) { getPermissionFlags(appId, userId, permissionName) }
+
+ if (permissionName !in androidPackage.requestedPermissions && oldFlags == 0) {
+ if (reportError) {
+ throw SecurityException(
+ "Permission $permissionName isn't requested by package $packageName"
+ )
+ }
+ return
+ }
+
+ if (oldFlags.hasBits(PermissionFlags.SYSTEM_FIXED)) {
+ if (reportError) {
+ Log.e(
+ LOG_TAG, "$methodName: Cannot change system fixed permission $permissionName" +
+ " for package $packageName"
+ )
+ }
+ return
+ }
+
+ if (oldFlags.hasBits(PermissionFlags.POLICY_FIXED) && !overridePolicyFixed) {
+ if (reportError) {
+ Log.e(
+ LOG_TAG, "$methodName: Cannot change policy fixed permission $permissionName" +
+ " for package $packageName"
+ )
+ }
+ return
+ }
+
+ if (isGranted && oldFlags.hasBits(PermissionFlags.RESTRICTION_REVOKED)) {
+ if (reportError) {
+ Log.e(
+ LOG_TAG, "$methodName: Cannot grant hard-restricted non-exempt permission" +
+ " $permissionName to package $packageName"
+ )
+ }
+ return
+ }
+
+ if (isGranted && oldFlags.hasBits(PermissionFlags.SOFT_RESTRICTED)) {
+ // TODO: Refactor SoftRestrictedPermissionPolicy.
+ val softRestrictedPermissionPolicy = SoftRestrictedPermissionPolicy.forPermission(
+ context, AndroidPackageUtils.generateAppInfoWithoutState(androidPackage),
+ androidPackage, UserHandle.of(userId), permissionName
+ )
+ if (!softRestrictedPermissionPolicy.mayGrantPermission()) {
+ if (reportError) {
+ Log.e(
+ LOG_TAG, "$methodName: Cannot grant soft-restricted non-exempt permission" +
+ " $permissionName to package $packageName"
+ )
+ }
+ return
+ }
+ }
+
+ val newFlags = PermissionFlags.updateRuntimePermissionGranted(oldFlags, isGranted)
+ if (oldFlags == newFlags) {
+ return
+ }
+
+ with(policy) { setPermissionFlags(appId, userId, permissionName, newFlags) }
+
+ if (permission.isRuntime) {
+ val action = if (isGranted) {
+ MetricsProto.MetricsEvent.ACTION_PERMISSION_GRANTED
+ } else {
+ MetricsProto.MetricsEvent.ACTION_PERMISSION_REVOKED
+ }
+ val log = LogMaker(action).apply {
+ setPackageName(packageName)
+ addTaggedData(MetricsProto.MetricsEvent.FIELD_PERMISSION, permissionName)
+ }
+ metricsLogger.write(log)
+ }
}
override fun getPermissionFlags(packageName: String, permissionName: String, userId: Int): Int {
- // TODO: Implement permission checks.
- val appId = 0
- val flags = service.getState {
- with(policy) { getPermissionFlags(appId, userId, permissionName) }
+ enforceCallingOrSelfCrossUserPermission(
+ userId, enforceFullPermission = true, enforceShellRestriction = false,
+ "getPermissionFlags"
+ )
+ enforceCallingOrSelfAnyPermission(
+ "getPermissionFlags", Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+ Manifest.permission.GET_RUNTIME_PERMISSIONS
+ )
+
+ if (!userManagerInternal.exists(userId)) {
+ Log.w(LOG_TAG, "getPermissionFlags: Unknown user $userId")
+ return 0
}
- return PermissionFlags.toApiFlags(flags)
+
+ val packageState = packageManagerLocal.withFilteredSnapshot()
+ .use { it.getPackageState(packageName) }
+ if (packageState == null) {
+ Log.w(LOG_TAG, "getPermissionFlags: Unknown package $packageName")
+ return 0
+ }
+
+ service.getState {
+ val permission = with(policy) { getPermissions()[permissionName] }
+ if (permission == null) {
+ Log.w(LOG_TAG, "getPermissionFlags: Unknown permission $permissionName")
+ return 0
+ }
+
+ val flags =
+ with(policy) { getPermissionFlags(packageState.appId, userId, permissionName) }
+ return PermissionFlags.toApiFlags(flags)
+ }
}
override fun isPermissionRevokedByPolicy(
@@ -328,49 +789,43 @@
permissionName: String,
userId: Int
): Boolean {
- if (UserHandle.getCallingUserId() != userId) {
- context.enforceCallingPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL,
- "isPermissionRevokedByPolicy for user $userId"
- )
- }
+ enforceCallingOrSelfCrossUserPermission(
+ userId, enforceFullPermission = true, enforceShellRestriction = false,
+ "isPermissionRevokedByPolicy"
+ )
- if (checkPermission(packageName, permissionName, userId) ==
- PackageManager.PERMISSION_GRANTED) {
+ if (!userManagerInternal.exists(userId)) {
+ Log.w(LOG_TAG, "isPermissionRevokedByPolicy: Unknown user $userId")
return false
}
- val callingUid = Binder.getCallingUid()
- if (packageManagerLocal.withUnfilteredSnapshot()
- .use { !it.isPackageVisibleToUid(packageName, userId, callingUid) }) {
- return false
+ val packageState = packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId)
+ .use { it.getPackageState(packageName) } ?: return false
+
+ service.getState {
+ if (isPermissionGranted(packageState, userId, permissionName)) {
+ return false
+ }
+
+ val flags = with(policy) {
+ getPermissionFlags(packageState.appId, userId, permissionName)
+ }
+ return flags.hasBits(PermissionFlags.POLICY_FIXED)
}
-
- val permissionFlags = getPermissionFlagsUnchecked(packageName,
- permissionName, callingUid, userId)
- return permissionFlags.hasBits(PackageManager.FLAG_PERMISSION_POLICY_FIXED)
- }
-
- private fun getPermissionFlagsUnchecked(
- packageName: String,
- permName: String,
- callingUid: Int,
- userId: Int
- ): Int {
- throw NotImplementedError()
}
override fun isPermissionsReviewRequired(packageName: String, userId: Int): Boolean {
- requireNotNull(packageName) { "packageName" }
+ requireNotNull(packageName) { "packageName cannot be null" }
// TODO(b/173235285): Some caller may pass USER_ALL as userId.
- // Preconditions.checkArgumentNonnegative(userId, "userId");
+ // Preconditions.checkArgumentNonnegative(userId, "userId")
+
val packageState = packageManagerLocal.withUnfilteredSnapshot()
- .use { it.packageStates[packageName] } ?: return false
+ .use { it.getPackageState(packageName) } ?: return false
+
val permissionFlags = service.getState {
with(policy) { getUidPermissionFlags(packageState.appId, userId) }
} ?: return false
- return permissionFlags.anyIndexed { _, _, flags -> PermissionFlags.isReviewRequired(flags)
- }
+ return permissionFlags.anyIndexed { _, _, it -> it.hasBits(REVIEW_REQUIRED_FLAGS) }
}
override fun shouldShowRequestPermissionRationale(
@@ -378,70 +833,54 @@
permissionName: String,
userId: Int
): Boolean {
- val callingUid = Binder.getCallingUid()
- if (UserHandle.getCallingUserId() != userId) {
- context.enforceCallingPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL,
- "canShowRequestPermissionRationale for user $userId"
- )
+ enforceCallingOrSelfCrossUserPermission(
+ userId, enforceFullPermission = true, enforceShellRestriction = false,
+ "shouldShowRequestPermissionRationale"
+ )
+
+ if (!userManagerInternal.exists(userId)) {
+ Log.w(LOG_TAG, "shouldShowRequestPermissionRationale: Unknown user $userId")
+ return false
}
- val appId = packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
- snapshot.packageStates[packageName]?.appId ?: return false
- }
+ val callingUid = Binder.getCallingUid()
+ val packageState = packageManagerLocal.withFilteredSnapshot(callingUid, userId)
+ .use { it.getPackageState(packageName) } ?: return false
+ val appId = packageState.appId
if (UserHandle.getAppId(callingUid) != appId) {
return false
}
- if (checkPermission(packageName, permissionName, userId) ==
- PackageManager.PERMISSION_GRANTED) {
+ val flags: Int
+ service.getState {
+ if (isPermissionGranted(packageState, userId, permissionName)) {
+ return false
+ }
+
+ flags = with(policy) { getPermissionFlags(appId, userId, permissionName) }
+ }
+ if (flags.hasAnyBit(UNREQUESTABLE_MASK)) {
return false
}
- val identity = Binder.clearCallingIdentity()
- val permissionFlags = try {
- getPermissionFlagsInternal(packageName, permissionName, callingUid, userId)
- } finally {
- Binder.restoreCallingIdentity(identity)
- }
-
- val fixedFlags = (PermissionFlags.SYSTEM_FIXED or PermissionFlags.POLICY_FIXED
- or PermissionFlags.USER_FIXED)
-
- if (permissionFlags.hasAnyBit(fixedFlags) ||
- permissionFlags.hasBits(PermissionFlags.RESTRICTION_REVOKED)) {
- return false
- }
-
- val token = Binder.clearCallingIdentity()
- try {
- if (permissionName == Manifest.permission.ACCESS_BACKGROUND_LOCATION &&
- platformCompat.isChangeEnabledByPackageName(
- BACKGROUND_RATIONALE_CHANGE_ID, packageName, userId)
- ) {
+ if (permissionName == Manifest.permission.ACCESS_BACKGROUND_LOCATION) {
+ val isBackgroundRationaleChangeEnabled = Binder::class.withClearedCallingIdentity {
+ try {
+ platformCompat.isChangeEnabledByPackageName(
+ BACKGROUND_RATIONALE_CHANGE_ID, packageName, userId
+ )
+ } catch (e: RemoteException) {
+ Log.e(LOG_TAG, "shouldShowRequestPermissionRationale: Unable to check if" +
+ " compatibility change is enabled", e)
+ false
+ }
+ }
+ if (isBackgroundRationaleChangeEnabled) {
return true
}
- } catch (e: RemoteException) {
- Log.e(LOG_TAG, "Unable to check if compatibility change is enabled.", e)
- } finally {
- Binder.restoreCallingIdentity(token)
}
- return permissionFlags and PackageManager.FLAG_PERMISSION_USER_SET != 0
- }
-
- /**
- * read internal permission flags
- * @return internal permission Flags
- * @see PermissionFlags
- */
- private fun getPermissionFlagsInternal(
- packageName: String,
- permName: String,
- callingUid: Int,
- userId: Int
- ): Int {
- throw NotImplementedError()
+ return flags.hasBits(PermissionFlags.USER_SET)
}
override fun updatePermissionFlags(
@@ -449,14 +888,215 @@
permissionName: String,
flagMask: Int,
flagValues: Int,
- checkAdjustPolicyFlagPermission: Boolean,
+ enforceAdjustPolicyPermission: Boolean,
userId: Int
) {
- TODO("Not yet implemented")
+ val callingUid = Binder.getCallingUid()
+ if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES &&
+ PermissionManager.shouldTraceGrant(packageName, permissionName, userId)) {
+ val flagMaskString = DebugUtils.flagsToString(
+ PackageManager::class.java, "FLAG_PERMISSION_", flagMask.toLong()
+ )
+ val flagValuesString = DebugUtils.flagsToString(
+ PackageManager::class.java, "FLAG_PERMISSION_", flagValues.toLong()
+ )
+ val callingUidName = packageManagerInternal.getNameForUid(callingUid)
+ Log.i(
+ LOG_TAG, "updatePermissionFlags(packageName = $packageName," +
+ " permissionName = $permissionName, flagMask = $flagMaskString," +
+ " flagValues = $flagValuesString, userId = $userId," +
+ " callingUid = $callingUidName ($callingUid))", RuntimeException()
+ )
+ }
+
+ enforceCallingOrSelfCrossUserPermission(
+ userId, enforceFullPermission = true, enforceShellRestriction = true,
+ "updatePermissionFlags"
+ )
+ enforceCallingOrSelfAnyPermission(
+ "updatePermissionFlags", Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
+ )
+
+ // Different from the old implementation, which implicitly didn't allow modifying the
+ // POLICY_FIXED flag if the caller is system or root UID, now we do allow that since system
+ // and root UIDs are supposed to have all permissions including
+ // ADJUST_RUNTIME_PERMISSIONS_POLICY.
+ if (!isRootOrSystem(callingUid)) {
+ if (flagMask.hasBits(PackageManager.FLAG_PERMISSION_POLICY_FIXED)) {
+ if (enforceAdjustPolicyPermission) {
+ context.enforceCallingOrSelfPermission(
+ Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY,
+ "Need ${Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY} to change" +
+ " policy flags"
+ )
+ } else {
+ val targetSdkVersion = packageManagerInternal.getUidTargetSdkVersion(callingUid)
+ require(targetSdkVersion < Build.VERSION_CODES.Q) {
+ "${Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY} needs to be" +
+ " checked for packages targeting ${Build.VERSION_CODES.Q} or later" +
+ " when changing policy flags"
+ }
+ }
+ }
+ }
+
+ if (!userManagerInternal.exists(userId)) {
+ Log.w(LOG_TAG, "updatePermissionFlags: Unknown user $userId")
+ return
+ }
+
+ // Using PackageManagerInternal instead of PackageManagerLocal for now due to need to access
+ // shared user packages.
+ // TODO: We probably shouldn't check the share user packages, since the package name is
+ // explicitly provided and grantRuntimePermission() isn't checking shared user packages
+ // anyway.
+ val packageState = packageManagerInternal.getPackageStateInternal(packageName)
+ val androidPackage = packageState?.androidPackage
+ // Different from the old implementation, which returns when package doesn't exist but
+ // throws when package exists but isn't visible, we now return in both cases to avoid
+ // leaking the package existence.
+ if (androidPackage == null ||
+ packageManagerInternal.filterAppAccess(packageName, callingUid, userId, false)) {
+ Log.w(LOG_TAG, "updatePermissionFlags: Unknown package $packageName")
+ return
+ }
+
+ val isPermissionRequested = if (permissionName in androidPackage.requestedPermissions) {
+ // Fast path, the current package has requested the permission.
+ true
+ } else {
+ // Slow path, go through all shared user packages.
+ val sharedUserPackageNames =
+ packageManagerInternal.getSharedUserPackagesForPackage(packageName, userId)
+ sharedUserPackageNames.any { sharedUserPackageName ->
+ val sharedUserPackage = packageManagerInternal.getPackage(sharedUserPackageName)
+ sharedUserPackage != null &&
+ permissionName in sharedUserPackage.requestedPermissions
+ }
+ }
+
+ val appId = packageState.appId
+ service.mutateState {
+ updatePermissionFlags(
+ appId, userId, permissionName, flagMask, flagValues,
+ reportErrorForUnknownPermission = true, isPermissionRequested,
+ "updatePermissionFlags", packageName
+ )
+ }
}
override fun updatePermissionFlagsForAllApps(flagMask: Int, flagValues: Int, userId: Int) {
- TODO("Not yet implemented")
+ val callingUid = Binder.getCallingUid()
+ if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES) {
+ val flagMaskString = DebugUtils.flagsToString(
+ PackageManager::class.java, "FLAG_PERMISSION_", flagMask.toLong()
+ )
+ val flagValuesString = DebugUtils.flagsToString(
+ PackageManager::class.java, "FLAG_PERMISSION_", flagValues.toLong()
+ )
+ val callingUidName = packageManagerInternal.getNameForUid(callingUid)
+ Log.i(
+ LOG_TAG, "updatePermissionFlagsForAllApps(flagMask = $flagMaskString," +
+ " flagValues = $flagValuesString, userId = $userId," +
+ " callingUid = $callingUidName ($callingUid))", RuntimeException()
+ )
+ }
+
+ enforceCallingOrSelfCrossUserPermission(
+ userId, enforceFullPermission = true, enforceShellRestriction = true,
+ "updatePermissionFlagsForAllApps"
+ )
+ enforceCallingOrSelfAnyPermission(
+ "updatePermissionFlagsForAllApps", Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
+ )
+
+ if (!userManagerInternal.exists(userId)) {
+ Log.w(LOG_TAG, "updatePermissionFlagsForAllApps: Unknown user $userId")
+ return
+ }
+
+ val packageStates = packageManagerLocal.withUnfilteredSnapshot()
+ .use { it.packageStates }
+ service.mutateState {
+ packageStates.forEach { (packageName, packageState) ->
+ val androidPackage = packageState.androidPackage ?: return@forEach
+ androidPackage.requestedPermissions.forEach { permissionName ->
+ // Different from the old implementation, which only sanitized the SYSTEM_FIXED
+ // flag, we now properly sanitize all flags as in updatePermissionFlags().
+ updatePermissionFlags(
+ packageState.appId, userId, permissionName, flagMask, flagValues,
+ reportErrorForUnknownPermission = false, isPermissionRequested = true,
+ "updatePermissionFlagsForAllApps", packageName
+ )
+ }
+ }
+ }
+ }
+
+ /**
+ * Shared internal implementation that should only be called by [updatePermissionFlags] and
+ * [updatePermissionFlagsForAllApps].
+ */
+ private fun MutateStateScope.updatePermissionFlags(
+ appId: Int,
+ userId: Int,
+ permissionName: String,
+ flagMask: Int,
+ flagValues: Int,
+ reportErrorForUnknownPermission: Boolean,
+ isPermissionRequested: Boolean,
+ methodName: String,
+ packageName: String
+ ) {
+ // Different from the old implementation, which only allowed the system UID to modify the
+ // following flags, we now allow the root UID as well since both should have all
+ // permissions.
+ // Only the system can change these flags and nothing else.
+ val callingUid = Binder.getCallingUid()
+ @Suppress("NAME_SHADOWING")
+ var flagMask = flagMask
+ @Suppress("NAME_SHADOWING")
+ var flagValues = flagValues
+ if (!isRootOrSystem(callingUid)) {
+ // Different from the old implementation, which allowed non-system UIDs to remove (but
+ // not add) permission restriction flags, we now consistently ignore them altogether.
+ val ignoredMask = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED or
+ PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT or
+ // REVIEW_REQUIRED can be set on any permission by the shell, or by any app for the
+ // NOTIFICATIONS permissions specifically.
+ if (isShell(callingUid) || permissionName in NOTIFICATIONS_PERMISSIONS) {
+ 0
+ } else {
+ PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+ } or PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT or
+ PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT or
+ PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT or
+ PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION
+ flagMask = flagMask andInv ignoredMask
+ flagValues = flagValues andInv ignoredMask
+ }
+
+ val permission = with(policy) { getPermissions()[permissionName] }
+ if (permission == null) {
+ if (reportErrorForUnknownPermission) {
+ throw IllegalArgumentException("Unknown permission $permissionName")
+ }
+ return
+ }
+
+ val oldFlags = with(policy) { getPermissionFlags(appId, userId, permissionName) }
+ if (!isPermissionRequested && oldFlags == 0) {
+ Log.w(
+ LOG_TAG, "$methodName: Permission $permissionName isn't requested by package" +
+ " $packageName"
+ )
+ return
+ }
+
+ val newFlags = PermissionFlags.updateFlags(permission, oldFlags, flagMask, flagValues)
+ with(policy) { setPermissionFlags(appId, userId, permissionName, newFlags) }
}
override fun addAllowlistedRestrictedPermission(
@@ -493,21 +1133,29 @@
TODO("Not yet implemented")
}
+ override fun addOnPermissionsChangeListener(listener: IOnPermissionsChangeListener) {
+ onPermissionsChangeListeners.addListener(listener)
+ }
+
+ override fun removeOnPermissionsChangeListener(listener: IOnPermissionsChangeListener) {
+ onPermissionsChangeListeners.removeListener(listener)
+ }
+
override fun addOnRuntimePermissionStateChangedListener(
listener: PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener
) {
- TODO("Not yet implemented")
+ // TODO: Should be removed once we remove PermissionPolicyService.
}
override fun removeOnRuntimePermissionStateChangedListener(
listener: PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener
) {
- TODO("Not yet implemented")
+ // TODO: Should be removed once we remove PermissionPolicyService.
}
override fun getSplitPermissions(): List<SplitPermissionInfoParcelable> {
return PermissionManager.splitPermissionInfoListToParcelableList(
- SystemConfig.getInstance().splitPermissions
+ systemConfig.splitPermissions
)
}
@@ -534,10 +1182,6 @@
return appOpPermissionPackageNames
}
- override fun getGidsForUid(uid: Int): IntArray {
- TODO("Not yet implemented")
- }
-
override fun backupRuntimePermissions(userId: Int): ByteArray? {
TODO("Not yet implemented")
}
@@ -633,6 +1277,8 @@
synchronized(mountedStorageVolumes) {
if (androidPackage.volumeUuid !in mountedStorageVolumes) {
// Wait for the storage volume to be mounted and batch the state mutation there.
+ // PackageInstalledParams won't exist when packages are being scanned instead of
+ // being installed by an installer.
return
}
}
@@ -641,8 +1287,15 @@
} else {
intArrayOf(userId)
}
- userIds.forEach { service.onPackageInstalled(androidPackage.packageName, it) }
- // TODO: Handle params.
+ @Suppress("NAME_SHADOWING")
+ userIds.forEach { userId ->
+ service.onPackageInstalled(androidPackage.packageName, userId)
+ // TODO: Remove when this callback receives packageState directly.
+ val packageState =
+ packageManagerInternal.getPackageStateInternal(androidPackage.packageName)!!
+ // TODO: Add allowlisting
+ grantRequestedRuntimePermissions(packageState, userId, params.grantedPermissions)
+ }
}
override fun onPackageUninstalled(
@@ -664,6 +1317,24 @@
}
}
+ /**
+ * Check whether a UID is root or system.
+ */
+ private fun isRootOrSystem(uid: Int) =
+ when (UserHandle.getAppId(uid)) {
+ Process.ROOT_UID, Process.SYSTEM_UID -> true
+ else -> false
+ }
+
+ /**
+ * Check whether a UID is shell.
+ */
+ private fun isShell(uid: Int) = UserHandle.getAppId(uid) == Process.SHELL_UID
+
+ /**
+ * Check whether a UID is root, system or shell.
+ */
+ private fun isRootOrSystemOrShell(uid: Int) = isRootOrSystem(uid) || isShell(uid)
/**
* This method should typically only be used when granting or revoking permissions, since the
@@ -689,21 +1360,33 @@
}
/**
+ * @see PackageManagerLocal.withFilteredSnapshot
+ */
+ private fun PackageManagerLocal.withFilteredSnapshot(
+ callingUid: Int,
+ userId: Int
+ ): PackageManagerLocal.FilteredSnapshot =
+ withFilteredSnapshot(callingUid, UserHandle.of(userId))
+
+ /**
+ * Get the [PackageState] for a package name.
+ *
+ * This is for parity with [PackageManagerLocal.FilteredSnapshot.getPackageState] which is more
+ * efficient than [PackageManagerLocal.FilteredSnapshot.getPackageStates], so that we can always
+ * prefer using `getPackageState()` without worrying about whether the snapshot is filtered.
+ */
+ private fun PackageManagerLocal.UnfilteredSnapshot.getPackageState(
+ packageName: String
+ ): PackageState? = packageStates[packageName]
+
+ /**
* Check whether a UID belongs to an instant app.
*/
- private fun PackageManagerLocal.UnfilteredSnapshot.isUidInstantApp(uid: Int): Boolean {
- if (Process.isIsolatedUid(uid)) {
- // Unfortunately we don't have the API for getting the owner UID of an isolated UID yet,
- // so for now we just keep calling the old API.
- return packageManagerInternal.getInstantAppPackageName(uid) != null
- }
- val appId = UserHandle.getAppId(uid)
- // Instant apps can't have shared UIDs, so we can just take the first package.
- val firstPackageState = packageStates.values.firstOrNull { it.appId == appId }
- ?: return false
- val userId = UserHandle.getUserId(uid)
- return firstPackageState.getUserStateOrDefault(userId).isInstantApp
- }
+ private fun PackageManagerLocal.UnfilteredSnapshot.isUidInstantApp(uid: Int): Boolean =
+ // Unfortunately we don't have the API for getting the owner UID of an isolated UID or the
+ // API for getting the SharedUserApi object for an app ID yet, so for now we just keep
+ // calling the old API.
+ packageManagerInternal.getInstantAppPackageName(uid) != null
/**
* Check whether a package is visible to a UID within the same user as the UID.
@@ -720,9 +1403,100 @@
packageName: String,
userId: Int,
uid: Int
- ): Boolean {
- val user = UserHandle.of(userId)
- return filtered(uid, user).use { it.getPackageState(packageName) != null }
+ ): Boolean = filtered(uid, userId).use { it.getPackageState(packageName) != null }
+
+ /**
+ * @see PackageManagerLocal.UnfilteredSnapshot.filtered
+ */
+ private fun PackageManagerLocal.UnfilteredSnapshot.filtered(
+ callingUid: Int,
+ userId: Int
+ ): PackageManagerLocal.FilteredSnapshot = filtered(callingUid, UserHandle.of(userId))
+
+ /**
+ * If neither you nor the calling process of an IPC you are handling has been granted the
+ * permission for accessing a particular [userId], throw a [SecurityException].
+ *
+ * @see Context.enforceCallingOrSelfPermission
+ * @see UserManager.DISALLOW_DEBUGGING_FEATURES
+ */
+ private fun enforceCallingOrSelfCrossUserPermission(
+ userId: Int,
+ enforceFullPermission: Boolean,
+ enforceShellRestriction: Boolean,
+ message: String?
+ ) {
+ require(userId >= 0) { "userId $userId is invalid" }
+ val callingUid = Binder.getCallingUid()
+ val callingUserId = UserHandle.getUserId(callingUid)
+ if (userId != callingUserId) {
+ val permissionName = if (enforceFullPermission) {
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL
+ } else {
+ Manifest.permission.INTERACT_ACROSS_USERS
+ }
+ if (context.checkCallingOrSelfPermission(permissionName) !=
+ PackageManager.PERMISSION_GRANTED) {
+ val exceptionMessage = buildString {
+ if (message != null) {
+ append(message)
+ append(": ")
+ }
+ append("Neither user ")
+ append(Binder.getCallingUid())
+ append(" nor current process has ")
+ append(permissionName)
+ append(" to access user ")
+ append(userId)
+ }
+ throw SecurityException(exceptionMessage)
+ }
+ }
+ if (enforceShellRestriction && isShell(callingUid)) {
+ val isShellRestricted = userManagerInternal.hasUserRestriction(
+ UserManager.DISALLOW_DEBUGGING_FEATURES, userId
+ )
+ if (isShellRestricted) {
+ val exceptionMessage = buildString {
+ if (message != null) {
+ append(message)
+ append(": ")
+ }
+ append("Shell is disallowed to access user ")
+ append(userId)
+ }
+ throw SecurityException(exceptionMessage)
+ }
+ }
+ }
+
+ /**
+ * If neither you nor the calling process of an IPC you are handling has been granted any of the
+ * permissions, throw a [SecurityException].
+ *
+ * @see Context.enforceCallingOrSelfPermission
+ */
+ private fun enforceCallingOrSelfAnyPermission(
+ message: String?,
+ vararg permissionNames: String
+ ) {
+ val hasAnyPermission = permissionNames.any { permissionName ->
+ context.checkCallingOrSelfPermission(permissionName) ==
+ PackageManager.PERMISSION_GRANTED
+ }
+ if (!hasAnyPermission) {
+ val exceptionMessage = buildString {
+ if (message != null) {
+ append(message)
+ append(": ")
+ }
+ append("Neither user ")
+ append(Binder.getCallingUid())
+ append(" nor current process has any of ")
+ permissionNames.joinTo(this, ", ")
+ }
+ throw SecurityException(exceptionMessage)
+ }
}
/**
@@ -735,6 +1509,17 @@
private val runtimePermissionRevokedUids = IntBooleanMap()
private val gidsChangedUids = IntSet()
+ private var isKillRuntimePermissionRevokedUidsSkipped = false
+ private val killRuntimePermissionRevokedUidsReasons = IndexedSet<String>()
+
+ fun MutateStateScope.skipKillRuntimePermissionRevokedUids() {
+ isKillRuntimePermissionRevokedUidsSkipped = true
+ }
+
+ fun MutateStateScope.addKillRuntimePermissionRevokedUidsReason(reason: String) {
+ killRuntimePermissionRevokedUidsReasons += reason
+ }
+
override fun onPermissionFlagsChanged(
appId: Int,
userId: Int,
@@ -773,14 +1558,22 @@
}
runtimePermissionChangedUids.clear()
- runtimePermissionRevokedUids.forEachIndexed {
- _, uid, areOnlyNotificationsPermissionsRevoked ->
- handler.post {
- if (areOnlyNotificationsPermissionsRevoked &&
- isAppBackupAndRestoreRunning(uid)) {
- return@post
+ if (!isKillRuntimePermissionRevokedUidsSkipped) {
+ val reason = if (killRuntimePermissionRevokedUidsReasons.isNotEmpty()) {
+ killRuntimePermissionRevokedUidsReasons.joinToString(", ")
+ } else {
+ PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED
+ }
+ runtimePermissionRevokedUids.forEachIndexed {
+ _, uid, areOnlyNotificationsPermissionsRevoked ->
+ handler.post {
+ if (areOnlyNotificationsPermissionsRevoked &&
+ isAppBackupAndRestoreRunning(uid)
+ ) {
+ return@post
+ }
+ killUid(uid, reason)
}
- killUid(uid, PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED)
}
}
runtimePermissionRevokedUids.clear()
@@ -789,6 +1582,9 @@
handler.post { killUid(uid, PermissionManager.KILL_APP_REASON_GIDS_CHANGED) }
}
gidsChangedUids.clear()
+
+ isKillRuntimePermissionRevokedUidsSkipped = false
+ killRuntimePermissionRevokedUidsReasons.clear()
}
private fun isAppBackupAndRestoreRunning(uid: Int): Boolean {
@@ -865,8 +1661,21 @@
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
private val BACKGROUND_RATIONALE_CHANGE_ID = 147316723L
+ private val FULLER_PERMISSIONS = IndexedMap<String, String>().apply {
+ this[Manifest.permission.ACCESS_COARSE_LOCATION] =
+ Manifest.permission.ACCESS_FINE_LOCATION
+ this[Manifest.permission.INTERACT_ACROSS_USERS] =
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL
+ }
+
private val NOTIFICATIONS_PERMISSIONS = indexedSetOf(
Manifest.permission.POST_NOTIFICATIONS
)
+
+ private const val REVIEW_REQUIRED_FLAGS = PermissionFlags.LEGACY_GRANTED or
+ PermissionFlags.IMPLICIT
+ private const val UNREQUESTABLE_MASK = PermissionFlags.RESTRICTION_REVOKED or
+ PermissionFlags.SYSTEM_FIXED or PermissionFlags.POLICY_FIXED or
+ PermissionFlags.USER_FIXED
}
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
index 1c36bcc..49759c0 100644
--- a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
@@ -78,6 +78,38 @@
onPermissionFlagsChangedListeners.forEachIndexed { _, it -> it.onStateMutated() }
}
+ override fun MutateStateScope.onInitialized() {
+ newState.systemState.configPermissions.forEach { (permissionName, permissionEntry) ->
+ val permissions = newState.systemState.permissions
+ val oldPermission = permissions[permissionName]
+ val newPermission = if (oldPermission != null) {
+ if (permissionEntry.gids != null) {
+ oldPermission.copy(
+ gids = permissionEntry.gids, areGidsPerUser = permissionEntry.perUser
+ )
+ } else {
+ return@forEach
+ }
+ } else {
+ @Suppress("DEPRECATION")
+ val permissionInfo = PermissionInfo().apply {
+ name = permissionName
+ packageName = PLATFORM_PACKAGE_NAME
+ protectionLevel = PermissionInfo.PROTECTION_SIGNATURE
+ }
+ if (permissionEntry.gids != null) {
+ Permission(
+ permissionInfo, false, Permission.TYPE_CONFIG, 0, permissionEntry.gids,
+ permissionEntry.perUser
+ )
+ } else {
+ Permission(permissionInfo, false, Permission.TYPE_CONFIG, 0)
+ }
+ }
+ permissions[permissionName] = newPermission
+ }
+ }
+
override fun MutateStateScope.onUserAdded(userId: Int) {
newState.systemState.packageStates.forEach { (_, packageState) ->
evaluateAllPermissionStatesForPackageAndUser(packageState, userId, null)
@@ -142,6 +174,7 @@
addPermissionGroups(packageState)
addPermissions(packageState, changedPermissionNames)
// TODO: revokeStoragePermissionsIfScopeExpandedInternal()
+ // TODO: revokeSystemAlertWindowIfUpgradedPast23()
trimPermissions(packageState.packageName, changedPermissionNames)
trimPermissionStates(packageState.appId)
changedPermissionNames.forEachIndexed { _, permissionName ->
@@ -348,6 +381,8 @@
if (packageState != null && androidPackage == null) {
return
}
+ // TODO: STOPSHIP: We may need to retain permission definitions by disabled system packages
+ // to retain their permission state.
val isPermissionTreeRemoved = systemState.permissionTrees.removeAllIndexed {
_, permissionTreeName, permissionTree ->
@@ -417,6 +452,7 @@
val requestedPermissions = IndexedSet<String>()
forEachPackageInAppId(appId) {
requestedPermissions += it.androidPackage!!.requestedPermissions
+ // TODO: STOPSHIP: Retain permissions requested by disabled system packages.
}
newState.userStates.forEachIndexed { _, userId, userState ->
userState.uidPermissionFlags[appId].forEachReversedIndexed { _, permissionName, _ ->
@@ -556,12 +592,21 @@
} else if (permission.isRuntime) {
var newFlags = oldFlags and PermissionFlags.MASK_RUNTIME
if (getAppIdTargetSdkVersion(appId, permissionName) < Build.VERSION_CODES.M) {
- newFlags = newFlags or PermissionFlags.LEGACY_GRANTED
- // Explicitly check against the old state to determine if this permission is new.
- val isNewPermission =
- getOldStatePermissionFlags(appId, userId, permissionName) == 0
- if (isNewPermission) {
- newFlags = newFlags or PermissionFlags.IMPLICIT
+ if (permission.isRuntimeOnly) {
+ // Different from the old implementation, which simply skips a runtime-only
+ // permission, we now only allow holding on to the restriction related flags,
+ // since such flags may only be set one-time in some cases, and disallow all
+ // other flags thus keeping it revoked.
+ newFlags = newFlags and PermissionFlags.MASK_EXEMPT
+ } else {
+ newFlags = newFlags or PermissionFlags.LEGACY_GRANTED
+ // Explicitly check against the old state to determine if this permission is
+ // new.
+ val isNewPermission =
+ getOldStatePermissionFlags(appId, userId, permissionName) == 0
+ if (isNewPermission) {
+ newFlags = newFlags or PermissionFlags.IMPLICIT
+ }
}
} else {
newFlags = newFlags andInv PermissionFlags.LEGACY_GRANTED
@@ -743,8 +788,7 @@
if (!androidPackage.isPrivileged) {
return true
}
- if (permission.packageName !in
- newState.systemState.privilegedPermissionAllowlistSourcePackageNames) {
+ if (permission.packageName !in newState.systemState.privilegedPermissionAllowlistPackages) {
return true
}
val allowlistState = getPrivilegedPermissionAllowlistState(androidPackage, permission.name)
diff --git a/services/permission/java/com/android/server/permission/access/util/BinderExtensions.kt b/services/permission/java/com/android/server/permission/access/util/BinderExtensions.kt
new file mode 100644
index 0000000..a55897d
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/util/BinderExtensions.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.util
+
+import android.os.Binder
+import kotlin.reflect.KClass
+
+inline fun <R> KClass<Binder>.withClearedCallingIdentity(action: () -> R): R {
+ val token = Binder.clearCallingIdentity()
+ try {
+ return action()
+ } finally {
+ Binder.restoreCallingIdentity(token)
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
deleted file mode 100644
index ea14ffb..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
+++ /dev/null
@@ -1,282 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.am;
-
-import static android.os.Process.myPid;
-import static android.os.Process.myUid;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-
-import android.app.ActivityManagerInternal;
-import android.app.IApplicationThread;
-import android.app.usage.UsageStatsManagerInternal;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManagerInternal;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.SystemClock;
-import android.util.Log;
-
-import androidx.test.filters.MediumTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.server.DropBoxManagerInternal;
-import com.android.server.LocalServices;
-import com.android.server.am.ActivityManagerService.Injector;
-import com.android.server.appop.AppOpsService;
-import com.android.server.wm.ActivityTaskManagerInternal;
-import com.android.server.wm.ActivityTaskManagerService;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.File;
-import java.util.Arrays;
-
-
-/**
- * Tests to verify process starts are completed or timeout correctly
- */
-@MediumTest
-@SuppressWarnings("GuardedBy")
-public class AsyncProcessStartTest {
- private static final String TAG = "AsyncProcessStartTest";
-
- private static final String PACKAGE = "com.foo";
-
- @Rule
- public final ApplicationExitInfoTest.ServiceThreadRule
- mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
-
- private Context mContext;
- private HandlerThread mHandlerThread;
-
- @Mock
- private AppOpsService mAppOpsService;
- @Mock
- private DropBoxManagerInternal mDropBoxManagerInt;
- @Mock
- private PackageManagerInternal mPackageManagerInt;
- @Mock
- private UsageStatsManagerInternal mUsageStatsManagerInt;
- @Mock
- private ActivityManagerInternal mActivityManagerInt;
- @Mock
- private ActivityTaskManagerInternal mActivityTaskManagerInt;
- @Mock
- private BatteryStatsService mBatteryStatsService;
-
- private ActivityManagerService mRealAms;
- private ActivityManagerService mAms;
-
- private ProcessList mRealProcessList = new ProcessList();
- private ProcessList mProcessList;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
-
- mHandlerThread = new HandlerThread(TAG);
- mHandlerThread.start();
-
- LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
- LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
-
- LocalServices.removeServiceForTest(PackageManagerInternal.class);
- LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
-
- LocalServices.removeServiceForTest(ActivityManagerInternal.class);
- LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInt);
-
- LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
- LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInt);
-
- doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
- doReturn(true).when(mActivityTaskManagerInt).attachApplication(any());
- doNothing().when(mActivityTaskManagerInt).onProcessMapped(anyInt(), any());
-
- mRealAms = new ActivityManagerService(
- new TestInjector(mContext), mServiceThreadRule.getThread());
- mRealAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
- mRealAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
- mRealAms.mAtmInternal = mActivityTaskManagerInt;
- mRealAms.mPackageManagerInt = mPackageManagerInt;
- mRealAms.mUsageStatsService = mUsageStatsManagerInt;
- mRealAms.mProcessesReady = true;
- mAms = spy(mRealAms);
- mRealProcessList.mService = mAms;
- mProcessList = spy(mRealProcessList);
-
- doAnswer((invocation) -> {
- Log.v(TAG, "Intercepting isProcStartValidLocked() for "
- + Arrays.toString(invocation.getArguments()));
- return null;
- }).when(mProcessList).isProcStartValidLocked(any(), anyLong());
- }
-
- @After
- public void tearDown() throws Exception {
- mHandlerThread.quit();
- }
-
- private class TestInjector extends Injector {
- TestInjector(Context context) {
- super(context);
- }
-
- @Override
- public AppOpsService getAppOpsService(File file, Handler handler) {
- return mAppOpsService;
- }
-
- @Override
- public Handler getUiHandler(ActivityManagerService service) {
- return mHandlerThread.getThreadHandler();
- }
-
- @Override
- public ProcessList getProcessList(ActivityManagerService service) {
- return mRealProcessList;
- }
-
- @Override
- public BatteryStatsService getBatteryStatsService() {
- return mBatteryStatsService;
- }
- }
-
- private ProcessRecord makeActiveProcessRecord(String packageName, boolean wedge)
- throws Exception {
- final ApplicationInfo ai = makeApplicationInfo(packageName);
- return makeActiveProcessRecord(ai, wedge);
- }
-
- private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai, boolean wedge)
- throws Exception {
- final IApplicationThread thread = mock(IApplicationThread.class);
- final IBinder threadBinder = new Binder();
- doReturn(threadBinder).when(thread).asBinder();
- doAnswer((invocation) -> {
- Log.v(TAG, "Intercepting bindApplication() for "
- + Arrays.toString(invocation.getArguments()));
- if (!wedge) {
- mRealAms.finishAttachApplication(0);
- }
- return null;
- }).when(thread).bindApplication(
- any(), any(),
- any(), any(),
- any(), any(),
- any(), any(),
- any(),
- any(), anyInt(),
- anyBoolean(), anyBoolean(),
- anyBoolean(), anyBoolean(), any(),
- any(), any(), any(),
- any(), any(),
- any(), any(),
- any(),
- anyLong(), anyLong());
-
- final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
- r.setPid(myPid());
- r.setStartUid(myUid());
- r.setHostingRecord(new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST));
- r.makeActive(thread, mAms.mProcessStats);
- doNothing().when(r).killLocked(any(), any(), anyInt(), anyInt(), anyBoolean(),
- anyBoolean());
-
- return r;
- }
-
- static ApplicationInfo makeApplicationInfo(String packageName) {
- final ApplicationInfo ai = new ApplicationInfo();
- ai.packageName = packageName;
- ai.processName = packageName;
- ai.uid = myUid();
- return ai;
- }
-
- /**
- * Verify that we don't kill a normal process
- */
- @Test
- public void testNormal() throws Exception {
- ProcessRecord app = startProcessAndWait(false);
-
- verify(app, never()).killLocked(any(), anyInt(), anyBoolean());
- }
-
- /**
- * Verify that we kill a wedged process after the process start timeout
- */
- @Test
- public void testWedged() throws Exception {
- ProcessRecord app = startProcessAndWait(true);
-
- verify(app).killLocked(any(), anyInt(), anyBoolean());
- }
-
- private ProcessRecord startProcessAndWait(boolean wedge) throws Exception {
- final ProcessRecord app = makeActiveProcessRecord(PACKAGE, wedge);
- final ApplicationInfo appInfo = makeApplicationInfo(PACKAGE);
-
- mProcessList.handleProcessStartedLocked(app, app.getPid(), /* usingWrapper */ false,
- /* expectedStartSeq */ 0, /* procAttached */ false);
-
- app.getThread().bindApplication(PACKAGE, appInfo,
- null, null,
- null,
- null,
- null, null,
- null,
- null, 0,
- false, false,
- true, false,
- null,
- null, null,
- null,
- null, null, null,
- null, null,
- 0, 0);
-
- // Sleep until timeout should have triggered
- SystemClock.sleep(ActivityManagerService.PROC_START_TIMEOUT + 1000);
-
- return app;
- }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index 2c10329..6374c66 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -254,11 +254,6 @@
ConnectivityController connectivityController = mService.getConnectivityController();
spyOn(connectivityController);
- mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS = 10 * MINUTE_IN_MILLIS;
- mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS = 60 * MINUTE_IN_MILLIS;
- mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.5f;
- mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS = HOUR_IN_MILLIS;
- mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS = 6 * HOUR_IN_MILLIS;
assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(ejMax));
@@ -311,7 +306,8 @@
.when(connectivityController).getEstimatedTransferTimeMs(any());
assertEquals(
(long) (mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS
- * 2 * 1.5),
+ * 2 * mService.mConstants
+ .RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR),
mService.getMinJobExecutionGuaranteeMs(jobUIDT));
doReturn(mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS * 2)
.when(connectivityController).getEstimatedTransferTimeMs(any());
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 4f56271..6dc45c3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -55,6 +55,7 @@
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder
import com.android.internal.R
+import com.android.server.LocalManagerRegistry
import com.android.server.LocalServices
import com.android.server.LockGuard
import com.android.server.SystemConfig
@@ -148,6 +149,7 @@
.mockStatic(LockGuard::class.java)
.mockStatic(EventLog::class.java)
.mockStatic(LocalServices::class.java)
+ .mockStatic(LocalManagerRegistry::class.java)
.mockStatic(DeviceConfig::class.java)
.mockStatic(HexEncoding::class.java)
.apply(withSession)
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
index aefe4b6..3a27e3b 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
@@ -17,6 +17,7 @@
package com.android.server.companion.virtual;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
import static android.hardware.Sensor.TYPE_ACCELEROMETER;
@@ -40,6 +41,8 @@
private static final String SENSOR_NAME = "VirtualSensorName";
private static final String SENSOR_VENDOR = "VirtualSensorVendor";
+ private static final int PLAYBACK_SESSION_ID = 42;
+ private static final int RECORDING_SESSION_ID = 77;
@Test
public void parcelable_shouldRecreateSuccessfully() {
@@ -47,6 +50,9 @@
.setLockState(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED)
.setUsersWithMatchingAccounts(Set.of(UserHandle.of(123), UserHandle.of(456)))
.setDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
+ .setDevicePolicy(POLICY_TYPE_AUDIO, DEVICE_POLICY_CUSTOM)
+ .setAudioPlaybackSessionId(PLAYBACK_SESSION_ID)
+ .setAudioRecordingSessionId(RECORDING_SESSION_ID)
.addVirtualSensorConfig(
new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
.setVendor(SENSOR_VENDOR)
@@ -62,6 +68,9 @@
assertThat(params.getUsersWithMatchingAccounts())
.containsExactly(UserHandle.of(123), UserHandle.of(456));
assertThat(params.getDevicePolicy(POLICY_TYPE_SENSORS)).isEqualTo(DEVICE_POLICY_CUSTOM);
+ assertThat(params.getDevicePolicy(POLICY_TYPE_AUDIO)).isEqualTo(DEVICE_POLICY_CUSTOM);
+ assertThat(params.getAudioPlaybackSessionId()).isEqualTo(PLAYBACK_SESSION_ID);
+ assertThat(params.getAudioRecordingSessionId()).isEqualTo(RECORDING_SESSION_ID);
List<VirtualSensorConfig> sensorConfigs = params.getVirtualSensorConfigs();
assertThat(sensorConfigs).hasSize(1);
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index ae36871..ddde385 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -26,6 +26,7 @@
import static org.mockito.Mockito.anyFloat;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -95,7 +96,8 @@
mLightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
mContext = InstrumentationRegistry.getContext();
- mController = setupController(mLightSensor);
+ mController = setupController(mLightSensor, BrightnessMappingStrategy.NO_USER_LUX,
+ BrightnessMappingStrategy.NO_USER_BRIGHTNESS);
}
@After
@@ -107,7 +109,8 @@
}
}
- private AutomaticBrightnessController setupController(Sensor lightSensor) {
+ private AutomaticBrightnessController setupController(Sensor lightSensor, float userLux,
+ float userBrightness) {
mClock = new OffsettableClock.Stopped();
mTestLooper = new TestLooper(mClock::now);
@@ -132,7 +135,7 @@
mAmbientBrightnessThresholds, mScreenBrightnessThresholds,
mAmbientBrightnessThresholdsIdle, mScreenBrightnessThresholdsIdle,
mContext, mHbmController, mBrightnessThrottler, mIdleBrightnessMappingStrategy,
- AMBIENT_LIGHT_HORIZON_SHORT, AMBIENT_LIGHT_HORIZON_LONG
+ AMBIENT_LIGHT_HORIZON_SHORT, AMBIENT_LIGHT_HORIZON_LONG, userLux, userBrightness
);
when(mHbmController.getCurrentBrightnessMax()).thenReturn(BRIGHTNESS_MAX_FLOAT);
@@ -143,9 +146,10 @@
// Configure the brightness controller and grab an instance of the sensor listener,
// through which we can deliver fake (for test) sensor values.
- controller.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
- 0 /* brightness */, false /* userChangedBrightness */, 0 /* adjustment */,
- false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+ controller.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+ 0 /* brightness= */, false /* userChangedBrightness= */, 0 /* adjustment= */,
+ false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ true);
return controller;
}
@@ -250,9 +254,10 @@
listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
// User sets brightness to 100
- mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
- 0.5f /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */,
- false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+ 0.5f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
+ false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ true);
// There should be a user data point added to the mapper.
verify(mBrightnessMappingStrategy).addUserDataPoint(1000f, 0.5f);
@@ -272,9 +277,10 @@
// User sets brightness to 0.5f
when(mBrightnessMappingStrategy.getBrightness(currentLux,
null, ApplicationInfo.CATEGORY_UNDEFINED)).thenReturn(0.5f);
- mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
- 0.5f /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */,
- false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+ 0.5f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
+ false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ true);
//Recalculating the spline with RBC enabled, verifying that the short term model is reset,
//and the interaction is learnt in short term model
@@ -305,9 +311,10 @@
listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
// User sets brightness to 100
- mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
- 0.5f /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */,
- false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+ 0.5f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
+ false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ true);
// There should be a user data point added to the mapper.
verify(mBrightnessMappingStrategy, times(1)).addUserDataPoint(1000f, 0.5f);
@@ -323,9 +330,10 @@
verifyNoMoreInteractions(mBrightnessMappingStrategy);
// User sets idle brightness to 0.5
- mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
- 0.5f /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */,
- false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+ 0.5f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
+ false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ true);
// Ensure we use the correct mapping strategy
verify(mIdleBrightnessMappingStrategy, times(1)).addUserDataPoint(1000f, 0.5f);
@@ -481,17 +489,19 @@
final float throttledBrightness = 0.123f;
when(mBrightnessThrottler.getBrightnessCap()).thenReturn(throttledBrightness);
when(mBrightnessThrottler.isThrottled()).thenReturn(true);
- mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
- BRIGHTNESS_MAX_FLOAT /* brightness */, false /* userChangedBrightness */,
- 0 /* adjustment */, false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+ BRIGHTNESS_MAX_FLOAT /* brightness= */, false /* userChangedBrightness= */,
+ 0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ true);
assertEquals(throttledBrightness, mController.getAutomaticScreenBrightness(), 0.0f);
// Remove throttling and notify ABC again
when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
when(mBrightnessThrottler.isThrottled()).thenReturn(false);
- mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
- BRIGHTNESS_MAX_FLOAT /* brightness */, false /* userChangedBrightness */,
- 0 /* adjustment */, false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+ BRIGHTNESS_MAX_FLOAT /* brightness= */, false /* userChangedBrightness= */,
+ 0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ true);
assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getAutomaticScreenBrightness(), 0.0f);
}
@@ -582,4 +592,32 @@
assertEquals(lux, sensorValues[0], EPSILON);
assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]);
}
+
+ @Test
+ public void testResetShortTermModelWhenConfigChanges() {
+ when(mBrightnessMappingStrategy.isForIdleMode()).thenReturn(false);
+ when(mBrightnessMappingStrategy.setBrightnessConfiguration(any())).thenReturn(true);
+
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+ BRIGHTNESS_MAX_FLOAT /* brightness= */, false /* userChangedBrightness= */,
+ 0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ false);
+ verify(mBrightnessMappingStrategy, never()).clearUserDataPoints();
+
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+ BRIGHTNESS_MAX_FLOAT /* brightness= */, false /* userChangedBrightness= */,
+ 0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ true);
+ verify(mBrightnessMappingStrategy).clearUserDataPoints();
+ }
+
+ @Test
+ public void testUseProvidedShortTermModel() {
+ verify(mBrightnessMappingStrategy, never()).addUserDataPoint(anyFloat(), anyFloat());
+
+ float userLux = 1000;
+ float userBrightness = 0.3f;
+ setupController(mLightSensor, userLux, userBrightness);
+ verify(mBrightnessMappingStrategy).addUserDataPoint(userLux, userBrightness);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
index 4d42afa..1b8958b 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
@@ -158,7 +158,7 @@
mUserMonitor = mBackupHelper.getUserMonitor();
mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper,
- systemAppUpdateTracker, appUpdateTracker);
+ systemAppUpdateTracker, appUpdateTracker, mMockLocaleManagerService);
setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS);
}
@@ -667,7 +667,7 @@
String pkgNameB = "com.android.myAppB";
setUpPackageNamesForSp(new ArraySet<>(Arrays.asList(pkgNameA, pkgNameB)));
- mPackageMonitor.onPackageRemoved(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+ mBackupHelper.onPackageRemoved(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
verify(mMockSpEditor, times(0)).putStringSet(anyString(), any());
}
@@ -679,7 +679,7 @@
Set<String> pkgNames = new ArraySet<>(Arrays.asList(pkgNameA, pkgNameB));
setUpPackageNamesForSp(pkgNames);
- mPackageMonitor.onPackageRemoved(pkgNameA, DEFAULT_UID);
+ mBackupHelper.onPackageRemoved(pkgNameA, DEFAULT_UID);
pkgNames.remove(pkgNameA);
verify(mMockSpEditor, times(1)).putStringSet(
@@ -693,7 +693,7 @@
Set<String> pkgNames = new ArraySet<>(Arrays.asList(pkgNameA, pkgNameB));
setUpPackageNamesForSp(pkgNames);
- mPackageMonitor.onPackageDataCleared(pkgNameB, DEFAULT_UID);
+ mBackupHelper.onPackageDataCleared(pkgNameB, DEFAULT_UID);
pkgNames.remove(pkgNameB);
verify(mMockSpEditor, times(1)).putStringSet(
diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
index dc0740a..cbf555e 100644
--- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
@@ -137,7 +137,7 @@
AppUpdateTracker appUpdateTracker = mock(AppUpdateTracker.class);
mPackageMonitor = new LocaleManagerServicePackageMonitor(mockLocaleManagerBackupHelper,
- mSystemAppUpdateTracker, appUpdateTracker);
+ mSystemAppUpdateTracker, appUpdateTracker, mLocaleManagerService);
}
@After
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java
index ec708ad..d251609 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java
@@ -39,10 +39,12 @@
import android.app.AlarmManager;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
+import android.platform.test.annotations.Presubmit;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.server.locksettings.LockSettingsStrongAuth.NonStrongBiometricIdleTimeoutAlarmListener;
import com.android.server.locksettings.LockSettingsStrongAuth.NonStrongBiometricTimeoutAlarmListener;
@@ -50,10 +52,13 @@
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
public class LockSettingsStrongAuthTest {
private static final String TAG = LockSettingsStrongAuthTest.class.getSimpleName();
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
index 10869da..2b49b8a 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
@@ -26,7 +26,9 @@
import android.app.PropertyInvalidatedCache;
import android.app.admin.DevicePolicyManager;
+import android.platform.test.annotations.Presubmit;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.widget.VerifyCredentialResponse;
@@ -38,6 +40,8 @@
/** Test setting a lockscreen credential and then verify it under USER_FRP */
+@SmallTest
+@Presubmit
@RunWith(AndroidJUnit4.class)
public class LockscreenFrpTest extends BaseLockSettingsServiceTests {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java b/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
index 8cb18a8..a40cb06 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
@@ -113,7 +113,7 @@
}
@Override
- protected IWeaver getWeaverService() throws RemoteException {
+ protected IWeaver getWeaverHidlService() throws RemoteException {
return mWeaverService;
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java
index 2eedc32..771c3f1 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java
@@ -19,6 +19,9 @@
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
@@ -34,6 +37,8 @@
/**
* atest FrameworksServicesTests:RebootEscrowDataTest
*/
+@SmallTest
+@Presubmit
@RunWith(AndroidJUnit4.class)
public class RebootEscrowDataTest {
private RebootEscrowKey mKey;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/ResumeOnRebootServiceProviderTests.java b/services/tests/servicestests/src/com/android/server/locksettings/ResumeOnRebootServiceProviderTests.java
index f3a38e6..f94a99f 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/ResumeOnRebootServiceProviderTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/ResumeOnRebootServiceProviderTests.java
@@ -30,6 +30,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.platform.test.annotations.Presubmit;
import android.service.resumeonreboot.ResumeOnRebootService;
import androidx.test.filters.SmallTest;
@@ -46,6 +47,7 @@
import java.util.ArrayList;
@SmallTest
+@Presubmit
@RunWith(JUnit4.class)
public class ResumeOnRebootServiceProviderTests {
diff --git a/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java
index 2cd5314..771916e 100644
--- a/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java
@@ -22,22 +22,19 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
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;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
-import android.content.ContextWrapper;
+import android.content.Context;
import android.content.pm.PackageManager;
import android.os.ILogd;
import android.os.Looper;
import android.os.UserHandle;
import android.os.test.TestLooper;
-import androidx.test.core.app.ApplicationProvider;
-
import com.android.server.LocalServices;
import com.android.server.logcat.LogcatManagerService.Injector;
import com.android.server.testutils.OffsettableClock;
@@ -70,6 +67,8 @@
private static final int FD2 = 11;
@Mock
+ private Context mContextSpy;
+ @Mock
private ActivityManagerInternal mActivityManagerInternalMock;
@Mock
private PackageManager mPackageManagerMock;
@@ -78,7 +77,6 @@
private LogcatManagerService mService;
private LogcatManagerService.LogAccessDialogCallback mDialogCallback;
- private ContextWrapper mContextSpy;
private OffsettableClock mClock;
private TestLooper mTestLooper;
@@ -89,7 +87,6 @@
when(mActivityManagerInternalMock.getInstrumentationSourceUid(anyInt()))
.thenReturn(INVALID_UID);
- mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
mClock = new OffsettableClock.Stopped();
mTestLooper = new TestLooper(mClock::now);
when(mContextSpy.getPackageManager()).thenReturn(mPackageManagerMock);
diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
index dcbdcdc..136507d 100644
--- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -19,6 +19,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
@@ -310,4 +311,32 @@
a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
assertTrue(a.updateHintAllowed());
}
+
+ @Test
+ public void testSetThreads() throws Exception {
+ HintManagerService service = createService();
+ IBinder token = new Binder();
+
+ AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
+ .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
+
+ a.updateTargetWorkDuration(100L);
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ a.setThreads(new int[]{});
+ });
+
+ a.setThreads(SESSION_TIDS_B);
+ verify(mNativeWrapperMock, times(1)).halSetThreads(anyLong(), eq(SESSION_TIDS_B));
+ assertArrayEquals(SESSION_TIDS_B, a.getThreadIds());
+
+ reset(mNativeWrapperMock);
+ // Set session to background, then the duration would not be updated.
+ service.mUidObserver.onUidStateChanged(
+ a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ FgThread.getHandler().runWithScissors(() -> { }, 500);
+ assertFalse(a.updateHintAllowed());
+ a.setThreads(SESSION_TIDS_A);
+ verify(mNativeWrapperMock, never()).halSetThreads(anyLong(), any());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 19d8894..b0489da 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3216,14 +3216,14 @@
public void testImeInsetsFrozenFlag_resetWhenReportedToBeImeInputTarget() {
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- InsetsSource imeSource = new InsetsSource(ITYPE_IME);
+ InsetsSource imeSource = new InsetsSource(ITYPE_IME, ime());
app.mAboveInsetsState.addSource(imeSource);
mDisplayContent.setImeLayeringTarget(app);
mDisplayContent.updateImeInputAndControlTarget(app);
InsetsState state = app.getInsetsState();
- assertFalse(state.getSource(ITYPE_IME).isVisible());
- assertTrue(state.getSource(ITYPE_IME).getFrame().isEmpty());
+ assertFalse(state.getSource(imeSource.getId()).isVisible());
+ assertTrue(state.getSource(imeSource.getId()).getFrame().isEmpty());
// Simulate app is closing and expect IME insets is frozen.
mDisplayContent.mOpeningApps.clear();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 10f2270..cf9ec81 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -24,6 +24,7 @@
import static android.view.Surface.ROTATION_0;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
@@ -184,7 +185,7 @@
mDisplayContent.setLayoutNeeded();
mDisplayContent.performLayout(true /* initial */, false /* updateImeWindows */);
- final InsetsSource navSource = new InsetsSource(ITYPE_NAVIGATION_BAR);
+ final InsetsSource navSource = new InsetsSource(ITYPE_NAVIGATION_BAR, navigationBars());
navSource.setFrame(mNavBarWindow.getFrame());
opaqueDarkNavBar.mAboveInsetsState.addSource(navSource);
opaqueLightNavBar.mAboveInsetsState.addSource(navSource);
@@ -254,14 +255,15 @@
@Test
public void testOverlappingWithNavBar() {
- final InsetsSource navSource = new InsetsSource(ITYPE_NAVIGATION_BAR);
+ final InsetsSource navSource = new InsetsSource(ITYPE_NAVIGATION_BAR, navigationBars());
navSource.setFrame(new Rect(100, 200, 200, 300));
testOverlappingWithNavBarType(navSource);
}
@Test
public void testOverlappingWithExtraNavBar() {
- final InsetsSource navSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR);
+ final InsetsSource navSource =
+ new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR, navigationBars());
navSource.setFrame(new Rect(100, 200, 200, 300));
testOverlappingWithNavBarType(navSource);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
index a26cad9..d4e860e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
import static android.view.InsetsState.ITYPE_IME;
+import static android.view.WindowInsets.Type.ime;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
@@ -38,7 +39,7 @@
@RunWith(WindowTestRunner.class)
public class ImeInsetsSourceProviderTest extends WindowTestsBase {
- private InsetsSource mImeSource = new InsetsSource(ITYPE_IME);
+ private InsetsSource mImeSource = new InsetsSource(ITYPE_IME, ime());
private ImeInsetsSourceProvider mImeProvider;
@Before
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index c7971bc7..9887839 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -197,14 +197,14 @@
mDisplayContent.getInsetsStateController().getControlsForDispatch(dialog);
assertNotNull(dialogControls);
assertEquals(1, dialogControls.length);
- assertEquals(ITYPE_NAVIGATION_BAR, dialogControls[0].getType());
+ assertEquals(navigationBars(), dialogControls[0].getType());
// fullscreenApp is hiding status bar, and it can keep controlling status bar.
final InsetsSourceControl[] fullscreenAppControls =
mDisplayContent.getInsetsStateController().getControlsForDispatch(fullscreenApp);
assertNotNull(fullscreenAppControls);
assertEquals(1, fullscreenAppControls.length);
- assertEquals(ITYPE_STATUS_BAR, fullscreenAppControls[0].getType());
+ assertEquals(statusBars(), fullscreenAppControls[0].getType());
// Assume mFocusedWindow is updated but mTopFullscreenOpaqueWindowState hasn't.
final WindowState newFocusedFullscreenApp = addWindow(TYPE_APPLICATION, "newFullscreenApp");
@@ -232,7 +232,7 @@
mDisplayContent.getInsetsStateController().getControlsForDispatch(panel);
assertNotNull(panelControls);
assertEquals(1, panelControls.length);
- assertEquals(ITYPE_NAVIGATION_BAR, panelControls[0].getType());
+ assertEquals(navigationBars(), panelControls[0].getType());
// Add notificationShade and make it can receive keys.
final WindowState shade = addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade");
@@ -254,7 +254,7 @@
panelControls = mDisplayContent.getInsetsStateController().getControlsForDispatch(panel);
assertNotNull(panelControls);
assertEquals(1, panelControls.length);
- assertEquals(ITYPE_NAVIGATION_BAR, panelControls[0].getType());
+ assertEquals(navigationBars(), panelControls[0].getType());
}
@SetupWindows(addWindows = W_ACTIVITY)
@@ -321,7 +321,7 @@
assertEquals(2, controls.length);
for (int i = controls.length - 1; i >= 0; i--) {
final InsetsSourceControl control = controls[i];
- if (control.getType() == ITYPE_STATUS_BAR) {
+ if (control.getType() == statusBars()) {
assertNull(controls[i].getLeash());
} else {
assertNotNull(controls[i].getLeash());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 6f2e3f2..367f91b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -804,19 +804,6 @@
}
@Test
- public void testVisibleEmbeddedTask_expectNotVisible() {
- Task task = createTaskBuilder(".Task")
- .setFlags(FLAG_ACTIVITY_NEW_TASK)
- .build();
- doReturn(true).when(task).isEmbedded();
- mRecentTasks.add(task);
-
- assertThat(mCallbacksRecorder.mAdded).hasSize(1);
- assertFalse("embedded task should not be visible recents",
- mRecentTasks.isVisibleRecentTask(task));
- }
-
- @Test
public void testVisibleTask_displayCanNotShowTaskFromRecents_expectNotVisible() {
final DisplayContent displayContent = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
doReturn(false).when(displayContent).canShowTasksInHostDeviceRecents();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 6877e4f..1484cb7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -33,6 +33,7 @@
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
+import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -2496,7 +2497,8 @@
organizer.mPrimary.setBounds(0, screenHeight / 2, screenWidth, screenHeight);
organizer.putTaskToPrimary(mTask, true);
- final InsetsSource navSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR);
+ final InsetsSource navSource =
+ new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR, navigationBars());
navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight));
mActivity.mWmService.mLetterboxConfiguration.setLetterboxActivityCornersRadius(15);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java
index 98dee38..06d30fc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java
@@ -23,8 +23,8 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import android.window.TaskSnapshot;
import android.platform.test.annotations.Presubmit;
+import android.window.TaskSnapshot;
import androidx.test.filters.SmallTest;
@@ -89,7 +89,7 @@
mCache.putSnapshot(window.getTask(), createSnapshot());
assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
false /* restoreFromDisk */, false /* isLowResolution */));
- mCache.onTaskRemoved(window.getTask().mTaskId);
+ mCache.onIdRemoved(window.getTask().mTaskId);
assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
false /* restoreFromDisk */, false /* isLowResolution */));
}
@@ -98,7 +98,7 @@
public void testReduced_notCached() {
final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
mPersister.persistSnapshot(window.getTask().mTaskId, mWm.mCurrentUserId, createSnapshot());
- mPersister.waitForQueueEmpty();
+ mSnapshotPersistQueue.waitForQueueEmpty();
assertNull(mCache.getSnapshot(window.getTask().mTaskId, mWm.mCurrentUserId,
false /* restoreFromDisk */, false /* isLowResolution */));
@@ -115,7 +115,7 @@
public void testRestoreFromDisk() {
final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
mPersister.persistSnapshot(window.getTask().mTaskId, mWm.mCurrentUserId, createSnapshot());
- mPersister.waitForQueueEmpty();
+ mSnapshotPersistQueue.waitForQueueEmpty();
assertNull(mCache.getSnapshot(window.getTask().mTaskId, mWm.mCurrentUserId,
false /* restoreFromDisk */, false /* isLowResolution */));
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
index 06a176f..4c7b0aa0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -202,7 +202,7 @@
// Verify no NPE happens when calling createTaskSnapshot.
try {
final TaskSnapshot.Builder builder = new TaskSnapshot.Builder();
- mWm.mTaskSnapshotController.createTaskSnapshot(mAppWindow.mActivityRecord.getTask(),
+ mWm.mTaskSnapshotController.createSnapshot(mAppWindow.mActivityRecord.getTask(),
1f /* scaleFraction */, PixelFormat.UNKNOWN, null /* outTaskSize */, builder);
} catch (NullPointerException e) {
fail("There should be no exception when calling createTaskSnapshot");
@@ -223,7 +223,7 @@
try {
final TaskSnapshot.Builder builder = new TaskSnapshot.Builder();
spyOn(builder);
- mWm.mTaskSnapshotController.createTaskSnapshot(
+ mWm.mTaskSnapshotController.createSnapshot(
mAppWindow.mActivityRecord.getTask(), 1f /* scaleFraction */,
PixelFormat.UNKNOWN, null /* outTaskSize */, builder);
// Verify the builder should includes IME surface.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java
index ec0ca01..df5f3d1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java
@@ -22,11 +22,11 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import android.window.TaskSnapshot;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.util.ArraySet;
+import android.window.TaskSnapshot;
import androidx.test.filters.MediumTest;
@@ -38,7 +38,7 @@
import java.io.File;
/**
- * Test class for {@link TaskSnapshotPersister} and {@link TaskSnapshotLoader}
+ * Test class for {@link TaskSnapshotPersister} and {@link AppSnapshotLoader}
*
* Build/Install/Run:
* atest TaskSnapshotPersisterLoaderTest
@@ -67,7 +67,7 @@
@Test
public void testPersistAndLoadSnapshot() {
mPersister.persistSnapshot(1, mTestUserId, createSnapshot());
- mPersister.waitForQueueEmpty();
+ mSnapshotPersistQueue.waitForQueueEmpty();
final File[] files = new File[]{
new File(FILES_DIR.getPath() + "/snapshots/1.proto"),
new File(FILES_DIR.getPath() + "/snapshots/1.jpg")};
@@ -91,7 +91,7 @@
final ArraySet<Integer> taskIds = new ArraySet<>();
taskIds.add(1);
mPersister.removeObsoleteFiles(taskIds, new int[]{mTestUserId});
- mPersister.waitForQueueEmpty();
+ mSnapshotPersistQueue.waitForQueueEmpty();
final File[] existsFiles = new File[]{
new File(FILES_DIR.getPath() + "/snapshots/1.proto"),
new File(FILES_DIR.getPath() + "/snapshots/1.jpg")};
@@ -111,7 +111,7 @@
taskIds.add(1);
mPersister.removeObsoleteFiles(taskIds, new int[]{mTestUserId});
mPersister.persistSnapshot(2, mTestUserId, createSnapshot());
- mPersister.waitForQueueEmpty();
+ mSnapshotPersistQueue.waitForQueueEmpty();
final File[] existsFiles = new File[]{
new File(FILES_DIR.getPath() + "/snapshots/1.proto"),
new File(FILES_DIR.getPath() + "/snapshots/1.jpg"),
@@ -128,7 +128,7 @@
public void testReduced_notCached() {
final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
mPersister.persistSnapshot(window.getTask().mTaskId, mWm.mCurrentUserId, createSnapshot());
- mPersister.waitForQueueEmpty();
+ mSnapshotPersistQueue.waitForQueueEmpty();
assertNull(mCache.getSnapshot(window.getTask().mTaskId, mWm.mCurrentUserId,
false /* restoreFromDisk */, false /* isLowResolution */));
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
index 7409d62..13a4c11 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
@@ -28,6 +28,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
@@ -41,7 +42,8 @@
import androidx.test.filters.MediumTest;
-import com.android.server.wm.TaskSnapshotLoader.PreRLegacySnapshotConfig;
+import com.android.server.wm.AppSnapshotLoader.PreRLegacySnapshotConfig;
+import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
import com.android.server.wm.TaskSnapshotPersister.RemoveObsoleteFilesQueueItem;
import org.junit.Test;
@@ -51,7 +53,7 @@
import java.io.File;
/**
- * Test class for {@link TaskSnapshotPersister} and {@link TaskSnapshotLoader}
+ * Test class for {@link TaskSnapshotPersister} and {@link AppSnapshotLoader}
*
* Build/Install/Run:
* atest TaskSnapshotPersisterLoaderTest
@@ -72,7 +74,7 @@
@Test
public void testPersistAndLoadSnapshot() {
mPersister.persistSnapshot(1, mTestUserId, createSnapshot());
- mPersister.waitForQueueEmpty();
+ mSnapshotPersistQueue.waitForQueueEmpty();
final File[] files = new File[]{new File(FILES_DIR.getPath() + "/snapshots/1.proto"),
new File(FILES_DIR.getPath() + "/snapshots/1.jpg"),
new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg")};
@@ -86,7 +88,7 @@
snapshot.getHardwareBuffer().close();
mPersister.persistSnapshot(1, mTestUserId, snapshot);
- mPersister.waitForQueueEmpty();
+ mSnapshotPersistQueue.waitForQueueEmpty();
assertTrueForFiles(files, file -> !file.exists(),
" snapshot files must be removed by invalid buffer");
}
@@ -95,7 +97,7 @@
public void testTaskRemovedFromRecents() {
mPersister.persistSnapshot(1, mTestUserId, createSnapshot());
mPersister.onTaskRemovedFromRecents(1, mTestUserId);
- mPersister.waitForQueueEmpty();
+ mSnapshotPersistQueue.waitForQueueEmpty();
assertFalse(new File(FILES_DIR.getPath() + "/snapshots/1.proto").exists());
assertFalse(new File(FILES_DIR.getPath() + "/snapshots/1.jpg").exists());
assertFalse(new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg").exists());
@@ -113,7 +115,7 @@
mPersister.removeObsoleteFiles(new ArraySet<>(), new int[]{mTestUserId});
mPersister.removeObsoleteFiles(new ArraySet<>(), new int[]{mTestUserId});
mPersister.removeObsoleteFiles(new ArraySet<>(), new int[]{mTestUserId});
- mPersister.waitForQueueEmpty();
+ mSnapshotPersistQueue.waitForQueueEmpty();
assertTrue(SystemClock.elapsedRealtime() - ms > 500);
}
@@ -123,15 +125,15 @@
@Test
public void testPurging() {
mPersister.persistSnapshot(100, mTestUserId, createSnapshot());
- mPersister.waitForQueueEmpty();
- mPersister.setPaused(true);
+ mSnapshotPersistQueue.waitForQueueEmpty();
+ mSnapshotPersistQueue.setPaused(true);
mPersister.persistSnapshot(1, mTestUserId, createSnapshot());
mPersister.removeObsoleteFiles(new ArraySet<>(), new int[]{mTestUserId});
mPersister.persistSnapshot(2, mTestUserId, createSnapshot());
mPersister.persistSnapshot(3, mTestUserId, createSnapshot());
mPersister.persistSnapshot(4, mTestUserId, createSnapshot());
- mPersister.setPaused(false);
- mPersister.waitForQueueEmpty();
+ mSnapshotPersistQueue.setPaused(false);
+ mSnapshotPersistQueue.waitForQueueEmpty();
// Make sure 1,2 were purged but removeObsoleteFiles wasn't.
final File[] existsFiles = new File[]{
@@ -147,8 +149,10 @@
@Test
public void testGetTaskId() {
+ PersistInfoProvider persistInfoProvider = mock(PersistInfoProvider.class);
RemoveObsoleteFilesQueueItem removeObsoleteFilesQueueItem =
- mPersister.new RemoveObsoleteFilesQueueItem(new ArraySet<>(), new int[]{});
+ mPersister.new RemoveObsoleteFilesQueueItem(
+ new ArraySet<>(), new int[]{}, persistInfoProvider);
assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("blablablulp"));
assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("nothing.err"));
assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("/invalid/"));
@@ -311,7 +315,7 @@
assertFalse(b.isRealSnapshot());
mPersister.persistSnapshot(1, mTestUserId, a);
mPersister.persistSnapshot(2, mTestUserId, b);
- mPersister.waitForQueueEmpty();
+ mSnapshotPersistQueue.waitForQueueEmpty();
final TaskSnapshot snapshotA = mLoader.loadTask(1, mTestUserId,
false /* isLowResolution */);
final TaskSnapshot snapshotB = mLoader.loadTask(2, mTestUserId,
@@ -334,7 +338,7 @@
assertEquals(WINDOWING_MODE_PINNED, b.getWindowingMode());
mPersister.persistSnapshot(1, mTestUserId, a);
mPersister.persistSnapshot(2, mTestUserId, b);
- mPersister.waitForQueueEmpty();
+ mSnapshotPersistQueue.waitForQueueEmpty();
final TaskSnapshot snapshotA = mLoader.loadTask(1, mTestUserId,
false /* isLowResolution */);
final TaskSnapshot snapshotB = mLoader.loadTask(2, mTestUserId,
@@ -357,7 +361,7 @@
assertFalse(b.isTranslucent());
mPersister.persistSnapshot(1, mTestUserId, a);
mPersister.persistSnapshot(2, mTestUserId, b);
- mPersister.waitForQueueEmpty();
+ mSnapshotPersistQueue.waitForQueueEmpty();
final TaskSnapshot snapshotA = mLoader.loadTask(1, mTestUserId,
false /* isLowResolution */);
final TaskSnapshot snapshotB = mLoader.loadTask(2, mTestUserId,
@@ -381,7 +385,7 @@
assertEquals(lightBarFlags, b.getAppearance());
mPersister.persistSnapshot(1, mTestUserId, a);
mPersister.persistSnapshot(2, mTestUserId, b);
- mPersister.waitForQueueEmpty();
+ mSnapshotPersistQueue.waitForQueueEmpty();
final TaskSnapshot snapshotA = mLoader.loadTask(1, mTestUserId,
false /* isLowResolution */);
final TaskSnapshot snapshotB = mLoader.loadTask(2, mTestUserId,
@@ -402,7 +406,7 @@
.build();
mPersister.persistSnapshot(1, mTestUserId, a);
mPersister.persistSnapshot(2, mTestUserId, b);
- mPersister.waitForQueueEmpty();
+ mSnapshotPersistQueue.waitForQueueEmpty();
final TaskSnapshot snapshotA = mLoader.loadTask(1, mTestUserId,
false /* isLowResolution */);
final TaskSnapshot snapshotB = mLoader.loadTask(2, mTestUserId,
@@ -418,7 +422,7 @@
final ArraySet<Integer> taskIds = new ArraySet<>();
taskIds.add(1);
mPersister.removeObsoleteFiles(taskIds, new int[]{mTestUserId});
- mPersister.waitForQueueEmpty();
+ mSnapshotPersistQueue.waitForQueueEmpty();
final File[] existsFiles = new File[]{
new File(FILES_DIR.getPath() + "/snapshots/1.proto"),
new File(FILES_DIR.getPath() + "/snapshots/1.jpg"),
@@ -438,7 +442,7 @@
taskIds.add(1);
mPersister.removeObsoleteFiles(taskIds, new int[]{mTestUserId});
mPersister.persistSnapshot(2, mTestUserId, createSnapshot());
- mPersister.waitForQueueEmpty();
+ mSnapshotPersistQueue.waitForQueueEmpty();
final File[] existsFiles = new File[]{
new File(FILES_DIR.getPath() + "/snapshots/1.proto"),
new File(FILES_DIR.getPath() + "/snapshots/1.jpg"),
@@ -456,7 +460,7 @@
.build();
mPersister.persistSnapshot(1, mTestUserId, createSnapshot());
mPersister.persistSnapshot(2, mTestUserId, a);
- mPersister.waitForQueueEmpty();
+ mSnapshotPersistQueue.waitForQueueEmpty();
final TaskSnapshot snapshotA = mLoader.loadTask(1, mTestUserId,
false /* isLowResolution */);
final TaskSnapshot snapshotB = mLoader.loadTask(2, mTestUserId,
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
index 677359f..b69874a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -45,6 +45,7 @@
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
+import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
import org.junit.After;
import org.junit.AfterClass;
@@ -66,8 +67,9 @@
private ContextWrapper mContextSpy;
private Resources mResourcesSpy;
+ SnapshotPersistQueue mSnapshotPersistQueue;
TaskSnapshotPersister mPersister;
- TaskSnapshotLoader mLoader;
+ AppSnapshotLoader mLoader;
int mTestUserId;
float mHighResScale;
float mLowResScale;
@@ -108,9 +110,12 @@
com.android.internal.R.dimen.config_lowResTaskSnapshotScale))
.thenReturn(mLowResScale);
- mPersister = new TaskSnapshotPersister(mWm, userId -> FILES_DIR);
- mLoader = new TaskSnapshotLoader(mPersister);
- mPersister.start();
+ mSnapshotPersistQueue = new SnapshotPersistQueue();
+ PersistInfoProvider provider =
+ TaskSnapshotController.createPersistInfoProvider(mWm, userId -> FILES_DIR);
+ mPersister = new TaskSnapshotPersister(mSnapshotPersistQueue, provider);
+ mLoader = new AppSnapshotLoader(provider);
+ mSnapshotPersistQueue.start();
}
@After
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index a95b811..0231b46 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -89,7 +89,6 @@
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -129,10 +128,10 @@
final ActivityRecord closing = createActivityRecord(oldTask);
final ActivityRecord opening = createActivityRecord(newTask);
// Start states.
- changes.put(newTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(oldTask, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
- changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
+ changes.put(newTask, new Transition.ChangeInfo(newTask, false /* vis */, true /* exChg */));
+ changes.put(oldTask, new Transition.ChangeInfo(oldTask, true /* vis */, true /* exChg */));
+ changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
+ changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, true /* exChg */));
fillChangeMap(changes, newTask);
// End states.
closing.setVisibleRequested(false);
@@ -144,9 +143,9 @@
// Check basic both tasks participating
participants.add(oldTask);
participants.add(newTask);
- ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
- TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
- mMockT);
+ ArrayList<Transition.ChangeInfo> targets =
+ Transition.calculateTargets(participants, changes);
+ TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
@@ -154,7 +153,7 @@
participants.add(opening);
participants.add(closing);
targets = Transition.calculateTargets(participants, changes);
- info = Transition.calculateTransitionInfo(transit, flags, targets, changes, mMockT);
+ info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
@@ -162,7 +161,7 @@
// Check combined prune and promote
participants.remove(newTask);
targets = Transition.calculateTargets(participants, changes);
- info = Transition.calculateTransitionInfo(transit, flags, targets, changes, mMockT);
+ info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
@@ -170,7 +169,7 @@
// Check multi promote
participants.remove(oldTask);
targets = Transition.calculateTargets(participants, changes);
- info = Transition.calculateTransitionInfo(transit, flags, targets, changes, mMockT);
+ info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
@@ -190,13 +189,16 @@
final ActivityRecord opening = createActivityRecord(newNestedTask);
final ActivityRecord opening2 = createActivityRecord(newNestedTask2);
// Start states.
- changes.put(newTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(newNestedTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(newNestedTask2, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(oldTask, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
- changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(opening2, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
+ changes.put(newTask, new Transition.ChangeInfo(newTask, false /* vis */, true /* exChg */));
+ changes.put(newNestedTask,
+ new Transition.ChangeInfo(newNestedTask, false /* vis */, true /* exChg */));
+ changes.put(newNestedTask2,
+ new Transition.ChangeInfo(newNestedTask2, false /* vis */, true /* exChg */));
+ changes.put(oldTask, new Transition.ChangeInfo(oldTask, true /* vis */, true /* exChg */));
+ changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
+ changes.put(opening2,
+ new Transition.ChangeInfo(opening2, false /* vis */, true /* exChg */));
+ changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, true /* exChg */));
fillChangeMap(changes, newTask);
// End states.
closing.setVisibleRequested(false);
@@ -210,9 +212,9 @@
participants.add(oldTask);
participants.add(opening);
participants.add(opening2);
- ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
- TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
- mMockT);
+ ArrayList<Transition.ChangeInfo> targets =
+ Transition.calculateTargets(participants, changes);
+ TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
@@ -221,7 +223,7 @@
// Check that unchanging but visible descendant of sibling prevents promotion
participants.remove(opening2);
targets = Transition.calculateTargets(participants, changes);
- info = Transition.calculateTransitionInfo(transit, flags, targets, changes, mMockT);
+ info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertNotNull(info.getChange(newNestedTask.mRemoteToken.toWindowContainerToken()));
assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
@@ -241,12 +243,16 @@
final ActivityRecord showing = createActivityRecord(showNestedTask);
final ActivityRecord showing2 = createActivityRecord(showTask2);
// Start states.
- changes.put(showTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(showNestedTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(showTask2, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(tda, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(showing, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(showing2, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
+ changes.put(showTask,
+ new Transition.ChangeInfo(showTask, false /* vis */, true /* exChg */));
+ changes.put(showNestedTask,
+ new Transition.ChangeInfo(showNestedTask, false /* vis */, true /* exChg */));
+ changes.put(showTask2,
+ new Transition.ChangeInfo(showTask2, false /* vis */, true /* exChg */));
+ changes.put(tda, new Transition.ChangeInfo(tda, false /* vis */, true /* exChg */));
+ changes.put(showing, new Transition.ChangeInfo(showing, false /* vis */, true /* exChg */));
+ changes.put(showing2,
+ new Transition.ChangeInfo(showing2, false /* vis */, true /* exChg */));
fillChangeMap(changes, tda);
// End states.
@@ -259,9 +265,9 @@
// Check promotion to DisplayArea
participants.add(showing);
participants.add(showing2);
- ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
- TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
- mMockT);
+ ArrayList<Transition.ChangeInfo> targets =
+ Transition.calculateTargets(participants, changes);
+ TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(1, info.getChanges().size());
assertEquals(transit, info.getType());
assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken()));
@@ -269,14 +275,14 @@
// Check that organized tasks get reported even if not top
makeTaskOrganized(showTask);
targets = Transition.calculateTargets(participants, changes);
- info = Transition.calculateTransitionInfo(transit, flags, targets, changes, mMockT);
+ info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken()));
assertNotNull(info.getChange(showTask.mRemoteToken.toWindowContainerToken()));
// Even if DisplayArea explicitly participating
participants.add(tda);
targets = Transition.calculateTargets(participants, changes);
- info = Transition.calculateTransitionInfo(transit, flags, targets, changes, mMockT);
+ info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
}
@@ -297,10 +303,9 @@
opening.setVisibleRequested(true);
closing.setVisibleRequested(false);
- ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
- TransitionInfo info = Transition.calculateTransitionInfo(
- 0, 0, targets, transition.mChanges, mMockT);
+ TransitionInfo info = Transition.calculateTransitionInfo(0, 0, targets, mMockT);
assertEquals(2, info.getChanges().size());
// There was an existence change on open, so it should be OPEN rather than SHOW
assertEquals(TRANSIT_OPEN,
@@ -334,10 +339,9 @@
tasks[i].getTopMostActivity().setVisibleRequested((i % 2) != 0);
}
- ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
- TransitionInfo info = Transition.calculateTransitionInfo(
- 0, 0, targets, transition.mChanges, mMockT);
+ TransitionInfo info = Transition.calculateTransitionInfo(0, 0, targets, mMockT);
assertEquals(taskCount, info.getChanges().size());
// verify order is top-to-bottem
for (int i = 0; i < taskCount; ++i) {
@@ -384,10 +388,9 @@
tasks[i].getTopMostActivity().setVisibleRequested((i % 2) != 0);
}
- ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
- TransitionInfo info = Transition.calculateTransitionInfo(
- 0, 0, targets, transition.mChanges, mMockT);
+ TransitionInfo info = Transition.calculateTransitionInfo(0, 0, targets, mMockT);
// verify that wallpaper is at bottom
assertEquals(taskCount + 1, info.getChanges().size());
// The wallpaper is not organized, so it won't have a token; however, it will be marked
@@ -410,11 +413,13 @@
final ActivityRecord hiding = createActivityRecord(topTask);
final ActivityRecord closing = createActivityRecord(topTask);
// Start states.
- changes.put(topTask, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
- changes.put(belowTask, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
- changes.put(showing, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
- changes.put(hiding, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
- changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
+ changes.put(topTask, new Transition.ChangeInfo(topTask, true /* vis */, false /* exChg */));
+ changes.put(belowTask,
+ new Transition.ChangeInfo(belowTask, false /* vis */, false /* exChg */));
+ changes.put(showing,
+ new Transition.ChangeInfo(showing, false /* vis */, false /* exChg */));
+ changes.put(hiding, new Transition.ChangeInfo(hiding, true /* vis */, false /* exChg */));
+ changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, true /* exChg */));
fillChangeMap(changes, topTask);
// End states.
showing.setVisibleRequested(true);
@@ -424,10 +429,11 @@
participants.add(belowTask);
participants.add(hiding);
participants.add(closing);
- ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
+ ArrayList<Transition.ChangeInfo> targets =
+ Transition.calculateTargets(participants, changes);
assertEquals(2, targets.size());
- assertTrue(targets.contains(belowTask));
- assertTrue(targets.contains(topTask));
+ assertTrue(Transition.containsChangeFor(belowTask, targets));
+ assertTrue(Transition.containsChangeFor(topTask, targets));
}
@Test
@@ -442,11 +448,14 @@
final ActivityRecord opening = createActivityRecord(topTask);
final ActivityRecord closing = createActivityRecord(belowTask);
// Start states.
- changes.put(topTask, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
- changes.put(belowTask, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
- changes.put(showing, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
- changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+ changes.put(topTask,
+ new Transition.ChangeInfo(topTask, false /* vis */, false /* exChg */));
+ changes.put(belowTask,
+ new Transition.ChangeInfo(belowTask, true /* vis */, false /* exChg */));
+ changes.put(showing,
+ new Transition.ChangeInfo(showing, false /* vis */, false /* exChg */));
+ changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
+ changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, false /* exChg */));
fillChangeMap(changes, topTask);
// End states.
showing.setVisibleRequested(true);
@@ -456,10 +465,11 @@
participants.add(belowTask);
participants.add(showing);
participants.add(opening);
- ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
+ ArrayList<Transition.ChangeInfo> targets =
+ Transition.calculateTargets(participants, changes);
assertEquals(2, targets.size());
- assertTrue(targets.contains(belowTask));
- assertTrue(targets.contains(topTask));
+ assertTrue(Transition.containsChangeFor(belowTask, targets));
+ assertTrue(Transition.containsChangeFor(topTask, targets));
}
@Test
@@ -473,10 +483,10 @@
final ActivityRecord closing = createActivityRecord(oldTask);
final ActivityRecord opening = createActivityRecord(newTask);
// Start states.
- changes.put(newTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(oldTask, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
- changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
+ changes.put(newTask, new Transition.ChangeInfo(newTask, false /* vis */, true /* exChg */));
+ changes.put(oldTask, new Transition.ChangeInfo(oldTask, true /* vis */, true /* exChg */));
+ changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
+ changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, true /* exChg */));
transition.setNoAnimation(opening);
fillChangeMap(changes, newTask);
// End states.
@@ -491,19 +501,20 @@
participants.add(newTask);
participants.add(opening);
participants.add(closing);
- ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
- TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
- mMockT);
+ ArrayList<Transition.ChangeInfo> targets =
+ Transition.calculateTargets(participants, changes);
+ TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
assertTrue(info.getChange(newTask.mRemoteToken.toWindowContainerToken())
.hasFlags(TransitionInfo.FLAG_NO_ANIMATION));
// Check that no-animation flag is NOT promoted if at-least on child *is* animated
final ActivityRecord opening2 = createActivityRecord(newTask);
- changes.put(opening2, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
+ changes.put(opening2,
+ new Transition.ChangeInfo(opening2, false /* vis */, true /* exChg */));
participants.add(opening2);
targets = Transition.calculateTargets(participants, changes);
- info = Transition.calculateTransitionInfo(transit, flags, targets, changes, mMockT);
+ info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
assertFalse(info.getChange(newTask.mRemoteToken.toWindowContainerToken())
.hasFlags(TransitionInfo.FLAG_NO_ANIMATION));
@@ -531,10 +542,9 @@
mDisplayContent.getWindowConfiguration().setRotation(
(mDisplayContent.getWindowConfiguration().getRotation() + 1) % 4);
- ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
- TransitionInfo info = Transition.calculateTransitionInfo(
- 0, 0, targets, transition.mChanges, mMockT);
+ TransitionInfo info = Transition.calculateTransitionInfo(0, 0, targets, mMockT);
// The wallpaper is not organized, so it won't have a token; however, it will be marked
// as IS_WALLPAPER
assertEquals(FLAG_IS_WALLPAPER, info.getChanges().get(0).getFlags());
@@ -596,10 +606,12 @@
wc.getWindowConfiguration().setRotation(newRotation);
}
- final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
// Especially the activities must be in the targets.
- assertTrue(targets.containsAll(Arrays.asList(wcs)));
+ for (WindowContainer<?> wc : wcs) {
+ assertTrue(Transition.containsChangeFor(wc, targets));
+ }
}
@Test
@@ -622,15 +634,22 @@
openInChangeTask);
// Start states.
- changes.put(openTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(changeTask, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
- changes.put(openInOpenTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(openInChangeTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
+ changes.put(openTask,
+ new Transition.ChangeInfo(openTask, false /* vis */, true /* exChg */));
+ changes.put(changeTask,
+ new Transition.ChangeInfo(changeTask, true /* vis */, false /* exChg */));
+ changes.put(openInOpenTask,
+ new Transition.ChangeInfo(openInOpenTask, false /* vis */, true /* exChg */));
+ changes.put(openInChangeTask,
+ new Transition.ChangeInfo(openInChangeTask, false /* vis */, true /* exChg */));
changes.put(changeInChangeTask,
- new Transition.ChangeInfo(true /* vis */, false /* exChg */));
- changes.put(openInOpen, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(openInChange, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(changeInChange, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+ new Transition.ChangeInfo(changeInChangeTask, true /* vis */, false /* exChg */));
+ changes.put(openInOpen,
+ new Transition.ChangeInfo(openInOpen, false /* vis */, true /* exChg */));
+ changes.put(openInChange,
+ new Transition.ChangeInfo(openInChange, false /* vis */, true /* exChg */));
+ changes.put(changeInChange,
+ new Transition.ChangeInfo(changeInChange, true /* vis */, false /* exChg */));
fillChangeMap(changes, openTask);
// End states.
changeInChange.setVisibleRequested(true);
@@ -646,10 +665,9 @@
participants.add(openInChange);
// Explicitly add changeTask (to test independence with parents)
participants.add(changeTask);
- final ArrayList<WindowContainer> targets =
+ final ArrayList<Transition.ChangeInfo> targets =
Transition.calculateTargets(participants, changes);
- TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
- mMockT);
+ TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
// Root changes should always be considered independent
assertTrue(isIndependent(
info.getChange(openTask.mRemoteToken.toWindowContainerToken()), info));
@@ -685,10 +703,10 @@
final ActivityRecord opening = createActivityRecord(newTask);
opening.setOccludesParent(false);
// Start states.
- changes.put(newTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(oldTask, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
- changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+ changes.put(newTask, new Transition.ChangeInfo(newTask, false /* vis */, true /* exChg */));
+ changes.put(oldTask, new Transition.ChangeInfo(oldTask, true /* vis */, false /* exChg */));
+ changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
+ changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, false /* exChg */));
fillChangeMap(changes, newTask);
// End states.
closing.setVisibleRequested(true);
@@ -700,9 +718,9 @@
// Check basic both tasks participating
participants.add(oldTask);
participants.add(newTask);
- ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
- TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
- mMockT);
+ ArrayList<Transition.ChangeInfo> targets =
+ Transition.calculateTargets(participants, changes);
+ TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
@@ -726,10 +744,10 @@
final ActivityRecord opening = createActivityRecord(newTask);
opening.setOccludesParent(false);
// Start states.
- changes.put(newTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(oldTask, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
- changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+ changes.put(newTask, new Transition.ChangeInfo(newTask, false /* vis */, true /* exChg */));
+ changes.put(oldTask, new Transition.ChangeInfo(oldTask, true /* vis */, false /* exChg */));
+ changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
+ changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, false /* exChg */));
fillChangeMap(changes, newTask);
// End states.
closing.setVisibleRequested(true);
@@ -741,9 +759,9 @@
// Check basic both tasks participating
participants.add(oldTask);
participants.add(newTask);
- ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
- TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
- mMockT);
+ ArrayList<Transition.ChangeInfo> targets =
+ Transition.calculateTargets(participants, changes);
+ TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
@@ -785,7 +803,7 @@
final int flags = 0;
final TransitionInfo info = Transition.calculateTransitionInfo(transition.mType, flags,
Transition.calculateTargets(transition.mParticipants, transition.mChanges),
- transition.mChanges, mMockT);
+ mMockT);
transition.abort();
return info.getChanges().get(0);
};
@@ -1141,7 +1159,7 @@
// normally.
mWm.mSyncEngine.abort(openTransition.getSyncId());
- verify(snapshotController, times(1)).recordTaskSnapshot(eq(task2), eq(false));
+ verify(snapshotController, times(1)).recordSnapshot(eq(task2), eq(false));
openTransition.finishTransition();
@@ -1165,12 +1183,12 @@
// Make sure we haven't called recordSnapshot (since we are transient, it shouldn't be
// called until finish).
- verify(snapshotController, times(0)).recordTaskSnapshot(eq(task1), eq(false));
+ verify(snapshotController, times(0)).recordSnapshot(eq(task1), eq(false));
enteringAnimReports.clear();
closeTransition.finishTransition();
- verify(snapshotController, times(1)).recordTaskSnapshot(eq(task1), eq(false));
+ verify(snapshotController, times(1)).recordSnapshot(eq(task1), eq(false));
assertTrue(enteringAnimReports.contains(activity2));
}
@@ -1210,18 +1228,20 @@
doReturn(true).when(activity1).hasStartingWindow();
// Start states.
- changes.put(activity0, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
- changes.put(activity1, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
+ changes.put(activity0,
+ new Transition.ChangeInfo(activity0, true /* vis */, false /* exChg */));
+ changes.put(activity1,
+ new Transition.ChangeInfo(activity1, false /* vis */, false /* exChg */));
// End states.
activity0.setVisibleRequested(false);
activity1.setVisibleRequested(true);
participants.add(activity0);
participants.add(activity1);
- final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(
- transition.mType, 0 /* flags */, targets, changes, mMockT);
+ transition.mType, 0 /* flags */, targets, mMockT);
// All windows in the Task should have FLAG_IS_BEHIND_STARTING_WINDOW because the starting
// window should cover the whole Task.
@@ -1251,11 +1271,14 @@
final ActivityRecord closingActivity = embeddedTf.getBottomMostActivity();
final ActivityRecord openingActivity = embeddedTf.getTopMostActivity();
// Start states.
- changes.put(embeddedTf, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
- changes.put(closingActivity, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
- changes.put(openingActivity, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(true /* vis */,
- false /* exChg */));
+ changes.put(embeddedTf,
+ new Transition.ChangeInfo(embeddedTf, true /* vis */, false /* exChg */));
+ changes.put(closingActivity,
+ new Transition.ChangeInfo(closingActivity, true /* vis */, false /* exChg */));
+ changes.put(openingActivity,
+ new Transition.ChangeInfo(openingActivity, false /* vis */, true /* exChg */));
+ changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(nonEmbeddedActivity,
+ true /* vis */, false /* exChg */));
// End states.
closingActivity.setVisibleRequested(false);
openingActivity.setVisibleRequested(true);
@@ -1264,10 +1287,10 @@
participants.add(closingActivity);
participants.add(openingActivity);
participants.add(nonEmbeddedActivity);
- final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(
- transition.mType, 0 /* flags */, targets, changes, mMockT);
+ transition.mType, 0 /* flags */, targets, mMockT);
// All windows in the Task should have FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY because the Task
// contains embedded activity.
@@ -1297,10 +1320,11 @@
.build();
final ActivityRecord embeddedActivity = embeddedTf.getTopMostActivity();
// Start states.
- changes.put(task, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
- changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(true /* vis */,
- false /* exChg */));
- changes.put(embeddedTf, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
+ changes.put(task, new Transition.ChangeInfo(task, true /* vis */, false /* exChg */));
+ changes.put(nonEmbeddedActivity,
+ new Transition.ChangeInfo(nonEmbeddedActivity, true /* vis */, false /* exChg */));
+ changes.put(embeddedTf,
+ new Transition.ChangeInfo(embeddedTf, false /* vis */, true /* exChg */));
// End states.
nonEmbeddedActivity.setVisibleRequested(false);
embeddedActivity.setVisibleRequested(true);
@@ -1308,10 +1332,10 @@
participants.add(nonEmbeddedActivity);
participants.add(embeddedTf);
- final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(
- transition.mType, 0 /* flags */, targets, changes, mMockT);
+ transition.mType, 0 /* flags */, targets, mMockT);
// The embedded with bounds overridden should not have the flag.
assertEquals(2, info.getChanges().size());
@@ -1339,10 +1363,10 @@
activity.setVisibleRequested(true);
participants.add(activity);
- final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(
- transition.mType, 0 /* flags */, targets, changes, mMockT);
+ transition.mType, 0 /* flags */, targets, mMockT);
// Opening activity that is filling Task after transition should have the flag.
assertEquals(1, info.getChanges().size());
@@ -1367,10 +1391,10 @@
activity.setVisibleRequested(false);
participants.add(activity);
- final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(
- transition.mType, 0 /* flags */, targets, changes, mMockT);
+ transition.mType, 0 /* flags */, targets, mMockT);
// Closing activity that is filling Task before transition should have the flag.
assertEquals(1, info.getChanges().size());
@@ -1395,10 +1419,10 @@
activity.setVisibleRequested(false);
participants.add(activity);
- final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(
- transition.mType, 0 /* flags */, targets, changes, mMockT);
+ transition.mType, 0 /* flags */, targets, mMockT);
// Change contains last parent info.
assertEquals(1, info.getChanges().size());
@@ -1433,10 +1457,10 @@
activity.reparent(embeddedTf, POSITION_TOP);
// Verify that both activity and TaskFragment are included.
- final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
- assertTrue(targets.contains(embeddedTf));
- assertTrue(targets.contains(activity));
+ assertTrue(Transition.containsChangeFor(embeddedTf, targets));
+ assertTrue(Transition.containsChangeFor(activity, targets));
}
@Test
@@ -1470,10 +1494,10 @@
participants.add(embeddedTf);
participants.add(nonEmbeddedActivity);
- final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(transition.mType,
- 0 /* flags */, targets, changes, mMockT);
+ 0 /* flags */, targets, mMockT);
// Background color should be set on both Activity and embedded TaskFragment.
final int expectedBackgroundColor = ColorUtils.setAlphaComponent(
@@ -1560,10 +1584,10 @@
c.windowConfiguration.setBounds(bounds);
task.onRequestedOverrideConfigurationChanged(c);
- ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
TransitionInfo info = Transition.calculateTransitionInfo(
- TRANSIT_CHANGE, 0, targets, transition.mChanges, mMockT);
+ TRANSIT_CHANGE, 0, targets, mMockT);
assertEquals(mockSnapshot,
info.getChange(task.mRemoteToken.toWindowContainerToken()).getSnapshot());
transition.abort();
@@ -1617,7 +1641,7 @@
private static void fillChangeMap(ArrayMap<WindowContainer, Transition.ChangeInfo> changes,
WindowContainer top) {
for (WindowContainer curr = top.getParent(); curr != null; curr = curr.getParent()) {
- changes.put(curr, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+ changes.put(curr, new Transition.ChangeInfo(curr, true /* vis */, false /* exChg */));
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
index 383722a..19da718 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
@@ -18,6 +18,7 @@
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
@@ -44,9 +45,9 @@
@RunWith(WindowTestRunner.class)
public class WindowContainerInsetsSourceProviderTest extends WindowTestsBase {
- private InsetsSource mSource = new InsetsSource(ITYPE_STATUS_BAR);
+ private InsetsSource mSource = new InsetsSource(ITYPE_STATUS_BAR, statusBars());
private WindowContainerInsetsSourceProvider mProvider;
- private InsetsSource mImeSource = new InsetsSource(ITYPE_IME);
+ private InsetsSource mImeSource = new InsetsSource(ITYPE_IME, ime());
private WindowContainerInsetsSourceProvider mImeProvider;
@Before
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index ebf3166..d583e89 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -24,6 +24,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.view.InsetsState.ITYPE_BOTTOM_GENERIC_OVERLAY;
import static android.view.InsetsState.ITYPE_TOP_GENERIC_OVERLAY;
+import static android.view.WindowInsets.Type.systemOverlays;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -1336,11 +1337,11 @@
new int[]{ITYPE_BOTTOM_GENERIC_OVERLAY});
InsetsSource genericOverlayInsetsProvider1Source = new InsetsSource(
- ITYPE_TOP_GENERIC_OVERLAY);
+ ITYPE_TOP_GENERIC_OVERLAY, systemOverlays());
genericOverlayInsetsProvider1Source.setFrame(genericOverlayInsetsRect1);
genericOverlayInsetsProvider1Source.setVisible(true);
InsetsSource genericOverlayInsetsProvider2Source = new InsetsSource(
- ITYPE_BOTTOM_GENERIC_OVERLAY);
+ ITYPE_BOTTOM_GENERIC_OVERLAY, systemOverlays());
genericOverlayInsetsProvider2Source.setFrame(genericOverlayInsetsRect2);
genericOverlayInsetsProvider2Source.setVisible(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index e5e9f54..f24318b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -802,7 +802,7 @@
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSourceProviders
- .valueAt(0).getSource().getType()).isEqualTo(ITYPE_TOP_GENERIC_OVERLAY);
+ .valueAt(0).getSource().getId()).isEqualTo(ITYPE_TOP_GENERIC_OVERLAY);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 69e3244..99d1772 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -27,6 +27,7 @@
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -1017,7 +1018,7 @@
@SetupWindows(addWindows = { W_INPUT_METHOD, W_ACTIVITY })
@Test
public void testImeAlwaysReceivesVisibleNavigationBarInsets() {
- final InsetsSource navSource = new InsetsSource(ITYPE_NAVIGATION_BAR);
+ final InsetsSource navSource = new InsetsSource(ITYPE_NAVIGATION_BAR, navigationBars());
mImeWindow.mAboveInsetsState.addSource(navSource);
mAppWindow.mAboveInsetsState.addSource(navSource);
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index 4bb9de5..1399458 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -682,7 +682,10 @@
mHandler.sendMessage(message);
}
- public void addSimulatedPort(String portId, int supportedModes, IndentingPrintWriter pw) {
+ public void addSimulatedPort(String portId, int supportedModes,
+ boolean supportsComplianceWarnings,
+ IndentingPrintWriter pw) {
+
synchronized (mLock) {
if (mSimulatedPorts.containsKey(portId)) {
pw.println("Port with same name already exists. Please remove it first.");
@@ -692,7 +695,25 @@
pw.println("Adding simulated port: portId=" + portId
+ ", supportedModes=" + UsbPort.modeToString(supportedModes));
mSimulatedPorts.put(portId,
- new RawPortInfo(portId, supportedModes));
+ new RawPortInfo(
+ portId,
+ supportedModes,
+ UsbPortStatus.CONTAMINANT_PROTECTION_NONE,
+ UsbPortStatus.MODE_NONE,
+ false,
+ UsbPortStatus.POWER_ROLE_NONE,
+ false,
+ UsbPortStatus.DATA_ROLE_NONE,
+ false,
+ false,
+ UsbPortStatus.CONTAMINANT_PROTECTION_NONE,
+ false,
+ UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED,
+ UsbPortStatus.DATA_STATUS_UNKNOWN,
+ false,
+ UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN,
+ supportsComplianceWarnings,
+ new int[] {}));
updatePortsLocked(pw, null);
}
}
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index d09f729..6eb04d9 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -987,9 +987,12 @@
mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")),
"", 0);
}
- } else if ("add-port".equals(args[0]) && args.length == 3) {
+ } else if ("add-port".equals(args[0]) && args.length >= 3) {
final String portId = args[1];
final int supportedModes;
+
+ int i;
+ boolean supportsComplianceWarnings = false;
switch (args[2]) {
case "ufp":
supportedModes = MODE_UFP;
@@ -1007,8 +1010,19 @@
pw.println("Invalid mode: " + args[2]);
return;
}
+ for (i=3; i<args.length; i++) {
+ switch (args[i]) {
+ case "--compliance-warnings":
+ supportsComplianceWarnings = true;
+ continue;
+ default:
+ pw.println("Invalid Identifier: " + args[i]);
+ }
+ }
if (mPortManager != null) {
- mPortManager.addSimulatedPort(portId, supportedModes, pw);
+ mPortManager.addSimulatedPort(portId, supportedModes,
+ supportsComplianceWarnings,
+ pw);
pw.println();
mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")),
"", 0);
@@ -1121,7 +1135,9 @@
pw.println("Dump current USB state or issue command:");
pw.println(" ports");
pw.println(" set-port-roles <id> <source|sink|no-power> <host|device|no-data>");
- pw.println(" add-port <id> <ufp|dfp|dual|none>");
+ pw.println(" add-port <id> <ufp|dfp|dual|none> <optional args>");
+ pw.println(" <optional args> include:");
+ pw.println(" --compliance-warnings: enables compliance warnings on port");
pw.println(" connect-port <id> <ufp|dfp><?> <source|sink><?> <host|device><?>");
pw.println(" (add ? suffix if mode, power role, or data role can be changed)");
pw.println(" disconnect-port <id>");
@@ -1132,7 +1148,7 @@
pw.println(" dumpsys usb set-port-roles \"default\" source device");
pw.println();
pw.println("Example USB type C port simulation with full capabilities:");
- pw.println(" dumpsys usb add-port \"matrix\" dual");
+ pw.println(" dumpsys usb add-port \"matrix\" dual --compliance-warnings");
pw.println(" dumpsys usb connect-port \"matrix\" ufp? sink? device?");
pw.println(" dumpsys usb ports");
pw.println(" dumpsys usb disconnect-port \"matrix\"");
@@ -1160,15 +1176,15 @@
pw.println(" dumpsys usb set-contaminant-status \"matrix\" false");
pw.println();
pw.println("Example simulate compliance warnings:");
- pw.println(" dumpsys usb add-port \"matrix\" dual");
+ pw.println(" dumpsys usb add-port \"matrix\" dual --compliance-warnings");
pw.println(" dumpsys usb set-compliance-reasons \"matrix\" <reason-list>");
pw.println(" dumpsys usb clear-compliance-reasons \"matrix\"");
pw.println("<reason-list> is expected to be formatted as \"1, ..., 4\"");
pw.println("with reasons that need to be simulated.");
- pw.println(" 1: debug accessory");
- pw.println(" 2: bc12");
- pw.println(" 3: missing rp");
- pw.println(" 4: type c");
+ pw.println(" 1: other");
+ pw.println(" 2: debug accessory");
+ pw.println(" 3: bc12");
+ pw.println(" 4: missing rp");
pw.println();
pw.println("Example USB device descriptors:");
pw.println(" dumpsys usb dump-descriptors -dump-short");
diff --git a/telecomm/java/android/telecom/CallEndpoint.aidl b/telecomm/java/android/telecom/CallEndpoint.aidl
new file mode 100644
index 0000000..45b2249
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpoint.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable CallEndpoint;
\ No newline at end of file
diff --git a/telecomm/java/android/telecom/CallEndpoint.java b/telecomm/java/android/telecom/CallEndpoint.java
new file mode 100644
index 0000000..0b2211d
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpoint.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telecom;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * Encapsulates the endpoint where call media can flow
+ */
+public final class CallEndpoint implements Parcelable {
+ /** @hide */
+ public static final int ENDPOINT_OPERATION_SUCCESS = 0;
+ /** @hide */
+ public static final int ENDPOINT_OPERATION_FAILED = 1;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({TYPE_UNKNOWN, TYPE_EARPIECE, TYPE_BLUETOOTH, TYPE_WIRED_HEADSET, TYPE_SPEAKER,
+ TYPE_STREAMING})
+ public @interface EndpointType {}
+
+ /** Indicates that the type of endpoint through which call media flows is unknown type. */
+ public static final int TYPE_UNKNOWN = -1;
+
+ /** Indicates that the type of endpoint through which call media flows is an earpiece. */
+ public static final int TYPE_EARPIECE = 1;
+
+ /** Indicates that the type of endpoint through which call media flows is a Bluetooth. */
+ public static final int TYPE_BLUETOOTH = 2;
+
+ /** Indicates that the type of endpoint through which call media flows is a wired headset. */
+ public static final int TYPE_WIRED_HEADSET = 3;
+
+ /** Indicates that the type of endpoint through which call media flows is a speakerphone. */
+ public static final int TYPE_SPEAKER = 4;
+
+ /** Indicates that the type of endpoint through which call media flows is an external. */
+ public static final int TYPE_STREAMING = 5;
+
+ private final CharSequence mName;
+ private final int mType;
+ private final ParcelUuid mIdentifier;
+
+ /**
+ * Constructor for a {@link CallEndpoint} object.
+ *
+ * @param name Human-readable name associated with the endpoint
+ * @param type The type of endpoint through which call media being routed
+ * Allowed values:
+ * {@link #TYPE_EARPIECE}
+ * {@link #TYPE_BLUETOOTH}
+ * {@link #TYPE_WIRED_HEADSET}
+ * {@link #TYPE_SPEAKER}
+ * {@link #TYPE_STREAMING}
+ * {@link #TYPE_UNKNOWN}
+ * @param id A unique identifier for this endpoint on the device
+ */
+ public CallEndpoint(@NonNull CharSequence name, @EndpointType int type,
+ @NonNull ParcelUuid id) {
+ this.mName = name;
+ this.mType = type;
+ this.mIdentifier = id;
+ }
+
+ /** @hide */
+ public CallEndpoint(@NonNull CharSequence name, @EndpointType int type) {
+ this(name, type, new ParcelUuid(UUID.randomUUID()));
+ }
+
+ /** @hide */
+ public CallEndpoint(CallEndpoint endpoint) {
+ mName = endpoint.getEndpointName();
+ mType = endpoint.getEndpointType();
+ mIdentifier = endpoint.getIdentifier();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof CallEndpoint)) {
+ return false;
+ }
+ CallEndpoint endpoint = (CallEndpoint) obj;
+ return getEndpointName().toString().contentEquals(endpoint.getEndpointName())
+ && getEndpointType() == endpoint.getEndpointType()
+ && getIdentifier().equals(endpoint.getIdentifier());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mName, mType, mIdentifier);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return TextUtils.formatSimple("[CallEndpoint Name: %s, Type: %s, Identifier: %s]",
+ mName.toString(), endpointTypeToString(mType), mIdentifier.toString());
+ }
+
+ /**
+ * @return Human-readable name associated with the endpoint
+ */
+ @NonNull
+ public CharSequence getEndpointName() {
+ return mName;
+ }
+
+ /**
+ * @return The type of endpoint through which call media being routed
+ */
+ @EndpointType
+ public int getEndpointType() {
+ return mType;
+ }
+
+ /**
+ * @return A unique identifier for this endpoint on the device
+ */
+ @NonNull
+ public ParcelUuid getIdentifier() {
+ return mIdentifier;
+ }
+
+ /**
+ * Converts the provided endpoint type into a human-readable string representation.
+ *
+ * @param endpointType to convert into a string.
+ * @return String representation of the provided endpoint type.
+ * @hide
+ */
+ @NonNull
+ public static String endpointTypeToString(int endpointType) {
+ switch (endpointType) {
+ case TYPE_EARPIECE:
+ return "EARPIECE";
+ case TYPE_BLUETOOTH:
+ return "BLUETOOTH";
+ case TYPE_WIRED_HEADSET:
+ return "WIRED_HEADSET";
+ case TYPE_SPEAKER:
+ return "SPEAKER";
+ case TYPE_STREAMING:
+ return "EXTERNAL";
+ default:
+ return "UNKNOWN (" + endpointType + ")";
+ }
+ }
+
+ /**
+ * Responsible for creating CallEndpoint objects for deserialized Parcels.
+ */
+ public static final @android.annotation.NonNull Parcelable.Creator<CallEndpoint> CREATOR =
+ new Parcelable.Creator<CallEndpoint>() {
+
+ @Override
+ public CallEndpoint createFromParcel(Parcel source) {
+ CharSequence name = source.readCharSequence();
+ int type = source.readInt();
+ ParcelUuid id = ParcelUuid.CREATOR.createFromParcel(source);
+
+ return new CallEndpoint(name, type, id);
+ }
+
+ @Override
+ public CallEndpoint[] newArray(int size) {
+ return new CallEndpoint[size];
+ }
+ };
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void writeToParcel(@NonNull Parcel destination, int flags) {
+ destination.writeCharSequence(mName);
+ destination.writeInt(mType);
+ mIdentifier.writeToParcel(destination, flags);
+ }
+}
diff --git a/telecomm/java/android/telecom/CallEndpointException.aidl b/telecomm/java/android/telecom/CallEndpointException.aidl
new file mode 100644
index 0000000..19b43c4b4
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpointException.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable CallEndpointException;
\ No newline at end of file
diff --git a/telecomm/java/android/telecom/CallEndpointException.java b/telecomm/java/android/telecom/CallEndpointException.java
new file mode 100644
index 0000000..e2238928
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpointException.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telecom;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class represents a set of exceptions that can occur when requesting a
+ * {@link CallEndpoint} change.
+ */
+public final class CallEndpointException extends RuntimeException implements Parcelable {
+ /** @hide */
+ public static final String CHANGE_ERROR = "ChangeErrorKey";
+
+ /**
+ * The operation has failed because requested CallEndpoint does not exist.
+ */
+ public static final int ERROR_ENDPOINT_DOES_NOT_EXIST = 1;
+
+ /**
+ * The operation was not completed on time.
+ */
+ public static final int ERROR_REQUEST_TIME_OUT = 2;
+
+ /**
+ * The operation was canceled by another request.
+ */
+ public static final int ERROR_ANOTHER_REQUEST = 3;
+
+ /**
+ * The operation has failed due to an unknown or unspecified error.
+ */
+ public static final int ERROR_UNSPECIFIED = 4;
+
+ private int mCode = ERROR_UNSPECIFIED;
+ private final String mMessage;
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(mMessage);
+ dest.writeInt(mCode);
+ }
+
+ /**
+ * Responsible for creating CallEndpointException objects for deserialized Parcels.
+ */
+ public static final @android.annotation.NonNull Parcelable.Creator<CallEndpointException>
+ CREATOR = new Parcelable.Creator<>() {
+ @Override
+ public CallEndpointException createFromParcel(Parcel source) {
+ return new CallEndpointException(source.readString8(), source.readInt());
+ }
+
+ @Override
+ public CallEndpointException[] newArray(int size) {
+ return new CallEndpointException[size];
+ }
+ };
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ERROR_ENDPOINT_DOES_NOT_EXIST, ERROR_REQUEST_TIME_OUT, ERROR_ANOTHER_REQUEST,
+ ERROR_UNSPECIFIED})
+ public @interface CallEndpointErrorCode {
+ }
+
+ public CallEndpointException(@Nullable String message) {
+ super(getMessage(message, ERROR_UNSPECIFIED));
+ mMessage = message;
+ }
+
+ public CallEndpointException(@Nullable String message, @CallEndpointErrorCode int code) {
+ super(getMessage(message, code));
+ mCode = code;
+ mMessage = message;
+ }
+
+ public CallEndpointException(@Nullable String message, @CallEndpointErrorCode int code,
+ @Nullable Throwable cause) {
+ super(getMessage(message, code), cause);
+ mCode = code;
+ mMessage = message;
+ }
+
+
+ public @CallEndpointErrorCode int getCode() {
+ return mCode;
+ }
+
+ private static String getMessage(String message, int code) {
+ StringBuilder builder;
+ if (!TextUtils.isEmpty(message)) {
+ builder = new StringBuilder(message);
+ builder.append(" (code: ");
+ builder.append(code);
+ builder.append(")");
+ return builder.toString();
+ } else {
+ return "code: " + code;
+ }
+ }
+}
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index f84dd7b..f803717 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -88,6 +88,7 @@
private String mTelecomCallId;
private PhoneAccountHandle mPhoneAccount;
private CallAudioState mCallAudioState;
+ private CallEndpoint mCallEndpoint;
private int mState = Connection.STATE_NEW;
private DisconnectCause mDisconnectCause;
private int mConnectionCapabilities;
@@ -223,12 +224,26 @@
* @return The audio state of the conference, describing how its audio is currently
* being routed by the system. This is {@code null} if this Conference
* does not directly know about its audio state.
+ * @deprecated Use {@link #getCurrentCallEndpoint()},
+ * {@link #onAvailableCallEndpointsChanged(List)} and
+ * {@link #onMuteStateChanged(boolean)} instead.
*/
+ @Deprecated
public final CallAudioState getCallAudioState() {
return mCallAudioState;
}
/**
+ * Obtains the current CallEndpoint.
+ *
+ * @return An object encapsulating the CallEndpoint.
+ */
+ @NonNull
+ public final CallEndpoint getCurrentCallEndpoint() {
+ return mCallEndpoint;
+ }
+
+ /**
* Returns VideoProvider of the primary call. This can be null.
*/
public VideoProvider getVideoProvider() {
@@ -314,10 +329,35 @@
* value.
*
* @param state The new call audio state.
+ * @deprecated Use {@link #onCallEndpointChanged(CallEndpoint)},
+ * {@link #onAvailableCallEndpointsChanged(List)} and
+ * {@link #onMuteStateChanged(boolean)} instead.
*/
+ @Deprecated
public void onCallAudioStateChanged(CallAudioState state) {}
/**
+ * Notifies the {@link Conference} that the audio endpoint has been changed.
+ *
+ * @param callEndpoint The new call endpoint.
+ */
+ public void onCallEndpointChanged(@NonNull CallEndpoint callEndpoint) {}
+
+ /**
+ * Notifies the {@link Conference} that the available call endpoints have been changed.
+ *
+ * @param availableEndpoints The available call endpoints.
+ */
+ public void onAvailableCallEndpointsChanged(@NonNull List<CallEndpoint> availableEndpoints) {}
+
+ /**
+ * Notifies the {@link Conference} that its audio mute state has been changed.
+ *
+ * @param isMuted The new mute state.
+ */
+ public void onMuteStateChanged(boolean isMuted) {}
+
+ /**
* Notifies the {@link Conference} that a {@link Connection} has been added to it.
*
* @param connection The newly added connection.
@@ -730,6 +770,40 @@
onCallAudioStateChanged(state);
}
+ /**
+ * Inform this Conference that the audio endpoint has been changed.
+ *
+ * @param endpoint The new call endpoint.
+ * @hide
+ */
+ final void setCallEndpoint(CallEndpoint endpoint) {
+ Log.d(this, "setCallEndpoint %s", endpoint);
+ mCallEndpoint = endpoint;
+ onCallEndpointChanged(endpoint);
+ }
+
+ /**
+ * Inform this Conference that the available call endpoints have been changed.
+ *
+ * @param availableEndpoints The available call endpoints.
+ * @hide
+ */
+ final void setAvailableCallEndpoints(List<CallEndpoint> availableEndpoints) {
+ Log.d(this, "setAvailableCallEndpoints");
+ onAvailableCallEndpointsChanged(availableEndpoints);
+ }
+
+ /**
+ * Inform this Conference that its audio mute state has been changed.
+ *
+ * @param isMuted The new mute state.
+ * @hide
+ */
+ final void setMuteState(boolean isMuted) {
+ Log.d(this, "setMuteState %s", isMuted);
+ onMuteStateChanged(isMuted);
+ }
+
private void setState(int newState) {
if (mState != newState) {
int oldState = mState;
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 568c8ab..4656226 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.MODIFY_PHONE_STATE;
import android.Manifest;
+import android.annotation.CallbackExecutor;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
import android.annotation.IntRange;
@@ -39,6 +40,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.OutcomeReceiver;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
@@ -69,6 +71,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
/**
* Represents a phone call or connection to a remote endpoint that carries voice and/or video
@@ -1280,6 +1283,8 @@
/** @hide */
public void onPhoneAccountChanged(Connection c, PhoneAccountHandle pHandle) {}
public void onConnectionTimeReset(Connection c) {}
+ public void onEndpointChanged(Connection c, CallEndpoint endpoint, Executor executor,
+ OutcomeReceiver<Void, CallEndpointException> callback) {}
}
/**
@@ -2162,6 +2167,7 @@
private PhoneAccountHandle mPhoneAccountHandle;
private int mState = STATE_NEW;
private CallAudioState mCallAudioState;
+ private CallEndpoint mCallEndpoint;
private Uri mAddress;
private int mAddressPresentation;
private String mCallerDisplayName;
@@ -2290,7 +2296,11 @@
* @return The audio state of the connection, describing how its audio is currently
* being routed by the system. This is {@code null} if this Connection
* does not directly know about its audio state.
+ * @deprecated Use {@link #getCurrentCallEndpoint()},
+ * {@link #onAvailableCallEndpointsChanged(List)} and
+ * {@link #onMuteStateChanged(boolean)} instead.
*/
+ @Deprecated
public final CallAudioState getCallAudioState() {
return mCallAudioState;
}
@@ -2457,6 +2467,43 @@
}
/**
+ * Inform this Connection that the audio endpoint has been changed.
+ *
+ * @param endpoint The new call endpoint.
+ * @hide
+ */
+ final void setCallEndpoint(CallEndpoint endpoint) {
+ checkImmutable();
+ Log.d(this, "setCallEndpoint %s", endpoint);
+ mCallEndpoint = endpoint;
+ onCallEndpointChanged(endpoint);
+ }
+
+ /**
+ * Inform this Connection that the available call endpoints have been changed.
+ *
+ * @param availableEndpoints The available call endpoints.
+ * @hide
+ */
+ final void setAvailableCallEndpoints(List<CallEndpoint> availableEndpoints) {
+ checkImmutable();
+ Log.d(this, "setAvailableCallEndpoints");
+ onAvailableCallEndpointsChanged(availableEndpoints);
+ }
+
+ /**
+ * Inform this Connection that its audio mute state has been changed.
+ *
+ * @param isMuted The new mute state.
+ * @hide
+ */
+ final void setMuteState(boolean isMuted) {
+ checkImmutable();
+ Log.d(this, "setMuteState %s", isMuted);
+ onMuteStateChanged(isMuted);
+ }
+
+ /**
* @param state An integer value of a {@code STATE_*} constant.
* @return A string representation of the value.
*/
@@ -3081,7 +3128,10 @@
* @param route The audio route to use (one of {@link CallAudioState#ROUTE_BLUETOOTH},
* {@link CallAudioState#ROUTE_EARPIECE}, {@link CallAudioState#ROUTE_SPEAKER}, or
* {@link CallAudioState#ROUTE_WIRED_HEADSET}).
+ * @deprecated Use {@link #requestCallEndpointChange(CallEndpoint, Executor, OutcomeReceiver)}
+ * instead.
*/
+ @Deprecated
public final void setAudioRoute(int route) {
for (Listener l : mListeners) {
l.onAudioRouteChanged(this, route, null);
@@ -3101,7 +3151,10 @@
* <p>
* See also {@link InCallService#requestBluetoothAudio(BluetoothDevice)}
* @param bluetoothDevice The bluetooth device to connect to.
+ * @deprecated Use {@link #requestCallEndpointChange(CallEndpoint, Executor, OutcomeReceiver)}
+ * instead.
*/
+ @Deprecated
public void requestBluetoothAudio(@NonNull BluetoothDevice bluetoothDevice) {
for (Listener l : mListeners) {
l.onAudioRouteChanged(this, CallAudioState.ROUTE_BLUETOOTH,
@@ -3110,6 +3163,40 @@
}
/**
+ * Request audio routing to a specific CallEndpoint. Clients should not define their own
+ * CallEndpoint when requesting a change. Instead, the new endpoint should be one of the valid
+ * endpoints provided by {@link #onAvailableCallEndpointsChanged(List)}.
+ * When this request is honored, there will be change to the {@link #getCurrentCallEndpoint()}.
+ * <p>
+ * Used by self-managed {@link ConnectionService}s which wish to change the CallEndpoint for a
+ * self-managed {@link Connection} (see {@link PhoneAccount#CAPABILITY_SELF_MANAGED}.)
+ * <p>
+ * See also
+ * {@link InCallService#requestCallEndpointChange(CallEndpoint, Executor, OutcomeReceiver)}.
+ *
+ * @param endpoint The call endpoint to use.
+ * @param executor The executor of where the callback will execute.
+ * @param callback The callback to notify the result of the endpoint change.
+ */
+ public final void requestCallEndpointChange(@NonNull CallEndpoint endpoint,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Void, CallEndpointException> callback) {
+ for (Listener l : mListeners) {
+ l.onEndpointChanged(this, endpoint, executor, callback);
+ }
+ }
+
+ /**
+ * Obtains the current CallEndpoint.
+ *
+ * @return An object encapsulating the CallEndpoint.
+ */
+ @NonNull
+ public final CallEndpoint getCurrentCallEndpoint() {
+ return mCallEndpoint;
+ }
+
+ /**
* Informs listeners that a previously requested RTT session via
* {@link ConnectionRequest#isRequestingRtt()} or
* {@link #onStartRtt(RttTextStream)} has succeeded.
@@ -3160,10 +3247,35 @@
* Notifies this Connection that the {@link #getCallAudioState()} property has a new value.
*
* @param state The new connection audio state.
+ * @deprecated Use {@link #onCallEndpointChanged(CallEndpoint)},
+ * {@link #onAvailableCallEndpointsChanged(List)} and
+ * {@link #onMuteStateChanged(boolean)} instead.
*/
+ @Deprecated
public void onCallAudioStateChanged(CallAudioState state) {}
/**
+ * Notifies this Connection that the audio endpoint has been changed.
+ *
+ * @param callEndpoint The current CallEndpoint.
+ */
+ public void onCallEndpointChanged(@NonNull CallEndpoint callEndpoint) {}
+
+ /**
+ * Notifies this Connection that the available call endpoints have been changed.
+ *
+ * @param availableEndpoints The set of available CallEndpoint.
+ */
+ public void onAvailableCallEndpointsChanged(@NonNull List<CallEndpoint> availableEndpoints) {}
+
+ /**
+ * Notifies this Connection that its audio mute state has been changed.
+ *
+ * @param isMuted The current mute state.
+ */
+ public void onMuteStateChanged(boolean isMuted) {}
+
+ /**
* Inform this Connection when it will or will not be tracked by an {@link InCallService} which
* can provide an InCall UI.
* This is primarily intended for use by Connections reported by self-managed
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index f341bc2..4d6caf8 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -31,6 +31,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.OutcomeReceiver;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.telecom.Logging.Session;
@@ -48,6 +49,7 @@
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
/**
* An abstract service that should be implemented by any apps which either:
@@ -164,6 +166,9 @@
private static final String SESSION_CREATE_CONF = "CS.crConf";
private static final String SESSION_CREATE_CONF_COMPLETE = "CS.crConfC";
private static final String SESSION_CREATE_CONF_FAILED = "CS.crConfF";
+ private static final String SESSION_CALL_ENDPOINT_CHANGED = "CS.oCEC";
+ private static final String SESSION_AVAILABLE_CALL_ENDPOINTS_CHANGED = "CS.oACEC";
+ private static final String SESSION_MUTE_STATE_CHANGED = "CS.oMSC";
private static final int MSG_ADD_CONNECTION_SERVICE_ADAPTER = 1;
private static final int MSG_CREATE_CONNECTION = 2;
@@ -208,6 +213,9 @@
private static final int MSG_ON_CALL_FILTERING_COMPLETED = 42;
private static final int MSG_ON_USING_ALTERNATIVE_UI = 43;
private static final int MSG_ON_TRACKED_BY_NON_UI_SERVICE = 44;
+ private static final int MSG_ON_CALL_ENDPOINT_CHANGED = 45;
+ private static final int MSG_ON_AVAILABLE_CALL_ENDPOINTS_CHANGED = 46;
+ private static final int MSG_ON_MUTE_STATE_CHANGED = 47;
private static Connection sNullConnection;
@@ -592,6 +600,51 @@
}
@Override
+ public void onCallEndpointChanged(String callId, CallEndpoint callEndpoint,
+ Session.Info sessionInfo) {
+ Log.startSession(sessionInfo, SESSION_CALL_ENDPOINT_CHANGED);
+ try {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = callEndpoint;
+ args.arg3 = Log.createSubsession();
+ mHandler.obtainMessage(MSG_ON_CALL_ENDPOINT_CHANGED, args).sendToTarget();
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
+ public void onAvailableCallEndpointsChanged(String callId,
+ List<CallEndpoint> availableCallEndpoints, Session.Info sessionInfo) {
+ Log.startSession(sessionInfo, SESSION_AVAILABLE_CALL_ENDPOINTS_CHANGED);
+ try {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = availableCallEndpoints;
+ args.arg3 = Log.createSubsession();
+ mHandler.obtainMessage(MSG_ON_AVAILABLE_CALL_ENDPOINTS_CHANGED, args)
+ .sendToTarget();
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
+ public void onMuteStateChanged(String callId, boolean isMuted, Session.Info sessionInfo) {
+ Log.startSession(sessionInfo, SESSION_MUTE_STATE_CHANGED);
+ try {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = isMuted;
+ args.arg3 = Log.createSubsession();
+ mHandler.obtainMessage(MSG_ON_MUTE_STATE_CHANGED, args).sendToTarget();
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
public void onUsingAlternativeUi(String callId, boolean usingAlternativeUiShowing,
Session.Info sessionInfo) {
Log.startSession(sessionInfo, SESSION_USING_ALTERNATIVE_UI);
@@ -1527,6 +1580,48 @@
case MSG_CONNECTION_SERVICE_FOCUS_LOST:
onConnectionServiceFocusLost();
break;
+ case MSG_ON_CALL_ENDPOINT_CHANGED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ Log.continueSession((Session) args.arg3,
+ SESSION_HANDLER + SESSION_CALL_AUDIO_SC);
+ try {
+ String callId = (String) args.arg1;
+ CallEndpoint callEndpoint = (CallEndpoint) args.arg2;
+ onCallEndpointChanged(callId, callEndpoint);
+ } finally {
+ args.recycle();
+ Log.endSession();
+ }
+ break;
+ }
+ case MSG_ON_AVAILABLE_CALL_ENDPOINTS_CHANGED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ Log.continueSession((Session) args.arg3,
+ SESSION_HANDLER + SESSION_CALL_AUDIO_SC);
+ try {
+ String callId = (String) args.arg1;
+ List<CallEndpoint> availableCallEndpoints = (List<CallEndpoint>) args.arg2;
+ onAvailableCallEndpointsChanged(callId, availableCallEndpoints);
+ } finally {
+ args.recycle();
+ Log.endSession();
+ }
+ break;
+ }
+ case MSG_ON_MUTE_STATE_CHANGED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ Log.continueSession((Session) args.arg3,
+ SESSION_HANDLER + SESSION_CALL_AUDIO_SC);
+ try {
+ String callId = (String) args.arg1;
+ boolean isMuted = (boolean) args.arg2;
+ onMuteStateChanged(callId, isMuted);
+ } finally {
+ args.recycle();
+ Log.endSession();
+ }
+ break;
+ }
default:
break;
}
@@ -1916,6 +2011,15 @@
mAdapter.resetConnectionTime(id);
}
}
+
+ @Override
+ public void onEndpointChanged(Connection c, CallEndpoint endpoint, Executor executor,
+ OutcomeReceiver<Void, CallEndpointException> callback) {
+ String id = mIdByConnection.get(c);
+ if (id != null) {
+ mAdapter.requestCallEndpointChange(id, endpoint, executor, callback);
+ }
+ }
};
/** {@inheritDoc} */
@@ -2313,6 +2417,36 @@
}
}
+ private void onCallEndpointChanged(String callId, CallEndpoint callEndpoint) {
+ Log.i(this, "onCallEndpointChanged %s %s", callId, callEndpoint);
+ if (mConnectionById.containsKey(callId)) {
+ findConnectionForAction(callId, "onCallEndpointChanged").setCallEndpoint(callEndpoint);
+ } else {
+ findConferenceForAction(callId, "onCallEndpointChanged").setCallEndpoint(callEndpoint);
+ }
+ }
+
+ private void onAvailableCallEndpointsChanged(String callId,
+ List<CallEndpoint> availableCallEndpoints) {
+ Log.i(this, "onAvailableCallEndpointsChanged %s", callId);
+ if (mConnectionById.containsKey(callId)) {
+ findConnectionForAction(callId, "onAvailableCallEndpointsChanged")
+ .setAvailableCallEndpoints(availableCallEndpoints);
+ } else {
+ findConferenceForAction(callId, "onAvailableCallEndpointsChanged")
+ .setAvailableCallEndpoints(availableCallEndpoints);
+ }
+ }
+
+ private void onMuteStateChanged(String callId, boolean isMuted) {
+ Log.i(this, "onMuteStateChanged %s %s", callId, isMuted);
+ if (mConnectionById.containsKey(callId)) {
+ findConnectionForAction(callId, "onMuteStateChanged").setMuteState(isMuted);
+ } else {
+ findConferenceForAction(callId, "onMuteStateChanged").setMuteState(isMuted);
+ }
+ }
+
private void onUsingAlternativeUi(String callId, boolean isUsingAlternativeUi) {
Log.i(this, "onUsingAlternativeUi %s %s", callId, isUsingAlternativeUi);
if (mConnectionById.containsKey(callId)) {
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
index f8a6cf0..39928ef 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
@@ -17,9 +17,12 @@
package android.telecom;
import android.net.Uri;
+import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder.DeathRecipient;
+import android.os.OutcomeReceiver;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import com.android.internal.telecom.IConnectionServiceAdapter;
import com.android.internal.telecom.RemoteServiceCallback;
@@ -29,6 +32,7 @@
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
/**
* Provides methods for IConnectionService implementations to interact with the system phone app.
@@ -567,6 +571,41 @@
}
}
+ /**
+ * Sets the call endpoint associated with a {@link Connection}.
+ *
+ * @param callId The unique ID of the call.
+ * @param endpoint The new call endpoint (see {@link CallEndpoint}).
+ * @param executor The executor of where the callback will execute.
+ * @param callback The callback to notify the result of the endpoint change.
+ */
+ void requestCallEndpointChange(String callId, CallEndpoint endpoint, Executor executor,
+ OutcomeReceiver<Void, CallEndpointException> callback) {
+ Log.v(this, "requestCallEndpointChange");
+ for (IConnectionServiceAdapter adapter : mAdapters) {
+ try {
+ adapter.requestCallEndpointChange(callId, endpoint, new ResultReceiver(null) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle result) {
+ super.onReceiveResult(resultCode, result);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (resultCode == CallEndpoint.ENDPOINT_OPERATION_SUCCESS) {
+ executor.execute(() -> callback.onResult(null));
+ } else {
+ executor.execute(() -> callback.onError(result.getParcelable(
+ CallEndpointException.CHANGE_ERROR,
+ CallEndpointException.class)));
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }}, Log.getExternalSession());
+ } catch (RemoteException ignored) {
+ Log.d(this, "Remote exception calling requestCallEndpointChange");
+ }
+ }
+ }
/**
* Informs Telecom of a connection level event.
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
index 6c1ea32..c95e14f 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
@@ -21,6 +21,7 @@
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.telecom.Logging.Session;
import com.android.internal.os.SomeArgs;
@@ -692,6 +693,12 @@
args.arg2 = sessionInfo;
mHandler.obtainMessage(MSG_SET_CALL_DIRECTION, args).sendToTarget();
}
+
+ @Override
+ public void requestCallEndpointChange(String callId, CallEndpoint endpoint,
+ ResultReceiver callback, Session.Info sessionInfo) {
+ // Do nothing
+ }
};
public ConnectionServiceAdapterServant(IConnectionServiceAdapter delegate) {
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
index ab35aff..7770145 100755
--- a/telecomm/java/android/telecom/InCallAdapter.java
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -19,12 +19,16 @@
import android.annotation.NonNull;
import android.bluetooth.BluetoothDevice;
import android.net.Uri;
+import android.os.Binder;
import android.os.Bundle;
+import android.os.OutcomeReceiver;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import com.android.internal.telecom.IInCallAdapter;
import java.util.List;
+import java.util.concurrent.Executor;
/**
* Receives commands from {@link InCallService} implementations which should be executed by
@@ -227,6 +231,39 @@
}
/**
+ * Request audio routing to a specific CallEndpoint.. See {@link CallEndpoint}.
+ *
+ * @param endpoint The call endpoint to use.
+ * @param executor The executor of where the callback will execute.
+ * @param callback The callback to notify the result of the endpoint change.
+ */
+ public void requestCallEndpointChange(CallEndpoint endpoint, Executor executor,
+ OutcomeReceiver<Void, CallEndpointException> callback) {
+ try {
+ mAdapter.requestCallEndpointChange(endpoint, new ResultReceiver(null) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle result) {
+ super.onReceiveResult(resultCode, result);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (resultCode == CallEndpoint.ENDPOINT_OPERATION_SUCCESS) {
+ executor.execute(() -> callback.onResult(null));
+ } else {
+ executor.execute(() -> callback.onError(
+ result.getParcelable(CallEndpointException.CHANGE_ERROR,
+ CallEndpointException.class)));
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ });
+ } catch (RemoteException e) {
+ Log.d(this, "Remote exception calling requestCallEndpointChange");
+ }
+ }
+
+ /**
* Instructs Telecom to play a dual-tone multi-frequency signaling (DTMF) tone in a call.
*
* Any other currently playing DTMF tone in the specified call is immediately stopped.
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index 64a86db..13a0458 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -16,6 +16,7 @@
package android.telecom;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
@@ -31,6 +32,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.OutcomeReceiver;
import android.view.Surface;
import com.android.internal.os.SomeArgs;
@@ -39,6 +41,8 @@
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
/**
* This service is implemented by an app that wishes to provide functionality for managing
@@ -269,6 +273,11 @@
private static final int MSG_ON_RTT_INITIATION_FAILURE = 11;
private static final int MSG_ON_HANDOVER_FAILED = 12;
private static final int MSG_ON_HANDOVER_COMPLETE = 13;
+ private static final int MSG_ON_CALL_ENDPOINT_CHANGED = 14;
+ private static final int MSG_ON_AVAILABLE_CALL_ENDPOINTS_CHANGED = 15;
+ private static final int MSG_ON_MUTE_STATE_CHANGED = 16;
+
+ private CallEndpoint mCallEndpoint;
/** Default Handler used to consolidate binder method calls onto a single thread. */
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@@ -350,6 +359,23 @@
mPhone.internalOnHandoverComplete(callId);
break;
}
+ case MSG_ON_CALL_ENDPOINT_CHANGED: {
+ CallEndpoint endpoint = (CallEndpoint) msg.obj;
+ if (!Objects.equals(mCallEndpoint, endpoint)) {
+ mCallEndpoint = endpoint;
+ InCallService.this.onCallEndpointChanged(mCallEndpoint);
+ }
+ break;
+ }
+ case MSG_ON_AVAILABLE_CALL_ENDPOINTS_CHANGED: {
+ InCallService.this.onAvailableCallEndpointsChanged(
+ (List<CallEndpoint>) msg.obj);
+ break;
+ }
+ case MSG_ON_MUTE_STATE_CHANGED: {
+ InCallService.this.onMuteStateChanged((boolean) msg.obj);
+ break;
+ }
default:
break;
}
@@ -392,6 +418,22 @@
}
@Override
+ public void onCallEndpointChanged(CallEndpoint callEndpoint) {
+ mHandler.obtainMessage(MSG_ON_CALL_ENDPOINT_CHANGED, callEndpoint).sendToTarget();
+ }
+
+ @Override
+ public void onAvailableCallEndpointsChanged(List<CallEndpoint> availableEndpoints) {
+ mHandler.obtainMessage(MSG_ON_AVAILABLE_CALL_ENDPOINTS_CHANGED, availableEndpoints)
+ .sendToTarget();
+ }
+
+ @Override
+ public void onMuteStateChanged(boolean isMuted) {
+ mHandler.obtainMessage(MSG_ON_MUTE_STATE_CHANGED, isMuted).sendToTarget();
+ }
+
+ @Override
public void bringToForeground(boolean showDialpad) {
mHandler.obtainMessage(MSG_BRING_TO_FOREGROUND, showDialpad ? 1 : 0, 0).sendToTarget();
}
@@ -559,7 +601,11 @@
*
* @return An object encapsulating the audio state. Returns null if the service is not
* fully initialized.
+ * @deprecated Use {@link #getCurrentCallEndpoint()},
+ * {@link #onAvailableCallEndpointsChanged(List)} and
+ * {@link #onMuteStateChanged(boolean)} instead.
*/
+ @Deprecated
public final CallAudioState getCallAudioState() {
return mPhone == null ? null : mPhone.getCallAudioState();
}
@@ -581,7 +627,10 @@
* be change to the {@link #getCallAudioState()}.
*
* @param route The audio route to use.
+ * @deprecated Use {@link #requestCallEndpointChange(CallEndpoint, Executor, OutcomeReceiver)}
+ * instead.
*/
+ @Deprecated
public final void setAudioRoute(int route) {
if (mPhone != null) {
mPhone.setAudioRoute(route);
@@ -596,7 +645,10 @@
* {@link CallAudioState#getSupportedBluetoothDevices()}
*
* @param bluetoothDevice The bluetooth device to connect to.
+ * @deprecated Use {@link #requestCallEndpointChange(CallEndpoint, Executor, OutcomeReceiver)}
+ * instead.
*/
+ @Deprecated
public final void requestBluetoothAudio(@NonNull BluetoothDevice bluetoothDevice) {
if (mPhone != null) {
mPhone.requestBluetoothAudio(bluetoothDevice.getAddress());
@@ -604,6 +656,34 @@
}
/**
+ * Request audio routing to a specific CallEndpoint. Clients should not define their own
+ * CallEndpoint when requesting a change. Instead, the new endpoint should be one of the valid
+ * endpoints provided by {@link #onAvailableCallEndpointsChanged(List)}.
+ * When this request is honored, there will be change to the {@link #getCurrentCallEndpoint()}.
+ *
+ * @param endpoint The call endpoint to use.
+ * @param executor The executor of where the callback will execute.
+ * @param callback The callback to notify the result of the endpoint change.
+ */
+ public final void requestCallEndpointChange(@NonNull CallEndpoint endpoint,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Void, CallEndpointException> callback) {
+ if (mPhone != null) {
+ mPhone.requestCallEndpointChange(endpoint, executor, callback);
+ }
+ }
+
+ /**
+ * Obtains the current CallEndpoint.
+ *
+ * @return An object encapsulating the CallEndpoint.
+ */
+ @NonNull
+ public final CallEndpoint getCurrentCallEndpoint() {
+ return mCallEndpoint;
+ }
+
+ /**
* Invoked when the {@code Phone} has been created. This is a signal to the in-call experience
* to start displaying in-call information to the user. Each instance of {@code InCallService}
* will have only one {@code Phone}, and this method will be called exactly once in the lifetime
@@ -648,11 +728,39 @@
* Called when the audio state changes.
*
* @param audioState The new {@link CallAudioState}.
+ * @deprecated Use {@link #onCallEndpointChanged(CallEndpoint)},
+ * {@link #onAvailableCallEndpointsChanged(List)} and
+ * {@link #onMuteStateChanged(boolean)} instead.
*/
+ @Deprecated
public void onCallAudioStateChanged(CallAudioState audioState) {
}
/**
+ * Called when the current CallEndpoint changes.
+ *
+ * @param callEndpoint The current CallEndpoint {@link CallEndpoint}.
+ */
+ public void onCallEndpointChanged(@NonNull CallEndpoint callEndpoint) {
+ }
+
+ /**
+ * Called when the available CallEndpoint changes.
+ *
+ * @param availableEndpoints The set of available CallEndpoint {@link CallEndpoint}.
+ */
+ public void onAvailableCallEndpointsChanged(@NonNull List<CallEndpoint> availableEndpoints) {
+ }
+
+ /**
+ * Called when the mute state changes.
+ *
+ * @param isMuted The current mute state.
+ */
+ public void onMuteStateChanged(boolean isMuted) {
+ }
+
+ /**
* Called to bring the in-call screen to the foreground. The in-call experience should
* respond immediately by coming to the foreground to inform the user of the state of
* ongoing {@code Call}s.
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index bc0a146..95a8e16 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -16,11 +16,14 @@
package android.telecom;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.bluetooth.BluetoothDevice;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Bundle;
+import android.os.OutcomeReceiver;
import android.util.ArrayMap;
import com.android.internal.annotations.GuardedBy;
@@ -30,6 +33,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
/**
* A unified virtual device providing a means of voice (and other) communication on a device.
@@ -378,6 +382,21 @@
}
/**
+ * Request audio routing to a specific CallEndpoint. When this request is honored, there will
+ * be change to the {@link #getCurrentCallEndpoint()}.
+ *
+ * @param endpoint The call endpoint to use.
+ * @param executor The executor of where the callback will execute.
+ * @param callback The callback to notify the result of the endpoint change.
+ * @hide
+ */
+ public void requestCallEndpointChange(@NonNull CallEndpoint endpoint,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Void, CallEndpointException> callback) {
+ mInCallAdapter.requestCallEndpointChange(endpoint, executor, callback);
+ }
+
+ /**
* Turns the proximity sensor on. When this request is made, the proximity sensor will
* become active, and the touch screen and display will be turned off when the user's face
* is detected to be in close proximity to the screen. This operation is a no-op on devices
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
index 7a6fddb..8b2b51e 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -36,12 +36,10 @@
import com.android.internal.telecom.IVideoProvider;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.stream.Collectors;
/**
* A connection provided to a {@link ConnectionService} by another {@code ConnectionService}
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index efe35d2..6561732 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -21,6 +21,7 @@
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.telecom.Logging.Session;
import com.android.internal.telecom.IConnectionService;
@@ -510,6 +511,12 @@
public void setCallDirection(String callId, int direction, Session.Info sessionInfo) {
// Do nothing
}
+
+ @Override
+ public void requestCallEndpointChange(String callId, CallEndpoint endpoint,
+ ResultReceiver callback, Session.Info sessionInfo) {
+ // Do nothing
+ }
};
private final ConnectionServiceAdapterServant mServant =
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index d72f8aa..29617f2 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -20,6 +20,7 @@
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
import android.telecom.Connection;
import android.telecom.ConnectionRequest;
import android.telecom.Logging.Session;
@@ -98,6 +99,14 @@
void onCallAudioStateChanged(String activeCallId, in CallAudioState callAudioState,
in Session.Info sessionInfo);
+ void onCallEndpointChanged(String activeCallId, in CallEndpoint callEndpoint,
+ in Session.Info sessionInfo);
+
+ void onAvailableCallEndpointsChanged(String activeCallId,
+ in List<CallEndpoint> availableCallEndpoints, in Session.Info sessionInfo);
+
+ void onMuteStateChanged(String activeCallId, boolean isMuted, in Session.Info sessionInfo);
+
void playDtmfTone(String callId, char digit, in Session.Info sessionInfo);
void stopDtmfTone(String callId, in Session.Info sessionInfo);
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
index 3fd7f949..6838fbd 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
@@ -19,6 +19,8 @@
import android.app.PendingIntent;
import android.net.Uri;
import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.telecom.CallEndpoint;
import android.telecom.ConnectionRequest;
import android.telecom.DisconnectCause;
import android.telecom.Logging.Session;
@@ -113,6 +115,9 @@
void setAudioRoute(String callId, int audioRoute, String bluetoothAddress,
in Session.Info sessionInfo);
+ void requestCallEndpointChange(String callId, in CallEndpoint endpoint,
+ in ResultReceiver callback, in Session.Info sessionInfo);
+
void onConnectionEvent(String callId, String event, in Bundle extras,
in Session.Info sessionInfo);
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
index edf1cf4..e381ce8 100755
--- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
@@ -18,7 +18,9 @@
import android.net.Uri;
import android.os.Bundle;
+import android.os.ResultReceiver;
import android.telecom.PhoneAccountHandle;
+import android.telecom.CallEndpoint;
/**
* Internal remote callback interface for in-call services.
@@ -50,6 +52,8 @@
void setAudioRoute(int route, String bluetoothAddress);
+ void requestCallEndpointChange(in CallEndpoint endpoint, in ResultReceiver callback);
+
void enterBackgroundAudioProcessing(String callId);
void exitBackgroundAudioProcessing(String callId, boolean shouldRing);
diff --git a/telecomm/java/com/android/internal/telecom/IInCallService.aidl b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
index b9563fa..bac295a 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
@@ -19,6 +19,7 @@
import android.app.PendingIntent;
import android.os.Bundle;
import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
import android.telecom.ParcelableCall;
import com.android.internal.telecom.IInCallAdapter;
@@ -43,6 +44,12 @@
void onCallAudioStateChanged(in CallAudioState callAudioState);
+ void onCallEndpointChanged(in CallEndpoint callEndpoint);
+
+ void onAvailableCallEndpointsChanged(in List<CallEndpoint> availableCallEndpoints);
+
+ void onMuteStateChanged(boolean isMuted);
+
void bringToForeground(boolean showDialpad);
void onCanAddCallChanged(boolean canAddCall);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index c4744ef..a5203c4 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -45,6 +45,7 @@
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.ImsRegistrationAttributes;
import android.telephony.ims.ImsSsData;
+import android.telephony.ims.MediaQualityStatus;
import android.telephony.ims.RcsUceAdapter;
import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.feature.RcsFeature;
@@ -2990,6 +2991,39 @@
"5g_nr_sssinr_thresholds_int_array";
/**
+ * An interval in dB for {@link SignalThresholdInfo#SIGNAL_MEASUREMENT_TYPE_SSRSRP} measurement
+ * type defining the required magnitude change between reports.
+ *
+ * <p>The default value is 2 and the minimum allowed value is 0. If no value or negative value
+ * is set, the default value 2 is used.
+ * @hide
+ */
+ public static final String
+ KEY_NGRAN_SSRSRP_HYSTERESIS_DB_INT = "ngran_ssrsrp_hysteresis_db_int";
+
+ /**
+ * An interval in dB for {@link SignalThresholdInfo#SIGNAL_MEASUREMENT_TYPE_SSRSRQ} measurement
+ * type defining the required magnitude change between reports.
+ *
+ * <p>The default value is 2 and the minimum allowed value is 0. If no value or negative value
+ * is set, the default value 2 is used.
+ *@hide
+ */
+ public static final String
+ KEY_NGRAN_SSRSRQ_HYSTERESIS_DB_INT = "ngran_ssrsrq_hysteresis_db_int";
+
+ /**
+ * An interval in dB for {@link SignalThresholdInfo#SIGNAL_MEASUREMENT_TYPE_SSSINR} measurement
+ * type defining the required magnitude change between reports.
+ *
+ * <p>The default value is 2 and the minimum allowed value is 0. If no value or negative value
+ * is set, the default value 2 is used.
+ * @hide
+ */
+ public static final String
+ KEY_NGRAN_SSSINR_HYSTERESIS_DB_INT = "ngran_sssinr_hysteresis_db_int";
+
+ /**
* Bit-field integer to determine whether to use SS reference signal received power (SSRSRP),
* SS reference signal received quality (SSRSRQ), or/and SS signal-to-noise and interference
* ratio (SSSINR) for the number of 5G NR signal bars and signal criteria reporting enabling.
@@ -3326,6 +3360,38 @@
"lte_rssnr_thresholds_int_array";
/**
+ * An interval in dB for {@link SignalThresholdInfo#SIGNAL_MEASUREMENT_TYPE_RSRP} measurement
+ * type defining the required magnitude change between reports.
+ *
+ * <p>The default value is 2 and the minimum allowed value is 0. If no value or negative value
+ * is set, the default value 2 is used.
+ * @hide
+ */
+ public static final String
+ KEY_EUTRAN_RSRP_HYSTERESIS_DB_INT = "eutran_rsrp_hysteresis_db_int";
+
+ /**
+ * An interval in dB for {@link SignalThresholdInfo#SIGNAL_MEASUREMENT_TYPE_RSRQ} measurement
+ * type defining the required magnitude change between reports.
+ *
+ * <p>The default value is 2 and the minimum allowed value is 0. If no value or negative value
+ * is set, the default value 2 is used.
+ * @hide
+ */
+ public static final String KEY_EUTRAN_RSRQ_HYSTERESIS_DB_INT = "eutran_rsrq_hysteresis_db_int";
+
+ /**
+ * An interval in dB for {@link SignalThresholdInfo#SIGNAL_MEASUREMENT_TYPE_RSSNR} measurement
+ * type defining the required magnitude change between reports.
+ *
+ * <p>The default value is 2 and the minimum allowed value is 0. If no value or negative value
+ * is set, the default value 2 is used.
+ * @hide
+ */
+ public static final String
+ KEY_EUTRAN_RSSNR_HYSTERESIS_DB_INT = "eutran_rssnr_hysteresis_db_int";
+
+ /**
* Decides when clients try to bind to iwlan network service, which package name will
* the binding intent go to.
* @hide
@@ -3402,6 +3468,26 @@
"wcdma_ecno_thresholds_int_array";
/**
+ * An interval in dB for {@link SignalThresholdInfo#SIGNAL_MEASUREMENT_TYPE_RSCP} measurement
+ * type defining the required magnitude change between reports.
+ *
+ * <p>The default value is 2 and the minimum allowed value is 0. If no value or negative value
+ * is set, the default value 2 is used.
+ * @hide
+ */
+ public static final String KEY_UTRAN_RSCP_HYSTERESIS_DB_INT = "utran_rscp_hysteresis_db_int";
+
+ /**
+ * An interval in dB for {@link SignalThresholdInfo#SIGNAL_MEASUREMENT_TYPE_ECNO} measurement
+ * type defining the required magnitude change between reports.
+ *
+ * <p>The default value is 2 and the minimum allowed value is 0. If no value or negative value
+ * is set, the default value 2 is used.
+ * @hide
+ */
+ public static final String KEY_UTRAN_ECNO_HYSTERESIS_DB_INT = "utran_ecno_hysteresis_db_int";
+
+ /**
* The default measurement to use for signal strength reporting. If this is not specified, the
* RSSI is used.
* <p>
@@ -6262,6 +6348,50 @@
public static final String KEY_DTMFNB_PAYLOAD_TYPE_INT_ARRAY =
KEY_PREFIX + "dtmfnb_payload_type_int_array";
+ /**
+ * This indicates the threshold for RTP packet loss rate in percentage. If measured packet
+ * loss rate crosses this, a callback with {@link MediaQualityStatus} will be invoked to
+ * listeners.
+ * See {@link android.telephony.TelephonyCallback.MediaQualityStatusChangedListener}
+ *
+ * <p/>
+ * Valid threshold range : 0 ~ 100
+ *
+ * @hide
+ */
+ public static final String KEY_VOICE_RTP_PACKET_LOSS_RATE_THRESHOLD_INT =
+ KEY_PREFIX + "rtp_packet_loss_rate_threshold_int";
+
+
+ /**
+ * This indicates the threshold for RTP jitter value in milliseconds (RFC3550). If measured
+ * jitter value crosses this, a callback with {@link MediaQualityStatus} will be invoked
+ * to listeners.
+ * See {@link android.telephony.TelephonyCallback.MediaQualityStatusChangedListener}
+ *
+ * <p/>
+ * Valid threshold range : 0 ~ 10000
+ *
+ * @hide
+ */
+ public static final String KEY_VOICE_RTP_JITTER_THRESHOLD_MILLIS_INT =
+ KEY_PREFIX + "rtp_jitter_threshold_millis_int";
+
+ /**
+ * This indicates the threshold for RTP inactivity time in milliseconds. If measured
+ * inactivity timer crosses this, a callback with {@link MediaQualityStatus} will be invoked
+ * to listeners.
+ * See {@link android.telephony.TelephonyCallback.MediaQualityStatusChangedListener}
+ *
+ * <p/>
+ * Valid threshold range : 0 ~ 60000
+ *
+ * @hide
+ */
+ public static final String KEY_VOICE_RTP_INACTIVITY_TIME_THRESHOLD_MILLIS_LONG =
+ KEY_PREFIX + "rtp_inactivity_time_threshold_millis_long";
+
+
/** @hide */
@IntDef({
BANDWIDTH_EFFICIENT,
@@ -6748,6 +6878,9 @@
defaults.putInt(KEY_AUDIO_AS_BANDWIDTH_KBPS_INT, 41);
defaults.putInt(KEY_AUDIO_RS_BANDWIDTH_BPS_INT, 600);
defaults.putInt(KEY_AUDIO_RR_BANDWIDTH_BPS_INT, 2000);
+ defaults.putInt(KEY_VOICE_RTP_PACKET_LOSS_RATE_THRESHOLD_INT, 40);
+ defaults.putInt(KEY_VOICE_RTP_JITTER_THRESHOLD_MILLIS_INT, 120);
+ defaults.putLong(KEY_VOICE_RTP_INACTIVITY_TIME_THRESHOLD_MILLIS_LONG, 5000);
defaults.putIntArray(
KEY_AUDIO_INACTIVITY_CALL_END_REASONS_INT_ARRAY,
@@ -8519,6 +8652,14 @@
public static final String KEY_SUPPORTS_EAP_AKA_FAST_REAUTH_BOOL =
KEY_PREFIX + "supports_eap_aka_fast_reauth_bool";
+ /**
+ * Type of IP preference used to prioritize ePDG servers. Possible values are
+ * {@link #EPDG_ADDRESS_IPV4_PREFERRED}, {@link #EPDG_ADDRESS_IPV6_PREFERRED},
+ * {@link #EPDG_ADDRESS_IPV4_ONLY}
+ */
+ public static final String KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT =
+ KEY_PREFIX + "epdg_address_ip_type_preference_int";
+
/** @hide */
@IntDef({AUTHENTICATION_METHOD_EAP_ONLY, AUTHENTICATION_METHOD_CERT})
public @interface AuthenticationMethodType {}
@@ -8583,6 +8724,39 @@
*/
public static final int ID_TYPE_KEY_ID = 11;
+ /** @hide */
+ @IntDef({
+ EPDG_ADDRESS_IPV4_PREFERRED,
+ EPDG_ADDRESS_IPV6_PREFERRED,
+ EPDG_ADDRESS_IPV4_ONLY,
+ EPDG_ADDRESS_IPV6_ONLY,
+ EPDG_ADDRESS_SYSTEM_PREFERRED
+ })
+ public @interface EpdgAddressIpPreference {}
+
+ /** Prioritize IPv4 ePDG addresses. */
+ public static final int EPDG_ADDRESS_IPV4_PREFERRED = 0;
+
+ /** Prioritize IPv6 ePDG addresses */
+ public static final int EPDG_ADDRESS_IPV6_PREFERRED = 1;
+
+ /** Use IPv4 ePDG addresses only. */
+ public static final int EPDG_ADDRESS_IPV4_ONLY = 2;
+
+ /** Use IPv6 ePDG addresses only.
+ * @hide
+ */
+ public static final int EPDG_ADDRESS_IPV6_ONLY = 3;
+
+ /** Follow the priority from DNS resolution results, which are sorted by using RFC6724
+ * algorithm.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc6724#section-6">RFC 6724, Default Address
+ * Selection for Internet Protocol Version 6 (IPv6)</a>
+ * @hide
+ */
+ public static final int EPDG_ADDRESS_SYSTEM_PREFERRED = 4;
+
private Iwlan() {}
private static PersistableBundle getDefaults() {
@@ -8666,7 +8840,7 @@
defaults.putInt(KEY_EPDG_PCO_ID_IPV6_INT, 0);
defaults.putInt(KEY_EPDG_PCO_ID_IPV4_INT, 0);
defaults.putBoolean(KEY_SUPPORTS_EAP_AKA_FAST_REAUTH_BOOL, false);
-
+ defaults.putInt(KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT, EPDG_ADDRESS_IPV4_PREFERRED);
return defaults;
}
}
@@ -8686,6 +8860,16 @@
"gsm_rssi_thresholds_int_array";
/**
+ * An interval in dB for {@link SignalThresholdInfo#SIGNAL_MEASUREMENT_TYPE_RSSI} measurement
+ * type defining the required magnitude change between reports.
+ *
+ * <p>The default value is 2 and the minimum allowed value is 0. If no value or negative value
+ * is set, the default value 2 is used.
+ * @hide
+ */
+ public static final String KEY_GERAN_RSSI_HYSTERESIS_DB_INT = "geran_rssi_hysteresis_db_int";
+
+ /**
* Determines whether Wireless Priority Service call is supported over IMS.
*
* See Wireless Priority Service from https://www.fcc.gov/general/wireless-priority-service-wps
@@ -9046,7 +9230,7 @@
/**
* A list of premium capabilities the carrier supports. Applications can prompt users to
- * purchase these premium capabilities from their carrier for a network boost.
+ * purchase these premium capabilities from their carrier for a performance boost.
* Valid values are any of {@link TelephonyManager.PremiumCapability}.
*
* This is empty by default, indicating that no premium capabilities are supported.
@@ -9058,12 +9242,12 @@
"supported_premium_capabilities_int_array";
/**
- * The amount of time in milliseconds the notification for a network boost via
+ * The amount of time in milliseconds the notification for a performance boost via
* premium capabilities will be visible to the user after
* {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
* requests user action to purchase the boost from the carrier. Once the timeout expires,
- * the booster notification will be automatically dismissed and the request will fail with
- * {@link TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT}.
+ * the performance boost notification will be automatically dismissed and the request will fail
+ * with {@link TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT}.
*
* The default value is 30 minutes.
*/
@@ -9071,11 +9255,11 @@
"premium_capability_notification_display_timeout_millis_long";
/**
- * The amount of time in milliseconds that the notification for a network boost via
+ * The amount of time in milliseconds that the notification for a performance boost via
* premium capabilities should be blocked when
* {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
* returns a failure due to user action or timeout.
- * The maximum number of network boost notifications to show the user are defined in
+ * The maximum number of performance boost notifications to show the user are defined in
* {@link #KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT} and
* {@link #KEY_PREMIUM_CAPABILITY_MAXIMUM_MONTHLY_NOTIFICATION_COUNT_INT}.
*
@@ -9089,7 +9273,7 @@
"premium_capability_notification_backoff_hysteresis_time_millis_long";
/**
- * The maximum number of times in a day that we display the notification for a network boost
+ * The maximum number of times in a day that we display the notification for a performance boost
* via premium capabilities when
* {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
* returns a failure due to user action or timeout.
@@ -9103,8 +9287,8 @@
"premium_capability_maximum_daily_notification_count_int";
/**
- * The maximum number of times in a month that we display the notification for a network boost
- * via premium capabilities when
+ * The maximum number of times in a month that we display the notification for a performance
+ * boost via premium capabilities when
* {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
* returns a failure due to user action or timeout.
*
@@ -9147,7 +9331,7 @@
"premium_capability_network_setup_time_millis_long";
/**
- * The URL to redirect to when the user clicks on the notification for a network boost via
+ * The URL to redirect to when the user clicks on the notification for a performance boost via
* premium capabilities after applications call
* {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}.
* If the URL is empty or invalid, the purchase request will return
@@ -9666,6 +9850,15 @@
15, /* SIGNAL_STRENGTH_GOOD */
30 /* SIGNAL_STRENGTH_GREAT */
});
+ sDefaults.putInt(KEY_GERAN_RSSI_HYSTERESIS_DB_INT, 2);
+ sDefaults.putInt(KEY_UTRAN_RSCP_HYSTERESIS_DB_INT, 2);
+ sDefaults.putInt(KEY_EUTRAN_RSRP_HYSTERESIS_DB_INT, 2);
+ sDefaults.putInt(KEY_EUTRAN_RSRQ_HYSTERESIS_DB_INT, 2);
+ sDefaults.putInt(KEY_EUTRAN_RSSNR_HYSTERESIS_DB_INT, 2);
+ sDefaults.putInt(KEY_NGRAN_SSRSRP_HYSTERESIS_DB_INT, 2);
+ sDefaults.putInt(KEY_NGRAN_SSRSRQ_HYSTERESIS_DB_INT, 2);
+ sDefaults.putInt(KEY_NGRAN_SSSINR_HYSTERESIS_DB_INT, 2);
+ sDefaults.putInt(KEY_UTRAN_ECNO_HYSTERESIS_DB_INT, 2);
sDefaults.putInt(KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT,
CellSignalStrengthNr.USE_SSRSRP);
sDefaults.putBoolean(KEY_SIGNAL_STRENGTH_NR_NSA_USE_LTE_AS_PRIMARY_BOOL, true);
diff --git a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
index ca6dc2d..5e5e028 100644
--- a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
+++ b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
@@ -16,6 +16,7 @@
package android.telephony;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -24,6 +25,8 @@
import com.android.internal.annotations.VisibleForTesting;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
@@ -33,6 +36,69 @@
*/
@SystemApi
public final class DataSpecificRegistrationInfo implements Parcelable {
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = "LTE_ATTACH_TYPE_",
+ value = {
+ LTE_ATTACH_TYPE_UNKNOWN,
+ LTE_ATTACH_TYPE_EPS_ONLY,
+ LTE_ATTACH_TYPE_COMBINED,
+ })
+ public @interface LteAttachResultType {}
+
+ /**
+ * Default value.
+ * Attach type is unknown.
+ */
+ public static final int LTE_ATTACH_TYPE_UNKNOWN = 0;
+
+ /**
+ * LTE is attached with EPS only.
+ *
+ * Reference: 3GPP TS 24.301 9.9.3 EMM information elements.
+ */
+ public static final int LTE_ATTACH_TYPE_EPS_ONLY = 1;
+
+ /**
+ * LTE combined EPS and IMSI attach.
+ *
+ * Reference: 3GPP TS 24.301 9.9.3 EMM information elements.
+ */
+ public static final int LTE_ATTACH_TYPE_COMBINED = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = {"LTE_ATTACH_EXTRA_INFO_"},
+ value = {
+ LTE_ATTACH_EXTRA_INFO_NONE,
+ LTE_ATTACH_EXTRA_INFO_CSFB_NOT_PREFERRED,
+ LTE_ATTACH_EXTRA_INFO_SMS_ONLY
+ })
+ public @interface LteAttachExtraInfo {}
+
+ /**
+ * Default value.
+ */
+ public static final int LTE_ATTACH_EXTRA_INFO_NONE = 0;
+
+ /**
+ * CSFB is not preferred.
+ * Applicable for LTE only.
+ *
+ * Reference: 3GPP TS 24.301 9.9.3 EMM information elements.
+ */
+ public static final int LTE_ATTACH_EXTRA_INFO_CSFB_NOT_PREFERRED = 1 << 0;
+
+ /**
+ * Attached for SMS only.
+ * Applicable for LTE only.
+ *
+ * Reference: 3GPP TS 24.301 9.9.3 EMM information elements.
+ */
+ public static final int LTE_ATTACH_EXTRA_INFO_SMS_ONLY = 1 << 1;
+
/**
* @hide
* The maximum number of simultaneous Data Calls that
@@ -75,6 +141,22 @@
@Nullable
private final VopsSupportInfo mVopsSupportInfo;
+ /** The type of network attachment */
+ private final @LteAttachResultType int mLteAttachResultType;
+
+ /** LTE attach extra info */
+ private final @LteAttachExtraInfo int mLteAttachExtraInfo;
+
+ private DataSpecificRegistrationInfo(Builder builder) {
+ this.maxDataCalls = builder.mMaxDataCalls;
+ this.isDcNrRestricted = builder.mIsDcNrRestricted;
+ this.isNrAvailable = builder.mIsNrAvailable;
+ this.isEnDcAvailable = builder.mIsEnDcAvailable;
+ this.mVopsSupportInfo = builder.mVopsSupportInfo;
+ this.mLteAttachResultType = builder.mLteAttachResultType;
+ this.mLteAttachExtraInfo = builder.mLteAttachExtraInfo;
+ }
+
/**
* @hide
*/
@@ -87,6 +169,8 @@
this.isNrAvailable = isNrAvailable;
this.isEnDcAvailable = isEnDcAvailable;
this.mVopsSupportInfo = vops;
+ this.mLteAttachResultType = LTE_ATTACH_TYPE_UNKNOWN;
+ this.mLteAttachExtraInfo = LTE_ATTACH_EXTRA_INFO_NONE;
}
/**
@@ -101,6 +185,8 @@
isNrAvailable = dsri.isNrAvailable;
isEnDcAvailable = dsri.isEnDcAvailable;
mVopsSupportInfo = dsri.mVopsSupportInfo;
+ mLteAttachResultType = dsri.mLteAttachResultType;
+ mLteAttachExtraInfo = dsri.mLteAttachExtraInfo;
}
private DataSpecificRegistrationInfo(/* @NonNull */ Parcel source) {
@@ -109,6 +195,8 @@
isNrAvailable = source.readBoolean();
isEnDcAvailable = source.readBoolean();
mVopsSupportInfo = source.readParcelable(VopsSupportInfo.class.getClassLoader(), android.telephony.VopsSupportInfo.class);
+ mLteAttachResultType = source.readInt();
+ mLteAttachExtraInfo = source.readInt();
}
@Override
@@ -118,6 +206,8 @@
dest.writeBoolean(isNrAvailable);
dest.writeBoolean(isEnDcAvailable);
dest.writeParcelable(mVopsSupportInfo, flags);
+ dest.writeInt(mLteAttachResultType);
+ dest.writeInt(mLteAttachExtraInfo);
}
@Override
@@ -134,6 +224,8 @@
.append(" isDcNrRestricted = " + isDcNrRestricted)
.append(" isNrAvailable = " + isNrAvailable)
.append(" isEnDcAvailable = " + isEnDcAvailable)
+ .append(" mLteAttachResultType = " + mLteAttachResultType)
+ .append(" mLteAttachExtraInfo = " + mLteAttachExtraInfo)
.append(" " + mVopsSupportInfo)
.append(" }")
.toString();
@@ -142,7 +234,8 @@
@Override
public int hashCode() {
return Objects.hash(maxDataCalls, isDcNrRestricted, isNrAvailable,
- isEnDcAvailable, mVopsSupportInfo);
+ isEnDcAvailable, mVopsSupportInfo,
+ mLteAttachResultType, mLteAttachExtraInfo);
}
@Override
@@ -156,7 +249,9 @@
&& this.isDcNrRestricted == other.isDcNrRestricted
&& this.isNrAvailable == other.isNrAvailable
&& this.isEnDcAvailable == other.isEnDcAvailable
- && Objects.equals(mVopsSupportInfo, other.mVopsSupportInfo);
+ && Objects.equals(mVopsSupportInfo, other.mVopsSupportInfo)
+ && this.mLteAttachResultType == other.mLteAttachResultType
+ && this.mLteAttachExtraInfo == other.mLteAttachExtraInfo;
}
public static final @NonNull Parcelable.Creator<DataSpecificRegistrationInfo> CREATOR =
@@ -196,4 +291,105 @@
public VopsSupportInfo getVopsSupportInfo() {
return mVopsSupportInfo;
}
+
+ /**
+ * Provides the LTE attach type.
+ */
+ public @LteAttachResultType int getLteAttachResultType() {
+ return mLteAttachResultType;
+ }
+
+ /**
+ * Provides the extra information of LTE attachment.
+ *
+ * @return the bitwise OR of {@link LteAttachExtraInfo}.
+ */
+ public @LteAttachExtraInfo int getLteAttachExtraInfo() {
+ return mLteAttachExtraInfo;
+ }
+
+ /**
+ * Builds {@link DataSpecificRegistrationInfo} instances, which may include optional parameters.
+ * @hide
+ */
+ public static final class Builder {
+ private final int mMaxDataCalls;
+
+ private boolean mIsDcNrRestricted;
+ private boolean mIsNrAvailable;
+ private boolean mIsEnDcAvailable;
+ private @Nullable VopsSupportInfo mVopsSupportInfo;
+ private @LteAttachResultType int mLteAttachResultType = LTE_ATTACH_TYPE_UNKNOWN;
+ private @LteAttachExtraInfo int mLteAttachExtraInfo = LTE_ATTACH_EXTRA_INFO_NONE;
+
+ public Builder(int maxDataCalls) {
+ mMaxDataCalls = maxDataCalls;
+ }
+
+ /**
+ * Ses whether the use of dual connectivity with NR is restricted.
+ * @param isDcNrRestricted {@code true} if the use of dual connectivity with NR is
+ * restricted.
+ */
+ public @NonNull Builder setDcNrRestricted(boolean isDcNrRestricted) {
+ mIsDcNrRestricted = isDcNrRestricted;
+ return this;
+ }
+
+ /**
+ * Sets whether NR is supported by the selected PLMN.
+ * @param isNrAvailable {@code true} if NR is supported.
+ */
+ public @NonNull Builder setNrAvailable(boolean isNrAvailable) {
+ mIsNrAvailable = isNrAvailable;
+ return this;
+ }
+
+ /**
+ * Sets whether E-UTRA-NR Dual Connectivity (EN-DC) is supported by the primary serving
+ * cell.
+ * @param isEnDcAvailable {@code true} if EN_DC is supported.
+ */
+ public @NonNull Builder setEnDcAvailable(boolean isEnDcAvailable) {
+ mIsEnDcAvailable = isEnDcAvailable;
+ return this;
+ }
+
+ /**
+ * Sets the network support info for VoPS and Emergency bearer support.
+ * @param vops The network support info for VoPS and Emergency bearer support.
+ */
+ @Nullable
+ public @NonNull Builder setVopsSupportInfo(VopsSupportInfo vops) {
+ mVopsSupportInfo = vops;
+ return this;
+ }
+
+ /**
+ * Sets the LTE attach type.
+ * @param lteAttachResultType the Lte attach type
+ */
+ public @NonNull Builder setLteAttachResultType(
+ @LteAttachResultType int lteAttachResultType) {
+ mLteAttachResultType = lteAttachResultType;
+ return this;
+ }
+
+ /**
+ * Sets the extra information of LTE attachment.
+ * @param lteAttachExtraInfo the extra information of LTE attachment.
+ */
+ public @NonNull Builder setLteAttachExtraInfo(
+ @LteAttachExtraInfo int lteAttachExtraInfo) {
+ mLteAttachExtraInfo = lteAttachExtraInfo;
+ return this;
+ }
+
+ /**
+ * @return a built {@link DataSpecificRegistrationInfo} instance.
+ */
+ public @NonNull DataSpecificRegistrationInfo build() {
+ return new DataSpecificRegistrationInfo(this);
+ }
+ }
}
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index a0467c2..b0552b4 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -20,6 +20,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -39,6 +42,16 @@
* Description of a mobile network registration info
*/
public final class NetworkRegistrationInfo implements Parcelable {
+
+ /**
+ * A new registration state, REGISTRATION_STATE_EMERGENCY, is added to
+ * {@link NetworkRegistrationInfo}. This change will affect the result of getRegistration().
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final long RETURN_REGISTRATION_STATE_EMERGENCY = 255938466L;
+
/**
* Network domain
* @hide
@@ -64,7 +77,8 @@
@IntDef(prefix = "REGISTRATION_STATE_",
value = {REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, REGISTRATION_STATE_HOME,
REGISTRATION_STATE_NOT_REGISTERED_SEARCHING, REGISTRATION_STATE_DENIED,
- REGISTRATION_STATE_UNKNOWN, REGISTRATION_STATE_ROAMING})
+ REGISTRATION_STATE_UNKNOWN, REGISTRATION_STATE_ROAMING,
+ REGISTRATION_STATE_EMERGENCY})
public @interface RegistrationState {}
/**
@@ -103,6 +117,18 @@
*/
@SystemApi
public static final int REGISTRATION_STATE_ROAMING = 5;
+ /**
+ * Emergency attached in EPS or in 5GS.
+ * IMS service will skip emergency registration if the device is in
+ * emergency attached state. {@link #mEmergencyOnly} can be true
+ * even in case it's not in emergency attached state.
+ *
+ * Reference: 3GPP TS 24.301 9.9.3.11 EPS attach type.
+ * Reference: 3GPP TS 24.501 9.11.3.6 5GS registration result.
+ * @hide
+ */
+ @SystemApi
+ public static final int REGISTRATION_STATE_EMERGENCY = 6;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -184,10 +210,11 @@
private final int mTransportType;
/**
- * The initial registration state
+ * The true registration state of network, This is not affected by any carrier config or
+ * resource overlay.
*/
@RegistrationState
- private final int mInitialRegistrationState;
+ private final int mNetworkRegistrationState;
/**
* The registration state that might have been overridden by config
@@ -264,7 +291,7 @@
mDomain = domain;
mTransportType = transportType;
mRegistrationState = registrationState;
- mInitialRegistrationState = registrationState;
+ mNetworkRegistrationState = registrationState;
mRoamingType = (registrationState == REGISTRATION_STATE_ROAMING)
? ServiceState.ROAMING_TYPE_UNKNOWN : ServiceState.ROAMING_TYPE_NOT_ROAMING;
setAccessNetworkTechnology(accessNetworkTechnology);
@@ -312,15 +339,19 @@
@Nullable VopsSupportInfo vopsSupportInfo) {
this(domain, transportType, registrationState, accessNetworkTechnology, rejectCause,
emergencyOnly, availableServices, cellIdentity, rplmn, null,
- new DataSpecificRegistrationInfo(maxDataCalls, isDcNrRestricted, isNrAvailable,
- isEndcAvailable, vopsSupportInfo));
+ new DataSpecificRegistrationInfo.Builder(maxDataCalls)
+ .setDcNrRestricted(isDcNrRestricted)
+ .setNrAvailable(isNrAvailable)
+ .setEnDcAvailable(isEndcAvailable)
+ .setVopsSupportInfo(vopsSupportInfo)
+ .build());
}
private NetworkRegistrationInfo(Parcel source) {
mDomain = source.readInt();
mTransportType = source.readInt();
mRegistrationState = source.readInt();
- mInitialRegistrationState = source.readInt();
+ mNetworkRegistrationState = source.readInt();
mRoamingType = source.readInt();
mAccessNetworkTechnology = source.readInt();
mRejectCause = source.readInt();
@@ -347,7 +378,7 @@
mDomain = nri.mDomain;
mTransportType = nri.mTransportType;
mRegistrationState = nri.mRegistrationState;
- mInitialRegistrationState = nri.mInitialRegistrationState;
+ mNetworkRegistrationState = nri.mNetworkRegistrationState;
mRoamingType = nri.mRoamingType;
mAccessNetworkTechnology = nri.mAccessNetworkTechnology;
mIsUsingCarrierAggregation = nri.mIsUsingCarrierAggregation;
@@ -400,40 +431,81 @@
}
/**
- * @return The registration state.
+ * @return The registration state. Note this value can be affected by the carrier config
+ * override.
*
+ * @deprecated Use {@link #getNetworkRegistrationState}, which is not affected by any carrier
+ * config or resource overlay, instead.
* @hide
*/
+ @Deprecated
@SystemApi
public @RegistrationState int getRegistrationState() {
+ if (mRegistrationState == REGISTRATION_STATE_EMERGENCY) {
+ if (!CompatChanges.isChangeEnabled(RETURN_REGISTRATION_STATE_EMERGENCY)) {
+ if (mAccessNetworkTechnology == TelephonyManager.NETWORK_TYPE_LTE) {
+ return REGISTRATION_STATE_DENIED;
+ } else if (mAccessNetworkTechnology == TelephonyManager.NETWORK_TYPE_NR) {
+ return REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING;
+ }
+ }
+ }
return mRegistrationState;
}
/**
- * @return The initial registration state.
+ * @return The true registration state of network. (This value is not affected by any carrier
+ * config or resource overlay override).
*
* @hide
*/
- public @RegistrationState int getInitialRegistrationState() {
- return mInitialRegistrationState;
+ @SystemApi
+ public @RegistrationState int getNetworkRegistrationState() {
+ return mNetworkRegistrationState;
}
/**
- * @return {@code true} if registered on roaming or home network, {@code false} otherwise.
+ * @return {@code true} if registered on roaming or home network. Note this value can be
+ * affected by the carrier config override.
+ *
+ * @deprecated Use {@link #isNetworkRegistered}, which is not affected by any carrier config or
+ * resource overlay, instead.
*/
+ @Deprecated
public boolean isRegistered() {
return mRegistrationState == REGISTRATION_STATE_HOME
|| mRegistrationState == REGISTRATION_STATE_ROAMING;
}
/**
- * @return {@code true} if searching for service, {@code false} otherwise.
+ * @return {@code true} if registered on roaming or home network, {@code false} otherwise. (This
+ * value is not affected by any carrier config or resource overlay override).
*/
+ public boolean isNetworkRegistered() {
+ return mNetworkRegistrationState == REGISTRATION_STATE_HOME
+ || mNetworkRegistrationState == REGISTRATION_STATE_ROAMING;
+ }
+
+ /**
+ * @return {@code true} if searching for service, {@code false} otherwise.
+ *
+ * @deprecated Use {@link #isNetworkRegistered}, which is not affected by any carrier config or
+ * resource overlay, instead.
+ */
+ @Deprecated
public boolean isSearching() {
return mRegistrationState == REGISTRATION_STATE_NOT_REGISTERED_SEARCHING;
}
/**
+ * @return {@code true} if searching for service, {@code false} otherwise. (This value is not
+ * affected by any carrier config or resource overlay override).
+ */
+ public boolean isNetworkSearching() {
+ return mNetworkRegistrationState == REGISTRATION_STATE_NOT_REGISTERED_SEARCHING;
+ }
+
+ /**
* Get the PLMN-ID for this Network Registration, also known as the RPLMN.
*
* <p>If the device is registered, this will return the registered PLMN-ID. If registration
@@ -450,13 +522,25 @@
}
/**
- * @return {@code true} if registered on roaming network, {@code false} otherwise.
+ * @return {@code true} if registered on roaming network overridden by config. Note this value
+ * can be affected by the carrier config override.
+ *
+ * @deprecated Use {@link TelephonyDisplayInfo#isRoaming} instead.
*/
+ @Deprecated
public boolean isRoaming() {
return mRoamingType != ServiceState.ROAMING_TYPE_NOT_ROAMING;
}
/**
+ * @return {@code true} if registered on roaming network. (This value is not affected by any
+ * carrier config or resource overlay override).
+ */
+ public boolean isNetworkRoaming() {
+ return mNetworkRegistrationState == REGISTRATION_STATE_ROAMING;
+ }
+
+ /**
* @hide
* @return {@code true} if in service.
*/
@@ -486,7 +570,8 @@
}
/**
- * @return the current network roaming type.
+ * @return the current network roaming type. Note that this value can be possibly overridden by
+ * the carrier config or resource overlay.
* @hide
*/
@SystemApi
@@ -630,6 +715,7 @@
case REGISTRATION_STATE_DENIED: return "DENIED";
case REGISTRATION_STATE_UNKNOWN: return "UNKNOWN";
case REGISTRATION_STATE_ROAMING: return "ROAMING";
+ case REGISTRATION_STATE_EMERGENCY: return "EMERGENCY";
}
return "Unknown reg state " + registrationState;
}
@@ -666,8 +752,8 @@
.append(" transportType=").append(
AccessNetworkConstants.transportTypeToString(mTransportType))
.append(" registrationState=").append(registrationStateToString(mRegistrationState))
- .append(" mInitialRegistrationState=")
- .append(registrationStateToString(mInitialRegistrationState))
+ .append(" networkRegistrationState=")
+ .append(registrationStateToString(mNetworkRegistrationState))
.append(" roamingType=").append(ServiceState.roamingTypeToString(mRoamingType))
.append(" accessNetworkTechnology=")
.append(TelephonyManager.getNetworkTypeName(mAccessNetworkTechnology))
@@ -688,7 +774,7 @@
@Override
public int hashCode() {
- return Objects.hash(mDomain, mTransportType, mRegistrationState, mInitialRegistrationState,
+ return Objects.hash(mDomain, mTransportType, mRegistrationState, mNetworkRegistrationState,
mRoamingType, mAccessNetworkTechnology, mRejectCause, mEmergencyOnly,
mAvailableServices, mCellIdentity, mVoiceSpecificInfo, mDataSpecificInfo, mNrState,
mRplmn, mIsUsingCarrierAggregation);
@@ -706,7 +792,7 @@
return mDomain == other.mDomain
&& mTransportType == other.mTransportType
&& mRegistrationState == other.mRegistrationState
- && mInitialRegistrationState == other.mInitialRegistrationState
+ && mNetworkRegistrationState == other.mNetworkRegistrationState
&& mRoamingType == other.mRoamingType
&& mAccessNetworkTechnology == other.mAccessNetworkTechnology
&& mRejectCause == other.mRejectCause
@@ -729,7 +815,7 @@
dest.writeInt(mDomain);
dest.writeInt(mTransportType);
dest.writeInt(mRegistrationState);
- dest.writeInt(mInitialRegistrationState);
+ dest.writeInt(mNetworkRegistrationState);
dest.writeInt(mRoamingType);
dest.writeInt(mAccessNetworkTechnology);
dest.writeInt(mRejectCause);
@@ -826,7 +912,7 @@
private int mTransportType;
@RegistrationState
- private int mInitialRegistrationState;
+ private int mNetworkRegistrationState;
@NetworkType
private int mAccessNetworkTechnology;
@@ -864,7 +950,7 @@
public Builder(@NonNull NetworkRegistrationInfo nri) {
mDomain = nri.mDomain;
mTransportType = nri.mTransportType;
- mInitialRegistrationState = nri.mInitialRegistrationState;
+ mNetworkRegistrationState = nri.mNetworkRegistrationState;
mAccessNetworkTechnology = nri.mAccessNetworkTechnology;
mRejectCause = nri.mRejectCause;
mEmergencyOnly = nri.mEmergencyOnly;
@@ -912,7 +998,7 @@
* @return The same instance of the builder.
*/
public @NonNull Builder setRegistrationState(@RegistrationState int registrationState) {
- mInitialRegistrationState = registrationState;
+ mNetworkRegistrationState = registrationState;
return this;
}
@@ -1031,7 +1117,7 @@
*/
@SystemApi
public @NonNull NetworkRegistrationInfo build() {
- return new NetworkRegistrationInfo(mDomain, mTransportType, mInitialRegistrationState,
+ return new NetworkRegistrationInfo(mDomain, mTransportType, mNetworkRegistrationState,
mAccessNetworkTechnology, mRejectCause, mEmergencyOnly, mAvailableServices,
mCellIdentity, mRplmn, mVoiceSpecificRegistrationInfo,
mDataSpecificRegistrationInfo);
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index cd1a40a..523d0b0 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -630,11 +630,17 @@
}
/**
- * Get current roaming indicator of phone
+ * Get current roaming indicator of phone. This roaming state could be overridden by the carrier
+ * config.
* (note: not just decoding from TS 27.007 7.2)
- *
+ * @see TelephonyDisplayInfo#isRoaming() for visualization purpose.
* @return true if TS 27.007 7.2 roaming is true
* and ONS is different from SPN
+ * @see CarrierConfigManager#KEY_FORCE_HOME_NETWORK_BOOL
+ * @see CarrierConfigManager#KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY
+ * @see CarrierConfigManager#KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY
+ * @see CarrierConfigManager#KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY
+ * @see CarrierConfigManager#KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY
*/
public boolean getRoaming() {
return getVoiceRoaming() || getDataRoaming();
@@ -649,8 +655,9 @@
public boolean getVoiceRoaming() {
return getVoiceRoamingType() != ROAMING_TYPE_NOT_ROAMING;
}
+
/**
- * Get current voice network roaming type
+ * Get current voice roaming type. This roaming type could be overridden by the carrier config.
* @return roaming type
* @hide
*/
@@ -700,7 +707,7 @@
}
/**
- * Get current data network roaming type
+ * Get current data roaming type. This roaming type could be overridden by the carrier config.
* @return roaming type
* @hide
*/
diff --git a/telephony/java/android/telephony/SignalThresholdInfo.java b/telephony/java/android/telephony/SignalThresholdInfo.java
index 7053b44..80f1de1 100644
--- a/telephony/java/android/telephony/SignalThresholdInfo.java
+++ b/telephony/java/android/telephony/SignalThresholdInfo.java
@@ -17,6 +17,7 @@
package android.telephony;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
@@ -169,11 +170,18 @@
public static final int HYSTERESIS_MS_DISABLED = 0;
/**
- * Indicates the hysteresisDb is disabled.
+ * Indicates the default hysteresis value in dB.
*
* @hide
*/
- public static final int HYSTERESIS_DB_DISABLED = 0;
+ private static final int HYSTERESIS_DB_DEFAULT = 2;
+
+ /**
+ * Indicates the hysteresisDb value is not set and to be initialised to default value.
+ *
+ * @hide
+ */
+ public static final int HYSTERESIS_DB_MINIMUM = 0;
/**
* Minimum valid value for {@link #SIGNAL_MEASUREMENT_TYPE_RSSI}.
@@ -339,7 +347,7 @@
mRan = ran;
mSignalMeasurementType = signalMeasurementType;
mHysteresisMs = hysteresisMs < 0 ? HYSTERESIS_MS_DISABLED : hysteresisMs;
- mHysteresisDb = hysteresisDb < 0 ? HYSTERESIS_DB_DISABLED : hysteresisDb;
+ mHysteresisDb = hysteresisDb;
mThresholds = thresholds;
mIsEnabled = isEnabled;
}
@@ -351,7 +359,7 @@
private int mRan = AccessNetworkConstants.AccessNetworkType.UNKNOWN;
private int mSignalMeasurementType = SIGNAL_MEASUREMENT_TYPE_UNKNOWN;
private int mHysteresisMs = HYSTERESIS_MS_DISABLED;
- private int mHysteresisDb = HYSTERESIS_DB_DISABLED;
+ private int mHysteresisDb = HYSTERESIS_DB_DEFAULT;
private int[] mThresholds = null;
private boolean mIsEnabled = false;
@@ -361,7 +369,8 @@
* @param ran The radio access network type
* @return the builder to facilitate the chaining
*/
- public @NonNull Builder setRadioAccessNetworkType(
+ @NonNull
+ public Builder setRadioAccessNetworkType(
@AccessNetworkConstants.RadioAccessNetworkType int ran) {
mRan = ran;
return this;
@@ -373,7 +382,8 @@
* @param signalMeasurementType The signal measurement type
* @return the builder to facilitate the chaining
*/
- public @NonNull Builder setSignalMeasurementType(
+ @NonNull
+ public Builder setSignalMeasurementType(
@SignalMeasurementType int signalMeasurementType) {
mSignalMeasurementType = signalMeasurementType;
return this;
@@ -387,20 +397,27 @@
* @return the builder to facilitate the chaining
* @hide
*/
- public @NonNull Builder setHysteresisMs(int hysteresisMs) {
+ @NonNull
+ public Builder setHysteresisMs(int hysteresisMs) {
mHysteresisMs = hysteresisMs;
return this;
}
/**
- * Set the interval in dB defining the required magnitude change between reports. A value of
- * zero disabled dB-based hysteresis restrictions.
+ * Set the interval in dB defining the required minimum magnitude change to report a
+ * signal strength change. A value of zero disables dB-based hysteresis restrictions.
+ * Note:
+ * <p>Default hysteresis db value is 2. Minimum hysteresis db value allowed to set is 0.
+ * If hysteresis db value is not set, default hysteresis db value of 2 will be used.
*
* @param hysteresisDb the interval in dB
* @return the builder to facilitate the chaining
- * @hide
*/
- public @NonNull Builder setHysteresisDb(int hysteresisDb) {
+ @NonNull
+ public Builder setHysteresisDb(@IntRange(from = 0) int hysteresisDb) {
+ if (hysteresisDb < 0) {
+ throw new IllegalArgumentException("hysteresis db value should not be less than 0");
+ }
mHysteresisDb = hysteresisDb;
return this;
}
@@ -428,7 +445,8 @@
* @see #SIGNAL_MEASUREMENT_TYPE_ECNO
* @see #getThresholds() for more details on signal strength thresholds
*/
- public @NonNull Builder setThresholds(@NonNull int[] thresholds) {
+ @NonNull
+ public Builder setThresholds(@NonNull int[] thresholds) {
return setThresholds(thresholds, false /*isSystem*/);
}
@@ -442,7 +460,8 @@
*
* @hide
*/
- public @NonNull Builder setThresholds(@NonNull int[] thresholds, boolean isSystem) {
+ @NonNull
+ public Builder setThresholds(@NonNull int[] thresholds, boolean isSystem) {
Objects.requireNonNull(thresholds, "thresholds must not be null");
if (!isSystem
&& (thresholds.length < MINIMUM_NUMBER_OF_THRESHOLDS_ALLOWED
@@ -465,7 +484,8 @@
* @return the builder to facilitate the chaining
* @hide
*/
- public @NonNull Builder setIsEnabled(boolean isEnabled) {
+ @NonNull
+ public Builder setIsEnabled(boolean isEnabled) {
mIsEnabled = isEnabled;
return this;
}
@@ -479,7 +499,8 @@
* the thresholds is out of range, or the RAN is not allowed to set with the signal
* measurement type
*/
- public @NonNull SignalThresholdInfo build() {
+ @NonNull
+ public SignalThresholdInfo build() {
return new SignalThresholdInfo(
mRan,
mSignalMeasurementType,
@@ -495,7 +516,8 @@
*
* @return radio access network type
*/
- public @AccessNetworkConstants.RadioAccessNetworkType int getRadioAccessNetworkType() {
+ @AccessNetworkConstants.RadioAccessNetworkType
+ public int getRadioAccessNetworkType() {
return mRan;
}
@@ -504,7 +526,8 @@
*
* @return the SignalMeasurementType value
*/
- public @SignalMeasurementType int getSignalMeasurementType() {
+ @SignalMeasurementType
+ public int getSignalMeasurementType() {
return mSignalMeasurementType;
}
@@ -513,7 +536,11 @@
return mHysteresisMs;
}
- /** @hide */
+ /**
+ * Get measurement hysteresis db.
+ *
+ * @return hysteresis db value
+ */
public int getHysteresisDb() {
return mHysteresisDb;
}
@@ -544,7 +571,8 @@
* @see #SIGNAL_MEASUREMENT_TYPE_SSSINR
* @see #SIGNAL_MEASUREMENT_TYPE_ECNO
*/
- public @NonNull int[] getThresholds() {
+ @NonNull
+ public int[] getThresholds() {
return mThresholds.clone();
}
@@ -618,7 +646,8 @@
mIsEnabled);
}
- public static final @NonNull Parcelable.Creator<SignalThresholdInfo> CREATOR =
+ @NonNull
+ public static final Parcelable.Creator<SignalThresholdInfo> CREATOR =
new Parcelable.Creator<SignalThresholdInfo>() {
@Override
public SignalThresholdInfo createFromParcel(Parcel in) {
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 8e8755d..4afc943 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1730,31 +1730,30 @@
}
/**
- * Get all subscription info records from SIMs that are inserted now or were inserted before.
+ * Get all subscription info records from SIMs that are inserted now or previously inserted.
*
* <p>
* If the caller does not have {@link Manifest.permission#READ_PHONE_NUMBERS} permission,
* {@link SubscriptionInfo#getNumber()} will return empty string.
* If the caller does not have {@link Manifest.permission#USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER},
- * {@link SubscriptionInfo#getIccId()} and {@link SubscriptionInfo#getCardString()} will return
- * empty string, and {@link SubscriptionInfo#getGroupUuid()} will return {@code null}.
+ * {@link SubscriptionInfo#getIccId()} will return an empty string, and
+ * {@link SubscriptionInfo#getGroupUuid()} will return {@code null}.
*
* <p>
- * The carrier app will always have full {@link SubscriptionInfo} for the subscriptions
- * that it has carrier privilege.
+ * The carrier app will only get the list of subscriptions that it has carrier privilege on,
+ * but will have non-stripped {@link SubscriptionInfo} in the list.
*
* @return List of all {@link SubscriptionInfo} records from SIMs that are inserted or
- * inserted before. Sorted by {@link SubscriptionInfo#getSimSlotIndex()}, then
+ * previously inserted. Sorted by {@link SubscriptionInfo#getSimSlotIndex()}, then
* {@link SubscriptionInfo#getSubscriptionId()}.
*
- * @hide
+ * @throws SecurityException if callers do not hold the required permission.
*/
+ @NonNull
@RequiresPermission(anyOf = {
Manifest.permission.READ_PHONE_STATE,
- Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
"carrier privileges",
})
- @NonNull
public List<SubscriptionInfo> getAllSubscriptionInfoList() {
List<SubscriptionInfo> result = null;
try {
@@ -2228,13 +2227,23 @@
}
/**
- * Get an array of Subscription Ids for specified slot Index.
- * @param slotIndex the slot index.
- * @return subscription Ids or null if the given slot Index is not valid or there are no active
- * subscriptions in the slot.
+ * Get an array of subscription ids for specified logical SIM slot Index.
+ *
+ * @param slotIndex The logical SIM slot index.
+ *
+ * @return subscription Ids or {@code null} if the given slot index is not valid or there are
+ * no active subscription in the slot. In the implementation today, there will be no more
+ * than one subscriptions per logical SIM slot.
+ *
+ * @deprecated Use {@link #getSubscriptionId(int)} instead.
*/
+ @Deprecated
@Nullable
public int[] getSubscriptionIds(int slotIndex) {
+ int subId = getSubscriptionId(slotIndex);
+ if (!isValidSubscriptionId(subId)) {
+ return null;
+ }
return new int[]{getSubscriptionId(slotIndex)};
}
@@ -2261,12 +2270,10 @@
}
/**
- * Get the subscription id for specified slot index.
+ * Get the subscription id for specified logical SIM slot index.
*
- * @param slotIndex Logical SIM slot index.
+ * @param slotIndex The logical SIM slot index.
* @return The subscription id. {@link #INVALID_SUBSCRIPTION_ID} if SIM is absent.
- *
- * @hide
*/
public static int getSubscriptionId(int slotIndex) {
if (!isValidSlotIndex(slotIndex)) {
diff --git a/telephony/java/android/telephony/TelephonyDisplayInfo.java b/telephony/java/android/telephony/TelephonyDisplayInfo.java
index f4e2ade..e01b10e 100644
--- a/telephony/java/android/telephony/TelephonyDisplayInfo.java
+++ b/telephony/java/android/telephony/TelephonyDisplayInfo.java
@@ -87,10 +87,12 @@
public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 5;
@NetworkType
- private final int mNetworkType;
+ private final int mNetworkType;
@OverrideNetworkType
- private final int mOverrideNetworkType;
+ private final int mOverrideNetworkType;
+
+ private final boolean mIsRoaming;
/**
* Constructor
@@ -98,18 +100,37 @@
* @param networkType Current packet-switching cellular network type
* @param overrideNetworkType The override network type
*
+ * @deprecated will not use this constructor anymore.
+ * @hide
+ */
+ @Deprecated
+ public TelephonyDisplayInfo(@NetworkType int networkType,
+ @OverrideNetworkType int overrideNetworkType) {
+ this(networkType, overrideNetworkType, false);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param networkType Current packet-switching cellular network type
+ * @param overrideNetworkType The override network type
+ * @param isRoaming True if the device is roaming after override.
+ *
* @hide
*/
public TelephonyDisplayInfo(@NetworkType int networkType,
- @OverrideNetworkType int overrideNetworkType) {
+ @OverrideNetworkType int overrideNetworkType,
+ boolean isRoaming) {
mNetworkType = networkType;
mOverrideNetworkType = overrideNetworkType;
+ mIsRoaming = isRoaming;
}
/** @hide */
public TelephonyDisplayInfo(Parcel p) {
mNetworkType = p.readInt();
mOverrideNetworkType = p.readInt();
+ mIsRoaming = p.readBoolean();
}
/**
@@ -135,10 +156,25 @@
return mOverrideNetworkType;
}
+ /**
+ * Get device is roaming or not. Note the isRoaming is for market branding or visualization
+ * purposes only. It cannot be treated as the actual roaming device is camped on.
+ *
+ * @return True if the device is registered on roaming network overridden by config.
+ * @see CarrierConfigManager#KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY
+ * @see CarrierConfigManager#KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY
+ * @see CarrierConfigManager#KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY
+ * @see CarrierConfigManager#KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY
+ */
+ public boolean isRoaming() {
+ return mIsRoaming;
+ }
+
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mNetworkType);
dest.writeInt(mOverrideNetworkType);
+ dest.writeBoolean(mIsRoaming);
}
public static final @NonNull Parcelable.Creator<TelephonyDisplayInfo> CREATOR =
@@ -165,12 +201,13 @@
if (o == null || getClass() != o.getClass()) return false;
TelephonyDisplayInfo that = (TelephonyDisplayInfo) o;
return mNetworkType == that.mNetworkType
- && mOverrideNetworkType == that.mOverrideNetworkType;
+ && mOverrideNetworkType == that.mOverrideNetworkType
+ && mIsRoaming == that.mIsRoaming;
}
@Override
public int hashCode() {
- return Objects.hash(mNetworkType, mOverrideNetworkType);
+ return Objects.hash(mNetworkType, mOverrideNetworkType, mIsRoaming);
}
/**
@@ -195,6 +232,7 @@
@Override
public String toString() {
return "TelephonyDisplayInfo {network=" + TelephonyManager.getNetworkTypeName(mNetworkType)
- + ", override=" + overrideNetworkTypeToString(mOverrideNetworkType) + "}";
+ + ", overrideNetwork=" + overrideNetworkTypeToString(mOverrideNetworkType)
+ + ", isRoaming=" + mIsRoaming + "}";
}
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 2a667e7..059f4c3 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -17280,7 +17280,7 @@
* During the setup time, subsequent attempts will return
* {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP}.
* After setup is complete, subsequent attempts will return
- * {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED} until the booster expires.
+ * {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED} until the boost expires.
* The expiry time is determined by the type or duration of boost purchased from the carrier,
* provided at {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING}.
*/
@@ -17291,13 +17291,13 @@
* If purchasing premium capabilities is throttled, it will be for the amount of time
* specified by {@link CarrierConfigManager
* #KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}.
- * If displaying the network boost notification is throttled, it will be for the amount of time
- * specified by {@link CarrierConfigManager
+ * If displaying the performance boost notification is throttled, it will be for the amount of
+ * time specified by {@link CarrierConfigManager
* #KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}.
- * If a foreground application requests premium capabilities, the network boost notification
+ * If a foreground application requests premium capabilities, the performance boost notification
* will be displayed to the user regardless of the throttled status.
- * We will show the network boost notification to the user up to the daily and monthly maximum
- * number of times specified by
+ * We will show the performance boost notification to the user up to the daily and monthly
+ * maximum number of times specified by
* {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT} and
* {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_MAXIMUM_MONTHLY_NOTIFICATION_COUNT_INT}.
* Subsequent attempts will return the same error until the request is no longer throttled
@@ -17307,7 +17307,7 @@
/**
* Purchase premium capability failed because it is already purchased and available.
- * Subsequent attempts will return the same error until the booster expires.
+ * Subsequent attempts will return the same error until the performance boost expires.
*/
public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED = 3;
@@ -17356,10 +17356,10 @@
/**
* Purchase premium capability failed because we did not receive a response from the user
- * for the booster notification within the time specified by
+ * for the performance boost notification within the time specified by
* {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG}.
- * The booster notification will be automatically dismissed and subsequent attempts will be
- * throttled for the amount of time specified by
+ * The performance boost notification will be automatically dismissed and subsequent attempts
+ * will be throttled for the amount of time specified by
* {@link CarrierConfigManager
* #KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}
* and return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED}.
diff --git a/telephony/java/android/telephony/ims/MediaQualityStatus.aidl b/telephony/java/android/telephony/ims/MediaQualityStatus.aidl
new file mode 100644
index 0000000..e570f6c
--- /dev/null
+++ b/telephony/java/android/telephony/ims/MediaQualityStatus.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.ims;
+
+parcelable MediaQualityStatus;
\ No newline at end of file
diff --git a/telephony/java/android/telephony/ims/MediaQualityStatus.java b/telephony/java/android/telephony/ims/MediaQualityStatus.java
new file mode 100644
index 0000000..62c289a
--- /dev/null
+++ b/telephony/java/android/telephony/ims/MediaQualityStatus.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.ims;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.AccessNetworkConstants.TransportType;
+
+import java.util.Objects;
+
+/**
+ * A representation of Media quality status.
+ *
+ * @hide
+ */
+@SystemApi
+public final class MediaQualityStatus implements Parcelable {
+ public static final int MEDIA_SESSION_TYPE_AUDIO = 1;
+ public static final int MEDIA_SESSION_TYPE_VIDEO = 2;
+
+ private final String mImsCallSessionId;
+ private final int mMediaSessionType;
+ private final int mTransportType;
+ private final int mRtpPacketLossRate;
+ private final int mRtpJitter;
+ private final long mRtpInactivityTimeMillis;
+
+ /** @hide */
+ @IntDef(
+ value = {
+ MEDIA_SESSION_TYPE_AUDIO,
+ MEDIA_SESSION_TYPE_VIDEO,
+ })
+ public @interface MediaSessionType {}
+
+ /**
+ * Constructor for this
+ *
+ * @param imsCallSessionId IMS call session id of this quality status
+ * @param mediaSessionType media session type of this quality status
+ * @param transportType transport type of this quality status
+ * @param rtpPacketLossRate measured RTP packet loss rate
+ * @param rtpJitter measured RTP jitter value
+ * @param rptInactivityTimeMillis measured RTP inactivity time in milliseconds
+ */
+ private MediaQualityStatus(@NonNull String imsCallSessionId,
+ @MediaSessionType int mediaSessionType, @TransportType int transportType,
+ int rtpPacketLossRate, int rtpJitter, long rptInactivityTimeMillis) {
+ mImsCallSessionId = imsCallSessionId;
+ mMediaSessionType = mediaSessionType;
+ mTransportType = transportType;
+ mRtpPacketLossRate = rtpPacketLossRate;
+ mRtpJitter = rtpJitter;
+ mRtpInactivityTimeMillis = rptInactivityTimeMillis;
+ }
+
+ /**
+ * Retrieves call session ID for this quality status
+ */
+ @NonNull
+ public String getCallSessionId() {
+ return mImsCallSessionId;
+ }
+
+ /**
+ * Retrieves media session type of this quality status
+ */
+ public @MediaSessionType int getMediaSessionType() {
+ return mMediaSessionType;
+ }
+
+
+ /**
+ * Retrieves Transport type for which this media quality was measured.
+ */
+ public @TransportType int getTransportType() {
+ return mTransportType;
+ }
+
+ /**
+ * Retrieves measured RTP packet loss rate in percentage.
+ */
+ @IntRange(from = 0, to = 100)
+ public int getRtpPacketLossRate() {
+ return mRtpPacketLossRate;
+ }
+
+ /**
+ * Retrieves measured RTP jitter(RFC3550) value in milliseconds
+ */
+ public int getRtpJitterMillis() {
+ return mRtpJitter;
+ }
+
+ /**
+ * Retrieves measured RTP inactivity time in milliseconds
+ */
+ public long getRtpInactivityMillis() {
+ return mRtpInactivityTimeMillis;
+ }
+
+ /**
+ * Creates a new instance of {@link MediaQualityStatus} from a parcel.
+ * @param in The parceled data to read.
+ */
+ private MediaQualityStatus(@NonNull Parcel in) {
+ mImsCallSessionId = in.readString();
+ mMediaSessionType = in.readInt();
+ mTransportType = in.readInt();
+ mRtpPacketLossRate = in.readInt();
+ mRtpJitter = in.readInt();
+ mRtpInactivityTimeMillis = in.readLong();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mImsCallSessionId);
+ dest.writeInt(mMediaSessionType);
+ dest.writeInt(mTransportType);
+ dest.writeInt(mRtpPacketLossRate);
+ dest.writeInt(mRtpJitter);
+ dest.writeLong(mRtpInactivityTimeMillis);
+ }
+
+ public static final @NonNull Creator<MediaQualityStatus> CREATOR =
+ new Creator<MediaQualityStatus>() {
+ @Override
+ public MediaQualityStatus createFromParcel(@NonNull Parcel in) {
+ return new MediaQualityStatus(in);
+ }
+
+ @Override
+ public MediaQualityStatus[] newArray(int size) {
+ return new MediaQualityStatus[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ MediaQualityStatus that = (MediaQualityStatus) o;
+ return mImsCallSessionId != null && mImsCallSessionId.equals(that.mImsCallSessionId)
+ && mMediaSessionType == that.mMediaSessionType
+ && mTransportType == that.mTransportType
+ && mRtpPacketLossRate == that.mRtpPacketLossRate
+ && mRtpJitter == that.mRtpJitter
+ && mRtpInactivityTimeMillis == that.mRtpInactivityTimeMillis;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mImsCallSessionId, mMediaSessionType, mTransportType,
+ mRtpPacketLossRate, mRtpJitter, mRtpInactivityTimeMillis);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("MediaThreshold{mImsCallSessionId=");
+ sb.append(mImsCallSessionId);
+ sb.append(", mMediaSessionType=");
+ sb.append(mMediaSessionType);
+ sb.append(", mTransportType=");
+ sb.append(mTransportType);
+ sb.append(", mRtpPacketLossRate=");
+ sb.append(mRtpPacketLossRate);
+ sb.append(", mRtpJitter=");
+ sb.append(mRtpJitter);
+ sb.append(", mRtpInactivityTimeMillis=");
+ sb.append(mRtpInactivityTimeMillis);
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Provides a convenient way to set the fields of an {@link MediaQualityStatus} when creating a
+ * new instance.
+ *
+ * <p>The example below shows how you might create a new {@code RtpQualityStatus}:
+ *
+ * <pre><code>
+ *
+ * MediaQualityStatus = new MediaQualityStatus.Builder(
+ * callSessionId, mediaSessionType, transportType)
+ * .setRtpPacketLossRate(packetLossRate)
+ * .setRtpJitter(jitter)
+ * .setRtpInactivityMillis(inactivityTimeMillis)
+ * .build();
+ * </code></pre>
+ */
+ public static final class Builder {
+ private final String mImsCallSessionId;
+ private final int mMediaSessionType;
+ private final int mTransportType;
+ private int mRtpPacketLossRate;
+ private int mRtpJitter;
+ private long mRtpInactivityTimeMillis;
+
+ /**
+ * Default constructor for the Builder.
+ */
+ public Builder(
+ @NonNull String imsCallSessionId,
+ @MediaSessionType int mediaSessionType,
+ int transportType) {
+ mImsCallSessionId = imsCallSessionId;
+ mMediaSessionType = mediaSessionType;
+ mTransportType = transportType;
+ }
+
+ /**
+ * Set RTP packet loss info.
+ *
+ * @param packetLossRate RTP packet loss rate in percentage
+ * @return The same instance of the builder.
+ */
+ @NonNull
+ public Builder setRtpPacketLossRate(@IntRange(from = 0, to = 100) int packetLossRate) {
+ this.mRtpPacketLossRate = packetLossRate;
+ return this;
+ }
+
+ /**
+ * Set calculated RTP jitter(RFC3550) value in milliseconds.
+ *
+ * @param jitter calculated RTP jitter value.
+ * @return The same instance of the builder.
+ */
+ @NonNull
+ public Builder setRtpJitterMillis(int jitter) {
+ this.mRtpJitter = jitter;
+ return this;
+ }
+
+ /**
+ * Set measured RTP inactivity time.
+ *
+ * @param inactivityTimeMillis RTP inactivity time in Milliseconds.
+ * @return The same instance of the builder.
+ */
+ @NonNull
+ public Builder setRtpInactivityMillis(long inactivityTimeMillis) {
+ this.mRtpInactivityTimeMillis = inactivityTimeMillis;
+ return this;
+ }
+
+ /**
+ * Build the {@link MediaQualityStatus}
+ *
+ * @return the {@link MediaQualityStatus} object
+ */
+ @NonNull
+ public MediaQualityStatus build() {
+ return new MediaQualityStatus(
+ mImsCallSessionId,
+ mMediaSessionType,
+ mTransportType,
+ mRtpPacketLossRate,
+ mRtpJitter,
+ mRtpInactivityTimeMillis);
+ }
+ }
+}
diff --git a/telephony/java/android/telephony/ims/MediaThreshold.aidl b/telephony/java/android/telephony/ims/MediaThreshold.aidl
new file mode 100644
index 0000000..dede418
--- /dev/null
+++ b/telephony/java/android/telephony/ims/MediaThreshold.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.ims;
+
+parcelable MediaThreshold;
\ No newline at end of file
diff --git a/telephony/java/android/telephony/ims/MediaThreshold.java b/telephony/java/android/telephony/ims/MediaThreshold.java
new file mode 100644
index 0000000..343aa4a
--- /dev/null
+++ b/telephony/java/android/telephony/ims/MediaThreshold.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.ims;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.ims.feature.MmTelFeature;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.TreeSet;
+
+/**
+ * A MediaThreshold represents a series of packet loss rate, jitter and rtp inactivity time
+ * thresholds which when crossed should result in a {@link MediaQualityStatus} report being
+ * generated by the {@link ImsService} via {@link MmTelFeature#notifyMediaQualityStatusChanged(
+ * MediaQualityStatus)}
+ *
+ * <p/>
+ * A {@link MediaQualityStatus} should be triggered when any of various
+ * attributes pass one of the thresholds defined here.
+ *
+ * @hide
+ */
+@SystemApi
+public final class MediaThreshold implements Parcelable {
+ private final int[] mRtpPacketLossRate;
+ private final int[] mRtpJitter;
+ private final long[] mRtpInactivityTimeMillis;
+
+ /**
+ * Retrieves threshold values for RTP packet loss rate in percentage.
+ *
+ * @return int array including threshold values for packet loss rate
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public int[] getThresholdsRtpPacketLossRate() {
+ return mRtpPacketLossRate;
+ }
+
+ /**
+ * Retrieves threshold values for jitter(RFC3550) in milliseconds.
+ *
+ * @return int array including threshold values for RTP jitter.
+ */
+ @NonNull
+ public int[] getThresholdsRtpJitterMillis() {
+ return mRtpJitter;
+ }
+
+ /**
+ * Retrieves threshold values for RTP inactivity time in milliseconds.
+ *
+ * @return int array including threshold values for RTP inactivity time.
+ */
+ @NonNull
+ public long[] getThresholdsRtpInactivityTimeMillis() {
+ return mRtpInactivityTimeMillis;
+ }
+
+ private MediaThreshold(
+ int[] packetLossRateThresholds,
+ int[] jitterThresholds,
+ long[] inactivityTimeThresholds) {
+ mRtpPacketLossRate = packetLossRateThresholds;
+ mRtpJitter = jitterThresholds;
+ mRtpInactivityTimeMillis = inactivityTimeThresholds;
+ }
+
+ /**
+ * Creates a new instance of {@link MediaThreshold} from a parcel.
+ * @param in The parceled data to read.
+ */
+ private MediaThreshold(@NonNull Parcel in) {
+ mRtpPacketLossRate = in.createIntArray();
+ mRtpJitter = in.createIntArray();
+ mRtpInactivityTimeMillis = in.createLongArray();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeIntArray(mRtpPacketLossRate);
+ dest.writeIntArray(mRtpJitter);
+ dest.writeLongArray(mRtpInactivityTimeMillis);
+ }
+
+ public static final @NonNull Creator<MediaThreshold> CREATOR =
+ new Creator<MediaThreshold>() {
+ @Override
+ public MediaThreshold createFromParcel(@NonNull Parcel in) {
+ return new MediaThreshold(in);
+ }
+
+ @Override
+ public MediaThreshold[] newArray(int size) {
+ return new MediaThreshold[size];
+ }
+ };
+
+ /**
+ * Returns whether the RTP packet loss rate threshold is valid or not.
+ *
+ * @param packetLossRate packet loss rate
+ * @return the threshold is valid or not.
+ * @hide
+ */
+ public static boolean isValidRtpPacketLossRate(int packetLossRate) {
+ return (packetLossRate >= 0 && packetLossRate <= 100);
+ }
+
+ /**
+ * Returns whether the RTP jitter threshold is valid or not.
+ *
+ * @param jitter jitter value in milliseconds
+ * @return the threshold is valid or not.
+ * @hide
+ */
+ public static boolean isValidJitterMillis(int jitter) {
+ return (jitter >= 0 && jitter <= 10000);
+ }
+
+ /**
+ * Returns whether the RTP packet loss rate threshold is valid or not.
+ *
+ * @param inactivityTime packet loss rate
+ * @return the threshold is valid or not.
+ * @hide
+ */
+ public static boolean isValidRtpInactivityTimeMillis(long inactivityTime) {
+ return (inactivityTime >= 0 && inactivityTime <= 60000);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ MediaThreshold that = (MediaThreshold) o;
+ return Arrays.equals(mRtpPacketLossRate, that.mRtpPacketLossRate)
+ && Arrays.equals(mRtpJitter, that.mRtpJitter)
+ && Arrays.equals(mRtpInactivityTimeMillis, that.mRtpInactivityTimeMillis);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(Arrays.hashCode(mRtpPacketLossRate), Arrays.hashCode(mRtpJitter),
+ Arrays.hashCode(mRtpInactivityTimeMillis));
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("MediaThreshold{mRtpPacketLossRate=");
+ for (int i : mRtpPacketLossRate) {
+ sb.append(" ").append(i);
+ }
+ sb.append(", mRtpJitter=");
+ for (int b : mRtpJitter) {
+ sb.append(" ").append(b);
+ }
+ sb.append(", mRtpInactivityTimeMillis=");
+ for (long i : mRtpInactivityTimeMillis) {
+ sb.append(" ").append(i);
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Provides a convenient way to set the fields of an {@link MediaThreshold} when creating a
+ * new instance.
+ *
+ * <p>The example below shows how you might create a new {@code RtpThreshold}:
+ *
+ * <pre><code>
+ *
+ * RtpThreshold = new RtpThreshold.Builder()
+ * .setRtpSessionType({@link MediaQualityStatus#MEDIA_SESSION_TYPE_AUDIO} or
+ * {@link MediaQualityStatus#MEDIA_SESSION_TYPE_VIDEO})
+ * .setThresholdsRtpPacketLossRate(int[] packetLossRateThresholds)
+ * .setThresholdsRtpJitterMillis(int[] jitterThresholds)
+ * .setThresholdsRtpInactivityTimeMillis(int[] inactivityTimeThresholds)
+ * .build();
+ * </code></pre>
+ *
+ * @hide
+ */
+ public static final class Builder {
+ private int[] mRtpPacketLossRate = null;
+ private int[] mRtpJitter = null;
+ private long[] mRtpInactivityTimeMillis = null;
+
+ /**
+ * Default constructor for the Builder.
+ *
+ * @hide
+ */
+ public Builder() {
+ }
+
+ /**
+ * Set threshold values for RTP packet loss rate in percentage.
+ * <p/>
+ * The packet loss calculation should be done at least once per
+ * second. It should be calculated with at least the last 3 seconds
+ * of data.
+ *
+ * @param packetLossRateThresholds int array for threshold values.
+ * @return The same instance of the builder.
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder setThresholdsRtpPacketLossRate(int[] packetLossRateThresholds) {
+ if (packetLossRateThresholds.length > 0) {
+ TreeSet<Integer> thresholds = new TreeSet<>();
+ for (Integer value : packetLossRateThresholds) {
+ if (isValidRtpPacketLossRate(value)) {
+ thresholds.add(value);
+ }
+ }
+ int[] targetArray = new int[thresholds.size()];
+ int i = 0;
+ for (int element : thresholds) {
+ targetArray[i++] = element;
+ }
+ this.mRtpPacketLossRate = targetArray;
+ } else {
+ this.mRtpPacketLossRate = packetLossRateThresholds;
+ }
+ return this;
+ }
+
+
+ /**
+ * Set threshold values for RTP jitter in Milliseconds.
+ *
+ * @param jitterThresholds int array including threshold values for Jitter.
+ * @return The same instance of the builder.
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder setThresholdsRtpJitterMillis(int[] jitterThresholds) {
+ if (jitterThresholds.length > 0) {
+ TreeSet<Integer> thresholds = new TreeSet<>();
+ for (Integer value : jitterThresholds) {
+ if (isValidJitterMillis(value)) {
+ thresholds.add(value);
+ }
+ }
+ int[] targetArray = new int[thresholds.size()];
+ int i = 0;
+ for (int element : thresholds) {
+ targetArray[i++] = element;
+ }
+ this.mRtpJitter = targetArray;
+ } else {
+ this.mRtpJitter = jitterThresholds;
+ }
+ return this;
+ }
+
+ /**
+ * Set threshold values for RTP inactivity time.
+ *
+ * @param inactivityTimeThresholds int array including threshold
+ * values for RTP inactivity time.
+ * @return The same instance of the builder.
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder setThresholdsRtpInactivityTimeMillis(long[] inactivityTimeThresholds) {
+ if (inactivityTimeThresholds.length > 0) {
+ TreeSet<Long> thresholds = new TreeSet<>();
+ for (Long value : inactivityTimeThresholds) {
+ if (isValidRtpInactivityTimeMillis(value)) {
+ thresholds.add(value);
+ }
+ }
+ long[] targetArray = new long[thresholds.size()];
+ int i = 0;
+ for (long element : thresholds) {
+ targetArray[i++] = element;
+ }
+ this.mRtpInactivityTimeMillis = targetArray;
+ } else {
+ this.mRtpInactivityTimeMillis = inactivityTimeThresholds;
+ }
+ return this;
+ }
+
+ /**
+ * Build the {@link MediaThreshold}
+ *
+ * @return the {@link MediaThreshold} object
+ *
+ * @hide
+ */
+ @NonNull
+ public MediaThreshold build() {
+ mRtpPacketLossRate = mRtpPacketLossRate != null ? mRtpPacketLossRate : new int[0];
+ mRtpJitter = mRtpJitter != null ? mRtpJitter : new int[0];
+ mRtpInactivityTimeMillis =
+ mRtpInactivityTimeMillis != null ? mRtpInactivityTimeMillis : new long[0];
+ return new MediaThreshold(mRtpPacketLossRate, mRtpJitter, mRtpInactivityTimeMillis);
+ }
+ }
+}
diff --git a/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl b/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl
index cf7e9e16..2f0eb6c 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl
@@ -22,6 +22,8 @@
import android.telephony.ims.aidl.IImsCapabilityCallback;
import android.telephony.ims.aidl.ISrvccStartedCallback;
import android.telephony.ims.feature.CapabilityChangeRequest;
+import android.telephony.ims.MediaQualityStatus;
+import android.telephony.ims.MediaThreshold;
import android.telephony.ims.RtpHeaderExtensionType;
import android.telephony.ims.ImsCallProfile;
@@ -60,6 +62,9 @@
oneway void notifySrvccCompleted();
oneway void notifySrvccFailed();
oneway void notifySrvccCanceled();
+ oneway void setMediaQualityThreshold(int mediaSessionType, in MediaThreshold threshold);
+ MediaQualityStatus queryMediaQualityStatus(int mediaSessionType);
+
// SMS APIs
void setSmsListener(IImsSmsListener l);
oneway void sendSms(in int token, int messageRef, String format, String smsc, boolean retry,
diff --git a/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl
index b8701f1..e016c61 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl
@@ -20,6 +20,7 @@
import android.telephony.ims.ImsCallProfile;
import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.MediaQualityStatus;
import android.telephony.ims.aidl.IImsCallSessionListener;
import android.telephony.ims.aidl.IImsTrafficSessionCallback;
@@ -42,4 +43,5 @@
int trafficDirection, in IImsTrafficSessionCallback callback);
oneway void onModifyImsTrafficSession(int token, int accessNetworkType);
oneway void onStopImsTrafficSession(int token);
+ oneway void onMediaQualityStatusChanged(in MediaQualityStatus status);
}
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
index e213588..1686f38 100644
--- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -34,6 +34,8 @@
import android.telephony.ims.ImsException;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.ImsService;
+import android.telephony.ims.MediaQualityStatus;
+import android.telephony.ims.MediaThreshold;
import android.telephony.ims.RtpHeaderExtensionType;
import android.telephony.ims.SrvccCall;
import android.telephony.ims.aidl.IImsCallSessionListener;
@@ -84,10 +86,12 @@
private static final String LOG_TAG = "MmTelFeature";
private Executor mExecutor;
+ private ImsSmsImplBase mSmsImpl;
private HashMap<ImsTrafficSessionCallback, ImsTrafficSessionCallbackWrapper> mTrafficCallbacks =
new HashMap<>();
/**
+ * Creates a new MmTelFeature using the Executor set in {@link ImsService#getExecutor}
* @hide
*/
@SystemApi
@@ -255,7 +259,7 @@
public void changeCapabilitiesConfiguration(CapabilityChangeRequest request,
IImsCapabilityCallback c) {
executeMethodAsyncNoException(() -> MmTelFeature.this
- .requestChangeEnabledCapabilities(request, c),
+ .requestChangeEnabledCapabilities(request, c),
"changeCapabilitiesConfiguration");
}
@@ -267,52 +271,76 @@
}
@Override
+ public void setMediaQualityThreshold(@MediaQualityStatus.MediaSessionType int sessionType,
+ MediaThreshold mediaThreshold) {
+ if (mediaThreshold != null) {
+ executeMethodAsyncNoException(() -> setMediaThreshold(sessionType, mediaThreshold),
+ "setMediaQualityThreshold");
+ } else {
+ executeMethodAsyncNoException(() -> clearMediaThreshold(sessionType),
+ "clearMediaQualityThreshold");
+ }
+ }
+
+ @Override
+ public MediaQualityStatus queryMediaQualityStatus(
+ @MediaQualityStatus.MediaSessionType int sessionType)
+ throws RemoteException {
+ return executeMethodAsyncForResult(() -> MmTelFeature.this.queryMediaQualityStatus(
+ sessionType), "queryMediaQualityStatus");
+ }
+
+ @Override
public void setSmsListener(IImsSmsListener l) {
executeMethodAsyncNoException(() -> MmTelFeature.this.setSmsListener(l),
- "setSmsListener");
+ "setSmsListener", getImsSmsImpl().getExecutor());
}
@Override
public void sendSms(int token, int messageRef, String format, String smsc, boolean retry,
byte[] pdu) {
executeMethodAsyncNoException(() -> MmTelFeature.this
- .sendSms(token, messageRef, format, smsc, retry, pdu), "sendSms");
+ .sendSms(token, messageRef, format, smsc, retry, pdu), "sendSms",
+ getImsSmsImpl().getExecutor());
}
@Override
public void onMemoryAvailable(int token) {
executeMethodAsyncNoException(() -> MmTelFeature.this
- .onMemoryAvailable(token), "onMemoryAvailable");
+ .onMemoryAvailable(token), "onMemoryAvailable", getImsSmsImpl().getExecutor());
}
@Override
public void acknowledgeSms(int token, int messageRef, int result) {
executeMethodAsyncNoException(() -> MmTelFeature.this
- .acknowledgeSms(token, messageRef, result), "acknowledgeSms");
+ .acknowledgeSms(token, messageRef, result), "acknowledgeSms",
+ getImsSmsImpl().getExecutor());
}
@Override
public void acknowledgeSmsWithPdu(int token, int messageRef, int result, byte[] pdu) {
executeMethodAsyncNoException(() -> MmTelFeature.this
- .acknowledgeSms(token, messageRef, result, pdu), "acknowledgeSms");
+ .acknowledgeSms(token, messageRef, result, pdu), "acknowledgeSms",
+ getImsSmsImpl().getExecutor());
}
@Override
public void acknowledgeSmsReport(int token, int messageRef, int result) {
executeMethodAsyncNoException(() -> MmTelFeature.this
- .acknowledgeSmsReport(token, messageRef, result), "acknowledgeSmsReport");
+ .acknowledgeSmsReport(token, messageRef, result), "acknowledgeSmsReport",
+ getImsSmsImpl().getExecutor());
}
@Override
public String getSmsFormat() {
return executeMethodAsyncForResultNoException(() -> MmTelFeature.this
- .getSmsFormat(), "getSmsFormat");
+ .getSmsFormat(), "getSmsFormat", getImsSmsImpl().getExecutor());
}
@Override
public void onSmsReady() {
executeMethodAsyncNoException(() -> MmTelFeature.this.onSmsReady(),
- "onSmsReady");
+ "onSmsReady", getImsSmsImpl().getExecutor());
}
@Override
@@ -347,6 +375,19 @@
() -> MmTelFeature.this.notifySrvccCanceled(), "notifySrvccCanceled");
}
+ @Override
+ public void setTerminalBasedCallWaitingStatus(boolean enabled) throws RemoteException {
+ synchronized (mLock) {
+ try {
+ MmTelFeature.this.setTerminalBasedCallWaitingStatus(enabled);
+ } catch (ServiceSpecificException se) {
+ throw new ServiceSpecificException(se.errorCode, se.getMessage());
+ } catch (Exception e) {
+ throw new RemoteException(e.getMessage());
+ }
+ }
+ }
+
// Call the methods with a clean calling identity on the executor and wait indefinitely for
// the future to return.
private void executeMethodAsync(Runnable r, String errorLogName) throws RemoteException {
@@ -370,6 +411,17 @@
}
}
+ private void executeMethodAsyncNoException(Runnable r, String errorLogName,
+ Executor executor) {
+ try {
+ CompletableFuture.runAsync(
+ () -> TelephonyUtils.runWithCleanCallingIdentity(r), executor).join();
+ } catch (CancellationException | CompletionException e) {
+ Log.w(LOG_TAG, "MmTelFeature Binder - " + errorLogName + " exception: "
+ + e.getMessage());
+ }
+ }
+
private <T> T executeMethodAsyncForResult(Supplier<T> r,
String errorLogName) throws RemoteException {
CompletableFuture<T> future = CompletableFuture.supplyAsync(
@@ -396,16 +448,16 @@
}
}
- @Override
- public void setTerminalBasedCallWaitingStatus(boolean enabled) throws RemoteException {
- synchronized (mLock) {
- try {
- MmTelFeature.this.setTerminalBasedCallWaitingStatus(enabled);
- } catch (ServiceSpecificException se) {
- throw new ServiceSpecificException(se.errorCode, se.getMessage());
- } catch (Exception e) {
- throw new RemoteException(e.getMessage());
- }
+ private <T> T executeMethodAsyncForResultNoException(Supplier<T> r,
+ String errorLogName, Executor executor) {
+ CompletableFuture<T> future = CompletableFuture.supplyAsync(
+ () -> TelephonyUtils.runWithCleanCallingIdentity(r), executor);
+ try {
+ return future.get();
+ } catch (ExecutionException | InterruptedException e) {
+ Log.w(LOG_TAG, "MmTelFeature Binder - " + errorLogName + " exception: "
+ + e.getMessage());
+ return null;
}
}
};
@@ -666,6 +718,17 @@
public void onStopImsTrafficSession(int token) {
}
+
+ /**
+ * Called when the IMS provider notifies {@link MediaQualityStatus}.
+ *
+ * @param status media quality status currently measured.
+ * @hide
+ */
+ @Override
+ public void onMediaQualityStatusChanged(MediaQualityStatus status) {
+
+ }
}
/**
@@ -1030,6 +1093,32 @@
}
/**
+ * Notify the framework that the measured media quality has crossed a threshold set by {@link
+ * MmTelFeature#setMediaThreshold}
+ *
+ * @param status current media quality status measured.
+ * @hide
+ */
+ @SystemApi
+ public final void notifyMediaQualityStatusChanged(
+ @NonNull MediaQualityStatus status) {
+ if (status == null) {
+ throw new IllegalArgumentException(
+ "MediaQualityStatus must be non-null!");
+ }
+ Log.i(LOG_TAG, "notifyMediaQualityStatusChanged " + status);
+ IImsMmTelListener listener = getListener();
+ if (listener == null) {
+ throw new IllegalStateException("Session is not available.");
+ }
+ try {
+ listener.onMediaQualityStatusChanged(status);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
* Notify the framework of an incoming call.
* @param c The {@link ImsCallSessionImplBase} of the new incoming call.
* @param extras A bundle containing extra parameters related to the call. See
@@ -1362,6 +1451,62 @@
}
/**
+ * Called by the framework to pass {@link MediaThreshold}. The MmTelFeature should override this
+ * method to get Media quality threshold. This will pass the consolidated threshold values from
+ * Telephony framework. IMS provider needs to monitor media quality of active call and notify
+ * media quality {@link #notifyMediaQualityStatusChanged(MediaQualityStatus)} when the measured
+ * media quality crosses at least one of {@link MediaThreshold} set by this.
+ *
+ * @param mediaSessionType media session type for this Threshold info.
+ * @param mediaThreshold media threshold information
+ * @hide
+ */
+ @SystemApi
+ public void setMediaThreshold(
+ @MediaQualityStatus.MediaSessionType int mediaSessionType,
+ @NonNull MediaThreshold mediaThreshold) {
+ // Base Implementation - Should be overridden.
+ Log.d(LOG_TAG, "setMediaThreshold is not supported." + mediaThreshold);
+ }
+
+ /**
+ * The MmTelFeature should override this method to clear Media quality thresholds that were
+ * registered and stop media quality status updates.
+ *
+ * @param mediaSessionType media session type
+ * @hide
+ */
+ @SystemApi
+ public void clearMediaThreshold(@MediaQualityStatus.MediaSessionType int mediaSessionType) {
+ // Base Implementation - Should be overridden.
+ Log.d(LOG_TAG, "clearMediaThreshold is not supported." + mediaSessionType);
+ }
+
+ /**
+ * IMS provider should override this method to return currently measured media quality status.
+ *
+ * <p/>
+ * If media quality status is not yet measured after call is active, it needs to notify media
+ * quality status {@link #notifyMediaQualityStatusChanged(MediaQualityStatus)} when the first
+ * measurement is done.
+ *
+ * @param mediaSessionType media session type
+ * @return Current media quality status. It could be null if media quality status is not
+ * measured yet or {@link MediaThreshold} was not set corresponding to the media session
+ * type.
+ *
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public MediaQualityStatus queryMediaQualityStatus(
+ @MediaQualityStatus.MediaSessionType int mediaSessionType) {
+ // Base Implementation - Should be overridden.
+ Log.d(LOG_TAG, "queryMediaQualityStatus is not supported." + mediaSessionType);
+ return null;
+ }
+
+ /**
* Creates a {@link ImsCallProfile} from the service capabilities & IMS registration state.
*
* @param callSessionType a service type that is specified in {@link ImsCallProfile}
@@ -1491,6 +1636,19 @@
}
/**
+ * @hide
+ */
+ public @NonNull ImsSmsImplBase getImsSmsImpl() {
+ synchronized (mLock) {
+ if (mSmsImpl == null) {
+ mSmsImpl = getSmsImplementation();
+ mSmsImpl.setDefaultExecutor(mExecutor);
+ }
+ return mSmsImpl;
+ }
+ }
+
+ /**
* @return The {@link ImsUtImplBase} Ut interface implementation for the supplementary service
* configuration.
* @hide
@@ -1638,35 +1796,35 @@
}
private void setSmsListener(IImsSmsListener listener) {
- getSmsImplementation().registerSmsListener(listener);
+ getImsSmsImpl().registerSmsListener(listener);
}
private void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
byte[] pdu) {
- getSmsImplementation().sendSms(token, messageRef, format, smsc, isRetry, pdu);
+ getImsSmsImpl().sendSms(token, messageRef, format, smsc, isRetry, pdu);
}
private void onMemoryAvailable(int token) {
- getSmsImplementation().onMemoryAvailable(token);
+ getImsSmsImpl().onMemoryAvailable(token);
}
private void acknowledgeSms(int token, int messageRef,
@ImsSmsImplBase.DeliverStatusResult int result) {
- getSmsImplementation().acknowledgeSms(token, messageRef, result);
+ getImsSmsImpl().acknowledgeSms(token, messageRef, result);
}
private void acknowledgeSms(int token, int messageRef,
@ImsSmsImplBase.DeliverStatusResult int result, byte[] pdu) {
- getSmsImplementation().acknowledgeSms(token, messageRef, result, pdu);
+ getImsSmsImpl().acknowledgeSms(token, messageRef, result, pdu);
}
private void acknowledgeSmsReport(int token, int messageRef,
@ImsSmsImplBase.StatusReportResult int result) {
- getSmsImplementation().acknowledgeSmsReport(token, messageRef, result);
+ getImsSmsImpl().acknowledgeSmsReport(token, messageRef, result);
}
private void onSmsReady() {
- getSmsImplementation().onReady();
+ getImsSmsImpl().onReady();
}
/**
@@ -1683,7 +1841,7 @@
}
private String getSmsFormat() {
- return getSmsImplementation().getSmsFormat();
+ return getImsSmsImpl().getSmsFormat();
}
/**
diff --git a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
index daab84e..17cb3b4 100644
--- a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
@@ -28,6 +28,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
/**
* Base implementation for SMS over IMS.
@@ -129,6 +130,22 @@
// Lock for feature synchronization
private final Object mLock = new Object();
private IImsSmsListener mListener;
+ private Executor mExecutor;
+
+ /**
+ * Create a new ImsSmsImplBase using the Executor set in MmTelFeature
+ */
+ public ImsSmsImplBase() {
+ }
+
+ /**
+ * Create a new ImsSmsImplBase with specified executor.
+ * <p>
+ * @param executor Default executor for ImsSmsImplBase
+ */
+ public ImsSmsImplBase(@NonNull Executor executor) {
+ mExecutor = executor;
+ }
/**
* Registers a listener responsible for handling tasks like delivering messages.
@@ -482,4 +499,29 @@
public void onReady() {
// Base Implementation - Should be overridden
}
+
+ /**
+ * Set default Executor for ImsSmsImplBase.
+ *
+ * @param executor The default executor for the framework to use when executing the methods
+ * overridden by the implementation of ImsSms.
+ * @hide
+ */
+ public final void setDefaultExecutor(@NonNull Executor executor) {
+ if (mExecutor == null) {
+ mExecutor = executor;
+ }
+ }
+
+ /**
+ * Get Executor from ImsSmsImplBase.
+ * If there is no settings for the executor, all ImsSmsImplBase method calls will use
+ * Runnable::run as default
+ *
+ * @return an Executor used to execute methods in ImsSms called remotely by the framework.
+ * @hide
+ */
+ public @NonNull Executor getExecutor() {
+ return mExecutor != null ? mExecutor : Runnable::run;
+ }
}
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index 612e3a6..7381a85 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -364,7 +364,8 @@
}
std::unordered_set<ResourceName> resources_exclude_list;
bool result = ParseResourceConfig(content, context, resources_exclude_list,
- out_options.name_collapse_exemptions);
+ out_options.name_collapse_exemptions,
+ out_options.path_shorten_exemptions);
if (!result) {
return false;
}
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index d7a39bf..dbe7970 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -322,7 +322,8 @@
return false;
}
return ParseResourceConfig(content, context, options->resources_exclude_list,
- options->table_flattener_options.name_collapse_exemptions);
+ options->table_flattener_options.name_collapse_exemptions,
+ options->table_flattener_options.path_shorten_exemptions);
}
bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk,
diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h
index 1879f25..ee53af1 100644
--- a/tools/aapt2/cmd/Optimize.h
+++ b/tools/aapt2/cmd/Optimize.h
@@ -122,7 +122,8 @@
"--resources-config-path.",
&options_.table_flattener_options.collapse_key_stringpool);
AddOptionalSwitch("--shorten-resource-paths",
- "Shortens the paths of resources inside the APK.",
+ "Shortens the paths of resources inside the APK. Resources can be exempted using the \n"
+ "\"no_path_shorten\" directive in a file specified by --resources-config-path.",
&options_.shorten_resource_paths);
// TODO(b/246489170): keep the old option and format until transform to the new one
AddOptionalFlag("--resource-path-shortening-map",
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index 92849cf..1671e1e 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -448,7 +448,8 @@
bool ParseResourceConfig(const std::string& content, IAaptContext* context,
std::unordered_set<ResourceName>& out_resource_exclude_list,
- std::set<ResourceName>& out_name_collapse_exemptions) {
+ std::set<ResourceName>& out_name_collapse_exemptions,
+ std::set<ResourceName>& out_path_shorten_exemptions) {
for (StringPiece line : util::Tokenize(content, '\n')) {
line = util::TrimWhitespace(line);
if (line.empty()) {
@@ -477,6 +478,8 @@
out_resource_exclude_list.insert(resource_name.ToResourceName());
} else if (directive == "no_collapse" || directive == "no_obfuscate") {
out_name_collapse_exemptions.insert(resource_name.ToResourceName());
+ } else if (directive == "no_path_shorten") {
+ out_path_shorten_exemptions.insert(resource_name.ToResourceName());
}
}
}
diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h
index 169d5f9..712c07b 100644
--- a/tools/aapt2/cmd/Util.h
+++ b/tools/aapt2/cmd/Util.h
@@ -81,7 +81,8 @@
bool ParseResourceConfig(const std::string& content, IAaptContext* context,
std::unordered_set<ResourceName>& out_resource_exclude_list,
- std::set<ResourceName>& out_name_collapse_exemptions);
+ std::set<ResourceName>& out_name_collapse_exemptions,
+ std::set<ResourceName>& out_path_shorten_exemptions);
} // namespace aapt
diff --git a/tools/aapt2/cmd/Util_test.cpp b/tools/aapt2/cmd/Util_test.cpp
index 28a6de8..139bfbc 100644
--- a/tools/aapt2/cmd/Util_test.cpp
+++ b/tools/aapt2/cmd/Util_test.cpp
@@ -416,19 +416,27 @@
const std::string& content = R"(
bool/remove_me#remove
bool/keep_name#no_collapse
+layout/keep_path#no_path_shorten
string/foo#no_obfuscate
dimen/bar#no_obfuscate
+layout/keep_name_and_path#no_collapse,no_path_shorten
)";
aapt::test::Context context;
std::unordered_set<ResourceName> resource_exclusion;
std::set<ResourceName> name_collapse_exemptions;
+ std::set<ResourceName> path_shorten_exemptions;
- EXPECT_TRUE(ParseResourceConfig(content, &context, resource_exclusion, name_collapse_exemptions));
+ EXPECT_TRUE(ParseResourceConfig(content, &context, resource_exclusion, name_collapse_exemptions,
+ path_shorten_exemptions));
EXPECT_THAT(name_collapse_exemptions,
UnorderedElementsAre(ResourceName({}, ResourceType::kString, "foo"),
ResourceName({}, ResourceType::kDimen, "bar"),
- ResourceName({}, ResourceType::kBool, "keep_name")));
+ ResourceName({}, ResourceType::kBool, "keep_name"),
+ ResourceName({}, ResourceType::kLayout, "keep_name_and_path")));
+ EXPECT_THAT(path_shorten_exemptions,
+ UnorderedElementsAre(ResourceName({}, ResourceType::kLayout, "keep_path"),
+ ResourceName({}, ResourceType::kLayout, "keep_name_and_path")));
EXPECT_THAT(resource_exclusion,
UnorderedElementsAre(ResourceName({}, ResourceType::kBool, "remove_me")));
}
@@ -440,9 +448,10 @@
aapt::test::Context context;
std::unordered_set<ResourceName> resource_exclusion;
std::set<ResourceName> name_collapse_exemptions;
+ std::set<ResourceName> path_shorten_exemptions;
- EXPECT_FALSE(
- ParseResourceConfig(content, &context, resource_exclusion, name_collapse_exemptions));
+ EXPECT_FALSE(ParseResourceConfig(content, &context, resource_exclusion, name_collapse_exemptions,
+ path_shorten_exemptions));
}
TEST(UtilTest, ParseConfigInvalidName) {
@@ -452,9 +461,10 @@
aapt::test::Context context;
std::unordered_set<ResourceName> resource_exclusion;
std::set<ResourceName> name_collapse_exemptions;
+ std::set<ResourceName> path_shorten_exemptions;
- EXPECT_FALSE(
- ParseResourceConfig(content, &context, resource_exclusion, name_collapse_exemptions));
+ EXPECT_FALSE(ParseResourceConfig(content, &context, resource_exclusion, name_collapse_exemptions,
+ path_shorten_exemptions));
}
TEST(UtilTest, ParseConfigNoHash) {
@@ -464,9 +474,10 @@
aapt::test::Context context;
std::unordered_set<ResourceName> resource_exclusion;
std::set<ResourceName> name_collapse_exemptions;
+ std::set<ResourceName> path_shorten_exemptions;
- EXPECT_FALSE(
- ParseResourceConfig(content, &context, resource_exclusion, name_collapse_exemptions));
+ EXPECT_FALSE(ParseResourceConfig(content, &context, resource_exclusion, name_collapse_exemptions,
+ path_shorten_exemptions));
}
} // namespace aapt
diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h
index 60605d2..0633bc81 100644
--- a/tools/aapt2/format/binary/TableFlattener.h
+++ b/tools/aapt2/format/binary/TableFlattener.h
@@ -60,6 +60,9 @@
// Set of resources to avoid collapsing to a single entry in key stringpool.
std::set<ResourceName> name_collapse_exemptions;
+ // Set of resources to avoid path shortening.
+ std::set<ResourceName> path_shorten_exemptions;
+
// Map from original resource paths to shortened resource paths.
std::map<std::string, std::string> shortened_path_map;
diff --git a/tools/aapt2/optimize/Obfuscator.cpp b/tools/aapt2/optimize/Obfuscator.cpp
index cc21093..8f12f735 100644
--- a/tools/aapt2/optimize/Obfuscator.cpp
+++ b/tools/aapt2/optimize/Obfuscator.cpp
@@ -83,13 +83,18 @@
};
static bool HandleShortenFilePaths(ResourceTable* table,
- std::map<std::string, std::string>& shortened_path_map) {
+ std::map<std::string, std::string>& shortened_path_map,
+ const std::set<ResourceName>& path_shorten_exemptions) {
// used to detect collisions
std::unordered_set<std::string> shortened_paths;
std::set<FileReference*, PathComparator> file_refs;
for (auto& package : table->packages) {
for (auto& type : package->types) {
for (auto& entry : type->entries) {
+ ResourceName resource_name({}, type->named_type, entry->name);
+ if (path_shorten_exemptions.find(resource_name) != path_shorten_exemptions.end()) {
+ continue;
+ }
for (auto& config_value : entry->values) {
FileReference* file_ref = ValueCast<FileReference>(config_value->value.get());
if (file_ref) {
@@ -188,7 +193,8 @@
HandleCollapseKeyStringPool(table, options_.collapse_key_stringpool,
options_.name_collapse_exemptions, options_.id_resource_map);
if (shorten_resource_paths_) {
- return HandleShortenFilePaths(table, options_.shortened_path_map);
+ return HandleShortenFilePaths(table, options_.shortened_path_map,
+ options_.path_shorten_exemptions);
}
return true;
}
diff --git a/tools/aapt2/optimize/Obfuscator_test.cpp b/tools/aapt2/optimize/Obfuscator_test.cpp
index 7f57b71..940cf10 100644
--- a/tools/aapt2/optimize/Obfuscator_test.cpp
+++ b/tools/aapt2/optimize/Obfuscator_test.cpp
@@ -28,6 +28,7 @@
using ::testing::AnyOf;
using ::testing::Eq;
using ::testing::HasSubstr;
+using ::testing::IsFalse;
using ::testing::IsTrue;
using ::testing::Not;
using ::testing::NotNull;
@@ -102,6 +103,44 @@
ASSERT_THAT(path_map.find("res/color-mdp-v21/colorlist.xml"), Eq(path_map.end()));
}
+TEST(ObfuscatorTest, SkipPathShortenExemptions) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddFileReference("android:drawable/xmlfile", "res/drawables/xmlfile.xml")
+ .AddFileReference("android:drawable/xmlfile2", "res/drawables/xmlfile2.xml")
+ .AddString("android:string/string", "res/should/still/be/the/same.png")
+ .Build();
+
+ OptimizeOptions options{.shorten_resource_paths = true};
+ TableFlattenerOptions& flattenerOptions = options.table_flattener_options;
+ flattenerOptions.path_shorten_exemptions.insert(
+ ResourceName({}, ResourceType::kDrawable, "xmlfile"));
+ std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map;
+ ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
+
+ // Expect that the path map to not contain the first drawable which is in exemption set
+ EXPECT_THAT(path_map.find("res/drawables/xmlfile.xml"), Eq(path_map.end()));
+
+ // Expect that the path map to contain the second drawable which is not in exemption set
+ EXPECT_THAT(path_map.find("res/drawables/xmlfile2.xml"), Not(Eq(path_map.end())));
+
+ FileReference* ref = GetValue<FileReference>(table.get(), "android:drawable/xmlfile");
+ EXPECT_THAT(ref, NotNull());
+ ASSERT_THAT(HasFailure(), IsFalse());
+ // The path of first drawable in exemption was not changed
+ EXPECT_THAT("res/drawables/xmlfile.xml", Eq(*ref->path));
+
+ // The file path of second drawable not in exemption set was changed
+ EXPECT_THAT(path_map.at("res/drawables/xmlfile2.xml"), Not(Eq("res/drawables/xmlfile2.xml")));
+
+ FileReference* ref2 = GetValue<FileReference>(table.get(), "android:drawable/xmlfile2");
+ ASSERT_THAT(ref, NotNull());
+ // The map of second drawable not in exemption correctly points to the new location of the file
+ EXPECT_THAT(path_map["res/drawables/xmlfile2.xml"], Eq(*ref2->path));
+}
+
TEST(ObfuscatorTest, KeepExtensions) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();