Merge "Let each window has its own requested insets state"
diff --git a/Android.bp b/Android.bp
index a10f8de..bba6185 100644
--- a/Android.bp
+++ b/Android.bp
@@ -642,13 +642,13 @@
name: "framework-tethering-shared-srcs",
srcs: [
"core/java/android/util/LocalLog.java",
- "core/java/com/android/internal/util/BitUtils.java",
"core/java/com/android/internal/util/IndentingPrintWriter.java",
"core/java/com/android/internal/util/IState.java",
"core/java/com/android/internal/util/MessageUtils.java",
"core/java/com/android/internal/util/Preconditions.java",
"core/java/com/android/internal/util/State.java",
"core/java/com/android/internal/util/StateMachine.java",
+ "core/java/android/net/shared/Inet4AddressUtils.java",
],
}
diff --git a/api/current.txt b/api/current.txt
index 7cb19a9..51641f2 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6790,6 +6790,7 @@
method @Nullable public java.util.List<java.lang.String> getPermittedAccessibilityServices(@NonNull android.content.ComponentName);
method @Nullable public java.util.List<java.lang.String> getPermittedCrossProfileNotificationListeners(@NonNull android.content.ComponentName);
method @Nullable public java.util.List<java.lang.String> getPermittedInputMethods(@NonNull android.content.ComponentName);
+ method @NonNull public java.util.List<java.lang.String> getProtectedPackages(@NonNull android.content.ComponentName);
method public long getRequiredStrongAuthTimeout(@Nullable android.content.ComponentName);
method public boolean getScreenCaptureDisabled(@Nullable android.content.ComponentName);
method public java.util.List<android.os.UserHandle> getSecondaryUsers(@NonNull android.content.ComponentName);
@@ -6911,6 +6912,7 @@
method public boolean setPermittedInputMethods(@NonNull android.content.ComponentName, java.util.List<java.lang.String>);
method public void setProfileEnabled(@NonNull android.content.ComponentName);
method public void setProfileName(@NonNull android.content.ComponentName, String);
+ method public void setProtectedPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>);
method public void setRecommendedGlobalProxy(@NonNull android.content.ComponentName, @Nullable android.net.ProxyInfo);
method public void setRequiredStrongAuthTimeout(@NonNull android.content.ComponentName, long);
method public boolean setResetPasswordToken(android.content.ComponentName, byte[]);
@@ -28679,6 +28681,7 @@
method public int getVideoHeight();
method public float getVideoPixelAspectRatio();
method public int getVideoWidth();
+ method public boolean isAudioDescription();
method public boolean isEncrypted();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.TvTrackInfo> CREATOR;
@@ -28691,6 +28694,7 @@
ctor public TvTrackInfo.Builder(int, @NonNull String);
method public android.media.tv.TvTrackInfo build();
method public android.media.tv.TvTrackInfo.Builder setAudioChannelCount(int);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setAudioDescription(boolean);
method public android.media.tv.TvTrackInfo.Builder setAudioSampleRate(int);
method public android.media.tv.TvTrackInfo.Builder setDescription(CharSequence);
method @NonNull public android.media.tv.TvTrackInfo.Builder setEncrypted(boolean);
@@ -29477,6 +29481,7 @@
method public android.net.NetworkRequest.Builder addCapability(int);
method public android.net.NetworkRequest.Builder addTransportType(int);
method public android.net.NetworkRequest build();
+ method @NonNull public android.net.NetworkRequest.Builder clearCapabilities();
method public android.net.NetworkRequest.Builder removeCapability(int);
method public android.net.NetworkRequest.Builder removeTransportType(int);
method public android.net.NetworkRequest.Builder setNetworkSpecifier(String);
@@ -30352,6 +30357,7 @@
method public String getPlmn();
method public String getRealm();
method @Deprecated public String getSubjectMatch();
+ method public boolean isAuthenticationSimBased();
method public void setAltSubjectMatch(String);
method public void setAnonymousIdentity(String);
method public void setCaCertificate(@Nullable java.security.cert.X509Certificate);
@@ -45593,6 +45599,7 @@
method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onImsCallDisconnectCauseChanged(@NonNull android.telephony.ims.ImsReasonInfo);
method public void onMessageWaitingIndicatorChanged(boolean);
method @RequiresPermission("android.permission.MODIFY_PHONE_STATE") public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
+ method public void onRegistrationFailed(@NonNull android.telephony.CellIdentity, @NonNull String, int, int, int);
method public void onServiceStateChanged(android.telephony.ServiceState);
method @Deprecated public void onSignalStrengthChanged(int);
method public void onSignalStrengthsChanged(android.telephony.SignalStrength);
@@ -45610,6 +45617,7 @@
field public static final int LISTEN_MESSAGE_WAITING_INDICATOR = 4; // 0x4
field public static final int LISTEN_NONE = 0; // 0x0
field @RequiresPermission("android.permission.MODIFY_PHONE_STATE") public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000
+ field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int LISTEN_REGISTRATION_FAILURE = 1073741824; // 0x40000000
field public static final int LISTEN_SERVICE_STATE = 1; // 0x1
field @Deprecated public static final int LISTEN_SIGNAL_STRENGTH = 2; // 0x2
field public static final int LISTEN_SIGNAL_STRENGTHS = 256; // 0x100
diff --git a/api/system-current.txt b/api/system-current.txt
index 653df73..41ded16 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -413,6 +413,16 @@
field public static final int UID_STATE_TOP = 200; // 0xc8
}
+ public static final class AppOpsManager.HistoricalFeatureOps implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public String getFeatureId();
+ method @Nullable public android.app.AppOpsManager.HistoricalOp getOp(@NonNull String);
+ method @NonNull public android.app.AppOpsManager.HistoricalOp getOpAt(@IntRange(from=0) int);
+ method @IntRange(from=0) public int getOpCount();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalFeatureOps> CREATOR;
+ }
+
public static final class AppOpsManager.HistoricalOp implements android.os.Parcelable {
method public int describeContents();
method public long getAccessCount(int, int, int);
@@ -446,6 +456,7 @@
public static final class AppOpsManager.HistoricalOpsRequest.Builder {
ctor public AppOpsManager.HistoricalOpsRequest.Builder(long, long);
method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest build();
+ method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setFeatureId(@Nullable String);
method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setFlags(int);
method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setOpNames(@Nullable java.util.List<java.lang.String>);
method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setPackageName(@Nullable String);
@@ -454,6 +465,9 @@
public static final class AppOpsManager.HistoricalPackageOps implements android.os.Parcelable {
method public int describeContents();
+ method @IntRange(from=0) public int getFeatureCount();
+ method @Nullable public android.app.AppOpsManager.HistoricalFeatureOps getFeatureOps(@NonNull String);
+ method @NonNull public android.app.AppOpsManager.HistoricalFeatureOps getFeatureOpsAt(@IntRange(from=0) int);
method @Nullable public android.app.AppOpsManager.HistoricalOp getOp(@NonNull String);
method @NonNull public android.app.AppOpsManager.HistoricalOp getOpAt(@IntRange(from=0) int);
method @IntRange(from=0) public int getOpCount();
@@ -1518,6 +1532,14 @@
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
}
+ public final class BluetoothHidHost implements android.bluetooth.BluetoothProfile {
+ method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice);
+ method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice, int);
+ field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
+ }
+
public final class BluetoothPan implements android.bluetooth.BluetoothProfile {
method protected void finalize();
method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
@@ -1535,6 +1557,7 @@
public class BluetoothPbap implements android.bluetooth.BluetoothProfile {
method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
}
@@ -1660,6 +1683,7 @@
field public static final String EUICC_CARD_SERVICE = "euicc_card";
field public static final String HDMI_CONTROL_SERVICE = "hdmi_control";
field public static final String NETD_SERVICE = "netd";
+ field public static final String NETWORK_POLICY_SERVICE = "netpolicy";
field public static final String NETWORK_SCORE_SERVICE = "network_score";
field public static final String OEM_LOCK_SERVICE = "oem_lock";
field public static final String PERMISSION_SERVICE = "permission";
@@ -2117,6 +2141,7 @@
field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES";
field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS";
field public static final String FEATURE_BROADCAST_RADIO = "android.hardware.broadcastradio";
+ field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow";
field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock";
field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000
field public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT = 32; // 0x20
@@ -4141,6 +4166,14 @@
}
+package android.media.audiofx {
+
+ public class AudioEffect {
+ ctor @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public AudioEffect(@NonNull java.util.UUID, @NonNull android.media.AudioDeviceAddress);
+ }
+
+}
+
package android.media.audiopolicy {
public class AudioMix {
@@ -4552,6 +4585,36 @@
public class Tuner.Descrambler {
}
+ public class Tuner.Filter {
+ }
+
+ public static interface Tuner.FilterCallback {
+ method public void onFilterEvent(@NonNull android.media.tv.tuner.Tuner.Filter, @NonNull android.media.tv.tuner.filter.FilterEvent[]);
+ method public void onFilterStatusChanged(@NonNull android.media.tv.tuner.Tuner.Filter, int);
+ }
+
+ public final class TunerConstants {
+ field public static final int FILTER_STATUS_DATA_READY = 1; // 0x1
+ field public static final int FILTER_STATUS_HIGH_WATER = 4; // 0x4
+ field public static final int FILTER_STATUS_LOW_WATER = 2; // 0x2
+ field public static final int FILTER_STATUS_OVERFLOW = 8; // 0x8
+ }
+
+}
+
+package android.media.tv.tuner.filter {
+
+ public abstract class FilterEvent {
+ ctor public FilterEvent();
+ }
+
+ public class SectionEvent extends android.media.tv.tuner.filter.FilterEvent {
+ method public int getDataLength();
+ method public int getSectionNumber();
+ method public int getTableId();
+ method public int getVersion();
+ }
+
}
package android.metrics {
@@ -4760,6 +4823,7 @@
public final class MatchAllNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
ctor public MatchAllNetworkSpecifier();
method public int describeContents();
+ method public boolean satisfiedBy(android.net.NetworkSpecifier);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.MatchAllNetworkSpecifier> CREATOR;
}
@@ -4823,6 +4887,12 @@
method public void updateScores(@NonNull java.util.List<android.net.ScoredNetwork>);
}
+ public abstract class NetworkSpecifier {
+ method public void assertValidFromUid(int);
+ method @Nullable public android.net.NetworkSpecifier redact();
+ method public abstract boolean satisfiedBy(@Nullable android.net.NetworkSpecifier);
+ }
+
public class NetworkStack {
field public static final String PERMISSION_MAINLINE_NETWORK_STACK = "android.permission.MAINLINE_NETWORK_STACK";
}
@@ -4893,6 +4963,7 @@
public final class StringNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
ctor public StringNetworkSpecifier(@NonNull String);
method public int describeContents();
+ method public boolean satisfiedBy(android.net.NetworkSpecifier);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.StringNetworkSpecifier> CREATOR;
field @NonNull public final String specifier;
@@ -5676,6 +5747,7 @@
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.SoftApCapability> CREATOR;
field public static final int SOFTAP_FEATURE_ACS_OFFLOAD = 1; // 0x1
field public static final int SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT = 2; // 0x2
+ field public static final int SOFTAP_FEATURE_WPA3_SAE = 4; // 0x4
}
public final class SoftApConfiguration implements android.os.Parcelable {
@@ -5684,6 +5756,7 @@
method @Nullable public android.net.MacAddress getBssid();
method public int getChannel();
method public int getMaxNumberOfClients();
+ method @Nullable public String getPassphrase();
method public int getSecurityType();
method @Nullable public String getSsid();
method @Nullable public String getWpa2Passphrase();
@@ -5696,6 +5769,8 @@
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.SoftApConfiguration> CREATOR;
field public static final int SECURITY_TYPE_OPEN = 0; // 0x0
field public static final int SECURITY_TYPE_WPA2_PSK = 1; // 0x1
+ field public static final int SECURITY_TYPE_WPA3_SAE = 3; // 0x3
+ field public static final int SECURITY_TYPE_WPA3_SAE_TRANSITION = 2; // 0x2
}
public static final class SoftApConfiguration.Builder {
@@ -5707,6 +5782,7 @@
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setChannel(int, int);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setHiddenSsid(boolean);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setMaxNumberOfClients(int);
+ method @NonNull public android.net.wifi.SoftApConfiguration.Builder setPassphrase(@Nullable String, int);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setSsid(@Nullable String);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setWpa2Passphrase(@Nullable String);
}
@@ -6005,6 +6081,10 @@
field public int numUsage;
}
+ public final class WifiNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
+ method public boolean satisfiedBy(android.net.NetworkSpecifier);
+ }
+
public static final class WifiNetworkSuggestion.Builder {
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public android.net.wifi.WifiNetworkSuggestion.Builder setCarrierId(int);
}
@@ -6191,6 +6271,10 @@
method @Deprecated public android.net.NetworkSpecifier createNetworkSpecifierPmk(@NonNull android.net.wifi.aware.PeerHandle, @NonNull byte[]);
}
+ public final class WifiAwareNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
+ method public boolean satisfiedBy(android.net.NetworkSpecifier);
+ }
+
public class WifiAwareSession implements java.lang.AutoCloseable {
method public android.net.NetworkSpecifier createNetworkSpecifierPmk(int, @NonNull byte[], @NonNull byte[]);
}
@@ -6905,9 +6989,12 @@
public class RecoverySystem {
method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void cancelScheduledUpdate(android.content.Context) throws java.io.IOException;
+ method @RequiresPermission(android.Manifest.permission.RECOVERY) public static boolean clearPrepareForUnattendedUpdate(@NonNull android.content.Context) throws java.io.IOException;
method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void installPackage(android.content.Context, java.io.File, boolean) throws java.io.IOException;
+ method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void prepareForUnattendedUpdate(@NonNull android.content.Context, @NonNull String, @Nullable android.content.IntentSender) throws java.io.IOException;
method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void processPackage(android.content.Context, java.io.File, android.os.RecoverySystem.ProgressListener, android.os.Handler) throws java.io.IOException;
method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void processPackage(android.content.Context, java.io.File, android.os.RecoverySystem.ProgressListener) throws java.io.IOException;
+ method @RequiresPermission(android.Manifest.permission.RECOVERY) public static boolean rebootAndApply(@NonNull android.content.Context, @NonNull String, @NonNull String) throws java.io.IOException;
method @RequiresPermission(allOf={android.Manifest.permission.RECOVERY, android.Manifest.permission.REBOOT}) public static void rebootWipeAb(android.content.Context, java.io.File, String) throws java.io.IOException;
method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void scheduleUpdateOnBoot(android.content.Context, java.io.File) throws java.io.IOException;
method public static boolean verifyPackageCompatibility(java.io.File) throws java.io.IOException;
@@ -7807,7 +7894,7 @@
field public static final String SERIAL_NUMBER = "serial_number";
field public static final String SERVICE_CATEGORY = "service_category";
field public static final String SLOT_INDEX = "slot_index";
- field public static final String SUB_ID = "sub_id";
+ field public static final String SUBSCRIPTION_ID = "sub_id";
}
public static final class Telephony.CellBroadcasts.Preference {
@@ -9516,6 +9603,7 @@
field public static final int IMS_ACCESS_BLOCKED = 60; // 0x3c
field public static final int IMS_MERGED_SUCCESSFULLY = 45; // 0x2d
field public static final int IMS_SIP_ALTERNATE_EMERGENCY_CALL = 71; // 0x47
+ field public static final int INCOMING_AUTO_REJECTED = 81; // 0x51
field public static final int INCOMING_MISSED = 1; // 0x1
field public static final int INCOMING_REJECTED = 16; // 0x10
field public static final int INVALID_CREDENTIALS = 10; // 0xa
@@ -9617,7 +9705,9 @@
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
+ field public static final int DOMAIN_CS_PS = 3; // 0x3
field public static final int DOMAIN_PS = 2; // 0x2
+ field public static final int DOMAIN_UNKNOWN = 0; // 0x0
field public static final int REGISTRATION_STATE_DENIED = 3; // 0x3
field public static final int REGISTRATION_STATE_HOME = 1; // 0x1
field public static final int REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING = 0; // 0x0
@@ -10006,6 +10096,7 @@
method public boolean canManageSubscription(@Nullable android.telephony.SubscriptionInfo, @Nullable String);
method @NonNull public int[] getActiveAndHiddenSubscriptionIdList();
method @NonNull public int[] getActiveSubscriptionIdList();
+ method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.SubscriptionInfo getActiveSubscriptionInfoForIcc(@NonNull String);
method public java.util.List<android.telephony.SubscriptionInfo> getAvailableSubscriptionInfoList();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEnabledSubscriptionId(int);
method @NonNull public static android.content.res.Resources getResourcesForSubId(@NonNull android.content.Context, int);
@@ -10251,6 +10342,7 @@
method public void addOnSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener, @NonNull java.util.concurrent.Executor);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyCallStateChangedForAllSubscriptions(int, @Nullable String);
method public void notifyCarrierNetworkChange(boolean);
+ method public void notifyRegistrationFailed(int, int, @NonNull android.telephony.CellIdentity, @NonNull String, int, int, int);
method public void removeOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener);
method public void removeOnSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
}
@@ -10669,6 +10761,7 @@
field public static final int DIALSTRING_USSD = 2; // 0x2
field public static final String EXTRA_ADDITIONAL_CALL_INFO = "AdditionalCallInfo";
field public static final String EXTRA_ADDITIONAL_SIP_INVITE_FIELDS = "android.telephony.ims.extra.ADDITIONAL_SIP_INVITE_FIELDS";
+ field public static final String EXTRA_CALL_DISCONNECT_CAUSE = "android.telephony.ims.extra.CALL_DISCONNECT_CAUSE";
field public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech";
field public static final String EXTRA_CHILD_NUMBER = "ChildNum";
field public static final String EXTRA_CNA = "cna";
diff --git a/api/test-current.txt b/api/test-current.txt
index 38d89d3..9967942 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -244,6 +244,16 @@
field public static final int UID_STATE_TOP = 200; // 0xc8
}
+ public static final class AppOpsManager.HistoricalFeatureOps implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public String getFeatureId();
+ method @Nullable public android.app.AppOpsManager.HistoricalOp getOp(@NonNull String);
+ method @NonNull public android.app.AppOpsManager.HistoricalOp getOpAt(@IntRange(from=0) int);
+ method @IntRange(from=0) public int getOpCount();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalFeatureOps> CREATOR;
+ }
+
public static final class AppOpsManager.HistoricalOp implements android.os.Parcelable {
method public int describeContents();
method public long getAccessCount(int, int, int);
@@ -268,9 +278,9 @@
method @IntRange(from=0) public int getUidCount();
method @Nullable public android.app.AppOpsManager.HistoricalUidOps getUidOps(int);
method @NonNull public android.app.AppOpsManager.HistoricalUidOps getUidOpsAt(@IntRange(from=0) int);
- method public void increaseAccessCount(int, int, @NonNull String, int, int, long);
- method public void increaseAccessDuration(int, int, @NonNull String, int, int, long);
- method public void increaseRejectCount(int, int, @NonNull String, int, int, long);
+ method public void increaseAccessCount(int, int, @NonNull String, @Nullable String, int, int, long);
+ method public void increaseAccessDuration(int, int, @NonNull String, @Nullable String, int, int, long);
+ method public void increaseRejectCount(int, int, @NonNull String, @Nullable String, int, int, long);
method public void offsetBeginAndEndTime(long);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOps> CREATOR;
@@ -282,6 +292,7 @@
public static final class AppOpsManager.HistoricalOpsRequest.Builder {
ctor public AppOpsManager.HistoricalOpsRequest.Builder(long, long);
method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest build();
+ method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setFeatureId(@Nullable String);
method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setFlags(int);
method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setOpNames(@Nullable java.util.List<java.lang.String>);
method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setPackageName(@Nullable String);
@@ -290,6 +301,9 @@
public static final class AppOpsManager.HistoricalPackageOps implements android.os.Parcelable {
method public int describeContents();
+ method @IntRange(from=0) public int getFeatureCount();
+ method @Nullable public android.app.AppOpsManager.HistoricalFeatureOps getFeatureOps(@NonNull String);
+ method @NonNull public android.app.AppOpsManager.HistoricalFeatureOps getFeatureOpsAt(@IntRange(from=0) int);
method @Nullable public android.app.AppOpsManager.HistoricalOp getOp(@NonNull String);
method @NonNull public android.app.AppOpsManager.HistoricalOp getOpAt(@IntRange(from=0) int);
method @IntRange(from=0) public int getOpCount();
@@ -2610,7 +2624,7 @@
field public static final String SERIAL_NUMBER = "serial_number";
field public static final String SERVICE_CATEGORY = "service_category";
field public static final String SLOT_INDEX = "slot_index";
- field public static final String SUB_ID = "sub_id";
+ field public static final String SUBSCRIPTION_ID = "sub_id";
}
public static final class Telephony.Sms.Intents {
@@ -3178,7 +3192,9 @@
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
+ field public static final int DOMAIN_CS_PS = 3; // 0x3
field public static final int DOMAIN_PS = 2; // 0x2
+ field public static final int DOMAIN_UNKNOWN = 0; // 0x0
field public static final int REGISTRATION_STATE_DENIED = 3; // 0x3
field public static final int REGISTRATION_STATE_HOME = 1; // 0x1
field public static final int REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING = 0; // 0x0
@@ -3378,6 +3394,7 @@
field public static final int DIALSTRING_USSD = 2; // 0x2
field public static final String EXTRA_ADDITIONAL_CALL_INFO = "AdditionalCallInfo";
field public static final String EXTRA_ADDITIONAL_SIP_INVITE_FIELDS = "android.telephony.ims.extra.ADDITIONAL_SIP_INVITE_FIELDS";
+ field public static final String EXTRA_CALL_DISCONNECT_CAUSE = "android.telephony.ims.extra.CALL_DISCONNECT_CAUSE";
field public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech";
field public static final String EXTRA_CHILD_NUMBER = "ChildNum";
field public static final String EXTRA_CNA = "cna";
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index de7cc9d..032e824 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -278,6 +278,9 @@
public abstract boolean isBackgroundActivityStartsEnabled();
public abstract void reportCurKeyguardUsageEvent(boolean keyguardShowing);
+ /** @see com.android.server.am.ActivityManagerService#monitor */
+ public abstract void monitor();
+
/** Input dispatch timeout to a window, start the ANR process. */
public abstract long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason);
public abstract boolean inputDispatchingTimedOut(Object proc, String activityShortComponentName,
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 42c4d36..4a8e4e2 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -3917,10 +3917,53 @@
void visitHistoricalOps(@NonNull HistoricalOps ops);
void visitHistoricalUidOps(@NonNull HistoricalUidOps ops);
void visitHistoricalPackageOps(@NonNull HistoricalPackageOps ops);
+ void visitHistoricalFeatureOps(@NonNull HistoricalFeatureOps ops);
void visitHistoricalOp(@NonNull HistoricalOp ops);
}
/**
+ * Specifies what parameters to filter historical appop requests for
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "FILTER_BY_" }, value = {
+ FILTER_BY_UID,
+ FILTER_BY_PACKAGE_NAME,
+ FILTER_BY_FEATURE_ID,
+ FILTER_BY_OP_NAMES
+ })
+ public @interface HistoricalOpsRequestFilter {}
+
+ /**
+ * Filter historical appop request by uid.
+ *
+ * @hide
+ */
+ public static final int FILTER_BY_UID = 1<<0;
+
+ /**
+ * Filter historical appop request by package name.
+ *
+ * @hide
+ */
+ public static final int FILTER_BY_PACKAGE_NAME = 1<<1;
+
+ /**
+ * Filter historical appop request by feature id.
+ *
+ * @hide
+ */
+ public static final int FILTER_BY_FEATURE_ID = 1<<2;
+
+ /**
+ * Filter historical appop request by op names.
+ *
+ * @hide
+ */
+ public static final int FILTER_BY_OP_NAMES = 1<<3;
+
+ /**
* Request for getting historical app op usage. The request acts
* as a filtering criteria when querying historical op usage.
*
@@ -3932,17 +3975,22 @@
public static final class HistoricalOpsRequest {
private final int mUid;
private final @Nullable String mPackageName;
+ private final @Nullable String mFeatureId;
private final @Nullable List<String> mOpNames;
+ private final @HistoricalOpsRequestFilter int mFilter;
private final long mBeginTimeMillis;
private final long mEndTimeMillis;
private final @OpFlags int mFlags;
private HistoricalOpsRequest(int uid, @Nullable String packageName,
- @Nullable List<String> opNames, long beginTimeMillis, long endTimeMillis,
- @OpFlags int flags) {
+ @Nullable String featureId, @Nullable List<String> opNames,
+ @HistoricalOpsRequestFilter int filter, long beginTimeMillis,
+ long endTimeMillis, @OpFlags int flags) {
mUid = uid;
mPackageName = packageName;
+ mFeatureId = featureId;
mOpNames = opNames;
+ mFilter = filter;
mBeginTimeMillis = beginTimeMillis;
mEndTimeMillis = endTimeMillis;
mFlags = flags;
@@ -3958,7 +4006,9 @@
public static final class Builder {
private int mUid = Process.INVALID_UID;
private @Nullable String mPackageName;
+ private @Nullable String mFeatureId;
private @Nullable List<String> mOpNames;
+ private @HistoricalOpsRequestFilter int mFilter;
private final long mBeginTimeMillis;
private final long mEndTimeMillis;
private @OpFlags int mFlags = OP_FLAGS_ALL;
@@ -3991,6 +4041,13 @@
Preconditions.checkArgument(uid == Process.INVALID_UID || uid >= 0,
"uid must be " + Process.INVALID_UID + " or non negative");
mUid = uid;
+
+ if (uid == Process.INVALID_UID) {
+ mFilter &= ~FILTER_BY_UID;
+ } else {
+ mFilter |= FILTER_BY_UID;
+ }
+
return this;
}
@@ -4002,6 +4059,26 @@
*/
public @NonNull Builder setPackageName(@Nullable String packageName) {
mPackageName = packageName;
+
+ if (packageName == null) {
+ mFilter &= ~FILTER_BY_PACKAGE_NAME;
+ } else {
+ mFilter |= FILTER_BY_PACKAGE_NAME;
+ }
+
+ return this;
+ }
+
+ /**
+ * Sets the feature id to query for.
+ *
+ * @param featureId The id of the feature.
+ * @return This builder.
+ */
+ public @NonNull Builder setFeatureId(@Nullable String featureId) {
+ mFeatureId = featureId;
+ mFilter |= FILTER_BY_FEATURE_ID;
+
return this;
}
@@ -4020,6 +4097,13 @@
}
}
mOpNames = opNames;
+
+ if (mOpNames == null) {
+ mFilter &= ~FILTER_BY_OP_NAMES;
+ } else {
+ mFilter |= FILTER_BY_OP_NAMES;
+ }
+
return this;
}
@@ -4044,8 +4128,8 @@
* @return a new {@link HistoricalOpsRequest}.
*/
public @NonNull HistoricalOpsRequest build() {
- return new HistoricalOpsRequest(mUid, mPackageName, mOpNames,
- mBeginTimeMillis, mEndTimeMillis, mFlags);
+ return new HistoricalOpsRequest(mUid, mPackageName, mFeatureId, mOpNames,
+ mFilter, mBeginTimeMillis, mEndTimeMillis, mFlags);
}
}
}
@@ -4202,15 +4286,18 @@
/**
* AppPermissionUsage the ops to leave only the data we filter for.
*
- * @param uid Uid to filter for or {@link android.os.Process#INCIDENTD_UID} for all.
- * @param packageName Package to filter for or null for all.
- * @param opNames Ops to filter for or null for all.
+ * @param uid Uid to filter for.
+ * @param packageName Package to filter for.
+ * @param featureId Package to filter for.
+ * @param opNames Ops to filter for.
+ * @param filter Which parameters to filter on.
* @param beginTimeMillis The begin time to filter for or {@link Long#MIN_VALUE} for all.
* @param endTimeMillis The end time to filter for or {@link Long#MAX_VALUE} for all.
*
* @hide
*/
- public void filter(int uid, @Nullable String packageName, @Nullable String[] opNames,
+ public void filter(int uid, @Nullable String packageName, @Nullable String featureId,
+ @Nullable String[] opNames, @HistoricalOpsRequestFilter int filter,
long beginTimeMillis, long endTimeMillis) {
final long durationMillis = getDurationMillis();
mBeginTimeMillis = Math.max(mBeginTimeMillis, beginTimeMillis);
@@ -4220,10 +4307,10 @@
final int uidCount = getUidCount();
for (int i = uidCount - 1; i >= 0; i--) {
final HistoricalUidOps uidOp = mHistoricalUidOps.valueAt(i);
- if (uid != Process.INVALID_UID && uid != uidOp.getUid()) {
+ if ((filter & FILTER_BY_UID) != 0 && uid != uidOp.getUid()) {
mHistoricalUidOps.removeAt(i);
} else {
- uidOp.filter(packageName, opNames, scaleFactor);
+ uidOp.filter(packageName, featureId, opNames, filter, scaleFactor);
}
}
}
@@ -4251,25 +4338,28 @@
/** @hide */
@TestApi
public void increaseAccessCount(int opCode, int uid, @NonNull String packageName,
- @UidState int uidState, @OpFlags int flags, long increment) {
+ @Nullable String featureId, @UidState int uidState, @OpFlags int flags,
+ long increment) {
getOrCreateHistoricalUidOps(uid).increaseAccessCount(opCode,
- packageName, uidState, flags, increment);
+ packageName, featureId, uidState, flags, increment);
}
/** @hide */
@TestApi
public void increaseRejectCount(int opCode, int uid, @NonNull String packageName,
- @UidState int uidState, @OpFlags int flags, long increment) {
+ @Nullable String featureId, @UidState int uidState, @OpFlags int flags,
+ long increment) {
getOrCreateHistoricalUidOps(uid).increaseRejectCount(opCode,
- packageName, uidState, flags, increment);
+ packageName, featureId, uidState, flags, increment);
}
/** @hide */
@TestApi
public void increaseAccessDuration(int opCode, int uid, @NonNull String packageName,
- @UidState int uidState, @OpFlags int flags, long increment) {
+ @Nullable String featureId, @UidState int uidState, @OpFlags int flags,
+ long increment) {
getOrCreateHistoricalUidOps(uid).increaseAccessDuration(opCode,
- packageName, uidState, flags, increment);
+ packageName, featureId, uidState, flags, increment);
}
/** @hide */
@@ -4549,15 +4639,17 @@
}
}
- private void filter(@Nullable String packageName, @Nullable String[] opNames,
+ private void filter(@Nullable String packageName, @Nullable String featureId,
+ @Nullable String[] opNames, @HistoricalOpsRequestFilter int filter,
double fractionToRemove) {
final int packageCount = getPackageCount();
for (int i = packageCount - 1; i >= 0; i--) {
final HistoricalPackageOps packageOps = getPackageOpsAt(i);
- if (packageName != null && !packageName.equals(packageOps.getPackageName())) {
+ if ((filter & FILTER_BY_PACKAGE_NAME) != 0 && !packageName.equals(
+ packageOps.getPackageName())) {
mHistoricalPackageOps.removeAt(i);
} else {
- packageOps.filter(opNames, fractionToRemove);
+ packageOps.filter(featureId, opNames, filter, fractionToRemove);
}
}
}
@@ -4574,21 +4666,24 @@
}
private void increaseAccessCount(int opCode, @NonNull String packageName,
- @UidState int uidState, @OpFlags int flags, long increment) {
+ @Nullable String featureId, @UidState int uidState, @OpFlags int flags,
+ long increment) {
getOrCreateHistoricalPackageOps(packageName).increaseAccessCount(
- opCode, uidState, flags, increment);
+ opCode, featureId, uidState, flags, increment);
}
private void increaseRejectCount(int opCode, @NonNull String packageName,
- @UidState int uidState, @OpFlags int flags, long increment) {
+ @Nullable String featureId, @UidState int uidState, @OpFlags int flags,
+ long increment) {
getOrCreateHistoricalPackageOps(packageName).increaseRejectCount(
- opCode, uidState, flags, increment);
+ opCode, featureId, uidState, flags, increment);
}
private void increaseAccessDuration(int opCode, @NonNull String packageName,
- @UidState int uidState, @OpFlags int flags, long increment) {
+ @Nullable String featureId, @UidState int uidState, @OpFlags int flags,
+ long increment) {
getOrCreateHistoricalPackageOps(packageName).increaseAccessDuration(
- opCode, uidState, flags, increment);
+ opCode, featureId, uidState, flags, increment);
}
/**
@@ -4733,7 +4828,7 @@
@SystemApi
public static final class HistoricalPackageOps implements Parcelable {
private final @NonNull String mPackageName;
- private @Nullable ArrayMap<String, HistoricalOp> mHistoricalOps;
+ private @Nullable ArrayMap<String, HistoricalFeatureOps> mHistoricalFeatureOps;
/** @hide */
public HistoricalPackageOps(@NonNull String packageName) {
@@ -4742,6 +4837,339 @@
private HistoricalPackageOps(@NonNull HistoricalPackageOps other) {
mPackageName = other.mPackageName;
+ final int opCount = other.getFeatureCount();
+ for (int i = 0; i < opCount; i++) {
+ final HistoricalFeatureOps origOps = other.getFeatureOpsAt(i);
+ final HistoricalFeatureOps cloneOps = new HistoricalFeatureOps(origOps);
+ if (mHistoricalFeatureOps == null) {
+ mHistoricalFeatureOps = new ArrayMap<>(opCount);
+ }
+ mHistoricalFeatureOps.put(cloneOps.getFeatureId(), cloneOps);
+ }
+ }
+
+ private HistoricalPackageOps(@NonNull Parcel parcel) {
+ mPackageName = parcel.readString();
+ mHistoricalFeatureOps = parcel.createTypedArrayMap(HistoricalFeatureOps.CREATOR);
+ }
+
+ private @Nullable HistoricalPackageOps splice(double fractionToRemove) {
+ HistoricalPackageOps splice = null;
+ final int featureCount = getFeatureCount();
+ for (int i = 0; i < featureCount; i++) {
+ final HistoricalFeatureOps origOps = getFeatureOpsAt(i);
+ final HistoricalFeatureOps spliceOps = origOps.splice(fractionToRemove);
+ if (spliceOps != null) {
+ if (splice == null) {
+ splice = new HistoricalPackageOps(mPackageName);
+ }
+ if (splice.mHistoricalFeatureOps == null) {
+ splice.mHistoricalFeatureOps = new ArrayMap<>();
+ }
+ splice.mHistoricalFeatureOps.put(spliceOps.getFeatureId(), spliceOps);
+ }
+ }
+ return splice;
+ }
+
+ private void merge(@NonNull HistoricalPackageOps other) {
+ final int featureCount = other.getFeatureCount();
+ for (int i = 0; i < featureCount; i++) {
+ final HistoricalFeatureOps otherFeatureOps = other.getFeatureOpsAt(i);
+ final HistoricalFeatureOps thisFeatureOps = getFeatureOps(
+ otherFeatureOps.getFeatureId());
+ if (thisFeatureOps != null) {
+ thisFeatureOps.merge(otherFeatureOps);
+ } else {
+ if (mHistoricalFeatureOps == null) {
+ mHistoricalFeatureOps = new ArrayMap<>();
+ }
+ mHistoricalFeatureOps.put(otherFeatureOps.getFeatureId(), otherFeatureOps);
+ }
+ }
+ }
+
+ private void filter(@Nullable String featureId, @Nullable String[] opNames,
+ @HistoricalOpsRequestFilter int filter, double fractionToRemove) {
+ final int featureCount = getFeatureCount();
+ for (int i = featureCount - 1; i >= 0; i--) {
+ final HistoricalFeatureOps featureOps = getFeatureOpsAt(i);
+ if ((filter & FILTER_BY_FEATURE_ID) != 0 && !Objects.equals(featureId,
+ featureOps.getFeatureId())) {
+ mHistoricalFeatureOps.removeAt(i);
+ } else {
+ featureOps.filter(opNames, filter, fractionToRemove);
+ }
+ }
+ }
+
+ private void accept(@NonNull HistoricalOpsVisitor visitor) {
+ visitor.visitHistoricalPackageOps(this);
+ final int featureCount = getFeatureCount();
+ for (int i = 0; i < featureCount; i++) {
+ getFeatureOpsAt(i).accept(visitor);
+ }
+ }
+
+ private boolean isEmpty() {
+ final int featureCount = getFeatureCount();
+ for (int i = featureCount - 1; i >= 0; i--) {
+ final HistoricalFeatureOps featureOps = mHistoricalFeatureOps.valueAt(i);
+ if (!featureOps.isEmpty()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void increaseAccessCount(int opCode, @Nullable String featureId,
+ @UidState int uidState, @OpFlags int flags, long increment) {
+ getOrCreateHistoricalFeatureOps(featureId).increaseAccessCount(
+ opCode, uidState, flags, increment);
+ }
+
+ private void increaseRejectCount(int opCode, @Nullable String featureId,
+ @UidState int uidState, @OpFlags int flags, long increment) {
+ getOrCreateHistoricalFeatureOps(featureId).increaseRejectCount(
+ opCode, uidState, flags, increment);
+ }
+
+ private void increaseAccessDuration(int opCode, @Nullable String featureId,
+ @UidState int uidState, @OpFlags int flags, long increment) {
+ getOrCreateHistoricalFeatureOps(featureId).increaseAccessDuration(
+ opCode, uidState, flags, increment);
+ }
+
+ /**
+ * Gets the package name which the data represents.
+ *
+ * @return The package name which the data represents.
+ */
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ private @NonNull HistoricalFeatureOps getOrCreateHistoricalFeatureOps(
+ @Nullable String featureId) {
+ if (mHistoricalFeatureOps == null) {
+ mHistoricalFeatureOps = new ArrayMap<>();
+ }
+ HistoricalFeatureOps historicalFeatureOp = mHistoricalFeatureOps.get(featureId);
+ if (historicalFeatureOp == null) {
+ historicalFeatureOp = new HistoricalFeatureOps(featureId);
+ mHistoricalFeatureOps.put(featureId, historicalFeatureOp);
+ }
+ return historicalFeatureOp;
+ }
+
+ /**
+ * Gets number historical app ops.
+ *
+ * @return The number historical app ops.
+ * @see #getOpAt(int)
+ */
+ public @IntRange(from = 0) int getOpCount() {
+ int numOps = 0;
+ int numFeatures = getFeatureCount();
+
+ for (int code = 0; code < _NUM_OP; code++) {
+ String opName = opToPublicName(code);
+
+ for (int featureNum = 0; featureNum < numFeatures; featureNum++) {
+ if (getFeatureOpsAt(featureNum).getOp(opName) != null) {
+ numOps++;
+ break;
+ }
+ }
+ }
+
+ return numOps;
+ }
+
+ /**
+ * Gets the historical op at a given index.
+ *
+ * <p>This combines the counts from all features.
+ *
+ * @param index The index to lookup.
+ * @return The op at the given index.
+ * @see #getOpCount()
+ */
+ public @NonNull HistoricalOp getOpAt(@IntRange(from = 0) int index) {
+ int numOpsFound = 0;
+ int numFeatures = getFeatureCount();
+
+ for (int code = 0; code < _NUM_OP; code++) {
+ String opName = opToPublicName(code);
+
+ for (int featureNum = 0; featureNum < numFeatures; featureNum++) {
+ if (getFeatureOpsAt(featureNum).getOp(opName) != null) {
+ if (numOpsFound == index) {
+ return getOp(opName);
+ } else {
+ numOpsFound++;
+ break;
+ }
+ }
+ }
+ }
+
+ throw new IndexOutOfBoundsException();
+ }
+
+ /**
+ * Gets the historical entry for a given op name.
+ *
+ * <p>This combines the counts from all features.
+ *
+ * @param opName The op name.
+ * @return The historical entry for that op name.
+ */
+ public @Nullable HistoricalOp getOp(@NonNull String opName) {
+ if (mHistoricalFeatureOps == null) {
+ return null;
+ }
+
+ HistoricalOp combinedOp = null;
+ int numFeatures = getFeatureCount();
+ for (int i = 0; i < numFeatures; i++) {
+ HistoricalOp featureOp = getFeatureOpsAt(i).getOp(opName);
+ if (featureOp != null) {
+ if (combinedOp == null) {
+ combinedOp = new HistoricalOp(featureOp);
+ } else {
+ combinedOp.merge(featureOp);
+ }
+ }
+ }
+
+ return combinedOp;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeString(mPackageName);
+ parcel.writeTypedArrayMap(mHistoricalFeatureOps, flags);
+ }
+
+ public static final @android.annotation.NonNull Creator<HistoricalPackageOps> CREATOR =
+ new Creator<HistoricalPackageOps>() {
+ @Override
+ public @NonNull HistoricalPackageOps createFromParcel(@NonNull Parcel parcel) {
+ return new HistoricalPackageOps(parcel);
+ }
+
+ @Override
+ public @NonNull HistoricalPackageOps[] newArray(int size) {
+ return new HistoricalPackageOps[size];
+ }
+ };
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final HistoricalPackageOps other = (HistoricalPackageOps) obj;
+ if (!mPackageName.equals(other.mPackageName)) {
+ return false;
+ }
+ if (mHistoricalFeatureOps == null) {
+ if (other.mHistoricalFeatureOps != null) {
+ return false;
+ }
+ } else if (!mHistoricalFeatureOps.equals(other.mHistoricalFeatureOps)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mPackageName != null ? mPackageName.hashCode() : 0;
+ result = 31 * result + (mHistoricalFeatureOps != null ? mHistoricalFeatureOps.hashCode()
+ : 0);
+ return result;
+ }
+
+ /**
+ * Gets number of feature with historical ops.
+ *
+ * @return The number of feature with historical ops.
+ *
+ * @see #getFeatureOpsAt(int)
+ */
+ public @IntRange(from = 0) int getFeatureCount() {
+ if (mHistoricalFeatureOps == null) {
+ return 0;
+ }
+ return mHistoricalFeatureOps.size();
+ }
+
+ /**
+ * Gets the historical feature ops at a given index.
+ *
+ * @param index The index.
+ *
+ * @return The historical feature ops at the given index.
+ *
+ * @see #getFeatureCount()
+ */
+ public @NonNull HistoricalFeatureOps getFeatureOpsAt(@IntRange(from = 0) int index) {
+ if (mHistoricalFeatureOps == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ return mHistoricalFeatureOps.valueAt(index);
+ }
+
+ /**
+ * Gets the historical feature ops for a given feature.
+ *
+ * @param featureId The feature id.
+ *
+ * @return The historical ops for the feature.
+ */
+ public @Nullable HistoricalFeatureOps getFeatureOps(@NonNull String featureId) {
+ if (mHistoricalFeatureOps == null) {
+ return null;
+ }
+ return mHistoricalFeatureOps.get(featureId);
+ }
+ }
+
+ /**
+ * This class represents historical app op information about a feature in a package.
+ *
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ /* codegen verifier cannot deal with nested class parameters
+ @DataClass(genHiddenConstructor = true,
+ genEqualsHashCode = true, genHiddenCopyConstructor = true) */
+ @DataClass.Suppress("getHistoricalOps")
+ public static final class HistoricalFeatureOps implements Parcelable {
+ /** Id of the {@link Context#createFeatureContext feature} in the package */
+ private final @Nullable String mFeatureId;
+
+ /** Ops for this feature */
+ private @Nullable ArrayMap<String, HistoricalOp> mHistoricalOps;
+
+ /** @hide */
+ public HistoricalFeatureOps(@NonNull String featureId) {
+ mFeatureId = featureId;
+ }
+
+ private HistoricalFeatureOps(@NonNull HistoricalFeatureOps other) {
+ mFeatureId = other.mFeatureId;
final int opCount = other.getOpCount();
for (int i = 0; i < opCount; i++) {
final HistoricalOp origOp = other.getOpAt(i);
@@ -4753,20 +5181,15 @@
}
}
- private HistoricalPackageOps(@NonNull Parcel parcel) {
- mPackageName = parcel.readString();
- mHistoricalOps = parcel.createTypedArrayMap(HistoricalOp.CREATOR);
- }
-
- private @Nullable HistoricalPackageOps splice(double fractionToRemove) {
- HistoricalPackageOps splice = null;
+ private @Nullable HistoricalFeatureOps splice(double fractionToRemove) {
+ HistoricalFeatureOps splice = null;
final int opCount = getOpCount();
for (int i = 0; i < opCount; i++) {
final HistoricalOp origOps = getOpAt(i);
final HistoricalOp spliceOps = origOps.splice(fractionToRemove);
if (spliceOps != null) {
if (splice == null) {
- splice = new HistoricalPackageOps(mPackageName);
+ splice = new HistoricalFeatureOps(mFeatureId, null);
}
if (splice.mHistoricalOps == null) {
splice.mHistoricalOps = new ArrayMap<>();
@@ -4777,7 +5200,7 @@
return splice;
}
- private void merge(@NonNull HistoricalPackageOps other) {
+ private void merge(@NonNull HistoricalFeatureOps other) {
final int opCount = other.getOpCount();
for (int i = 0; i < opCount; i++) {
final HistoricalOp otherOp = other.getOpAt(i);
@@ -4793,11 +5216,13 @@
}
}
- private void filter(@Nullable String[] opNames, double scaleFactor) {
+ private void filter(@Nullable String[] opNames, @HistoricalOpsRequestFilter int filter,
+ double scaleFactor) {
final int opCount = getOpCount();
for (int i = opCount - 1; i >= 0; i--) {
final HistoricalOp op = mHistoricalOps.valueAt(i);
- if (opNames != null && !ArrayUtils.contains(opNames, op.getOpName())) {
+ if ((filter & FILTER_BY_OP_NAMES) != 0 && !ArrayUtils.contains(opNames,
+ op.getOpName())) {
mHistoricalOps.removeAt(i);
} else {
op.filter(scaleFactor);
@@ -4832,15 +5257,6 @@
}
/**
- * Gets the package name which the data represents.
- *
- * @return The package name which the data represents.
- */
- public @NonNull String getPackageName() {
- return mPackageName;
- }
-
- /**
* Gets number historical app ops.
*
* @return The number historical app ops.
@@ -4880,19 +5296,8 @@
return mHistoricalOps.get(opName);
}
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel parcel, int flags) {
- parcel.writeString(mPackageName);
- parcel.writeTypedArrayMap(mHistoricalOps, flags);
- }
-
private void accept(@NonNull HistoricalOpsVisitor visitor) {
- visitor.visitHistoricalPackageOps(this);
+ visitor.visitHistoricalFeatureOps(this);
final int opCount = getOpCount();
for (int i = 0; i < opCount; i++) {
getOpAt(i).accept(visitor);
@@ -4912,47 +5317,143 @@
return op;
}
- public static final @android.annotation.NonNull Creator<HistoricalPackageOps> CREATOR =
- new Creator<HistoricalPackageOps>() {
+
+
+ // Code below generated by codegen v1.0.14.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/AppOpsManager.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new HistoricalFeatureOps.
+ *
+ * @param featureId
+ * Id of the {@link Context#createFeatureContext feature} in the package
+ * @param historicalOps
+ * Ops for this feature
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public HistoricalFeatureOps(
+ @Nullable String featureId,
+ @Nullable ArrayMap<String,HistoricalOp> historicalOps) {
+ this.mFeatureId = featureId;
+ this.mHistoricalOps = historicalOps;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Id of the {@link Context#createFeatureContext feature} in the package
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getFeatureId() {
+ return mFeatureId;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(HistoricalFeatureOps other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ HistoricalFeatureOps that = (HistoricalFeatureOps) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && Objects.equals(mFeatureId, that.mFeatureId)
+ && Objects.equals(mHistoricalOps, that.mHistoricalOps);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + Objects.hashCode(mFeatureId);
+ _hash = 31 * _hash + Objects.hashCode(mHistoricalOps);
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mFeatureId != null) flg |= 0x1;
+ if (mHistoricalOps != null) flg |= 0x2;
+ dest.writeByte(flg);
+ if (mFeatureId != null) dest.writeString(mFeatureId);
+ if (mHistoricalOps != null) dest.writeMap(mHistoricalOps);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ HistoricalFeatureOps(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ String featureId = (flg & 0x1) == 0 ? null : in.readString();
+ ArrayMap<String,HistoricalOp> historicalOps = null;
+ if ((flg & 0x2) != 0) {
+ historicalOps = new ArrayMap();
+ in.readMap(historicalOps, HistoricalOp.class.getClassLoader());
+ }
+
+ this.mFeatureId = featureId;
+ this.mHistoricalOps = historicalOps;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<HistoricalFeatureOps> CREATOR
+ = new Parcelable.Creator<HistoricalFeatureOps>() {
@Override
- public @NonNull HistoricalPackageOps createFromParcel(@NonNull Parcel parcel) {
- return new HistoricalPackageOps(parcel);
+ public HistoricalFeatureOps[] newArray(int size) {
+ return new HistoricalFeatureOps[size];
}
@Override
- public @NonNull HistoricalPackageOps[] newArray(int size) {
- return new HistoricalPackageOps[size];
+ public HistoricalFeatureOps createFromParcel(@NonNull Parcel in) {
+ return new HistoricalFeatureOps(in);
}
};
- @Override
- public boolean equals(@Nullable Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null || getClass() != obj.getClass()) {
- return false;
- }
- final HistoricalPackageOps other = (HistoricalPackageOps) obj;
- if (!mPackageName.equals(other.mPackageName)) {
- return false;
- }
- if (mHistoricalOps == null) {
- if (other.mHistoricalOps != null) {
- return false;
- }
- } else if (!mHistoricalOps.equals(other.mHistoricalOps)) {
- return false;
- }
- return true;
- }
+ /*
+ @DataClass.Generated(
+ time = 1578113234821L,
+ codegenVersion = "1.0.14",
+ sourceFile = "frameworks/base/core/java/android/app/AppOpsManager.java",
+ inputSignatures = "private final @android.annotation.Nullable java.lang.String mFeatureId\nprivate @android.annotation.Nullable android.util.ArrayMap<java.lang.String,android.app.HistoricalOp> mHistoricalOps\nprivate @android.annotation.Nullable android.app.HistoricalFeatureOps splice(double)\nprivate void merge(android.app.HistoricalFeatureOps)\nprivate void filter(java.lang.String[],int,double)\nprivate boolean isEmpty()\nprivate void increaseAccessCount(int,int,int,long)\nprivate void increaseRejectCount(int,int,int,long)\nprivate void increaseAccessDuration(int,int,int,long)\npublic @android.annotation.IntRange(from=0L) int getOpCount()\npublic @android.annotation.NonNull android.app.HistoricalOp getOpAt(int)\npublic @android.annotation.Nullable android.app.HistoricalOp getOp(java.lang.String)\nprivate void accept(android.app.HistoricalOpsVisitor)\nprivate @android.annotation.NonNull android.app.HistoricalOp getOrCreateHistoricalOp(int)\nclass HistoricalFeatureOps extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true, genEqualsHashCode=true, genHiddenCopyConstructor=true)")
+ @Deprecated
+ private void __metadata() {}
+ */
- @Override
- public int hashCode() {
- int result = mPackageName != null ? mPackageName.hashCode() : 0;
- result = 31 * result + (mHistoricalOps != null ? mHistoricalOps.hashCode() : 0);
- return result;
- }
+ //@formatter:on
+ // End of generated code
+
}
/**
@@ -5288,13 +5789,13 @@
if (mOp != other.mOp) {
return false;
}
- if (!Objects.equals(mAccessCount, other.mAccessCount)) {
+ if (!equalsLongSparseLongArray(mAccessCount, other.mAccessCount)) {
return false;
}
- if (!Objects.equals(mRejectCount, other.mRejectCount)) {
+ if (!equalsLongSparseLongArray(mRejectCount, other.mRejectCount)) {
return false;
}
- return Objects.equals(mAccessDuration, other.mAccessDuration);
+ return equalsLongSparseLongArray(mAccessDuration, other.mAccessDuration);
}
@Override
@@ -5621,9 +6122,9 @@
Objects.requireNonNull(executor, "executor cannot be null");
Objects.requireNonNull(callback, "callback cannot be null");
try {
- mService.getHistoricalOps(request.mUid, request.mPackageName, request.mOpNames,
- request.mBeginTimeMillis, request.mEndTimeMillis, request.mFlags,
- new RemoteCallback((result) -> {
+ mService.getHistoricalOps(request.mUid, request.mPackageName, request.mFeatureId,
+ request.mOpNames, request.mFilter, request.mBeginTimeMillis,
+ request.mEndTimeMillis, request.mFlags, new RemoteCallback((result) -> {
final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS);
final long identity = Binder.clearCallingIdentity();
try {
@@ -5661,8 +6162,8 @@
Objects.requireNonNull(callback, "callback cannot be null");
try {
mService.getHistoricalOpsFromDiskRaw(request.mUid, request.mPackageName,
- request.mOpNames, request.mBeginTimeMillis, request.mEndTimeMillis,
- request.mFlags, new RemoteCallback((result) -> {
+ request.mFeatureId, request.mOpNames, request.mFilter, request.mBeginTimeMillis,
+ request.mEndTimeMillis, request.mFlags, new RemoteCallback((result) -> {
final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS);
final long identity = Binder.clearCallingIdentity();
try {
@@ -7503,6 +8004,30 @@
return lastEvent;
}
+ private static boolean equalsLongSparseLongArray(@Nullable LongSparseLongArray a,
+ @Nullable LongSparseLongArray b) {
+ if (a == b) {
+ return true;
+ }
+
+ if (a == null || b == null) {
+ return false;
+ }
+
+ if (a.size() != b.size()) {
+ return false;
+ }
+
+ int numEntries = a.size();
+ for (int i = 0; i < numEntries; i++) {
+ if (a.keyAt(i) != b.keyAt(i) || a.valueAt(i) != b.valueAt(i)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
private static void writeLongSparseLongArrayToParcel(
@Nullable LongSparseLongArray array, @NonNull Parcel parcel) {
if (array != null) {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index d3132d8..f3028a1 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -11338,4 +11338,39 @@
}
return false;
}
+
+ /**
+ * Called by Device owner to set packages as protected. User will not be able to clear app
+ * data or force-stop protected packages.
+ *
+ * @param admin which {@link DeviceAdminReceiver} this request is associated with
+ * @param packages The package names to protect.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ */
+ public void setProtectedPackages(@NonNull ComponentName admin, @NonNull List<String> packages) {
+ if (mService != null) {
+ try {
+ mService.setProtectedPackages(admin, packages);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Returns the list of packages protected by the device owner.
+ *
+ * @param admin which {@link DeviceAdminReceiver} this request is associated with
+ * @throws SecurityException if {@code admin} is not a device owner.
+ */
+ public @NonNull List<String> getProtectedPackages(@NonNull ComponentName admin) {
+ if (mService != null) {
+ try {
+ return mService.getProtectedPackages(admin);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ return Collections.emptyList();
+ }
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 08c5dff..55dfe2f 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -452,4 +452,8 @@
boolean startViewCalendarEventInManagedProfile(String packageName, long eventId, long start, long end, boolean allDay, int flags);
boolean setKeyGrantForApp(in ComponentName admin, String callerPackage, String alias, String packageName, boolean hasGrant);
+
+ void setProtectedPackages(in ComponentName admin, in List<String> packages);
+
+ List<String> getProtectedPackages(in ComponentName admin);
}
diff --git a/core/java/android/bluetooth/BluetoothHidHost.java b/core/java/android/bluetooth/BluetoothHidHost.java
index 8f5cdf0..c1233b8 100644
--- a/core/java/android/bluetooth/BluetoothHidHost.java
+++ b/core/java/android/bluetooth/BluetoothHidHost.java
@@ -17,9 +17,12 @@
package android.bluetooth;
import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.content.Context;
import android.os.Binder;
@@ -43,6 +46,7 @@
*
* @hide
*/
+@SystemApi
public final class BluetoothHidHost implements BluetoothProfile {
private static final String TAG = "BluetoothHidHost";
private static final boolean DBG = true;
@@ -66,6 +70,7 @@
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
* receive.
*/
+ @SuppressLint("ActionValue")
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_CONNECTION_STATE_CHANGED =
"android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
@@ -325,7 +330,7 @@
* {@inheritDoc}
*/
@Override
- public List<BluetoothDevice> getConnectedDevices() {
+ public @NonNull List<BluetoothDevice> getConnectedDevices() {
if (VDBG) log("getConnectedDevices()");
final IBluetoothHidHost service = getService();
if (service != null && isEnabled()) {
@@ -342,6 +347,8 @@
/**
* {@inheritDoc}
+ *
+ * @hide
*/
@Override
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
@@ -363,7 +370,7 @@
* {@inheritDoc}
*/
@Override
- public int getConnectionState(BluetoothDevice device) {
+ public int getConnectionState(@Nullable BluetoothDevice device) {
if (VDBG) log("getState(" + device + ")");
final IBluetoothHidHost service = getService();
if (service != null && isEnabled() && isValidDevice(device)) {
@@ -409,7 +416,7 @@
*/
@SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
- public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
+ public boolean setConnectionPolicy(@Nullable BluetoothDevice device, int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
final IBluetoothHidHost service = getService();
if (service != null && isEnabled() && isValidDevice(device)) {
@@ -457,7 +464,7 @@
*/
@SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH)
- public int getConnectionPolicy(BluetoothDevice device) {
+ public int getConnectionPolicy(@Nullable BluetoothDevice device) {
if (VDBG) log("getConnectionPolicy(" + device + ")");
final IBluetoothHidHost service = getService();
if (service != null && isEnabled() && isValidDevice(device)) {
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index c579fdf..948885e 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -16,7 +16,10 @@
package android.bluetooth;
+import android.Manifest;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
@@ -271,6 +274,42 @@
}
/**
+ * Pbap does not store connection policy, so this function only disconnects Pbap if
+ * connectionPolicy is CONNECTION_POLICY_FORBIDDEN.
+ *
+ * <p> The device should already be paired.
+ * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
+ * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Paired bluetooth device
+ * @param connectionPolicy is the connection policy to set to for this profile
+ * @return true if pbap is successfully disconnected, false otherwise
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+ @ConnectionPolicy int connectionPolicy) {
+ if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+ try {
+ final IBluetoothPbap service = mService;
+ if (service != null && isEnabled()
+ && isValidDevice(device)) {
+ if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+ && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ return false;
+ }
+ return service.setConnectionPolicy(device, connectionPolicy);
+ }
+ if (service == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+
+ /**
* Disconnects the current Pbap client (PCE). Currently this call blocks,
* it may soon be made asynchronous. Returns false if this proxy object is
* not currently connected to the Pbap service.
diff --git a/core/java/android/content/ComponentName.java b/core/java/android/content/ComponentName.java
index 27960b0..1967a01 100644
--- a/core/java/android/content/ComponentName.java
+++ b/core/java/android/content/ComponentName.java
@@ -305,6 +305,12 @@
proto.end(token);
}
+ /**
+ * {@inheritDoc}
+ *
+ * <p>Two components are considered to be equal if the packages in which they reside have the
+ * same name, and if the classes that implement each component also have the same name.
+ */
@Override
public boolean equals(Object obj) {
try {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 4815d78..a28868e 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3992,6 +3992,7 @@
*/
public static final String NETWORK_STATS_SERVICE = "netstats";
/** {@hide} */
+ @SystemApi
public static final String NETWORK_POLICY_SERVICE = "netpolicy";
/** {@hide} */
public static final String NETWORK_WATCHLIST_SERVICE = "network_watchlist";
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index f792127..b85c58a 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2918,6 +2918,18 @@
public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels";
/**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+ * the requisite hardware support to support reboot escrow of synthetic password for updates.
+ *
+ * <p>This feature implies that the device has the RebootEscrow HAL implementation.
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow";
+
+ /**
* Extra field name for the URI to a verification file. Passed to a package
* verifier.
*
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index b808088..55505ba 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -47,8 +47,7 @@
import java.util.UUID;
/**
- * The SoundTrigger class provides access via JNI to the native service managing
- * the sound trigger HAL.
+ * The SoundTrigger class provides access to the service managing the sound trigger HAL.
*
* @hide
*/
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 431773d..9731f3c 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -247,9 +247,8 @@
* removing even the capabilities that are set by default when the object is constructed.
*
* @return The builder to facilitate chaining.
- * @hide
*/
- @UnsupportedAppUsage
+ @NonNull
public Builder clearCapabilities() {
mNetworkCapabilities.clearAll();
return this;
diff --git a/core/java/android/net/NetworkSpecifier.java b/core/java/android/net/NetworkSpecifier.java
index 2bc3eb5..cf31d21 100644
--- a/core/java/android/net/NetworkSpecifier.java
+++ b/core/java/android/net/NetworkSpecifier.java
@@ -16,6 +16,9 @@
package android.net;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
/**
* Describes specific properties of a requested network for use in a {@link NetworkRequest}.
*
@@ -31,7 +34,8 @@
*
* @hide
*/
- public abstract boolean satisfiedBy(NetworkSpecifier other);
+ @SystemApi
+ public abstract boolean satisfiedBy(@Nullable NetworkSpecifier other);
/**
* Optional method which can be overridden by concrete implementations of NetworkSpecifier to
@@ -45,6 +49,7 @@
*
* @hide
*/
+ @SystemApi
public void assertValidFromUid(int requestorUid) {
// empty
}
@@ -68,6 +73,8 @@
*
* @hide
*/
+ @SystemApi
+ @Nullable
public NetworkSpecifier redact() {
// TODO (b/122160111): convert default to null once all platform NetworkSpecifiers
// implement this method.
diff --git a/core/java/android/os/ExternalVibration.java b/core/java/android/os/ExternalVibration.java
index 2991db2..e62244f 100644
--- a/core/java/android/os/ExternalVibration.java
+++ b/core/java/android/os/ExternalVibration.java
@@ -161,7 +161,6 @@
out.writeInt(mUid);
out.writeString(mPkg);
writeAudioAttributes(mAttrs, out, flags);
- out.writeParcelable(mAttrs, flags);
out.writeStrongBinder(mController.asBinder());
out.writeStrongBinder(mToken);
}
diff --git a/core/java/android/os/IRecoverySystem.aidl b/core/java/android/os/IRecoverySystem.aidl
index c5ceecd..2561e1e 100644
--- a/core/java/android/os/IRecoverySystem.aidl
+++ b/core/java/android/os/IRecoverySystem.aidl
@@ -17,6 +17,7 @@
package android.os;
+import android.content.IntentSender;
import android.os.IRecoverySystemProgressListener;
/** @hide */
@@ -26,4 +27,7 @@
boolean setupBcb(in String command);
boolean clearBcb();
void rebootRecoveryWithCommand(in String command);
+ boolean requestLskf(in String updateToken, in IntentSender sender);
+ boolean clearLskf();
+ boolean rebootWithLskf(in String updateToken, in String reason);
}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index e81a505..edaaf81 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -62,7 +62,7 @@
boolean canAddMoreManagedProfiles(int userId, boolean allowedToRemoveOne);
UserInfo getProfileParent(int userId);
boolean isSameProfileGroup(int userId, int otherUserHandle);
- String getUserTypeForUser(int userId);
+ boolean isUserOfType(int userId, in String userType);
@UnsupportedAppUsage
UserInfo getUserInfo(int userId);
String getUserAccount(int userId);
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index 1901820..cdcb3ff 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -18,6 +18,8 @@
import static java.nio.charset.StandardCharsets.UTF_8;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
@@ -29,6 +31,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.provider.Settings;
import android.telephony.SubscriptionInfo;
@@ -624,22 +627,91 @@
}
/**
- * Schedule to install the given package on next boot. The caller needs to
- * ensure that the package must have been processed (uncrypt'd) if needed.
- * It sets up the command in BCB (bootloader control block), which will
- * be read by the bootloader and the recovery image.
+ * Prepare to apply an unattended update by asking the user for their Lock Screen Knowledge
+ * Factor (LSKF). If supplied, the {@code intentSender} will be called when the system is setup
+ * and ready to apply the OTA.
+ * <p>
+ * When the system is already prepared for update and this API is called again with the same
+ * {@code updateToken}, it will not call the intent sender nor request the user enter their Lock
+ * Screen Knowledge Factor.
+ * <p>
+ * When this API is called again with a different {@code updateToken}, the prepared-for-update
+ * status is reset and process repeats as though it's the initial call to this method as
+ * described in the first paragraph.
*
- * @param Context the Context to use.
- * @param packageFile the package to be installed.
- *
- * @throws IOException if there were any errors setting up the BCB.
- *
+ * @param context the Context to use.
+ * @param updateToken token used to indicate which update was prepared
+ * @param intentSender the intent to call when the update is prepared; may be {@code null}
+ * @throws IOException if there were any errors setting up unattended update
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.RECOVERY)
- public static void scheduleUpdateOnBoot(Context context, File packageFile)
+ public static void prepareForUnattendedUpdate(@NonNull Context context,
+ @NonNull String updateToken, @Nullable IntentSender intentSender) throws IOException {
+ if (updateToken == null) {
+ throw new NullPointerException("updateToken == null");
+ }
+ RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
+ if (!rs.requestLskf(updateToken, intentSender)) {
+ throw new IOException("preparation for update failed");
+ }
+ }
+
+ /**
+ * Request that any previously requested Lock Screen Knowledge Factor (LSKF) is cleared and
+ * the preparation for unattended update is reset.
+ *
+ * @param context the Context to use.
+ * @throws IOException if there were any errors setting up unattended update
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.RECOVERY)
+ public static boolean clearPrepareForUnattendedUpdate(@NonNull Context context)
throws IOException {
+ RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
+ return rs.clearLskf();
+ }
+
+ /**
+ * Request that the device reboot and apply the update that has been prepared. The
+ * {@code updateToken} must match what was given for {@link #prepareForUnattendedUpdate} or
+ * this will return {@code false}.
+ *
+ * @param context the Context to use.
+ * @param updateToken the token used to call {@link #prepareForUnattendedUpdate} before
+ * @param reason the reboot reason to give to the {@link PowerManager}
+ * @throws IOException if there were any errors setting up unattended update
+ * @return false if the reboot couldn't proceed because the device wasn't ready for an
+ * unattended reboot or if the {@code updateToken} did not match the previously
+ * given token
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.RECOVERY)
+ public static boolean rebootAndApply(@NonNull Context context, @NonNull String updateToken,
+ @NonNull String reason) throws IOException {
+ if (updateToken == null) {
+ throw new NullPointerException("updateToken == null");
+ }
+ RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
+ return rs.rebootWithLskf(updateToken, reason);
+ }
+
+ /**
+ * Schedule to install the given package on next boot. The caller needs to ensure that the
+ * package must have been processed (uncrypt'd) if needed. It sets up the command in BCB
+ * (bootloader control block), which will be read by the bootloader and the recovery image.
+ *
+ * @param context the Context to use.
+ * @param packageFile the package to be installed.
+ * @throws IOException if there were any errors setting up the BCB.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.RECOVERY)
+ public static void scheduleUpdateOnBoot(Context context, File packageFile) throws IOException {
String filename = packageFile.getCanonicalPath();
boolean securityUpdate = filename.endsWith("_s.zip");
@@ -1204,6 +1276,49 @@
}
/**
+ * Begins the process of asking the user for the Lock Screen Knowledge Factor.
+ *
+ * @param updateToken token that will be used in calls to {@link #rebootAndApply} to ensure
+ * that the preparation was for the correct update
+ * @return true if the request was correct
+ * @throws IOException if the recovery system service could not be contacted
+ */
+ private boolean requestLskf(String updateToken, IntentSender sender) throws IOException {
+ try {
+ return mService.requestLskf(updateToken, sender);
+ } catch (RemoteException e) {
+ throw new IOException("could request update");
+ }
+ }
+
+ /**
+ * Calls the recovery system service and clears the setup for the OTA.
+ *
+ * @return true if the setup for OTA was cleared
+ * @throws IOException if the recovery system service could not be contacted
+ */
+ private boolean clearLskf() throws IOException {
+ try {
+ return mService.clearLskf();
+ } catch (RemoteException e) {
+ throw new IOException("could not clear LSKF");
+ }
+ }
+
+ /**
+ * Calls the recovery system service to reboot and apply update.
+ *
+ * @param updateToken the update token for which the update was prepared
+ */
+ private boolean rebootWithLskf(String updateToken, String reason) throws IOException {
+ try {
+ return mService.rebootWithLskf(updateToken, reason);
+ } catch (RemoteException e) {
+ throw new IOException("could not reboot for update");
+ }
+ }
+
+ /**
* Internally, recovery treats each line of the command file as a separate
* argv, so we only need to protect against newlines and nulls.
*/
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index fa9569b..6e199ce3 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1626,39 +1626,35 @@
}
/**
- * Returns the calling user's user type.
+ * Returns whether the current user is of the given user type, such as
+ * {@link UserManager#USER_TYPE_FULL_GUEST}.
*
- * // TODO(b/142482943): Decide on the appropriate permission requirements.
- *
- * @return the name of the user type, such as {@link UserManager#USER_TYPE_PROFILE_MANAGED}.
+ * @return true if the user is of the given user type.
* @hide
*/
- public @NonNull String getUserType() {
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ public boolean isUserOfType(@NonNull String userType) {
try {
- return mService.getUserTypeForUser(UserHandle.myUserId());
+ return mService.isUserOfType(UserHandle.myUserId(), userType);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
/**
- * Returns the given user's user type.
- *
- * // TODO(b/142482943): Decide on the appropriate permission requirements.
- * Requires {@link android.Manifest.permission#MANAGE_USERS} or
- * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the caller
- * must be in the same profile group of specified user.
+ * Returns whether the given user is of the given user type, such as
+ * {@link UserManager#USER_TYPE_FULL_GUEST}.
*
* @param userHandle the user handle of the user whose type is being requested.
- * @return the name of the user's user type, e.g. {@link UserManager#USER_TYPE_PROFILE_MANAGED},
- * or {@code null} if there is no such user.
+ * @param userType the name of the user's user type, e.g.
+ * {@link UserManager#USER_TYPE_PROFILE_MANAGED}.
+ * @return true if the userHandle user is of type userType
* @hide
*/
- @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
- android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
- public @Nullable String getUserTypeForUser(@NonNull UserHandle userHandle) {
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ public boolean isUserOfType(@NonNull UserHandle userHandle, @NonNull String userType) {
try {
- return mService.getUserTypeForUser(userHandle.getIdentifier());
+ return mService.isUserOfType(userHandle.getIdentifier(), userType);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 34c03ae..bbaf94a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -13713,14 +13713,6 @@
"backup_agent_timeout_parameters";
/**
- * Whether the backup system service supports multiple users (0 = disabled, 1 = enabled). If
- * disabled, the service will only be active for the system user.
- *
- * @hide
- */
- public static final String BACKUP_MULTI_USER_ENABLED = "backup_multi_user_enabled";
-
- /**
* Blacklist of GNSS satellites.
*
* This is a list of integers separated by commas to represent pairs of (constellation,
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 565a522..1d9bdb8 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4216,7 +4216,7 @@
* The subscription which received this cell broadcast message.
* <P>Type: INTEGER</P>
*/
- public static final String SUB_ID = "sub_id";
+ public static final String SUBSCRIPTION_ID = "sub_id";
/**
* The slot which received this cell broadcast message.
@@ -4474,7 +4474,7 @@
public static final String[] QUERY_COLUMNS_FWK = {
_ID,
SLOT_INDEX,
- SUB_ID,
+ SUBSCRIPTION_ID,
GEOGRAPHICAL_SCOPE,
PLMN,
LAC,
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index 26ed5c9..e08a06a 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -369,6 +369,21 @@
@RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 0x20000000;
+ /**
+ * Listen for Registration Failures.
+ *
+ * Listen for indications that a registration procedure has failed in either the CS or PS
+ * domain. This indication does not necessarily indicate a change of service state, which should
+ * be tracked via {@link #LISTEN_SERVICE_STATE}.
+ *
+ * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling
+ * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
+ *
+ * @see #onRegistrationFailed()
+ */
+ @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
+ public static final int LISTEN_REGISTRATION_FAILURE = 0x40000000;
+
/*
* Subscription used to listen to the phone state changes
* @hide
@@ -932,6 +947,38 @@
}
/**
+ * Report that Registration or a Location/Routing/Tracking Area update has failed.
+ *
+ * <p>Indicate whenever a registration procedure, including a location, routing, or tracking
+ * area update fails. This includes procedures that do not necessarily result in a change of
+ * the modem's registration status. If the modem's registration status changes, that is
+ * reflected in the onNetworkStateChanged() and subsequent get{Voice/Data}RegistrationState().
+ *
+ * <p>Because registration failures are ephemeral, this callback is not sticky.
+ * Registrants will not receive the most recent past value when registering.
+ *
+ * @param cellIdentity the CellIdentity, which must include the globally unique identifier
+ * for the cell (for example, all components of the CGI or ECGI).
+ * @param chosenPlmn a 5 or 6 digit alphanumeric PLMN (MCC|MNC) among those broadcast by the
+ * cell that was chosen for the failed registration attempt.
+ * @param domain DOMAIN_CS, DOMAIN_PS or both in case of a combined procedure.
+ * @param causeCode the primary failure cause code of the procedure.
+ * For GSM/UMTS (MM), values are in TS 24.008 Sec 10.5.95
+ * For GSM/UMTS (GMM), values are in TS 24.008 Sec 10.5.147
+ * For LTE (EMM), cause codes are TS 24.301 Sec 9.9.3.9
+ * For NR (5GMM), cause codes are TS 24.501 Sec 9.11.3.2
+ * Integer.MAX_VALUE if this value is unused.
+ * @param additionalCauseCode the cause code of any secondary/combined procedure if appropriate.
+ * For UMTS, if a combined attach succeeds for PS only, then the GMM cause code shall be
+ * included as an additionalCauseCode. For LTE (ESM), cause codes are in
+ * TS 24.301 9.9.4.4. Integer.MAX_VALUE if this value is unused.
+ */
+ public void onRegistrationFailed(@NonNull CellIdentity cellIdentity, @NonNull String chosenPlmn,
+ @NetworkRegistrationInfo.Domain int domain, int causeCode, int additionalCauseCode) {
+ // default implementation empty
+ }
+
+ /**
* 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.
*
@@ -1203,6 +1250,18 @@
() -> psl.onImsCallDisconnectCauseChanged(disconnectCause)));
}
+
+ public void onRegistrationFailed(@NonNull CellIdentity cellIdentity,
+ @NonNull String chosenPlmn, @NetworkRegistrationInfo.Domain int domain,
+ int causeCode, int additionalCauseCode) {
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> psl.onRegistrationFailed(
+ cellIdentity, chosenPlmn, domain, causeCode, additionalCauseCode)));
+ // default implementation empty
+ }
}
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 1b2feda..9387a2c 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -24,6 +24,7 @@
import android.os.Binder;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.telephony.Annotation.ApnType;
import android.telephony.Annotation.CallState;
import android.telephony.Annotation.DataActivityType;
import android.telephony.Annotation.DataFailureCause;
@@ -352,7 +353,7 @@
* @param subId for which data connection state changed.
* @param slotIndex for which data connections state changed. Can be derived from subId except
* when subId is invalid.
- * @param apnType the APN type that triggered this update
+ * @param apnType the apn type bitmask, defined with {@code ApnSetting#TYPE_*} flags.
* @param preciseState the PreciseDataConnectionState
*
* @see android.telephony.PreciseDataConnection
@@ -360,7 +361,7 @@
* @hide
*/
public void notifyDataConnectionForSubscriber(int slotIndex, int subId,
- String apnType, PreciseDataConnectionState preciseState) {
+ @ApnType int apnType, PreciseDataConnectionState preciseState) {
try {
sRegistry.notifyDataConnectionForSubscriber(
slotIndex, subId, apnType, preciseState);
@@ -553,13 +554,13 @@
* @param subId for which data connection failed.
* @param slotIndex for which data conenction failed. Can be derived from subId except when
* subId is invalid.
- * @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN.
+ * @param apnType the apn type bitmask, defined with {@code ApnSetting#TYPE_*} flags.
* @param apn the APN {@link ApnSetting#getApnName()} of this data connection.
* @param failCause data fail cause.
*
* @hide
*/
- public void notifyPreciseDataConnectionFailed(int subId, int slotIndex, String apnType,
+ public void notifyPreciseDataConnectionFailed(int subId, int slotIndex, @ApnType int apnType,
String apn, @DataFailureCause int failCause) {
try {
sRegistry.notifyPreciseDataConnectionFailed(slotIndex, subId, apnType, apn, failCause);
@@ -614,9 +615,9 @@
* Notify call disconnect causes which contains {@link DisconnectCause} and {@link
* android.telephony.PreciseDisconnectCause}.
*
- * @param subId for which call disconnected.
* @param slotIndex for which call disconnected. Can be derived from subId except when subId is
* invalid.
+ * @param subId for which call disconnected.
* @param cause {@link DisconnectCause} for the disconnected call.
* @param preciseCause {@link android.telephony.PreciseDisconnectCause} for the disconnected
* call.
@@ -675,4 +676,36 @@
}
}
+
+ /**
+ * Report that Registration or a Location/Routing/Tracking Area update has failed.
+ *
+ * @param slotIndex for which call disconnected. Can be derived from subId except when subId is
+ * invalid.
+ * @param subId for which cellinfo changed.
+ * @param cellIdentity the CellIdentity, which must include the globally unique identifier
+ * for the cell (for example, all components of the CGI or ECGI).
+ * @param chosenPlmn a 5 or 6 digit alphanumeric PLMN (MCC|MNC) among those broadcast by the
+ * cell that was chosen for the failed registration attempt.
+ * @param domain DOMAIN_CS, DOMAIN_PS or both in case of a combined procedure.
+ * @param causeCode the primary failure cause code of the procedure.
+ * For GSM/UMTS (MM), values are in TS 24.008 Sec 10.5.95
+ * For GSM/UMTS (GMM), values are in TS 24.008 Sec 10.5.147
+ * For LTE (EMM), cause codes are TS 24.301 Sec 9.9.3.9
+ * For NR (5GMM), cause codes are TS 24.501 Sec 9.11.3.2
+ * Integer.MAX_VALUE if this value is unused.
+ * @param additionalCauseCode the cause code of any secondary/combined procedure if appropriate.
+ * For UMTS, if a combined attach succeeds for PS only, then the GMM cause code shall be
+ * included as an additionalCauseCode. For LTE (ESM), cause codes are in
+ * TS 24.301 9.9.4.4. Integer.MAX_VALUE if this value is unused.
+ */
+ public void notifyRegistrationFailed(int slotIndex, int subId,
+ @NonNull CellIdentity cellIdentity, @NonNull String chosenPlmn,
+ @NetworkRegistrationInfo.Domain int domain, int causeCode, int additionalCauseCode) {
+ try {
+ sRegistry.notifyRegistrationFailed(slotIndex, subId, cellIdentity,
+ chosenPlmn, domain, causeCode, additionalCauseCode);
+ } catch (RemoteException ex) {
+ }
+ }
}
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
index 66281fc..2223d14 100644
--- a/core/java/android/util/NtpTrustedTime.java
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -183,12 +183,11 @@
*
* @throws IllegalStateException if there is no cached value
*/
- @UnsupportedAppUsage
public TimestampedValue<Long> getCachedNtpTimeSignal() {
if (!mHasCache) {
throw new IllegalStateException("Missing authoritative time source");
}
- if (LOGD) Log.d(TAG, "currentTimeMillis() cache hit");
+ if (LOGD) Log.d(TAG, "getCachedNtpTimeSignal() cache hit");
return new TimestampedValue<>(mCachedNtpElapsedRealtime, mCachedNtpTime);
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 0340cb3..a0cf534 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -1462,7 +1462,11 @@
return false;
}
- void onTouchEvent(MotionEvent event) {
+ /**
+ * Handles touch events on an editable text view, implementing cursor movement, selection, etc.
+ */
+ @VisibleForTesting
+ public void onTouchEvent(MotionEvent event) {
final boolean filterOutEvent = shouldFilterOutTouchEvent(event);
mLastButtonState = event.getButtonState();
if (filterOutEvent) {
@@ -2424,7 +2428,9 @@
return mSelectionControllerEnabled;
}
- private InsertionPointCursorController getInsertionController() {
+ /** Returns the controller for the insertion cursor. */
+ @VisibleForTesting
+ public @Nullable InsertionPointCursorController getInsertionController() {
if (!mInsertionControllerEnabled) {
return null;
}
@@ -2439,8 +2445,9 @@
return mInsertionPointCursorController;
}
- @Nullable
- SelectionModifierCursorController getSelectionController() {
+ /** Returns the controller for selection. */
+ @VisibleForTesting
+ public @Nullable SelectionModifierCursorController getSelectionController() {
if (!mSelectionControllerEnabled) {
return null;
}
@@ -5723,11 +5730,16 @@
}
}
- class InsertionPointCursorController implements CursorController {
+ /** Controller for the insertion cursor. */
+ @VisibleForTesting
+ public class InsertionPointCursorController implements CursorController {
private InsertionHandleView mHandle;
private boolean mIsDraggingCursor;
public void onTouchEvent(MotionEvent event) {
+ if (getSelectionController().isCursorBeingModified()) {
+ return;
+ }
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mIsDraggingCursor = false;
@@ -5738,7 +5750,8 @@
} else if (FLAG_ENABLE_CURSOR_DRAG
&& mTextView.getLayout() != null
&& mTextView.isFocused()
- && mTouchState.isMovedEnoughForDrag()) {
+ && mTouchState.isMovedEnoughForDrag()
+ && !mTouchState.isDragCloseToVertical()) {
startCursorDrag(event);
}
break;
@@ -5899,7 +5912,9 @@
}
}
- class SelectionModifierCursorController implements CursorController {
+ /** Controller for selection. */
+ @VisibleForTesting
+ public class SelectionModifierCursorController implements CursorController {
// The cursor controller handles, lazily created when shown.
private SelectionHandleView mStartHandle;
private SelectionHandleView mEndHandle;
diff --git a/core/java/android/widget/EditorTouchState.java b/core/java/android/widget/EditorTouchState.java
index 3798d00..d53099d 100644
--- a/core/java/android/widget/EditorTouchState.java
+++ b/core/java/android/widget/EditorTouchState.java
@@ -56,6 +56,7 @@
private boolean mMultiTapInSameArea;
private boolean mMovedEnoughForDrag;
+ private boolean mIsDragCloseToVertical;
public float getLastDownX() {
return mLastDownX;
@@ -94,6 +95,10 @@
return mMovedEnoughForDrag;
}
+ public boolean isDragCloseToVertical() {
+ return mIsDragCloseToVertical;
+ }
+
/**
* Updates the state based on the new event.
*/
@@ -129,6 +134,7 @@
mLastDownX = event.getX();
mLastDownY = event.getY();
mMovedEnoughForDrag = false;
+ mIsDragCloseToVertical = false;
} else if (action == MotionEvent.ACTION_UP) {
if (TextView.DEBUG_CURSOR) {
logCursor("EditorTouchState", "ACTION_UP");
@@ -137,9 +143,24 @@
mLastUpY = event.getY();
mLastUpMillis = event.getEventTime();
mMovedEnoughForDrag = false;
+ mIsDragCloseToVertical = false;
} else if (action == MotionEvent.ACTION_MOVE) {
- mMovedEnoughForDrag = !isDistanceWithin(mLastDownX, mLastDownY,
- event.getX(), event.getY(), config.getScaledTouchSlop());
+ if (!mMovedEnoughForDrag) {
+ float deltaX = event.getX() - mLastDownX;
+ float deltaY = event.getY() - mLastDownY;
+ float deltaXSquared = deltaX * deltaX;
+ float distanceSquared = (deltaXSquared) + (deltaY * deltaY);
+ int touchSlop = config.getScaledTouchSlop();
+ mMovedEnoughForDrag = distanceSquared > touchSlop * touchSlop;
+ if (mMovedEnoughForDrag) {
+ // If the direction of the swipe motion is within 30 degrees of vertical, it is
+ // considered a vertical drag. We don't actually have to compute the angle to
+ // implement the check though. When the angle is exactly 30 degrees from
+ // vertical, 2*deltaX = distance. When the angle is less than 30 degrees from
+ // vertical, 2*deltaX < distance.
+ mIsDragCloseToVertical = (4 * deltaXSquared) <= distanceSquared;
+ }
+ }
}
}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 1cc6d78..b6b548c 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -2434,11 +2434,6 @@
FooterViewHolder(View itemView) {
super(itemView);
}
-
- public void setHeight(int height) {
- itemView.setLayoutParams(
- new RecyclerView.LayoutParams(LayoutParams.MATCH_PARENT, height));
- }
}
/**
@@ -2471,7 +2466,7 @@
private boolean mLayoutRequested = false;
- private FooterViewHolder mFooterViewHolder;
+ private int mFooterHeight = 0;
private static final int VIEW_TYPE_DIRECT_SHARE = 0;
private static final int VIEW_TYPE_NORMAL = 1;
@@ -2490,9 +2485,6 @@
mChooserListAdapter = wrappedAdapter;
mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
- mFooterViewHolder = new FooterViewHolder(
- new Space(ChooserActivity.this.getApplicationContext()));
-
mShowAzLabelIfPoss = getNumSheetExpansions() < NUM_EXPANSIONS_TO_HIDE_AZ_LABEL;
wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
@@ -2511,7 +2503,7 @@
}
public void setFooterHeight(int height) {
- mFooterViewHolder.setHeight(height);
+ mFooterHeight = height;
}
/**
@@ -2614,7 +2606,10 @@
case VIEW_TYPE_CALLER_AND_RANK:
return createItemGroupViewHolder(viewType, parent);
case VIEW_TYPE_FOOTER:
- return mFooterViewHolder;
+ Space sp = new Space(parent.getContext());
+ sp.setLayoutParams(new RecyclerView.LayoutParams(
+ LayoutParams.MATCH_PARENT, mFooterHeight));
+ return new FooterViewHolder(sp);
default:
// Since we catch all possible viewTypes above, no chance this is being called.
return null;
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 4e764fc..dabaf5a 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -58,10 +58,12 @@
List<AppOpsManager.PackageOps> getPackagesForOps(in int[] ops);
@UnsupportedAppUsage
List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, in int[] ops);
- void getHistoricalOps(int uid, String packageName, in List<String> ops, long beginTimeMillis,
- long endTimeMillis, int flags, in RemoteCallback callback);
- void getHistoricalOpsFromDiskRaw(int uid, String packageName, in List<String> ops,
- long beginTimeMillis, long endTimeMillis, int flags, in RemoteCallback callback);
+ void getHistoricalOps(int uid, String packageName, String featureId, in List<String> ops,
+ int filter, long beginTimeMillis, long endTimeMillis, int flags,
+ in RemoteCallback callback);
+ void getHistoricalOpsFromDiskRaw(int uid, String packageName, String featureId,
+ in List<String> ops, int filter, long beginTimeMillis, long endTimeMillis, int flags,
+ in RemoteCallback callback);
void offsetHistory(long duration);
void setHistoryParameters(int mode, long baseSnapshotInterval, int compressionStep);
void addHistoricalOps(in AppOpsManager.HistoricalOps ops);
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index a86d702..179828c 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -35,7 +35,6 @@
import com.android.server.NetworkManagementSocketTagger;
import dalvik.system.RuntimeHooks;
-import dalvik.system.ThreadPrioritySetter;
import dalvik.system.VMRuntime;
import libcore.content.type.MimeMap;
@@ -205,7 +204,6 @@
*/
public static void preForkInit() {
if (DEBUG) Slog.d(TAG, "Entered preForkInit.");
- RuntimeHooks.setThreadPrioritySetter(new RuntimeThreadPrioritySetter());
RuntimeInit.enableDdms();
// TODO(b/142019040#comment13): Decide whether to load the default instance eagerly, i.e.
// MimeMap.setDefault(DefaultMimeMapFactory.create());
@@ -218,35 +216,6 @@
MimeMap.setDefaultSupplier(DefaultMimeMapFactory::create);
}
- private static class RuntimeThreadPrioritySetter implements ThreadPrioritySetter {
- // Should remain consistent with kNiceValues[] in system/libartpalette/palette_android.cc
- private static final int[] NICE_VALUES = {
- Process.THREAD_PRIORITY_LOWEST, // 1 (MIN_PRIORITY)
- Process.THREAD_PRIORITY_BACKGROUND + 6,
- Process.THREAD_PRIORITY_BACKGROUND + 3,
- Process.THREAD_PRIORITY_BACKGROUND,
- Process.THREAD_PRIORITY_DEFAULT, // 5 (NORM_PRIORITY)
- Process.THREAD_PRIORITY_DEFAULT - 2,
- Process.THREAD_PRIORITY_DEFAULT - 4,
- Process.THREAD_PRIORITY_URGENT_DISPLAY + 3,
- Process.THREAD_PRIORITY_URGENT_DISPLAY + 2,
- Process.THREAD_PRIORITY_URGENT_DISPLAY // 10 (MAX_PRIORITY)
- };
-
- @Override
- public void setPriority(int priority) {
- // Check NICE_VALUES[] length first.
- if (NICE_VALUES.length != (1 + Thread.MAX_PRIORITY - Thread.MIN_PRIORITY)) {
- throw new AssertionError("Unexpected NICE_VALUES.length=" + NICE_VALUES.length);
- }
- // Priority should be in the range of MIN_PRIORITY (1) to MAX_PRIORITY (10).
- if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
- throw new IllegalArgumentException("Priority out of range: " + priority);
- }
- Process.setThreadPriority(NICE_VALUES[priority - Thread.MIN_PRIORITY]);
- }
- }
-
@UnsupportedAppUsage
protected static final void commonInit() {
if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 6c7e3dc..6fd271c 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -62,4 +62,6 @@
void onOutgoingEmergencySms(in EmergencyNumber sentEmergencyNumber);
void onCallDisconnectCauseChanged(in int disconnectCause, in int preciseDisconnectCause);
void onImsCallDisconnectCauseChanged(in ImsReasonInfo imsReasonInfo);
+ void onRegistrationFailed(in CellIdentity cellIdentity,
+ String chosenPlmn, int domain, int causeCode, int additionalCauseCode);
}
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 4e40503..8e97ae1 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -63,7 +63,7 @@
void notifyDataActivity(int state);
void notifyDataActivityForSubscriber(in int subId, int state);
void notifyDataConnectionForSubscriber(
- int phoneId, int subId, String apnType, in PreciseDataConnectionState preciseState);
+ int phoneId, int subId, int apnType, in PreciseDataConnectionState preciseState);
@UnsupportedAppUsage
void notifyDataConnectionFailed(String apnType);
// Uses CellIdentity which is Parcelable here; will convert to CellLocation in client.
@@ -75,7 +75,7 @@
int foregroundCallState, int backgroundCallState);
void notifyDisconnectCause(int phoneId, int subId, int disconnectCause,
int preciseDisconnectCause);
- void notifyPreciseDataConnectionFailed(int phoneId, int subId, String apnType, String apn,
+ void notifyPreciseDataConnectionFailed(int phoneId, int subId, int apnType, String apn,
int failCause);
void notifyCellInfoForSubscriber(in int subId, in List<CellInfo> cellInfo);
void notifySrvccStateChanged(in int subId, in int lteState);
@@ -97,4 +97,6 @@
void notifyCallQualityChanged(in CallQuality callQuality, int phoneId, int subId,
int callNetworkType);
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/java/com/android/internal/util/GrowingArrayUtils.java b/core/java/com/android/internal/util/GrowingArrayUtils.java
index 9f56366..597fe6b 100644
--- a/core/java/com/android/internal/util/GrowingArrayUtils.java
+++ b/core/java/com/android/internal/util/GrowingArrayUtils.java
@@ -16,7 +16,7 @@
package com.android.internal.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* A helper class that aims to provide comparable growth performance to ArrayList, but on primitive
diff --git a/core/java/com/android/internal/util/HexDump.java b/core/java/com/android/internal/util/HexDump.java
index 6ffc928..ad88dd6 100644
--- a/core/java/com/android/internal/util/HexDump.java
+++ b/core/java/com/android/internal/util/HexDump.java
@@ -17,7 +17,7 @@
package com.android.internal.util;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
public class HexDump
{
diff --git a/core/java/com/android/internal/util/IState.java b/core/java/com/android/internal/util/IState.java
index eb66e2c..07837bf 100644
--- a/core/java/com/android/internal/util/IState.java
+++ b/core/java/com/android/internal/util/IState.java
@@ -16,7 +16,7 @@
package com.android.internal.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Message;
/**
diff --git a/core/java/com/android/internal/util/IndentingPrintWriter.java b/core/java/com/android/internal/util/IndentingPrintWriter.java
index 03a555e..34c6a05 100644
--- a/core/java/com/android/internal/util/IndentingPrintWriter.java
+++ b/core/java/com/android/internal/util/IndentingPrintWriter.java
@@ -16,7 +16,8 @@
package com.android.internal.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import java.io.PrintWriter;
import java.io.Writer;
import java.util.Arrays;
diff --git a/core/java/com/android/internal/util/JournaledFile.java b/core/java/com/android/internal/util/JournaledFile.java
index 065cc5b2..a9d8f72 100644
--- a/core/java/com/android/internal/util/JournaledFile.java
+++ b/core/java/com/android/internal/util/JournaledFile.java
@@ -16,7 +16,7 @@
package com.android.internal.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import java.io.File;
diff --git a/core/java/com/android/internal/util/MemInfoReader.java b/core/java/com/android/internal/util/MemInfoReader.java
index 580c2fa..5de77d9 100644
--- a/core/java/com/android/internal/util/MemInfoReader.java
+++ b/core/java/com/android/internal/util/MemInfoReader.java
@@ -16,7 +16,7 @@
package com.android.internal.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Debug;
import android.os.StrictMode;
diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java
index 3fff5c2..5bc96d8 100644
--- a/core/java/com/android/internal/util/Preconditions.java
+++ b/core/java/com/android/internal/util/Preconditions.java
@@ -18,7 +18,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.text.TextUtils;
import java.util.Collection;
diff --git a/core/java/com/android/internal/util/State.java b/core/java/com/android/internal/util/State.java
index 3c61e03..636378e 100644
--- a/core/java/com/android/internal/util/State.java
+++ b/core/java/com/android/internal/util/State.java
@@ -16,7 +16,7 @@
package com.android.internal.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Message;
/**
diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java
index 6c217e5..0c24065 100644
--- a/core/java/com/android/internal/util/StateMachine.java
+++ b/core/java/com/android/internal/util/StateMachine.java
@@ -16,7 +16,7 @@
package com.android.internal.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java
index 8799e3d..c1be33a 100644
--- a/core/java/com/android/internal/util/XmlUtils.java
+++ b/core/java/com/android/internal/util/XmlUtils.java
@@ -16,10 +16,10 @@
package com.android.internal.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
import android.net.Uri;
import android.text.TextUtils;
import android.util.ArrayMap;
diff --git a/core/java/com/android/internal/view/ActionBarPolicy.java b/core/java/com/android/internal/view/ActionBarPolicy.java
index d18c35e..d16cb43 100644
--- a/core/java/com/android/internal/view/ActionBarPolicy.java
+++ b/core/java/com/android/internal/view/ActionBarPolicy.java
@@ -16,15 +16,15 @@
package com.android.internal.view;
-import com.android.internal.R;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Build;
+import com.android.internal.R;
+
/**
* Allows components to query for various configuration policy decisions
* about how the action bar should lay out and behave on the current device.
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index 2ac2975..5dd3389b 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -16,6 +16,7 @@
package com.android.internal.view;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.input.InputManager;
@@ -34,8 +35,6 @@
import com.android.internal.os.IResultReceiver;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import java.io.IOException;
public class BaseIWindow extends IWindow.Stub {
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index ececba1..6278d4a3 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
diff --git a/core/java/com/android/internal/view/InputBindResult.java b/core/java/com/android/internal/view/InputBindResult.java
index 1b133d2..a5964b5 100644
--- a/core/java/com/android/internal/view/InputBindResult.java
+++ b/core/java/com/android/internal/view/InputBindResult.java
@@ -20,7 +20,7 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index 0c057ea..a41048c 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -19,7 +19,7 @@
import android.annotation.AnyThread;
import android.annotation.BinderThread;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.inputmethodservice.AbstractInputMethodService;
import android.os.Bundle;
import android.os.Handler;
diff --git a/core/java/com/android/internal/view/WindowManagerPolicyThread.java b/core/java/com/android/internal/view/WindowManagerPolicyThread.java
index b009a2d..6d691fc 100644
--- a/core/java/com/android/internal/view/WindowManagerPolicyThread.java
+++ b/core/java/com/android/internal/view/WindowManagerPolicyThread.java
@@ -16,7 +16,7 @@
package com.android.internal.view;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Looper;
/**
diff --git a/core/java/com/android/internal/view/menu/ActionMenu.java b/core/java/com/android/internal/view/menu/ActionMenu.java
index 977c1f6..6482629 100644
--- a/core/java/com/android/internal/view/menu/ActionMenu.java
+++ b/core/java/com/android/internal/view/menu/ActionMenu.java
@@ -16,10 +16,7 @@
package com.android.internal.view.menu;
-import java.util.ArrayList;
-import java.util.List;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -30,6 +27,9 @@
import android.view.MenuItem;
import android.view.SubMenu;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* @hide
*/
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItem.java b/core/java/com/android/internal/view/menu/ActionMenuItem.java
index ed253d5..bd8bcb9 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItem.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItem.java
@@ -17,7 +17,7 @@
package com.android.internal.view.menu;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.content.res.ColorStateList;
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
index eb94db3..7622b93 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -16,7 +16,7 @@
package com.android.internal.view.menu;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
diff --git a/core/java/com/android/internal/view/menu/ContextMenuBuilder.java b/core/java/com/android/internal/view/menu/ContextMenuBuilder.java
index 3d3aceb..a9f5e47 100644
--- a/core/java/com/android/internal/view/menu/ContextMenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/ContextMenuBuilder.java
@@ -16,7 +16,7 @@
package com.android.internal.view.menu;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.IBinder;
diff --git a/core/java/com/android/internal/view/menu/IconMenuItemView.java b/core/java/com/android/internal/view/menu/IconMenuItemView.java
index 3d888d3..539c71e 100644
--- a/core/java/com/android/internal/view/menu/IconMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/IconMenuItemView.java
@@ -16,13 +16,12 @@
package com.android.internal.view.menu;
-import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.text.Layout;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;
@@ -30,7 +29,8 @@
import android.view.View;
import android.view.ViewDebug;
import android.widget.TextView;
-import android.text.Layout;
+
+import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
/**
* The item view for each item in the {@link IconMenuView}.
diff --git a/core/java/com/android/internal/view/menu/IconMenuView.java b/core/java/com/android/internal/view/menu/IconMenuView.java
index 6f26434..9e240db 100644
--- a/core/java/com/android/internal/view/menu/IconMenuView.java
+++ b/core/java/com/android/internal/view/menu/IconMenuView.java
@@ -16,9 +16,7 @@
package com.android.internal.view.menu;
-import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -29,10 +27,12 @@
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
-import android.view.LayoutInflater;
+
+import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
import java.util.ArrayList;
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
index 0e07ca7..b31ae38b 100644
--- a/core/java/com/android/internal/view/menu/MenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
diff --git a/core/java/com/android/internal/view/menu/MenuDialogHelper.java b/core/java/com/android/internal/view/menu/MenuDialogHelper.java
index 88d0a03..d02b8f6 100644
--- a/core/java/com/android/internal/view/menu/MenuDialogHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuDialogHelper.java
@@ -16,9 +16,9 @@
package com.android.internal.view.menu;
-import android.annotation.UnsupportedAppUsage;
import android.app.AlertDialog;
import android.app.Dialog;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.DialogInterface;
import android.os.IBinder;
import android.view.KeyEvent;
diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java
index 994a9c1..218f518 100644
--- a/core/java/com/android/internal/view/menu/MenuItemImpl.java
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -16,10 +16,8 @@
package com.android.internal.view.menu;
-import com.android.internal.view.menu.MenuView.ItemView;
-
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
@@ -39,6 +37,8 @@
import android.view.ViewDebug;
import android.widget.LinearLayout;
+import com.android.internal.view.menu.MenuView.ItemView;
+
/**
* @hide
*/
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index d00108e..bac6025 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -20,7 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StyleRes;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
diff --git a/core/java/com/android/internal/view/menu/MenuPresenter.java b/core/java/com/android/internal/view/menu/MenuPresenter.java
index c5df8ad..35b8fef 100644
--- a/core/java/com/android/internal/view/menu/MenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/MenuPresenter.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Parcelable;
import android.view.ViewGroup;
diff --git a/core/java/com/android/internal/view/menu/MenuView.java b/core/java/com/android/internal/view/menu/MenuView.java
index 67a5530..a31c820 100644
--- a/core/java/com/android/internal/view/menu/MenuView.java
+++ b/core/java/com/android/internal/view/menu/MenuView.java
@@ -16,10 +16,7 @@
package com.android.internal.view.menu;
-import com.android.internal.view.menu.MenuBuilder;
-import com.android.internal.view.menu.MenuItemImpl;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.drawable.Drawable;
/**
diff --git a/core/java/com/android/internal/view/menu/SubMenuBuilder.java b/core/java/com/android/internal/view/menu/SubMenuBuilder.java
index cf6d974..6eb215e 100644
--- a/core/java/com/android/internal/view/menu/SubMenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/SubMenuBuilder.java
@@ -16,7 +16,7 @@
package com.android.internal.view.menu;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.Menu;
diff --git a/core/java/com/android/internal/widget/AbsActionBarView.java b/core/java/com/android/internal/widget/AbsActionBarView.java
index 9ccee7f..0f0c1a3 100644
--- a/core/java/com/android/internal/widget/AbsActionBarView.java
+++ b/core/java/com/android/internal/widget/AbsActionBarView.java
@@ -15,26 +15,25 @@
*/
package com.android.internal.widget;
-import com.android.internal.R;
-
-import android.util.TypedValue;
-import android.view.ContextThemeWrapper;
-import android.view.MotionEvent;
-import android.widget.ActionMenuPresenter;
-import android.widget.ActionMenuView;
-
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
+import android.widget.ActionMenuPresenter;
+import android.widget.ActionMenuView;
+
+import com.android.internal.R;
public abstract class AbsActionBarView extends ViewGroup {
private static final TimeInterpolator sAlphaInterpolator = new DecelerateInterpolator();
diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java
index 78ed53f..051526e 100644
--- a/core/java/com/android/internal/widget/ActionBarContextView.java
+++ b/core/java/com/android/internal/widget/ActionBarContextView.java
@@ -15,13 +15,7 @@
*/
package com.android.internal.widget;
-import com.android.internal.R;
-
-import android.widget.ActionMenuPresenter;
-import android.widget.ActionMenuView;
-import com.android.internal.view.menu.MenuBuilder;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
@@ -32,9 +26,14 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
+import android.widget.ActionMenuPresenter;
+import android.widget.ActionMenuView;
import android.widget.LinearLayout;
import android.widget.TextView;
+import com.android.internal.R;
+import com.android.internal.view.menu.MenuBuilder;
+
/**
* @hide
*/
diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
index e9e3cda..aca0b71 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -18,7 +18,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
@@ -41,6 +41,7 @@
import android.view.WindowInsets;
import android.widget.OverScroller;
import android.widget.Toolbar;
+
import com.android.internal.view.menu.MenuPresenter;
/**
diff --git a/core/java/com/android/internal/widget/AlertDialogLayout.java b/core/java/com/android/internal/widget/AlertDialogLayout.java
index 7a01749..d879b6d 100644
--- a/core/java/com/android/internal/widget/AlertDialogLayout.java
+++ b/core/java/com/android/internal/widget/AlertDialogLayout.java
@@ -18,8 +18,8 @@
import android.annotation.AttrRes;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.StyleRes;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
diff --git a/core/java/com/android/internal/widget/ButtonBarLayout.java b/core/java/com/android/internal/widget/ButtonBarLayout.java
index 0ca6743..ff13107 100644
--- a/core/java/com/android/internal/widget/ButtonBarLayout.java
+++ b/core/java/com/android/internal/widget/ButtonBarLayout.java
@@ -16,7 +16,7 @@
package com.android.internal.widget;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
diff --git a/core/java/com/android/internal/widget/CachingIconView.java b/core/java/com/android/internal/widget/CachingIconView.java
index 35bff6d..74ad815 100644
--- a/core/java/com/android/internal/widget/CachingIconView.java
+++ b/core/java/com/android/internal/widget/CachingIconView.java
@@ -18,7 +18,7 @@
import android.annotation.DrawableRes;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Bitmap;
diff --git a/core/java/com/android/internal/widget/DialogTitle.java b/core/java/com/android/internal/widget/DialogTitle.java
index 405436c..0bfd684 100644
--- a/core/java/com/android/internal/widget/DialogTitle.java
+++ b/core/java/com/android/internal/widget/DialogTitle.java
@@ -16,7 +16,7 @@
package com.android.internal.widget;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.TypedArray;
import android.text.Layout;
diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java
index 2b648e9..ff3543c8 100644
--- a/core/java/com/android/internal/widget/EditableInputConnection.java
+++ b/core/java/com/android/internal/widget/EditableInputConnection.java
@@ -16,7 +16,7 @@
package com.android.internal.widget;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Bundle;
import android.text.Editable;
import android.text.method.KeyListener;
diff --git a/core/java/com/android/internal/widget/LinearLayoutWithDefaultTouchRecepient.java b/core/java/com/android/internal/widget/LinearLayoutWithDefaultTouchRecepient.java
index cc7911d..9ef9f697 100644
--- a/core/java/com/android/internal/widget/LinearLayoutWithDefaultTouchRecepient.java
+++ b/core/java/com/android/internal/widget/LinearLayoutWithDefaultTouchRecepient.java
@@ -16,12 +16,12 @@
package com.android.internal.widget;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.view.View;
import android.view.MotionEvent;
+import android.view.View;
import android.widget.LinearLayout;
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 4f4c8c3..f37a468 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -25,11 +25,11 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.app.admin.DevicePolicyManager;
import android.app.admin.PasswordMetrics;
import android.app.trust.IStrongAuthTracker;
import android.app.trust.TrustManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -55,10 +55,10 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
-import com.google.android.collect.Lists;
-
import libcore.util.HexEncoding;
+import com.google.android.collect.Lists;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.security.MessageDigest;
@@ -1599,6 +1599,11 @@
public static final int STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN = 0x20;
/**
+ * Strong authentication is required to prepare for unattended upgrade.
+ */
+ public static final int STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE = 0x40;
+
+ /**
* Strong auth flags that do not prevent biometric methods from being accepted as auth.
* If any other flags are set, biometric authentication is disabled.
*/
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index 74a0aa3..4ddc782 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -19,7 +19,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java
index dd05576..90a18ef 100644
--- a/core/java/com/android/internal/widget/LockSettingsInternal.java
+++ b/core/java/com/android/internal/widget/LockSettingsInternal.java
@@ -77,4 +77,34 @@
* @return the user password metrics.
*/
public abstract @Nullable PasswordMetrics getUserPasswordMetrics(int userHandle);
+
+ /**
+ * Prepare for reboot escrow. This triggers the strong auth to be required. After the escrow
+ * is complete as indicated by calling to the listener registered with {@link
+ * #setRebootEscrowListener}, then {@link #armRebootEscrow()} should be called before
+ * rebooting to apply the update.
+ */
+ public abstract void prepareRebootEscrow();
+
+ /**
+ * Registers a listener for when the RebootEscrow HAL has stored its data needed for rebooting
+ * for an OTA.
+ *
+ * @see RebootEscrowListener
+ * @param listener
+ */
+ public abstract void setRebootEscrowListener(RebootEscrowListener listener);
+
+ /**
+ * Requests that any data needed for rebooting is cleared from the RebootEscrow HAL.
+ */
+ public abstract void clearRebootEscrow();
+
+ /**
+ * Should be called immediately before rebooting for an update. This depends on {@link
+ * #prepareRebootEscrow()} having been called and the escrow completing.
+ *
+ * @return true if the arming worked
+ */
+ public abstract boolean armRebootEscrow();
}
diff --git a/core/java/com/android/internal/widget/NumericTextView.java b/core/java/com/android/internal/widget/NumericTextView.java
index d215670..c8f9011 100644
--- a/core/java/com/android/internal/widget/NumericTextView.java
+++ b/core/java/com/android/internal/widget/NumericTextView.java
@@ -16,7 +16,7 @@
package com.android.internal.widget;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index 37046af..dc8d57a 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -16,7 +16,7 @@
package com.android.internal.widget;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
diff --git a/core/java/com/android/internal/widget/PreferenceImageView.java b/core/java/com/android/internal/widget/PreferenceImageView.java
index 02a0b8d..43b6b5a 100644
--- a/core/java/com/android/internal/widget/PreferenceImageView.java
+++ b/core/java/com/android/internal/widget/PreferenceImageView.java
@@ -16,7 +16,7 @@
package com.android.internal.widget;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ImageView;
diff --git a/core/java/com/android/internal/widget/RebootEscrowListener.java b/core/java/com/android/internal/widget/RebootEscrowListener.java
new file mode 100644
index 0000000..1654532
--- /dev/null
+++ b/core/java/com/android/internal/widget/RebootEscrowListener.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+/**
+ * Private API to be notified about reboot escrow events.
+ *
+ * {@hide}
+ */
+public interface RebootEscrowListener {
+ /**
+ * Called when the preparation status has changed. When {@code prepared} is {@code true} the
+ * user has entered their lock screen knowledge factor (LSKF) and the HAL has confirmed that
+ * it is ready to retrieve the secret after a reboot. When {@code prepared} is {@code false}
+ * then those conditions are not true.
+ */
+ void onPreparedForReboot(boolean prepared);
+}
diff --git a/core/java/com/android/internal/widget/RecyclerView.java b/core/java/com/android/internal/widget/RecyclerView.java
index b66a7b4..43a227a 100644
--- a/core/java/com/android/internal/widget/RecyclerView.java
+++ b/core/java/com/android/internal/widget/RecyclerView.java
@@ -20,7 +20,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.Observable;
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index 1bb2ba2..e0c3823 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -187,7 +187,7 @@
public void setCollapsed(boolean collapsed) {
if (!isLaidOut()) {
- mOpenOnLayout = collapsed;
+ mOpenOnLayout = !collapsed;
} else {
smoothScrollTo(collapsed ? mCollapsibleHeight : 0, 0);
}
diff --git a/core/java/com/android/internal/widget/ScrollBarUtils.java b/core/java/com/android/internal/widget/ScrollBarUtils.java
index 982e315..3e9d697 100644
--- a/core/java/com/android/internal/widget/ScrollBarUtils.java
+++ b/core/java/com/android/internal/widget/ScrollBarUtils.java
@@ -16,7 +16,7 @@
package com.android.internal.widget;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
public class ScrollBarUtils {
diff --git a/core/java/com/android/internal/widget/ScrollingTabContainerView.java b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
index 5d48ab9..aa0b0bb 100644
--- a/core/java/com/android/internal/widget/ScrollingTabContainerView.java
+++ b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
@@ -15,13 +15,11 @@
*/
package com.android.internal.widget;
-import com.android.internal.view.ActionBarPolicy;
-
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActionBar;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
@@ -42,6 +40,8 @@
import android.widget.Spinner;
import android.widget.TextView;
+import com.android.internal.view.ActionBarPolicy;
+
/**
* This widget implements the dynamic action bar tab behavior that can change
* across different configurations or circumstances.
diff --git a/core/java/com/android/internal/widget/SlidingTab.java b/core/java/com/android/internal/widget/SlidingTab.java
index 4b5d624..5e6f3a4 100644
--- a/core/java/com/android/internal/widget/SlidingTab.java
+++ b/core/java/com/android/internal/widget/SlidingTab.java
@@ -16,7 +16,7 @@
package com.android.internal.widget;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -34,12 +34,12 @@
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
import android.view.animation.LinearInterpolator;
import android.view.animation.TranslateAnimation;
-import android.view.animation.Animation.AnimationListener;
import android.widget.ImageView;
-import android.widget.TextView;
import android.widget.ImageView.ScaleType;
+import android.widget.TextView;
import com.android.internal.R;
diff --git a/core/java/com/android/internal/widget/TextViewInputDisabler.java b/core/java/com/android/internal/widget/TextViewInputDisabler.java
index 8d8f0fe..57806eb 100644
--- a/core/java/com/android/internal/widget/TextViewInputDisabler.java
+++ b/core/java/com/android/internal/widget/TextViewInputDisabler.java
@@ -16,7 +16,7 @@
package com.android.internal.widget;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.text.InputFilter;
import android.text.Spanned;
import android.widget.TextView;
diff --git a/core/java/com/android/internal/widget/ViewPager.java b/core/java/com/android/internal/widget/ViewPager.java
index 7d36b02..c8a86d1 100644
--- a/core/java/com/android/internal/widget/ViewPager.java
+++ b/core/java/com/android/internal/widget/ViewPager.java
@@ -18,7 +18,7 @@
import android.annotation.DrawableRes;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
diff --git a/core/java/com/android/server/ResettableTimeout.java b/core/java/com/android/server/ResettableTimeout.java
index 64083f7..511af94 100644
--- a/core/java/com/android/server/ResettableTimeout.java
+++ b/core/java/com/android/server/ResettableTimeout.java
@@ -16,10 +16,9 @@
package com.android.server;
-import android.os.SystemClock;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.ConditionVariable;
+import android.os.SystemClock;
/**
* Utility class that you can call on with a timeout, and get called back
diff --git a/core/java/com/android/server/net/BaseNetworkObserver.java b/core/java/com/android/server/net/BaseNetworkObserver.java
index e1a10a5..2a9c0b4 100644
--- a/core/java/com/android/server/net/BaseNetworkObserver.java
+++ b/core/java/com/android/server/net/BaseNetworkObserver.java
@@ -16,7 +16,7 @@
package com.android.server.net;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.INetworkManagementEventObserver;
import android.net.LinkAddress;
import android.net.RouteInfo;
diff --git a/core/java/com/android/server/net/NetlinkTracker.java b/core/java/com/android/server/net/NetlinkTracker.java
index 647fb5b..b57397f 100644
--- a/core/java/com/android/server/net/NetlinkTracker.java
+++ b/core/java/com/android/server/net/NetlinkTracker.java
@@ -16,7 +16,7 @@
package com.android.server.net;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.RouteInfo;
diff --git a/core/java/com/google/android/collect/Lists.java b/core/java/com/google/android/collect/Lists.java
index 8f6594a..585847d 100644
--- a/core/java/com/google/android/collect/Lists.java
+++ b/core/java/com/google/android/collect/Lists.java
@@ -16,7 +16,8 @@
package com.google.android.collect;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import java.util.ArrayList;
import java.util.Collections;
diff --git a/core/java/com/google/android/collect/Maps.java b/core/java/com/google/android/collect/Maps.java
index 6ba3320..cd4c128 100644
--- a/core/java/com/google/android/collect/Maps.java
+++ b/core/java/com/google/android/collect/Maps.java
@@ -16,7 +16,7 @@
package com.google.android.collect;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.ArrayMap;
import java.util.HashMap;
diff --git a/core/java/com/google/android/collect/Sets.java b/core/java/com/google/android/collect/Sets.java
index 09b5e51..c67a88a 100644
--- a/core/java/com/google/android/collect/Sets.java
+++ b/core/java/com/google/android/collect/Sets.java
@@ -16,7 +16,7 @@
package com.google.android.collect;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.ArraySet;
import java.util.Collections;
diff --git a/core/java/com/google/android/util/AbstractMessageParser.java b/core/java/com/google/android/util/AbstractMessageParser.java
index f11e6b2..0da7607 100644
--- a/core/java/com/google/android/util/AbstractMessageParser.java
+++ b/core/java/com/google/android/util/AbstractMessageParser.java
@@ -16,7 +16,7 @@
package com.google.android.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import java.util.ArrayList;
import java.util.HashMap;
diff --git a/core/java/org/apache/http/conn/ssl/AbstractVerifier.java b/core/java/org/apache/http/conn/ssl/AbstractVerifier.java
index 36d6e22..2848ad7 100644
--- a/core/java/org/apache/http/conn/ssl/AbstractVerifier.java
+++ b/core/java/org/apache/http/conn/ssl/AbstractVerifier.java
@@ -31,7 +31,7 @@
package org.apache.http.conn.ssl;
-import java.util.regex.Pattern;
+import android.compat.annotation.UnsupportedAppUsage;
import java.io.IOException;
import java.security.cert.Certificate;
@@ -43,10 +43,10 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
-import java.util.logging.Logger;
import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
-import android.annotation.UnsupportedAppUsage;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
diff --git a/core/java/org/apache/http/conn/ssl/SSLSocketFactory.java b/core/java/org/apache/http/conn/ssl/SSLSocketFactory.java
index b2e8b5e..ffae757 100644
--- a/core/java/org/apache/http/conn/ssl/SSLSocketFactory.java
+++ b/core/java/org/apache/http/conn/ssl/SSLSocketFactory.java
@@ -31,20 +31,14 @@
package org.apache.http.conn.ssl;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+
import org.apache.http.conn.scheme.HostNameResolver;
import org.apache.http.conn.scheme.LayeredSocketFactory;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
-import android.annotation.UnsupportedAppUsage;
-import android.os.Build;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.KeyManager;
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.TrustManagerFactory;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -57,6 +51,14 @@
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
/**
* Layered socket factory for TLS/SSL connections, based on JSSE.
*.
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index d5a3b5e..b83b31c 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -702,6 +702,12 @@
// CATEGORY: SETTINGS
// OS: R
ACTION_DASHBOARD_VISIBLE_TIME = 1729;
+
+ // ACTION: Allow "Access all files" for an app
+ APP_SPECIAL_PERMISSION_MANAGE_EXT_STRG_ALLOW = 1730;
+
+ // ACTION: Deny "Access all files" for an app
+ APP_SPECIAL_PERMISSION_MANAGE_EXT_STRG_DENY = 1731;
}
/**
@@ -2542,4 +2548,9 @@
// OS: R
FUELGAUGE_BATTERY_SHARE = 1821;
+ // OPEN: Settings -> Apps & Notifications -> Special App Access
+ // CATEGORY: SETTINGS
+ // OS: R
+ MANAGE_EXTERNAL_STORAGE = 1822;
+
}
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index a98d7db..d5384a1 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -115,7 +115,7 @@
option (android.msg_privacy).dest = DEST_EXPLICIT;
optional SettingProto backup_agent_timeout_parameters = 1;
- optional SettingProto backup_multi_user_enabled = 2;
+ reserved 2; // Used to be backup_multi_user_enabled which was never used
}
optional Backup backup = 146;
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index ee5144c..9054d54 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -153,4 +153,5 @@
CROSS_PROFILE_APPS_START_ACTIVITY_AS_USER = 126;
SET_AUTO_TIME = 127;
SET_AUTO_TIME_ZONE = 128;
+ SET_PACKAGES_PROTECTED = 129;
}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e839709..6691d4c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4284,4 +4284,12 @@
<!-- Whether or not to use assistant stream volume separately from music volume -->
<bool name="config_useAssistantVolume">false</bool>
+
+ <!-- Whether to use a custom Bugreport handling. When true, ACTION_CUSTOM_BUGREPORT_REQUESTED
+ intent is broadcasted on bugreporting chord (instead of the default full bugreport
+ generation). -->
+ <bool name="config_customBugreport">false</bool>
+
+ <!-- Class name of the custom country detector to be used. -->
+ <string name="config_customCountryDetector" translatable="false">com.android.server.location.ComprehensiveCountryDetector</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 129c862..01bd510 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3605,6 +3605,8 @@
<java-symbol type="bool" name="config_maskMainBuiltInDisplayCutout" />
+ <java-symbol type="string" name="config_customCountryDetector" />
+
<!-- For Foldables -->
<java-symbol type="bool" name="config_lidControlsDisplayFold" />
<java-symbol type="string" name="config_foldedArea" />
diff --git a/core/tests/coretests/src/android/os/ExternalVibrationTest.java b/core/tests/coretests/src/android/os/ExternalVibrationTest.java
new file mode 100644
index 0000000..3b872d5
--- /dev/null
+++ b/core/tests/coretests/src/android/os/ExternalVibrationTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.os;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.Mockito.mock;
+
+import android.media.AudioAttributes;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ExternalVibrationTest {
+ @Test
+ public void testSerialization() {
+ AudioAttributes audio = new AudioAttributes.Builder().build();
+ IExternalVibrationController controller = mock(IExternalVibrationController.class);
+ ExternalVibration original = new ExternalVibration(
+ 123, // uid
+ "pkg",
+ audio,
+ controller);
+ Parcel p = Parcel.obtain();
+ original.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ ExternalVibration restored = ExternalVibration.CREATOR.createFromParcel(p);
+ assertEquals(original, restored);
+ }
+}
+
diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
index b4f2c91..f497db2 100644
--- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
+++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
@@ -16,15 +16,23 @@
package android.widget;
+import static android.widget.espresso.TextViewActions.clickOnTextAtIndex;
import static android.widget.espresso.TextViewActions.dragOnText;
import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex;
+import static android.widget.espresso.TextViewAssertions.hasSelection;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.replaceText;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static org.hamcrest.Matchers.emptyString;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
import android.app.Activity;
import android.app.Instrumentation;
+import android.view.MotionEvent;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -70,107 +78,247 @@
onView(withId(R.id.textview)).perform(replaceText(text));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
- // Drag left to right. The cursor should end up at the position where the finger is lifted.
+ // Swipe left to right to drag the cursor. The cursor should end up at the position where
+ // the finger is lifted.
onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("llo"), text.indexOf("!")));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(11));
- // Drag right to left. The cursor should end up at the position where the finger is lifted.
+ // Swipe right to left to drag the cursor. The cursor should end up at the position where
+ // the finger is lifted.
onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("!"), text.indexOf("llo")));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(2));
}
@Test
public void testCursorDrag_horizontal_whenTextViewContentsLargerThanScreen() throws Throwable {
- String text = "Hello world!"
- + Strings.repeat("\n", 500) + "012345middle"
- + Strings.repeat("\n", 10) + "012345last";
+ String text = "Hello world!\n\n"
+ + Strings.repeat("Bla\n\n", 200) + "Bye";
onView(withId(R.id.textview)).perform(replaceText(text));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
- // Drag left to right. The cursor should end up at the position where the finger is lifted.
+ // Swipe left to right to drag the cursor. The cursor should end up at the position where
+ // the finger is lifted.
onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("llo"), text.indexOf("!")));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(11));
- // Drag right to left. The cursor should end up at the position where the finger is lifted.
+ // Swipe right to left to drag the cursor. The cursor should end up at the position where
+ // the finger is lifted.
onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("!"), text.indexOf("llo")));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(2));
}
@Test
+ public void testCursorDrag_diagonal_whenTextViewContentsFitOnScreen() throws Throwable {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 1; i <= 9; i++) {
+ sb.append("line").append(i).append("\n");
+ }
+ String text = sb.toString();
+ onView(withId(R.id.textview)).perform(replaceText(text));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+
+ // Swipe along a diagonal path. This should drag the cursor.
+ onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line1"), text.indexOf("2")));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("2")));
+
+ // Swipe along a steeper diagonal path. This should still drag the cursor.
+ onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line1"), text.indexOf("3")));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("3")));
+
+ // Swipe right-down along a very steep diagonal path. This should not drag the cursor.
+ // Normally this would trigger a scroll, but since the full view fits on the screen there
+ // is nothing to scroll and the gesture will trigger a selection drag.
+ onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line1"), text.indexOf("7")));
+ onView(withId(R.id.textview)).check(hasSelection(not(emptyString())));
+
+ // Swipe right-up along a very steep diagonal path. This should not drag the cursor.
+ // Normally this would trigger a scroll, but since the full view fits on the screen there
+ // is nothing to scroll and the gesture will trigger a selection drag.
+ int index = text.indexOf("line9");
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(index));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(index));
+ onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line7"), text.indexOf("1")));
+ onView(withId(R.id.textview)).check(hasSelection(not(emptyString())));
+ }
+
+ @Test
public void testCursorDrag_diagonal_whenTextViewContentsLargerThanScreen() throws Throwable {
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= 9; i++) {
sb.append("line").append(i).append("\n");
}
- sb.append(Strings.repeat("0123456789\n\n", 500)).append("Last line");
+ sb.append(Strings.repeat("0123456789\n", 400)).append("Last");
String text = sb.toString();
onView(withId(R.id.textview)).perform(replaceText(text));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
- // Drag along a diagonal path.
+ // Swipe along a diagonal path. This should drag the cursor.
onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line1"), text.indexOf("2")));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("2")));
- // Drag along a steeper diagonal path.
- onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line1"), text.indexOf("9")));
- onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("9")));
+ // Swipe along a steeper diagonal path. This should still drag the cursor.
+ onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line1"), text.indexOf("3")));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("3")));
- // Drag along an almost vertical path.
- // TODO(b/145833335): Consider whether this should scroll instead of dragging the cursor.
- onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("ne1"), text.indexOf("9")));
- onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("9")));
+ // Swipe right-down along a very steep diagonal path. This should not drag the cursor.
+ // Normally this would trigger a scroll up, but since the view is already at the top there
+ // is nothing to scroll and the gesture will trigger a selection drag.
+ onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line1"), text.indexOf("7")));
+ onView(withId(R.id.textview)).check(hasSelection(not(emptyString())));
- // Drag along a vertical path from line 1 to line 9.
- // TODO(b/145833335): Consider whether this should scroll instead of dragging the cursor.
- onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("e1"), text.indexOf("e9")));
- onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("e9")));
-
- // Drag along a vertical path from line 9 to line 1.
- // TODO(b/145833335): Consider whether this should scroll instead of dragging the cursor.
- onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("e9"), text.indexOf("e1")));
- onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("e1")));
+ // Swipe right-up along a very steep diagonal path. This should not drag the cursor. This
+ // will trigger a downward scroll and the cursor position will not change.
+ int index = text.indexOf("line9");
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(index));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(index));
+ onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line7"), text.indexOf("1")));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(index));
}
@Test
public void testCursorDrag_vertical_whenTextViewContentsFitOnScreen() throws Throwable {
- String text = "012first\n\n" + Strings.repeat("012345\n\n", 10) + "012last";
+ String text = "012345_aaa\n"
+ + "0123456789\n"
+ + "012345_bbb\n"
+ + "0123456789\n"
+ + "012345_ccc\n"
+ + "0123456789\n"
+ + "012345_ddd";
onView(withId(R.id.textview)).perform(replaceText(text));
+
+ // Swipe up vertically. This should not drag the cursor. Since there's also nothing to
+ // scroll, the gesture will trigger a selection drag.
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+ onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("bbb"), text.indexOf("aaa")));
+ onView(withId(R.id.textview)).check(hasSelection(not(emptyString())));
- // Drag down. Since neither the TextView nor its container require scrolling, the cursor
- // drag should execute and the cursor should end up at the position where the finger is
- // lifted.
- onView(withId(R.id.textview)).perform(
- dragOnText(text.indexOf("first"), text.indexOf("last")));
- onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length() - 4));
-
- // Drag up. Since neither the TextView nor its container require scrolling, the cursor
- // drag should execute and the cursor should end up at the position where the finger is
- // lifted.
- onView(withId(R.id.textview)).perform(
- dragOnText(text.indexOf("last"), text.indexOf("first")));
- onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(3));
+ // Swipe down vertically. This should not drag the cursor. Since there's also nothing to
+ // scroll, the gesture will trigger a selection drag.
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+ onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("ccc"), text.indexOf("ddd")));
+ onView(withId(R.id.textview)).check(hasSelection(not(emptyString())));
}
@Test
public void testCursorDrag_vertical_whenTextViewContentsLargerThanScreen() throws Throwable {
- String text = "012345first\n\n"
- + Strings.repeat("0123456789\n\n", 10) + "012345middle"
- + Strings.repeat("0123456789\n\n", 500) + "012345last";
+ String text = "012345_aaa\n"
+ + "0123456789\n"
+ + "012345_bbb\n"
+ + "0123456789\n"
+ + "012345_ccc\n"
+ + "0123456789\n"
+ + "012345_ddd\n"
+ + Strings.repeat("0123456789\n", 400) + "012345_zzz";
onView(withId(R.id.textview)).perform(replaceText(text));
- int initialCursorPosition = 0;
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.indexOf("ddd")));
+ int initialCursorPosition = text.indexOf("ddd");
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(initialCursorPosition));
- // Drag up.
- // TODO(b/145833335): Consider whether this should scroll instead of dragging the cursor.
- onView(withId(R.id.textview)).perform(
- dragOnText(text.indexOf("middle"), text.indexOf("first")));
- onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("first")));
+ // Swipe up vertically. This should trigger a downward scroll.
+ onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("bbb"), text.indexOf("aaa")));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(initialCursorPosition));
- // Drag down.
- // TODO(b/145833335): Consider whether this should scroll instead of dragging the cursor.
- onView(withId(R.id.textview)).perform(
- dragOnText(text.indexOf("first"), text.indexOf("middle")));
- onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("middle")));
+ // Swipe down vertically. This should trigger an upward scroll.
+ onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("ccc"), text.indexOf("ddd")));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(initialCursorPosition));
+ }
+
+ @Test
+ public void testEditor_onTouchEvent_cursorDrag() throws Throwable {
+ String text = "testEditor_onTouchEvent_cursorDrag";
+ onView(withId(R.id.textview)).perform(replaceText(text));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+
+ TextView tv = mActivity.findViewById(R.id.textview);
+ Editor editor = tv.getEditorForTesting();
+
+ // Simulate a tap-and-drag gesture. This should trigger a cursor drag.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+ mActivity.runOnUiThread(() -> editor.onTouchEvent(event1));
+ mInstrumentation.waitForIdleSync();
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+ long event2Time = 1002;
+ MotionEvent event2 = moveEvent(event1Time, event2Time, 21f, 30f);
+ mActivity.runOnUiThread(() -> editor.onTouchEvent(event2));
+ mInstrumentation.waitForIdleSync();
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+ long event3Time = 1003;
+ MotionEvent event3 = moveEvent(event3Time, event3Time, 120f, 30f);
+ mActivity.runOnUiThread(() -> editor.onTouchEvent(event3));
+ mInstrumentation.waitForIdleSync();
+ assertTrue(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+ long event4Time = 1004;
+ MotionEvent event4 = upEvent(event3Time, event4Time, 120f, 30f);
+ mActivity.runOnUiThread(() -> editor.onTouchEvent(event4));
+ mInstrumentation.waitForIdleSync();
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+ }
+
+ @Test
+ public void testEditor_onTouchEvent_selectionDrag() throws Throwable {
+ String text = "testEditor_onTouchEvent_selectionDrag";
+ onView(withId(R.id.textview)).perform(replaceText(text));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+
+ TextView tv = mActivity.findViewById(R.id.textview);
+ Editor editor = tv.getEditorForTesting();
+
+ // Simulate a double-tap followed by a drag. This should trigger a selection drag.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+ mActivity.runOnUiThread(() -> editor.onTouchEvent(event1));
+ mInstrumentation.waitForIdleSync();
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+ long event2Time = 1002;
+ MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
+ mActivity.runOnUiThread(() -> editor.onTouchEvent(event2));
+ mInstrumentation.waitForIdleSync();
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+ long event3Time = 1003;
+ MotionEvent event3 = downEvent(event3Time, event3Time, 20f, 30f);
+ mActivity.runOnUiThread(() -> editor.onTouchEvent(event3));
+ mInstrumentation.waitForIdleSync();
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertTrue(editor.getSelectionController().isCursorBeingModified());
+
+ long event4Time = 1004;
+ MotionEvent event4 = moveEvent(event3Time, event4Time, 120f, 30f);
+ mActivity.runOnUiThread(() -> editor.onTouchEvent(event4));
+ mInstrumentation.waitForIdleSync();
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertTrue(editor.getSelectionController().isCursorBeingModified());
+
+ long event5Time = 1005;
+ MotionEvent event5 = upEvent(event3Time, event5Time, 120f, 30f);
+ mActivity.runOnUiThread(() -> editor.onTouchEvent(event5));
+ mInstrumentation.waitForIdleSync();
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+ }
+
+ private static MotionEvent downEvent(long downTime, long eventTime, float x, float y) {
+ return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0);
+ }
+
+ private static MotionEvent upEvent(long downTime, long eventTime, float x, float y) {
+ return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
+ }
+
+ private static MotionEvent moveEvent(long downTime, long eventTime, float x, float y) {
+ return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
}
}
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index 6e7f286..bee8d5e 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -21,7 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Canvas.VertexMode;
import android.graphics.text.MeasuredText;
import android.text.GraphicsOperations;
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index d900a42..ac094ba 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -21,8 +21,8 @@
import android.annotation.ColorLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.WorkerThread;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.ResourcesImpl;
import android.hardware.HardwareBuffer;
import android.os.Build;
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index 5623a8a..bad487b 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -20,7 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Trace;
diff --git a/graphics/java/android/graphics/BitmapRegionDecoder.java b/graphics/java/android/graphics/BitmapRegionDecoder.java
index 629d8c1..34eba97 100644
--- a/graphics/java/android/graphics/BitmapRegionDecoder.java
+++ b/graphics/java/android/graphics/BitmapRegionDecoder.java
@@ -15,7 +15,7 @@
package android.graphics;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetManager;
import android.os.Build;
diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java
index 198d1e7..edf53c4 100644
--- a/graphics/java/android/graphics/BitmapShader.java
+++ b/graphics/java/android/graphics/BitmapShader.java
@@ -17,7 +17,7 @@
package android.graphics;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* Shader used to draw a bitmap as a texture. The bitmap can be repeated or
diff --git a/graphics/java/android/graphics/Camera.java b/graphics/java/android/graphics/Camera.java
index cbd4ead..80a3740 100644
--- a/graphics/java/android/graphics/Camera.java
+++ b/graphics/java/android/graphics/Camera.java
@@ -16,7 +16,7 @@
package android.graphics;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* A camera instance can be used to compute 3D transformations and
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index a815f20..9a0ca3e 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -22,7 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.text.MeasuredText;
import android.os.Build;
diff --git a/graphics/java/android/graphics/CanvasProperty.java b/graphics/java/android/graphics/CanvasProperty.java
index 1275e08..4263772c 100644
--- a/graphics/java/android/graphics/CanvasProperty.java
+++ b/graphics/java/android/graphics/CanvasProperty.java
@@ -16,7 +16,8 @@
package android.graphics;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import com.android.internal.util.VirtualRefBasePtr;
/**
diff --git a/graphics/java/android/graphics/ColorMatrixColorFilter.java b/graphics/java/android/graphics/ColorMatrixColorFilter.java
index 0f7980c..a8b18a9 100644
--- a/graphics/java/android/graphics/ColorMatrixColorFilter.java
+++ b/graphics/java/android/graphics/ColorMatrixColorFilter.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* A color filter that transforms colors through a 4x5 color matrix. This filter
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index 5ad93f4..ae90995 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -17,7 +17,7 @@
package android.graphics;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetManager;
import android.graphics.fonts.FontVariationAxis;
import android.text.TextUtils;
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 21cc375..c146bbd 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -16,7 +16,7 @@
package android.graphics;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.fonts.FontVariationAxis;
import android.text.FontConfig;
import android.util.Xml;
diff --git a/graphics/java/android/graphics/GraphicBuffer.java b/graphics/java/android/graphics/GraphicBuffer.java
index 3b1fc70..99fa5ee 100644
--- a/graphics/java/android/graphics/GraphicBuffer.java
+++ b/graphics/java/android/graphics/GraphicBuffer.java
@@ -16,7 +16,7 @@
package android.graphics;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index aecef8e..83432c3 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -28,8 +28,8 @@
import android.annotation.Nullable;
import android.annotation.Px;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.WorkerThread;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
@@ -277,6 +277,10 @@
assetFd = mResolver.openAssetFileDescriptor(mUri, "r");
}
} catch (FileNotFoundException e) {
+ // Handled below, along with the case where assetFd was set to null.
+ }
+
+ if (assetFd == null) {
// Some images cannot be opened as AssetFileDescriptors (e.g.
// bmp, ico). Open them as InputStreams.
InputStream is = mResolver.openInputStream(mUri);
@@ -286,9 +290,7 @@
return createFromStream(is, true, preferAnimation, this);
}
- if (assetFd == null) {
- throw new FileNotFoundException(mUri.toString());
- }
+
return createFromAssetFileDescriptor(assetFd, preferAnimation, this);
}
}
diff --git a/graphics/java/android/graphics/LightingColorFilter.java b/graphics/java/android/graphics/LightingColorFilter.java
index 62a890f..221dfa1 100644
--- a/graphics/java/android/graphics/LightingColorFilter.java
+++ b/graphics/java/android/graphics/LightingColorFilter.java
@@ -22,7 +22,7 @@
package android.graphics;
import android.annotation.ColorInt;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* A color filter that can be used to simulate simple lighting effects.
diff --git a/graphics/java/android/graphics/LinearGradient.java b/graphics/java/android/graphics/LinearGradient.java
index 12e63c0..3f3ad96 100644
--- a/graphics/java/android/graphics/LinearGradient.java
+++ b/graphics/java/android/graphics/LinearGradient.java
@@ -20,7 +20,7 @@
import android.annotation.ColorLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
public class LinearGradient extends Shader {
diff --git a/graphics/java/android/graphics/Matrix.java b/graphics/java/android/graphics/Matrix.java
index 22b6401..cf914c2 100644
--- a/graphics/java/android/graphics/Matrix.java
+++ b/graphics/java/android/graphics/Matrix.java
@@ -16,7 +16,7 @@
package android.graphics;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
diff --git a/graphics/java/android/graphics/Movie.java b/graphics/java/android/graphics/Movie.java
index 6f030ff..4b3924f 100644
--- a/graphics/java/android/graphics/Movie.java
+++ b/graphics/java/android/graphics/Movie.java
@@ -16,7 +16,7 @@
package android.graphics;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetManager;
import android.os.Build;
diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java
index c4c1eac..ff32393 100644
--- a/graphics/java/android/graphics/NinePatch.java
+++ b/graphics/java/android/graphics/NinePatch.java
@@ -16,7 +16,7 @@
package android.graphics;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* The NinePatch class permits drawing a bitmap in nine or more sections.
diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java
index 1fc056c..91a60c3 100644
--- a/graphics/java/android/graphics/Outline.java
+++ b/graphics/java/android/graphics/Outline.java
@@ -19,7 +19,7 @@
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.drawable.Drawable;
import java.lang.annotation.Retention;
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 109d863..3b58624 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -24,7 +24,7 @@
import android.annotation.Nullable;
import android.annotation.Px;
import android.annotation.Size;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.fonts.FontVariationAxis;
import android.os.Build;
import android.os.LocaleList;
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index 7282d52..1362fd8 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -20,7 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java
index 8d12cbf..390d3d4 100644
--- a/graphics/java/android/graphics/Picture.java
+++ b/graphics/java/android/graphics/Picture.java
@@ -17,7 +17,7 @@
package android.graphics;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import java.io.InputStream;
import java.io.OutputStream;
diff --git a/graphics/java/android/graphics/PorterDuff.java b/graphics/java/android/graphics/PorterDuff.java
index bc1f66f..1275cb9 100644
--- a/graphics/java/android/graphics/PorterDuff.java
+++ b/graphics/java/android/graphics/PorterDuff.java
@@ -16,7 +16,7 @@
package android.graphics;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* <p>This class contains the list of alpha compositing and blending modes
diff --git a/graphics/java/android/graphics/PorterDuffColorFilter.java b/graphics/java/android/graphics/PorterDuffColorFilter.java
index cc2d3a8..50ecb62 100644
--- a/graphics/java/android/graphics/PorterDuffColorFilter.java
+++ b/graphics/java/android/graphics/PorterDuffColorFilter.java
@@ -18,7 +18,7 @@
import android.annotation.ColorInt;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* A color filter that can be used to tint the source pixels using a single
diff --git a/graphics/java/android/graphics/RadialGradient.java b/graphics/java/android/graphics/RadialGradient.java
index acbe3da..96b7b9a 100644
--- a/graphics/java/android/graphics/RadialGradient.java
+++ b/graphics/java/android/graphics/RadialGradient.java
@@ -20,7 +20,7 @@
import android.annotation.ColorLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
public class RadialGradient extends Shader {
@UnsupportedAppUsage
diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java
index 9e1946c..081b851 100644
--- a/graphics/java/android/graphics/Rect.java
+++ b/graphics/java/android/graphics/Rect.java
@@ -19,7 +19,7 @@
import android.annotation.CheckResult;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
diff --git a/graphics/java/android/graphics/Region.java b/graphics/java/android/graphics/Region.java
index ec7f7a0..d8d9641 100644
--- a/graphics/java/android/graphics/Region.java
+++ b/graphics/java/android/graphics/Region.java
@@ -17,7 +17,7 @@
package android.graphics;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Pools.SynchronizedPool;
diff --git a/graphics/java/android/graphics/Shader.java b/graphics/java/android/graphics/Shader.java
index 3050d1d..5335aa4 100644
--- a/graphics/java/android/graphics/Shader.java
+++ b/graphics/java/android/graphics/Shader.java
@@ -20,7 +20,7 @@
import android.annotation.ColorLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import libcore.util.NativeAllocationRegistry;
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index 99f440d..697daa8 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -17,7 +17,7 @@
package android.graphics;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
diff --git a/graphics/java/android/graphics/SweepGradient.java b/graphics/java/android/graphics/SweepGradient.java
index 667f45a..0852004 100644
--- a/graphics/java/android/graphics/SweepGradient.java
+++ b/graphics/java/android/graphics/SweepGradient.java
@@ -20,7 +20,7 @@
import android.annotation.ColorLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
public class SweepGradient extends Shader {
@UnsupportedAppUsage
diff --git a/graphics/java/android/graphics/TableMaskFilter.java b/graphics/java/android/graphics/TableMaskFilter.java
index d81c491..204f970 100644
--- a/graphics/java/android/graphics/TableMaskFilter.java
+++ b/graphics/java/android/graphics/TableMaskFilter.java
@@ -16,7 +16,7 @@
package android.graphics;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* @hide
diff --git a/graphics/java/android/graphics/TemporaryBuffer.java b/graphics/java/android/graphics/TemporaryBuffer.java
index 0ae2c70..ef3f7f7 100644
--- a/graphics/java/android/graphics/TemporaryBuffer.java
+++ b/graphics/java/android/graphics/TemporaryBuffer.java
@@ -16,7 +16,8 @@
package android.graphics;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import com.android.internal.util.ArrayUtils;
/**
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 6d20ec3..a2dd9a8 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -25,7 +25,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetManager;
import android.graphics.fonts.Font;
import android.graphics.fonts.FontFamily;
diff --git a/graphics/java/android/graphics/Xfermode.java b/graphics/java/android/graphics/Xfermode.java
index 6f4adfd..e79fb76 100644
--- a/graphics/java/android/graphics/Xfermode.java
+++ b/graphics/java/android/graphics/Xfermode.java
@@ -21,7 +21,7 @@
package android.graphics;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* Xfermode is the base class for objects that are called to implement custom
diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
index 82f5870..d894600 100644
--- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
@@ -19,7 +19,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
diff --git a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
index b29fd4d..686f146 100644
--- a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
@@ -18,23 +18,23 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.Resources.Theme;
+import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.TypedValue;
-import android.os.SystemClock;
+
+import com.android.internal.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
-import com.android.internal.R;
-
/**
* @hide
*/
diff --git a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
index 11a46c4..06159d8 100644
--- a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
@@ -20,7 +20,7 @@
import android.animation.TimeInterpolator;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index 66947da..1acf6c5 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -25,9 +25,9 @@
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
import android.app.Application;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.Resources;
diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java
index 57764c2..8c3fa44 100644
--- a/graphics/java/android/graphics/drawable/AnimationDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java
@@ -16,20 +16,20 @@
package android.graphics.drawable;
-import com.android.internal.R;
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.os.SystemClock;
+import android.util.AttributeSet;
-import java.io.IOException;
+import com.android.internal.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.Resources.Theme;
-import android.os.SystemClock;
-import android.util.AttributeSet;
+import java.io.IOException;
/**
* An object used to create frame-by-frame animations, defined by a series of
diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java
index e4aa774..4e768c9 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -17,7 +17,7 @@
package android.graphics.drawable;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.Resources;
diff --git a/graphics/java/android/graphics/drawable/ClipDrawable.java b/graphics/java/android/graphics/drawable/ClipDrawable.java
index 31fdb02..69ed9b4 100644
--- a/graphics/java/android/graphics/drawable/ClipDrawable.java
+++ b/graphics/java/android/graphics/drawable/ClipDrawable.java
@@ -16,21 +16,23 @@
package android.graphics.drawable;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.Gravity;
+
import com.android.internal.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.Resources.Theme;
-import android.graphics.*;
-import android.view.Gravity;
-import android.util.AttributeSet;
-
import java.io.IOException;
/**
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 506d616..4e6b4af 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -156,8 +156,6 @@
@Nullable
final Bundle mExtras;
- private final String mUniqueId;
-
MediaRoute2Info(@NonNull Builder builder) {
mId = builder.mId;
mProviderId = builder.mProviderId;
@@ -172,7 +170,6 @@
mVolumeHandling = builder.mVolumeHandling;
mDeviceType = builder.mDeviceType;
mExtras = builder.mExtras;
- mUniqueId = createUniqueId();
}
MediaRoute2Info(@NonNull Parcel in) {
@@ -189,18 +186,12 @@
mVolumeHandling = in.readInt();
mDeviceType = in.readInt();
mExtras = in.readBundle();
- mUniqueId = createUniqueId();
}
- private String createUniqueId() {
- String uniqueId = null;
- if (mProviderId != null) {
- uniqueId = toUniqueId(mProviderId, mId);
- }
- return uniqueId;
- }
-
- static String toUniqueId(String providerId, String routeId) {
+ /**
+ * @hide
+ */
+ public static String toUniqueId(String providerId, String routeId) {
return providerId + ":" + routeId;
}
@@ -251,25 +242,30 @@
}
/**
- * Gets the id of the route.
- * Use {@link #getUniqueId()} if you need a unique identifier.
+ * Gets the id of the route. The routes which are given by {@link MediaRouter2} will have
+ * unique IDs.
+ * <p>
+ * In order to ensure uniqueness in {@link MediaRouter2} side, the value of this method
+ * can be different from what was set in {@link MediaRoute2ProviderService}.
*
- * @see #getUniqueId()
+ * @see Builder#setId(String)
*/
@NonNull
public String getId() {
- return mId;
+ if (mProviderId != null) {
+ return toUniqueId(mProviderId, mId);
+ } else {
+ return mId;
+ }
}
/**
- * Gets the unique id of the route. A route obtained from
- * {@link com.android.server.media.MediaRouterService} always has a unique id.
- *
- * @return unique id of the route or null if it has no unique id.
+ * Gets the original id set by {@link Builder#setId(String)}.
+ * @hide
*/
- @Nullable
- public String getUniqueId() {
- return mUniqueId;
+ @NonNull
+ public String getOriginalId() {
+ return mId;
}
/**
@@ -499,7 +495,15 @@
}
/**
- * Sets the unique id of the route.
+ * Sets the unique id of the route. The value given here must be unique for each of your
+ * route.
+ * <p>
+ * In order to ensure uniqueness in {@link MediaRouter2} side, the value of
+ * {@link MediaRoute2Info#getId()} can be different from what was set in
+ * {@link MediaRoute2ProviderService}.
+ * </p>
+ *
+ * @see MediaRoute2Info#getId()
*/
@NonNull
public Builder setId(@NonNull String id) {
diff --git a/media/java/android/media/MediaRoute2ProviderInfo.java b/media/java/android/media/MediaRoute2ProviderInfo.java
index 7078d4a..e2f246c 100644
--- a/media/java/android/media/MediaRoute2ProviderInfo.java
+++ b/media/java/android/media/MediaRoute2ProviderInfo.java
@@ -25,6 +25,7 @@
import java.util.Arrays;
import java.util.Collection;
+import java.util.Map;
import java.util.Objects;
/**
@@ -161,14 +162,17 @@
return this;
}
mUniqueId = uniqueId;
- final int count = mRoutes.size();
- for (int i = 0; i < count; i++) {
- MediaRoute2Info route = mRoutes.valueAt(i);
- mRoutes.setValueAt(i, new MediaRoute2Info.Builder(route)
+
+ final ArrayMap<String, MediaRoute2Info> newRoutes = new ArrayMap<>();
+ for (Map.Entry<String, MediaRoute2Info> entry : mRoutes.entrySet()) {
+ MediaRoute2Info routeWithProviderId = new MediaRoute2Info.Builder(entry.getValue())
.setProviderId(mUniqueId)
- .build());
+ .build();
+ newRoutes.put(routeWithProviderId.getId(), routeWithProviderId);
}
+ mRoutes.clear();
+ mRoutes.putAll(newRoutes);
return this;
}
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index f5cfde4b..bddfa69 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -177,9 +177,9 @@
* @hide
*/
public static boolean checkRouteListContainsRouteId(@NonNull List<MediaRoute2Info> routeList,
- @NonNull String uniqueRouteId) {
+ @NonNull String routeId) {
for (MediaRoute2Info info : routeList) {
- if (TextUtils.equals(uniqueRouteId, info.getUniqueId())) {
+ if (TextUtils.equals(routeId, info.getId())) {
return true;
}
}
@@ -499,7 +499,7 @@
List<MediaRoute2Info> addedRoutes = new ArrayList<>();
synchronized (sRouterLock) {
for (MediaRoute2Info route : routes) {
- mRoutes.put(route.getUniqueId(), route);
+ mRoutes.put(route.getId(), route);
if (route.supportsControlCategories(mControlCategories)) {
addedRoutes.add(route);
}
@@ -515,7 +515,7 @@
List<MediaRoute2Info> removedRoutes = new ArrayList<>();
synchronized (sRouterLock) {
for (MediaRoute2Info route : routes) {
- mRoutes.remove(route.getUniqueId());
+ mRoutes.remove(route.getId());
if (route.supportsControlCategories(mControlCategories)) {
removedRoutes.add(route);
}
@@ -531,7 +531,7 @@
List<MediaRoute2Info> changedRoutes = new ArrayList<>();
synchronized (sRouterLock) {
for (MediaRoute2Info route : routes) {
- mRoutes.put(route.getUniqueId(), route);
+ mRoutes.put(route.getId(), route);
if (route.supportsControlCategories(mControlCategories)) {
changedRoutes.add(route);
}
@@ -935,13 +935,13 @@
}
List<MediaRoute2Info> selectedRoutes = getSelectedRoutes();
- if (checkRouteListContainsRouteId(selectedRoutes, route.getUniqueId())) {
+ if (checkRouteListContainsRouteId(selectedRoutes, route.getId())) {
Log.w(TAG, "Ignoring selecting a route that is already selected. route=" + route);
return;
}
List<MediaRoute2Info> selectableRoutes = getSelectableRoutes();
- if (!checkRouteListContainsRouteId(selectableRoutes, route.getUniqueId())) {
+ if (!checkRouteListContainsRouteId(selectableRoutes, route.getId())) {
Log.w(TAG, "Ignoring selecting a non-selectable route=" + route);
return;
}
@@ -982,13 +982,13 @@
}
List<MediaRoute2Info> selectedRoutes = getSelectedRoutes();
- if (!checkRouteListContainsRouteId(selectedRoutes, route.getUniqueId())) {
+ if (!checkRouteListContainsRouteId(selectedRoutes, route.getId())) {
Log.w(TAG, "Ignoring deselecting a route that is not selected. route=" + route);
return;
}
List<MediaRoute2Info> deselectableRoutes = getDeselectableRoutes();
- if (!checkRouteListContainsRouteId(deselectableRoutes, route.getUniqueId())) {
+ if (!checkRouteListContainsRouteId(deselectableRoutes, route.getId())) {
Log.w(TAG, "Ignoring deselecting a non-deselectable route=" + route);
return;
}
@@ -1029,14 +1029,14 @@
}
List<MediaRoute2Info> selectedRoutes = getSelectedRoutes();
- if (checkRouteListContainsRouteId(selectedRoutes, route.getUniqueId())) {
+ if (checkRouteListContainsRouteId(selectedRoutes, route.getId())) {
Log.w(TAG, "Ignoring transferring to a route that is already added. route="
+ route);
return;
}
List<MediaRoute2Info> transferrableRoutes = getTransferrableRoutes();
- if (!checkRouteListContainsRouteId(transferrableRoutes, route.getUniqueId())) {
+ if (!checkRouteListContainsRouteId(transferrableRoutes, route.getId())) {
Log.w(TAG, "Ignoring transferring to a non-transferrable route=" + route);
return;
}
@@ -1084,8 +1084,12 @@
}
}
+ /**
+ * TODO: Change this to package private. (Hidden for debugging purposes)
+ * @hide
+ */
@NonNull
- RouteSessionInfo getRouteSessionInfo() {
+ public RouteSessionInfo getRouteSessionInfo() {
synchronized (mControllerLock) {
return mSessionInfo;
}
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 2e68e42..3cbbea1 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -295,7 +295,7 @@
void addRoutesOnHandler(List<MediaRoute2Info> routes) {
synchronized (mRoutesLock) {
for (MediaRoute2Info route : routes) {
- mRoutes.put(route.getUniqueId(), route);
+ mRoutes.put(route.getId(), route);
}
}
if (routes.size() > 0) {
@@ -306,7 +306,7 @@
void removeRoutesOnHandler(List<MediaRoute2Info> routes) {
synchronized (mRoutesLock) {
for (MediaRoute2Info route : routes) {
- mRoutes.remove(route.getUniqueId());
+ mRoutes.remove(route.getId());
}
}
if (routes.size() > 0) {
@@ -317,7 +317,7 @@
void changeRoutesOnHandler(List<MediaRoute2Info> routes) {
synchronized (mRoutesLock) {
for (MediaRoute2Info route : routes) {
- mRoutes.put(route.getUniqueId(), route);
+ mRoutes.put(route.getId(), route);
}
}
if (routes.size() > 0) {
diff --git a/media/java/android/media/RouteSessionInfo.java b/media/java/android/media/RouteSessionInfo.java
index 2d7bc24..4a9298a 100644
--- a/media/java/android/media/RouteSessionInfo.java
+++ b/media/java/android/media/RouteSessionInfo.java
@@ -327,14 +327,30 @@
}
/**
- * Sets the provider id of the session.
+ * Sets the provider ID of the session.
+ * Also, calling this method will make all type of route IDs be unique by adding
+ * {@code providerId:} as a prefix. So do NOT call this method twice on same instance.
+ *
+ * @hide
*/
@NonNull
public Builder setProviderId(String providerId) {
mProviderId = providerId;
+ convertToUniqueRouteIds(providerId, mSelectedRoutes);
+ convertToUniqueRouteIds(providerId, mSelectableRoutes);
+ convertToUniqueRouteIds(providerId, mDeselectableRoutes);
+ convertToUniqueRouteIds(providerId, mTransferrableRoutes);
return this;
}
+ private void convertToUniqueRouteIds(@NonNull String providerId,
+ @NonNull List<String> routeIds) {
+ for (int i = 0; i < routeIds.size(); i++) {
+ String routeId = routeIds.get(i);
+ routeIds.set(i, MediaRoute2Info.toUniqueId(providerId, routeId));
+ }
+ }
+
/**
* Clears the selected routes.
*/
diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java
index 4a40c62..cad5aa6 100644
--- a/media/java/android/media/audiofx/AudioEffect.java
+++ b/media/java/android/media/audiofx/AudioEffect.java
@@ -16,11 +16,18 @@
package android.media.audiofx;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.ActivityThread;
import android.compat.annotation.UnsupportedAppUsage;
+import android.media.AudioDeviceAddress;
+import android.media.AudioDeviceInfo;
+import android.media.AudioSystem;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
@@ -448,12 +455,46 @@
public AudioEffect(UUID type, UUID uuid, int priority, int audioSession)
throws IllegalArgumentException, UnsupportedOperationException,
RuntimeException {
+ this(type, uuid, priority, audioSession, null);
+ }
+
+ /**
+ * Constructs an AudioEffect attached to a particular audio device.
+ * The device does not have to be attached when the effect is created. The effect will only
+ * be applied when the device is actually selected for playback or capture.
+ * @param uuid unique identifier of a particular effect implementation.
+ * @param device the device the effect must be attached to.
+ *
+ * @throws java.lang.IllegalArgumentException
+ * @throws java.lang.UnsupportedOperationException
+ * @throws java.lang.RuntimeException
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public AudioEffect(@NonNull UUID uuid, @NonNull AudioDeviceAddress device) {
+ this(EFFECT_TYPE_NULL, Objects.requireNonNull(uuid), 0, -2, Objects.requireNonNull(device));
+ }
+
+ private AudioEffect(UUID type, UUID uuid, int priority,
+ int audioSession, @Nullable AudioDeviceAddress device)
+ throws IllegalArgumentException, UnsupportedOperationException,
+ RuntimeException {
int[] id = new int[1];
Descriptor[] desc = new Descriptor[1];
+
+ int deviceType = AudioSystem.DEVICE_NONE;
+ String deviceAddress = "";
+ if (device != null) {
+ deviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType());
+ deviceAddress = device.getAddress();
+ }
+
// native initialization
int initResult = native_setup(new WeakReference<AudioEffect>(this),
- type.toString(), uuid.toString(), priority, audioSession, id,
- desc, ActivityThread.currentOpPackageName());
+ type.toString(), uuid.toString(), priority, audioSession,
+ deviceType, deviceAddress,
+ id, desc, ActivityThread.currentOpPackageName());
if (initResult != SUCCESS && initResult != ALREADY_EXISTS) {
Log.e(TAG, "Error code " + initResult
+ " when initializing AudioEffect.");
@@ -1293,7 +1334,8 @@
private static native final void native_init();
private native final int native_setup(Object audioeffect_this, String type,
- String uuid, int priority, int audioSession, int[] id, Object[] desc,
+ String uuid, int priority, int audioSession,
+ int deviceType, String deviceAddress, int[] id, Object[] desc,
String opPackageName);
private native final void native_finalize();
diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java
index ea00d6e..ab7bbca 100644
--- a/media/java/android/media/tv/TvTrackInfo.java
+++ b/media/java/android/media/tv/TvTrackInfo.java
@@ -61,6 +61,7 @@
private final boolean mEncrypted;
private final int mAudioChannelCount;
private final int mAudioSampleRate;
+ private final boolean mAudioDescription;
private final int mVideoWidth;
private final int mVideoHeight;
private final float mVideoFrameRate;
@@ -70,8 +71,8 @@
private final Bundle mExtra;
private TvTrackInfo(int type, String id, String language, CharSequence description,
- boolean encrypted, int audioChannelCount, int audioSampleRate, int videoWidth,
- int videoHeight, float videoFrameRate, float videoPixelAspectRatio,
+ boolean encrypted, int audioChannelCount, int audioSampleRate, boolean audioDescription,
+ int videoWidth, int videoHeight, float videoFrameRate, float videoPixelAspectRatio,
byte videoActiveFormatDescription, Bundle extra) {
mType = type;
mId = id;
@@ -80,6 +81,7 @@
mEncrypted = encrypted;
mAudioChannelCount = audioChannelCount;
mAudioSampleRate = audioSampleRate;
+ mAudioDescription = audioDescription;
mVideoWidth = videoWidth;
mVideoHeight = videoHeight;
mVideoFrameRate = videoFrameRate;
@@ -96,6 +98,7 @@
mEncrypted = in.readInt() != 0;
mAudioChannelCount = in.readInt();
mAudioSampleRate = in.readInt();
+ mAudioDescription = in.readInt() != 0;
mVideoWidth = in.readInt();
mVideoHeight = in.readInt();
mVideoFrameRate = in.readFloat();
@@ -172,6 +175,23 @@
}
/**
+ * Returns {@code true} if the track is an audio description intended for people with visual
+ * impairment, {@code false} otherwise. Valid only for {@link #TYPE_AUDIO} tracks.
+ *
+ * <p>For example of broadcast, audio description information may be referred to broadcast
+ * standard (e.g. ISO 639 Language Descriptor of ISO/IEC 13818-1, Supplementary Audio Language
+ * Descriptor, AC-3 Descriptor, Enhanced AC-3 Descriptor, AAC Descriptor of ETSI EN 300 468).
+ *
+ * @throws IllegalStateException if not called on an audio track
+ */
+ public boolean isAudioDescription() {
+ if (mType != TYPE_AUDIO) {
+ throw new IllegalStateException("Not an audio track");
+ }
+ return mAudioDescription;
+ }
+
+ /**
* Returns the width of the video, in the unit of pixels. Valid only for {@link #TYPE_VIDEO}
* tracks.
*
@@ -266,6 +286,7 @@
dest.writeInt(mEncrypted ? 1 : 0);
dest.writeInt(mAudioChannelCount);
dest.writeInt(mAudioSampleRate);
+ dest.writeInt(mAudioDescription ? 1 : 0);
dest.writeInt(mVideoWidth);
dest.writeInt(mVideoHeight);
dest.writeFloat(mVideoFrameRate);
@@ -296,7 +317,8 @@
switch (mType) {
case TYPE_AUDIO:
return mAudioChannelCount == obj.mAudioChannelCount
- && mAudioSampleRate == obj.mAudioSampleRate;
+ && mAudioSampleRate == obj.mAudioSampleRate
+ && mAudioDescription == obj.mAudioDescription;
case TYPE_VIDEO:
return mVideoWidth == obj.mVideoWidth
@@ -338,6 +360,7 @@
private boolean mEncrypted;
private int mAudioChannelCount;
private int mAudioSampleRate;
+ private boolean mAudioDescription;
private int mVideoWidth;
private int mVideoHeight;
private float mVideoFrameRate;
@@ -430,6 +453,27 @@
}
/**
+ * Sets the audio description attribute of the audio. Valid only for {@link #TYPE_AUDIO}
+ * tracks.
+ *
+ * <p>For example of broadcast, audio description information may be referred to broadcast
+ * standard (e.g. ISO 639 Language Descriptor of ISO/IEC 13818-1, Supplementary Audio
+ * Language Descriptor, AC-3 Descriptor, Enhanced AC-3 Descriptor, AAC Descriptor of ETSI EN
+ * 300 468).
+ *
+ * @param audioDescription The audio description attribute of the audio.
+ * @throws IllegalStateException if not called on an audio track
+ */
+ @NonNull
+ public Builder setAudioDescription(boolean audioDescription) {
+ if (mType != TYPE_AUDIO) {
+ throw new IllegalStateException("Not an audio track");
+ }
+ mAudioDescription = audioDescription;
+ return this;
+ }
+
+ /**
* Sets the width of the video, in the unit of pixels. Valid only for {@link #TYPE_VIDEO}
* tracks.
*
@@ -531,8 +575,9 @@
*/
public TvTrackInfo build() {
return new TvTrackInfo(mType, mId, mLanguage, mDescription, mEncrypted,
- mAudioChannelCount, mAudioSampleRate, mVideoWidth, mVideoHeight,
- mVideoFrameRate, mVideoPixelAspectRatio, mVideoActiveFormatDescription, mExtra);
+ mAudioChannelCount, mAudioSampleRate, mAudioDescription, mVideoWidth,
+ mVideoHeight, mVideoFrameRate, mVideoPixelAspectRatio,
+ mVideoActiveFormatDescription, mExtra);
}
}
}
diff --git a/media/java/android/media/tv/tuner/DemuxCapabilities.java b/media/java/android/media/tv/tuner/DemuxCapabilities.java
new file mode 100644
index 0000000..bda166e
--- /dev/null
+++ b/media/java/android/media/tv/tuner/DemuxCapabilities.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner;
+
+/**
+ * Capabilities info for Demux.
+ * @hide
+ */
+public class DemuxCapabilities {
+ private final int mNumDemux;
+ private final int mNumRecord;
+ private final int mNumPlayback;
+ private final int mNumTsFilter;
+ private final int mNumSectionFilter;
+ private final int mNumAudioFilter;
+ private final int mNumVideoFilter;
+ private final int mNumPesFilter;
+ private final int mNumPcrFilter;
+ private final int mNumBytesInSectionFilter;
+ private final int mFilterCaps;
+ private final int[] mLinkCaps;
+
+ DemuxCapabilities(int numDemux, int numRecord, int numPlayback, int numTsFilter,
+ int numSectionFilter, int numAudioFilter, int numVideoFilter, int numPesFilter,
+ int numPcrFilter, int numBytesInSectionFilter, int filterCaps, int[] linkCaps) {
+ mNumDemux = numDemux;
+ mNumRecord = numRecord;
+ mNumPlayback = numPlayback;
+ mNumTsFilter = numTsFilter;
+ mNumSectionFilter = numSectionFilter;
+ mNumAudioFilter = numAudioFilter;
+ mNumVideoFilter = numVideoFilter;
+ mNumPesFilter = numPesFilter;
+ mNumPcrFilter = numPcrFilter;
+ mNumBytesInSectionFilter = numBytesInSectionFilter;
+ mFilterCaps = filterCaps;
+ mLinkCaps = linkCaps;
+ }
+
+ /** Gets total number of demuxes. */
+ public int getNumDemux() {
+ return mNumDemux;
+ }
+ /** Gets max number of recordings at a time. */
+ public int getNumRecord() {
+ return mNumRecord;
+ }
+ /** Gets max number of playbacks at a time. */
+ public int getNumPlayback() {
+ return mNumPlayback;
+ }
+ /** Gets number of TS filters. */
+ public int getNumTsFilter() {
+ return mNumTsFilter;
+ }
+ /** Gets number of section filters. */
+ public int getNumSectionFilter() {
+ return mNumSectionFilter;
+ }
+ /** Gets number of audio filters. */
+ public int getNumAudioFilter() {
+ return mNumAudioFilter;
+ }
+ /** Gets number of video filters. */
+ public int getNumVideoFilter() {
+ return mNumVideoFilter;
+ }
+ /** Gets number of PES filters. */
+ public int getNumPesFilter() {
+ return mNumPesFilter;
+ }
+ /** Gets number of PCR filters. */
+ public int getNumPcrFilter() {
+ return mNumPcrFilter;
+ }
+ /** Gets number of bytes in the mask of a section filter. */
+ public int getNumBytesInSectionFilter() {
+ return mNumBytesInSectionFilter;
+ }
+ /**
+ * Gets filter capabilities in bit field.
+ *
+ * The bits of the returned value is corresponding to the types in
+ * {@link TunerConstants.FilterType}.
+ */
+ public int getFilterCapabilities() {
+ return mFilterCaps;
+ }
+
+ /**
+ * Gets link capabilities.
+ *
+ * The returned array contains the same elements as the number of types in
+ * {@link TunerConstants.FilterType}.
+ * The ith element represents the filter's capability as the source for the ith type
+ */
+ public int[] getLinkCapabilities() {
+ return mLinkCaps;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/FilterSettings.java b/media/java/android/media/tv/tuner/FilterConfiguration.java
similarity index 84%
rename from media/java/android/media/tv/tuner/FilterSettings.java
rename to media/java/android/media/tv/tuner/FilterConfiguration.java
index 4ee61a8..b80a82a 100644
--- a/media/java/android/media/tv/tuner/FilterSettings.java
+++ b/media/java/android/media/tv/tuner/FilterConfiguration.java
@@ -22,20 +22,20 @@
import java.util.List;
/**
- * Demux Filter settings.
+ * Demux Filter configuration.
*
* @hide
*/
-public abstract class FilterSettings {
+public abstract class FilterConfiguration {
@Nullable
protected final Settings mSettings;
- protected FilterSettings(Settings settings) {
+ protected FilterConfiguration(Settings settings) {
mSettings = settings;
}
/**
- * Gets filter settings type
+ * Gets filter configuration type
*/
@FilterType
public abstract int getType();
@@ -47,12 +47,12 @@
// TODO: more builders and getters
/**
- * Filter Settings for a TS filter.
+ * Filter configuration for a TS filter.
*/
- public static class TsFilterSettings extends FilterSettings {
+ public static class TsFilterConfiguration extends FilterConfiguration {
private int mTpid;
- private TsFilterSettings(Settings settings, int tpid) {
+ private TsFilterConfiguration(Settings settings, int tpid) {
super(settings);
mTpid = tpid;
}
@@ -70,7 +70,7 @@
}
/**
- * Builder for TsFilterSettings.
+ * Builder for TsFilterConfiguration.
*/
public static class Builder {
private Settings mSettings;
@@ -93,21 +93,21 @@
}
/**
- * Builds a TsFilterSettings instance.
+ * Builds a TsFilterConfiguration instance.
*/
- public TsFilterSettings build() {
- return new TsFilterSettings(mSettings, mTpid);
+ public TsFilterConfiguration build() {
+ return new TsFilterConfiguration(mSettings, mTpid);
}
}
}
/**
- * Filter Settings for a MMTP filter.
+ * Filter configuration for a MMTP filter.
*/
- public static class MmtpFilterSettings extends FilterSettings {
+ public static class MmtpFilterConfiguration extends FilterConfiguration {
private int mMmtpPid;
- public MmtpFilterSettings(Settings settings) {
+ public MmtpFilterConfiguration(Settings settings) {
super(settings);
}
@@ -119,16 +119,16 @@
/**
- * Filter Settings for a IP filter.
+ * Filter configuration for a IP filter.
*/
- public static class IpFilterSettings extends FilterSettings {
+ public static class IpFilterConfiguration extends FilterConfiguration {
private byte[] mSrcIpAddress;
private byte[] mDstIpAddress;
private int mSrcPort;
private int mDstPort;
private boolean mPassthrough;
- public IpFilterSettings(Settings settings) {
+ public IpFilterConfiguration(Settings settings) {
super(settings);
}
@@ -140,14 +140,14 @@
/**
- * Filter Settings for a TLV filter.
+ * Filter configuration for a TLV filter.
*/
- public static class TlvFilterSettings extends FilterSettings {
+ public static class TlvFilterConfiguration extends FilterConfiguration {
private int mPacketType;
private boolean mIsCompressedIpPacket;
private boolean mPassthrough;
- public TlvFilterSettings(Settings settings) {
+ public TlvFilterConfiguration(Settings settings) {
super(settings);
}
@@ -159,13 +159,13 @@
/**
- * Filter Settings for a ALP filter.
+ * Filter configuration for a ALP filter.
*/
- public static class AlpFilterSettings extends FilterSettings {
+ public static class AlpFilterConfiguration extends FilterConfiguration {
private int mPacketType;
private int mLengthType;
- public AlpFilterSettings(Settings settings) {
+ public AlpFilterConfiguration(Settings settings) {
super(settings);
}
diff --git a/media/java/android/media/tv/tuner/FrontendSettings.java b/media/java/android/media/tv/tuner/FrontendSettings.java
index 531e210..e2e9910 100644
--- a/media/java/android/media/tv/tuner/FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/FrontendSettings.java
@@ -19,8 +19,6 @@
import android.annotation.SystemApi;
import android.media.tv.tuner.TunerConstants.FrontendSettingsType;
-import java.util.List;
-
/**
* Frontend settings for tune and scan operations.
* @hide
@@ -29,7 +27,8 @@
public abstract class FrontendSettings {
private final int mFrequency;
- FrontendSettings(int frequency) {
+ /** @hide */
+ public FrontendSettings(int frequency) {
mFrequency = frequency;
}
@@ -48,282 +47,4 @@
return mFrequency;
}
- // TODO: use hal constants for enum fields
- // TODO: javaDoc
- // TODO: add builders and getters for other settings type
-
- /**
- * Frontend settings for analog.
- * @hide
- */
- public static class FrontendAnalogSettings extends FrontendSettings {
- private int mAnalogType;
- private int mSifStandard;
-
- @Override
- public int getType() {
- return TunerConstants.FRONTEND_TYPE_ANALOG;
- }
-
- public int getAnalogType() {
- return mAnalogType;
- }
-
- public int getSifStandard() {
- return mSifStandard;
- }
-
- /**
- * Creates a new builder object.
- */
- public static Builder newBuilder() {
- return new Builder();
- }
-
- private FrontendAnalogSettings(int frequency, int analogType, int sifStandard) {
- super(frequency);
- mAnalogType = analogType;
- mSifStandard = sifStandard;
- }
-
- /**
- * Builder for FrontendAnalogSettings.
- */
- public static class Builder {
- private int mFrequency;
- private int mAnalogType;
- private int mSifStandard;
-
- private Builder() {}
-
- /**
- * Sets frequency.
- */
- public Builder setFrequency(int frequency) {
- mFrequency = frequency;
- return this;
- }
-
- /**
- * Sets analog type.
- */
- public Builder setAnalogType(int analogType) {
- mAnalogType = analogType;
- return this;
- }
-
- /**
- * Sets sif standard.
- */
- public Builder setSifStandard(int sifStandard) {
- mSifStandard = sifStandard;
- return this;
- }
-
- /**
- * Builds a FrontendAnalogSettings instance.
- */
- public FrontendAnalogSettings build() {
- return new FrontendAnalogSettings(mFrequency, mAnalogType, mSifStandard);
- }
- }
- }
-
- /**
- * Frontend settings for ATSC.
- * @hide
- */
- public static class FrontendAtscSettings extends FrontendSettings {
- public int modulation;
-
- FrontendAtscSettings(int frequency) {
- super(frequency);
- }
-
- @Override
- public int getType() {
- return TunerConstants.FRONTEND_TYPE_ATSC;
- }
- }
-
- /**
- * Frontend settings for ATSC-3.
- * @hide
- */
- public static class FrontendAtsc3Settings extends FrontendSettings {
- public int bandwidth;
- public byte demodOutputFormat;
- public List<FrontendAtsc3PlpSettings> plpSettings;
-
- FrontendAtsc3Settings(int frequency) {
- super(frequency);
- }
-
- @Override
- public int getType() {
- return TunerConstants.FRONTEND_TYPE_ATSC3;
- }
- }
-
- /**
- * Frontend settings for DVBS.
- * @hide
- */
- public static class FrontendDvbsSettings extends FrontendSettings {
- public int modulation;
- public FrontendDvbsCodeRate coderate;
- public int symbolRate;
- public int rolloff;
- public int pilot;
- public int inputStreamId;
- public byte standard;
-
- FrontendDvbsSettings(int frequency) {
- super(frequency);
- }
-
- @Override
- public int getType() {
- return TunerConstants.FRONTEND_TYPE_DVBS;
- }
- }
-
- /**
- * Frontend settings for DVBC.
- * @hide
- */
- public static class FrontendDvbcSettings extends FrontendSettings {
- public int modulation;
- public long fec;
- public int symbolRate;
- public int outerFec;
- public byte annex;
- public int spectralInversion;
-
- FrontendDvbcSettings(int frequency) {
- super(frequency);
- }
-
- @Override
- public int getType() {
- return TunerConstants.FRONTEND_TYPE_DVBC;
- }
- }
-
- /**
- * Frontend settings for DVBT.
- * @hide
- */
- public static class FrontendDvbtSettings extends FrontendSettings {
- public int transmissionMode;
- public int bandwidth;
- public int constellation;
- public int hierarchy;
- public int hpCoderate;
- public int lpCoderate;
- public int guardInterval;
- public boolean isHighPriority;
- public byte standard;
- public boolean isMiso;
- public int plpMode;
- public byte plpId;
- public byte plpGroupId;
-
- FrontendDvbtSettings(int frequency) {
- super(frequency);
- }
-
- @Override
- public int getType() {
- return TunerConstants.FRONTEND_TYPE_DVBT;
- }
- }
-
- /**
- * Frontend settings for ISDBS.
- * @hide
- */
- public static class FrontendIsdbsSettings extends FrontendSettings {
- public int streamId;
- public int streamIdType;
- public int modulation;
- public int coderate;
- public int symbolRate;
- public int rolloff;
-
- FrontendIsdbsSettings(int frequency) {
- super(frequency);
- }
-
- @Override
- public int getType() {
- return TunerConstants.FRONTEND_TYPE_ISDBS;
- }
- }
-
- /**
- * Frontend settings for ISDBS-3.
- * @hide
- */
- public static class FrontendIsdbs3Settings extends FrontendSettings {
- public int streamId;
- public int streamIdType;
- public int modulation;
- public int coderate;
- public int symbolRate;
- public int rolloff;
-
- FrontendIsdbs3Settings(int frequency) {
- super(frequency);
- }
-
- @Override
- public int getType() {
- return TunerConstants.FRONTEND_TYPE_ISDBS3;
- }
- }
-
- /**
- * Frontend settings for ISDBT.
- * @hide
- */
- public static class FrontendIsdbtSettings extends FrontendSettings {
- public int modulation;
- public int bandwidth;
- public int coderate;
- public int guardInterval;
- public int serviceAreaId;
-
- FrontendIsdbtSettings(int frequency) {
- super(frequency);
- }
-
- @Override
- public int getType() {
- return TunerConstants.FRONTEND_TYPE_ISDBT;
- }
- }
-
- /**
- * PLP settings for ATSC-3.
- * @hide
- */
- public static class FrontendAtsc3PlpSettings {
- public byte plpId;
- public int modulation;
- public int interleaveMode;
- public int codeRate;
- public int fec;
- }
-
- /**
- * Code rate for DVBS.
- * @hide
- */
- public static class FrontendDvbsCodeRate {
- public long fec;
- public boolean isLinear;
- public boolean isShortFrames;
- public int bitsPer1000Symbol;
- }
}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 63ef4b8..43f9a89 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -21,8 +21,9 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.content.Context;
-import android.media.tv.tuner.FilterSettings.Settings;
+import android.media.tv.tuner.FilterConfiguration.Settings;
import android.media.tv.tuner.TunerConstants.DemuxPidType;
+import android.media.tv.tuner.TunerConstants.FilterStatus;
import android.media.tv.tuner.TunerConstants.FilterSubtype;
import android.media.tv.tuner.TunerConstants.FilterType;
import android.media.tv.tuner.TunerConstants.FrontendScanType;
@@ -31,6 +32,9 @@
import android.media.tv.tuner.TunerConstants.LnbVoltage;
import android.media.tv.tuner.TunerConstants.Result;
import android.media.tv.tuner.filter.FilterEvent;
+import android.media.tv.tuner.frontend.FrontendCallback;
+import android.media.tv.tuner.frontend.FrontendInfo;
+import android.media.tv.tuner.frontend.FrontendStatus;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -127,24 +131,7 @@
private native Dvr nativeOpenDvr(int type, int bufferSize);
- /**
- * Frontend Callback.
- *
- * @hide
- */
- public interface FrontendCallback {
-
- /**
- * Invoked when there is a frontend event.
- */
- void onEvent(int frontendEventType);
-
- /**
- * Invoked when there is a scan message.
- * @param msg
- */
- void onScanMessage(ScanMessage msg);
- }
+ private static native DemuxCapabilities nativeGetDemuxCapabilities();
/**
* LNB Callback.
@@ -168,19 +155,23 @@
}
/**
- * Frontend Callback.
- *
- * @hide
+ * Callback interface for receiving information from the corresponding filters.
*/
public interface FilterCallback {
/**
* Invoked when there are filter events.
+ *
+ * @param filter the corresponding filter which sent the events.
+ * @param events the filter events sent from the filter.
*/
- void onFilterEvent(FilterEvent[] events);
+ void onFilterEvent(@NonNull Filter filter, @NonNull FilterEvent[] events);
/**
* Invoked when filter status changed.
+ *
+ * @param filter the corresponding filter whose status is changed.
+ * @param status the new status of the filter.
*/
- void onFilterStatus(int status);
+ void onFilterStatusChanged(@NonNull Filter filter, @FilterStatus int status);
}
/**
@@ -226,7 +217,7 @@
case MSG_ON_FILTER_STATUS: {
Filter filter = (Filter) msg.obj;
if (filter.mCallback != null) {
- filter.mCallback.onFilterStatus(msg.arg1);
+ filter.mCallback.onFilterStatusChanged(filter, msg.arg1);
}
break;
}
@@ -434,6 +425,11 @@
return mFrontend.mId;
}
+ /** @hide */
+ private static DemuxCapabilities getDemuxCapabilities() {
+ return nativeGetDemuxCapabilities();
+ }
+
private List<Integer> getFrontendIds() {
mFrontendIds = nativeGetFrontendIds();
return mFrontendIds;
@@ -456,13 +452,18 @@
}
}
- /** @hide */
+ /**
+ * Tuner data filter.
+ *
+ * <p> This class is used to filter wanted data according to the filter's configuration.
+ */
public class Filter {
private long mNativeContext;
private FilterCallback mCallback;
int mId;
- private native int nativeConfigureFilter(int type, int subType, FilterSettings settings);
+ private native int nativeConfigureFilter(
+ int type, int subType, FilterConfiguration settings);
private native int nativeGetId();
private native int nativeSetDataSource(Filter source);
private native int nativeStartFilter();
@@ -487,8 +488,9 @@
*
* @param settings the settings of the filter.
* @return result status of the operation.
+ * @hide
*/
- public int configure(FilterSettings settings) {
+ public int configure(FilterConfiguration settings) {
int subType = -1;
Settings s = settings.getSettings();
if (s != null) {
@@ -501,6 +503,7 @@
* Gets the filter Id.
*
* @return the hardware resource Id for the filter.
+ * @hide
*/
public int getId() {
return nativeGetId();
@@ -517,6 +520,7 @@
* @param source the filter instance which provides data input. Switch to
* use demux as data source if the filter instance is NULL.
* @return result status of the operation.
+ * @hide
*/
public int setDataSource(@Nullable Filter source) {
return nativeSetDataSource(source);
@@ -526,6 +530,7 @@
* Starts the filter.
*
* @return result status of the operation.
+ * @hide
*/
public int start() {
return nativeStartFilter();
@@ -536,6 +541,7 @@
* Stops the filter.
*
* @return result status of the operation.
+ * @hide
*/
public int stop() {
return nativeStopFilter();
@@ -545,11 +551,13 @@
* Flushes the filter.
*
* @return result status of the operation.
+ * @hide
*/
public int flush() {
return nativeFlushFilter();
}
+ /** @hide */
public int read(@NonNull byte[] buffer, int offset, int size) {
size = Math.min(size, buffer.length - offset);
return nativeRead(buffer, offset, size);
@@ -559,6 +567,7 @@
* Release the Filter instance.
*
* @return result status of the operation.
+ * @hide
*/
public int close() {
return nativeClose();
diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java
index e8e4b22..d24e582 100644
--- a/media/java/android/media/tv/tuner/TunerConstants.java
+++ b/media/java/android/media/tv/tuner/TunerConstants.java
@@ -18,16 +18,22 @@
import android.annotation.IntDef;
import android.annotation.LongDef;
+import android.annotation.SystemApi;
import android.hardware.tv.tuner.V1_0.Constants;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
+ * Constants for tuner framework.
+ *
* @hide
*/
+@SystemApi
public final class TunerConstants {
+ /** @hide */
public static final int INVALID_TS_PID = Constants.Constant.INVALID_TS_PID;
+ /** @hide */
public static final int INVALID_STREAM_ID = Constants.Constant.INVALID_STREAM_ID;
@@ -180,6 +186,37 @@
public static final int FILTER_SUBTYPE_PTP = 16;
/** @hide */
+ @IntDef({FILTER_STATUS_DATA_READY, FILTER_STATUS_LOW_WATER, FILTER_STATUS_HIGH_WATER,
+ FILTER_STATUS_OVERFLOW})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FilterStatus {}
+
+ /**
+ * The status of a filter that the data in the filter buffer is ready to be read.
+ */
+ public static final int FILTER_STATUS_DATA_READY = Constants.DemuxFilterStatus.DATA_READY;
+ /**
+ * The status of a filter that the amount of available data in the filter buffer is at low
+ * level.
+ *
+ * The value is set to 25 percent of the buffer size by default. It can be changed when
+ * configuring the filter.
+ */
+ public static final int FILTER_STATUS_LOW_WATER = Constants.DemuxFilterStatus.LOW_WATER;
+ /**
+ * The status of a filter that the amount of available data in the filter buffer is at high
+ * level.
+ * The value is set to 75 percent of the buffer size by default. It can be changed when
+ * configuring the filter.
+ */
+ public static final int FILTER_STATUS_HIGH_WATER = Constants.DemuxFilterStatus.HIGH_WATER;
+ /**
+ * The status of a filter that the filter buffer is full and newly filtered data is being
+ * discarded.
+ */
+ public static final int FILTER_STATUS_OVERFLOW = Constants.DemuxFilterStatus.OVERFLOW;
+
+ /** @hide */
@IntDef({FRONTEND_SCAN_UNDEFINED, FRONTEND_SCAN_AUTO, FRONTEND_SCAN_BLIND})
@Retention(RetentionPolicy.SOURCE)
public @interface FrontendScanType {}
@@ -739,119 +776,162 @@
public static final int HIERARCHY_4_INDEPTH =
Constants.FrontendDvbtHierarchy.HIERARCHY_4_INDEPTH;
- @Retention(RetentionPolicy.SOURCE)
+ /** @hide */
@IntDef({FRONTEND_ANALOG_TYPE_UNDEFINED, FRONTEND_ANALOG_TYPE_PAL, FRONTEND_ANALOG_TYPE_SECAM,
FRONTEND_ANALOG_TYPE_NTSC})
+ @Retention(RetentionPolicy.SOURCE)
public @interface FrontendAnalogType {}
-
+ /** @hide */
public static final int FRONTEND_ANALOG_TYPE_UNDEFINED = Constants.FrontendAnalogType.UNDEFINED;
+ /** @hide */
public static final int FRONTEND_ANALOG_TYPE_PAL = Constants.FrontendAnalogType.PAL;
+ /** @hide */
public static final int FRONTEND_ANALOG_TYPE_SECAM = Constants.FrontendAnalogType.SECAM;
+ /** @hide */
public static final int FRONTEND_ANALOG_TYPE_NTSC = Constants.FrontendAnalogType.NTSC;
- @Retention(RetentionPolicy.SOURCE)
+ /** @hide */
@IntDef({FRONTEND_ANALOG_SIF_UNDEFINED, FRONTEND_ANALOG_SIF_BG, FRONTEND_ANALOG_SIF_BG_A2,
FRONTEND_ANALOG_SIF_BG_NICAM, FRONTEND_ANALOG_SIF_I, FRONTEND_ANALOG_SIF_DK,
FRONTEND_ANALOG_SIF_DK1, FRONTEND_ANALOG_SIF_DK2, FRONTEND_ANALOG_SIF_DK3,
FRONTEND_ANALOG_SIF_DK_NICAM, FRONTEND_ANALOG_SIF_L, FRONTEND_ANALOG_SIF_M,
FRONTEND_ANALOG_SIF_M_BTSC, FRONTEND_ANALOG_SIF_M_A2, FRONTEND_ANALOG_SIF_M_EIA_J,
FRONTEND_ANALOG_SIF_I_NICAM, FRONTEND_ANALOG_SIF_L_NICAM, FRONTEND_ANALOG_SIF_L_PRIME})
+ @Retention(RetentionPolicy.SOURCE)
public @interface FrontendAnalogSifStandard {}
-
+ /** @hide */
public static final int FRONTEND_ANALOG_SIF_UNDEFINED =
Constants.FrontendAnalogSifStandard.UNDEFINED;
+ /** @hide */
public static final int FRONTEND_ANALOG_SIF_BG = Constants.FrontendAnalogSifStandard.BG;
+ /** @hide */
public static final int FRONTEND_ANALOG_SIF_BG_A2 = Constants.FrontendAnalogSifStandard.BG_A2;
+ /** @hide */
public static final int FRONTEND_ANALOG_SIF_BG_NICAM =
Constants.FrontendAnalogSifStandard.BG_NICAM;
+ /** @hide */
public static final int FRONTEND_ANALOG_SIF_I = Constants.FrontendAnalogSifStandard.I;
+ /** @hide */
public static final int FRONTEND_ANALOG_SIF_DK = Constants.FrontendAnalogSifStandard.DK;
+ /** @hide */
public static final int FRONTEND_ANALOG_SIF_DK1 = Constants.FrontendAnalogSifStandard.DK1;
+ /** @hide */
public static final int FRONTEND_ANALOG_SIF_DK2 = Constants.FrontendAnalogSifStandard.DK2;
+ /** @hide */
public static final int FRONTEND_ANALOG_SIF_DK3 = Constants.FrontendAnalogSifStandard.DK3;
+ /** @hide */
public static final int FRONTEND_ANALOG_SIF_DK_NICAM =
Constants.FrontendAnalogSifStandard.DK_NICAM;
+ /** @hide */
public static final int FRONTEND_ANALOG_SIF_L = Constants.FrontendAnalogSifStandard.L;
+ /** @hide */
public static final int FRONTEND_ANALOG_SIF_M = Constants.FrontendAnalogSifStandard.M;
+ /** @hide */
public static final int FRONTEND_ANALOG_SIF_M_BTSC = Constants.FrontendAnalogSifStandard.M_BTSC;
+ /** @hide */
public static final int FRONTEND_ANALOG_SIF_M_A2 = Constants.FrontendAnalogSifStandard.M_A2;
+ /** @hide */
public static final int FRONTEND_ANALOG_SIF_M_EIA_J =
Constants.FrontendAnalogSifStandard.M_EIA_J;
+ /** @hide */
public static final int FRONTEND_ANALOG_SIF_I_NICAM =
Constants.FrontendAnalogSifStandard.I_NICAM;
+ /** @hide */
public static final int FRONTEND_ANALOG_SIF_L_NICAM =
Constants.FrontendAnalogSifStandard.L_NICAM;
+ /** @hide */
public static final int FRONTEND_ANALOG_SIF_L_PRIME =
Constants.FrontendAnalogSifStandard.L_PRIME;
- @Retention(RetentionPolicy.SOURCE)
+ /** @hide */
@IntDef({FRONTEND_ATSC_MODULATION_UNDEFINED, FRONTEND_ATSC_MODULATION_AUTO,
FRONTEND_ATSC_MODULATION_MOD_8VSB, FRONTEND_ATSC_MODULATION_MOD_16VSB})
+ @Retention(RetentionPolicy.SOURCE)
public @interface FrontendAtscModulation {}
-
+ /** @hide */
public static final int FRONTEND_ATSC_MODULATION_UNDEFINED =
Constants.FrontendAtscModulation.UNDEFINED;
+ /** @hide */
public static final int FRONTEND_ATSC_MODULATION_AUTO = Constants.FrontendAtscModulation.AUTO;
+ /** @hide */
public static final int FRONTEND_ATSC_MODULATION_MOD_8VSB =
Constants.FrontendAtscModulation.MOD_8VSB;
+ /** @hide */
public static final int FRONTEND_ATSC_MODULATION_MOD_16VSB =
Constants.FrontendAtscModulation.MOD_16VSB;
- @Retention(RetentionPolicy.SOURCE)
+ /** @hide */
@IntDef({FRONTEND_ATSC3_BANDWIDTH_UNDEFINED, FRONTEND_ATSC3_BANDWIDTH_AUTO,
FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_6MHZ, FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_7MHZ,
FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_8MHZ})
+ @Retention(RetentionPolicy.SOURCE)
public @interface FrontendAtsc3Bandwidth {}
-
+ /** @hide */
public static final int FRONTEND_ATSC3_BANDWIDTH_UNDEFINED =
Constants.FrontendAtsc3Bandwidth.UNDEFINED;
+ /** @hide */
public static final int FRONTEND_ATSC3_BANDWIDTH_AUTO = Constants.FrontendAtsc3Bandwidth.AUTO;
+ /** @hide */
public static final int FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_6MHZ =
Constants.FrontendAtsc3Bandwidth.BANDWIDTH_6MHZ;
+ /** @hide */
public static final int FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_7MHZ =
Constants.FrontendAtsc3Bandwidth.BANDWIDTH_7MHZ;
+ /** @hide */
public static final int FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_8MHZ =
Constants.FrontendAtsc3Bandwidth.BANDWIDTH_8MHZ;
- @Retention(RetentionPolicy.SOURCE)
+ /** @hide */
@IntDef({FRONTEND_ATSC3_MODULATION_UNDEFINED, FRONTEND_ATSC3_MODULATION_AUTO,
FRONTEND_ATSC3_MODULATION_MOD_QPSK, FRONTEND_ATSC3_MODULATION_MOD_16QAM,
FRONTEND_ATSC3_MODULATION_MOD_64QAM, FRONTEND_ATSC3_MODULATION_MOD_256QAM,
FRONTEND_ATSC3_MODULATION_MOD_1024QAM, FRONTEND_ATSC3_MODULATION_MOD_4096QAM})
+ @Retention(RetentionPolicy.SOURCE)
public @interface FrontendAtsc3Modulation {}
-
+ /** @hide */
public static final int FRONTEND_ATSC3_MODULATION_UNDEFINED =
Constants.FrontendAtsc3Modulation.UNDEFINED;
+ /** @hide */
public static final int FRONTEND_ATSC3_MODULATION_AUTO = Constants.FrontendAtsc3Modulation.AUTO;
+ /** @hide */
public static final int FRONTEND_ATSC3_MODULATION_MOD_QPSK =
Constants.FrontendAtsc3Modulation.MOD_QPSK;
+ /** @hide */
public static final int FRONTEND_ATSC3_MODULATION_MOD_16QAM =
Constants.FrontendAtsc3Modulation.MOD_16QAM;
+ /** @hide */
public static final int FRONTEND_ATSC3_MODULATION_MOD_64QAM =
Constants.FrontendAtsc3Modulation.MOD_64QAM;
+ /** @hide */
public static final int FRONTEND_ATSC3_MODULATION_MOD_256QAM =
Constants.FrontendAtsc3Modulation.MOD_256QAM;
+ /** @hide */
public static final int FRONTEND_ATSC3_MODULATION_MOD_1024QAM =
Constants.FrontendAtsc3Modulation.MOD_1024QAM;
+ /** @hide */
public static final int FRONTEND_ATSC3_MODULATION_MOD_4096QAM =
Constants.FrontendAtsc3Modulation.MOD_4096QAM;
- @Retention(RetentionPolicy.SOURCE)
+ /** @hide */
@IntDef({FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_UNDEFINED,
FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_AUTO, FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_CTI,
FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_HTI})
+ @Retention(RetentionPolicy.SOURCE)
public @interface FrontendAtsc3TimeInterleaveMode {}
-
+ /** @hide */
public static final int FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_UNDEFINED =
Constants.FrontendAtsc3TimeInterleaveMode.UNDEFINED;
+ /** @hide */
public static final int FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_AUTO =
Constants.FrontendAtsc3TimeInterleaveMode.AUTO;
+ /** @hide */
public static final int FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_CTI =
Constants.FrontendAtsc3TimeInterleaveMode.CTI;
+ /** @hide */
public static final int FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_HTI =
Constants.FrontendAtsc3TimeInterleaveMode.HTI;
- @Retention(RetentionPolicy.SOURCE)
+ /** @hide */
@IntDef({FRONTEND_ATSC3_CODERATE_UNDEFINED, FRONTEND_ATSC3_CODERATE_AUTO,
FRONTEND_ATSC3_CODERATE_2_15, FRONTEND_ATSC3_CODERATE_3_15,
FRONTEND_ATSC3_CODERATE_4_15, FRONTEND_ATSC3_CODERATE_5_15,
@@ -859,258 +939,350 @@
FRONTEND_ATSC3_CODERATE_8_15, FRONTEND_ATSC3_CODERATE_9_15,
FRONTEND_ATSC3_CODERATE_10_15, FRONTEND_ATSC3_CODERATE_11_15,
FRONTEND_ATSC3_CODERATE_12_15, FRONTEND_ATSC3_CODERATE_13_15})
+ @Retention(RetentionPolicy.SOURCE)
public @interface FrontendAtsc3CodeRate {}
-
+ /** @hide */
public static final int FRONTEND_ATSC3_CODERATE_UNDEFINED =
Constants.FrontendAtsc3CodeRate.UNDEFINED;
+ /** @hide */
public static final int FRONTEND_ATSC3_CODERATE_AUTO = Constants.FrontendAtsc3CodeRate.AUTO;
+ /** @hide */
public static final int FRONTEND_ATSC3_CODERATE_2_15 =
Constants.FrontendAtsc3CodeRate.CODERATE_2_15;
+ /** @hide */
public static final int FRONTEND_ATSC3_CODERATE_3_15 =
Constants.FrontendAtsc3CodeRate.CODERATE_3_15;
+ /** @hide */
public static final int FRONTEND_ATSC3_CODERATE_4_15 =
Constants.FrontendAtsc3CodeRate.CODERATE_4_15;
+ /** @hide */
public static final int FRONTEND_ATSC3_CODERATE_5_15 =
Constants.FrontendAtsc3CodeRate.CODERATE_5_15;
+ /** @hide */
public static final int FRONTEND_ATSC3_CODERATE_6_15 =
Constants.FrontendAtsc3CodeRate.CODERATE_6_15;
+ /** @hide */
public static final int FRONTEND_ATSC3_CODERATE_7_15 =
Constants.FrontendAtsc3CodeRate.CODERATE_7_15;
+ /** @hide */
public static final int FRONTEND_ATSC3_CODERATE_8_15 =
Constants.FrontendAtsc3CodeRate.CODERATE_8_15;
+ /** @hide */
public static final int FRONTEND_ATSC3_CODERATE_9_15 =
Constants.FrontendAtsc3CodeRate.CODERATE_9_15;
+ /** @hide */
public static final int FRONTEND_ATSC3_CODERATE_10_15 =
Constants.FrontendAtsc3CodeRate.CODERATE_10_15;
+ /** @hide */
public static final int FRONTEND_ATSC3_CODERATE_11_15 =
Constants.FrontendAtsc3CodeRate.CODERATE_11_15;
+ /** @hide */
public static final int FRONTEND_ATSC3_CODERATE_12_15 =
Constants.FrontendAtsc3CodeRate.CODERATE_12_15;
+ /** @hide */
public static final int FRONTEND_ATSC3_CODERATE_13_15 =
Constants.FrontendAtsc3CodeRate.CODERATE_13_15;
- @Retention(RetentionPolicy.SOURCE)
+ /** @hide */
@IntDef({FRONTEND_ATSC3_FEC_UNDEFINED, FRONTEND_ATSC3_FEC_AUTO, FRONTEND_ATSC3_FEC_BCH_LDPC_16K,
FRONTEND_ATSC3_FEC_BCH_LDPC_64K, FRONTEND_ATSC3_FEC_CRC_LDPC_16K,
FRONTEND_ATSC3_FEC_CRC_LDPC_64K, FRONTEND_ATSC3_FEC_LDPC_16K,
FRONTEND_ATSC3_FEC_LDPC_64K})
+ @Retention(RetentionPolicy.SOURCE)
public @interface FrontendAtsc3Fec {}
-
+ /** @hide */
public static final int FRONTEND_ATSC3_FEC_UNDEFINED = Constants.FrontendAtsc3Fec.UNDEFINED;
+ /** @hide */
public static final int FRONTEND_ATSC3_FEC_AUTO = Constants.FrontendAtsc3Fec.AUTO;
+ /** @hide */
public static final int FRONTEND_ATSC3_FEC_BCH_LDPC_16K =
Constants.FrontendAtsc3Fec.BCH_LDPC_16K;
+ /** @hide */
public static final int FRONTEND_ATSC3_FEC_BCH_LDPC_64K =
Constants.FrontendAtsc3Fec.BCH_LDPC_64K;
+ /** @hide */
public static final int FRONTEND_ATSC3_FEC_CRC_LDPC_16K =
Constants.FrontendAtsc3Fec.CRC_LDPC_16K;
+ /** @hide */
public static final int FRONTEND_ATSC3_FEC_CRC_LDPC_64K =
Constants.FrontendAtsc3Fec.CRC_LDPC_64K;
+ /** @hide */
public static final int FRONTEND_ATSC3_FEC_LDPC_16K = Constants.FrontendAtsc3Fec.LDPC_16K;
+ /** @hide */
public static final int FRONTEND_ATSC3_FEC_LDPC_64K = Constants.FrontendAtsc3Fec.LDPC_64K;
- @Retention(RetentionPolicy.SOURCE)
+ /** @hide */
@IntDef({FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_UNDEFINED,
FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_ATSC3_LINKLAYER_PACKET,
FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_BASEBAND_PACKET})
+ @Retention(RetentionPolicy.SOURCE)
public @interface FrontendAtsc3DemodOutputFormat {}
-
+ /** @hide */
public static final int FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_UNDEFINED =
Constants.FrontendAtsc3DemodOutputFormat.UNDEFINED;
+ /** @hide */
public static final int FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_ATSC3_LINKLAYER_PACKET =
Constants.FrontendAtsc3DemodOutputFormat.ATSC3_LINKLAYER_PACKET;
+ /** @hide */
public static final int FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_BASEBAND_PACKET =
Constants.FrontendAtsc3DemodOutputFormat.BASEBAND_PACKET;
- @Retention(RetentionPolicy.SOURCE)
+ /** @hide */
@IntDef({FRONTEND_DVBS_STANDARD_AUTO, FRONTEND_DVBS_STANDARD_S, FRONTEND_DVBS_STANDARD_S2,
FRONTEND_DVBS_STANDARD_S2X})
+ @Retention(RetentionPolicy.SOURCE)
public @interface FrontendDvbsStandard {}
-
+ /** @hide */
public static final int FRONTEND_DVBS_STANDARD_AUTO = Constants.FrontendDvbsStandard.AUTO;
+ /** @hide */
public static final int FRONTEND_DVBS_STANDARD_S = Constants.FrontendDvbsStandard.S;
+ /** @hide */
public static final int FRONTEND_DVBS_STANDARD_S2 = Constants.FrontendDvbsStandard.S2;
+ /** @hide */
public static final int FRONTEND_DVBS_STANDARD_S2X = Constants.FrontendDvbsStandard.S2X;
- @Retention(RetentionPolicy.SOURCE)
+ /** @hide */
@IntDef({FRONTEND_DVBC_ANNEX_UNDEFINED, FRONTEND_DVBC_ANNEX_A, FRONTEND_DVBC_ANNEX_B,
FRONTEND_DVBC_ANNEX_C})
+ @Retention(RetentionPolicy.SOURCE)
public @interface FrontendDvbcAnnex {}
-
+ /** @hide */
public static final int FRONTEND_DVBC_ANNEX_UNDEFINED = Constants.FrontendDvbcAnnex.UNDEFINED;
+ /** @hide */
public static final int FRONTEND_DVBC_ANNEX_A = Constants.FrontendDvbcAnnex.A;
+ /** @hide */
public static final int FRONTEND_DVBC_ANNEX_B = Constants.FrontendDvbcAnnex.B;
+ /** @hide */
public static final int FRONTEND_DVBC_ANNEX_C = Constants.FrontendDvbcAnnex.C;
- @Retention(RetentionPolicy.SOURCE)
+ /** @hide */
@IntDef({FRONTEND_DVBT_TRANSMISSION_MODE_UNDEFINED, FRONTEND_DVBT_TRANSMISSION_MODE_AUTO,
FRONTEND_DVBT_TRANSMISSION_MODE_2K, FRONTEND_DVBT_TRANSMISSION_MODE_8K,
FRONTEND_DVBT_TRANSMISSION_MODE_4K, FRONTEND_DVBT_TRANSMISSION_MODE_1K,
FRONTEND_DVBT_TRANSMISSION_MODE_16K, FRONTEND_DVBT_TRANSMISSION_MODE_32K})
+ @Retention(RetentionPolicy.SOURCE)
public @interface FrontendDvbtTransmissionMode {}
-
+ /** @hide */
public static final int FRONTEND_DVBT_TRANSMISSION_MODE_UNDEFINED =
Constants.FrontendDvbtTransmissionMode.UNDEFINED;
+ /** @hide */
public static final int FRONTEND_DVBT_TRANSMISSION_MODE_AUTO =
Constants.FrontendDvbtTransmissionMode.AUTO;
+ /** @hide */
public static final int FRONTEND_DVBT_TRANSMISSION_MODE_2K =
Constants.FrontendDvbtTransmissionMode.MODE_2K;
+ /** @hide */
public static final int FRONTEND_DVBT_TRANSMISSION_MODE_8K =
Constants.FrontendDvbtTransmissionMode.MODE_8K;
+ /** @hide */
public static final int FRONTEND_DVBT_TRANSMISSION_MODE_4K =
Constants.FrontendDvbtTransmissionMode.MODE_4K;
+ /** @hide */
public static final int FRONTEND_DVBT_TRANSMISSION_MODE_1K =
Constants.FrontendDvbtTransmissionMode.MODE_1K;
+ /** @hide */
public static final int FRONTEND_DVBT_TRANSMISSION_MODE_16K =
Constants.FrontendDvbtTransmissionMode.MODE_16K;
+ /** @hide */
public static final int FRONTEND_DVBT_TRANSMISSION_MODE_32K =
Constants.FrontendDvbtTransmissionMode.MODE_32K;
- @Retention(RetentionPolicy.SOURCE)
+ /** @hide */
@IntDef({FRONTEND_DVBT_BANDWIDTH_UNDEFINED, FRONTEND_DVBT_BANDWIDTH_AUTO,
FRONTEND_DVBT_BANDWIDTH_8MHZ, FRONTEND_DVBT_BANDWIDTH_7MHZ,
FRONTEND_DVBT_BANDWIDTH_6MHZ, FRONTEND_DVBT_BANDWIDTH_5MHZ,
FRONTEND_DVBT_BANDWIDTH_1_7MHZ, FRONTEND_DVBT_BANDWIDTH_10MHZ})
+ @Retention(RetentionPolicy.SOURCE)
public @interface FrontendDvbtBandwidth {}
-
+ /** @hide */
public static final int FRONTEND_DVBT_BANDWIDTH_UNDEFINED =
Constants.FrontendDvbtBandwidth.UNDEFINED;
+ /** @hide */
public static final int FRONTEND_DVBT_BANDWIDTH_AUTO = Constants.FrontendDvbtBandwidth.AUTO;
+ /** @hide */
public static final int FRONTEND_DVBT_BANDWIDTH_8MHZ =
Constants.FrontendDvbtBandwidth.BANDWIDTH_8MHZ;
+ /** @hide */
public static final int FRONTEND_DVBT_BANDWIDTH_7MHZ =
Constants.FrontendDvbtBandwidth.BANDWIDTH_7MHZ;
+ /** @hide */
public static final int FRONTEND_DVBT_BANDWIDTH_6MHZ =
Constants.FrontendDvbtBandwidth.BANDWIDTH_6MHZ;
+ /** @hide */
public static final int FRONTEND_DVBT_BANDWIDTH_5MHZ =
Constants.FrontendDvbtBandwidth.BANDWIDTH_5MHZ;
+ /** @hide */
public static final int FRONTEND_DVBT_BANDWIDTH_1_7MHZ =
Constants.FrontendDvbtBandwidth.BANDWIDTH_1_7MHZ;
+ /** @hide */
public static final int FRONTEND_DVBT_BANDWIDTH_10MHZ =
Constants.FrontendDvbtBandwidth.BANDWIDTH_10MHZ;
- @Retention(RetentionPolicy.SOURCE)
+ /** @hide */
@IntDef({FRONTEND_DVBT_CONSTELLATION_UNDEFINED, FRONTEND_DVBT_CONSTELLATION_AUTO,
FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_QPSK,
FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_16QAM,
FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_64QAM,
FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_256QAM})
+ @Retention(RetentionPolicy.SOURCE)
public @interface FrontendDvbtConstellation {}
-
+ /** @hide */
public static final int FRONTEND_DVBT_CONSTELLATION_UNDEFINED =
Constants.FrontendDvbtConstellation.UNDEFINED;
+ /** @hide */
public static final int FRONTEND_DVBT_CONSTELLATION_AUTO =
Constants.FrontendDvbtConstellation.AUTO;
+ /** @hide */
public static final int FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_QPSK =
Constants.FrontendDvbtConstellation.CONSTELLATION_QPSK;
+ /** @hide */
public static final int FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_16QAM =
Constants.FrontendDvbtConstellation.CONSTELLATION_16QAM;
+ /** @hide */
public static final int FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_64QAM =
Constants.FrontendDvbtConstellation.CONSTELLATION_64QAM;
+ /** @hide */
public static final int FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_256QAM =
Constants.FrontendDvbtConstellation.CONSTELLATION_256QAM;
- @Retention(RetentionPolicy.SOURCE)
+ /** @hide */
@IntDef({FRONTEND_DVBT_CODERATE_UNDEFINED, FRONTEND_DVBT_CODERATE_AUTO,
FRONTEND_DVBT_CODERATE_1_2, FRONTEND_DVBT_CODERATE_2_3, FRONTEND_DVBT_CODERATE_3_4,
FRONTEND_DVBT_CODERATE_5_6, FRONTEND_DVBT_CODERATE_7_8, FRONTEND_DVBT_CODERATE_3_5,
FRONTEND_DVBT_CODERATE_4_5, FRONTEND_DVBT_CODERATE_6_7, FRONTEND_DVBT_CODERATE_8_9})
+ @Retention(RetentionPolicy.SOURCE)
public @interface FrontendDvbtCoderate {}
-
+ /** @hide */
public static final int FRONTEND_DVBT_CODERATE_UNDEFINED =
Constants.FrontendDvbtCoderate.UNDEFINED;
+ /** @hide */
public static final int FRONTEND_DVBT_CODERATE_AUTO = Constants.FrontendDvbtCoderate.AUTO;
+ /** @hide */
public static final int FRONTEND_DVBT_CODERATE_1_2 =
Constants.FrontendDvbtCoderate.CODERATE_1_2;
+ /** @hide */
public static final int FRONTEND_DVBT_CODERATE_2_3 =
Constants.FrontendDvbtCoderate.CODERATE_2_3;
+ /** @hide */
public static final int FRONTEND_DVBT_CODERATE_3_4 =
Constants.FrontendDvbtCoderate.CODERATE_3_4;
+ /** @hide */
public static final int FRONTEND_DVBT_CODERATE_5_6 =
Constants.FrontendDvbtCoderate.CODERATE_5_6;
+ /** @hide */
public static final int FRONTEND_DVBT_CODERATE_7_8 =
Constants.FrontendDvbtCoderate.CODERATE_7_8;
+ /** @hide */
public static final int FRONTEND_DVBT_CODERATE_3_5 =
Constants.FrontendDvbtCoderate.CODERATE_3_5;
+ /** @hide */
public static final int FRONTEND_DVBT_CODERATE_4_5 =
Constants.FrontendDvbtCoderate.CODERATE_4_5;
+ /** @hide */
public static final int FRONTEND_DVBT_CODERATE_6_7 =
Constants.FrontendDvbtCoderate.CODERATE_6_7;
+ /** @hide */
public static final int FRONTEND_DVBT_CODERATE_8_9 =
Constants.FrontendDvbtCoderate.CODERATE_8_9;
- @Retention(RetentionPolicy.SOURCE)
+ /** @hide */
@IntDef({FRONTEND_DVBT_GUARD_INTERVAL_UNDEFINED, FRONTEND_DVBT_GUARD_INTERVAL_AUTO,
FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_32, FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_16,
FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_8, FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_4,
FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_128,
FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_19_128,
FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_19_256})
+ @Retention(RetentionPolicy.SOURCE)
public @interface FrontendDvbtGuardInterval {}
-
+ /** @hide */
public static final int FRONTEND_DVBT_GUARD_INTERVAL_UNDEFINED =
Constants.FrontendDvbtGuardInterval.UNDEFINED;
+ /** @hide */
public static final int FRONTEND_DVBT_GUARD_INTERVAL_AUTO =
Constants.FrontendDvbtGuardInterval.AUTO;
+ /** @hide */
public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_32 =
Constants.FrontendDvbtGuardInterval.INTERVAL_1_32;
+ /** @hide */
public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_16 =
Constants.FrontendDvbtGuardInterval.INTERVAL_1_16;
+ /** @hide */
public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_8 =
Constants.FrontendDvbtGuardInterval.INTERVAL_1_8;
+ /** @hide */
public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_4 =
Constants.FrontendDvbtGuardInterval.INTERVAL_1_4;
+ /** @hide */
public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_128 =
Constants.FrontendDvbtGuardInterval.INTERVAL_1_128;
+ /** @hide */
public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_19_128 =
Constants.FrontendDvbtGuardInterval.INTERVAL_19_128;
+ /** @hide */
public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_19_256 =
Constants.FrontendDvbtGuardInterval.INTERVAL_19_256;
- @Retention(RetentionPolicy.SOURCE)
+ /** @hide */
@IntDef({FRONTEND_ISDBS_CODERATE_UNDEFINED, FRONTEND_ISDBS_CODERATE_AUTO,
FRONTEND_ISDBS_CODERATE_1_2, FRONTEND_ISDBS_CODERATE_2_3, FRONTEND_ISDBS_CODERATE_3_4,
FRONTEND_ISDBS_CODERATE_5_6, FRONTEND_ISDBS_CODERATE_7_8})
+ @Retention(RetentionPolicy.SOURCE)
public @interface FrontendIsdbsCoderate {}
-
+ /** @hide */
public static final int FRONTEND_ISDBS_CODERATE_UNDEFINED =
Constants.FrontendIsdbsCoderate.UNDEFINED;
+ /** @hide */
public static final int FRONTEND_ISDBS_CODERATE_AUTO = Constants.FrontendIsdbsCoderate.AUTO;
+ /** @hide */
public static final int FRONTEND_ISDBS_CODERATE_1_2 =
Constants.FrontendIsdbsCoderate.CODERATE_1_2;
+ /** @hide */
public static final int FRONTEND_ISDBS_CODERATE_2_3 =
Constants.FrontendIsdbsCoderate.CODERATE_2_3;
+ /** @hide */
public static final int FRONTEND_ISDBS_CODERATE_3_4 =
Constants.FrontendIsdbsCoderate.CODERATE_3_4;
+ /** @hide */
public static final int FRONTEND_ISDBS_CODERATE_5_6 =
Constants.FrontendIsdbsCoderate.CODERATE_5_6;
+ /** @hide */
public static final int FRONTEND_ISDBS_CODERATE_7_8 =
Constants.FrontendIsdbsCoderate.CODERATE_7_8;
- @Retention(RetentionPolicy.SOURCE)
+ /** @hide */
@IntDef({FRONTEND_ISDBT_MODE_UNDEFINED, FRONTEND_ISDBT_MODE_AUTO, FRONTEND_ISDBT_MODE_1,
FRONTEND_ISDBT_MODE_2, FRONTEND_ISDBT_MODE_3})
+ @Retention(RetentionPolicy.SOURCE)
public @interface FrontendIsdbtMode {}
-
+ /** @hide */
public static final int FRONTEND_ISDBT_MODE_UNDEFINED = Constants.FrontendIsdbtMode.UNDEFINED;
+ /** @hide */
public static final int FRONTEND_ISDBT_MODE_AUTO = Constants.FrontendIsdbtMode.AUTO;
+ /** @hide */
public static final int FRONTEND_ISDBT_MODE_1 = Constants.FrontendIsdbtMode.MODE_1;
+ /** @hide */
public static final int FRONTEND_ISDBT_MODE_2 = Constants.FrontendIsdbtMode.MODE_2;
+ /** @hide */
public static final int FRONTEND_ISDBT_MODE_3 = Constants.FrontendIsdbtMode.MODE_3;
- @Retention(RetentionPolicy.SOURCE)
+ /** @hide */
@IntDef({FRONTEND_ISDBT_BANDWIDTH_UNDEFINED, FRONTEND_ISDBT_BANDWIDTH_AUTO,
FRONTEND_ISDBT_BANDWIDTH_8MHZ, FRONTEND_ISDBT_BANDWIDTH_7MHZ,
FRONTEND_ISDBT_BANDWIDTH_6MHZ})
+ @Retention(RetentionPolicy.SOURCE)
public @interface FrontendIsdbtBandwidth {}
-
+ /** @hide */
public static final int FRONTEND_ISDBT_BANDWIDTH_UNDEFINED =
Constants.FrontendIsdbtBandwidth.UNDEFINED;
+ /** @hide */
public static final int FRONTEND_ISDBT_BANDWIDTH_AUTO = Constants.FrontendIsdbtBandwidth.AUTO;
+ /** @hide */
public static final int FRONTEND_ISDBT_BANDWIDTH_8MHZ =
Constants.FrontendIsdbtBandwidth.BANDWIDTH_8MHZ;
+ /** @hide */
public static final int FRONTEND_ISDBT_BANDWIDTH_7MHZ =
Constants.FrontendIsdbtBandwidth.BANDWIDTH_7MHZ;
+ /** @hide */
public static final int FRONTEND_ISDBT_BANDWIDTH_6MHZ =
Constants.FrontendIsdbtBandwidth.BANDWIDTH_6MHZ;
diff --git a/media/java/android/media/tv/tuner/filter/FilterEvent.java b/media/java/android/media/tv/tuner/filter/FilterEvent.java
index 0a4fa86..56a77d4 100644
--- a/media/java/android/media/tv/tuner/filter/FilterEvent.java
+++ b/media/java/android/media/tv/tuner/filter/FilterEvent.java
@@ -16,10 +16,13 @@
package android.media.tv.tuner.filter;
+import android.annotation.SystemApi;
+
/**
- * Demux filter event.
+ * An entity class that is passed to the filter callbacks.
*
* @hide
*/
+@SystemApi
public abstract class FilterEvent {
}
diff --git a/media/java/android/media/tv/tuner/filter/MediaEvent.java b/media/java/android/media/tv/tuner/filter/MediaEvent.java
index 5e45350..7703248 100644
--- a/media/java/android/media/tv/tuner/filter/MediaEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MediaEvent.java
@@ -22,7 +22,7 @@
* Media event.
* @hide
*/
-public class MediaEvent {
+public class MediaEvent extends FilterEvent {
private int mStreamId;
private boolean mIsPtsPresent;
private long mPts;
diff --git a/media/java/android/media/tv/tuner/filter/SectionEvent.java b/media/java/android/media/tv/tuner/filter/SectionEvent.java
index 5b5f8c9..e211dda 100644
--- a/media/java/android/media/tv/tuner/filter/SectionEvent.java
+++ b/media/java/android/media/tv/tuner/filter/SectionEvent.java
@@ -16,14 +16,54 @@
package android.media.tv.tuner.filter;
+import android.annotation.SystemApi;
+import android.media.tv.tuner.Tuner.Filter;
+
/**
- * Section event.
+ * Filter event sent from {@link Filter} objects with section type.
+ *
* @hide
*/
-public class SectionEvent {
- // TODO: add constructor and getters
- private int mTableId;
- private int mVersion;
- private int mSectionNum;
- private int mDataLength;
+@SystemApi
+public class SectionEvent extends FilterEvent {
+ private final int mTableId;
+ private final int mVersion;
+ private final int mSectionNum;
+ private final int mDataLength;
+
+ // This constructor is used by JNI code only
+ private SectionEvent(int tableId, int version, int sectionNum, int dataLength) {
+ mTableId = tableId;
+ mVersion = version;
+ mSectionNum = sectionNum;
+ mDataLength = dataLength;
+ }
+
+ /**
+ * Gets table ID of filtered data.
+ */
+ public int getTableId() {
+ return mTableId;
+ }
+
+ /**
+ * Gets version number of filtered data.
+ */
+ public int getVersion() {
+ return mVersion;
+ }
+
+ /**
+ * Gets section number of filtered data.
+ */
+ public int getSectionNumber() {
+ return mSectionNum;
+ }
+
+ /**
+ * Gets data size in bytes of filtered data.
+ */
+ public int getDataLength() {
+ return mDataLength;
+ }
}
diff --git a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
new file mode 100644
index 0000000..16308ce
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+import android.media.tv.tuner.FrontendSettings;
+import android.media.tv.tuner.TunerConstants;
+
+/**
+ * Frontend settings for analog.
+ * @hide
+ */
+public class AnalogFrontendSettings extends FrontendSettings {
+ private int mAnalogType;
+ private int mSifStandard;
+
+ @Override
+ public int getType() {
+ return TunerConstants.FRONTEND_TYPE_ANALOG;
+ }
+
+ public int getAnalogType() {
+ return mAnalogType;
+ }
+
+ public int getSifStandard() {
+ return mSifStandard;
+ }
+
+ /**
+ * Creates a new builder object.
+ */
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ private AnalogFrontendSettings(int frequency, int analogType, int sifStandard) {
+ super(frequency);
+ mAnalogType = analogType;
+ mSifStandard = sifStandard;
+ }
+
+ /**
+ * Builder for FrontendAnalogSettings.
+ */
+ public static class Builder {
+ private int mFrequency;
+ private int mAnalogType;
+ private int mSifStandard;
+
+ private Builder() {}
+
+ /**
+ * Sets frequency.
+ */
+ public Builder setFrequency(int frequency) {
+ mFrequency = frequency;
+ return this;
+ }
+
+ /**
+ * Sets analog type.
+ */
+ public Builder setAnalogType(int analogType) {
+ mAnalogType = analogType;
+ return this;
+ }
+
+ /**
+ * Sets sif standard.
+ */
+ public Builder setSifStandard(int sifStandard) {
+ mSifStandard = sifStandard;
+ return this;
+ }
+
+ /**
+ * Builds a FrontendAnalogSettings instance.
+ */
+ public AnalogFrontendSettings build() {
+ return new AnalogFrontendSettings(mFrequency, mAnalogType, mSifStandard);
+ }
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
new file mode 100644
index 0000000..bce8a64
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+
+import android.media.tv.tuner.FrontendSettings;
+import android.media.tv.tuner.TunerConstants;
+
+import java.util.List;
+
+/**
+ * Frontend settings for ATSC-3.
+ * @hide
+ */
+public class Atsc3FrontendSettings extends FrontendSettings {
+ public int bandwidth;
+ public byte demodOutputFormat;
+ public List<Atsc3PlpSettings> plpSettings;
+
+ Atsc3FrontendSettings(int frequency) {
+ super(frequency);
+ }
+
+ @Override
+ public int getType() {
+ return TunerConstants.FRONTEND_TYPE_ATSC3;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3PlpSettings.java b/media/java/android/media/tv/tuner/frontend/Atsc3PlpSettings.java
new file mode 100644
index 0000000..61c6fec
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/Atsc3PlpSettings.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+/**
+ * PLP settings for ATSC-3.
+ * @hide
+ */
+public class Atsc3PlpSettings {
+ public byte plpId;
+ public int modulation;
+ public int interleaveMode;
+ public int codeRate;
+ public int fec;
+}
diff --git a/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
new file mode 100644
index 0000000..14c5cdd
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+import android.media.tv.tuner.FrontendSettings;
+import android.media.tv.tuner.TunerConstants;
+
+/**
+ * Frontend settings for ATSC.
+ * @hide
+ */
+public class AtscFrontendSettings extends FrontendSettings {
+ public int modulation;
+
+ AtscFrontendSettings(int frequency) {
+ super(frequency);
+ }
+
+ @Override
+ public int getType() {
+ return TunerConstants.FRONTEND_TYPE_ATSC;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
new file mode 100644
index 0000000..07e49ff
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+import android.media.tv.tuner.FrontendSettings;
+import android.media.tv.tuner.TunerConstants;
+
+/**
+ * Frontend settings for DVBC.
+ * @hide
+ */
+public class DvbcFrontendSettings extends FrontendSettings {
+ public int modulation;
+ public long fec;
+ public int symbolRate;
+ public int outerFec;
+ public byte annex;
+ public int spectralInversion;
+
+ DvbcFrontendSettings(int frequency) {
+ super(frequency);
+ }
+
+ @Override
+ public int getType() {
+ return TunerConstants.FRONTEND_TYPE_DVBC;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbsCodeRate.java b/media/java/android/media/tv/tuner/frontend/DvbsCodeRate.java
new file mode 100644
index 0000000..bfa4391
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/DvbsCodeRate.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+/**
+ * Code rate for DVBS.
+ * @hide
+ */
+public class DvbsCodeRate {
+ public long fec;
+ public boolean isLinear;
+ public boolean isShortFrames;
+ public int bitsPer1000Symbol;
+}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
new file mode 100644
index 0000000..23c0a7b1
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+import android.media.tv.tuner.FrontendSettings;
+import android.media.tv.tuner.TunerConstants;
+
+/**
+ * Frontend settings for DVBS.
+ * @hide
+ */
+public class DvbsFrontendSettings extends FrontendSettings {
+ public int modulation;
+ public DvbsCodeRate coderate;
+ public int symbolRate;
+ public int rolloff;
+ public int pilot;
+ public int inputStreamId;
+ public byte standard;
+
+ DvbsFrontendSettings(int frequency) {
+ super(frequency);
+ }
+
+ @Override
+ public int getType() {
+ return TunerConstants.FRONTEND_TYPE_DVBS;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
new file mode 100644
index 0000000..eec00f3
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+
+import android.media.tv.tuner.FrontendSettings;
+import android.media.tv.tuner.TunerConstants;
+
+/**
+ * Frontend settings for DVBT.
+ * @hide
+ */
+public class DvbtFrontendSettings extends FrontendSettings {
+ public int transmissionMode;
+ public int bandwidth;
+ public int constellation;
+ public int hierarchy;
+ public int hpCoderate;
+ public int lpCoderate;
+ public int guardInterval;
+ public boolean isHighPriority;
+ public byte standard;
+ public boolean isMiso;
+ public int plpMode;
+ public byte plpId;
+ public byte plpGroupId;
+
+ DvbtFrontendSettings(int frequency) {
+ super(frequency);
+ }
+
+ @Override
+ public int getType() {
+ return TunerConstants.FRONTEND_TYPE_DVBT;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendCallback.java b/media/java/android/media/tv/tuner/frontend/FrontendCallback.java
new file mode 100644
index 0000000..91776e1
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/FrontendCallback.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+import android.media.tv.tuner.ScanMessage;
+
+/**
+ * Frontend Callback.
+ *
+ * @hide
+ */
+public interface FrontendCallback {
+
+ /**
+ * Invoked when there is a frontend event.
+ */
+ void onEvent(int frontendEventType);
+
+ /**
+ * Invoked when there is a scan message.
+ * @param msg
+ */
+ void onScanMessage(ScanMessage msg);
+}
diff --git a/media/java/android/media/tv/tuner/FrontendInfo.java b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
similarity index 96%
rename from media/java/android/media/tv/tuner/FrontendInfo.java
rename to media/java/android/media/tv/tuner/frontend/FrontendInfo.java
index 2ab100d..ef6c029 100644
--- a/media/java/android/media/tv/tuner/FrontendInfo.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package android.media.tv.tuner;
+package android.media.tv.tuner.frontend;
+import android.media.tv.tuner.FrontendCapabilities;
import android.media.tv.tuner.TunerConstants.FrontendType;
/**
diff --git a/media/java/android/media/tv/tuner/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
similarity index 98%
rename from media/java/android/media/tv/tuner/FrontendStatus.java
rename to media/java/android/media/tv/tuner/frontend/FrontendStatus.java
index f8b2d12..89ec536 100644
--- a/media/java/android/media/tv/tuner/FrontendStatus.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package android.media.tv.tuner;
+package android.media.tv.tuner.frontend;
+import android.media.tv.tuner.TunerConstants;
import android.media.tv.tuner.TunerConstants.FrontendDvbcSpectralInversion;
import android.media.tv.tuner.TunerConstants.FrontendDvbtHierarchy;
import android.media.tv.tuner.TunerConstants.FrontendInnerFec;
diff --git a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
new file mode 100644
index 0000000..736d0b1
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+import android.media.tv.tuner.FrontendSettings;
+import android.media.tv.tuner.TunerConstants;
+
+/**
+ * Frontend settings for ISDBS-3.
+ * @hide
+ */
+public class Isdbs3FrontendSettings extends FrontendSettings {
+ public int streamId;
+ public int streamIdType;
+ public int modulation;
+ public int coderate;
+ public int symbolRate;
+ public int rolloff;
+
+ Isdbs3FrontendSettings(int frequency) {
+ super(frequency);
+ }
+
+ @Override
+ public int getType() {
+ return TunerConstants.FRONTEND_TYPE_ISDBS3;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
new file mode 100644
index 0000000..7fd5da7
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+import android.media.tv.tuner.FrontendSettings;
+import android.media.tv.tuner.TunerConstants;
+
+/**
+ * Frontend settings for ISDBS.
+ * @hide
+ */
+public class IsdbsFrontendSettings extends FrontendSettings {
+ public int streamId;
+ public int streamIdType;
+ public int modulation;
+ public int coderate;
+ public int symbolRate;
+ public int rolloff;
+
+ IsdbsFrontendSettings(int frequency) {
+ super(frequency);
+ }
+
+ @Override
+ public int getType() {
+ return TunerConstants.FRONTEND_TYPE_ISDBS;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
new file mode 100644
index 0000000..3f83267
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+
+import android.media.tv.tuner.FrontendSettings;
+import android.media.tv.tuner.TunerConstants;
+
+/**
+ * Frontend settings for ISDBT.
+ * @hide
+ */
+public class IsdbtFrontendSettings extends FrontendSettings {
+ public int modulation;
+ public int bandwidth;
+ public int coderate;
+ public int guardInterval;
+ public int serviceAreaId;
+
+ IsdbtFrontendSettings(int frequency) {
+ super(frequency);
+ }
+
+ @Override
+ public int getType() {
+ return TunerConstants.FRONTEND_TYPE_ISDBT;
+ }
+}
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 3562782..f0f3688 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -100,7 +100,7 @@
/////////////// Dvr ///////////////////////
-Dvr::Dvr(sp<IDvr> sp, jweak obj) : mDvrSp(sp), mDvrObj(obj) {}
+Dvr::Dvr(sp<IDvr> sp, jweak obj) : mDvrSp(sp), mDvrObj(obj), mDvrMQEventFlag(nullptr) {}
Dvr::~Dvr() {
EventFlag::deleteEventFlag(&mDvrMQEventFlag);
@@ -880,6 +880,10 @@
return tuner->openDvr(static_cast<DvrType>(type), bufferSize);
}
+static jobject android_media_tv_Tuner_get_demux_caps(JNIEnv*, jobject) {
+ return NULL;
+}
+
static int android_media_tv_Tuner_attach_filter(JNIEnv *env, jobject dvr, jobject filter) {
sp<IDvr> dvrSp = getDvr(env, dvr)->getIDvr();
sp<IFilter> filterSp = getFilter(env, filter)->getIFilter();
@@ -1123,6 +1127,8 @@
(void *)android_media_tv_Tuner_open_descrambler },
{ "nativeOpenDvr", "(II)Landroid/media/tv/tuner/Tuner$Dvr;",
(void *)android_media_tv_Tuner_open_dvr },
+ { "nativeGetDemuxCapabilities", "()Landroid/media/tv/tuner/DemuxCapabilities;",
+ (void *)android_media_tv_Tuner_get_demux_caps },
};
static const JNINativeMethod gFilterMethods[] = {
diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp
index 747d4c01..007dd10 100644
--- a/media/jni/audioeffect/android_media_AudioEffect.cpp
+++ b/media/jni/audioeffect/android_media_AudioEffect.cpp
@@ -268,8 +268,9 @@
static jint
android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
- jstring type, jstring uuid, jint priority, jint sessionId, jintArray jId,
- jobjectArray javadesc, jstring opPackageName)
+ jstring type, jstring uuid, jint priority, jint sessionId,
+ jint deviceType, jstring deviceAddress,
+ jintArray jId, jobjectArray javadesc, jstring opPackageName)
{
ALOGV("android_media_AudioEffect_native_setup");
AudioEffectJniStorage* lpJniStorage = NULL;
@@ -280,6 +281,7 @@
const char *uuidStr = NULL;
effect_descriptor_t desc;
jobject jdesc;
+ AudioDeviceTypeAddr device;
ScopedUtfChars opPackageNameStr(env, opPackageName);
@@ -328,6 +330,12 @@
goto setup_failure;
}
+ if (deviceType != AUDIO_DEVICE_NONE) {
+ device.mType = deviceType;
+ ScopedUtfChars address(env, deviceAddress);
+ device.mAddress = address.c_str();
+ }
+
// create the native AudioEffect object
lpAudioEffect = new AudioEffect(typeStr,
String16(opPackageNameStr.c_str()),
@@ -336,7 +344,8 @@
effectCallback,
&lpJniStorage->mCallbackData,
(audio_session_t) sessionId,
- AUDIO_IO_HANDLE_NONE);
+ AUDIO_IO_HANDLE_NONE,
+ device);
if (lpAudioEffect == 0) {
ALOGE("Error creating AudioEffect");
goto setup_failure;
@@ -757,7 +766,7 @@
// Dalvik VM type signatures
static const JNINativeMethod gMethods[] = {
{"native_init", "()V", (void *)android_media_AudioEffect_native_init},
- {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;II[I[Ljava/lang/Object;Ljava/lang/String;)I",
+ {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;IIILjava/lang/String;[I[Ljava/lang/Object;Ljava/lang/String;)I",
(void *)android_media_AudioEffect_native_setup},
{"native_finalize", "()V", (void *)android_media_AudioEffect_native_finalize},
{"native_release", "()V", (void *)android_media_AudioEffect_native_release},
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
index 6fe847b..86b9706 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
@@ -701,7 +701,6 @@
static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
Map<String, MediaRoute2Info> routeMap = new HashMap<>();
for (MediaRoute2Info route : routes) {
- // intentionally not using route.getUniqueId() for convenience.
routeMap.put(route.getId(), route);
}
return routeMap;
@@ -741,7 +740,7 @@
}
/**
- * Returns a list of IDs (not uniqueId) of the given route list.
+ * Returns a list of IDs of the given route list.
*/
List<String> getRouteIds(@NonNull List<MediaRoute2Info> routes) {
List<String> result = new ArrayList<>();
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
index 83c7c17..1fd0141 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
@@ -57,30 +57,37 @@
public class MediaRouterManagerTest {
private static final String TAG = "MediaRouterManagerTest";
- // Must be the same as SampleMediaRoute2ProviderService
- public static final String ROUTE_ID1 = "route_id1";
+ public static final String SAMPLE_PROVIDER_ROUTES_ID_PREFIX =
+ "com.android.mediarouteprovider.example/.SampleMediaRoute2ProviderService:";
+
+ // Must be the same as SampleMediaRoute2ProviderService except the prefix of IDs.
+ public static final String ROUTE_ID1 = SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id1";
public static final String ROUTE_NAME1 = "Sample Route 1";
- public static final String ROUTE_ID2 = "route_id2";
+ public static final String ROUTE_ID2 = SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id2";
public static final String ROUTE_NAME2 = "Sample Route 2";
public static final String ROUTE_ID3_SESSION_CREATION_FAILED =
- "route_id3_session_creation_failed";
+ SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id3_session_creation_failed";
public static final String ROUTE_NAME3 = "Sample Route 3 - Session creation failed";
public static final String ROUTE_ID4_TO_SELECT_AND_DESELECT =
- "route_id4_to_select_and_deselect";
+ SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id4_to_select_and_deselect";
public static final String ROUTE_NAME4 = "Sample Route 4 - Route to select and deselect";
- public static final String ROUTE_ID5_TO_TRANSFER_TO = "route_id5_to_transfer_to";
+ public static final String ROUTE_ID5_TO_TRANSFER_TO =
+ SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id5_to_transfer_to";
public static final String ROUTE_NAME5 = "Sample Route 5 - Route to transfer to";
- public static final String ROUTE_ID_SPECIAL_CATEGORY = "route_special_category";
+ public static final String ROUTE_ID_SPECIAL_CATEGORY =
+ SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_special_category";
public static final String ROUTE_NAME_SPECIAL_CATEGORY = "Special Category Route";
public static final String SYSTEM_PROVIDER_ID =
"com.android.server.media/.SystemMediaRoute2Provider";
public static final int VOLUME_MAX = 100;
- public static final String ROUTE_ID_FIXED_VOLUME = "route_fixed_volume";
+ public static final String ROUTE_ID_FIXED_VOLUME =
+ SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_fixed_volume";
public static final String ROUTE_NAME_FIXED_VOLUME = "Fixed Volume Route";
- public static final String ROUTE_ID_VARIABLE_VOLUME = "route_variable_volume";
+ public static final String ROUTE_ID_VARIABLE_VOLUME =
+ SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_variable_volume";
public static final String ROUTE_NAME_VARIABLE_VOLUME = "Variable Volume Route";
public static final String ACTION_REMOVE_ROUTE =
@@ -430,7 +437,6 @@
static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
Map<String, MediaRoute2Info> routeMap = new HashMap<>();
for (MediaRoute2Info route : routes) {
- // intentionally not using route.getUniqueId() for convenience.
routeMap.put(route.getId(), route);
}
return routeMap;
diff --git a/packages/CarSystemUI/res/drawable/car_ic_notification_selected.xml b/packages/CarSystemUI/res/drawable/car_ic_notification_selected.xml
deleted file mode 100644
index dd22545..0000000
--- a/packages/CarSystemUI/res/drawable/car_ic_notification_selected.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2019 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:viewportWidth="44"
- android:viewportHeight="44"
- android:width="44dp"
- android:height="44dp">
- <path
- android:pathData="M22 39.125C23.925 39.125 25.5 37.55 25.5 35.625L18.5 35.625C18.5 37.55 20.0575 39.125 22 39.125ZM32.5 28.625L32.5 19.875C32.5 14.5025 29.63 10.005 24.625 8.815L24.625 7.625C24.625 6.1725 23.4525 5 22 5C20.5475 5 19.375 6.1725 19.375 7.625L19.375 8.815C14.3525 10.005 11.5 14.485 11.5 19.875L11.5 28.625L8 32.125L8 33.875L36 33.875L36 32.125L32.5 28.625Z"
- android:fillColor="@color/car_nav_icon_fill_color_selected" />
-</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/res/drawable/car_ic_notification_selected_unseen.xml b/packages/CarSystemUI/res/drawable/car_ic_notification_selected_unseen.xml
deleted file mode 100644
index c5d7728..0000000
--- a/packages/CarSystemUI/res/drawable/car_ic_notification_selected_unseen.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2019 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:viewportWidth="44"
- android:viewportHeight="44"
- android:width="44dp"
- android:height="44dp">
- <path
- android:pathData="M22 39.125C23.925 39.125 25.5 37.55 25.5 35.625L18.5 35.625C18.5 37.55 20.0575 39.125 22 39.125ZM32.5 28.625L32.5 19.875C32.5 14.5025 29.63 10.005 24.625 8.815L24.625 7.625C24.625 6.1725 23.4525 5 22 5C20.5475 5 19.375 6.1725 19.375 7.625L19.375 8.815C14.3525 10.005 11.5 14.485 11.5 19.875L11.5 28.625L8 32.125L8 33.875L36 33.875L36 32.125L32.5 28.625Z"
- android:fillColor="@color/car_nav_icon_fill_color_selected" />
- <group
- android:translateX="30"
- android:translateY="2">
- <path
- android:fillColor="@color/car_nav_notification_unseen_indicator_color"
- android:strokeWidth="1"
- android:pathData="M 6 0 C 9.31370849898 0 12 2.68629150102 12 6 C 12 9.31370849898 9.31370849898 12 6 12 C 2.68629150102 12 0 9.31370849898 0 6 C 0 2.68629150102 2.68629150102 0 6 0 Z" />
- </group>
-</vector>
-
diff --git a/packages/CarSystemUI/res/drawable/car_ic_notification_unseen.xml b/packages/CarSystemUI/res/drawable/car_ic_unseen_indicator.xml
similarity index 69%
rename from packages/CarSystemUI/res/drawable/car_ic_notification_unseen.xml
rename to packages/CarSystemUI/res/drawable/car_ic_unseen_indicator.xml
index 25d1af3..025fc9c 100644
--- a/packages/CarSystemUI/res/drawable/car_ic_notification_unseen.xml
+++ b/packages/CarSystemUI/res/drawable/car_ic_unseen_indicator.xml
@@ -19,14 +19,11 @@
android:viewportHeight="44"
android:width="44dp"
android:height="44dp">
- <path
- android:pathData="M22 39.125C23.925 39.125 25.5 37.55 25.5 35.625L18.5 35.625C18.5 37.55 20.0575 39.125 22 39.125ZM32.5 28.625L32.5 19.875C32.5 14.5025 29.63 10.005 24.625 8.815L24.625 7.625C24.625 6.1725 23.4525 5 22 5C20.5475 5 19.375 6.1725 19.375 7.625L19.375 8.815C14.3525 10.005 11.5 14.485 11.5 19.875L11.5 28.625L8 32.125L8 33.875L36 33.875L36 32.125L32.5 28.625Z"
- android:fillColor="@color/car_nav_icon_fill_color" />
<group
android:translateX="30"
android:translateY="2">
<path
- android:fillColor="@color/car_nav_notification_unseen_indicator_color"
+ android:fillColor="@color/car_nav_unseen_indicator_color"
android:strokeWidth="1"
android:pathData="M 6 0 C 9.31370849898 0 12 2.68629150102 12 6 C 12 9.31370849898 9.31370849898 12 6 12 C 2.68629150102 12 0 9.31370849898 0 6 C 0 2.68629150102 2.68629150102 0 6 0 Z" />
</group>
diff --git a/packages/CarSystemUI/res/layout/car_facet_button.xml b/packages/CarSystemUI/res/layout/car_facet_button.xml
deleted file mode 100644
index 8e7ebad..0000000
--- a/packages/CarSystemUI/res/layout/car_facet_button.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2018, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/car_facet_button"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:gravity="center"
- android:animateLayoutChanges="true"
- android:orientation="vertical">
-
- <com.android.keyguard.AlphaOptimizedImageButton
- android:id="@+id/car_nav_button_icon"
- android:layout_height="wrap_content"
- android:layout_width="match_parent"
- android:animateLayoutChanges="true"
- android:background="@android:color/transparent"
- android:scaleType="fitCenter">
- </com.android.keyguard.AlphaOptimizedImageButton>
-
- <com.android.keyguard.AlphaOptimizedImageButton
- android:id="@+id/car_nav_button_more_icon"
- android:layout_height="wrap_content"
- android:layout_width="match_parent"
- android:animateLayoutChanges="true"
- android:src="@drawable/car_ic_arrow"
- android:background="@android:color/transparent"
- android:scaleType="fitCenter">
- </com.android.keyguard.AlphaOptimizedImageButton>
-
- </LinearLayout>
-</merge>
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
index 6c7a04f..e2e9a33 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
@@ -33,14 +33,14 @@
android:paddingEnd="20dp"
android:gravity="center">
- <com.android.systemui.navigationbar.car.CarFacetButton
+ <com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/home"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
systemui:icon="@drawable/car_ic_overview"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
systemui:selectedIcon="@drawable/car_ic_overview_selected"
- systemui:useMoreIcon="false"
+ systemui:highlightWhenSelected="true"
/>
<Space
@@ -48,14 +48,14 @@
android:layout_height="match_parent"
android:layout_weight="1"/>
- <com.android.systemui.navigationbar.car.CarFacetButton
+ <com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/maps_nav"
style="@style/NavigationBarButton"
systemui:categories="android.intent.category.APP_MAPS"
systemui:icon="@drawable/car_ic_navigation"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MAPS;launchFlags=0x14000000;end"
systemui:selectedIcon="@drawable/car_ic_navigation_selected"
- systemui:useMoreIcon="false"
+ systemui:highlightWhenSelected="true"
/>
<Space
@@ -63,7 +63,7 @@
android:layout_height="match_parent"
android:layout_weight="1"/>
- <com.android.systemui.navigationbar.car.CarFacetButton
+ <com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/music_nav"
style="@style/NavigationBarButton"
systemui:categories="android.intent.category.APP_MUSIC"
@@ -71,7 +71,7 @@
systemui:intent="intent:#Intent;action=android.car.intent.action.MEDIA_TEMPLATE;launchFlags=0x10000000;end"
systemui:packages="com.android.car.media"
systemui:selectedIcon="@drawable/car_ic_music_selected"
- systemui:useMoreIcon="false"
+ systemui:highlightWhenSelected="true"
/>
<Space
@@ -79,14 +79,14 @@
android:layout_height="match_parent"
android:layout_weight="1"/>
- <com.android.systemui.navigationbar.car.CarFacetButton
+ <com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/phone_nav"
style="@style/NavigationBarButton"
systemui:icon="@drawable/car_ic_phone"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;package=com.android.car.dialer;launchFlags=0x10000000;end"
systemui:packages="com.android.car.dialer"
systemui:selectedIcon="@drawable/car_ic_phone_selected"
- systemui:useMoreIcon="false"
+ systemui:highlightWhenSelected="true"
/>
<Space
@@ -94,14 +94,14 @@
android:layout_height="match_parent"
android:layout_weight="1"/>
- <com.android.systemui.navigationbar.car.CarFacetButton
+ <com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/grid_nav"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.AppGridActivity"
systemui:icon="@drawable/car_ic_apps"
systemui:intent="intent:#Intent;component=com.android.car.carlauncher/.AppGridActivity;launchFlags=0x24000000;end"
systemui:selectedIcon="@drawable/car_ic_apps_selected"
- systemui:useMoreIcon="false"
+ systemui:highlightWhenSelected="true"
/>
<Space
@@ -114,8 +114,6 @@
style="@style/NavigationBarButton"
systemui:icon="@drawable/car_ic_notification"
systemui:longIntent="intent:#Intent;component=com.android.car.bugreport/.BugReportActivity;end"
- systemui:selectedIcon="@drawable/car_ic_notification_selected"
- systemui:useMoreIcon="false"
/>
<Space
@@ -127,7 +125,6 @@
android:id="@+id/assist"
style="@style/NavigationBarButton"
systemui:icon="@drawable/ic_mic_white"
- systemui:useMoreIcon="false"
/>
</LinearLayout>
@@ -140,8 +137,7 @@
android:paddingStart="@dimen/car_keyline_1"
android:paddingEnd="@dimen/car_keyline_1"
android:gravity="center"
- android:visibility="gone">
-
- </LinearLayout>
+ android:visibility="gone"
+ />
</com.android.systemui.navigationbar.car.CarNavigationBarView>
\ No newline at end of file
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml b/packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml
index 0f964fd..1c5d37f 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml
@@ -31,7 +31,7 @@
android:paddingStart="@*android:dimen/car_padding_5"
android:paddingEnd="@*android:dimen/car_padding_5">
- <com.android.systemui.navigationbar.car.CarFacetButton
+ <com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/home"
android:layout_width="@*android:dimen/car_touch_target_size"
android:layout_height="match_parent"
@@ -39,7 +39,8 @@
systemui:icon="@drawable/car_ic_overview"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
systemui:selectedIcon="@drawable/car_ic_overview_selected"
- systemui:useMoreIcon="false"/>
+ systemui:highlightWhenSelected="true"
+ />
</LinearLayout>
</com.android.systemui.navigationbar.car.CarNavigationBarView>
diff --git a/packages/CarSystemUI/res/layout/car_navigation_button.xml b/packages/CarSystemUI/res/layout/car_navigation_button.xml
index 6d8cca9..837252b 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_button.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_button.xml
@@ -18,12 +18,48 @@
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <com.android.keyguard.AlphaOptimizedImageButton
+ <FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/car_nav_button_icon"
- android:layout_height="wrap_content"
- android:layout_width="@dimen/car_navigation_button_width"
- android:layout_centerInParent="true"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
android:animateLayoutChanges="true"
- android:scaleType="fitCenter">
- </com.android.keyguard.AlphaOptimizedImageButton>
+ android:orientation="vertical">
+
+ <com.android.keyguard.AlphaOptimizedImageButton
+ android:id="@+id/car_nav_button_icon_image"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_gravity="center"
+ android:animateLayoutChanges="true"
+ android:background="@android:color/transparent"
+ android:scaleType="fitCenter"
+ android:clickable="false"
+ />
+
+ <com.android.keyguard.AlphaOptimizedImageButton
+ android:id="@+id/car_nav_button_more_icon"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_gravity="center"
+ android:animateLayoutChanges="true"
+ android:src="@drawable/car_ic_arrow"
+ android:background="@android:color/transparent"
+ android:scaleType="fitCenter"
+ android:clickable="false"
+ />
+
+ <ImageView
+ android:id="@+id/car_nav_button_unseen_icon"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_gravity="center"
+ android:src="@drawable/car_ic_unseen_indicator"
+ android:background="@android:color/transparent"
+ android:scaleType="fitCenter"
+ android:clickable="false"
+ />
+
+ </FrameLayout>
</merge>
diff --git a/packages/CarSystemUI/res/values/attrs.xml b/packages/CarSystemUI/res/values/attrs.xml
index 6178738..54026af 100644
--- a/packages/CarSystemUI/res/values/attrs.xml
+++ b/packages/CarSystemUI/res/values/attrs.xml
@@ -16,6 +16,12 @@
-->
<resources>
+ <attr name="icon" format="reference"/>
+ <attr name="selectedIcon" format="reference"/>
+ <attr name="intent" format="string"/>
+ <attr name="longIntent" format="string"/>
+ <attr name="selectedAlpha" format="float" />
+ <attr name="unselectedAlpha" format="float" />
<!-- Custom attributes to configure hvac values -->
<declare-styleable name="AnimatedTemperatureView">
@@ -32,4 +38,67 @@
<attr name="android:minEms"/>
<attr name="android:textAppearance"/>
</declare-styleable>
+
+ <!-- Allow for custom attribs to be added to a nav button -->
+ <declare-styleable name="CarNavigationButton">
+ <!-- intent to start when button is click -->
+ <attr name="intent" />
+ <!-- intent to start when a long press has happened -->
+ <attr name="longIntent" />
+ <!-- start the intent as a broad cast instead of an activity if true-->
+ <attr name="broadcast" format="boolean"/>
+ <!-- Alpha value to used when in selected state. Defaults 1f -->
+ <attr name="selectedAlpha" />
+ <!-- Alpha value to used when in un-selected state. Defaults 0.7f -->
+ <attr name="unselectedAlpha" />
+ <!-- icon to be rendered when in selected state -->
+ <attr name="selectedIcon" />
+ <!-- icon to be rendered (drawable) -->
+ <attr name="icon"/>
+ <!-- categories that will be added as extras to the fired intents -->
+ <attr name="categories" format="string"/>
+ <!-- package names that will be added as extras to the fired intents -->
+ <attr name="packages" format="string" />
+ <!-- componentName names that will be used for detecting selected state -->
+ <attr name="componentNames" format="string" />
+ <!-- whether to highlight the button when selected. Defaults false -->
+ <attr name="showMoreWhenSelected" format="boolean" />
+ <!-- whether to highlight the button when selected. Defaults false -->
+ <attr name="highlightWhenSelected" format="boolean" />
+ </declare-styleable>
+
+ <!-- Custom attributes to configure hvac values -->
+ <declare-styleable name="TemperatureView">
+ <attr name="hvacAreaId" format="integer"/>
+ <attr name="hvacPropertyId" format="integer"/>
+ <attr name="hvacTempFormat" format="string"/>
+ </declare-styleable>
+
+ <declare-styleable name="carVolumeItems"/>
+ <declare-styleable name="carVolumeItems_item">
+ <!-- Align with AudioAttributes.USAGE_* -->
+ <attr name="usage">
+ <enum name="unknown" value="0"/>
+ <enum name="media" value="1"/>
+ <enum name="voice_communication" value="2"/>
+ <enum name="voice_communication_signalling" value="3"/>
+ <enum name="alarm" value="4"/>
+ <enum name="notification" value="5"/>
+ <enum name="notification_ringtone" value="6"/>
+ <enum name="notification_communication_request" value="7"/>
+ <enum name="notification_communication_instant" value="8"/>
+ <enum name="notification_communication_delayed" value="9"/>
+ <enum name="notification_event" value="10"/>
+ <enum name="assistance_accessibility" value="11"/>
+ <enum name="assistance_navigation_guidance" value="12"/>
+ <enum name="assistance_sonification" value="13"/>
+ <enum name="game" value="14"/>
+ <!-- hidden, do not use -->
+ <!-- enum name="virtual_source" value="15"/ -->
+ <enum name="assistant" value="16"/>
+ </attr>
+
+ <!-- Icon resource ids to render on UI -->
+ <attr name="icon" />
+ </declare-styleable>
</resources>
diff --git a/packages/CarSystemUI/res/values/colors.xml b/packages/CarSystemUI/res/values/colors.xml
index 5fcf38fc..7972e09 100644
--- a/packages/CarSystemUI/res/values/colors.xml
+++ b/packages/CarSystemUI/res/values/colors.xml
@@ -43,8 +43,8 @@
<!-- The color of the dividing line between grouped notifications. -->
<color name="notification_divider_color">@*android:color/notification_action_list</color>
- <!-- The color for the unseen notification indicator. -->
- <color name="car_nav_notification_unseen_indicator_color">#e25142</color>
+ <!-- The color for the unseen indicator. -->
+ <color name="car_nav_unseen_indicator_color">#e25142</color>
<!-- The color of the ripples on the untinted notifications -->
<color name="notification_ripple_untinted_color">@color/ripple_material_light</color>
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/AssitantButton.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/AssitantButton.java
index c50de22..98cc00e 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/AssitantButton.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/AssitantButton.java
@@ -30,9 +30,9 @@
/**
* AssitantButton is a ui component that will trigger the Voice Interaction Service.
*/
-public class AssitantButton extends CarFacetButton {
+public class AssitantButton extends CarNavigationButton {
- private static final String TAG = "CarFacetButton";
+ private static final String TAG = "AssistantButton";
private final AssistUtils mAssistUtils;
private IVoiceInteractionSessionShowCallback mShowCallback =
new IVoiceInteractionSessionShowCallback.Stub() {
@@ -62,7 +62,7 @@
}
@Override
- protected void setupIntents(TypedArray typedArray) {
+ protected void setUpIntents(TypedArray typedArray) {
// left blank because for the assistant button Intent will not be passed from the layout.
}
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/ButtonSelectionStateController.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/ButtonSelectionStateController.java
new file mode 100644
index 0000000..c36aaa0
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/ButtonSelectionStateController.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.navigationbar.car;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.view.Display;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * CarNavigationButtons can optionally have selection state that toggles certain visual indications
+ * based on whether the active application on screen is associated with it. This is basically a
+ * similar concept to a radio button group.
+ *
+ * This class controls the selection state of CarNavigationButtons that have opted in to have such
+ * selection state-dependent visual indications.
+ */
+@Singleton
+public class ButtonSelectionStateController {
+
+ private final Set<CarNavigationButton> mRegisteredViews = new HashSet<>();
+
+ protected ButtonMap mButtonsByCategory = new ButtonMap();
+ protected ButtonMap mButtonsByPackage = new ButtonMap();
+ protected ButtonMap mButtonsByComponentName = new ButtonMap();
+ protected HashSet<CarNavigationButton> mSelectedButtons;
+ protected Context mContext;
+
+ @Inject
+ public ButtonSelectionStateController(Context context) {
+ mContext = context;
+ mSelectedButtons = new HashSet<>();
+ }
+
+ /**
+ * Iterate through a view looking for CarNavigationButton and add it to the controller if it
+ * opted in to be highlighted when the active application is associated with it.
+ *
+ * @param v the View that may contain CarFacetButtons
+ */
+ protected void addAllButtonsWithSelectionState(View v) {
+ if (v instanceof CarNavigationButton) {
+ if (((CarNavigationButton) v).hasSelectionState()) {
+ addButtonWithSelectionState((CarNavigationButton) v);
+ }
+ } else if (v instanceof ViewGroup) {
+ ViewGroup viewGroup = (ViewGroup) v;
+ for (int i = 0; i < viewGroup.getChildCount(); i++) {
+ addAllButtonsWithSelectionState(viewGroup.getChildAt(i));
+ }
+ }
+ }
+
+ /** Removes all buttons from the button maps. */
+ protected void removeAll() {
+ mButtonsByCategory.clear();
+ mButtonsByPackage.clear();
+ mButtonsByComponentName.clear();
+ mSelectedButtons.clear();
+ mRegisteredViews.clear();
+ }
+
+ /**
+ * This will unselect the currently selected CarNavigationButton and determine which one should
+ * be selected next. It does this by reading the properties on the CarNavigationButton and
+ * seeing if they are a match with the supplied StackInfo list.
+ * The order of selection detection is ComponentName, PackageName then Category
+ * They will then be compared with the supplied StackInfo list.
+ * The StackInfo is expected to be supplied in order of recency and StackInfo will only be used
+ * for consideration if it has the same displayId as the CarNavigationButton.
+ *
+ * @param stackInfoList of the currently running application
+ * @param validDisplay index of the valid display
+ */
+
+ protected void taskChanged(List<ActivityManager.StackInfo> stackInfoList, int validDisplay) {
+ ActivityManager.StackInfo validStackInfo = null;
+ for (ActivityManager.StackInfo stackInfo : stackInfoList) {
+ // Find the first stack info with a topActivity in the primary display.
+ // TODO: We assume that CarFacetButton will launch an app only in the primary display.
+ // We need to extend the functionality to handle the multiple display properly.
+ if (stackInfo.topActivity != null && stackInfo.displayId == validDisplay) {
+ validStackInfo = stackInfo;
+ break;
+ }
+ }
+
+ if (validStackInfo == null) {
+ // No stack was found that was on the same display as the buttons thus return
+ return;
+ }
+ int displayId = validStackInfo.displayId;
+
+ mSelectedButtons.forEach(carNavigationButton -> {
+ if (carNavigationButton.getDisplayId() == displayId) {
+ carNavigationButton.setSelected(false);
+ }
+ });
+ mSelectedButtons.clear();
+
+ HashSet<CarNavigationButton> selectedButtons = findSelectedButtons(validStackInfo);
+
+ if (selectedButtons != null) {
+ selectedButtons.forEach(carNavigationButton -> {
+ if (carNavigationButton.getDisplayId() == displayId) {
+ carNavigationButton.setSelected(true);
+ mSelectedButtons.add(carNavigationButton);
+ }
+ });
+ }
+ }
+
+ /**
+ * Defaults to Display.DEFAULT_DISPLAY when no parameter is provided for the validDisplay.
+ *
+ * @param stackInfoList
+ */
+ protected void taskChanged(List<ActivityManager.StackInfo> stackInfoList) {
+ taskChanged(stackInfoList, Display.DEFAULT_DISPLAY);
+ }
+
+ /**
+ * Add navigation button to this controller if it uses selection state.
+ */
+ private void addButtonWithSelectionState(CarNavigationButton carNavigationButton) {
+ if (mRegisteredViews.contains(carNavigationButton)) {
+ return;
+ }
+ String[] categories = carNavigationButton.getCategories();
+ for (int i = 0; i < categories.length; i++) {
+ mButtonsByCategory.add(categories[i], carNavigationButton);
+ }
+
+ String[] packages = carNavigationButton.getPackages();
+ for (int i = 0; i < packages.length; i++) {
+ mButtonsByPackage.add(packages[i], carNavigationButton);
+ }
+ String[] componentNames = carNavigationButton.getComponentName();
+ for (int i = 0; i < componentNames.length; i++) {
+ mButtonsByComponentName.add(componentNames[i], carNavigationButton);
+ }
+
+ mRegisteredViews.add(carNavigationButton);
+ }
+
+ private HashSet<CarNavigationButton> findSelectedButtons(
+ ActivityManager.StackInfo validStackInfo) {
+ String packageName = validStackInfo.topActivity.getPackageName();
+
+ HashSet<CarNavigationButton> selectedButtons =
+ findButtonsByComponentName(validStackInfo.topActivity);
+ if (selectedButtons == null) {
+ selectedButtons = mButtonsByPackage.get(packageName);
+ }
+ if (selectedButtons == null) {
+ String category = getPackageCategory(packageName);
+ if (category != null) {
+ selectedButtons = mButtonsByCategory.get(category);
+ }
+ }
+
+ return selectedButtons;
+ }
+
+ private HashSet<CarNavigationButton> findButtonsByComponentName(
+ ComponentName componentName) {
+ HashSet<CarNavigationButton> buttons =
+ mButtonsByComponentName.get(componentName.flattenToShortString());
+ return (buttons != null) ? buttons :
+ mButtonsByComponentName.get(componentName.flattenToString());
+ }
+
+ private String getPackageCategory(String packageName) {
+ PackageManager pm = mContext.getPackageManager();
+ Set<String> supportedCategories = mButtonsByCategory.keySet();
+ for (String category : supportedCategories) {
+ Intent intent = new Intent();
+ intent.setPackage(packageName);
+ intent.setAction(Intent.ACTION_MAIN);
+ intent.addCategory(category);
+ List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
+ if (list.size() > 0) {
+ // Cache this package name into ButtonsByPackage map, so we won't have to query
+ // all categories next time this package name shows up.
+ mButtonsByPackage.put(packageName, mButtonsByCategory.get(category));
+ return category;
+ }
+ }
+ return null;
+ }
+
+ // simple multi-map
+ private static class ButtonMap extends HashMap<String, HashSet<CarNavigationButton>> {
+
+ public boolean add(String key, CarNavigationButton value) {
+ if (containsKey(key)) {
+ return get(key).add(value);
+ }
+ HashSet<CarNavigationButton> set = new HashSet<>();
+ set.add(value);
+ put(key, set);
+ return true;
+ }
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/FacetButtonTaskStackListener.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/ButtonSelectionStateListener.java
similarity index 75%
rename from packages/CarSystemUI/src/com/android/systemui/navigationbar/car/FacetButtonTaskStackListener.java
rename to packages/CarSystemUI/src/com/android/systemui/navigationbar/car/ButtonSelectionStateListener.java
index 4925220..9da4121 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/FacetButtonTaskStackListener.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/ButtonSelectionStateListener.java
@@ -24,28 +24,25 @@
import javax.inject.Inject;
import javax.inject.Singleton;
-import dagger.Lazy;
-
/**
* An implementation of TaskStackChangeListener, that listens for changes in the system
* task stack and notifies the navigation bar.
*/
@Singleton
-class FacetButtonTaskStackListener extends TaskStackChangeListener {
- private static final String TAG = FacetButtonTaskStackListener.class.getSimpleName();
+class ButtonSelectionStateListener extends TaskStackChangeListener {
+ private static final String TAG = ButtonSelectionStateListener.class.getSimpleName();
- private final Lazy<CarFacetButtonController> mFacetButtonControllerLazy;
+ private final ButtonSelectionStateController mButtonSelectionStateController;
@Inject
- FacetButtonTaskStackListener(
- Lazy<CarFacetButtonController> carFacetButtonControllerLazy) {
- mFacetButtonControllerLazy = carFacetButtonControllerLazy;
+ ButtonSelectionStateListener(ButtonSelectionStateController carNavigationButtonController) {
+ mButtonSelectionStateController = carNavigationButtonController;
}
@Override
public void onTaskStackChanged() {
try {
- mFacetButtonControllerLazy.get().taskChanged(
+ mButtonSelectionStateController.taskChanged(
ActivityTaskManager.getService().getAllStackInfos());
} catch (Exception e) {
Log.e(TAG, "Getting StackInfo from activity manager failed", e);
@@ -55,7 +52,7 @@
@Override
public void onTaskDisplayChanged(int taskId, int newDisplayId) {
try {
- mFacetButtonControllerLazy.get().taskChanged(
+ mButtonSelectionStateController.taskChanged(
ActivityTaskManager.getService().getAllStackInfos());
} catch (Exception e) {
Log.e(TAG, "Getting StackInfo from activity manager failed", e);
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarFacetButton.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarFacetButton.java
deleted file mode 100644
index 0b89992..0000000
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarFacetButton.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.navigationbar.car;
-
-import android.app.ActivityOptions;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.TypedArray;
-import android.os.Build;
-import android.os.UserHandle;
-import android.util.AttributeSet;
-import android.view.Display;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import com.android.keyguard.AlphaOptimizedImageButton;
-import com.android.systemui.R;
-
-/**
- * CarFacetButton is a ui component designed to be used as a shortcut for an app of a defined
- * category. It can also render a indicator implying that there are more options of apps to launch
- * using this component. This is done with a "More icon" currently an arrow as defined in the layout
- * file. The class is to serve as an example.
- *
- * New activity will be launched on the same display as the button is on.
- * Usage example: A button that allows a user to select a music app and indicate that there are
- * other music apps installed.
- */
-public class CarFacetButton extends LinearLayout {
- private static final String FACET_FILTER_DELIMITER = ";";
- /**
- * Extra information to be sent to a helper to make the decision of what app to launch when
- * clicked.
- */
- private static final String EXTRA_FACET_CATEGORIES = "categories";
- private static final String EXTRA_FACET_PACKAGES = "packages";
- private static final String EXTRA_FACET_ID = "filter_id";
- private static final String EXTRA_FACET_LAUNCH_PICKER = "launch_picker";
- private static final String TAG = "CarFacetButton";
-
- private Context mContext;
- private AlphaOptimizedImageButton mIcon;
- private AlphaOptimizedImageButton mMoreIcon;
- private boolean mSelected = false;
- private String[] mComponentNames;
- /** App categories that are to be used with this widget */
- private String[] mFacetCategories;
- /** App packages that are allowed to be used with this widget */
- private String[] mFacetPackages;
- private int mIconResourceId;
- /**
- * If defined in the xml this will be the icon that's rendered when the button is marked as
- * selected
- */
- private int mSelectedIconResourceId;
- private boolean mUseMoreIcon = true;
- private float mSelectedAlpha = 1f;
- private float mUnselectedAlpha = 1f;
-
- public CarFacetButton(Context context, AttributeSet attrs) {
- super(context, attrs);
- mContext = context;
- View.inflate(context, R.layout.car_facet_button, this);
- // extract custom attributes
- TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CarFacetButton);
- setupIntents(typedArray);
- setupIcons(typedArray);
- }
-
- /**
- * Reads the custom attributes to setup click handlers for this component.
- */
- protected void setupIntents(TypedArray typedArray) {
- String intentString = typedArray.getString(R.styleable.CarFacetButton_intent);
- String longPressIntentString = typedArray.getString(R.styleable.CarFacetButton_longIntent);
- String categoryString = typedArray.getString(R.styleable.CarFacetButton_categories);
- String packageString = typedArray.getString(R.styleable.CarFacetButton_packages);
- String componentNameString =
- typedArray.getString(R.styleable.CarFacetButton_componentNames);
- try {
- final Intent intent = Intent.parseUri(intentString, Intent.URI_INTENT_SCHEME);
- intent.putExtra(EXTRA_FACET_ID, Integer.toString(getId()));
-
- if (packageString != null) {
- mFacetPackages = packageString.split(FACET_FILTER_DELIMITER);
- intent.putExtra(EXTRA_FACET_PACKAGES, mFacetPackages);
- }
- if (categoryString != null) {
- mFacetCategories = categoryString.split(FACET_FILTER_DELIMITER);
- intent.putExtra(EXTRA_FACET_CATEGORIES, mFacetCategories);
- }
- if (componentNameString != null) {
- mComponentNames = componentNameString.split(FACET_FILTER_DELIMITER);
- }
-
- setOnClickListener(v -> {
- ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchDisplayId(mContext.getDisplayId());
- intent.putExtra(EXTRA_FACET_LAUNCH_PICKER, mSelected);
- mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT);
- mContext.sendBroadcastAsUser(
- new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), UserHandle.CURRENT);
- });
-
- if (longPressIntentString != null && (Build.IS_ENG || Build.IS_USERDEBUG)) {
- final Intent longPressIntent = Intent.parseUri(longPressIntentString,
- Intent.URI_INTENT_SCHEME);
- setOnLongClickListener(v -> {
- ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchDisplayId(mContext.getDisplayId());
- mContext.startActivityAsUser(longPressIntent, options.toBundle(),
- UserHandle.CURRENT);
- mContext.sendBroadcastAsUser(
- new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), UserHandle.CURRENT);
- return true;
- });
- }
- } catch (Exception e) {
- throw new RuntimeException("Failed to attach intent", e);
- }
- }
-
- private void setupIcons(TypedArray styledAttributes) {
- mSelectedAlpha = styledAttributes.getFloat(
- R.styleable.CarFacetButton_selectedAlpha, mSelectedAlpha);
- mUnselectedAlpha = styledAttributes.getFloat(
- R.styleable.CarFacetButton_unselectedAlpha, mUnselectedAlpha);
- mIcon = findViewById(R.id.car_nav_button_icon);
- mIcon.setScaleType(ImageView.ScaleType.CENTER);
- mIcon.setClickable(false);
- mIcon.setAlpha(mUnselectedAlpha);
- mIconResourceId = styledAttributes.getResourceId(R.styleable.CarFacetButton_icon, 0);
- mIcon.setImageResource(mIconResourceId);
- mSelectedIconResourceId = styledAttributes.getResourceId(
- R.styleable.CarFacetButton_selectedIcon, mIconResourceId);
-
- mMoreIcon = findViewById(R.id.car_nav_button_more_icon);
- mMoreIcon.setClickable(false);
- mMoreIcon.setAlpha(mSelectedAlpha);
- mMoreIcon.setVisibility(GONE);
- mUseMoreIcon = styledAttributes.getBoolean(R.styleable.CarFacetButton_useMoreIcon, true);
- }
-
- /**
- * @return The app categories the component represents
- */
- public String[] getCategories() {
- if (mFacetCategories == null) {
- return new String[0];
- }
- return mFacetCategories;
- }
-
- /**
- * @return The valid packages that should be considered.
- */
- public String[] getFacetPackages() {
- if (mFacetPackages == null) {
- return new String[0];
- }
- return mFacetPackages;
- }
-
- /**
- * @return The list of component names.
- */
- public String[] getComponentName() {
- if (mComponentNames == null) {
- return new String[0];
- }
- return mComponentNames;
- }
-
- /**
- * Updates the alpha of the icons to "selected" and shows the "More icon"
- *
- * @param selected true if the view must be selected, false otherwise
- */
- public void setSelected(boolean selected) {
- super.setSelected(selected);
- setSelected(selected, selected);
- }
-
- /**
- * Updates the visual state to let the user know if it's been selected.
- *
- * @param selected true if should update the alpha of the icon to selected, false otherwise
- * @param showMoreIcon true if the "more icon" should be shown, false otherwise. Note this
- * is ignored if the attribute useMoreIcon is set to false
- */
- public void setSelected(boolean selected, boolean showMoreIcon) {
- mSelected = selected;
- mIcon.setAlpha(mSelected ? mSelectedAlpha : mUnselectedAlpha);
- mIcon.setImageResource(mSelected ? mSelectedIconResourceId : mIconResourceId);
- if (mUseMoreIcon) {
- mMoreIcon.setVisibility(showMoreIcon ? VISIBLE : GONE);
- }
- }
-
- /**
- * @return The id of the display the button is on or Display.INVALID_DISPLAY if it's not yet on
- * a display.
- */
- public int getDisplayId() {
- Display display = getDisplay();
- if (display == null) {
- return Display.INVALID_DISPLAY;
- }
- return display.getDisplayId();
- }
-}
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarFacetButtonController.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarFacetButtonController.java
deleted file mode 100644
index f66e828..0000000
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarFacetButtonController.java
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.navigationbar.car;
-
-import android.app.ActivityManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.view.Display;
-import android.view.View;
-import android.view.ViewGroup;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * CarFacetButtons placed on the nav bar are designed to have visual indication that the active
- * application on screen is associated with it. This is basically a similar concept to a radio
- * button group.
- */
-@Singleton
-public class CarFacetButtonController {
-
- private final Set<CarFacetButton> mRegisteredViews = new HashSet<>();
-
- protected ButtonMap mButtonsByCategory = new ButtonMap();
- protected ButtonMap mButtonsByPackage = new ButtonMap();
- protected ButtonMap mButtonsByComponentName = new ButtonMap();
- protected HashSet<CarFacetButton> mSelectedFacetButtons;
- protected Context mContext;
-
- @Inject
- public CarFacetButtonController(Context context) {
- mContext = context;
- mSelectedFacetButtons = new HashSet<>();
- }
-
- /**
- * Add facet button to this controller. The expected use is for the facet button
- * to get a reference to this controller via {@link com.android.systemui.Dependency}
- * and self add.
- */
- private void addFacetButton(CarFacetButton facetButton) {
- if (mRegisteredViews.contains(facetButton)) {
- return;
- }
-
- String[] categories = facetButton.getCategories();
- for (int i = 0; i < categories.length; i++) {
- mButtonsByCategory.add(categories[i], facetButton);
- }
-
- String[] facetPackages = facetButton.getFacetPackages();
- for (int i = 0; i < facetPackages.length; i++) {
- mButtonsByPackage.add(facetPackages[i], facetButton);
- }
- String[] componentNames = facetButton.getComponentName();
- for (int i = 0; i < componentNames.length; i++) {
- mButtonsByComponentName.add(componentNames[i], facetButton);
- }
-
- mRegisteredViews.add(facetButton);
- }
-
- /** Removes all buttons from the button maps. */
- public void removeAll() {
- mButtonsByCategory.clear();
- mButtonsByPackage.clear();
- mButtonsByComponentName.clear();
- mSelectedFacetButtons.clear();
- mRegisteredViews.clear();
- }
-
- /**
- * Iterate through a view looking for CarFacetButtons and adding them to the controller if found
- *
- * @param v the View that may contain CarFacetButtons
- */
- public void addAllFacetButtons(View v) {
- if (v instanceof CarFacetButton) {
- addFacetButton((CarFacetButton) v);
- } else if (v instanceof ViewGroup) {
- ViewGroup viewGroup = (ViewGroup) v;
- for (int i = 0; i < viewGroup.getChildCount(); i++) {
- addAllFacetButtons(viewGroup.getChildAt(i));
- }
- }
- }
-
- /**
- * This will unselect the currently selected CarFacetButton and determine which one should be
- * selected next. It does this by reading the properties on the CarFacetButton and seeing if
- * they are a match with the supplied StackInfo list.
- * The order of selection detection is ComponentName, PackageName then Category
- * They will then be compared with the supplied StackInfo list.
- * The StackInfo is expected to be supplied in order of recency and StackInfo will only be used
- * for consideration if it has the same displayId as the CarFacetButtons.
- *
- * @param stackInfoList of the currently running application
- */
- public void taskChanged(List<ActivityManager.StackInfo> stackInfoList) {
- ActivityManager.StackInfo validStackInfo = null;
- for (ActivityManager.StackInfo stackInfo : stackInfoList) {
- // Find the first stack info with a topActivity in the primary display.
- // TODO: We assume that CarFacetButton will launch an app only in the primary display.
- // We need to extend the functionality to handle the mutliple display properly.
- if (stackInfo.topActivity != null && stackInfo.displayId == Display.DEFAULT_DISPLAY) {
- validStackInfo = stackInfo;
- break;
- }
- }
-
- if (validStackInfo == null) {
- // No stack was found that was on the same display as the facet buttons thus return
- return;
- }
-
- if (mSelectedFacetButtons != null) {
- Iterator<CarFacetButton> iterator = mSelectedFacetButtons.iterator();
- while (iterator.hasNext()) {
- CarFacetButton carFacetButton = iterator.next();
- if (carFacetButton.getDisplayId() == validStackInfo.displayId) {
- carFacetButton.setSelected(false);
- iterator.remove();
- }
- }
- }
-
- String packageName = validStackInfo.topActivity.getPackageName();
- HashSet<CarFacetButton> facetButton =
- findFacetButtonByComponentName(validStackInfo.topActivity);
- if (facetButton == null) {
- facetButton = mButtonsByPackage.get(packageName);
- }
-
- if (facetButton == null) {
- String category = getPackageCategory(packageName);
- if (category != null) {
- facetButton = mButtonsByCategory.get(category);
- }
- }
-
- if (facetButton != null) {
- for (CarFacetButton carFacetButton : facetButton) {
- if (carFacetButton.getDisplayId() == validStackInfo.displayId) {
- carFacetButton.setSelected(true);
- mSelectedFacetButtons.add(carFacetButton);
- }
- }
- }
-
- }
-
- private HashSet<CarFacetButton> findFacetButtonByComponentName(ComponentName componentName) {
- HashSet<CarFacetButton> buttons =
- mButtonsByComponentName.get(componentName.flattenToShortString());
- return (buttons != null) ? buttons :
- mButtonsByComponentName.get(componentName.flattenToString());
- }
-
- protected String getPackageCategory(String packageName) {
- PackageManager pm = mContext.getPackageManager();
- Set<String> supportedCategories = mButtonsByCategory.keySet();
- for (String category : supportedCategories) {
- Intent intent = new Intent();
- intent.setPackage(packageName);
- intent.setAction(Intent.ACTION_MAIN);
- intent.addCategory(category);
- List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
- if (list.size() > 0) {
- // Cache this package name into facetPackageMap, so we won't have to query
- // all categories next time this package name shows up.
- mButtonsByPackage.put(packageName, mButtonsByCategory.get(category));
- return category;
- }
- }
- return null;
- }
-
- // simple multi-map
- private static class ButtonMap extends HashMap<String, HashSet<CarFacetButton>> {
-
- public boolean add(String key, CarFacetButton value) {
- if (containsKey(key)) {
- return get(key).add(value);
- }
- HashSet<CarFacetButton> set = new HashSet<>();
- set.add(value);
- put(key, set);
- return true;
- }
- }
-}
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
index 59a084e..d8c9d17 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
@@ -57,12 +57,12 @@
private final WindowManager mWindowManager;
private final CarDeviceProvisionedController mCarDeviceProvisionedController;
private final CommandQueue mCommandQueue;
- private final Lazy<FacetButtonTaskStackListener> mFacetButtonTaskStackListenerLazy;
+ private final ButtonSelectionStateListener mButtonSelectionStateListener;
private final Handler mMainHandler;
private final Lazy<KeyguardStateController> mKeyguardStateControllerLazy;
private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
private final SuperStatusBarViewFactory mSuperStatusBarViewFactory;
- private final Lazy<CarFacetButtonController> mCarFacetButtonControllerLazy;
+ private final ButtonSelectionStateController mButtonSelectionStateController;
private IStatusBarService mBarService;
private ActivityManagerWrapper mActivityManagerWrapper;
@@ -92,24 +92,24 @@
WindowManager windowManager,
DeviceProvisionedController deviceProvisionedController,
CommandQueue commandQueue,
- Lazy<FacetButtonTaskStackListener> facetButtonTaskStackListenerLazy,
+ ButtonSelectionStateListener buttonSelectionStateListener,
@Main Handler mainHandler,
Lazy<KeyguardStateController> keyguardStateControllerLazy,
Lazy<NavigationBarController> navigationBarControllerLazy,
SuperStatusBarViewFactory superStatusBarViewFactory,
- Lazy<CarFacetButtonController> carFacetButtonControllerLazy) {
+ ButtonSelectionStateController buttonSelectionStateController) {
super(context);
mCarNavigationBarController = carNavigationBarController;
mWindowManager = windowManager;
mCarDeviceProvisionedController = (CarDeviceProvisionedController)
deviceProvisionedController;
mCommandQueue = commandQueue;
- mFacetButtonTaskStackListenerLazy = facetButtonTaskStackListenerLazy;
+ mButtonSelectionStateListener = buttonSelectionStateListener;
mMainHandler = mainHandler;
mKeyguardStateControllerLazy = keyguardStateControllerLazy;
mNavigationBarControllerLazy = navigationBarControllerLazy;
mSuperStatusBarViewFactory = superStatusBarViewFactory;
- mCarFacetButtonControllerLazy = carFacetButtonControllerLazy;
+ mButtonSelectionStateController = buttonSelectionStateController;
}
@Override
@@ -156,7 +156,7 @@
createNavigationBar(result);
mActivityManagerWrapper = ActivityManagerWrapper.getInstance();
- mActivityManagerWrapper.registerTaskStackListener(mFacetButtonTaskStackListenerLazy.get());
+ mActivityManagerWrapper.registerTaskStackListener(mButtonSelectionStateListener);
mCarNavigationBarController.connectToHvac();
}
@@ -181,7 +181,7 @@
// remove and reattach all hvac components such that we don't keep a reference to unused
// ui elements
mCarNavigationBarController.removeAllFromHvac();
- mCarFacetButtonControllerLazy.get().removeAll();
+ mButtonSelectionStateController.removeAll();
if (mTopNavigationBarWindow != null) {
mTopNavigationBarWindow.removeAllViews();
@@ -343,7 +343,7 @@
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.print(" mTaskStackListener=");
- pw.println(mFacetButtonTaskStackListenerLazy.get());
+ pw.println(mButtonSelectionStateListener);
pw.print(" mBottomNavigationBarView=");
pw.println(mBottomNavigationBarView);
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java
index 6f28843..a56c4ed 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java
@@ -37,7 +37,7 @@
private final Context mContext;
private final NavigationBarViewFactory mNavigationBarViewFactory;
- private final Lazy<CarFacetButtonController> mCarFacetButtonControllerLazy;
+ private final ButtonSelectionStateController mButtonSelectionStateController;
private final Lazy<HvacController> mHvacControllerLazy;
private boolean mShowBottom;
@@ -58,11 +58,11 @@
@Inject
public CarNavigationBarController(Context context,
NavigationBarViewFactory navigationBarViewFactory,
- Lazy<CarFacetButtonController> carFacetButtonControllerLazy,
+ ButtonSelectionStateController buttonSelectionStateController,
Lazy<HvacController> hvacControllerLazy) {
mContext = context;
mNavigationBarViewFactory = navigationBarViewFactory;
- mCarFacetButtonControllerLazy = carFacetButtonControllerLazy;
+ mButtonSelectionStateController = buttonSelectionStateController;
mHvacControllerLazy = hvacControllerLazy;
// Read configuration.
@@ -175,7 +175,7 @@
NotificationsShadeController notifShadeController) {
view.setStatusBarWindowTouchListener(statusBarTouchListener);
view.setNotificationsPanelController(notifShadeController);
- mCarFacetButtonControllerLazy.get().addAllFacetButtons(view);
+ mButtonSelectionStateController.addAllButtonsWithSelectionState(view);
mHvacControllerLazy.get().addTemperatureViewToController(view);
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationButton.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationButton.java
index 922bfff..b4d4785 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationButton.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationButton.java
@@ -24,8 +24,12 @@
import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.Display;
+import android.view.View;
import android.widget.ImageView;
+import android.widget.LinearLayout;
+import com.android.keyguard.AlphaOptimizedImageButton;
import com.android.systemui.R;
import java.net.URISyntaxException;
@@ -35,110 +39,64 @@
* xml file level. This allows for more control via overlays instead of having to update
* code.
*/
-public class CarNavigationButton extends com.android.keyguard.AlphaOptimizedImageButton {
- private static final String TAG = "CarNavigationButton";
+public class CarNavigationButton extends LinearLayout {
- private static final int UNSEEN_ICON_RESOURCE_ID = R.drawable.car_ic_notification_unseen;
- private static final int UNSEEN_SELECTED_ICON_RESOURCE_ID =
- R.drawable.car_ic_notification_selected_unseen;
+ protected static final float DEFAULT_SELECTED_ALPHA = 1f;
+ protected static final float DEFAULT_UNSELECTED_ALPHA = 0.75f;
+
+ private static final String TAG = "CarNavigationButton";
+ private static final String BUTTON_FILTER_DELIMITER = ";";
+ private static final String EXTRA_BUTTON_CATEGORIES = "categories";
+ private static final String EXTRA_BUTTON_PACKAGES = "packages";
private Context mContext;
+ private AlphaOptimizedImageButton mIcon;
+ private AlphaOptimizedImageButton mMoreIcon;
+ private ImageView mUnseenIcon;
private String mIntent;
private String mLongIntent;
private boolean mBroadcastIntent;
private boolean mHasUnseen = false;
private boolean mSelected = false;
- private float mSelectedAlpha = 1f;
- private float mUnselectedAlpha = 1f;
+ private float mSelectedAlpha;
+ private float mUnselectedAlpha;
private int mSelectedIconResourceId;
private int mIconResourceId;
-
+ private String[] mComponentNames;
+ /** App categories that are to be used with this widget */
+ private String[] mButtonCategories;
+ /** App packages that are allowed to be used with this widget */
+ private String[] mButtonPackages;
+ /** Whether to display more icon beneath the primary icon when the button is selected */
+ private boolean mShowMoreWhenSelected = false;
+ /** Whether to highlight the button if the active application is associated with it */
+ private boolean mHighlightWhenSelected = false;
public CarNavigationButton(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
-
+ View.inflate(mContext, R.layout.car_navigation_button, /* root= */ this);
// CarNavigationButton attrs
- TypedArray typedArray = context.obtainStyledAttributes(
- attrs, R.styleable.CarNavigationButton);
- mIntent = typedArray.getString(R.styleable.CarNavigationButton_intent);
- mLongIntent = typedArray.getString(R.styleable.CarNavigationButton_longIntent);
- mBroadcastIntent = typedArray.getBoolean(R.styleable.CarNavigationButton_broadcast, false);
- mSelectedAlpha = typedArray.getFloat(
- R.styleable.CarNavigationButton_selectedAlpha, mSelectedAlpha);
- mUnselectedAlpha = typedArray.getFloat(
- R.styleable.CarNavigationButton_unselectedAlpha, mUnselectedAlpha);
- mSelectedIconResourceId = typedArray.getResourceId(
- R.styleable.CarNavigationButton_selectedIcon, mIconResourceId);
- mIconResourceId = typedArray.getResourceId(
- R.styleable.CarNavigationButton_icon, 0);
+ TypedArray typedArray = context.obtainStyledAttributes(attrs,
+ R.styleable.CarNavigationButton);
+
+ setUpIntents(typedArray);
+ setUpIcons(typedArray);
typedArray.recycle();
}
-
- /**
- * After the standard inflate this then adds the xml defined intents to click and long click
- * actions if defined.
- */
- @Override
- public void onFinishInflate() {
- super.onFinishInflate();
- setScaleType(ImageView.ScaleType.CENTER);
- setAlpha(mUnselectedAlpha);
- setImageResource(mIconResourceId);
- try {
- if (mIntent != null) {
- final Intent intent = Intent.parseUri(mIntent, Intent.URI_INTENT_SCHEME);
- setOnClickListener(v -> {
- try {
- if (mBroadcastIntent) {
- mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
- mContext.sendBroadcastAsUser(
- new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
- UserHandle.CURRENT);
- return;
- }
- ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchDisplayId(mContext.getDisplayId());
- mContext.startActivityAsUser(intent, options.toBundle(),
- UserHandle.CURRENT);
- } catch (Exception e) {
- Log.e(TAG, "Failed to launch intent", e);
- }
- });
- }
- } catch (URISyntaxException e) {
- throw new RuntimeException("Failed to attach intent", e);
- }
-
- try {
- if (mLongIntent != null && (Build.IS_ENG || Build.IS_USERDEBUG)) {
- final Intent intent = Intent.parseUri(mLongIntent, Intent.URI_INTENT_SCHEME);
- setOnLongClickListener(v -> {
- try {
- ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchDisplayId(mContext.getDisplayId());
- mContext.startActivityAsUser(intent, options.toBundle(),
- UserHandle.CURRENT);
- } catch (Exception e) {
- Log.e(TAG, "Failed to launch intent", e);
- }
- // consume event either way
- return true;
- });
- }
- } catch (URISyntaxException e) {
- throw new RuntimeException("Failed to attach long press intent", e);
- }
- }
-
/**
* @param selected true if should indicate if this is a selected state, false otherwise
*/
public void setSelected(boolean selected) {
super.setSelected(selected);
mSelected = selected;
- setAlpha(mSelected ? mSelectedAlpha : mUnselectedAlpha);
+ if (mHighlightWhenSelected) {
+ setAlpha(mSelected ? mSelectedAlpha : mUnselectedAlpha);
+ }
+ if (mShowMoreWhenSelected && mMoreIcon != null) {
+ mMoreIcon.setVisibility(selected ? VISIBLE : GONE);
+ }
updateImage();
}
@@ -155,12 +113,172 @@
return mHasUnseen;
}
- private void updateImage() {
- if (mHasUnseen) {
- setImageResource(mSelected ? UNSEEN_SELECTED_ICON_RESOURCE_ID
- : UNSEEN_ICON_RESOURCE_ID);
- } else {
- setImageResource(mSelected ? mSelectedIconResourceId : mIconResourceId);
+ /**
+ * @return The app categories the component represents
+ */
+ public String[] getCategories() {
+ if (mButtonCategories == null) {
+ return new String[0];
+ }
+ return mButtonCategories;
+ }
+
+ /**
+ * @return The valid packages that should be considered.
+ */
+ public String[] getPackages() {
+ if (mButtonPackages == null) {
+ return new String[0];
+ }
+ return mButtonPackages;
+ }
+
+ /**
+ * @return The list of component names.
+ */
+ public String[] getComponentName() {
+ if (mComponentNames == null) {
+ return new String[0];
+ }
+ return mComponentNames;
+ }
+
+ /**
+ * @return The id of the display the button is on or Display.INVALID_DISPLAY if it's not yet on
+ * a display.
+ */
+ protected int getDisplayId() {
+ Display display = getDisplay();
+ if (display == null) {
+ return Display.INVALID_DISPLAY;
+ }
+ return display.getDisplayId();
+ }
+
+ protected boolean hasSelectionState() {
+ return mHighlightWhenSelected || mShowMoreWhenSelected;
+ }
+
+ /**
+ * Sets up intents for click, long touch, and broadcast.
+ */
+ protected void setUpIntents(TypedArray typedArray) {
+ mIntent = typedArray.getString(R.styleable.CarNavigationButton_intent);
+ mLongIntent = typedArray.getString(R.styleable.CarNavigationButton_longIntent);
+ mBroadcastIntent = typedArray.getBoolean(R.styleable.CarNavigationButton_broadcast, false);
+
+ String categoryString = typedArray.getString(R.styleable.CarNavigationButton_categories);
+ String packageString = typedArray.getString(R.styleable.CarNavigationButton_packages);
+ String componentNameString =
+ typedArray.getString(R.styleable.CarNavigationButton_componentNames);
+
+ try {
+ if (mIntent != null) {
+ final Intent intent = Intent.parseUri(mIntent, Intent.URI_INTENT_SCHEME);
+ setOnClickListener(getButtonClickListener(intent));
+ if (packageString != null) {
+ mButtonPackages = packageString.split(BUTTON_FILTER_DELIMITER);
+ intent.putExtra(EXTRA_BUTTON_PACKAGES, mButtonPackages);
+ }
+ if (categoryString != null) {
+ mButtonCategories = categoryString.split(BUTTON_FILTER_DELIMITER);
+ intent.putExtra(EXTRA_BUTTON_CATEGORIES, mButtonCategories);
+ }
+ if (componentNameString != null) {
+ mComponentNames = componentNameString.split(BUTTON_FILTER_DELIMITER);
+ }
+ }
+ } catch (URISyntaxException e) {
+ throw new RuntimeException("Failed to attach intent", e);
+ }
+
+ try {
+ if (mLongIntent != null && (Build.IS_ENG || Build.IS_USERDEBUG)) {
+ final Intent intent = Intent.parseUri(mLongIntent, Intent.URI_INTENT_SCHEME);
+ setOnLongClickListener(getButtonLongClickListener(intent));
+ }
+ } catch (URISyntaxException e) {
+ throw new RuntimeException("Failed to attach long press intent", e);
}
}
+
+ /** Defines the behavior of a button click. */
+ protected OnClickListener getButtonClickListener(Intent toSend) {
+ return v -> {
+ mContext.sendBroadcastAsUser(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
+ UserHandle.CURRENT);
+ try {
+ if (mBroadcastIntent) {
+ mContext.sendBroadcastAsUser(toSend, UserHandle.CURRENT);
+ return;
+ }
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(mContext.getDisplayId());
+ mContext.startActivityAsUser(toSend, options.toBundle(),
+ UserHandle.CURRENT);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to launch intent", e);
+ }
+ };
+ }
+
+ /** Defines the behavior of a long click. */
+ protected OnLongClickListener getButtonLongClickListener(Intent toSend) {
+ return v -> {
+ mContext.sendBroadcastAsUser(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
+ UserHandle.CURRENT);
+ try {
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(mContext.getDisplayId());
+ mContext.startActivityAsUser(toSend, options.toBundle(),
+ UserHandle.CURRENT);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to launch intent", e);
+ }
+ // consume event either way
+ return true;
+ };
+ }
+
+
+ /**
+ * Initializes view-related aspects of the button.
+ */
+ private void setUpIcons(TypedArray typedArray) {
+ mSelectedAlpha = typedArray.getFloat(
+ R.styleable.CarNavigationButton_selectedAlpha, DEFAULT_SELECTED_ALPHA);
+ mUnselectedAlpha = typedArray.getFloat(
+ R.styleable.CarNavigationButton_unselectedAlpha, DEFAULT_UNSELECTED_ALPHA);
+ mHighlightWhenSelected = typedArray.getBoolean(
+ R.styleable.CarNavigationButton_highlightWhenSelected,
+ mHighlightWhenSelected);
+ mShowMoreWhenSelected = typedArray.getBoolean(
+ R.styleable.CarNavigationButton_showMoreWhenSelected,
+ mShowMoreWhenSelected);
+
+ mSelectedIconResourceId = typedArray.getResourceId(
+ R.styleable.CarNavigationButton_selectedIcon, mIconResourceId);
+ mIconResourceId = typedArray.getResourceId(
+ R.styleable.CarNavigationButton_icon, 0);
+
+ mIcon = findViewById(R.id.car_nav_button_icon_image);
+ mIcon.setScaleType(ImageView.ScaleType.CENTER);
+ // Always apply selected alpha if the button does not toggle alpha based on selection state.
+ mIcon.setAlpha(mHighlightWhenSelected ? mUnselectedAlpha : mSelectedAlpha);
+ mIcon.setImageResource(mIconResourceId);
+
+ mMoreIcon = findViewById(R.id.car_nav_button_more_icon);
+ mMoreIcon.setAlpha(mSelectedAlpha);
+ mMoreIcon.setVisibility(GONE);
+
+ mUnseenIcon = findViewById(R.id.car_nav_button_unseen_icon);
+
+ mUnseenIcon.setVisibility(mHasUnseen ? VISIBLE : GONE);
+ }
+
+ private void updateImage() {
+ mIcon.setImageResource(mSelected ? mSelectedIconResourceId : mIconResourceId);
+ mUnseenIcon.setVisibility(mHasUnseen ? VISIBLE : GONE);
+ }
+
}
diff --git a/packages/CarSystemUI/tests/res/layout/car_button_selection_state_controller_test.xml b/packages/CarSystemUI/tests/res/layout/car_button_selection_state_controller_test.xml
new file mode 100644
index 0000000..f0e0216
--- /dev/null
+++ b/packages/CarSystemUI/tests/res/layout/car_button_selection_state_controller_test.xml
@@ -0,0 +1,55 @@
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@id/nav_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingStart="20dp"
+ android:paddingEnd="20dp"
+ android:gravity="center">
+
+ <com.android.systemui.navigationbar.car.CarNavigationButton
+ android:id="@+id/detectable_by_component_name"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
+ systemui:icon="@drawable/car_ic_overview"
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+ systemui:highlightWhenSelected="true"
+ />
+
+ <com.android.systemui.navigationbar.car.CarNavigationButton
+ android:id="@+id/detectable_by_category"
+ style="@style/NavigationBarButton"
+ systemui:categories="android.intent.category.APP_MAPS"
+ systemui:icon="@drawable/car_ic_navigation"
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MAPS;launchFlags=0x14000000;end"
+ systemui:highlightWhenSelected="true"
+ />
+
+ <com.android.systemui.navigationbar.car.CarNavigationButton
+ android:id="@+id/detectable_by_package"
+ style="@style/NavigationBarButton"
+ systemui:icon="@drawable/car_ic_phone"
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;package=com.android.car.dialer;launchFlags=0x10000000;end"
+ systemui:packages="com.android.car.dialer"
+ systemui:highlightWhenSelected="true"
+ />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/CarSystemUI/tests/res/layout/car_navigation_button_test.xml b/packages/CarSystemUI/tests/res/layout/car_navigation_button_test.xml
new file mode 100644
index 0000000..576928c
--- /dev/null
+++ b/packages/CarSystemUI/tests/res/layout/car_navigation_button_test.xml
@@ -0,0 +1,115 @@
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@id/nav_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingStart="20dp"
+ android:paddingEnd="20dp"
+ android:gravity="center">
+
+ <com.android.systemui.navigationbar.car.CarNavigationButton
+ android:id="@+id/default_no_selection_state"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
+ systemui:icon="@drawable/car_ic_overview"
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+ systemui:selectedIcon="@drawable/car_ic_overview_selected"
+ />
+
+ <com.android.systemui.navigationbar.car.CarNavigationButton
+ android:id="@+id/app_grid_activity"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.carlauncher/.AppGridActivity"
+ systemui:icon="@drawable/car_ic_apps"
+ systemui:intent="intent:#Intent;component=com.android.car.carlauncher/.AppGridActivity;launchFlags=0x24000000;end"
+ systemui:selectedIcon="@drawable/car_ic_apps_selected"
+ systemui:highlightWhenSelected="true"
+ />
+
+ <com.android.systemui.navigationbar.car.CarNavigationButton
+ android:id="@+id/long_click_app_grid_activity"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.carlauncher/.AppGridActivity"
+ systemui:icon="@drawable/car_ic_apps"
+ systemui:longIntent="intent:#Intent;component=com.android.car.carlauncher/.AppGridActivity;launchFlags=0x24000000;end"
+ systemui:selectedIcon="@drawable/car_ic_apps_selected"
+ systemui:highlightWhenSelected="true"
+ />
+
+ <com.android.systemui.navigationbar.car.CarNavigationButton
+ android:id="@+id/broadcast"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@null"
+ systemui:broadcast="true"
+ systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
+ />
+
+ <com.android.systemui.navigationbar.car.CarNavigationButton
+ android:id="@+id/selected_icon_undefined"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
+ systemui:icon="@drawable/car_ic_overview"
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+ />
+
+ <com.android.systemui.navigationbar.car.CarNavigationButton
+ android:id="@+id/highlightable_no_more_button"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
+ systemui:icon="@drawable/car_ic_overview"
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+ systemui:selectedIcon="@drawable/car_ic_overview_selected"
+ systemui:highlightWhenSelected="true"
+ />
+
+ <com.android.systemui.navigationbar.car.CarNavigationButton
+ android:id="@+id/not_highlightable_more_button"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
+ systemui:icon="@drawable/car_ic_overview"
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+ systemui:selectedIcon="@drawable/car_ic_overview_selected"
+ systemui:showMoreWhenSelected="true"
+ />
+
+ <com.android.systemui.navigationbar.car.CarNavigationButton
+ android:id="@+id/highlightable_more_button"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
+ systemui:icon="@drawable/car_ic_overview"
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+ systemui:selectedIcon="@drawable/car_ic_overview_selected"
+ systemui:highlightWhenSelected="true"
+ systemui:showMoreWhenSelected="true"
+ />
+
+ <com.android.systemui.navigationbar.car.CarNavigationButton
+ android:id="@+id/broadcast"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
+ systemui:icon="@drawable/car_ic_overview"
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+ systemui:selectedIcon="@drawable/car_ic_overview_selected"
+ systemui:broadcast="true"
+ />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/ButtonSelectionStateControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/ButtonSelectionStateControllerTest.java
new file mode 100644
index 0000000..f94dd82
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/ButtonSelectionStateControllerTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.navigationbar.car;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.LayoutInflater;
+import android.widget.LinearLayout;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.tests.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class ButtonSelectionStateControllerTest extends SysuiTestCase {
+
+ private static final String TEST_COMPONENT_NAME_PACKAGE = "com.android.car.carlauncher";
+ private static final String TEST_COMPONENT_NAME_CLASS = ".CarLauncher";
+ private static final String TEST_CATEGORY = "com.google.android.apps.maps";
+ private static final String TEST_CATEGORY_CLASS = ".APP_MAPS";
+ private static final String TEST_PACKAGE = "com.android.car.dialer";
+ private static final String TEST_PACKAGE_CLASS = ".Dialer";
+
+ // LinearLayout with CarNavigationButtons with different configurations.
+ private LinearLayout mTestView;
+ private ButtonSelectionStateController mButtonSelectionStateController;
+ private ComponentName mComponentName;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mTestView = (LinearLayout) LayoutInflater.from(mContext).inflate(
+ R.layout.car_button_selection_state_controller_test, /* root= */ null);
+ mButtonSelectionStateController = new ButtonSelectionStateController(mContext);
+ mButtonSelectionStateController.addAllButtonsWithSelectionState(mTestView);
+ }
+
+ @Test
+ public void onTaskChanged_buttonDetectableByComponentName_selectsAssociatedButton() {
+ CarNavigationButton testButton = mTestView.findViewById(R.id.detectable_by_component_name);
+ mComponentName = new ComponentName(TEST_COMPONENT_NAME_PACKAGE, TEST_COMPONENT_NAME_CLASS);
+ List<ActivityManager.StackInfo> testStack = createTestStack(mComponentName);
+ testButton.setSelected(false);
+ mButtonSelectionStateController.taskChanged(testStack, /* validDisplay= */ -1);
+
+ assertbuttonSelected(testButton);
+ }
+
+ @Test
+ public void onTaskChanged_buttonDetectableByCategory_selectsAssociatedButton() {
+ CarNavigationButton testButton = mTestView.findViewById(R.id.detectable_by_category);
+ mComponentName = new ComponentName(TEST_CATEGORY, TEST_CATEGORY_CLASS);
+ List<ActivityManager.StackInfo> testStack = createTestStack(mComponentName);
+ testButton.setSelected(false);
+ mButtonSelectionStateController.taskChanged(testStack, /* validDisplay= */ -1);
+
+ assertbuttonSelected(testButton);
+ }
+
+ @Test
+ public void onTaskChanged_buttonDetectableByPackage_selectsAssociatedButton() {
+ CarNavigationButton testButton = mTestView.findViewById(R.id.detectable_by_package);
+ mComponentName = new ComponentName(TEST_PACKAGE, TEST_PACKAGE_CLASS);
+ List<ActivityManager.StackInfo> testStack = createTestStack(mComponentName);
+ testButton.setSelected(false);
+ mButtonSelectionStateController.taskChanged(testStack, /* validDisplay= */ -1);
+
+ assertbuttonSelected(testButton);
+ }
+
+ @Test
+ public void onTaskChanged_deselectsPreviouslySelectedButton() {
+ CarNavigationButton oldButton = mTestView.findViewById(R.id.detectable_by_component_name);
+ mComponentName = new ComponentName(TEST_COMPONENT_NAME_PACKAGE, TEST_COMPONENT_NAME_CLASS);
+ List<ActivityManager.StackInfo> oldStack = createTestStack(mComponentName);
+ oldButton.setSelected(false);
+ mButtonSelectionStateController.taskChanged(oldStack, /* validDisplay= */ -1);
+
+ mComponentName = new ComponentName(TEST_PACKAGE, TEST_PACKAGE_CLASS);
+ List<ActivityManager.StackInfo> newStack = createTestStack(mComponentName);
+ mButtonSelectionStateController.taskChanged(newStack, /* validDisplay= */ -1);
+
+ assertButtonUnselected(oldButton);
+ }
+
+ // Comparing alpha is a valid way to verify button selection state because all test buttons use
+ // highlightWhenSelected = true.
+ private void assertbuttonSelected(CarNavigationButton button) {
+ assertThat(button.getAlpha()).isEqualTo(CarNavigationButton.DEFAULT_SELECTED_ALPHA);
+ }
+
+ private void assertButtonUnselected(CarNavigationButton button) {
+ assertThat(button.getAlpha()).isEqualTo(CarNavigationButton.DEFAULT_UNSELECTED_ALPHA);
+ }
+
+ private List<ActivityManager.StackInfo> createTestStack(ComponentName componentName) {
+ ActivityManager.StackInfo validStackInfo = new ActivityManager.StackInfo();
+ validStackInfo.displayId = -1; // No display is assigned to this test view
+ validStackInfo.topActivity = componentName;
+
+ List<ActivityManager.StackInfo> testStack = new ArrayList<>();
+ testStack.add(validStackInfo);
+
+ return testStack;
+ }
+}
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java
index 642b114..e0c13ed 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java
@@ -51,7 +51,7 @@
private TestableResources mTestableResources;
@Mock
- private CarFacetButtonController mCarFacetButtonController;
+ private ButtonSelectionStateController mButtonSelectionStateController;
@Mock
private HvacController mHvacController;
@@ -69,7 +69,7 @@
@Test
public void testConnectToHvac_callsConnect() {
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
mCarNavigationBar.connectToHvac();
@@ -79,7 +79,7 @@
@Test
public void testRemoveAllFromHvac_callsRemoveAll() {
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
mCarNavigationBar.removeAllFromHvac();
@@ -90,7 +90,7 @@
public void testGetBottomWindow_bottomDisabled_returnsNull() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, false);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getBottomWindow();
@@ -101,7 +101,7 @@
public void testGetBottomWindow_bottomEnabled_returnsWindow() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getBottomWindow();
@@ -112,7 +112,7 @@
public void testGetBottomWindow_bottomEnabled_calledTwice_returnsSameWindow() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window1 = mCarNavigationBar.getBottomWindow();
ViewGroup window2 = mCarNavigationBar.getBottomWindow();
@@ -124,7 +124,7 @@
public void testGetLeftWindow_leftDisabled_returnsNull() {
mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, false);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getLeftWindow();
assertThat(window).isNull();
}
@@ -133,7 +133,7 @@
public void testGetLeftWindow_leftEnabled_returnsWindow() {
mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getLeftWindow();
@@ -144,7 +144,7 @@
public void testGetLeftWindow_leftEnabled_calledTwice_returnsSameWindow() {
mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window1 = mCarNavigationBar.getLeftWindow();
ViewGroup window2 = mCarNavigationBar.getLeftWindow();
@@ -156,7 +156,7 @@
public void testGetRightWindow_rightDisabled_returnsNull() {
mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, false);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getRightWindow();
@@ -167,7 +167,7 @@
public void testGetRightWindow_rightEnabled_returnsWindow() {
mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getRightWindow();
@@ -178,7 +178,7 @@
public void testGetRightWindow_rightEnabled_calledTwice_returnsSameWindow() {
mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window1 = mCarNavigationBar.getRightWindow();
ViewGroup window2 = mCarNavigationBar.getRightWindow();
@@ -190,7 +190,7 @@
public void testSetBottomWindowVisibility_setTrue_isVisible() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getBottomWindow();
mCarNavigationBar.setBottomWindowVisibility(View.VISIBLE);
@@ -202,7 +202,7 @@
public void testSetBottomWindowVisibility_setFalse_isGone() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getBottomWindow();
mCarNavigationBar.setBottomWindowVisibility(View.GONE);
@@ -214,7 +214,7 @@
public void testSetLeftWindowVisibility_setTrue_isVisible() {
mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getLeftWindow();
mCarNavigationBar.setLeftWindowVisibility(View.VISIBLE);
@@ -226,7 +226,7 @@
public void testSetLeftWindowVisibility_setFalse_isGone() {
mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getLeftWindow();
mCarNavigationBar.setLeftWindowVisibility(View.GONE);
@@ -238,7 +238,7 @@
public void testSetRightWindowVisibility_setTrue_isVisible() {
mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getRightWindow();
mCarNavigationBar.setRightWindowVisibility(View.VISIBLE);
@@ -250,7 +250,7 @@
public void testSetRightWindowVisibility_setFalse_isGone() {
mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getRightWindow();
mCarNavigationBar.setRightWindowVisibility(View.GONE);
@@ -262,7 +262,7 @@
public void testRegisterBottomBarTouchListener_createViewFirst_registrationSuccessful() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
View.OnTouchListener controller = bottomBar.getStatusBarWindowTouchListener();
@@ -277,7 +277,7 @@
public void testRegisterBottomBarTouchListener_registerFirst_registrationSuccessful() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
mCarNavigationBar.registerBottomBarTouchListener(mock(View.OnTouchListener.class));
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
@@ -290,7 +290,7 @@
public void testRegisterNotificationController_createViewFirst_registrationSuccessful() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
CarNavigationBarController.NotificationsShadeController controller =
@@ -307,7 +307,7 @@
public void testRegisterNotificationController_registerFirst_registrationSuccessful() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
mCarNavigationBar.registerNotificationController(
mock(CarNavigationBarController.NotificationsShadeController.class));
@@ -322,7 +322,7 @@
public void testShowAllKeyguardButtons_bottomEnabled_bottomKeyguardButtonsVisible() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
View bottomKeyguardButtons = bottomBar.findViewById(R.id.lock_screen_nav_buttons);
@@ -335,7 +335,7 @@
public void testShowAllKeyguardButtons_bottomEnabled_bottomNavButtonsGone() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
View bottomButtons = bottomBar.findViewById(R.id.nav_buttons);
@@ -348,7 +348,7 @@
public void testHideAllKeyguardButtons_bottomEnabled_bottomKeyguardButtonsGone() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
View bottomKeyguardButtons = bottomBar.findViewById(R.id.lock_screen_nav_buttons);
@@ -363,7 +363,7 @@
public void testHideAllKeyguardButtons_bottomEnabled_bottomNavButtonsVisible() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
View bottomButtons = bottomBar.findViewById(R.id.nav_buttons);
@@ -378,7 +378,7 @@
public void testToggleAllNotificationsUnseenIndicator_bottomEnabled_hasUnseen_setCorrectly() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
CarNavigationButton notifications = bottomBar.findViewById(R.id.notifications);
@@ -393,7 +393,7 @@
public void testToggleAllNotificationsUnseenIndicator_bottomEnabled_noUnseen_setCorrectly() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
CarNavigationButton notifications = bottomBar.findViewById(R.id.notifications);
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationButtonTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationButtonTest.java
new file mode 100644
index 0000000..96d567d
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationButtonTest.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.navigationbar.car;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.keyguard.AlphaOptimizedImageButton;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.tests.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class CarNavigationButtonTest extends SysuiTestCase {
+
+ private static final String DEFAULT_BUTTON_ACTIVITY_NAME =
+ "com.android.car.carlauncher/.CarLauncher";
+ private static final String APP_GRID_BUTTON_ACTIVITY_NAME =
+ "com.android.car.carlauncher/.AppGridActivity";
+ private static final String BROADCAST_ACTION_NAME =
+ "android.car.intent.action.TOGGLE_HVAC_CONTROLS";
+
+ private ActivityManager mActivityManager;
+ // LinearLayout with CarNavigationButtons with different configurations.
+ private LinearLayout mTestView;
+ // Does not have any selection state which is the default configuration.
+ private CarNavigationButton mDefaultButton;
+
+ @Before
+ public void setUp() {
+ mContext = spy(mContext);
+ mTestView = (LinearLayout) LayoutInflater.from(mContext).inflate(
+ R.layout.car_navigation_button_test, /* root= */ null);
+ mDefaultButton = mTestView.findViewById(R.id.default_no_selection_state);
+ mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ }
+
+ @Test
+ public void onCreate_iconIsVisible() {
+ AlphaOptimizedImageButton icon = mDefaultButton.findViewById(
+ R.id.car_nav_button_icon_image);
+
+ assertThat(icon.getDrawable()).isNotNull();
+ }
+
+ @Test
+ public void onSelected_selectedIconDefined_togglesIcon() {
+ mDefaultButton.setSelected(true);
+ Drawable selectedIconDrawable = ((AlphaOptimizedImageButton) mDefaultButton.findViewById(
+ R.id.car_nav_button_icon_image)).getDrawable();
+
+
+ mDefaultButton.setSelected(false);
+ Drawable unselectedIconDrawable = ((AlphaOptimizedImageButton) mDefaultButton.findViewById(
+ R.id.car_nav_button_icon_image)).getDrawable();
+
+ assertThat(selectedIconDrawable).isNotEqualTo(unselectedIconDrawable);
+ }
+
+ @Test
+ public void onSelected_selectedIconUndefined_displaysSameIcon() {
+ CarNavigationButton selectedIconUndefinedButton = mTestView.findViewById(
+ R.id.selected_icon_undefined);
+
+ selectedIconUndefinedButton.setSelected(true);
+ Drawable selectedIconDrawable = ((AlphaOptimizedImageButton) mDefaultButton.findViewById(
+ R.id.car_nav_button_icon_image)).getDrawable();
+
+
+ selectedIconUndefinedButton.setSelected(false);
+ Drawable unselectedIconDrawable = ((AlphaOptimizedImageButton) mDefaultButton.findViewById(
+ R.id.car_nav_button_icon_image)).getDrawable();
+
+ assertThat(selectedIconDrawable).isEqualTo(unselectedIconDrawable);
+ }
+
+ @Test
+ public void onUnselected_doesNotHighlightWhenSelected_applySelectedAlpha() {
+ mDefaultButton.setSelected(false);
+
+ assertThat(mDefaultButton.getAlpha()).isEqualTo(
+ CarNavigationButton.DEFAULT_SELECTED_ALPHA);
+ }
+
+ @Test
+ public void onSelected_doesNotHighlightWhenSelected_applySelectedAlpha() {
+ mDefaultButton.setSelected(true);
+
+ assertThat(mDefaultButton.getAlpha()).isEqualTo(
+ CarNavigationButton.DEFAULT_SELECTED_ALPHA);
+ }
+
+ @Test
+ public void onUnselected_highlightWhenSelected_applyDefaultUnselectedAlpha() {
+ CarNavigationButton highlightWhenSelectedButton = mTestView.findViewById(
+ R.id.highlightable_no_more_button);
+ highlightWhenSelectedButton.setSelected(false);
+
+ assertThat(highlightWhenSelectedButton.getAlpha()).isEqualTo(
+ CarNavigationButton.DEFAULT_UNSELECTED_ALPHA);
+ }
+
+ @Test
+ public void onSelected_highlightWhenSelected_applyDefaultSelectedAlpha() {
+ CarNavigationButton highlightWhenSelectedButton = mTestView.findViewById(
+ R.id.highlightable_no_more_button);
+ highlightWhenSelectedButton.setSelected(true);
+
+ assertThat(highlightWhenSelectedButton.getAlpha()).isEqualTo(
+ CarNavigationButton.DEFAULT_SELECTED_ALPHA);
+ }
+
+ @Test
+ public void onSelected_doesNotShowMoreWhenSelected_doesNotShowMoreIcon() {
+ mDefaultButton.setSelected(true);
+ AlphaOptimizedImageButton moreIcon = mDefaultButton.findViewById(
+ R.id.car_nav_button_more_icon);
+
+ assertThat(moreIcon.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void onSelected_showMoreWhenSelected_showsMoreIcon() {
+ CarNavigationButton showMoreWhenSelected = mTestView.findViewById(
+ R.id.not_highlightable_more_button);
+ showMoreWhenSelected.setSelected(true);
+ AlphaOptimizedImageButton moreIcon = showMoreWhenSelected.findViewById(
+ R.id.car_nav_button_more_icon);
+
+ assertThat(moreIcon.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void onUnselected_showMoreWhenSelected_doesNotShowMoreIcon() {
+ CarNavigationButton showMoreWhenSelected = mTestView.findViewById(
+ R.id.highlightable_no_more_button);
+ showMoreWhenSelected.setSelected(true);
+ showMoreWhenSelected.setSelected(false);
+ AlphaOptimizedImageButton moreIcon = showMoreWhenSelected.findViewById(
+ R.id.car_nav_button_more_icon);
+
+ assertThat(moreIcon.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void onClick_launchesIntentActivity() {
+ mDefaultButton.performClick();
+
+ assertThat(getCurrentActivityName()).isEqualTo(DEFAULT_BUTTON_ACTIVITY_NAME);
+
+ CarNavigationButton appGridButton = mTestView.findViewById(R.id.app_grid_activity);
+ appGridButton.performClick();
+
+ assertThat(getCurrentActivityName()).isEqualTo(APP_GRID_BUTTON_ACTIVITY_NAME);
+ }
+
+ @Test
+ public void onLongClick_longIntentDefined_launchesLongIntentActivity() {
+ mDefaultButton.performClick();
+
+ assertThat(getCurrentActivityName()).isEqualTo(DEFAULT_BUTTON_ACTIVITY_NAME);
+
+ CarNavigationButton appGridButton = mTestView.findViewById(
+ R.id.long_click_app_grid_activity);
+ appGridButton.performLongClick();
+
+ assertThat(getCurrentActivityName()).isEqualTo(APP_GRID_BUTTON_ACTIVITY_NAME);
+ }
+
+ @Test
+ public void onClick_useBroadcast_broadcastsIntent() {
+ CarNavigationButton appGridButton = mTestView.findViewById(R.id.broadcast);
+ appGridButton.performClick();
+
+ verify(mContext).sendBroadcastAsUser(argThat(new ArgumentMatcher<Intent>() {
+ @Override
+ public boolean matches(Intent argument) {
+ return argument.getAction().equals(BROADCAST_ACTION_NAME);
+ }
+ }), any());
+ }
+
+ @Test
+ public void onSetUnseen_hasUnseen_showsUnseenIndicator() {
+ mDefaultButton.setUnseen(true);
+ ImageView hasUnseenIndicator = mDefaultButton.findViewById(R.id.car_nav_button_unseen_icon);
+
+ assertThat(hasUnseenIndicator.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void onSetUnseen_doesNotHaveUnseen_hidesUnseenIndicator() {
+ mDefaultButton.setUnseen(false);
+ ImageView hasUnseenIndicator = mDefaultButton.findViewById(R.id.car_nav_button_unseen_icon);
+
+ assertThat(hasUnseenIndicator.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ private String getCurrentActivityName() {
+ return mActivityManager.getRunningTasks(1).get(0).topActivity.flattenToShortString();
+ }
+}
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index 9e49826..c2ce840 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -262,10 +262,11 @@
return;
}
+ stopForeground(true);
mJustCancelledByUser = true;
if (mInstallTask.cancel(false)) {
- // Will cleanup and post status in onResult()
+ // Will stopSelf() in onResult()
Log.d(TAG, "Cancel request filed successfully");
} else {
Log.e(TAG, "Trying to cancel installation while it's already completed.");
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 0e3f81b..aa36dca 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -266,9 +266,6 @@
dumpSetting(s, p,
Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS,
GlobalSettingsProto.Backup.BACKUP_AGENT_TIMEOUT_PARAMETERS);
- dumpSetting(s, p,
- Settings.Global.BACKUP_MULTI_USER_ENABLED,
- GlobalSettingsProto.Backup.BACKUP_MULTI_USER_ENABLED);
p.end(backupToken);
final long batteryToken = p.start(GlobalSettingsProto.BATTERY);
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 443811f..7278225 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -571,7 +571,6 @@
Settings.Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED,
Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS,
Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS,
- Settings.Global.BACKUP_MULTI_USER_ENABLED,
Settings.Global.ISOLATED_STORAGE_LOCAL,
Settings.Global.ISOLATED_STORAGE_REMOTE,
Settings.Global.APPOP_HISTORY_PARAMETERS,
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index f7e9fed..4d184d5 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -240,6 +240,15 @@
<!-- Description of airplane mode -->
<string name="airplane_mode">Airplane mode</string>
+ <!-- An explanation text that the PIN needs to be entered to prepare for an operating system update. [CHAR LIMIT=80] -->
+ <string name="kg_prompt_reason_prepare_for_update_pin">PIN required to prepare for update</string>
+
+ <!-- An explanation text that the pattern needs to be entered to prepare for an operating system update. [CHAR LIMIT=80] -->
+ <string name="kg_prompt_reason_prepare_for_update_pattern">Pattern required to prepare for update</string>
+
+ <!-- An explanation text that the password needs to be entered to prepare for an operating system update. [CHAR LIMIT=80] -->
+ <string name="kg_prompt_reason_prepare_for_update_password">Password required to prepare for update</string>
+
<!-- An explanation text that the pattern needs to be solved since the device has just been restarted. [CHAR LIMIT=80] -->
<string name="kg_prompt_reason_restart_pattern">Pattern required after device restarts</string>
diff --git a/packages/SystemUI/res/values/attrs_car.xml b/packages/SystemUI/res/values/attrs_car.xml
deleted file mode 100644
index 49b87f3..0000000
--- a/packages/SystemUI/res/values/attrs_car.xml
+++ /dev/null
@@ -1,103 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
- <attr name="icon" format="reference"/>
- <attr name="selectedIcon" format="reference"/>
- <attr name="intent" format="string"/>
- <attr name="longIntent" format="string"/>
- <attr name="selectedAlpha" format="float" />
- <attr name="unselectedAlpha" format="float" />
-
- <!-- Allow for custom attribs to be added to a facet button -->
- <declare-styleable name="CarFacetButton">
- <!-- icon to be rendered (drawable) -->
- <attr name="icon"/>
- <!-- icon to be rendered when in selected state -->
- <attr name="selectedIcon"/>
- <!-- intent to start when button is click -->
- <attr name="intent"/>
- <!-- intent to start when a long press has happened -->
- <attr name="longIntent"/>
- <!-- categories that will be added as extras to the fired intents -->
- <attr name="categories" format="string"/>
- <!-- package names that will be added as extras to the fired intents -->
- <attr name="packages" format="string" />
- <!-- componentName names that will be used for detecting selected state -->
- <attr name="componentNames" format="string" />
- <!-- Alpha value to used when in selected state. Defaults 1f -->
- <attr name="selectedAlpha" />
- <!-- Alpha value to used when in un-selected state. Defaults 0.7f -->
- <attr name="unselectedAlpha" />
- <!-- Render a "more" icon. Defaults true -->
- <attr name="useMoreIcon" format="boolean" />
-
- </declare-styleable>
-
-
- <!-- Allow for custom attribs to be added to a nav button -->
- <declare-styleable name="CarNavigationButton">
- <!-- intent to start when button is click -->
- <attr name="intent" />
- <!-- intent to start when a long press has happened -->
- <attr name="longIntent" />
- <!-- start the intent as a broad cast instead of an activity if true-->
- <attr name="broadcast" format="boolean"/>
- <!-- Alpha value to used when in selected state. Defaults 1f -->
- <attr name="selectedAlpha" />
- <!-- Alpha value to used when in un-selected state. Defaults 0.7f -->
- <attr name="unselectedAlpha" />
- <!-- icon to be rendered when in selected state -->
- <attr name="selectedIcon" />
- <!-- icon to be rendered (drawable) -->
- <attr name="icon"/>
- </declare-styleable>
-
- <!-- Custom attributes to configure hvac values -->
- <declare-styleable name="TemperatureView">
- <attr name="hvacAreaId" format="integer"/>
- <attr name="hvacPropertyId" format="integer"/>
- <attr name="hvacTempFormat" format="string"/>
- </declare-styleable>
-
- <declare-styleable name="carVolumeItems"/>
- <declare-styleable name="carVolumeItems_item">
- <!-- Align with AudioAttributes.USAGE_* -->
- <attr name="usage">
- <enum name="unknown" value="0"/>
- <enum name="media" value="1"/>
- <enum name="voice_communication" value="2"/>
- <enum name="voice_communication_signalling" value="3"/>
- <enum name="alarm" value="4"/>
- <enum name="notification" value="5"/>
- <enum name="notification_ringtone" value="6"/>
- <enum name="notification_communication_request" value="7"/>
- <enum name="notification_communication_instant" value="8"/>
- <enum name="notification_communication_delayed" value="9"/>
- <enum name="notification_event" value="10"/>
- <enum name="assistance_accessibility" value="11"/>
- <enum name="assistance_navigation_guidance" value="12"/>
- <enum name="assistance_sonification" value="13"/>
- <enum name="game" value="14"/>
- <!-- hidden, do not use -->
- <!-- enum name="virtual_source" value="15"/ -->
- <enum name="assistant" value="16"/>
- </attr>
-
- <!-- Icon resource ids to render on UI -->
- <attr name="icon" />
- </declare-styleable>
-</resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
index 2c8f238..ed1cd81 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
@@ -168,8 +168,9 @@
*
* @param reason a flag indicating which string should be shown, see
* {@link KeyguardSecurityView#PROMPT_REASON_NONE},
- * {@link KeyguardSecurityView#PROMPT_REASON_RESTART} and
- * {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}.
+ * {@link KeyguardSecurityView#PROMPT_REASON_RESTART},
+ * {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}, and
+ * {@link KeyguardSecurityView#PROMPT_REASON_PREPARE_FOR_UPDATE}.
*/
public void showPromptReason(int reason) {
mSecurityContainer.showPromptReason(reason);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index f8f3dc8..718bcf1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -137,6 +137,8 @@
return R.string.kg_prompt_reason_device_admin;
case PROMPT_REASON_USER_REQUEST:
return R.string.kg_prompt_reason_user_request;
+ case PROMPT_REASON_PREPARE_FOR_UPDATE:
+ return R.string.kg_prompt_reason_prepare_for_update_password;
case PROMPT_REASON_NONE:
return 0;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index 9eb168a..48c6bd1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -438,6 +438,10 @@
case PROMPT_REASON_USER_REQUEST:
mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_user_request);
break;
+ case PROMPT_REASON_PREPARE_FOR_UPDATE:
+ mSecurityMessageDisplay.setMessage(
+ R.string.kg_prompt_reason_prepare_for_update_pattern);
+ break;
case PROMPT_REASON_NONE:
break;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index c67decc..6d865ab 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -116,6 +116,8 @@
return R.string.kg_prompt_reason_device_admin;
case PROMPT_REASON_USER_REQUEST:
return R.string.kg_prompt_reason_user_request;
+ case PROMPT_REASON_PREPARE_FOR_UPDATE:
+ return R.string.kg_prompt_reason_prepare_for_update_pin;
case PROMPT_REASON_NONE:
return 0;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
index e108194..09d4d5f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -51,6 +51,11 @@
*/
int PROMPT_REASON_AFTER_LOCKOUT = 5;
+ /***
+ * Strong auth is require to prepare for an unattended update.
+ */
+ int PROMPT_REASON_PREPARE_FOR_UPDATE = 6;
+
/**
* Interface back to keyguard to tell it when security
* @param callback
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index c1934c6..e13c3e0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -22,6 +22,7 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
import static com.android.systemui.DejankUtils.whitelistIpcs;
import android.app.ActivityManager;
@@ -670,6 +671,8 @@
return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
} else if (any && (strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0) {
return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT;
+ } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE) != 0) {
+ return KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
}
return KeyguardSecurityView.PROMPT_REASON_NONE;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
index e4a57d7..c6c36ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
@@ -112,7 +112,12 @@
LIFETIME_EXTENDED,
REMOVE_INTERCEPTED,
INFLATION_ABORTED,
- INFLATED
+ INFLATED,
+
+ // GroupCoalescer
+ COALESCED_EVENT,
+ EARLY_BATCH_EMIT,
+ EMIT_EVENT_BATCH
})
@Retention(RetentionPolicy.SOURCE)
public @interface EventType {}
@@ -147,8 +152,10 @@
"InflationAborted",
"Inflated",
+ // GroupCoalescer labels:
"CoalescedEvent",
- "EarlyBatchEmit"
+ "EarlyBatchEmit",
+ "EmitEventBatch"
};
private static final int TOTAL_EVENT_LABELS = EVENT_LABELS.length;
@@ -174,25 +181,28 @@
/**
* Events related to {@link NotificationEntryManager}
*/
- public static final int NOTIF_ADDED = TOTAL_LIST_BUILDER_EVENT_TYPES;
- public static final int NOTIF_REMOVED = TOTAL_LIST_BUILDER_EVENT_TYPES + 1;
- public static final int NOTIF_UPDATED = TOTAL_LIST_BUILDER_EVENT_TYPES + 2;
- public static final int FILTER = TOTAL_LIST_BUILDER_EVENT_TYPES + 3;
- public static final int SORT = TOTAL_LIST_BUILDER_EVENT_TYPES + 4;
- public static final int FILTER_AND_SORT = TOTAL_LIST_BUILDER_EVENT_TYPES + 5;
- public static final int NOTIF_VISIBILITY_CHANGED = TOTAL_LIST_BUILDER_EVENT_TYPES + 6;
- public static final int LIFETIME_EXTENDED = TOTAL_LIST_BUILDER_EVENT_TYPES + 7;
+ private static final int NEM_EVENT_START_INDEX = TOTAL_LIST_BUILDER_EVENT_TYPES;
+ public static final int NOTIF_ADDED = NEM_EVENT_START_INDEX;
+ public static final int NOTIF_REMOVED = NEM_EVENT_START_INDEX + 1;
+ public static final int NOTIF_UPDATED = NEM_EVENT_START_INDEX + 2;
+ public static final int FILTER = NEM_EVENT_START_INDEX + 3;
+ public static final int SORT = NEM_EVENT_START_INDEX + 4;
+ public static final int FILTER_AND_SORT = NEM_EVENT_START_INDEX + 5;
+ public static final int NOTIF_VISIBILITY_CHANGED = NEM_EVENT_START_INDEX + 6;
+ public static final int LIFETIME_EXTENDED = NEM_EVENT_START_INDEX + 7;
// unable to remove notif - removal intercepted by {@link NotificationRemoveInterceptor}
- public static final int REMOVE_INTERCEPTED = TOTAL_LIST_BUILDER_EVENT_TYPES + 8;
- public static final int INFLATION_ABORTED = TOTAL_LIST_BUILDER_EVENT_TYPES + 9;
- public static final int INFLATED = TOTAL_LIST_BUILDER_EVENT_TYPES + 10;
+ public static final int REMOVE_INTERCEPTED = NEM_EVENT_START_INDEX + 8;
+ public static final int INFLATION_ABORTED = NEM_EVENT_START_INDEX + 9;
+ public static final int INFLATED = NEM_EVENT_START_INDEX + 10;
private static final int TOTAL_NEM_EVENT_TYPES = 11;
/**
* Events related to {@link GroupCoalescer}
*/
- public static final int COALESCED_EVENT = TOTAL_NEM_EVENT_TYPES;
- public static final int EARLY_BATCH_EMIT = TOTAL_NEM_EVENT_TYPES + 1;
- public static final int EMIT_EVENT_BATCH = TOTAL_NEM_EVENT_TYPES + 2;
- private static final int TOTAL_COALESCER_EVENT_TYPES = 2;
+ private static final int COALESCER_EVENT_START_INDEX = NEM_EVENT_START_INDEX
+ + TOTAL_NEM_EVENT_TYPES;
+ public static final int COALESCED_EVENT = COALESCER_EVENT_START_INDEX;
+ public static final int EARLY_BATCH_EMIT = COALESCER_EVENT_START_INDEX + 1;
+ public static final int EMIT_EVENT_BATCH = COALESCER_EVENT_START_INDEX + 2;
+ private static final int TOTAL_COALESCER_EVENT_TYPES = 3;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index e7d1c95..718522c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -85,7 +85,8 @@
boolean visibleWhenEnabled = mContext.getResources().getBoolean(
R.bool.config_showWifiIndicatorWhenEnabled);
boolean wifiVisible = mCurrentState.enabled
- && (mCurrentState.connected || !mHasMobileData || visibleWhenEnabled);
+ && ((mCurrentState.connected && mCurrentState.inetCondition == 1)
+ || !mHasMobileData || visibleWhenEnabled);
String wifiDesc = wifiVisible ? mCurrentState.ssid : null;
boolean ssidPresent = wifiVisible && mCurrentState.ssid != null;
String contentDescription = getStringIfExists(getContentDescription());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index 3451183..32da4c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -39,7 +39,8 @@
setWifiState(true, testSsid);
setWifiLevel(0);
- verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[0][0]);
+ // Connected, but still not validated - does not show
+ verifyLastWifiIcon(false, WifiIcons.WIFI_SIGNAL_STRENGTH[0][0]);
for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) {
setWifiLevel(testLevel);
@@ -47,7 +48,8 @@
setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, false, true);
- verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[0][testLevel]);
+ // Icon does not show if not validated
+ verifyLastWifiIcon(false, WifiIcons.WIFI_SIGNAL_STRENGTH[0][testLevel]);
}
}
@@ -70,7 +72,7 @@
testSsid);
setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, false, true);
verifyLastQsWifiIcon(true, true, WifiIcons.QS_WIFI_SIGNAL_STRENGTH[0][testLevel],
- testSsid);
+ null);
}
}
@@ -132,7 +134,7 @@
verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
setConnectivityViaCallback(NetworkCapabilities.TRANSPORT_WIFI, false, true);
- verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[0][testLevel]);
+ verifyLastWifiIcon(false, WifiIcons.WIFI_SIGNAL_STRENGTH[0][testLevel]);
}
@Test
diff --git a/packages/Tethering/jarjar-rules.txt b/packages/Tethering/jarjar-rules.txt
index dd9eab7..d93531b 100644
--- a/packages/Tethering/jarjar-rules.txt
+++ b/packages/Tethering/jarjar-rules.txt
@@ -13,3 +13,5 @@
rule com.android.internal.util.StateMachine* com.android.networkstack.tethering.util.StateMachine@1
rule android.net.LocalLog* com.android.networkstack.tethering.LocalLog@1
+
+rule android.net.shared.Inet4AddressUtils* com.android.networkstack.tethering.shared.Inet4AddressUtils@1
diff --git a/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java b/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
index 1fe2328..d6bc063 100644
--- a/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
+++ b/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
@@ -20,11 +20,11 @@
import android.annotation.NonNull;
import android.net.LinkAddress;
-
-import com.google.android.collect.Sets;
+import android.util.ArraySet;
import java.net.Inet4Address;
import java.util.Collection;
+import java.util.Collections;
import java.util.Set;
/**
@@ -68,7 +68,7 @@
* but it must always be set explicitly.
*/
public DhcpServingParamsParcelExt setDefaultRouters(@NonNull Inet4Address... defaultRouters) {
- return setDefaultRouters(Sets.newArraySet(defaultRouters));
+ return setDefaultRouters(newArraySet(defaultRouters));
}
/**
@@ -96,7 +96,7 @@
* <p>This may be an empty list of servers, but it must always be set explicitly.
*/
public DhcpServingParamsParcelExt setDnsServers(@NonNull Inet4Address... dnsServers) {
- return setDnsServers(Sets.newArraySet(dnsServers));
+ return setDnsServers(newArraySet(dnsServers));
}
/**
@@ -126,7 +126,7 @@
* and do not need to be set here.
*/
public DhcpServingParamsParcelExt setExcludedAddrs(@NonNull Inet4Address... excludedAddrs) {
- return setExcludedAddrs(Sets.newArraySet(excludedAddrs));
+ return setExcludedAddrs(newArraySet(excludedAddrs));
}
/**
@@ -169,4 +169,10 @@
}
return res;
}
+
+ private static ArraySet<Inet4Address> newArraySet(Inet4Address... addrs) {
+ ArraySet<Inet4Address> addrSet = new ArraySet<>(addrs.length);
+ Collections.addAll(addrSet, addrs);
+ return addrSet;
+ }
}
diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java
index 8fde520..abfb33c 100644
--- a/packages/Tethering/src/android/net/ip/IpServer.java
+++ b/packages/Tethering/src/android/net/ip/IpServer.java
@@ -17,10 +17,12 @@
package android.net.ip;
import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
import static android.net.util.NetworkConstants.FF;
import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
import static android.net.util.NetworkConstants.asByte;
+import static android.net.util.TetheringMessageBase.BASE_IPSERVER;
import android.net.ConnectivityManager;
import android.net.INetd;
@@ -46,11 +48,9 @@
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.Log;
-import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.util.MessageUtils;
-import com.android.internal.util.Protocol;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
@@ -153,27 +153,26 @@
DhcpServerCallbacks cb);
}
- private static final int BASE_IFACE = Protocol.BASE_TETHERING + 100;
// request from the user that it wants to tether
- public static final int CMD_TETHER_REQUESTED = BASE_IFACE + 2;
+ public static final int CMD_TETHER_REQUESTED = BASE_IPSERVER + 1;
// request from the user that it wants to untether
- public static final int CMD_TETHER_UNREQUESTED = BASE_IFACE + 3;
+ public static final int CMD_TETHER_UNREQUESTED = BASE_IPSERVER + 2;
// notification that this interface is down
- public static final int CMD_INTERFACE_DOWN = BASE_IFACE + 4;
+ public static final int CMD_INTERFACE_DOWN = BASE_IPSERVER + 3;
// notification from the master SM that it had trouble enabling IP Forwarding
- public static final int CMD_IP_FORWARDING_ENABLE_ERROR = BASE_IFACE + 7;
+ public static final int CMD_IP_FORWARDING_ENABLE_ERROR = BASE_IPSERVER + 4;
// notification from the master SM that it had trouble disabling IP Forwarding
- public static final int CMD_IP_FORWARDING_DISABLE_ERROR = BASE_IFACE + 8;
+ public static final int CMD_IP_FORWARDING_DISABLE_ERROR = BASE_IPSERVER + 5;
// notification from the master SM that it had trouble starting tethering
- public static final int CMD_START_TETHERING_ERROR = BASE_IFACE + 9;
+ public static final int CMD_START_TETHERING_ERROR = BASE_IPSERVER + 6;
// notification from the master SM that it had trouble stopping tethering
- public static final int CMD_STOP_TETHERING_ERROR = BASE_IFACE + 10;
+ public static final int CMD_STOP_TETHERING_ERROR = BASE_IPSERVER + 7;
// notification from the master SM that it had trouble setting the DNS forwarders
- public static final int CMD_SET_DNS_FORWARDERS_ERROR = BASE_IFACE + 11;
+ public static final int CMD_SET_DNS_FORWARDERS_ERROR = BASE_IPSERVER + 8;
// the upstream connection has changed
- public static final int CMD_TETHER_CONNECTION_CHANGED = BASE_IFACE + 12;
+ public static final int CMD_TETHER_CONNECTION_CHANGED = BASE_IPSERVER + 9;
// new IPv6 tethering parameters need to be processed
- public static final int CMD_IPV6_TETHER_UPDATE = BASE_IFACE + 13;
+ public static final int CMD_IPV6_TETHER_UPDATE = BASE_IPSERVER + 10;
private final State mInitialState;
private final State mLocalHotspotState;
@@ -486,7 +485,9 @@
}
// Directly-connected route.
- final RouteInfo route = new RouteInfo(linkAddr);
+ final IpPrefix ipv4Prefix = new IpPrefix(linkAddr.getAddress(),
+ linkAddr.getPrefixLength());
+ final RouteInfo route = new RouteInfo(ipv4Prefix, null, null, RTN_UNICAST);
if (enabled) {
mLinkProperties.addLinkAddress(linkAddr);
mLinkProperties.addRoute(route);
@@ -1007,7 +1008,7 @@
String ifname, HashSet<IpPrefix> prefixes) {
final ArrayList<RouteInfo> localRoutes = new ArrayList<RouteInfo>();
for (IpPrefix ipp : prefixes) {
- localRoutes.add(new RouteInfo(ipp, null, ifname));
+ localRoutes.add(new RouteInfo(ipp, null, ifname, RTN_UNICAST));
}
return localRoutes;
}
@@ -1019,7 +1020,7 @@
try {
return Inet6Address.getByAddress(null, dnsBytes, 0);
} catch (UnknownHostException e) {
- Slog.wtf(TAG, "Failed to construct Inet6Address from: " + localPrefix);
+ Log.wtf(TAG, "Failed to construct Inet6Address from: " + localPrefix);
return null;
}
}
diff --git a/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java b/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
index 4396cdb..bba61d7 100644
--- a/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
+++ b/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
@@ -22,14 +22,13 @@
import static android.system.OsConstants.IPPROTO_ICMPV6;
import static android.system.OsConstants.SOCK_RAW;
import static android.system.OsConstants.SOL_SOCKET;
-import static android.system.OsConstants.SO_BINDTODEVICE;
import static android.system.OsConstants.SO_SNDTIMEO;
import android.net.IpPrefix;
import android.net.LinkAddress;
-import android.net.NetworkUtils;
import android.net.TrafficStats;
import android.net.util.InterfaceParams;
+import android.net.util.SocketUtils;
import android.net.util.TetheringUtils;
import android.system.ErrnoException;
import android.system.Os;
@@ -39,8 +38,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.TrafficStatsConstants;
-import libcore.io.IoBridge;
-
import java.io.FileDescriptor;
import java.io.IOException;
import java.net.Inet6Address;
@@ -612,8 +609,7 @@
// Setting SNDTIMEO is purely for defensive purposes.
Os.setsockoptTimeval(
mSocket, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(send_timout_ms));
- Os.setsockoptIfreq(mSocket, SOL_SOCKET, SO_BINDTODEVICE, mInterface.name);
- NetworkUtils.protectFromVpn(mSocket);
+ SocketUtils.bindSocketToInterface(mSocket, mInterface.name);
TetheringUtils.setupRaSocket(mSocket, mInterface.index);
} catch (ErrnoException | IOException e) {
Log.e(TAG, "Failed to create RA daemon socket: " + e);
@@ -628,7 +624,7 @@
private void closeSocket() {
if (mSocket != null) {
try {
- IoBridge.closeAndSignalBlockedThreads(mSocket);
+ SocketUtils.closeSocket(mSocket);
} catch (IOException ignored) { }
}
mSocket = null;
diff --git a/packages/Tethering/src/android/net/util/TetheringMessageBase.java b/packages/Tethering/src/android/net/util/TetheringMessageBase.java
new file mode 100644
index 0000000..1b763ce
--- /dev/null
+++ b/packages/Tethering/src/android/net/util/TetheringMessageBase.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.util;
+
+/**
+ * This class defines Message.what base addresses for various state machine.
+ */
+public class TetheringMessageBase {
+ public static final int BASE_MASTER = 0;
+ public static final int BASE_IPSERVER = 100;
+
+}
diff --git a/packages/Tethering/src/android/net/util/TetheringUtils.java b/packages/Tethering/src/android/net/util/TetheringUtils.java
index 2fb73ce..fa543bd 100644
--- a/packages/Tethering/src/android/net/util/TetheringUtils.java
+++ b/packages/Tethering/src/android/net/util/TetheringUtils.java
@@ -39,4 +39,11 @@
*/
public static native void setupRaSocket(FileDescriptor fd, int ifIndex)
throws SocketException;
+
+ /**
+ * Read s as an unsigned 16-bit integer.
+ */
+ public static int uint16(short s) {
+ return s & 0xffff;
+ }
}
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
index ba5d08d..7e685fb 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
@@ -38,7 +38,6 @@
import android.content.IntentFilter;
import android.content.res.Resources;
import android.net.util.SharedLog;
-import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -48,7 +47,6 @@
import android.os.ResultReceiver;
import android.os.SystemClock;
import android.os.SystemProperties;
-import android.os.UserHandle;
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
import android.util.ArraySet;
@@ -196,9 +194,9 @@
// till upstream change to cellular.
if (mUsingCellularAsUpstream) {
if (showProvisioningUi) {
- runUiTetherProvisioning(type, config.subId);
+ runUiTetherProvisioning(type, config.activeDataSubId);
} else {
- runSilentTetherProvisioning(type, config.subId);
+ runSilentTetherProvisioning(type, config.activeDataSubId);
}
mNeedReRunProvisioningUi = false;
} else {
@@ -270,9 +268,9 @@
if (mCellularPermitted.indexOfKey(downstream) < 0) {
if (mNeedReRunProvisioningUi) {
mNeedReRunProvisioningUi = false;
- runUiTetherProvisioning(downstream, config.subId);
+ runUiTetherProvisioning(downstream, config.activeDataSubId);
} else {
- runSilentTetherProvisioning(downstream, config.subId);
+ runSilentTetherProvisioning(downstream, config.activeDataSubId);
}
}
}
@@ -336,7 +334,8 @@
.getSystemService(Context.CARRIER_CONFIG_SERVICE);
if (configManager == null) return null;
- final PersistableBundle carrierConfig = configManager.getConfigForSubId(config.subId);
+ final PersistableBundle carrierConfig = configManager.getConfigForSubId(
+ config.activeDataSubId);
if (CarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfig)) {
return carrierConfig;
@@ -379,12 +378,9 @@
intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver);
intent.putExtra(EXTRA_SUBID, subId);
intent.setComponent(TETHER_SERVICE);
- final long ident = Binder.clearCallingIdentity();
- try {
- mContext.startServiceAsUser(intent, UserHandle.CURRENT);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ // Only admin user can change tethering and SilentTetherProvisioning don't need to
+ // show UI, it is fine to always start setting's background service as system user.
+ mContext.startService(intent);
}
private void runUiTetherProvisioning(int type, int subId) {
@@ -407,12 +403,9 @@
intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver);
intent.putExtra(EXTRA_SUBID, subId);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- final long ident = Binder.clearCallingIdentity();
- try {
- mContext.startActivityAsUser(intent, UserHandle.CURRENT);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ // Only launch entitlement UI for system user. Entitlement UI should not appear for other
+ // user because only admin user is allowed to change tethering.
+ mContext.startActivity(intent);
}
// Not needed to check if this don't run on the handler thread because it's private.
@@ -671,7 +664,7 @@
receiver.send(cacheValue, null);
} else {
ResultReceiver proxy = buildProxyReceiver(downstream, false/* notifyFail */, receiver);
- runUiTetherProvisioning(downstream, config.subId, proxy);
+ runUiTetherProvisioning(downstream, config.activeDataSubId, proxy);
}
}
}
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java b/packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
index 9305414..66b9ade8 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
@@ -29,6 +29,7 @@
import java.net.Inet6Address;
import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
@@ -257,7 +258,7 @@
final LinkProperties lp = new LinkProperties();
final IpPrefix local48 = makeUniqueLocalPrefix(ulp, (short) 0, 48);
- lp.addRoute(new RouteInfo(local48, null, null));
+ lp.addRoute(new RouteInfo(local48, null, null, RouteInfo.RTN_UNICAST));
final IpPrefix local64 = makeUniqueLocalPrefix(ulp, subnetId, 64);
// Because this is a locally-generated ULA, we don't have an upstream
@@ -273,7 +274,13 @@
final byte[] bytes = Arrays.copyOf(in6addr, in6addr.length);
bytes[7] = (byte) (subnetId >> 8);
bytes[8] = (byte) subnetId;
- return new IpPrefix(bytes, prefixlen);
+ final InetAddress addr;
+ try {
+ addr = InetAddress.getByAddress(bytes);
+ } catch (UnknownHostException e) {
+ throw new IllegalStateException("Invalid address length: " + bytes.length, e);
+ }
+ return new IpPrefix(addr, prefixlen);
}
// Generates a Unique Locally-assigned Prefix:
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java
index 16734d8..38fa91e 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java
@@ -25,6 +25,7 @@
import android.content.ContentResolver;
import android.net.ITetheringStatsProvider;
+import android.net.InetAddresses;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
@@ -33,7 +34,6 @@
import android.net.netlink.ConntrackMessage;
import android.net.netlink.NetlinkConstants;
import android.net.netlink.NetlinkSocket;
-import android.net.util.IpUtils;
import android.net.util.SharedLog;
import android.os.Handler;
import android.os.INetworkManagementService;
@@ -477,9 +477,10 @@
if (!ri.hasGateway()) continue;
final String gateway = ri.getGateway().getHostAddress();
- if (ri.isIPv4Default()) {
+ final InetAddress address = ri.getDestination().getAddress();
+ if (ri.isDefaultRoute() && address instanceof Inet4Address) {
v4gateway = gateway;
- } else if (ri.isIPv6Default()) {
+ } else if (ri.isDefaultRoute() && address instanceof Inet6Address) {
v6gateways.add(gateway);
}
}
@@ -547,7 +548,10 @@
private static boolean shouldIgnoreDownstreamRoute(RouteInfo route) {
// Ignore any link-local routes.
- if (!route.getDestinationLinkAddress().isGlobalPreferred()) return true;
+ final IpPrefix destination = route.getDestination();
+ final LinkAddress linkAddr = new LinkAddress(destination.getAddress(),
+ destination.getPrefixLength());
+ if (!linkAddr.isGlobalPreferred()) return true;
return false;
}
@@ -588,7 +592,7 @@
return;
}
- if (!IpUtils.isValidUdpOrTcpPort(srcPort)) {
+ if (!isValidUdpOrTcpPort(srcPort)) {
mLog.e("Invalid src port: " + srcPort);
return;
}
@@ -599,7 +603,7 @@
return;
}
- if (!IpUtils.isValidUdpOrTcpPort(dstPort)) {
+ if (!isValidUdpOrTcpPort(dstPort)) {
mLog.e("Invalid dst port: " + dstPort);
return;
}
@@ -628,7 +632,7 @@
private static Inet4Address parseIPv4Address(String addrString) {
try {
- final InetAddress ip = InetAddress.parseNumericAddress(addrString);
+ final InetAddress ip = InetAddresses.parseNumericAddress(addrString);
// TODO: Consider other sanitization steps here, including perhaps:
// not eql to 0.0.0.0
// not within 169.254.0.0/16
@@ -668,4 +672,8 @@
return 180;
}
}
+
+ private static boolean isValidUdpOrTcpPort(int port) {
+ return port > 0 && port < 65536;
+ }
}
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
index 85164ed..4a8ef1f 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
@@ -16,7 +16,7 @@
package com.android.server.connectivity.tethering;
-import static com.android.internal.util.BitUtils.uint16;
+import static android.net.util.TetheringUtils.uint16;
import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback;
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
index 0b5759b..5b26704 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
@@ -37,6 +37,7 @@
import static android.net.ConnectivityManager.TETHER_ERROR_SERVICE_UNAVAIL;
import static android.net.ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
import static android.net.ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
+import static android.net.util.TetheringMessageBase.BASE_MASTER;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE;
@@ -106,7 +107,6 @@
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.MessageUtils;
-import com.android.internal.util.Protocol;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.networkstack.tethering.R;
@@ -120,6 +120,8 @@
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
/**
*
@@ -185,10 +187,10 @@
private final TetheringDependencies mDeps;
private final EntitlementManager mEntitlementMgr;
private final Handler mHandler;
- private final PhoneStateListener mPhoneStateListener;
private final INetd mNetd;
private final NetdCallback mNetdCallback;
private final UserRestrictionActionListener mTetheringRestriction;
+ private final ActiveDataSubIdListener mActiveDataSubIdListener;
private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
// All the usage of mTetheringEventCallback should run in the same thread.
private ITetheringEventCallback mTetheringEventCallback = null;
@@ -252,26 +254,6 @@
mEntitlementMgr.reevaluateSimCardProvisioning(mConfig);
});
- mPhoneStateListener = new PhoneStateListener(mLooper) {
- @Override
- public void onActiveDataSubscriptionIdChanged(int subId) {
- mLog.log("OBSERVED active data subscription change, from " + mActiveDataSubId
- + " to " + subId);
- if (subId == mActiveDataSubId) return;
-
- mActiveDataSubId = subId;
- updateConfiguration();
- // To avoid launching unexpected provisioning checks, ignore re-provisioning when
- // no CarrierConfig loaded yet. Assume reevaluateSimCardProvisioning() will be
- // triggered again when CarrierConfig is loaded.
- if (mEntitlementMgr.getCarrierConfig(mConfig) != null) {
- mEntitlementMgr.reevaluateSimCardProvisioning(mConfig);
- } else {
- mLog.log("IGNORED reevaluate provisioning due to no carrier config loaded");
- }
- }
- };
-
mStateReceiver = new StateReceiver();
mNetdCallback = new NetdCallback();
@@ -284,6 +266,8 @@
final UserManager userManager = (UserManager) mContext.getSystemService(
Context.USER_SERVICE);
mTetheringRestriction = new UserRestrictionActionListener(userManager, this);
+ final TetheringThreadExecutor executor = new TetheringThreadExecutor(mHandler);
+ mActiveDataSubIdListener = new ActiveDataSubIdListener(executor);
// Load tethering configuration.
updateConfiguration();
@@ -294,8 +278,8 @@
private void startStateMachineUpdaters(Handler handler) {
mCarrierConfigChange.startListening();
- mContext.getSystemService(TelephonyManager.class).listen(
- mPhoneStateListener, PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
+ mContext.getSystemService(TelephonyManager.class).listen(mActiveDataSubIdListener,
+ PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
IntentFilter filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_STATE);
@@ -314,6 +298,43 @@
}
+ private class TetheringThreadExecutor implements Executor {
+ private final Handler mTetherHandler;
+ TetheringThreadExecutor(Handler handler) {
+ mTetherHandler = handler;
+ }
+ @Override
+ public void execute(Runnable command) {
+ if (!mTetherHandler.post(command)) {
+ throw new RejectedExecutionException(mTetherHandler + " is shutting down");
+ }
+ }
+ }
+
+ private class ActiveDataSubIdListener extends PhoneStateListener {
+ ActiveDataSubIdListener(Executor executor) {
+ super(executor);
+ }
+
+ @Override
+ public void onActiveDataSubscriptionIdChanged(int subId) {
+ mLog.log("OBSERVED active data subscription change, from " + mActiveDataSubId
+ + " to " + subId);
+ if (subId == mActiveDataSubId) return;
+
+ mActiveDataSubId = subId;
+ updateConfiguration();
+ // To avoid launching unexpected provisioning checks, ignore re-provisioning
+ // when no CarrierConfig loaded yet. Assume reevaluateSimCardProvisioning()
+ // ill be triggered again when CarrierConfig is loaded.
+ if (mEntitlementMgr.getCarrierConfig(mConfig) != null) {
+ mEntitlementMgr.reevaluateSimCardProvisioning(mConfig);
+ } else {
+ mLog.log("IGNORED reevaluate provisioning, no carrier config loaded");
+ }
+ }
+ }
+
private WifiManager getWifiManager() {
return (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
}
@@ -326,8 +347,7 @@
}
private void maybeDunSettingChanged() {
- final boolean isDunRequired = TetheringConfiguration.checkDunRequired(
- mContext, mActiveDataSubId);
+ final boolean isDunRequired = TetheringConfiguration.checkDunRequired(mContext);
if (isDunRequired == mConfig.isDunRequired) return;
updateConfiguration();
}
@@ -1162,7 +1182,6 @@
}
class TetherMasterSM extends StateMachine {
- private static final int BASE_MASTER = Protocol.BASE_TETHERING;
// an interface SM has requested Tethering/Local Hotspot
static final int EVENT_IFACE_SERVING_STATE_ACTIVE = BASE_MASTER + 1;
// an interface SM has unrequested Tethering/Local Hotspot
@@ -1179,7 +1198,6 @@
static final int EVENT_IFACE_UPDATE_LINKPROPERTIES = BASE_MASTER + 7;
// Events from EntitlementManager to choose upstream again.
static final int EVENT_UPSTREAM_PERMISSION_CHANGED = BASE_MASTER + 8;
-
private final State mInitialState;
private final State mTetherModeAliveState;
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
index 0ab4d63..490614b 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -100,13 +100,13 @@
public final String provisioningAppNoUi;
public final int provisioningCheckPeriod;
- public final int subId;
+ public final int activeDataSubId;
public TetheringConfiguration(Context ctx, SharedLog log, int id) {
final SharedLog configLog = log.forSubComponent("config");
- subId = id;
- Resources res = getResources(ctx, subId);
+ activeDataSubId = id;
+ Resources res = getResources(ctx, activeDataSubId);
tetherableUsbRegexs = getResourceStringArray(res, config_tether_usb_regexs);
// TODO: Evaluate deleting this altogether now that Wi-Fi always passes
@@ -116,7 +116,7 @@
tetherableWifiP2pRegexs = getResourceStringArray(res, config_tether_wifi_p2p_regexs);
tetherableBluetoothRegexs = getResourceStringArray(res, config_tether_bluetooth_regexs);
- isDunRequired = checkDunRequired(ctx, subId);
+ isDunRequired = checkDunRequired(ctx);
chooseUpstreamAutomatically = getResourceBoolean(res, config_tether_upstream_automatic);
preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(res, isDunRequired);
@@ -166,8 +166,8 @@
/** Does the dumping.*/
public void dump(PrintWriter pw) {
- pw.print("subId: ");
- pw.println(subId);
+ pw.print("activeDataSubId: ");
+ pw.println(activeDataSubId);
dumpStringArray(pw, "tetherableUsbRegexs", tetherableUsbRegexs);
dumpStringArray(pw, "tetherableWifiRegexs", tetherableWifiRegexs);
@@ -196,7 +196,7 @@
/** Returns the string representation of this object.*/
public String toString() {
final StringJoiner sj = new StringJoiner(" ");
- sj.add(String.format("subId:%d", subId));
+ sj.add(String.format("activeDataSubId:%d", activeDataSubId));
sj.add(String.format("tetherableUsbRegexs:%s", makeString(tetherableUsbRegexs)));
sj.add(String.format("tetherableWifiRegexs:%s", makeString(tetherableWifiRegexs)));
sj.add(String.format("tetherableWifiP2pRegexs:%s", makeString(tetherableWifiP2pRegexs)));
@@ -250,9 +250,11 @@
}
/** Check whether dun is required. */
- public static boolean checkDunRequired(Context ctx, int id) {
+ public static boolean checkDunRequired(Context ctx) {
final TelephonyManager tm = (TelephonyManager) ctx.getSystemService(TELEPHONY_SERVICE);
- return (tm != null) ? tm.isTetheringApnRequired(id) : false;
+ // TelephonyManager would uses the active data subscription, which should be the one used
+ // by tethering.
+ return (tm != null) ? tm.isTetheringApnRequired() : false;
}
private static Collection<Integer> getUpstreamIfaceTypes(Resources res, boolean dunRequired) {
@@ -391,7 +393,7 @@
*/
public TetheringConfigurationParcel toStableParcelable() {
final TetheringConfigurationParcel parcel = new TetheringConfigurationParcel();
- parcel.subId = subId;
+ parcel.subId = activeDataSubId;
parcel.tetherableUsbRegexs = tetherableUsbRegexs;
parcel.tetherableWifiRegexs = tetherableWifiRegexs;
parcel.tetherableBluetoothRegexs = tetherableBluetoothRegexs;
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
index 1d59986..775484e 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
@@ -21,6 +21,7 @@
import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED;
+import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR;
import android.app.Service;
import android.content.Context;
@@ -340,7 +341,10 @@
service.makeDhcpServer(ifName, params, cb);
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ Log.e(TAG, "Fail to make dhcp server");
+ try {
+ cb.onDhcpServerCreated(STATUS_UNKNOWN_ERROR, null);
+ } catch (RemoteException re) { }
}
}
};
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index dc38c49a..22150f6 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -328,13 +328,6 @@
network, newNc));
}
- // Log changes in upstream network signal strength, if available.
- if (network.equals(mTetheringUpstreamNetwork) && newNc.hasSignalStrength()) {
- final int newSignal = newNc.getSignalStrength();
- final String prevSignal = getSignalStrength(prev.networkCapabilities);
- mLog.logf("upstream network signal strength: %s -> %s", prevSignal, newSignal);
- }
-
mNetworkMap.put(network, new UpstreamNetworkState(
prev.linkProperties, newNc, network));
// TODO: If sufficient information is available to select a more
@@ -557,11 +550,6 @@
return prefixSet;
}
- private static String getSignalStrength(NetworkCapabilities nc) {
- if (nc == null || !nc.hasSignalStrength()) return "unknown";
- return Integer.toString(nc.getSignalStrength());
- }
-
private static boolean isCellular(UpstreamNetworkState ns) {
return (ns != null) && isCellular(ns.networkCapabilities);
}
diff --git a/packages/Tethering/tests/unit/Android.bp b/packages/Tethering/tests/unit/Android.bp
index 81a0548..53782fed 100644
--- a/packages/Tethering/tests/unit/Android.bp
+++ b/packages/Tethering/tests/unit/Android.bp
@@ -20,7 +20,11 @@
srcs: [
"src/**/*.java",
],
- test_suites: ["device-tests"],
+ test_suites: [
+ "device-tests",
+ "mts",
+ ],
+ compile_multilib: "both",
static_libs: [
"androidx.test.rules",
"frameworks-base-testutils",
diff --git a/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java b/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java
index e01ac7f..e8add98 100644
--- a/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java
+++ b/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java
@@ -18,8 +18,6 @@
import static android.net.InetAddresses.parseNumericAddress;
-import static com.google.android.collect.Sets.newHashSet;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -34,6 +32,8 @@
import org.junit.runner.RunWith;
import java.net.Inet4Address;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -47,9 +47,10 @@
private static final int TEST_LEASE_TIME_SECS = 120;
private static final int TEST_MTU = 1000;
private static final Set<Inet4Address> TEST_ADDRESS_SET =
- newHashSet(inet4Addr("192.168.1.123"), inet4Addr("192.168.1.124"));
+ new HashSet<Inet4Address>(Arrays.asList(
+ new Inet4Address[] {inet4Addr("192.168.1.123"), inet4Addr("192.168.1.124")}));
private static final Set<Integer> TEST_ADDRESS_SET_PARCELED =
- newHashSet(0xc0a8017b, 0xc0a8017c);
+ new HashSet<Integer>(Arrays.asList(new Integer[] {0xc0a8017b, 0xc0a8017c}));
private DhcpServingParamsParcelExt mParcel;
diff --git a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 4358cd6..fd2f708 100644
--- a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -510,8 +510,10 @@
}
assertNotNull("missing IPv4 address", addr4);
+ final IpPrefix destination = new IpPrefix(addr4.getAddress(), addr4.getPrefixLength());
// Assert the presence of the associated directly connected route.
- final RouteInfo directlyConnected = new RouteInfo(addr4, null, lp.getInterfaceName());
+ final RouteInfo directlyConnected = new RouteInfo(destination, null, lp.getInterfaceName(),
+ RouteInfo.RTN_UNICAST);
assertTrue("missing directly connected route: '" + directlyConnected.toString() + "'",
lp.getRoutes().contains(directlyConnected));
}
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java
index 8574f54..7886ca6 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java
@@ -21,6 +21,7 @@
import static android.net.NetworkStats.STATS_PER_UID;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
+import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.TrafficStats.UID_TETHERING;
import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
@@ -269,7 +270,7 @@
final String ipv4Addr = "192.0.2.5";
final String linkAddr = ipv4Addr + "/24";
lp.addLinkAddress(new LinkAddress(linkAddr));
- lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24")));
+ lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, null, RTN_UNICAST));
offload.setUpstreamLinkProperties(lp);
// IPv4 prefixes and addresses on the upstream are simply left as whole
// prefixes (already passed in from UpstreamNetworkMonitor code). If a
@@ -285,7 +286,7 @@
inOrder.verifyNoMoreInteractions();
final String ipv4Gateway = "192.0.2.1";
- lp.addRoute(new RouteInfo(InetAddress.getByName(ipv4Gateway)));
+ lp.addRoute(new RouteInfo(null, InetAddress.getByName(ipv4Gateway), null, RTN_UNICAST));
offload.setUpstreamLinkProperties(lp);
// No change in local addresses means no call to setLocalPrefixes().
inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
@@ -296,7 +297,7 @@
inOrder.verifyNoMoreInteractions();
final String ipv6Gw1 = "fe80::cafe";
- lp.addRoute(new RouteInfo(InetAddress.getByName(ipv6Gw1)));
+ lp.addRoute(new RouteInfo(null, InetAddress.getByName(ipv6Gw1), null, RTN_UNICAST));
offload.setUpstreamLinkProperties(lp);
// No change in local addresses means no call to setLocalPrefixes().
inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
@@ -310,7 +311,7 @@
inOrder.verifyNoMoreInteractions();
final String ipv6Gw2 = "fe80::d00d";
- lp.addRoute(new RouteInfo(InetAddress.getByName(ipv6Gw2)));
+ lp.addRoute(new RouteInfo(null, InetAddress.getByName(ipv6Gw2), null, RTN_UNICAST));
offload.setUpstreamLinkProperties(lp);
// No change in local addresses means no call to setLocalPrefixes().
inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
@@ -327,8 +328,10 @@
final LinkProperties stacked = new LinkProperties();
stacked.setInterfaceName("stacked");
stacked.addLinkAddress(new LinkAddress("192.0.2.129/25"));
- stacked.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254")));
- stacked.addRoute(new RouteInfo(InetAddress.getByName("fe80::bad:f00")));
+ stacked.addRoute(new RouteInfo(null, InetAddress.getByName("192.0.2.254"), null,
+ RTN_UNICAST));
+ stacked.addRoute(new RouteInfo(null, InetAddress.getByName("fe80::bad:f00"), null,
+ RTN_UNICAST));
assertTrue(lp.addStackedLink(stacked));
offload.setUpstreamLinkProperties(lp);
// No change in local addresses means no call to setLocalPrefixes().
@@ -348,7 +351,7 @@
// removed from "local prefixes" and /128s added for the upstream IPv6
// addresses. This is not yet implemented, and for now we simply
// expect to see these /128s.
- lp.addRoute(new RouteInfo(new IpPrefix("2001:db8::/64")));
+ lp.addRoute(new RouteInfo(new IpPrefix("2001:db8::/64"), null, null, RTN_UNICAST));
// "2001:db8::/64" plus "assigned" ASCII in hex
lp.addLinkAddress(new LinkAddress("2001:db8::6173:7369:676e:6564/64"));
// "2001:db8::/64" plus "random" ASCII in hex
@@ -574,13 +577,15 @@
final LinkProperties usbLinkProperties = new LinkProperties();
usbLinkProperties.setInterfaceName(RNDIS0);
usbLinkProperties.addLinkAddress(new LinkAddress("192.168.42.1/24"));
- usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(USB_PREFIX)));
+ usbLinkProperties.addRoute(
+ new RouteInfo(new IpPrefix(USB_PREFIX), null, null, RTN_UNICAST));
offload.notifyDownstreamLinkProperties(usbLinkProperties);
inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, USB_PREFIX);
inOrder.verifyNoMoreInteractions();
// [2] Routes for IPv6 link-local prefixes should never be added.
- usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_LINKLOCAL)));
+ usbLinkProperties.addRoute(
+ new RouteInfo(new IpPrefix(IPV6_LINKLOCAL), null, null, RTN_UNICAST));
offload.notifyDownstreamLinkProperties(usbLinkProperties);
inOrder.verify(mHardware, never()).addDownstreamPrefix(eq(RNDIS0), anyString());
inOrder.verifyNoMoreInteractions();
@@ -588,7 +593,8 @@
// [3] Add an IPv6 prefix for good measure. Only new offload-able
// prefixes should be passed to the HAL.
usbLinkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64"));
- usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX)));
+ usbLinkProperties.addRoute(
+ new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX), null, null, RTN_UNICAST));
offload.notifyDownstreamLinkProperties(usbLinkProperties);
inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, IPV6_DOC_PREFIX);
inOrder.verifyNoMoreInteractions();
@@ -601,8 +607,10 @@
// [5] Differences in local routes are converted into addDownstream()
// and removeDownstream() invocations accordingly.
- usbLinkProperties.removeRoute(new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX), null, RNDIS0));
- usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_DISCARD_PREFIX)));
+ usbLinkProperties.removeRoute(
+ new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX), null, RNDIS0, RTN_UNICAST));
+ usbLinkProperties.addRoute(
+ new RouteInfo(new IpPrefix(IPV6_DISCARD_PREFIX), null, null, RTN_UNICAST));
offload.notifyDownstreamLinkProperties(usbLinkProperties);
inOrder.verify(mHardware, times(1)).removeDownstreamPrefix(RNDIS0, IPV6_DOC_PREFIX);
inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, IPV6_DISCARD_PREFIX);
@@ -680,19 +688,23 @@
final LinkProperties usbLinkProperties = new LinkProperties();
usbLinkProperties.setInterfaceName(RNDIS0);
usbLinkProperties.addLinkAddress(new LinkAddress("192.168.42.1/24"));
- usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(USB_PREFIX)));
+ usbLinkProperties.addRoute(
+ new RouteInfo(new IpPrefix(USB_PREFIX), null, null, RTN_UNICAST));
offload.notifyDownstreamLinkProperties(usbLinkProperties);
final LinkProperties wifiLinkProperties = new LinkProperties();
wifiLinkProperties.setInterfaceName(WLAN0);
wifiLinkProperties.addLinkAddress(new LinkAddress("192.168.43.1/24"));
- wifiLinkProperties.addRoute(new RouteInfo(new IpPrefix(WIFI_PREFIX)));
- wifiLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_LINKLOCAL)));
+ wifiLinkProperties.addRoute(
+ new RouteInfo(new IpPrefix(WIFI_PREFIX), null, null, RTN_UNICAST));
+ wifiLinkProperties.addRoute(
+ new RouteInfo(new IpPrefix(IPV6_LINKLOCAL), null, null, RTN_UNICAST));
// Use a benchmark prefix (RFC 5180 + erratum), since the documentation
// prefix is included in the excluded prefix list.
wifiLinkProperties.addLinkAddress(new LinkAddress("2001:2::1/64"));
wifiLinkProperties.addLinkAddress(new LinkAddress("2001:2::2/64"));
- wifiLinkProperties.addRoute(new RouteInfo(new IpPrefix("2001:2::/64")));
+ wifiLinkProperties.addRoute(
+ new RouteInfo(new IpPrefix("2001:2::/64"), null, null, RTN_UNICAST));
offload.notifyDownstreamLinkProperties(wifiLinkProperties);
offload.removeDownstreamInterface(RNDIS0);
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
index 30bff35..7799da4 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
@@ -34,7 +34,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.when;
import android.content.ContentResolver;
@@ -145,7 +144,7 @@
@Test
public void testDunFromTelephonyManagerMeansDun() {
- when(mTelephonyManager.isTetheringApnRequired(anyInt())).thenReturn(true);
+ when(mTelephonyManager.isTetheringApnRequired()).thenReturn(true);
final TetheringConfiguration cfgWifi = getTetheringConfiguration(TYPE_WIFI);
final TetheringConfiguration cfgMobileWifiHipri = getTetheringConfiguration(
@@ -169,7 +168,7 @@
@Test
public void testDunNotRequiredFromTelephonyManagerMeansNoDun() {
- when(mTelephonyManager.isTetheringApnRequired(anyInt())).thenReturn(false);
+ when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
final TetheringConfiguration cfgWifi = getTetheringConfiguration(TYPE_WIFI);
final TetheringConfiguration cfgMobileWifiHipri = getTetheringConfiguration(
@@ -212,7 +211,7 @@
@Test
public void testNoDefinedUpstreamTypesAddsEthernet() {
when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(new int[]{});
- when(mTelephonyManager.isTetheringApnRequired(anyInt())).thenReturn(false);
+ when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
final TetheringConfiguration cfg = new TetheringConfiguration(
mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
@@ -235,7 +234,7 @@
public void testDefinedUpstreamTypesSansEthernetAddsEthernet() {
when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(
new int[]{TYPE_WIFI, TYPE_MOBILE_HIPRI});
- when(mTelephonyManager.isTetheringApnRequired(anyInt())).thenReturn(false);
+ when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
final TetheringConfiguration cfg = new TetheringConfiguration(
mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
@@ -253,7 +252,7 @@
public void testDefinedUpstreamTypesWithEthernetDoesNotAddEthernet() {
when(mResources.getIntArray(config_tether_upstream_types))
.thenReturn(new int[]{TYPE_WIFI, TYPE_ETHERNET, TYPE_MOBILE_HIPRI});
- when(mTelephonyManager.isTetheringApnRequired(anyInt())).thenReturn(false);
+ when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
final TetheringConfiguration cfg = new TetheringConfiguration(
mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index 9d9ad10..04b2eb4 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
@@ -28,6 +28,7 @@
import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
import static android.net.ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
+import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
@@ -72,6 +73,7 @@
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
import android.net.ITetheringEventCallback;
+import android.net.InetAddresses;
import android.net.InterfaceConfiguration;
import android.net.IpPrefix;
import android.net.LinkAddress;
@@ -81,7 +83,6 @@
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkRequest;
-import android.net.NetworkUtils;
import android.net.RouteInfo;
import android.net.TetherStatesParcel;
import android.net.TetheringConfigurationParcel;
@@ -365,23 +366,26 @@
if (withIPv4) {
prop.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0),
- NetworkUtils.numericToInetAddress("10.0.0.1"), TEST_MOBILE_IFNAME));
+ InetAddresses.parseNumericAddress("10.0.0.1"),
+ TEST_MOBILE_IFNAME, RTN_UNICAST));
}
if (withIPv6) {
- prop.addDnsServer(NetworkUtils.numericToInetAddress("2001:db8::2"));
+ prop.addDnsServer(InetAddresses.parseNumericAddress("2001:db8::2"));
prop.addLinkAddress(
- new LinkAddress(NetworkUtils.numericToInetAddress("2001:db8::"),
+ new LinkAddress(InetAddresses.parseNumericAddress("2001:db8::"),
NetworkConstants.RFC7421_PREFIX_LENGTH));
prop.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0),
- NetworkUtils.numericToInetAddress("2001:db8::1"), TEST_MOBILE_IFNAME));
+ InetAddresses.parseNumericAddress("2001:db8::1"),
+ TEST_MOBILE_IFNAME, RTN_UNICAST));
}
if (with464xlat) {
final LinkProperties stackedLink = new LinkProperties();
stackedLink.setInterfaceName(TEST_XLAT_MOBILE_IFNAME);
stackedLink.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0),
- NetworkUtils.numericToInetAddress("192.0.0.1"), TEST_XLAT_MOBILE_IFNAME));
+ InetAddresses.parseNumericAddress("192.0.0.1"),
+ TEST_XLAT_MOBILE_IFNAME, RTN_UNICAST));
prop.addStackedLink(stackedLink);
}
@@ -1210,12 +1214,12 @@
@Test
public void testMultiSimAware() throws Exception {
final TetheringConfiguration initailConfig = mTethering.getTetheringConfiguration();
- assertEquals(INVALID_SUBSCRIPTION_ID, initailConfig.subId);
+ assertEquals(INVALID_SUBSCRIPTION_ID, initailConfig.activeDataSubId);
final int fakeSubId = 1234;
mPhoneStateListener.onActiveDataSubscriptionIdChanged(fakeSubId);
final TetheringConfiguration newConfig = mTethering.getTetheringConfiguration();
- assertEquals(fakeSubId, newConfig.subId);
+ assertEquals(fakeSubId, newConfig.activeDataSubId);
}
private void workingWifiP2pGroupOwner(
diff --git a/services/Android.bp b/services/Android.bp
index ede4c3e..1e11936 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -61,6 +61,7 @@
"services.devicepolicy",
"services.midi",
"services.net",
+ "services.people",
"services.print",
"services.restrictions",
"services.startop",
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 8d22300..a1f57cb 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -117,6 +117,7 @@
"android.hardware.oemlock-V1.0-java",
"android.hardware.configstore-V1.0-java",
"android.hardware.contexthub-V1.0-java",
+ "android.hardware.rebootescrow-java",
"android.hardware.soundtrigger-V2.3-java",
"android.hidl.manager-V1.2-java",
"dnsresolver_aidl_interface-V2-java",
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 40a7dfc..312dd46 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -319,6 +319,12 @@
int deviceOwnerUserId, String deviceOwner, SparseArray<String> profileOwners);
/**
+ * Called by DevicePolicyManagerService to set the package names protected by the device
+ * owner.
+ */
+ public abstract void setDeviceOwnerProtectedPackages(List<String> packageNames);
+
+ /**
* Returns {@code true} if a given package can't be wiped. Otherwise, returns {@code false}.
*/
public abstract boolean isPackageDataProtected(int userId, String packageName);
diff --git a/services/core/java/com/android/server/CountryDetectorService.java b/services/core/java/com/android/server/CountryDetectorService.java
index 861c731..b0132d3 100644
--- a/services/core/java/com/android/server/CountryDetectorService.java
+++ b/services/core/java/com/android/server/CountryDetectorService.java
@@ -24,21 +24,29 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.text.TextUtils;
import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.server.location.ComprehensiveCountryDetector;
+import com.android.server.location.CountryDetectorBase;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
/**
- * This class detects the country that the user is in through {@link ComprehensiveCountryDetector}.
+ * This class detects the country that the user is in. The default country detection is made through
+ * {@link com.android.server.location.ComprehensiveCountryDetector}. It is possible to overlay the
+ * detection algorithm by overlaying the attribute R.string.config_customCountryDetector with the
+ * custom class name to use instead. The custom class must extend
+ * {@link com.android.server.location.CountryDetectorBase}
*
* @hide
*/
@@ -88,7 +96,7 @@
private final HashMap<IBinder, Receiver> mReceivers;
private final Context mContext;
- private ComprehensiveCountryDetector mCountryDetector;
+ private CountryDetectorBase mCountryDetector;
private boolean mSystemReady;
private Handler mHandler;
private CountryListener mLocationBasedDetectorListener;
@@ -184,8 +192,17 @@
});
}
- private void initialize() {
- mCountryDetector = new ComprehensiveCountryDetector(mContext);
+ @VisibleForTesting
+ void initialize() {
+ final String customCountryClass = mContext.getString(R.string.config_customCountryDetector);
+ if (!TextUtils.isEmpty(customCountryClass)) {
+ mCountryDetector = loadCustomCountryDetectorIfAvailable(customCountryClass);
+ }
+
+ if (mCountryDetector == null) {
+ Slog.d(TAG, "Using default country detector");
+ mCountryDetector = new ComprehensiveCountryDetector(mContext);
+ }
mLocationBasedDetectorListener = country -> mHandler.post(() -> notifyReceivers(country));
}
@@ -194,10 +211,32 @@
}
@VisibleForTesting
+ CountryDetectorBase getCountryDetector() {
+ return mCountryDetector;
+ }
+
+ @VisibleForTesting
boolean isSystemReady() {
return mSystemReady;
}
+ private CountryDetectorBase loadCustomCountryDetectorIfAvailable(
+ final String customCountryClass) {
+ CountryDetectorBase customCountryDetector = null;
+
+ Slog.d(TAG, "Using custom country detector class: " + customCountryClass);
+ try {
+ customCountryDetector = Class.forName(customCountryClass).asSubclass(
+ CountryDetectorBase.class).getConstructor(Context.class).newInstance(
+ mContext);
+ } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
+ | NoSuchMethodException | InvocationTargetException e) {
+ Slog.e(TAG, "Could not instantiate the custom country detector class");
+ }
+
+ return customCountryDetector;
+ }
+
@SuppressWarnings("unused")
@Override
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
@@ -206,9 +245,10 @@
try {
final Printer p = new PrintWriterPrinter(fout);
p.println("CountryDetectorService state:");
+ p.println("Country detector class=" + mCountryDetector.getClass().getName());
p.println(" Number of listeners=" + mReceivers.keySet().size());
if (mCountryDetector == null) {
- p.println(" ComprehensiveCountryDetector not initialized");
+ p.println(" CountryDetector not initialized");
} else {
p.println(" " + mCountryDetector.toString());
}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 8f99c1c..3e6ccb5 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -23,6 +23,7 @@
import static java.util.Arrays.copyOf;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AppOpsManager;
@@ -41,6 +42,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.telephony.Annotation;
+import android.telephony.Annotation.ApnType;
import android.telephony.Annotation.DataFailureCause;
import android.telephony.Annotation.RadioPowerState;
import android.telephony.Annotation.SrvccState;
@@ -81,7 +83,6 @@
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
import com.android.internal.telephony.IPhoneStateListener;
import com.android.internal.telephony.ITelephonyRegistry;
-import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.TelephonyPermissions;
import com.android.internal.util.ArrayUtils;
@@ -268,8 +269,8 @@
private final LocalLog mListenLog = new LocalLog(100);
// Per-phoneMap of APN Type to DataConnectionState
- private List<Map<String, PreciseDataConnectionState>> mPreciseDataConnectionStates =
- new ArrayList<Map<String, PreciseDataConnectionState>>();
+ private List<Map<Integer, PreciseDataConnectionState>> mPreciseDataConnectionStates =
+ new ArrayList<Map<Integer, PreciseDataConnectionState>>();
// Nothing here yet, but putting it here in case we want to add more in the future.
static final int ENFORCE_COARSE_LOCATION_PERMISSION_MASK = 0;
@@ -281,11 +282,12 @@
static final int ENFORCE_PHONE_STATE_PERMISSION_MASK =
PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR
| PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR
- | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST;
+ | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST
+ | PhoneStateListener.LISTEN_REGISTRATION_FAILURE;
static final int PRECISE_PHONE_STATE_PERMISSION_MASK =
- PhoneStateListener.LISTEN_PRECISE_CALL_STATE |
- PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE;
+ PhoneStateListener.LISTEN_PRECISE_CALL_STATE
+ | PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE;
static final int READ_ACTIVE_EMERGENCY_SESSION_PERMISSION_MASK =
PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_CALL
@@ -465,7 +467,7 @@
mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
- mPreciseDataConnectionStates.add(new HashMap<String, PreciseDataConnectionState>());
+ mPreciseDataConnectionStates.add(new HashMap<Integer, PreciseDataConnectionState>());
}
}
@@ -549,7 +551,7 @@
mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
- mPreciseDataConnectionStates.add(new HashMap<String, PreciseDataConnectionState>());
+ mPreciseDataConnectionStates.add(new HashMap<Integer, PreciseDataConnectionState>());
}
mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -1494,11 +1496,12 @@
*
* @param phoneId the phoneId carrying the data connection
* @param subId the subscriptionId for the data connection
- * @param apnType the APN type that triggered a change in the data connection
+ * @param apnType the apn type bitmask, defined with {@code ApnSetting#TYPE_*} flags.
* @param preciseState a PreciseDataConnectionState that has info about the data connection
*/
+ @Override
public void notifyDataConnectionForSubscriber(
- int phoneId, int subId, String apnType, PreciseDataConnectionState preciseState) {
+ int phoneId, int subId, @ApnType int apnType, PreciseDataConnectionState preciseState) {
if (!checkNotifyPermission("notifyDataConnection()" )) {
return;
}
@@ -1524,7 +1527,7 @@
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
// We only call the callback when the change is for default APN type.
- if (PhoneConstants.APN_TYPE_DEFAULT.equals(apnType)
+ if ((ApnSetting.TYPE_DEFAULT & apnType) != 0
&& (mDataConnectionState[phoneId] != state
|| mDataConnectionNetworkType[phoneId] != networkType)) {
String str = "onDataConnectionStateChanged("
@@ -1593,7 +1596,7 @@
loge("This function should not be invoked");
}
- private void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, String apnType) {
+ private void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, int apnType) {
if (!checkNotifyPermission("notifyDataConnectionFailed()")) {
return;
}
@@ -1608,7 +1611,7 @@
new PreciseDataConnectionState(
TelephonyManager.DATA_UNKNOWN,
TelephonyManager.NETWORK_TYPE_UNKNOWN,
- ApnSetting.getApnTypesBitmaskFromString(apnType), null, null,
+ apnType, null, null,
DataFailCause.NONE, null));
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
@@ -1777,7 +1780,8 @@
}
}
- public void notifyPreciseDataConnectionFailed(int phoneId, int subId, String apnType,
+ @Override
+ public void notifyPreciseDataConnectionFailed(int phoneId, int subId, @ApnType int apnType,
String apn, @DataFailureCause int failCause) {
if (!checkNotifyPermission("notifyPreciseDataConnectionFailed()")) {
return;
@@ -1793,7 +1797,7 @@
new PreciseDataConnectionState(
TelephonyManager.DATA_UNKNOWN,
TelephonyManager.NETWORK_TYPE_UNKNOWN,
- ApnSetting.getApnTypesBitmaskFromString(apnType), null, null,
+ apnType, null, null,
failCause, null));
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
@@ -2066,6 +2070,40 @@
}
@Override
+ public void notifyRegistrationFailed(int phoneId, int subId, @NonNull CellIdentity cellIdentity,
+ @NonNull String chosenPlmn, int domain, int causeCode, int additionalCauseCode) {
+ if (!checkNotifyPermission("notifyRegistrationFailed()")) {
+ return;
+ }
+
+ // In case callers don't have fine location access, pre-construct a location-free version
+ // of the CellIdentity. This will still have the PLMN ID, which should be sufficient for
+ // most purposes.
+ final CellIdentity noLocationCi = cellIdentity.sanitizeLocationInfo();
+
+ synchronized (mRecords) {
+ if (validatePhoneId(phoneId)) {
+ for (Record r : mRecords) {
+ if (r.matchPhoneStateListenerEvent(
+ PhoneStateListener.LISTEN_REGISTRATION_FAILURE)
+ && idMatch(r.subId, subId, phoneId)) {
+ try {
+ r.callback.onRegistrationFailed(
+ checkFineLocationAccess(r, Build.VERSION_CODES.R)
+ ? cellIdentity : noLocationCi,
+ chosenPlmn, domain, causeCode,
+ additionalCauseCode);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
@@ -2141,10 +2179,19 @@
/** Fired when a subscription's phone state changes. */
private static final String ACTION_SUBSCRIPTION_PHONE_STATE_CHANGED =
"android.intent.action.SUBSCRIPTION_PHONE_STATE";
+ /**
+ * Broadcast Action: The data connection state has changed for any one of the
+ * phone's mobile data connections (eg, default, MMS or GPS specific connection).
+ */
+ private static final String ACTION_ANY_DATA_CONNECTION_STATE_CHANGED =
+ "android.intent.action.ANY_DATA_STATE";
// Legacy intent extra keys, copied from PhoneConstants.
// Used in legacy intents sent here, for backward compatibility.
+ private static final String PHONE_CONSTANTS_DATA_APN_TYPE_KEY = "apnType";
+ private static final String PHONE_CONSTANTS_DATA_APN_KEY = "apn";
private static final String PHONE_CONSTANTS_SLOT_KEY = "slot";
+ private static final String PHONE_CONSTANTS_STATE_KEY = "state";
private static final String PHONE_CONSTANTS_SUBSCRIPTION_KEY = "subscription";
private void broadcastServiceStateChanged(ServiceState state, int phoneId, int subId) {
@@ -2282,19 +2329,50 @@
}
private void broadcastDataConnectionStateChanged(int state, String apn,
- String apnType, int subId) {
+ int apnType, int subId) {
// Note: not reporting to the battery stats service here, because the
// status bar takes care of that after taking into account all of the
// required info.
- Intent intent = new Intent(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
- intent.putExtra(TelephonyManager.EXTRA_STATE, dataStateToString(state));
-
- intent.putExtra(PhoneConstants.DATA_APN_KEY, apn);
- intent.putExtra(PhoneConstants.DATA_APN_TYPE_KEY, apnType);
+ Intent intent = new Intent(ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
+ intent.putExtra(PHONE_CONSTANTS_STATE_KEY, dataStateToString(state));
+ intent.putExtra(PHONE_CONSTANTS_DATA_APN_KEY, apn);
+ intent.putExtra(PHONE_CONSTANTS_DATA_APN_TYPE_KEY, getApnTypesStringFromBitmask(apnType));
intent.putExtra(PHONE_CONSTANTS_SUBSCRIPTION_KEY, subId);
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
+ private static final Map<Integer, String> APN_TYPE_INT_MAP;
+ static {
+ APN_TYPE_INT_MAP = new android.util.ArrayMap<Integer, String>();
+ APN_TYPE_INT_MAP.put(ApnSetting.TYPE_DEFAULT, "default");
+ APN_TYPE_INT_MAP.put(ApnSetting.TYPE_MMS, "mms");
+ APN_TYPE_INT_MAP.put(ApnSetting.TYPE_SUPL, "supl");
+ APN_TYPE_INT_MAP.put(ApnSetting.TYPE_DUN, "dun");
+ APN_TYPE_INT_MAP.put(ApnSetting.TYPE_HIPRI, "hipri");
+ APN_TYPE_INT_MAP.put(ApnSetting.TYPE_FOTA, "fota");
+ APN_TYPE_INT_MAP.put(ApnSetting.TYPE_IMS, "ims");
+ APN_TYPE_INT_MAP.put(ApnSetting.TYPE_CBS, "cbs");
+ APN_TYPE_INT_MAP.put(ApnSetting.TYPE_IA, "ia");
+ APN_TYPE_INT_MAP.put(ApnSetting.TYPE_EMERGENCY, "emergency");
+ APN_TYPE_INT_MAP.put(ApnSetting.TYPE_MCX, "mcx");
+ APN_TYPE_INT_MAP.put(ApnSetting.TYPE_XCAP, "xcap");
+ }
+
+ /**
+ * Copy of ApnSetting#getApnTypesStringFromBitmask for legacy broadcast.
+ * @param apnTypeBitmask bitmask of APN types.
+ * @return comma delimited list of APN types.
+ */
+ private static String getApnTypesStringFromBitmask(int apnTypeBitmask) {
+ List<String> types = new ArrayList<>();
+ for (Integer type : APN_TYPE_INT_MAP.keySet()) {
+ if ((apnTypeBitmask & type) == type) {
+ types.add(APN_TYPE_INT_MAP.get(type));
+ }
+ }
+ return android.text.TextUtils.join(",", types);
+ }
+
private void enforceNotifyPermissionOrCarrierPrivilege(String method) {
if (checkNotifyPermission()) {
return;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 4bc0297..c21adb0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18673,6 +18673,11 @@
}
@Override
+ public void monitor() {
+ ActivityManagerService.this.monitor();
+ }
+
+ @Override
public long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason) {
synchronized (ActivityManagerService.this) {
return ActivityManagerService.this.inputDispatchingTimedOut(
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 5bbb517..a7593c7 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -17,6 +17,11 @@
package com.android.server.appop;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
+import static android.app.AppOpsManager.FILTER_BY_FEATURE_ID;
+import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
+import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
+import static android.app.AppOpsManager.FILTER_BY_UID;
+import static android.app.AppOpsManager.HistoricalOpsRequestFilter;
import static android.app.AppOpsManager.NoteOpEvent;
import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_COARSE_LOCATION;
@@ -53,7 +58,6 @@
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AppOpsManager.HistoricalOps;
-import android.app.AppOpsManager.HistoricalOpsRequest;
import android.app.AppOpsManager.Mode;
import android.app.AppOpsManager.OpEntry;
import android.app.AppOpsManager.OpFeatureEntry;
@@ -632,6 +636,7 @@
}
private final class FeatureOp {
+ public final @Nullable String featureId;
public final @NonNull Op parent;
/**
@@ -658,7 +663,8 @@
@GuardedBy("AppOpsService.this")
private @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mInProgressEvents;
- FeatureOp(@NonNull Op parent) {
+ FeatureOp(@Nullable String featureId, @NonNull Op parent) {
+ this.featureId = featureId;
this.parent = parent;
}
@@ -676,6 +682,9 @@
@OpFlags int flags) {
accessed(System.currentTimeMillis(), -1, proxyUid, proxyPackageName,
proxyFeatureId, uidState, flags);
+
+ mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName,
+ featureId, uidState, flags);
}
/**
@@ -720,6 +729,9 @@
*/
public void rejected(@AppOpsManager.UidState int uidState, @OpFlags int flags) {
rejected(System.currentTimeMillis(), uidState, flags);
+
+ mHistoricalRegistry.incrementOpRejected(parent.op, parent.uid, parent.packageName,
+ featureId, uidState, flags);
}
/**
@@ -780,7 +792,7 @@
// startOp events don't support proxy, hence use flags==SELF
mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName,
- uidState, OP_FLAG_SELF);
+ featureId, uidState, OP_FLAG_SELF);
}
/**
@@ -820,8 +832,8 @@
mAccessEvents.put(makeKey(event.getUidState(), OP_FLAG_SELF), finishedEvent);
mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid,
- parent.packageName, event.getUidState(), AppOpsManager.OP_FLAG_SELF,
- finishedEvent.getDuration());
+ parent.packageName, featureId, event.getUidState(),
+ AppOpsManager.OP_FLAG_SELF, finishedEvent.getDuration());
mInProgressStartOpEventPool.release(event);
@@ -1031,7 +1043,7 @@
featureOp = mFeatures.get(featureId);
if (featureOp == null) {
- featureOp = new FeatureOp(parent);
+ featureOp = new FeatureOp(featureId, parent);
mFeatures.put(featureId, featureOp);
}
@@ -1697,18 +1709,47 @@
}
}
+ /**
+ * Verify that historical appop request arguments are valid.
+ */
+ private void ensureHistoricalOpRequestIsValid(int uid, String packageName, String featureId,
+ List<String> opNames, int filter, long beginTimeMillis, long endTimeMillis,
+ int flags) {
+ if ((filter & FILTER_BY_UID) != 0) {
+ Preconditions.checkArgument(uid != Process.INVALID_UID);
+ } else {
+ Preconditions.checkArgument(uid == Process.INVALID_UID);
+ }
+
+ if ((filter & FILTER_BY_PACKAGE_NAME) != 0) {
+ Objects.requireNonNull(packageName);
+ } else {
+ Preconditions.checkArgument(packageName == null);
+ }
+
+ if ((filter & FILTER_BY_FEATURE_ID) == 0) {
+ Preconditions.checkArgument(featureId == null);
+ }
+
+ if ((filter & FILTER_BY_OP_NAMES) != 0) {
+ Objects.requireNonNull(opNames);
+ } else {
+ Preconditions.checkArgument(opNames == null);
+ }
+
+ Preconditions.checkFlagsArgument(filter,
+ FILTER_BY_UID | FILTER_BY_PACKAGE_NAME | FILTER_BY_FEATURE_ID | FILTER_BY_OP_NAMES);
+ Preconditions.checkArgumentNonnegative(beginTimeMillis);
+ Preconditions.checkArgument(endTimeMillis > beginTimeMillis);
+ Preconditions.checkFlagsArgument(flags, OP_FLAGS_ALL);
+ }
+
@Override
- public void getHistoricalOps(int uid, @NonNull String packageName,
- @Nullable List<String> opNames, long beginTimeMillis, long endTimeMillis,
- @OpFlags int flags, @NonNull RemoteCallback callback) {
- // Use the builder to validate arguments.
- new HistoricalOpsRequest.Builder(
- beginTimeMillis, endTimeMillis)
- .setUid(uid)
- .setPackageName(packageName)
- .setOpNames(opNames)
- .setFlags(flags)
- .build();
+ public void getHistoricalOps(int uid, String packageName, String featureId,
+ List<String> opNames, int filter, long beginTimeMillis, long endTimeMillis,
+ int flags, RemoteCallback callback) {
+ ensureHistoricalOpRequestIsValid(uid, packageName, featureId, opNames, filter,
+ beginTimeMillis, endTimeMillis, flags);
Objects.requireNonNull(callback, "callback cannot be null");
mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
@@ -1718,22 +1759,16 @@
? opNames.toArray(new String[opNames.size()]) : null;
// Must not hold the appops lock
- mHistoricalRegistry.getHistoricalOps(uid, packageName, opNamesArray,
+ mHistoricalRegistry.getHistoricalOps(uid, packageName, featureId, opNamesArray, filter,
beginTimeMillis, endTimeMillis, flags, callback);
}
@Override
- public void getHistoricalOpsFromDiskRaw(int uid, @NonNull String packageName,
- @Nullable List<String> opNames, long beginTimeMillis, long endTimeMillis,
- @OpFlags int flags, @NonNull RemoteCallback callback) {
- // Use the builder to validate arguments.
- new HistoricalOpsRequest.Builder(
- beginTimeMillis, endTimeMillis)
- .setUid(uid)
- .setPackageName(packageName)
- .setOpNames(opNames)
- .setFlags(flags)
- .build();
+ public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String featureId,
+ List<String> opNames, int filter, long beginTimeMillis, long endTimeMillis,
+ int flags, RemoteCallback callback) {
+ ensureHistoricalOpRequestIsValid(uid, packageName, featureId, opNames, filter,
+ beginTimeMillis, endTimeMillis, flags);
Objects.requireNonNull(callback, "callback cannot be null");
mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
@@ -1743,8 +1778,8 @@
? opNames.toArray(new String[opNames.size()]) : null;
// Must not hold the appops lock
- mHistoricalRegistry.getHistoricalOpsFromDiskRaw(uid, packageName, opNamesArray,
- beginTimeMillis, endTimeMillis, flags, callback);
+ mHistoricalRegistry.getHistoricalOpsFromDiskRaw(uid, packageName, featureId, opNamesArray,
+ filter, beginTimeMillis, endTimeMillis, flags, callback);
}
@Override
@@ -2631,8 +2666,6 @@
+ switchCode + " (" + code + ") uid " + uid + " package "
+ packageName);
featureOp.rejected(uidState.state, flags);
- mHistoricalRegistry.incrementOpRejected(code, uid, packageName,
- uidState.state, flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName, uidMode);
return uidMode;
}
@@ -2645,8 +2678,6 @@
+ switchCode + " (" + code + ") uid " + uid + " package "
+ packageName);
featureOp.rejected(uidState.state, flags);
- mHistoricalRegistry.incrementOpRejected(code, uid, packageName,
- uidState.state, flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName, mode);
return mode;
}
@@ -2654,9 +2685,6 @@
if (DEBUG) Slog.d(TAG, "noteOperation: allowing code " + code + " uid " + uid
+ " package " + packageName + (featureId == null ? "" : "." + featureId));
featureOp.accessed(proxyUid, proxyPackageName, proxyFeatureId, uidState.state, flags);
- // TODO moltmann: Add features to historical app-ops
- mHistoricalRegistry.incrementOpAccessedCount(op.op, uid, packageName,
- uidState.state, flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName,
AppOpsManager.MODE_ALLOWED);
@@ -2938,8 +2966,6 @@
+ switchCode + " (" + code + ") uid " + uid + " package "
+ resolvedPackageName);
featureOp.rejected(uidState.state, AppOpsManager.OP_FLAG_SELF);
- mHistoricalRegistry.incrementOpRejected(opCode, uid, packageName,
- uidState.state, AppOpsManager.OP_FLAG_SELF);
return uidMode;
}
} else {
@@ -2952,8 +2978,6 @@
+ switchCode + " (" + code + ") uid " + uid + " package "
+ resolvedPackageName);
featureOp.rejected(uidState.state, AppOpsManager.OP_FLAG_SELF);
- mHistoricalRegistry.incrementOpRejected(opCode, uid, packageName,
- uidState.state, AppOpsManager.OP_FLAG_SELF);
return mode;
}
}
@@ -4437,16 +4461,24 @@
pw.println(" Limit output to data associated with the given app op mode.");
pw.println(" --package [PACKAGE]");
pw.println(" Limit output to data associated with the given package name.");
+ pw.println(" --featureId [featureId]");
+ pw.println(" Limit output to data associated with the given feature id.");
pw.println(" --watchers");
pw.println(" Only output the watcher sections.");
pw.println(" --history");
pw.println(" Output the historical data.");
}
- private void dumpStatesLocked(@NonNull PrintWriter pw, long nowElapsed, @NonNull Op op,
- long now, @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) {
+ private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterFeatureId,
+ @HistoricalOpsRequestFilter int filter, long nowElapsed, @NonNull Op op, long now,
+ @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) {
final int numFeatures = op.mFeatures.size();
for (int i = 0; i < numFeatures; i++) {
+ if ((filter & FILTER_BY_FEATURE_ID) != 0 && !Objects.equals(op.mFeatures.keyAt(i),
+ filterFeatureId)) {
+ continue;
+ }
+
pw.print(prefix + op.mFeatures.keyAt(i) + "=[\n");
dumpStatesLocked(pw, nowElapsed, op, op.mFeatures.keyAt(i), now, sdf, date,
prefix + " ");
@@ -4563,10 +4595,12 @@
int dumpOp = OP_NONE;
String dumpPackage = null;
+ String dumpFeatureId = null;
int dumpUid = Process.INVALID_UID;
int dumpMode = -1;
boolean dumpWatchers = false;
boolean dumpHistory = false;
+ @HistoricalOpsRequestFilter int dumpFilter = 0;
if (args != null) {
for (int i=0; i<args.length; i++) {
@@ -4583,6 +4617,7 @@
return;
}
dumpOp = Shell.strOpToOp(args[i], pw);
+ dumpFilter |= FILTER_BY_OP_NAMES;
if (dumpOp < 0) {
return;
}
@@ -4593,6 +4628,7 @@
return;
}
dumpPackage = args[i];
+ dumpFilter |= FILTER_BY_PACKAGE_NAME;
try {
dumpUid = AppGlobals.getPackageManager().getPackageUid(dumpPackage,
PackageManager.MATCH_KNOWN_PACKAGES | PackageManager.MATCH_INSTANT,
@@ -4604,6 +4640,15 @@
return;
}
dumpUid = UserHandle.getAppId(dumpUid);
+ dumpFilter |= FILTER_BY_UID;
+ } else if ("--featureId".equals(arg)) {
+ i++;
+ if (i >= args.length) {
+ pw.println("No argument for --featureId option");
+ return;
+ }
+ dumpFeatureId = args[i];
+ dumpFilter |= FILTER_BY_FEATURE_ID;
} else if ("--mode".equals(arg)) {
i++;
if (i >= args.length) {
@@ -4640,8 +4685,8 @@
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
final Date date = new Date();
boolean needSep = false;
- if (dumpOp < 0 && dumpMode < 0 && dumpPackage == null && mProfileOwners != null
- && !dumpWatchers && !dumpHistory) {
+ if (dumpFilter == 0 && dumpMode < 0 && mProfileOwners != null && !dumpWatchers
+ && !dumpHistory) {
pw.println(" Profile owners:");
for (int poi = 0; poi < mProfileOwners.size(); poi++) {
pw.print(" User #");
@@ -4944,7 +4989,8 @@
pw.print("="); pw.print(AppOpsManager.modeToName(mode));
}
pw.println("): ");
- dumpStatesLocked(pw, nowElapsed, op, now, sdf, date, " ");
+ dumpStatesLocked(pw, dumpFeatureId, dumpFilter, nowElapsed, op, now, sdf,
+ date, " ");
}
}
}
@@ -5043,7 +5089,8 @@
// Must not hold the appops lock
if (dumpHistory && !dumpWatchers) {
- mHistoricalRegistry.dump(" ", pw, dumpUid, dumpPackage, dumpOp);
+ mHistoricalRegistry.dump(" ", pw, dumpUid, dumpPackage, dumpFeatureId, dumpOp,
+ dumpFilter);
}
}
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index 2175ca0..cd450d4 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -15,12 +15,19 @@
*/
package com.android.server.appop;
+import static android.app.AppOpsManager.FILTER_BY_FEATURE_ID;
+import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
+import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
+import static android.app.AppOpsManager.FILTER_BY_UID;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
+import android.app.AppOpsManager.HistoricalFeatureOps;
import android.app.AppOpsManager.HistoricalMode;
import android.app.AppOpsManager.HistoricalOp;
import android.app.AppOpsManager.HistoricalOps;
+import android.app.AppOpsManager.HistoricalOpsRequestFilter;
import android.app.AppOpsManager.HistoricalPackageOps;
import android.app.AppOpsManager.HistoricalUidOps;
import android.app.AppOpsManager.OpFlags;
@@ -72,6 +79,7 @@
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -273,8 +281,9 @@
+ "=" + setting + " resetting!");
}
- void dump(String prefix, PrintWriter pw, int filterUid,
- String filterPackage, int filterOp) {
+ void dump(String prefix, PrintWriter pw, int filterUid, @Nullable String filterPackage,
+ @Nullable String filterFeatureId, int filterOp,
+ @HistoricalOpsRequestFilter int filter) {
if (!isApiEnabled()) {
return;
}
@@ -289,7 +298,7 @@
pw.println(AppOpsManager.historicalModeToString(mMode));
final StringDumpVisitor visitor = new StringDumpVisitor(prefix + " ",
- pw, filterUid, filterPackage, filterOp);
+ pw, filterUid, filterPackage, filterFeatureId, filterOp, filter);
final long nowMillis = System.currentTimeMillis();
// Dump in memory state first
@@ -329,7 +338,8 @@
}
void getHistoricalOpsFromDiskRaw(int uid, @NonNull String packageName,
- @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
+ @Nullable String featureId, @Nullable String[] opNames,
+ @HistoricalOpsRequestFilter int filter, long beginTimeMillis, long endTimeMillis,
@OpFlags int flags, @NonNull RemoteCallback callback) {
if (!isApiEnabled()) {
callback.sendResult(new Bundle());
@@ -344,8 +354,8 @@
return;
}
final HistoricalOps result = new HistoricalOps(beginTimeMillis, endTimeMillis);
- mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, opNames,
- beginTimeMillis, endTimeMillis, flags);
+ mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, featureId,
+ opNames, filter, beginTimeMillis, endTimeMillis, flags);
final Bundle payload = new Bundle();
payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result);
callback.sendResult(payload);
@@ -353,9 +363,10 @@
}
}
- void getHistoricalOps(int uid, @NonNull String packageName,
- @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
- @OpFlags int flags, @NonNull RemoteCallback callback) {
+ void getHistoricalOps(int uid, @NonNull String packageName, @Nullable String featureId,
+ @Nullable String[] opNames, @HistoricalOpsRequestFilter int filter,
+ long beginTimeMillis, long endTimeMillis, @OpFlags int flags,
+ @NonNull RemoteCallback callback) {
if (!isApiEnabled()) {
callback.sendResult(new Bundle());
return;
@@ -390,8 +401,8 @@
|| inMemoryAdjEndTimeMillis <= currentOps.getBeginTimeMillis())) {
// Some of the current batch falls into the query, so extract that.
final HistoricalOps currentOpsCopy = new HistoricalOps(currentOps);
- currentOpsCopy.filter(uid, packageName, opNames, inMemoryAdjBeginTimeMillis,
- inMemoryAdjEndTimeMillis);
+ currentOpsCopy.filter(uid, packageName, featureId, opNames, filter,
+ inMemoryAdjBeginTimeMillis, inMemoryAdjEndTimeMillis);
result.merge(currentOpsCopy);
}
pendingWrites = new ArrayList<>(mPendingWrites);
@@ -410,8 +421,8 @@
- onDiskAndInMemoryOffsetMillis, 0);
final long onDiskAdjEndTimeMillis = Math.max(inMemoryAdjEndTimeMillis
- onDiskAndInMemoryOffsetMillis, 0);
- mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, opNames,
- onDiskAdjBeginTimeMillis, onDiskAdjEndTimeMillis, flags);
+ mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, featureId,
+ opNames, filter, onDiskAdjBeginTimeMillis, onDiskAdjEndTimeMillis, flags);
}
// Rebase the result time to be since epoch.
@@ -425,43 +436,47 @@
}
void incrementOpAccessedCount(int op, int uid, @NonNull String packageName,
- @UidState int uidState, @OpFlags int flags) {
+ @Nullable String featureId, @UidState int uidState, @OpFlags int flags) {
synchronized (mInMemoryLock) {
if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
if (!isPersistenceInitializedMLocked()) {
Slog.e(LOG_TAG, "Interaction before persistence initialized");
return;
}
- getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis())
- .increaseAccessCount(op, uid, packageName, uidState, flags, 1);
+ getUpdatedPendingHistoricalOpsMLocked(
+ System.currentTimeMillis()).increaseAccessCount(op, uid, packageName,
+ featureId, uidState, flags, 1);
}
}
}
void incrementOpRejected(int op, int uid, @NonNull String packageName,
- @UidState int uidState, @OpFlags int flags) {
+ @Nullable String featureId, @UidState int uidState, @OpFlags int flags) {
synchronized (mInMemoryLock) {
if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
if (!isPersistenceInitializedMLocked()) {
Slog.e(LOG_TAG, "Interaction before persistence initialized");
return;
}
- getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis())
- .increaseRejectCount(op, uid, packageName, uidState, flags, 1);
+ getUpdatedPendingHistoricalOpsMLocked(
+ System.currentTimeMillis()).increaseRejectCount(op, uid, packageName,
+ featureId, uidState, flags, 1);
}
}
}
void increaseOpAccessDuration(int op, int uid, @NonNull String packageName,
- @UidState int uidState, @OpFlags int flags, long increment) {
+ @Nullable String featureId, @UidState int uidState, @OpFlags int flags,
+ long increment) {
synchronized (mInMemoryLock) {
if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
if (!isPersistenceInitializedMLocked()) {
Slog.e(LOG_TAG, "Interaction before persistence initialized");
return;
}
- getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis())
- .increaseAccessDuration(op, uid, packageName, uidState, flags, increment);
+ getUpdatedPendingHistoricalOpsMLocked(
+ System.currentTimeMillis()).increaseAccessDuration(op, uid, packageName,
+ featureId, uidState, flags, increment);
}
}
}
@@ -713,6 +728,7 @@
private static final String TAG_OPS = "ops";
private static final String TAG_UID = "uid";
private static final String TAG_PACKAGE = "pkg";
+ private static final String TAG_FEATURE = "ftr";
private static final String TAG_OP = "op";
private static final String TAG_STATE = "st";
@@ -791,8 +807,8 @@
@Nullable List<HistoricalOps> readHistoryRawDLocked() {
return collectHistoricalOpsBaseDLocked(Process.INVALID_UID /*filterUid*/,
- null /*filterPackageName*/, null /*filterOpNames*/,
- 0 /*filterBeginTimeMills*/, Long.MAX_VALUE /*filterEndTimeMills*/,
+ null /*filterPackageName*/, null /*filterFeatureId*/, null /*filterOpNames*/,
+ 0 /*filter*/, 0 /*filterBeginTimeMills*/, Long.MAX_VALUE /*filterEndTimeMills*/,
AppOpsManager.OP_FLAGS_ALL);
}
@@ -846,11 +862,12 @@
}
private void collectHistoricalOpsDLocked(@NonNull HistoricalOps currentOps,
- int filterUid, @NonNull String filterPackageName, @Nullable String[] filterOpNames,
+ int filterUid, @Nullable String filterPackageName, @Nullable String filterFeatureId,
+ @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
long filterBeingMillis, long filterEndMillis, @OpFlags int filterFlags) {
final List<HistoricalOps> readOps = collectHistoricalOpsBaseDLocked(filterUid,
- filterPackageName, filterOpNames, filterBeingMillis, filterEndMillis,
- filterFlags);
+ filterPackageName, filterFeatureId, filterOpNames, filter, filterBeingMillis,
+ filterEndMillis, filterFlags);
if (readOps != null) {
final int readCount = readOps.size();
for (int i = 0; i < readCount; i++) {
@@ -861,7 +878,8 @@
}
private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsBaseDLocked(
- int filterUid, @NonNull String filterPackageName, @Nullable String[] filterOpNames,
+ int filterUid, @Nullable String filterPackageName, @Nullable String filterFeatureId,
+ @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags) {
File baseDir = null;
try {
@@ -874,9 +892,9 @@
final Set<String> historyFiles = getHistoricalFileNames(baseDir);
final long[] globalContentOffsetMillis = {0};
final LinkedList<HistoricalOps> ops = collectHistoricalOpsRecursiveDLocked(
- baseDir, filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis,
- filterEndTimeMillis, filterFlags, globalContentOffsetMillis,
- null /*outOps*/, 0 /*depth*/, historyFiles);
+ baseDir, filterUid, filterPackageName, filterFeatureId, filterOpNames,
+ filter, filterBeginTimeMillis, filterEndTimeMillis, filterFlags,
+ globalContentOffsetMillis, null /*outOps*/, 0 /*depth*/, historyFiles);
if (DEBUG) {
filesInvariant.stopTracking(baseDir);
}
@@ -890,8 +908,9 @@
}
private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsRecursiveDLocked(
- @NonNull File baseDir, int filterUid, @NonNull String filterPackageName,
- @Nullable String[] filterOpNames, long filterBeginTimeMillis,
+ @NonNull File baseDir, int filterUid, @Nullable String filterPackageName,
+ @Nullable String filterFeatureId, @Nullable String[] filterOpNames,
+ @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis,
long filterEndTimeMillis, @OpFlags int filterFlags,
@NonNull long[] globalContentOffsetMillis,
@Nullable LinkedList<HistoricalOps> outOps, int depth,
@@ -908,17 +927,17 @@
// Read historical data at this level
final List<HistoricalOps> readOps = readHistoricalOpsLocked(baseDir,
previousIntervalEndMillis, currentIntervalEndMillis, filterUid,
- filterPackageName, filterOpNames, filterBeginTimeMillis, filterEndTimeMillis,
- filterFlags, globalContentOffsetMillis, depth, historyFiles);
-
+ filterPackageName, filterFeatureId, filterOpNames, filter,
+ filterBeginTimeMillis, filterEndTimeMillis, filterFlags,
+ globalContentOffsetMillis, depth, historyFiles);
// Empty is a special signal to stop diving
if (readOps != null && readOps.isEmpty()) {
return outOps;
}
// Collect older historical data from subsequent levels
- outOps = collectHistoricalOpsRecursiveDLocked(
- baseDir, filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis,
+ outOps = collectHistoricalOpsRecursiveDLocked(baseDir, filterUid, filterPackageName,
+ filterFeatureId, filterOpNames, filter, filterBeginTimeMillis,
filterEndTimeMillis, filterFlags, globalContentOffsetMillis, outOps, depth + 1,
historyFiles);
@@ -987,10 +1006,10 @@
final List<HistoricalOps> existingOps = readHistoricalOpsLocked(oldBaseDir,
previousIntervalEndMillis, currentIntervalEndMillis,
Process.INVALID_UID /*filterUid*/, null /*filterPackageName*/,
- null /*filterOpNames*/, Long.MIN_VALUE /*filterBeginTimeMillis*/,
- Long.MAX_VALUE /*filterEndTimeMillis*/, AppOpsManager.OP_FLAGS_ALL,
- null, depth, null /*historyFiles*/);
-
+ null /*filterFeatureId*/, null /*filterOpNames*/, 0 /*filter*/,
+ Long.MIN_VALUE /*filterBeginTimeMillis*/,
+ Long.MAX_VALUE /*filterEndTimeMillis*/, AppOpsManager.OP_FLAGS_ALL, null, depth,
+ null /*historyFiles*/);
if (DEBUG) {
enforceOpsWellFormed(existingOps);
}
@@ -1100,8 +1119,9 @@
}
private @Nullable List<HistoricalOps> readHistoricalOpsLocked(File baseDir,
- long intervalBeginMillis, long intervalEndMillis,
- int filterUid, @Nullable String filterPackageName, @Nullable String[] filterOpNames,
+ long intervalBeginMillis, long intervalEndMillis, int filterUid,
+ @Nullable String filterPackageName, @Nullable String filterFeatureId,
+ @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags,
@Nullable long[] cumulativeOverflowMillis, int depth,
@NonNull Set<String> historyFiles)
@@ -1127,13 +1147,14 @@
return null;
}
}
- return readHistoricalOpsLocked(file, filterUid, filterPackageName, filterOpNames,
- filterBeginTimeMillis, filterEndTimeMillis, filterFlags,
+ return readHistoricalOpsLocked(file, filterUid, filterPackageName, filterFeatureId,
+ filterOpNames, filter, filterBeginTimeMillis, filterEndTimeMillis, filterFlags,
cumulativeOverflowMillis);
}
private @Nullable List<HistoricalOps> readHistoricalOpsLocked(@NonNull File file,
- int filterUid, @Nullable String filterPackageName, @Nullable String[] filterOpNames,
+ int filterUid, @Nullable String filterPackageName, @Nullable String filterFeatureId,
+ @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags,
@Nullable long[] cumulativeOverflowMillis)
throws IOException, XmlPullParserException {
@@ -1158,9 +1179,10 @@
final int depth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, depth)) {
if (TAG_OPS.equals(parser.getName())) {
- final HistoricalOps ops = readeHistoricalOpsDLocked(parser,
- filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis,
- filterEndTimeMillis, filterFlags, cumulativeOverflowMillis);
+ final HistoricalOps ops = readeHistoricalOpsDLocked(parser, filterUid,
+ filterPackageName, filterFeatureId, filterOpNames, filter,
+ filterBeginTimeMillis, filterEndTimeMillis, filterFlags,
+ cumulativeOverflowMillis);
if (ops == null) {
continue;
}
@@ -1193,7 +1215,8 @@
private @Nullable HistoricalOps readeHistoricalOpsDLocked(
@NonNull XmlPullParser parser, int filterUid, @Nullable String filterPackageName,
- @Nullable String[] filterOpNames, long filterBeginTimeMillis,
+ @Nullable String filterFeatureId, @Nullable String[] filterOpNames,
+ @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis,
long filterEndTimeMillis, @OpFlags int filterFlags,
@Nullable long[] cumulativeOverflowMillis)
throws IOException, XmlPullParserException {
@@ -1222,7 +1245,8 @@
while (XmlUtils.nextElementWithin(parser, depth)) {
if (TAG_UID.equals(parser.getName())) {
final HistoricalOps returnedOps = readHistoricalUidOpsDLocked(ops, parser,
- filterUid, filterPackageName, filterOpNames, filterFlags, filterScale);
+ filterUid, filterPackageName, filterFeatureId, filterOpNames, filter,
+ filterFlags, filterScale);
if (ops == null) {
ops = returnedOps;
}
@@ -1236,11 +1260,12 @@
private @Nullable HistoricalOps readHistoricalUidOpsDLocked(
@Nullable HistoricalOps ops, @NonNull XmlPullParser parser, int filterUid,
- @Nullable String filterPackageName, @Nullable String[] filterOpNames,
+ @Nullable String filterPackageName, @Nullable String filterFeatureId,
+ @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
@OpFlags int filterFlags, double filterScale)
throws IOException, XmlPullParserException {
final int uid = XmlUtils.readIntAttribute(parser, ATTR_NAME);
- if (filterUid != Process.INVALID_UID && filterUid != uid) {
+ if ((filter & FILTER_BY_UID) != 0 && filterUid != uid) {
XmlUtils.skipCurrentTag(parser);
return null;
}
@@ -1248,8 +1273,8 @@
while (XmlUtils.nextElementWithin(parser, depth)) {
if (TAG_PACKAGE.equals(parser.getName())) {
final HistoricalOps returnedOps = readHistoricalPackageOpsDLocked(ops,
- uid, parser, filterPackageName, filterOpNames, filterFlags,
- filterScale);
+ uid, parser, filterPackageName, filterFeatureId, filterOpNames, filter,
+ filterFlags, filterScale);
if (ops == null) {
ops = returnedOps;
}
@@ -1260,19 +1285,46 @@
private @Nullable HistoricalOps readHistoricalPackageOpsDLocked(
@Nullable HistoricalOps ops, int uid, @NonNull XmlPullParser parser,
- @Nullable String filterPackageName, @Nullable String[] filterOpNames,
+ @Nullable String filterPackageName, @Nullable String filterFeatureId,
+ @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
@OpFlags int filterFlags, double filterScale)
throws IOException, XmlPullParserException {
final String packageName = XmlUtils.readStringAttribute(parser, ATTR_NAME);
- if (filterPackageName != null && !filterPackageName.equals(packageName)) {
+ if ((filter & FILTER_BY_PACKAGE_NAME) != 0 && !filterPackageName.equals(packageName)) {
+ XmlUtils.skipCurrentTag(parser);
+ return null;
+ }
+ final int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ if (TAG_FEATURE.equals(parser.getName())) {
+ final HistoricalOps returnedOps = readHistoricalFeatureOpsDLocked(ops, uid,
+ packageName, parser, filterFeatureId, filterOpNames, filter,
+ filterFlags, filterScale);
+ if (ops == null) {
+ ops = returnedOps;
+ }
+ }
+ }
+ return ops;
+ }
+
+ private @Nullable HistoricalOps readHistoricalFeatureOpsDLocked(@Nullable HistoricalOps ops,
+ int uid, String packageName, @NonNull XmlPullParser parser,
+ @Nullable String filterFeatureId, @Nullable String[] filterOpNames,
+ @HistoricalOpsRequestFilter int filter, @OpFlags int filterFlags,
+ double filterScale)
+ throws IOException, XmlPullParserException {
+ final String featureId = XmlUtils.readStringAttribute(parser, ATTR_NAME);
+ if ((filter & FILTER_BY_FEATURE_ID) != 0 && !Objects.equals(filterFeatureId,
+ featureId)) {
XmlUtils.skipCurrentTag(parser);
return null;
}
final int depth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, depth)) {
if (TAG_OP.equals(parser.getName())) {
- final HistoricalOps returnedOps = readHistoricalOpDLocked(ops, uid,
- packageName, parser, filterOpNames, filterFlags, filterScale);
+ final HistoricalOps returnedOps = readHistoricalOpDLocked(ops, uid, packageName,
+ featureId, parser, filterOpNames, filter, filterFlags, filterScale);
if (ops == null) {
ops = returnedOps;
}
@@ -1282,11 +1334,13 @@
}
private @Nullable HistoricalOps readHistoricalOpDLocked(@Nullable HistoricalOps ops,
- int uid, String packageName, @NonNull XmlPullParser parser,
- @Nullable String[] filterOpNames, @OpFlags int filterFlags, double filterScale)
+ int uid, @NonNull String packageName, @Nullable String featureId,
+ @NonNull XmlPullParser parser, @Nullable String[] filterOpNames,
+ @HistoricalOpsRequestFilter int filter, @OpFlags int filterFlags,
+ double filterScale)
throws IOException, XmlPullParserException {
final int op = XmlUtils.readIntAttribute(parser, ATTR_NAME);
- if (filterOpNames != null && !ArrayUtils.contains(filterOpNames,
+ if ((filter & FILTER_BY_OP_NAMES) != 0 && !ArrayUtils.contains(filterOpNames,
AppOpsManager.opToPublicName(op))) {
XmlUtils.skipCurrentTag(parser);
return null;
@@ -1295,7 +1349,7 @@
while (XmlUtils.nextElementWithin(parser, depth)) {
if (TAG_STATE.equals(parser.getName())) {
final HistoricalOps returnedOps = readStateDLocked(ops, uid,
- packageName, op, parser, filterFlags, filterScale);
+ packageName, featureId, op, parser, filterFlags, filterScale);
if (ops == null) {
ops = returnedOps;
}
@@ -1305,8 +1359,9 @@
}
private @Nullable HistoricalOps readStateDLocked(@Nullable HistoricalOps ops,
- int uid, String packageName, int op, @NonNull XmlPullParser parser,
- @OpFlags int filterFlags, double filterScale) throws IOException {
+ int uid, @NonNull String packageName, @Nullable String featureId, int op,
+ @NonNull XmlPullParser parser, @OpFlags int filterFlags, double filterScale)
+ throws IOException {
final long key = XmlUtils.readLongAttribute(parser, ATTR_NAME);
final int flags = AppOpsManager.extractFlagsFromKey(key) & filterFlags;
if (flags == 0) {
@@ -1322,7 +1377,8 @@
if (ops == null) {
ops = new HistoricalOps(0, 0);
}
- ops.increaseAccessCount(op, uid, packageName, uidState, flags, accessCount);
+ ops.increaseAccessCount(op, uid, packageName, featureId, uidState, flags,
+ accessCount);
}
long rejectCount = XmlUtils.readLongAttribute(parser, ATTR_REJECT_COUNT, 0);
if (rejectCount > 0) {
@@ -1333,7 +1389,8 @@
if (ops == null) {
ops = new HistoricalOps(0, 0);
}
- ops.increaseRejectCount(op, uid, packageName, uidState, flags, rejectCount);
+ ops.increaseRejectCount(op, uid, packageName, featureId, uidState, flags,
+ rejectCount);
}
long accessDuration = XmlUtils.readLongAttribute(parser, ATTR_ACCESS_DURATION, 0);
if (accessDuration > 0) {
@@ -1344,7 +1401,8 @@
if (ops == null) {
ops = new HistoricalOps(0, 0);
}
- ops.increaseAccessDuration(op, uid, packageName, uidState, flags, accessDuration);
+ ops.increaseAccessDuration(op, uid, packageName, featureId, uidState, flags,
+ accessDuration);
}
return ops;
}
@@ -1409,14 +1467,26 @@
@NonNull XmlSerializer serializer) throws IOException {
serializer.startTag(null, TAG_PACKAGE);
serializer.attribute(null, ATTR_NAME, packageOps.getPackageName());
- final int opCount = packageOps.getOpCount();
- for (int i = 0; i < opCount; i++) {
- final HistoricalOp op = packageOps.getOpAt(i);
- writeHistoricalOpDLocked(op, serializer);
+ final int numFeatures = packageOps.getFeatureCount();
+ for (int i = 0; i < numFeatures; i++) {
+ final HistoricalFeatureOps op = packageOps.getFeatureOpsAt(i);
+ writeHistoricalFeatureOpsDLocked(op, serializer);
}
serializer.endTag(null, TAG_PACKAGE);
}
+ private void writeHistoricalFeatureOpsDLocked(@NonNull HistoricalFeatureOps featureOps,
+ @NonNull XmlSerializer serializer) throws IOException {
+ serializer.startTag(null, TAG_FEATURE);
+ XmlUtils.writeStringAttribute(serializer, ATTR_NAME, featureOps.getFeatureId());
+ final int opCount = featureOps.getOpCount();
+ for (int i = 0; i < opCount; i++) {
+ final HistoricalOp op = featureOps.getOpAt(i);
+ writeHistoricalOpDLocked(op, serializer);
+ }
+ serializer.endTag(null, TAG_FEATURE);
+ }
+
private void writeHistoricalOpDLocked(@NonNull HistoricalOp op,
@NonNull XmlSerializer serializer) throws IOException {
final LongSparseArray keys = op.collectKeys();
@@ -1648,24 +1718,31 @@
private final @NonNull String mOpsPrefix;
private final @NonNull String mUidPrefix;
private final @NonNull String mPackagePrefix;
+ private final @NonNull String mFeaturePrefix;
private final @NonNull String mEntryPrefix;
private final @NonNull String mUidStatePrefix;
private final @NonNull PrintWriter mWriter;
private final int mFilterUid;
private final String mFilterPackage;
+ private final String mFilterFeatureId;
private final int mFilterOp;
+ private final @HistoricalOpsRequestFilter int mFilter;
- StringDumpVisitor(@NonNull String prefix, @NonNull PrintWriter writer,
- int filterUid, String filterPackage, int filterOp) {
+ StringDumpVisitor(@NonNull String prefix, @NonNull PrintWriter writer, int filterUid,
+ @Nullable String filterPackage, @Nullable String filterFeatureId, int filterOp,
+ @HistoricalOpsRequestFilter int filter) {
mOpsPrefix = prefix + " ";
mUidPrefix = mOpsPrefix + " ";
mPackagePrefix = mUidPrefix + " ";
- mEntryPrefix = mPackagePrefix + " ";
+ mFeaturePrefix = mPackagePrefix + " ";
+ mEntryPrefix = mFeaturePrefix + " ";
mUidStatePrefix = mEntryPrefix + " ";
mWriter = writer;
mFilterUid = filterUid;
mFilterPackage = filterPackage;
+ mFilterFeatureId = filterFeatureId;
mFilterOp = filterOp;
+ mFilter = filter;
}
@Override
@@ -1691,7 +1768,7 @@
@Override
public void visitHistoricalUidOps(HistoricalUidOps ops) {
- if (mFilterUid != Process.INVALID_UID && mFilterUid != ops.getUid()) {
+ if ((mFilter & FILTER_BY_UID) != 0 && mFilterUid != ops.getUid()) {
return;
}
mWriter.println();
@@ -1703,7 +1780,8 @@
@Override
public void visitHistoricalPackageOps(HistoricalPackageOps ops) {
- if (mFilterPackage != null && !mFilterPackage.equals(ops.getPackageName())) {
+ if ((mFilter & FILTER_BY_PACKAGE_NAME) != 0 && !mFilterPackage.equals(
+ ops.getPackageName())) {
return;
}
mWriter.print(mPackagePrefix);
@@ -1713,8 +1791,20 @@
}
@Override
+ public void visitHistoricalFeatureOps(HistoricalFeatureOps ops) {
+ if ((mFilter & FILTER_BY_FEATURE_ID) != 0 && !Objects.equals(mFilterPackage,
+ ops.getFeatureId())) {
+ return;
+ }
+ mWriter.print(mFeaturePrefix);
+ mWriter.print("Feature ");
+ mWriter.print(ops.getFeatureId());
+ mWriter.println(":");
+ }
+
+ @Override
public void visitHistoricalOp(HistoricalOp ops) {
- if (mFilterOp != AppOpsManager.OP_NONE && mFilterOp != ops.getOpCode()) {
+ if ((mFilter & FILTER_BY_OP_NAMES) != 0 && mFilterOp != ops.getOpCode()) {
return;
}
mWriter.print(mEntryPrefix);
diff --git a/services/core/java/com/android/server/integrity/IntegrityFileManager.java b/services/core/java/com/android/server/integrity/IntegrityFileManager.java
index 46daf12..f90fab4 100644
--- a/services/core/java/com/android/server/integrity/IntegrityFileManager.java
+++ b/services/core/java/com/android/server/integrity/IntegrityFileManager.java
@@ -25,6 +25,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.integrity.model.RuleMetadata;
import com.android.server.integrity.parser.RuleBinaryParser;
+import com.android.server.integrity.parser.RuleIndexingController;
import com.android.server.integrity.parser.RuleMetadataParser;
import com.android.server.integrity.parser.RuleParseException;
import com.android.server.integrity.parser.RuleParser;
@@ -61,7 +62,10 @@
// update rules atomically.
private final File mStagingDir;
- @Nullable private RuleMetadata mRuleMetadataCache;
+ @Nullable
+ private RuleMetadata mRuleMetadataCache;
+ @Nullable
+ private RuleIndexingController mRuleIndexingController;
/** Get the singleton instance of this class. */
public static synchronized IntegrityFileManager getInstance() {
@@ -100,6 +104,8 @@
Slog.e(TAG, "Error reading metadata file.", e);
}
}
+
+ updateRuleIndexingController();
}
/**
@@ -109,7 +115,8 @@
*/
public boolean initialized() {
return new File(mRulesDir, RULES_FILE).exists()
- && new File(mRulesDir, METADATA_FILE).exists();
+ && new File(mRulesDir, METADATA_FILE).exists()
+ && new File(mRulesDir, INDEXING_FILE).exists();
}
/** Write rules to persistent storage. */
@@ -131,6 +138,9 @@
}
switchStagingRulesDir();
+
+ // Update object holding the indexing information.
+ updateRuleIndexingController();
}
/**
@@ -140,8 +150,13 @@
*/
public List<Rule> readRules(AppInstallMetadata appInstallMetadata)
throws IOException, RuleParseException {
- // TODO: select rules by index
synchronized (RULES_LOCK) {
+ // Try to identify indexes from the index file.
+ List<List<Integer>> ruleReadingIndexes =
+ mRuleIndexingController.identifyRulesToEvaluate(appInstallMetadata);
+
+ // Read the rules based on the index information.
+ // TODO(b/145493956): Provide the identified indexes to the rule reader.
try (FileInputStream inputStream =
new FileInputStream(new File(mRulesDir, RULES_FILE))) {
List<Rule> rules = mRuleParser.parse(inputStream);
@@ -168,6 +183,17 @@
}
}
+ private void updateRuleIndexingController() {
+ File ruleIndexingFile = new File(mRulesDir, INDEXING_FILE);
+ if (ruleIndexingFile.exists()) {
+ try (FileInputStream inputStream = new FileInputStream(ruleIndexingFile)) {
+ mRuleIndexingController = new RuleIndexingController(inputStream);
+ } catch (Exception e) {
+ Slog.e(TAG, "Error parsing the rule indexing file.", e);
+ }
+ }
+ }
+
private void writeMetadata(File directory, String ruleProvider, String version)
throws IOException {
mRuleMetadataCache = new RuleMetadata(ruleProvider, version);
diff --git a/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java b/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java
new file mode 100644
index 0000000..52df89870
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.integrity.model;
+
+/** A helper class containing special indexing file constants. */
+public final class IndexingFileConstants {
+ // The parsing time seems acceptable for this block size based on the tests in
+ // go/ic-rule-file-format.
+ public static final int INDEXING_BLOCK_SIZE = 100;
+
+ public static final String START_INDEXING_KEY = "START_KEY";
+ public static final String END_INDEXING_KEY = "END_KEY";
+}
diff --git a/services/core/java/com/android/server/integrity/parser/BinaryFileOperations.java b/services/core/java/com/android/server/integrity/parser/BinaryFileOperations.java
new file mode 100644
index 0000000..2c5b7d3
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/parser/BinaryFileOperations.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.integrity.parser;
+
+import static com.android.server.integrity.model.ComponentBitSize.IS_HASHED_BITS;
+import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
+
+import com.android.server.integrity.IntegrityUtils;
+import com.android.server.integrity.model.BitInputStream;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * Helper methods for reading standard data structures from {@link BitInputStream}.
+ */
+public class BinaryFileOperations {
+
+ /**
+ * Read an string value with the given size and hash status from a {@code BitInputStream}.
+ *
+ * If the value is hashed, get the hex-encoding of the value. Serialized values are in raw form.
+ * All hashed values are hex-encoded.
+ */
+ public static String getStringValue(BitInputStream bitInputStream) throws IOException {
+ boolean isHashedValue = bitInputStream.getNext(IS_HASHED_BITS) == 1;
+ int valueSize = bitInputStream.getNext(VALUE_SIZE_BITS);
+ return getStringValue(bitInputStream, valueSize, isHashedValue);
+ }
+
+ /**
+ * Read an string value with the given size and hash status from a {@code BitInputStream}.
+ *
+ * If the value is hashed, get the hex-encoding of the value. Serialized values are in raw form.
+ * All hashed values are hex-encoded.
+ */
+ public static String getStringValue(
+ BitInputStream bitInputStream, int valueSize, boolean isHashedValue)
+ throws IOException {
+ if (!isHashedValue) {
+ StringBuilder value = new StringBuilder();
+ while (valueSize-- > 0) {
+ value.append((char) bitInputStream.getNext(/* numOfBits= */ 8));
+ }
+ return value.toString();
+ }
+ ByteBuffer byteBuffer = ByteBuffer.allocate(valueSize);
+ while (valueSize-- > 0) {
+ byteBuffer.put((byte) (bitInputStream.getNext(/* numOfBits= */ 8) & 0xFF));
+ }
+ return IntegrityUtils.getHexDigest(byteBuffer.array());
+ }
+
+ /** Read an integer value from a {@code BitInputStream}. */
+ public static int getIntValue(BitInputStream bitInputStream) throws IOException {
+ return bitInputStream.getNext(/* numOfBits= */ 32);
+ }
+
+ /** Read an boolean value from a {@code BitInputStream}. */
+ public static boolean getBooleanValue(BitInputStream bitInputStream) throws IOException {
+ return bitInputStream.getNext(/* numOfBits= */ 1) == 1;
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
index 8f84abc..cbb6e4e 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
@@ -28,18 +28,19 @@
import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS;
import static com.android.server.integrity.model.ComponentBitSize.SIGNAL_BIT;
import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
+import static com.android.server.integrity.parser.BinaryFileOperations.getBooleanValue;
+import static com.android.server.integrity.parser.BinaryFileOperations.getIntValue;
+import static com.android.server.integrity.parser.BinaryFileOperations.getStringValue;
import android.content.integrity.AtomicFormula;
import android.content.integrity.CompoundFormula;
import android.content.integrity.Formula;
import android.content.integrity.Rule;
-import com.android.server.integrity.IntegrityUtils;
import com.android.server.integrity.model.BitInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
@@ -145,33 +146,4 @@
throw new IllegalArgumentException(String.format("Unknown key: %d", key));
}
}
-
- // Get value string from stream.
- // If the value is not hashed, get its raw form directly.
- // If the value is hashed, get the hex-encoding of the value. Serialized values are in raw form.
- // All hashed values are hex-encoded.
- private static String getStringValue(
- BitInputStream bitInputStream, int valueSize, boolean isHashedValue)
- throws IOException {
- if (!isHashedValue) {
- StringBuilder value = new StringBuilder();
- while (valueSize-- > 0) {
- value.append((char) bitInputStream.getNext(/* numOfBits= */ 8));
- }
- return value.toString();
- }
- ByteBuffer byteBuffer = ByteBuffer.allocate(valueSize);
- while (valueSize-- > 0) {
- byteBuffer.put((byte) (bitInputStream.getNext(/* numOfBits= */ 8) & 0xFF));
- }
- return IntegrityUtils.getHexDigest(byteBuffer.array());
- }
-
- private static int getIntValue(BitInputStream bitInputStream) throws IOException {
- return bitInputStream.getNext(/* numOfBits= */ 32);
- }
-
- private static boolean getBooleanValue(BitInputStream bitInputStream) throws IOException {
- return bitInputStream.getNext(/* numOfBits= */ 1) == 1;
- }
}
diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
new file mode 100644
index 0000000..b642fa6
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.integrity.parser;
+
+import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
+import static com.android.server.integrity.parser.BinaryFileOperations.getIntValue;
+import static com.android.server.integrity.parser.BinaryFileOperations.getStringValue;
+
+import android.content.integrity.AppInstallMetadata;
+
+import com.android.server.integrity.model.BitInputStream;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.TreeMap;
+
+/** Helper class to identify the necessary indexes that needs to be read. */
+public class RuleIndexingController {
+
+ private static TreeMap<String, Integer> sPackageNameBasedIndexes;
+ private static TreeMap<String, Integer> sAppCertificateBasedIndexes;
+ private static TreeMap<String, Integer> sUnindexedRuleIndexes;
+
+ /**
+ * Provide the indexing file to read and the object will be constructed by reading and
+ * identifying the indexes.
+ */
+ public RuleIndexingController(FileInputStream fileInputStream) throws IOException {
+ BitInputStream bitInputStream = new BitInputStream(fileInputStream);
+ sPackageNameBasedIndexes = getNextIndexGroup(bitInputStream);
+ sAppCertificateBasedIndexes = getNextIndexGroup(bitInputStream);
+ sUnindexedRuleIndexes = getNextIndexGroup(bitInputStream);
+ }
+
+ /**
+ * Returns a list of integers with the starting and ending bytes of the rules that needs to be
+ * read and evaluated.
+ */
+ public List<List<Integer>> identifyRulesToEvaluate(AppInstallMetadata appInstallMetadata) {
+ // TODO(b/145493956): Identify and return the indexes that needs to be read.
+ return new ArrayList<>();
+ }
+
+ private TreeMap<String, Integer> getNextIndexGroup(BitInputStream bitInputStream)
+ throws IOException {
+ TreeMap<String, Integer> keyToIndexMap = new TreeMap<>();
+ while (bitInputStream.hasNext()) {
+ String key = getStringValue(bitInputStream);
+ int value = getIntValue(bitInputStream);
+
+ keyToIndexMap.put(key, value);
+
+ if (key == END_INDEXING_KEY) {
+ break;
+ }
+ }
+ return keyToIndexMap;
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
index 35e673f..b8791c3 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
@@ -27,6 +27,9 @@
import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS;
import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS;
import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
+import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
+import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE;
+import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
@@ -52,13 +55,6 @@
/** A helper class to serialize rules from the {@link Rule} model to Binary representation. */
public class RuleBinarySerializer implements RuleSerializer {
- // The parsing time seems acceptable for this block size based on the tests in
- // go/ic-rule-file-format.
- public static final int INDEXING_BLOCK_SIZE = 100;
-
- public static final String START_INDEXING_KEY = "START_KEY";
- public static final String END_INDEXING_KEY = "END_KEY";
-
// Get the byte representation for a list of rules.
@Override
public byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion)
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 4546ea4..7233f34 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -20,6 +20,7 @@
import static android.Manifest.permission.READ_CONTACTS;
import static android.content.Context.KEYGUARD_SERVICE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.UserHandle.USER_ALL;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
@@ -29,6 +30,7 @@
import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
import static com.android.internal.widget.LockPatternUtils.USER_FRP;
import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled;
import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential;
@@ -118,6 +120,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
import com.android.internal.widget.LockscreenCredential;
+import com.android.internal.widget.RebootEscrowListener;
import com.android.internal.widget.VerifyCredentialResponse;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
@@ -222,7 +225,10 @@
private final RecoverableKeyStoreManager mRecoverableKeyStoreManager;
+ private final RebootEscrowManager mRebootEscrowManager;
+
private boolean mFirstCallToVold;
+
// Current password metric for all users on the device. Updated when user unlocks
// the device or changes password. Removed when user is stopped.
@GuardedBy("this")
@@ -483,6 +489,11 @@
new PasswordSlotManager());
}
+ public RebootEscrowManager getRebootEscrowManager(RebootEscrowManager.Callbacks callbacks,
+ LockSettingsStorage storage) {
+ return new RebootEscrowManager(mContext, callbacks, storage);
+ }
+
public boolean hasEnrolledBiometrics(int userId) {
BiometricManager bm = mContext.getSystemService(BiometricManager.class);
return bm.hasEnrolledBiometrics(userId);
@@ -550,6 +561,9 @@
mSpManager = injector.getSyntheticPasswordManager(mStorage);
+ mRebootEscrowManager = injector.getRebootEscrowManager(new RebootEscrowCallbacks(),
+ mStorage);
+
LocalServices.addService(LockSettingsInternal.class, new LocalService());
}
@@ -782,7 +796,14 @@
migrateOldData();
getGateKeeperService();
mSpManager.initWeaverService();
- // Find the AuthSecret HAL
+ getAuthSecretHal();
+ mDeviceProvisionedObserver.onSystemReady();
+ mRebootEscrowManager.loadRebootEscrowDataIfAvailable();
+ // TODO: maybe skip this for split system user mode.
+ mStorage.prefetchUser(UserHandle.USER_SYSTEM);
+ }
+
+ private void getAuthSecretHal() {
try {
mAuthSecretService = IAuthSecret.getService();
} catch (NoSuchElementException e) {
@@ -790,9 +811,6 @@
} catch (RemoteException e) {
Slog.w(TAG, "Failed to get AuthSecret HAL", e);
}
- mDeviceProvisionedObserver.onSystemReady();
- // TODO: maybe skip this for split system user mode.
- mStorage.prefetchUser(UserHandle.USER_SYSTEM);
}
private void migrateOldData() {
@@ -2466,10 +2484,18 @@
private void onAuthTokenKnownForUser(@UserIdInt int userId, AuthenticationToken auth) {
if (mInjector.isGsiRunning()) {
- Slog.w(TAG, "AuthSecret disabled in GSI");
+ Slog.w(TAG, "Running in GSI; skipping calls to AuthSecret and RebootEscrow");
return;
}
+ mRebootEscrowManager.callToRebootEscrowIfNeeded(userId, auth.getVersion(),
+ auth.getSyntheticPassword());
+
+ callToAuthSecretIfNeeded(userId, auth);
+ }
+
+ private void callToAuthSecretIfNeeded(@UserIdInt int userId,
+ AuthenticationToken auth) {
// Pass the primary user's auth secret to the HAL
if (mAuthSecretService != null && mUserManager.getUserInfo(userId).isPrimary()) {
try {
@@ -3288,5 +3314,46 @@
return LockSettingsService.this.getUserPasswordMetrics(userHandle);
}
+ @Override
+ public void prepareRebootEscrow() {
+ if (!mRebootEscrowManager.prepareRebootEscrow()) {
+ return;
+ }
+ mStrongAuth.requireStrongAuth(STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE, USER_ALL);
+ }
+
+ @Override
+ public void setRebootEscrowListener(RebootEscrowListener listener) {
+ mRebootEscrowManager.setRebootEscrowListener(listener);
+ }
+
+ @Override
+ public void clearRebootEscrow() {
+ if (!mRebootEscrowManager.clearRebootEscrow()) {
+ return;
+ }
+ mStrongAuth.noLongerRequireStrongAuth(STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE,
+ USER_ALL);
+ }
+
+ @Override
+ public boolean armRebootEscrow() {
+ return mRebootEscrowManager.armRebootEscrowIfNeeded();
+ }
+ }
+
+ private class RebootEscrowCallbacks implements RebootEscrowManager.Callbacks {
+ @Override
+ public boolean isUserSecure(int userId) {
+ return LockSettingsService.this.isUserSecure(userId);
+ }
+
+ @Override
+ public void onRebootEscrowRestored(byte spVersion, byte[] syntheticPassword, int userId) {
+ SyntheticPasswordManager.AuthenticationToken
+ authToken = new SyntheticPasswordManager.AuthenticationToken(spVersion);
+ authToken.recreateDirectly(syntheticPassword);
+ onCredentialVerified(authToken, CHALLENGE_NONE, 0, null, userId);
+ }
}
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 3dab3ce..fec0189 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -81,6 +81,8 @@
private static final String LOCK_PASSWORD_FILE = "gatekeeper.password.key";
private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key";
+ private static final String REBOOT_ESCROW_FILE = "reboot.escrow.key";
+
private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/";
private static final Object DEFAULT = new Object();
@@ -261,6 +263,22 @@
return hasFile(getChildProfileLockFile(userId));
}
+ public void writeRebootEscrow(int userId, byte[] rebootEscrow) {
+ writeFile(getRebootEscrowFile(userId), rebootEscrow);
+ }
+
+ public byte[] readRebootEscrow(int userId) {
+ return readFile(getRebootEscrowFile(userId));
+ }
+
+ public boolean hasRebootEscrow(int userId) {
+ return hasFile(getRebootEscrowFile(userId));
+ }
+
+ public void removeRebootEscrow(int userId) {
+ deleteFile(getRebootEscrowFile(userId));
+ }
+
public boolean hasPassword(int userId) {
return hasFile(getLockPasswordFilename(userId));
}
@@ -384,6 +402,11 @@
return getLockCredentialFilePathForUser(userId, CHILD_PROFILE_LOCK_FILE);
}
+ @VisibleForTesting
+ String getRebootEscrowFile(int userId) {
+ return getLockCredentialFilePathForUser(userId, REBOOT_ESCROW_FILE);
+ }
+
private String getLockCredentialFilePathForUser(int userId, String basename) {
String dataSystemDirectory = Environment.getDataDirectory().getAbsolutePath() +
SYSTEM_DIRECTORY;
@@ -479,18 +502,10 @@
if (parentInfo == null) {
// This user owns its lock settings files - safe to delete them
synchronized (mFileWriteLock) {
- String name = getLockPasswordFilename(userId);
- File file = new File(name);
- if (file.exists()) {
- file.delete();
- mCache.putFile(name, null);
- }
- name = getLockPatternFilename(userId);
- file = new File(name);
- if (file.exists()) {
- file.delete();
- mCache.putFile(name, null);
- }
+ deleteFilesAndRemoveCache(
+ getLockPasswordFilename(userId),
+ getLockPatternFilename(userId),
+ getRebootEscrowFile(userId));
}
} else {
// Managed profile
@@ -512,6 +527,16 @@
}
}
+ private void deleteFilesAndRemoveCache(String... names) {
+ for (String name : names) {
+ File file = new File(name);
+ if (file.exists()) {
+ file.delete();
+ mCache.putFile(name, null);
+ }
+ }
+ }
+
@VisibleForTesting
void closeDatabase() {
mOpenHelper.close();
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
index a84306c..91cf53e 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
@@ -16,10 +16,8 @@
package com.android.server.locksettings;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker
- .STRONG_AUTH_NOT_REQUIRED;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker
- .STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
import android.app.AlarmManager;
import android.app.AlarmManager.OnAlarmListener;
@@ -50,6 +48,7 @@
private static final int MSG_UNREGISTER_TRACKER = 3;
private static final int MSG_REMOVE_USER = 4;
private static final int MSG_SCHEDULE_STRONG_AUTH_TIMEOUT = 5;
+ private static final int MSG_NO_LONGER_REQUIRE_STRONG_AUTH = 6;
private static final String STRONG_AUTH_TIMEOUT_ALARM_TAG =
"LockSettingsStrongAuth.timeoutForUser";
@@ -109,6 +108,26 @@
}
}
+ private void handleNoLongerRequireStrongAuth(int strongAuthReason, int userId) {
+ if (userId == UserHandle.USER_ALL) {
+ for (int i = 0; i < mStrongAuthForUser.size(); i++) {
+ int key = mStrongAuthForUser.keyAt(i);
+ handleNoLongerRequireStrongAuthOneUser(strongAuthReason, key);
+ }
+ } else {
+ handleNoLongerRequireStrongAuthOneUser(strongAuthReason, userId);
+ }
+ }
+
+ private void handleNoLongerRequireStrongAuthOneUser(int strongAuthReason, int userId) {
+ int oldValue = mStrongAuthForUser.get(userId, mDefaultStrongAuthFlags);
+ int newValue = oldValue & ~strongAuthReason;
+ if (oldValue != newValue) {
+ mStrongAuthForUser.put(userId, newValue);
+ notifyStrongAuthTrackers(newValue, userId);
+ }
+ }
+
private void handleRemoveUser(int userId) {
int index = mStrongAuthForUser.indexOfKey(userId);
if (index >= 0) {
@@ -174,6 +193,16 @@
}
}
+ void noLongerRequireStrongAuth(int strongAuthReason, int userId) {
+ if (userId == UserHandle.USER_ALL || userId >= UserHandle.USER_SYSTEM) {
+ mHandler.obtainMessage(MSG_NO_LONGER_REQUIRE_STRONG_AUTH, strongAuthReason,
+ userId).sendToTarget();
+ } else {
+ throw new IllegalArgumentException(
+ "userId must be an explicit user id or USER_ALL");
+ }
+ }
+
public void reportUnlock(int userId) {
requireStrongAuth(STRONG_AUTH_NOT_REQUIRED, userId);
}
@@ -216,6 +245,9 @@
case MSG_SCHEDULE_STRONG_AUTH_TIMEOUT:
handleScheduleStrongAuthTimeout(msg.arg1);
break;
+ case MSG_NO_LONGER_REQUIRE_STRONG_AUTH:
+ handleNoLongerRequireStrongAuth(msg.arg1, msg.arg2);
+ break;
}
}
};
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowData.java b/services/core/java/com/android/server/locksettings/RebootEscrowData.java
new file mode 100644
index 0000000..aee608e
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowData.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Holds the data necessary to complete a reboot escrow of the Synthetic Password.
+ */
+class RebootEscrowData {
+ /**
+ * This is the current version of the escrow data format. This should be incremented if the
+ * format on disk is changed.
+ */
+ private static final int CURRENT_VERSION = 1;
+
+ /** The secret key will be of this format. */
+ private static final String KEY_ALGO = "AES";
+
+ /** The key size used for encrypting the reboot escrow data. */
+ private static final int KEY_SIZE_BITS = 256;
+
+ /** The algorithm used for the encryption of the key blob. */
+ private static final String CIPHER_ALGO = "AES/GCM/NoPadding";
+
+ private RebootEscrowData(byte spVersion, byte[] iv, byte[] syntheticPassword, byte[] blob,
+ byte[] key) {
+ mSpVersion = spVersion;
+ mIv = iv;
+ mSyntheticPassword = syntheticPassword;
+ mBlob = blob;
+ mKey = key;
+ }
+
+ private final byte mSpVersion;
+ private final byte[] mIv;
+ private final byte[] mSyntheticPassword;
+ private final byte[] mBlob;
+ private final byte[] mKey;
+
+ public byte getSpVersion() {
+ return mSpVersion;
+ }
+
+ public byte[] getIv() {
+ return mIv;
+ }
+
+ public byte[] getSyntheticPassword() {
+ return mSyntheticPassword;
+ }
+
+ public byte[] getBlob() {
+ return mBlob;
+ }
+
+ public byte[] getKey() {
+ return mKey;
+ }
+
+ static SecretKeySpec fromKeyBytes(byte[] keyBytes) {
+ return new SecretKeySpec(keyBytes, KEY_ALGO);
+ }
+
+ static RebootEscrowData fromEncryptedData(SecretKeySpec keySpec, byte[] blob)
+ throws IOException {
+ Preconditions.checkNotNull(keySpec);
+ Preconditions.checkNotNull(blob);
+
+ DataInputStream dis = new DataInputStream(new ByteArrayInputStream(blob));
+ int version = dis.readInt();
+ if (version != CURRENT_VERSION) {
+ throw new IOException("Unsupported version " + version);
+ }
+
+ byte spVersion = dis.readByte();
+
+ int ivSize = dis.readInt();
+ if (ivSize < 0 || ivSize > 32) {
+ throw new IOException("IV out of range: " + ivSize);
+ }
+ byte[] iv = new byte[ivSize];
+ dis.readFully(iv);
+
+ int cipherTextSize = dis.readInt();
+ if (cipherTextSize < 0) {
+ throw new IOException("Invalid cipher text size: " + cipherTextSize);
+ }
+
+ byte[] cipherText = new byte[cipherTextSize];
+ dis.readFully(cipherText);
+
+ final byte[] syntheticPassword;
+ try {
+ Cipher c = Cipher.getInstance(CIPHER_ALGO);
+ c.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv));
+ syntheticPassword = c.doFinal(cipherText);
+ } catch (NoSuchAlgorithmException | InvalidKeyException | BadPaddingException
+ | IllegalBlockSizeException | NoSuchPaddingException
+ | InvalidAlgorithmParameterException e) {
+ throw new IOException("Could not decrypt ciphertext", e);
+ }
+
+ return new RebootEscrowData(spVersion, iv, syntheticPassword, blob, keySpec.getEncoded());
+ }
+
+ static RebootEscrowData fromSyntheticPassword(byte spVersion, byte[] syntheticPassword)
+ throws IOException {
+ Preconditions.checkNotNull(syntheticPassword);
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(bos);
+
+ final SecretKey secretKey;
+ try {
+ KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGO);
+ keyGenerator.init(KEY_SIZE_BITS, new SecureRandom());
+ secretKey = keyGenerator.generateKey();
+ } catch (NoSuchAlgorithmException e) {
+ throw new IOException("Could not generate new secret key", e);
+ }
+
+ final byte[] cipherText;
+ final byte[] iv;
+ try {
+ Cipher cipher = Cipher.getInstance(CIPHER_ALGO);
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey);
+ cipherText = cipher.doFinal(syntheticPassword);
+ iv = cipher.getIV();
+ } catch (NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException
+ | NoSuchPaddingException | InvalidKeyException e) {
+ throw new IOException("Could not encrypt reboot escrow data", e);
+ }
+
+ dos.writeInt(CURRENT_VERSION);
+ dos.writeByte(spVersion);
+ dos.writeInt(iv.length);
+ dos.write(iv);
+ dos.writeInt(cipherText.length);
+ dos.write(cipherText);
+
+ return new RebootEscrowData(spVersion, iv, syntheticPassword, bos.toByteArray(),
+ secretKey.getEncoded());
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
new file mode 100644
index 0000000..d2e54f9
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.hardware.rebootescrow.IRebootEscrow;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserManager;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.RebootEscrowListener;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.crypto.spec.SecretKeySpec;
+
+class RebootEscrowManager {
+ private static final String TAG = "RebootEscrowManager";
+
+ /**
+ * Used to track when the reboot escrow is wanted. Set to false when mRebootEscrowReady is
+ * true.
+ */
+ private final AtomicBoolean mRebootEscrowWanted = new AtomicBoolean(false);
+
+ /** Used to track when reboot escrow is ready. */
+ private boolean mRebootEscrowReady;
+
+ /** Notified when mRebootEscrowReady changes. */
+ private RebootEscrowListener mRebootEscrowListener;
+
+ /**
+ * Stores the reboot escrow data between when it's supplied and when
+ * {@link #armRebootEscrowIfNeeded()} is called.
+ */
+ private RebootEscrowData mPendingRebootEscrowData;
+
+ private final UserManager mUserManager;
+
+ private final Injector mInjector;
+
+ private final LockSettingsStorage mStorage;
+
+ private final Callbacks mCallbacks;
+
+ interface Callbacks {
+ boolean isUserSecure(int userId);
+ void onRebootEscrowRestored(byte spVersion, byte[] syntheticPassword, int userId);
+ }
+
+ static class Injector {
+ protected Context mContext;
+
+ Injector(Context context) {
+ mContext = context;
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+ public UserManager getUserManager() {
+ return (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ }
+
+ public @Nullable IRebootEscrow getRebootEscrow() {
+ try {
+ return IRebootEscrow.Stub.asInterface(ServiceManager.getService(
+ "android.hardware.rebootescrow.IRebootEscrow/default"));
+ } catch (NoSuchElementException e) {
+ Slog.i(TAG, "Device doesn't implement RebootEscrow HAL");
+ }
+ return null;
+ }
+ }
+
+ RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage) {
+ this(new Injector(context), callbacks, storage);
+ }
+
+ @VisibleForTesting
+ RebootEscrowManager(Injector injector, Callbacks callbacks,
+ LockSettingsStorage storage) {
+ mInjector = injector;
+ mCallbacks = callbacks;
+ mStorage = storage;
+ mUserManager = injector.getUserManager();
+ }
+
+ void loadRebootEscrowDataIfAvailable() {
+ IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
+ if (rebootEscrow == null) {
+ return;
+ }
+
+ final SecretKeySpec escrowKey;
+ try {
+ byte[] escrowKeyBytes = rebootEscrow.retrieveKey();
+ if (escrowKeyBytes == null) {
+ return;
+ } else if (escrowKeyBytes.length != 32) {
+ Slog.e(TAG, "IRebootEscrow returned key of incorrect size "
+ + escrowKeyBytes.length);
+ return;
+ }
+
+ // Make sure we didn't get the null key.
+ int zero = 0;
+ for (int i = 0; i < escrowKeyBytes.length; i++) {
+ zero |= escrowKeyBytes[i];
+ }
+ if (zero == 0) {
+ Slog.w(TAG, "IRebootEscrow returned an all-zeroes key");
+ return;
+ }
+
+ // Overwrite the existing key with the null key
+ rebootEscrow.storeKey(new byte[32]);
+
+ escrowKey = RebootEscrowData.fromKeyBytes(escrowKeyBytes);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Could not retrieve escrow data");
+ return;
+ }
+
+ List<UserInfo> users = mUserManager.getUsers();
+ for (UserInfo user : users) {
+ if (mCallbacks.isUserSecure(user.id)) {
+ restoreRebootEscrowForUser(user.id, escrowKey);
+ }
+ }
+ }
+
+ private void restoreRebootEscrowForUser(@UserIdInt int userId, SecretKeySpec escrowKey) {
+ if (!mStorage.hasRebootEscrow(userId)) {
+ return;
+ }
+
+ try {
+ byte[] blob = mStorage.readRebootEscrow(userId);
+ mStorage.removeRebootEscrow(userId);
+
+ RebootEscrowData escrowData = RebootEscrowData.fromEncryptedData(escrowKey, blob);
+
+ mCallbacks.onRebootEscrowRestored(escrowData.getSpVersion(),
+ escrowData.getSyntheticPassword(), userId);
+ } catch (IOException e) {
+ Slog.w(TAG, "Could not load reboot escrow data for user " + userId, e);
+ }
+ }
+
+ void callToRebootEscrowIfNeeded(@UserIdInt int userId, byte spVersion,
+ byte[] syntheticPassword) {
+ if (!mRebootEscrowWanted.compareAndSet(true, false)) {
+ return;
+ }
+
+ IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
+ if (rebootEscrow == null) {
+ setRebootEscrowReady(false);
+ return;
+ }
+
+ final RebootEscrowData escrowData;
+ try {
+ escrowData = RebootEscrowData.fromSyntheticPassword(spVersion, syntheticPassword);
+ } catch (IOException e) {
+ setRebootEscrowReady(false);
+ Slog.w(TAG, "Could not escrow reboot data", e);
+ return;
+ }
+
+ mPendingRebootEscrowData = escrowData;
+ mStorage.writeRebootEscrow(userId, escrowData.getBlob());
+
+ setRebootEscrowReady(true);
+ }
+
+ private void clearRebootEscrowIfNeeded() {
+ mRebootEscrowWanted.set(false);
+ setRebootEscrowReady(false);
+
+ IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
+ if (rebootEscrow == null) {
+ return;
+ }
+
+ try {
+ rebootEscrow.storeKey(new byte[32]);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Could not call RebootEscrow HAL to shred key");
+ }
+
+ List<UserInfo> users = mUserManager.getUsers();
+ for (UserInfo user : users) {
+ mStorage.removeRebootEscrow(user.id);
+ }
+ }
+
+ boolean armRebootEscrowIfNeeded() {
+ if (!mRebootEscrowReady) {
+ return false;
+ }
+
+ IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
+ if (rebootEscrow == null) {
+ return false;
+ }
+
+ RebootEscrowData escrowData = mPendingRebootEscrowData;
+ if (escrowData == null) {
+ return false;
+ }
+
+ boolean armedRebootEscrow = false;
+ try {
+ rebootEscrow.storeKey(escrowData.getKey());
+ armedRebootEscrow = true;
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed escrow secret to RebootEscrow HAL", e);
+ }
+ return armedRebootEscrow;
+ }
+
+ private void setRebootEscrowReady(boolean ready) {
+ if (mRebootEscrowReady != ready) {
+ mRebootEscrowListener.onPreparedForReboot(ready);
+ }
+ mRebootEscrowReady = ready;
+ }
+
+ boolean prepareRebootEscrow() {
+ if (mInjector.getRebootEscrow() == null) {
+ return false;
+ }
+
+ clearRebootEscrowIfNeeded();
+ mRebootEscrowWanted.set(true);
+ return true;
+ }
+
+ boolean clearRebootEscrow() {
+ if (mInjector.getRebootEscrow() == null) {
+ return false;
+ }
+
+ clearRebootEscrowIfNeeded();
+ return true;
+ }
+
+ void setRebootEscrowListener(RebootEscrowListener listener) {
+ mRebootEscrowListener = listener;
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 0fe16be..b726e57 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -38,7 +38,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.Preconditions;
import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
@@ -285,6 +284,14 @@
public byte[] getSyntheticPassword() {
return mSyntheticPassword;
}
+
+ /**
+ * Returns the version of this AuthenticationToken for use with reconstructing
+ * this with a synthetic password version.
+ */
+ public byte getVersion() {
+ return mVersion;
+ }
}
static class PasswordData {
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 115155c..55c4e21 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -20,7 +20,6 @@
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Intent;
-import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
import android.media.RouteSessionInfo;
@@ -50,13 +49,13 @@
String controlCategory, long requestId);
public abstract void releaseSession(int sessionId);
- public abstract void selectRoute(int sessionId, MediaRoute2Info route);
- public abstract void deselectRoute(int sessionId, MediaRoute2Info route);
- public abstract void transferToRoute(int sessionId, MediaRoute2Info route);
+ public abstract void selectRoute(int sessionId, String routeId);
+ public abstract void deselectRoute(int sessionId, String routeId);
+ public abstract void transferToRoute(int sessionId, String routeId);
- public abstract void sendControlRequest(MediaRoute2Info route, Intent request);
- public abstract void requestSetVolume(MediaRoute2Info route, int volume);
- public abstract void requestUpdateVolume(MediaRoute2Info route, int delta);
+ public abstract void sendControlRequest(String routeId, Intent request);
+ public abstract void requestSetVolume(String routeId, int volume);
+ public abstract void requestUpdateVolume(String routeId, int delta);
@NonNull
public String getUniqueId() {
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
index f8d8f9f..28bb034 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
@@ -24,7 +24,6 @@
import android.content.ServiceConnection;
import android.media.IMediaRoute2Provider;
import android.media.IMediaRoute2ProviderClient;
-import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2ProviderService;
import android.media.RouteSessionInfo;
@@ -95,46 +94,46 @@
}
@Override
- public void selectRoute(int sessionId, MediaRoute2Info route) {
+ public void selectRoute(int sessionId, String routeId) {
if (mConnectionReady) {
- mActiveConnection.selectRoute(sessionId, route.getId());
+ mActiveConnection.selectRoute(sessionId, routeId);
}
}
@Override
- public void deselectRoute(int sessionId, MediaRoute2Info route) {
+ public void deselectRoute(int sessionId, String routeId) {
if (mConnectionReady) {
- mActiveConnection.deselectRoute(sessionId, route.getId());
+ mActiveConnection.deselectRoute(sessionId, routeId);
}
}
@Override
- public void transferToRoute(int sessionId, MediaRoute2Info route) {
+ public void transferToRoute(int sessionId, String routeId) {
if (mConnectionReady) {
- mActiveConnection.transferToRoute(sessionId, route.getId());
+ mActiveConnection.transferToRoute(sessionId, routeId);
}
}
@Override
- public void sendControlRequest(MediaRoute2Info route, Intent request) {
+ public void sendControlRequest(String routeId, Intent request) {
if (mConnectionReady) {
- mActiveConnection.sendControlRequest(route.getId(), request);
+ mActiveConnection.sendControlRequest(routeId, request);
updateBinding();
}
}
@Override
- public void requestSetVolume(MediaRoute2Info route, int volume) {
+ public void requestSetVolume(String routeId, int volume) {
if (mConnectionReady) {
- mActiveConnection.requestSetVolume(route.getId(), volume);
+ mActiveConnection.requestSetVolume(routeId, volume);
updateBinding();
}
}
@Override
- public void requestUpdateVolume(MediaRoute2Info route, int delta) {
+ public void requestUpdateVolume(String routeId, int delta) {
if (mConnectionReady) {
- mActiveConnection.requestUpdateVolume(route.getId(), delta);
+ mActiveConnection.requestUpdateVolume(routeId, delta);
updateBinding();
}
}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 22ba1bf..a5ffbb8 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -79,7 +79,6 @@
@GuardedBy("mLock")
private int mCurrentUserId = -1;
-
MediaRouter2ServiceImpl(Context context) {
mContext = context;
}
@@ -89,17 +88,23 @@
final int uid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(uid);
- Collection<MediaRoute2Info> systemRoutes;
- synchronized (mLock) {
- UserRecord userRecord = mUserRecords.get(userId);
- if (userRecord == null) {
- userRecord = new UserRecord(userId);
- mUserRecords.put(userId, userRecord);
- initializeUserLocked(userRecord);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ Collection<MediaRoute2Info> systemRoutes;
+ synchronized (mLock) {
+ UserRecord userRecord = getOrCreateUserRecordLocked(userId);
+ MediaRoute2ProviderInfo providerInfo =
+ userRecord.mHandler.mSystemProvider.getProviderInfo();
+ if (providerInfo != null) {
+ systemRoutes = providerInfo.getRoutes();
+ } else {
+ systemRoutes = Collections.emptyList();
+ }
}
- systemRoutes = userRecord.mHandler.mSystemProvider.getProviderInfo().getRoutes();
+ return new ArrayList<>(systemRoutes);
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
- return new ArrayList<>(systemRoutes);
}
public void registerClient(@NonNull IMediaRouter2Client client,
@@ -400,12 +405,7 @@
int uid, int pid, String packageName, int userId, boolean trusted) {
final IBinder binder = client.asBinder();
if (mAllClientRecords.get(binder) == null) {
- UserRecord userRecord = mUserRecords.get(userId);
- if (userRecord == null) {
- userRecord = new UserRecord(userId);
- mUserRecords.put(userId, userRecord);
- initializeUserLocked(userRecord);
- }
+ UserRecord userRecord = getOrCreateUserRecordLocked(userId);
Client2Record clientRecord = new Client2Record(userRecord, client, uid, pid,
packageName, trusted);
try {
@@ -556,12 +556,7 @@
final IBinder binder = manager.asBinder();
ManagerRecord managerRecord = mAllManagerRecords.get(binder);
if (managerRecord == null) {
- boolean newUser = false;
- UserRecord userRecord = mUserRecords.get(userId);
- if (userRecord == null) {
- userRecord = new UserRecord(userId);
- newUser = true;
- }
+ UserRecord userRecord = getOrCreateUserRecordLocked(userId);
managerRecord = new ManagerRecord(userRecord, manager, uid, pid, packageName, trusted);
try {
binder.linkToDeath(managerRecord, 0);
@@ -569,11 +564,6 @@
throw new RuntimeException("Media router manager died prematurely.", ex);
}
- if (newUser) {
- mUserRecords.put(userId, userRecord);
- initializeUserLocked(userRecord);
- }
-
userRecord.mManagerRecords.add(managerRecord);
mAllManagerRecords.put(binder, managerRecord);
@@ -661,14 +651,17 @@
return sessionInfos;
}
- private void initializeUserLocked(UserRecord userRecord) {
- if (DEBUG) {
- Slog.d(TAG, userRecord + ": Initialized");
+ private UserRecord getOrCreateUserRecordLocked(int userId) {
+ UserRecord userRecord = mUserRecords.get(userId);
+ if (userRecord == null) {
+ userRecord = new UserRecord(userId);
+ mUserRecords.put(userId, userRecord);
+ if (userId == mCurrentUserId) {
+ userRecord.mHandler.sendMessage(
+ obtainMessage(UserHandler::start, userRecord.mHandler));
+ }
}
- if (userRecord.mUserId == mCurrentUserId) {
- userRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::start, userRecord.mHandler));
- }
+ return userRecord;
}
private void disposeUserIfNeededLocked(UserRecord userRecord) {
@@ -990,7 +983,7 @@
clientRecord, route, controlCategory, requestId);
mSessionCreationRequests.add(request);
- provider.requestCreateSession(clientRecord.mPackageName, route.getId(),
+ provider.requestCreateSession(clientRecord.mPackageName, route.getOriginalId(),
controlCategory, requestId);
}
@@ -1007,7 +1000,8 @@
if (provider == null) {
return;
}
- provider.selectRoute(RouteSessionInfo.getSessionId(uniqueSessionId), route);
+ provider.selectRoute(RouteSessionInfo.getSessionId(uniqueSessionId),
+ route.getOriginalId());
}
private void deselectRouteOnHandler(@NonNull Client2Record clientRecord,
@@ -1023,7 +1017,8 @@
if (provider == null) {
return;
}
- provider.deselectRoute(RouteSessionInfo.getSessionId(uniqueSessionId), route);
+ provider.deselectRoute(RouteSessionInfo.getSessionId(uniqueSessionId),
+ route.getOriginalId());
}
private void transferToRouteOnHandler(@NonNull Client2Record clientRecord,
@@ -1039,7 +1034,8 @@
if (provider == null) {
return;
}
- provider.transferToRoute(RouteSessionInfo.getSessionId(uniqueSessionId), route);
+ provider.transferToRoute(RouteSessionInfo.getSessionId(uniqueSessionId),
+ route.getOriginalId());
}
private boolean checkArgumentsForSessionControl(@NonNull Client2Record clientRecord,
@@ -1246,21 +1242,21 @@
private void sendControlRequest(MediaRoute2Info route, Intent request) {
final MediaRoute2Provider provider = findProvider(route.getProviderId());
if (provider != null) {
- provider.sendControlRequest(route, request);
+ provider.sendControlRequest(route.getOriginalId(), request);
}
}
private void requestSetVolume(MediaRoute2Info route, int volume) {
final MediaRoute2Provider provider = findProvider(route.getProviderId());
if (provider != null) {
- provider.requestSetVolume(route, volume);
+ provider.requestSetVolume(route.getOriginalId(), volume);
}
}
private void requestUpdateVolume(MediaRoute2Info route, int delta) {
final MediaRoute2Provider provider = findProvider(route.getProviderId());
if (provider != null) {
- provider.requestUpdateVolume(route, delta);
+ provider.requestUpdateVolume(route.getOriginalId(), delta);
}
}
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 8fdfcbf..5302765 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -102,33 +102,33 @@
}
@Override
- public void selectRoute(int sessionId, MediaRoute2Info route) {
+ public void selectRoute(int sessionId, String routeId) {
//TODO: implement method
}
@Override
- public void deselectRoute(int sessionId, MediaRoute2Info route) {
+ public void deselectRoute(int sessionId, String routeId) {
//TODO: implement method
}
@Override
- public void transferToRoute(int sessionId, MediaRoute2Info route) {
+ public void transferToRoute(int sessionId, String routeId) {
//TODO: implement method
}
//TODO: implement method
@Override
- public void sendControlRequest(@NonNull MediaRoute2Info route, @NonNull Intent request) {
+ public void sendControlRequest(@NonNull String routeId, @NonNull Intent request) {
}
//TODO: implement method
@Override
- public void requestSetVolume(MediaRoute2Info route, int volume) {
+ public void requestSetVolume(String routeId, int volume) {
}
//TODO: implement method
@Override
- public void requestUpdateVolume(MediaRoute2Info route, int delta) {
+ public void requestUpdateVolume(String routeId, int delta) {
}
void initializeRoutes() {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index f42f4f7..a4f4d07 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -236,7 +236,6 @@
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.TriPredicate;
import com.android.server.DeviceIdleInternal;
@@ -1182,21 +1181,14 @@
@Override
public void onNotificationBubbleChanged(String key, boolean isBubble) {
- String pkg;
- synchronized (mNotificationLock) {
- NotificationRecord r = mNotificationsByKey.get(key);
- pkg = r != null && r.sbn != null ? r.sbn.getPackageName() : null;
- }
- boolean isAppForeground = pkg != null
- && mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
synchronized (mNotificationLock) {
NotificationRecord r = mNotificationsByKey.get(key);
if (r != null) {
final StatusBarNotification n = r.sbn;
final int callingUid = n.getUid();
- pkg = n.getPackageName();
+ final String pkg = n.getPackageName();
if (isBubble && isNotificationAppropriateToBubble(r, pkg, callingUid,
- null /* oldEntry */, isAppForeground)) {
+ null /* oldEntry */)) {
r.getNotification().flags |= FLAG_BUBBLE;
} else {
r.getNotification().flags &= ~FLAG_BUBBLE;
@@ -5386,7 +5378,7 @@
private void flagNotificationForBubbles(NotificationRecord r, String pkg, int userId,
NotificationRecord oldRecord, boolean isAppForeground) {
Notification notification = r.getNotification();
- if (isNotificationAppropriateToBubble(r, pkg, userId, oldRecord, isAppForeground)) {
+ if (isNotificationAppropriateToBubble(r, pkg, userId, oldRecord)) {
notification.flags |= FLAG_BUBBLE;
} else {
notification.flags &= ~FLAG_BUBBLE;
@@ -5406,7 +5398,7 @@
* accounting for user choice & policy.
*/
private boolean isNotificationAppropriateToBubble(NotificationRecord r, String pkg, int userId,
- NotificationRecord oldRecord, boolean isAppForeground) {
+ NotificationRecord oldRecord) {
Notification notification = r.getNotification();
if (!canBubble(r, pkg, userId)) {
// no log: canBubble has its own
@@ -5418,11 +5410,6 @@
return false;
}
- if (isAppForeground) {
- // If the app is foreground it always gets to bubble
- return true;
- }
-
if (oldRecord != null && (oldRecord.getNotification().flags & FLAG_BUBBLE) != 0) {
// This is an update to an active bubble
return true;
@@ -5438,7 +5425,7 @@
boolean isMessageStyle = Notification.MessagingStyle.class.equals(
notification.getNotificationStyle());
if (!isMessageStyle && (peopleList == null || peopleList.isEmpty())) {
- logBubbleError(r.getKey(), "if not foreground, must have a person and be "
+ logBubbleError(r.getKey(), "Must have a person and be "
+ "Notification.MessageStyle or Notification.CATEGORY_CALL");
return false;
}
@@ -5446,6 +5433,11 @@
// Communication is a message or a call
boolean isCall = CATEGORY_CALL.equals(notification.category);
boolean hasForegroundService = (notification.flags & FLAG_FOREGROUND_SERVICE) != 0;
+ if (hasForegroundService && !isCall) {
+ logBubbleError(r.getKey(),
+ "foreground services must be Notification.CATEGORY_CALL to bubble");
+ return false;
+ }
if (isMessageStyle) {
if (hasValidRemoteInput(notification)) {
return true;
@@ -5459,7 +5451,7 @@
logBubbleError(r.getKey(), "calls require foreground service");
return false;
}
- logBubbleError(r.getKey(), "if not foreground, must be "
+ logBubbleError(r.getKey(), "Must be "
+ "Notification.MessageStyle or Notification.CATEGORY_CALL");
return false;
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index f1947ac..b782ca96 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -1154,6 +1154,10 @@
throws RemoteException, IOException {
PackageInfo packageInfo = mPackageManager.getPackageInfo(targetPackageName, 0,
userId);
+ if (packageInfo == null) {
+ throw new IOException("Unable to get target package");
+ }
+
String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
ApkAssets apkAssets = null;
diff --git a/services/core/java/com/android/server/people/PeopleServiceInternal.java b/services/core/java/com/android/server/people/PeopleServiceInternal.java
new file mode 100644
index 0000000..31d30362
--- /dev/null
+++ b/services/core/java/com/android/server/people/PeopleServiceInternal.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.people;
+
+import android.service.appprediction.IPredictionService;
+
+/**
+ * @hide Only for use within the system server.
+ */
+public abstract class PeopleServiceInternal extends IPredictionService.Stub {}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index c868953..426cd01 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -119,7 +119,6 @@
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.dex.DexManager;
@@ -1684,7 +1683,7 @@
computeProgressLocked(true);
// Unpack native libraries for non-incremental installation
- if (isIncrementalInstallation()) {
+ if (!isIncrementalInstallation()) {
extractNativeLibraries(stageDir, params.abiOverride, mayInheritNativeLibs());
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 04e7372..6bd9c48 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -13452,9 +13452,7 @@
// Okay!
targetPackageSetting.setInstallerPackageName(installerPackageName);
- if (installerPackageName != null) {
- mSettings.mInstallerPackages.add(installerPackageName);
- }
+ mSettings.addInstallerPackageNames(targetPackageSetting.installSource);
scheduleWriteSettingsLocked();
}
}
@@ -15160,7 +15158,8 @@
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
final String pkgName = pkg.getPackageName();
- final String installerPackageName = installArgs.installSource.installerPackageName;
+ final InstallSource installSource = installArgs.installSource;
+ final String installerPackageName = installSource.installerPackageName;
final int[] installedForUsers = res.origUsers;
final int installReason = installArgs.installReason;
@@ -15171,7 +15170,7 @@
// For system-bundled packages, we assume that installing an upgraded version
// of the package implies that the user actually wants to run that new code,
// so we enable the package.
- PackageSetting ps = mSettings.mPackages.get(pkgName);
+ final PackageSetting ps = mSettings.mPackages.get(pkgName);
final int userId = installArgs.user.getIdentifier();
if (ps != null) {
if (isSystemApp(pkg)) {
@@ -15208,8 +15207,8 @@
ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName);
}
- ps.setInstallSource(installArgs.installSource);
-
+ ps.setInstallSource(installSource);
+ mSettings.addInstallerPackageNames(installSource);
// When replacing an existing package, preserve the original install reason for all
// users that had the package installed before.
@@ -15239,7 +15238,6 @@
res.name = pkgName;
res.uid = pkg.getUid();
res.pkg = pkg;
- mSettings.setInstallerPackageName(pkgName, installerPackageName);
res.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
//to update install status
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "writeSettings");
@@ -23057,6 +23055,11 @@
}
@Override
+ public void setDeviceOwnerProtectedPackages(List<String> packageNames) {
+ mProtectedPackages.setDeviceOwnerProtectedPackages(packageNames);
+ }
+
+ @Override
public boolean isPackageDataProtected(int userId, String packageName) {
return mProtectedPackages.isPackageDataProtected(userId, packageName);
}
diff --git a/services/core/java/com/android/server/pm/ProtectedPackages.java b/services/core/java/com/android/server/pm/ProtectedPackages.java
index 231168e..4da3cc3 100644
--- a/services/core/java/com/android/server/pm/ProtectedPackages.java
+++ b/services/core/java/com/android/server/pm/ProtectedPackages.java
@@ -24,6 +24,10 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.List;
/**
* Manages package names that need special protection.
@@ -49,6 +53,10 @@
@GuardedBy("this")
private final String mDeviceProvisioningPackage;
+ @Nullable
+ @GuardedBy("this")
+ private List<String> mDeviceOwnerProtectedPackages;
+
private final Context mContext;
public ProtectedPackages(Context context) {
@@ -70,6 +78,10 @@
: profileOwnerPackages.clone();
}
+ public synchronized void setDeviceOwnerProtectedPackages(List<String> packageNames) {
+ mDeviceOwnerProtectedPackages = new ArrayList<String>(packageNames);
+ }
+
private synchronized boolean hasDeviceOwnerOrProfileOwner(int userId, String packageName) {
if (packageName == null) {
return false;
@@ -105,7 +117,8 @@
* can modify its data or package state.
*/
private synchronized boolean isProtectedPackage(String packageName) {
- return packageName != null && packageName.equals(mDeviceProvisioningPackage);
+ return packageName != null && (packageName.equals(mDeviceProvisioningPackage)
+ || ArrayUtils.contains(mDeviceOwnerProtectedPackages, packageName));
}
/**
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 9642a1a..f9a3361 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -280,8 +280,11 @@
/** Map from package name to settings */
final ArrayMap<String, PackageSetting> mPackages = new ArrayMap<>();
- /** List of packages that installed other packages */
- final ArraySet<String> mInstallerPackages = new ArraySet<>();
+ /**
+ * List of packages that were involved in installing other packages, i.e. are listed
+ * in at least one app's InstallSource.
+ */
+ private final ArraySet<String> mInstallerPackages = new ArraySet<>();
/** Map from package name to appId and excluded userids */
private final ArrayMap<String, KernelPackageState> mKernelMapping = new ArrayMap<>();
@@ -441,16 +444,6 @@
return mPermissions.canPropagatePermissionToInstantApp(permName);
}
- void setInstallerPackageName(String pkgName, String installerPkgName) {
- PackageSetting p = mPackages.get(pkgName);
- if (p != null) {
- p.setInstallerPackageName(installerPkgName);
- if (installerPkgName != null) {
- mInstallerPackages.add(installerPkgName);
- }
- }
- }
-
/** Gets and optionally creates a new shared user id. */
SharedUserSetting getSharedUserLPw(String name, int pkgFlags, int pkgPrivateFlags,
boolean create) throws PackageManagerException {
@@ -3777,9 +3770,10 @@
}
if (packageSetting != null) {
packageSetting.uidError = "true".equals(uidError);
- packageSetting.installSource = InstallSource.create(
+ InstallSource installSource = InstallSource.create(
installInitiatingPackageName, installOriginatingPackageName,
installerPackageName, "true".equals(isOrphaned));
+ packageSetting.installSource = installSource;
packageSetting.volumeUuid = volumeUuid;
packageSetting.categoryHint = categoryHint;
packageSetting.legacyNativeLibraryPathString = legacyNativeLibraryPathStr;
@@ -3809,9 +3803,7 @@
packageSetting.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, 0, null);
}
- if (installerPackageName != null) {
- mInstallerPackages.add(installerPackageName);
- }
+ addInstallerPackageNames(installSource);
int outerDepth = parser.getDepth();
int type;
@@ -3870,6 +3862,18 @@
}
}
+ void addInstallerPackageNames(InstallSource installSource) {
+ if (installSource.installerPackageName != null) {
+ mInstallerPackages.add(installSource.installerPackageName);
+ }
+ if (installSource.initiatingPackageName != null) {
+ mInstallerPackages.add(installSource.initiatingPackageName);
+ }
+ if (installSource.originatingPackageName != null) {
+ mInstallerPackages.add(installSource.originatingPackageName);
+ }
+ }
+
private void readDisabledComponentsLPw(PackageSettingBase packageSetting, XmlPullParser parser,
int userId) throws IOException, XmlPullParserException {
int outerDepth = parser.getDepth();
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 1455d4a..e5d5b57 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1132,14 +1132,13 @@
}
/**
- * Returns the user type, e.g. {@link UserManager#USER_TYPE_FULL_GUEST}, of the given userId,
- * or null if the user doesn't exist.
+ * Returns whether the given user (specified by userId) is of the given user type, such as
+ * {@link UserManager#USER_TYPE_FULL_GUEST}.
*/
@Override
- public @Nullable String getUserTypeForUser(@UserIdInt int userId) {
- // TODO(b/142482943): Decide on the appropriate permission requirements.
- checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "getUserTypeForUser");
- return getUserTypeNoChecks(userId);
+ public boolean isUserOfType(@UserIdInt int userId, String userType) {
+ checkManageUsersPermission("check user type");
+ return userType != null && userType.equals(getUserTypeNoChecks(userId));
}
/**
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
index fdb14be..c36d5ef 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
@@ -17,8 +17,10 @@
package com.android.server.recoverysystem;
import android.content.Context;
+import android.content.IntentSender;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
+import android.os.Binder;
import android.os.IRecoverySystem;
import android.os.IRecoverySystemProgressListener;
import android.os.PowerManager;
@@ -28,6 +30,9 @@
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.LockSettingsInternal;
+import com.android.internal.widget.RebootEscrowListener;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
import libcore.io.IoUtils;
@@ -45,7 +50,7 @@
* triggers /system/bin/uncrypt via init to de-encrypt an OTA package on the
* /data partition so that it can be accessed under the recovery image.
*/
-public class RecoverySystemService extends IRecoverySystem.Stub {
+public class RecoverySystemService extends IRecoverySystem.Stub implements RebootEscrowListener {
private static final String TAG = "RecoverySystemService";
private static final boolean DEBUG = false;
@@ -67,6 +72,10 @@
private final Injector mInjector;
private final Context mContext;
+ private boolean mPreparedForReboot;
+ private String mUnattendedRebootToken;
+ private IntentSender mPreparedForRebootIntentSender;
+
static class Injector {
protected final Context mContext;
@@ -78,6 +87,10 @@
return mContext;
}
+ public LockSettingsInternal getLockSettingsService() {
+ return LocalServices.getService(LockSettingsInternal.class);
+ }
+
public PowerManager getPowerManager() {
return (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
}
@@ -120,14 +133,23 @@
* Handles the lifecycle events for the RecoverySystemService.
*/
public static final class Lifecycle extends SystemService {
+ private RecoverySystemService mRecoverySystemService;
+
public Lifecycle(Context context) {
super(context);
}
@Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+ mRecoverySystemService.onSystemServicesReady();
+ }
+ }
+
+ @Override
public void onStart() {
- RecoverySystemService recoverySystemService = new RecoverySystemService(getContext());
- publishBinderService(Context.RECOVERY_SERVICE, recoverySystemService);
+ mRecoverySystemService = new RecoverySystemService(getContext());
+ publishBinderService(Context.RECOVERY_SERVICE, mRecoverySystemService);
}
}
@@ -141,6 +163,11 @@
mContext = injector.getContext();
}
+ @VisibleForTesting
+ void onSystemServicesReady() {
+ mInjector.getLockSettingsService().setRebootEscrowListener(this);
+ }
+
@Override // Binder call
public boolean uncrypt(String filename, IRecoverySystemProgressListener listener) {
if (DEBUG) Slog.d(TAG, "uncrypt: " + filename);
@@ -255,6 +282,95 @@
}
}
+ @Override // Binder call
+ public boolean requestLskf(String updateToken, IntentSender intentSender) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
+
+ if (updateToken == null) {
+ return false;
+ }
+
+ // No need to prepare again for the same token.
+ if (mPreparedForReboot && updateToken.equals(mUnattendedRebootToken)) {
+ return true;
+ }
+
+ mPreparedForReboot = false;
+ mUnattendedRebootToken = updateToken;
+ mPreparedForRebootIntentSender = intentSender;
+
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ mInjector.getLockSettingsService().prepareRebootEscrow();
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+
+ return true;
+ }
+
+ @Override
+ public void onPreparedForReboot(boolean ready) {
+ if (mUnattendedRebootToken == null) {
+ Slog.w(TAG, "onPreparedForReboot called when mUnattendedRebootToken is null");
+ }
+
+ mPreparedForReboot = ready;
+ if (ready) {
+ sendPreparedForRebootIntentIfNeeded();
+ }
+ }
+
+ private void sendPreparedForRebootIntentIfNeeded() {
+ final IntentSender intentSender = mPreparedForRebootIntentSender;
+ if (intentSender != null) {
+ try {
+ intentSender.sendIntent(null, 0, null, null, null);
+ } catch (IntentSender.SendIntentException e) {
+ Slog.w(TAG, "Could not send intent for prepared reboot: " + e.getMessage());
+ }
+ }
+ }
+
+ @Override // Binder call
+ public boolean clearLskf() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
+
+ mPreparedForReboot = false;
+ mUnattendedRebootToken = null;
+ mPreparedForRebootIntentSender = null;
+
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ mInjector.getLockSettingsService().clearRebootEscrow();
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+
+ return true;
+ }
+
+ @Override // Binder call
+ public boolean rebootWithLskf(String updateToken, String reason) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
+
+ if (!mPreparedForReboot) {
+ return false;
+ }
+
+ if (updateToken != null && updateToken.equals(mUnattendedRebootToken)) {
+ if (!mInjector.getLockSettingsService().armRebootEscrow()) {
+ return false;
+ }
+
+ PowerManager pm = mInjector.getPowerManager();
+ pm.reboot(reason);
+ return true;
+ }
+
+ return false;
+ }
+
/**
* Check if any of the init services is still running. If so, we cannot
* start a new uncrypt/setup-bcb/clear-bcb service right away; otherwise
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
index 9b22f33..0b89646 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
@@ -40,7 +40,6 @@
import android.os.HidlMemoryUtil;
import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/**
* Utilities for type conversion between SoundTrigger HAL types and SoundTriggerMiddleware service
@@ -60,7 +59,8 @@
aidlProperties.maxSoundModels = hidlProperties.maxSoundModels;
aidlProperties.maxKeyPhrases = hidlProperties.maxKeyPhrases;
aidlProperties.maxUsers = hidlProperties.maxUsers;
- aidlProperties.recognitionModes = hidlProperties.recognitionModes;
+ aidlProperties.recognitionModes =
+ hidl2aidlRecognitionModes(hidlProperties.recognitionModes);
aidlProperties.captureTransition = hidlProperties.captureTransition;
aidlProperties.maxBufferMs = hidlProperties.maxBufferMs;
aidlProperties.concurrentCapture = hidlProperties.concurrentCapture;
diff --git a/services/core/java/com/android/server/utils/quota/CountQuotaTracker.java b/services/core/java/com/android/server/utils/quota/CountQuotaTracker.java
new file mode 100644
index 0000000..7fe4bf8
--- /dev/null
+++ b/services/core/java/com/android/server/utils/quota/CountQuotaTracker.java
@@ -0,0 +1,802 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.utils.quota;
+
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
+import static com.android.server.utils.quota.Uptc.string;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AlarmManager;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.LongArrayQueue;
+import android.util.Slog;
+import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+import android.util.quota.CountQuotaTrackerProto;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * Class that tracks whether an app has exceeded its defined count quota.
+ *
+ * Quotas are applied per userId-package-tag combination (UPTC). Tags can be null.
+ *
+ * This tracker tracks the count of instantaneous events.
+ *
+ * Limits are applied according to the category the UPTC is placed in. If a UPTC reaches its limit,
+ * it will be considered out of quota until it is below that limit again. A {@link Category} is a
+ * basic construct to apply different limits to different groups of UPTCs. For example, standby
+ * buckets can be a set of categories, or foreground & background could be two categories. If every
+ * UPTC should have the same limits applied, then only one category is needed
+ * ({@see Category.SINGLE_CATEGORY}).
+ *
+ * Note: all limits are enforced per category unless explicitly stated otherwise.
+ *
+ * Test: atest com.android.server.utils.quota.CountQuotaTrackerTest
+ *
+ * @hide
+ */
+public class CountQuotaTracker extends QuotaTracker {
+ private static final String TAG = CountQuotaTracker.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private static final String ALARM_TAG_CLEANUP = "*" + TAG + ".cleanup*";
+
+ @VisibleForTesting
+ static class ExecutionStats {
+ /**
+ * The time after which this record should be considered invalid (out of date), in the
+ * elapsed realtime timebase.
+ */
+ public long expirationTimeElapsed;
+
+ /** The window size that's used when counting the number of events. */
+ public long windowSizeMs;
+ /** The maximum number of events allowed within the window size. */
+ public int countLimit;
+
+ /** The total number of events that occurred in the window. */
+ public int countInWindow;
+
+ /**
+ * The time after which the app will be under the category quota again. This is only valid
+ * if {@link #countInWindow} >= {@link #countLimit}.
+ */
+ public long inQuotaTimeElapsed;
+
+ @Override
+ public String toString() {
+ return "expirationTime=" + expirationTimeElapsed + ", "
+ + "windowSizeMs=" + windowSizeMs + ", "
+ + "countLimit=" + countLimit + ", "
+ + "countInWindow=" + countInWindow + ", "
+ + "inQuotaTime=" + inQuotaTimeElapsed;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ExecutionStats) {
+ ExecutionStats other = (ExecutionStats) obj;
+ return this.expirationTimeElapsed == other.expirationTimeElapsed
+ && this.windowSizeMs == other.windowSizeMs
+ && this.countLimit == other.countLimit
+ && this.countInWindow == other.countInWindow
+ && this.inQuotaTimeElapsed == other.inQuotaTimeElapsed;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 0;
+ result = 31 * result + Long.hashCode(expirationTimeElapsed);
+ result = 31 * result + Long.hashCode(windowSizeMs);
+ result = 31 * result + countLimit;
+ result = 31 * result + countInWindow;
+ result = 31 * result + Long.hashCode(inQuotaTimeElapsed);
+ return result;
+ }
+ }
+
+ /** List of times of all instantaneous events for a UPTC, in chronological order. */
+ // TODO(146148168): introduce a bucketized mode that's more efficient but less accurate
+ @GuardedBy("mLock")
+ private final UptcMap<LongArrayQueue> mEventTimes = new UptcMap<>();
+
+ /** Cached calculation results for each app. */
+ @GuardedBy("mLock")
+ private final UptcMap<ExecutionStats> mExecutionStatsCache = new UptcMap<>();
+
+ private final Handler mHandler;
+
+ @GuardedBy("mLock")
+ private long mNextCleanupTimeElapsed = 0;
+ @GuardedBy("mLock")
+ private final AlarmManager.OnAlarmListener mEventCleanupAlarmListener = () ->
+ CountQuotaTracker.this.mHandler.obtainMessage(MSG_CLEAN_UP_EVENTS).sendToTarget();
+
+ /** The rolling window size for each Category's count limit. */
+ @GuardedBy("mLock")
+ private final ArrayMap<Category, Long> mCategoryCountWindowSizesMs = new ArrayMap<>();
+
+ /**
+ * The maximum count for each Category. For each max value count in the map, the app will
+ * not be allowed any more events within the latest time interval of its rolling window size.
+ *
+ * @see #mCategoryCountWindowSizesMs
+ */
+ @GuardedBy("mLock")
+ private final ArrayMap<Category, Integer> mMaxCategoryCounts = new ArrayMap<>();
+
+ /** The longest period a registered category applies to. */
+ @GuardedBy("mLock")
+ private long mMaxPeriodMs = 0;
+
+ /** Drop any old events. */
+ private static final int MSG_CLEAN_UP_EVENTS = 1;
+
+ public CountQuotaTracker(@NonNull Context context, @NonNull Categorizer categorizer) {
+ this(context, categorizer, new Injector());
+ }
+
+ @VisibleForTesting
+ CountQuotaTracker(@NonNull Context context, @NonNull Categorizer categorizer,
+ Injector injector) {
+ super(context, categorizer, injector);
+
+ mHandler = new CqtHandler(context.getMainLooper());
+ }
+
+ // Exposed API to users.
+
+ /**
+ * Record that an instantaneous event happened.
+ *
+ * @return true if the UPTC is within quota, false otherwise.
+ */
+ public boolean noteEvent(int userId, @NonNull String packageName, @Nullable String tag) {
+ synchronized (mLock) {
+ if (!isEnabledLocked() || isQuotaFreeLocked(userId, packageName)) {
+ return true;
+ }
+ final long nowElapsed = mInjector.getElapsedRealtime();
+
+ final LongArrayQueue times = mEventTimes
+ .getOrCreate(userId, packageName, tag, mCreateLongArrayQueue);
+ times.addLast(nowElapsed);
+ final ExecutionStats stats = getExecutionStatsLocked(userId, packageName, tag);
+ stats.countInWindow++;
+ stats.expirationTimeElapsed = Math.min(stats.expirationTimeElapsed,
+ nowElapsed + stats.windowSizeMs);
+ if (stats.countInWindow == stats.countLimit) {
+ final long windowEdgeElapsed = nowElapsed - stats.windowSizeMs;
+ while (times.size() > 0 && times.peekFirst() < windowEdgeElapsed) {
+ times.removeFirst();
+ }
+ stats.inQuotaTimeElapsed = times.peekFirst() + stats.windowSizeMs;
+ postQuotaStatusChanged(userId, packageName, tag);
+ } else if (stats.countLimit > 9
+ && stats.countInWindow == stats.countLimit * 4 / 5) {
+ // TODO: log high watermark to statsd
+ Slog.w(TAG, string(userId, packageName, tag)
+ + " has reached 80% of it's count limit of " + stats.countLimit);
+ }
+ maybeScheduleCleanupAlarmLocked();
+ return isWithinQuotaLocked(stats);
+ }
+ }
+
+ /**
+ * Set count limit over a rolling time window for the specified category.
+ *
+ * @param category The category these limits apply to.
+ * @param limit The maximum event count an app can have in the rolling window. Must be
+ * nonnegative.
+ * @param timeWindowMs The rolling time window (in milliseconds) to use when checking quota
+ * usage. Must be at least {@value #MIN_WINDOW_SIZE_MS} and no longer than
+ * {@value #MAX_WINDOW_SIZE_MS}
+ */
+ public void setCountLimit(@NonNull Category category, int limit, long timeWindowMs) {
+ if (limit < 0 || timeWindowMs < 0) {
+ throw new IllegalArgumentException("Limit and window size must be nonnegative.");
+ }
+ synchronized (mLock) {
+ final Integer oldLimit = mMaxCategoryCounts.put(category, limit);
+ final long newWindowSizeMs = Math.max(MIN_WINDOW_SIZE_MS,
+ Math.min(timeWindowMs, MAX_WINDOW_SIZE_MS));
+ final Long oldWindowSizeMs = mCategoryCountWindowSizesMs.put(category, newWindowSizeMs);
+ if (oldLimit != null && oldWindowSizeMs != null
+ && oldLimit == limit && oldWindowSizeMs == newWindowSizeMs) {
+ // No change.
+ return;
+ }
+ mDeleteOldEventTimesFunctor.updateMaxPeriod();
+ mMaxPeriodMs = mDeleteOldEventTimesFunctor.mMaxPeriodMs;
+ invalidateAllExecutionStatsLocked();
+ }
+ scheduleQuotaCheck();
+ }
+
+ /**
+ * Gets the count limit for the specified category.
+ */
+ public int getLimit(@NonNull Category category) {
+ synchronized (mLock) {
+ final Integer limit = mMaxCategoryCounts.get(category);
+ if (limit == null) {
+ throw new IllegalArgumentException("Limit for " + category + " not defined");
+ }
+ return limit;
+ }
+ }
+
+ /**
+ * Gets the count time window for the specified category.
+ */
+ public long getWindowSizeMs(@NonNull Category category) {
+ synchronized (mLock) {
+ final Long limitMs = mCategoryCountWindowSizesMs.get(category);
+ if (limitMs == null) {
+ throw new IllegalArgumentException("Limit for " + category + " not defined");
+ }
+ return limitMs;
+ }
+ }
+
+ // Internal implementation.
+
+ @Override
+ @GuardedBy("mLock")
+ void dropEverythingLocked() {
+ mExecutionStatsCache.clear();
+ mEventTimes.clear();
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ @NonNull
+ Handler getHandler() {
+ return mHandler;
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ long getInQuotaTimeElapsedLocked(final int userId, @NonNull final String packageName,
+ @Nullable final String tag) {
+ return getExecutionStatsLocked(userId, packageName, tag).inQuotaTimeElapsed;
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ void handleRemovedAppLocked(String packageName, int uid) {
+ if (packageName == null) {
+ Slog.wtf(TAG, "Told app removed but given null package name.");
+ return;
+ }
+ final int userId = UserHandle.getUserId(uid);
+
+ mEventTimes.delete(userId, packageName);
+ mExecutionStatsCache.delete(userId, packageName);
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ void handleRemovedUserLocked(int userId) {
+ mEventTimes.delete(userId);
+ mExecutionStatsCache.delete(userId);
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName,
+ @Nullable final String tag) {
+ if (!isEnabledLocked()) return true;
+
+ // Quota constraint is not enforced when quota is free.
+ if (isQuotaFreeLocked(userId, packageName)) {
+ return true;
+ }
+
+ return isWithinQuotaLocked(getExecutionStatsLocked(userId, packageName, tag));
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ void maybeUpdateAllQuotaStatusLocked() {
+ final UptcMap<Boolean> doneMap = new UptcMap<>();
+ mEventTimes.forEach((userId, packageName, tag, events) -> {
+ if (!doneMap.contains(userId, packageName, tag)) {
+ maybeUpdateStatusForUptcLocked(userId, packageName, tag);
+ doneMap.add(userId, packageName, tag, Boolean.TRUE);
+ }
+ });
+
+ }
+
+ @Override
+ void maybeUpdateQuotaStatus(final int userId, @NonNull final String packageName,
+ @Nullable final String tag) {
+ synchronized (mLock) {
+ maybeUpdateStatusForUptcLocked(userId, packageName, tag);
+ }
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ void onQuotaFreeChangedLocked(boolean isFree) {
+ // Nothing to do here.
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ void onQuotaFreeChangedLocked(int userId, @NonNull String packageName, boolean isFree) {
+ maybeUpdateStatusForPkgLocked(userId, packageName);
+ }
+
+ @GuardedBy("mLock")
+ private boolean isWithinQuotaLocked(@NonNull final ExecutionStats stats) {
+ return isUnderCountQuotaLocked(stats);
+ }
+
+ @GuardedBy("mLock")
+ private boolean isUnderCountQuotaLocked(@NonNull ExecutionStats stats) {
+ return stats.countInWindow < stats.countLimit;
+ }
+
+ /** Returns the execution stats of the app in the most recent window. */
+ @GuardedBy("mLock")
+ @VisibleForTesting
+ @NonNull
+ ExecutionStats getExecutionStatsLocked(final int userId, @NonNull final String packageName,
+ @Nullable final String tag) {
+ return getExecutionStatsLocked(userId, packageName, tag, true);
+ }
+
+ @GuardedBy("mLock")
+ @NonNull
+ private ExecutionStats getExecutionStatsLocked(final int userId,
+ @NonNull final String packageName, @Nullable String tag,
+ final boolean refreshStatsIfOld) {
+ final ExecutionStats stats =
+ mExecutionStatsCache.getOrCreate(userId, packageName, tag, mCreateExecutionStats);
+ if (refreshStatsIfOld) {
+ final Category category = mCategorizer.getCategory(userId, packageName, tag);
+ final long countWindowSizeMs = mCategoryCountWindowSizesMs.getOrDefault(category,
+ Long.MAX_VALUE);
+ final int countLimit = mMaxCategoryCounts.getOrDefault(category, Integer.MAX_VALUE);
+ if (stats.expirationTimeElapsed <= mInjector.getElapsedRealtime()
+ || stats.windowSizeMs != countWindowSizeMs
+ || stats.countLimit != countLimit) {
+ // The stats are no longer valid.
+ stats.windowSizeMs = countWindowSizeMs;
+ stats.countLimit = countLimit;
+ updateExecutionStatsLocked(userId, packageName, tag, stats);
+ }
+ }
+
+ return stats;
+ }
+
+ @GuardedBy("mLock")
+ @VisibleForTesting
+ void updateExecutionStatsLocked(final int userId, @NonNull final String packageName,
+ @Nullable final String tag, @NonNull ExecutionStats stats) {
+ stats.countInWindow = 0;
+ stats.inQuotaTimeElapsed = 0;
+
+ // This can be used to determine when an app will have enough quota to transition from
+ // out-of-quota to in-quota.
+ final long nowElapsed = mInjector.getElapsedRealtime();
+ stats.expirationTimeElapsed = nowElapsed + mMaxPeriodMs;
+
+ final LongArrayQueue events = mEventTimes.get(userId, packageName, tag);
+ if (events == null) {
+ return;
+ }
+
+ // The minimum time between the start time and the beginning of the events that were
+ // looked at --> how much time the stats will be valid for.
+ long emptyTimeMs = Long.MAX_VALUE - nowElapsed;
+
+ final long eventStartWindowElapsed = nowElapsed - stats.windowSizeMs;
+ for (int i = events.size() - 1; i >= 0; --i) {
+ final long eventTimeElapsed = events.get(i);
+ if (eventTimeElapsed < eventStartWindowElapsed) {
+ // This event happened before the window. No point in going any further.
+ break;
+ }
+ stats.countInWindow++;
+ emptyTimeMs = Math.min(emptyTimeMs, eventTimeElapsed - eventStartWindowElapsed);
+
+ if (stats.countInWindow >= stats.countLimit) {
+ stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
+ eventTimeElapsed + stats.windowSizeMs);
+ }
+ }
+
+ stats.expirationTimeElapsed = nowElapsed + emptyTimeMs;
+ }
+
+ /** Invalidate ExecutionStats for all apps. */
+ @GuardedBy("mLock")
+ private void invalidateAllExecutionStatsLocked() {
+ final long nowElapsed = mInjector.getElapsedRealtime();
+ mExecutionStatsCache.forEach((appStats) -> {
+ if (appStats != null) {
+ appStats.expirationTimeElapsed = nowElapsed;
+ }
+ });
+ }
+
+ @GuardedBy("mLock")
+ private void invalidateAllExecutionStatsLocked(final int userId,
+ @NonNull final String packageName) {
+ final ArrayMap<String, ExecutionStats> appStats =
+ mExecutionStatsCache.get(userId, packageName);
+ if (appStats != null) {
+ final long nowElapsed = mInjector.getElapsedRealtime();
+ final int numStats = appStats.size();
+ for (int i = 0; i < numStats; ++i) {
+ final ExecutionStats stats = appStats.valueAt(i);
+ if (stats != null) {
+ stats.expirationTimeElapsed = nowElapsed;
+ }
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void invalidateExecutionStatsLocked(final int userId, @NonNull final String packageName,
+ @Nullable String tag) {
+ final ExecutionStats stats = mExecutionStatsCache.get(userId, packageName, tag);
+ if (stats != null) {
+ stats.expirationTimeElapsed = mInjector.getElapsedRealtime();
+ }
+ }
+
+ private static final class EarliestEventTimeFunctor implements Consumer<LongArrayQueue> {
+ long earliestTimeElapsed = Long.MAX_VALUE;
+
+ @Override
+ public void accept(LongArrayQueue events) {
+ if (events != null && events.size() > 0) {
+ earliestTimeElapsed = Math.min(earliestTimeElapsed, events.get(0));
+ }
+ }
+
+ void reset() {
+ earliestTimeElapsed = Long.MAX_VALUE;
+ }
+ }
+
+ private final EarliestEventTimeFunctor mEarliestEventTimeFunctor =
+ new EarliestEventTimeFunctor();
+
+ /** Schedule a cleanup alarm if necessary and there isn't already one scheduled. */
+ @GuardedBy("mLock")
+ @VisibleForTesting
+ void maybeScheduleCleanupAlarmLocked() {
+ if (mNextCleanupTimeElapsed > mInjector.getElapsedRealtime()) {
+ // There's already an alarm scheduled. Just stick with that one. There's no way we'll
+ // end up scheduling an earlier alarm.
+ if (DEBUG) {
+ Slog.v(TAG, "Not scheduling cleanup since there's already one at "
+ + mNextCleanupTimeElapsed + " (in " + (mNextCleanupTimeElapsed
+ - mInjector.getElapsedRealtime()) + "ms)");
+ }
+ return;
+ }
+
+ mEarliestEventTimeFunctor.reset();
+ mEventTimes.forEach(mEarliestEventTimeFunctor);
+ final long earliestEndElapsed = mEarliestEventTimeFunctor.earliestTimeElapsed;
+ if (earliestEndElapsed == Long.MAX_VALUE) {
+ // Couldn't find a good time to clean up. Maybe this was called after we deleted all
+ // events.
+ if (DEBUG) {
+ Slog.d(TAG, "Didn't find a time to schedule cleanup");
+ }
+ return;
+ }
+
+ // Need to keep events for all apps up to the max period, regardless of their current
+ // category.
+ long nextCleanupElapsed = earliestEndElapsed + mMaxPeriodMs;
+ if (nextCleanupElapsed - mNextCleanupTimeElapsed <= 10 * MINUTE_IN_MILLIS) {
+ // No need to clean up too often. Delay the alarm if the next cleanup would be too soon
+ // after it.
+ nextCleanupElapsed += 10 * MINUTE_IN_MILLIS;
+ }
+ mNextCleanupTimeElapsed = nextCleanupElapsed;
+ scheduleAlarm(AlarmManager.ELAPSED_REALTIME, nextCleanupElapsed, ALARM_TAG_CLEANUP,
+ mEventCleanupAlarmListener);
+ if (DEBUG) {
+ Slog.d(TAG, "Scheduled next cleanup for " + mNextCleanupTimeElapsed);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean maybeUpdateStatusForPkgLocked(final int userId,
+ @NonNull final String packageName) {
+ final UptcMap<Boolean> done = new UptcMap<>();
+
+ if (!mEventTimes.contains(userId, packageName)) {
+ return false;
+ }
+ final ArrayMap<String, LongArrayQueue> events = mEventTimes.get(userId, packageName);
+ if (events == null) {
+ Slog.wtf(TAG,
+ "Events map was null even though mEventTimes said it contained "
+ + string(userId, packageName, null));
+ return false;
+ }
+
+ // Lambdas can't interact with non-final outer variables.
+ final boolean[] changed = {false};
+ events.forEach((tag, eventList) -> {
+ if (!done.contains(userId, packageName, tag)) {
+ changed[0] |= maybeUpdateStatusForUptcLocked(userId, packageName, tag);
+ done.add(userId, packageName, tag, Boolean.TRUE);
+ }
+ });
+
+ return changed[0];
+ }
+
+ /**
+ * Posts that the quota status for the UPTC has changed if it has changed. Avoid calling if
+ * there are no {@link QuotaChangeListener}s registered as the work done will be useless.
+ *
+ * @return true if the in/out quota status changed
+ */
+ @GuardedBy("mLock")
+ private boolean maybeUpdateStatusForUptcLocked(final int userId,
+ @NonNull final String packageName, @Nullable final String tag) {
+ final boolean oldInQuota = isWithinQuotaLocked(
+ getExecutionStatsLocked(userId, packageName, tag, false));
+
+ final boolean newInQuota;
+ if (!isEnabledLocked() || isQuotaFreeLocked(userId, packageName)) {
+ newInQuota = true;
+ } else {
+ newInQuota = isWithinQuotaLocked(
+ getExecutionStatsLocked(userId, packageName, tag, true));
+ }
+
+ if (!newInQuota) {
+ maybeScheduleStartAlarmLocked(userId, packageName, tag);
+ } else {
+ cancelScheduledStartAlarmLocked(userId, packageName, tag);
+ }
+
+ if (oldInQuota != newInQuota) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "Quota status changed from " + oldInQuota + " to " + newInQuota + " for "
+ + string(userId, packageName, tag));
+ }
+ postQuotaStatusChanged(userId, packageName, tag);
+ return true;
+ }
+
+ return false;
+ }
+
+ private final class DeleteEventTimesFunctor implements Consumer<LongArrayQueue> {
+ private long mMaxPeriodMs;
+
+ @Override
+ public void accept(LongArrayQueue times) {
+ if (times != null) {
+ // Remove everything older than mMaxPeriodMs time ago.
+ while (times.size() > 0
+ && times.peekFirst() <= mInjector.getElapsedRealtime() - mMaxPeriodMs) {
+ times.removeFirst();
+ }
+ }
+ }
+
+ private void updateMaxPeriod() {
+ long maxPeriodMs = 0;
+ for (int i = mCategoryCountWindowSizesMs.size() - 1; i >= 0; --i) {
+ maxPeriodMs = Long.max(maxPeriodMs, mCategoryCountWindowSizesMs.valueAt(i));
+ }
+ mMaxPeriodMs = maxPeriodMs;
+ }
+ }
+
+ private final DeleteEventTimesFunctor mDeleteOldEventTimesFunctor =
+ new DeleteEventTimesFunctor();
+
+ @GuardedBy("mLock")
+ @VisibleForTesting
+ void deleteObsoleteEventsLocked() {
+ mEventTimes.forEach(mDeleteOldEventTimesFunctor);
+ }
+
+ private class CqtHandler extends Handler {
+ CqtHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ synchronized (mLock) {
+ switch (msg.what) {
+ case MSG_CLEAN_UP_EVENTS: {
+ if (DEBUG) {
+ Slog.d(TAG, "Cleaning up events.");
+ }
+ deleteObsoleteEventsLocked();
+ maybeScheduleCleanupAlarmLocked();
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ private Function<Void, LongArrayQueue> mCreateLongArrayQueue = aVoid -> new LongArrayQueue();
+ private Function<Void, ExecutionStats> mCreateExecutionStats = aVoid -> new ExecutionStats();
+
+ //////////////////////// TESTING HELPERS /////////////////////////////
+
+ @VisibleForTesting
+ @Nullable
+ LongArrayQueue getEvents(int userId, String packageName, String tag) {
+ return mEventTimes.get(userId, packageName, tag);
+ }
+
+ //////////////////////////// DATA DUMP //////////////////////////////
+
+ /** Dump state in text format. */
+ public void dump(final IndentingPrintWriter pw) {
+ pw.print(TAG);
+ pw.println(":");
+ pw.increaseIndent();
+
+ synchronized (mLock) {
+ super.dump(pw);
+ pw.println();
+
+ pw.println("Instantaneous events:");
+ pw.increaseIndent();
+ mEventTimes.forEach((userId, pkgName, tag, events) -> {
+ if (events.size() > 0) {
+ pw.print(string(userId, pkgName, tag));
+ pw.println(":");
+ pw.increaseIndent();
+ pw.print(events.get(0));
+ for (int i = 1; i < events.size(); ++i) {
+ pw.print(", ");
+ pw.print(events.get(i));
+ }
+ pw.decreaseIndent();
+ pw.println();
+ }
+ });
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println("Cached execution stats:");
+ pw.increaseIndent();
+ mExecutionStatsCache.forEach((userId, pkgName, tag, stats) -> {
+ if (stats != null) {
+ pw.print(string(userId, pkgName, tag));
+ pw.println(":");
+ pw.increaseIndent();
+ pw.println(stats);
+ pw.decreaseIndent();
+ }
+ });
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println("Limits:");
+ pw.increaseIndent();
+ final int numCategories = mCategoryCountWindowSizesMs.size();
+ for (int i = 0; i < numCategories; ++i) {
+ final Category category = mCategoryCountWindowSizesMs.keyAt(i);
+ pw.print(category);
+ pw.print(": ");
+ pw.print(mMaxCategoryCounts.get(category));
+ pw.print(" events in ");
+ pw.println(TimeUtils.formatDuration(mCategoryCountWindowSizesMs.get(category)));
+ }
+ pw.decreaseIndent();
+ }
+ pw.decreaseIndent();
+ }
+
+ /**
+ * Dump state to proto.
+ *
+ * @param proto The ProtoOutputStream to write to.
+ * @param fieldId The field ID of the {@link CountQuotaTrackerProto}.
+ */
+ public void dump(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+
+ synchronized (mLock) {
+ super.dump(proto, CountQuotaTrackerProto.BASE_QUOTA_DATA);
+
+ for (int i = 0; i < mCategoryCountWindowSizesMs.size(); ++i) {
+ final Category category = mCategoryCountWindowSizesMs.keyAt(i);
+ final long clToken = proto.start(CountQuotaTrackerProto.COUNT_LIMIT);
+ category.dumpDebug(proto, CountQuotaTrackerProto.CountLimit.CATEGORY);
+ proto.write(CountQuotaTrackerProto.CountLimit.LIMIT,
+ mMaxCategoryCounts.get(category));
+ proto.write(CountQuotaTrackerProto.CountLimit.WINDOW_SIZE_MS,
+ mCategoryCountWindowSizesMs.get(category));
+ proto.end(clToken);
+ }
+
+ mExecutionStatsCache.forEach((userId, pkgName, tag, stats) -> {
+ final boolean isQuotaFree = isIndividualQuotaFreeLocked(userId, pkgName);
+
+ final long usToken = proto.start(CountQuotaTrackerProto.UPTC_STATS);
+
+ (new Uptc(userId, pkgName, tag))
+ .dumpDebug(proto, CountQuotaTrackerProto.UptcStats.UPTC);
+
+ proto.write(CountQuotaTrackerProto.UptcStats.IS_QUOTA_FREE, isQuotaFree);
+
+ final LongArrayQueue events = mEventTimes.get(userId, pkgName, tag);
+ if (events != null) {
+ for (int j = events.size() - 1; j >= 0; --j) {
+ final long eToken = proto.start(CountQuotaTrackerProto.UptcStats.EVENTS);
+ proto.write(CountQuotaTrackerProto.Event.TIMESTAMP_ELAPSED, events.get(j));
+ proto.end(eToken);
+ }
+ }
+
+ final long statsToken = proto.start(
+ CountQuotaTrackerProto.UptcStats.EXECUTION_STATS);
+ proto.write(
+ CountQuotaTrackerProto.ExecutionStats.EXPIRATION_TIME_ELAPSED,
+ stats.expirationTimeElapsed);
+ proto.write(
+ CountQuotaTrackerProto.ExecutionStats.WINDOW_SIZE_MS,
+ stats.windowSizeMs);
+ proto.write(CountQuotaTrackerProto.ExecutionStats.COUNT_LIMIT, stats.countLimit);
+ proto.write(
+ CountQuotaTrackerProto.ExecutionStats.COUNT_IN_WINDOW,
+ stats.countInWindow);
+ proto.write(
+ CountQuotaTrackerProto.ExecutionStats.IN_QUOTA_TIME_ELAPSED,
+ stats.inQuotaTimeElapsed);
+ proto.end(statsToken);
+
+ proto.end(usToken);
+ });
+
+ proto.end(token);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/utils/quota/UptcMap.java b/services/core/java/com/android/server/utils/quota/UptcMap.java
index 80f4f0a..a3d6ee5 100644
--- a/services/core/java/com/android/server/utils/quota/UptcMap.java
+++ b/services/core/java/com/android/server/utils/quota/UptcMap.java
@@ -112,37 +112,20 @@
return data.get(tag);
}
- /**
- * Returns the index for which {@link #getUserIdAtIndex(int)} would return the specified userId,
- * or a negative number if the specified userId is not mapped.
- */
- public int indexOfUserId(int userId) {
- return mData.indexOfKey(userId);
- }
-
- /**
- * Returns the index for which {@link #getPackageNameAtIndex(int, int)} would return the
- * specified userId, or a negative number if the specified userId and packageName are not mapped
- * together.
- */
- public int indexOfUserIdAndPackage(int userId, @NonNull String packageName) {
- return mData.indexOfKey(userId, packageName);
- }
-
/** Returns the userId at the given index. */
- public int getUserIdAtIndex(int index) {
+ private int getUserIdAtIndex(int index) {
return mData.keyAt(index);
}
/** Returns the package name at the given index. */
@NonNull
- public String getPackageNameAtIndex(int userIndex, int packageIndex) {
+ private String getPackageNameAtIndex(int userIndex, int packageIndex) {
return mData.keyAt(userIndex, packageIndex);
}
/** Returns the tag at the given index. */
@NonNull
- public String getTagAtIndex(int userIndex, int packageIndex, int tagIndex) {
+ private String getTagAtIndex(int userIndex, int packageIndex, int tagIndex) {
// This structure never inserts a null ArrayMap, so if the indices are valid, valueAt()
// won't return null.
return mData.valueAt(userIndex, packageIndex).keyAt(tagIndex);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index cf37bb5..65d0c4c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -555,7 +555,7 @@
private RemoteAnimationDefinition mRemoteAnimationDefinition;
- private AnimatingActivityRegistry mAnimatingActivityRegistry;
+ AnimatingActivityRegistry mAnimatingActivityRegistry;
private Task mLastParent;
@@ -3088,7 +3088,7 @@
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"Removing app %s delayed=%b animation=%s animating=%b", this, delayed,
- getAnimation(), isAnimating(TRANSITION));
+ getAnimation(), isAnimating(TRANSITION | PARENTS));
ProtoLog.v(WM_DEBUG_ADD_REMOVE, "removeAppToken: %s"
+ " delayed=%b Callers=%s", this, delayed, Debug.getCallers(4));
@@ -3100,7 +3100,7 @@
// If this window was animating, then we need to ensure that the app transition notifies
// that animations have completed in DisplayContent.handleAnimatingStoppedAndTransition(),
// so add to that list now
- if (isAnimating(TRANSITION)) {
+ if (isAnimating(TRANSITION | PARENTS)) {
getDisplayContent().mNoAnimationNotifyOnTransitionFinished.add(token);
}
@@ -3436,7 +3436,7 @@
* color mode set to avoid jank in the middle of the transition.
*/
boolean canShowWindows() {
- return allDrawn && !(isAnimating() && hasNonDefaultColorWindow());
+ return allDrawn && !(isAnimating(PARENTS) && hasNonDefaultColorWindow());
}
/**
@@ -5343,13 +5343,13 @@
if (!allDrawn && w.mightAffectAllDrawn()) {
if (DEBUG_VISIBILITY || WM_DEBUG_ORIENTATION.isLogToLogcat()) {
Slog.v(TAG, "Eval win " + w + ": isDrawn=" + w.isDrawnLw()
- + ", isAnimationSet=" + isAnimating(TRANSITION));
+ + ", isAnimationSet=" + isAnimating(TRANSITION | PARENTS));
if (!w.isDrawnLw()) {
Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceController
+ " pv=" + w.isVisibleByPolicy()
+ " mDrawState=" + winAnimator.drawStateToString()
+ " ph=" + w.isParentWindowHidden() + " th=" + mVisibleRequested
- + " a=" + isAnimating(TRANSITION));
+ + " a=" + isAnimating(TRANSITION | PARENTS));
}
}
@@ -5950,7 +5950,7 @@
@Override
void prepareSurfaces() {
- final boolean show = isVisible() || isAnimating();
+ final boolean show = isVisible() || isAnimating(PARENTS);
if (mSurfaceControl != null) {
if (show && !mLastSurfaceShowing) {
@@ -5978,7 +5978,7 @@
}
void attachThumbnailAnimation() {
- if (!isAnimating()) {
+ if (!isAnimating(PARENTS)) {
return;
}
final GraphicBuffer thumbnailHeader =
@@ -5998,7 +5998,7 @@
* {@link android.app.ActivityOptions#ANIM_OPEN_CROSS_PROFILE_APPS} animation.
*/
void attachCrossProfileAppsThumbnailAnimation() {
- if (!isAnimating()) {
+ if (!isAnimating(PARENTS)) {
return;
}
clearThumbnail();
@@ -7516,7 +7516,7 @@
super.dumpDebug(proto, WINDOW_TOKEN, logLevel);
proto.write(LAST_SURFACE_SHOWING, mLastSurfaceShowing);
proto.write(IS_WAITING_FOR_TRANSITION_START, isWaitingForTransitionStart());
- proto.write(IS_ANIMATING, isAnimating());
+ proto.write(IS_ANIMATING, isAnimating(PARENTS));
if (mThumbnail != null){
mThumbnail.dumpDebug(proto, THUMBNAIL);
}
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 814cb45..c959439 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -784,6 +784,10 @@
null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
null /* tempOtherTaskBounds */, null /* tempOtherTaskInsetBounds */,
PRESERVE_WINDOWS, true /* deferResume */);
+ } else if (overrideWindowingMode != WINDOWING_MODE_PINNED) {
+ // For pinned stack, resize is now part of the {@link WindowContainerTransaction}
+ resize(new Rect(newBounds), null /* tempTaskBounds */,
+ null /* tempTaskInsetBounds */, PRESERVE_WINDOWS, true /* deferResume */);
}
}
if (prevIsAlwaysOnTop != isAlwaysOnTop()) {
@@ -4888,6 +4892,18 @@
}
}
+ @Override
+ protected void onAnimationFinished() {
+ super.onAnimationFinished();
+ // TODO(b/142617871): we may need to add animation type parameter on onAnimationFinished to
+ // identify if the callback is for launch animation finish and then calling
+ // activity#onAnimationFinished.
+ final ActivityRecord activity = getTopMostActivity();
+ if (activity != null) {
+ activity.onAnimationFinished();
+ }
+ }
+
/**
* Sets the current picture-in-picture aspect ratio.
*/
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 3a33a3d..09111d0 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -436,9 +436,11 @@
mNextAppTransition = TRANSIT_UNSET;
mNextAppTransitionFlags = 0;
setAppTransitionState(APP_STATE_RUNNING);
- final AnimationAdapter topOpeningAnim = topOpeningApp != null
- ? topOpeningApp.getAnimation()
- : null;
+ final AnimationAdapter topOpeningAnim =
+ (topOpeningApp != null && topOpeningApp.getAnimatingContainer() != null)
+ ? topOpeningApp.getAnimatingContainer().getAnimation()
+ : null;
+
int redoLayout = notifyAppTransitionStartingLocked(transit,
topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0,
topOpeningAnim != null
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index fcab796..7f94445 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1032,31 +1032,13 @@
// Sets the display content for the children.
onDisplayChanged(this);
- // Add itself as a child to the root container.
- mWmService.mRoot.addChild(this, POSITION_BOTTOM);
-
- // TODO(b/62541591): evaluate whether this is the best spot to declare the
- // {@link DisplayContent} ready for use.
- mDisplayReady = true;
-
- mWmService.mAnimator.addDisplayLocked(mDisplayId);
- mInputMonitor = new InputMonitor(mWmService, mDisplayId);
+ mInputMonitor = new InputMonitor(mWmService, this);
mInsetsStateController = new InsetsStateController(this);
mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this);
- if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Adding display=" + display);
+ if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Creating display=" + display);
mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this);
-
- if (mWmService.mDisplayManagerInternal != null) {
- mWmService.mDisplayManagerInternal
- .setDisplayInfoOverrideFromWindowManager(mDisplayId, getDisplayInfo());
- configureDisplayPolicy();
- }
-
- reconfigureDisplayLocked();
- onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration());
- mWmService.mDisplayNotificationController.dispatchDisplayAdded(this);
}
boolean isReady() {
@@ -3360,7 +3342,7 @@
// to look at all windows below the current target that are in this app, finding the
// highest visible one in layering.
WindowState highestTarget = null;
- if (activity.isAnimating(TRANSITION)) {
+ if (activity.isAnimating(PARENTS | TRANSITION)) {
highestTarget = activity.getHighestAnimLayerWindow(curTarget);
}
@@ -4992,6 +4974,24 @@
// we create the root surfaces explicitly rather than chaining
// up as the default implementation in onParentChanged does. So we
// explicitly do NOT call super here.
+
+ if (!isReady()) {
+ // TODO(b/62541591): evaluate whether this is the best spot to declare the
+ // {@link DisplayContent} ready for use.
+ mDisplayReady = true;
+
+ mWmService.mAnimator.addDisplayLocked(mDisplayId);
+
+ if (mWmService.mDisplayManagerInternal != null) {
+ mWmService.mDisplayManagerInternal
+ .setDisplayInfoOverrideFromWindowManager(mDisplayId, getDisplayInfo());
+ configureDisplayPolicy();
+ }
+
+ reconfigureDisplayLocked();
+ onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration());
+ mWmService.mDisplayNotificationController.dispatchDisplayAdded(this);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 1f9f883..5b892f8 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -10,23 +10,40 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.H.ON_POINTER_DOWN_OUTSIDE_FOCUS;
+import android.os.Build;
import android.os.Debug;
import android.os.IBinder;
+import android.os.Process;
import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.ArrayMap;
import android.util.Slog;
import android.view.IWindow;
import android.view.InputApplicationHandle;
import android.view.KeyEvent;
import android.view.WindowManager;
+import com.android.server.am.ActivityManagerService;
import com.android.server.input.InputManagerService;
import com.android.server.wm.EmbeddedWindowController.EmbeddedWindow;
+import java.io.File;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
final class InputManagerCallback implements InputManagerService.WindowManagerCallbacks {
private static final String TAG = TAG_WITH_CLASS_NAME ? "InputManagerCallback" : TAG_WM;
+
+ /** Prevent spamming the traces because pre-dump cannot aware duplicated ANR. */
+ private static final long PRE_DUMP_MIN_INTERVAL_MS = TimeUnit.SECONDS.toMillis(20);
+ /** The timeout to detect if a monitor is held for a while. */
+ private static final long PRE_DUMP_MONITOR_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(1);
+ /** The last time pre-dump was executed. */
+ private volatile long mLastPreDumpTimeMs;
+
private final WindowManagerService mService;
// Set to true when the first input device configuration change notification
@@ -77,6 +94,77 @@
}
/**
+ * Pre-dump stack trace if the locks of activity manager or window manager (they may be locked
+ * in the path of reporting ANR) cannot be acquired in time. That provides the stack traces
+ * before the real blocking symptom has gone.
+ * <p>
+ * Do not hold the {@link WindowManagerGlobalLock} while calling this method.
+ */
+ private void preDumpIfLockTooSlow() {
+ if (!Build.IS_DEBUGGABLE) {
+ return;
+ }
+ final long now = SystemClock.uptimeMillis();
+ if (mLastPreDumpTimeMs > 0 && now - mLastPreDumpTimeMs < PRE_DUMP_MIN_INTERVAL_MS) {
+ return;
+ }
+
+ final boolean[] shouldDumpSf = { true };
+ final ArrayMap<String, Runnable> monitors = new ArrayMap<>(2);
+ monitors.put(TAG_WM, mService::monitor);
+ monitors.put("ActivityManager", mService.mAmInternal::monitor);
+ final CountDownLatch latch = new CountDownLatch(monitors.size());
+ // The pre-dump will execute if one of the monitors doesn't complete within the timeout.
+ for (int i = 0; i < monitors.size(); i++) {
+ final String name = monitors.keyAt(i);
+ final Runnable monitor = monitors.valueAt(i);
+ // Always create new thread to avoid noise of existing threads. Suppose here won't
+ // create too many threads because it means that watchdog will be triggered first.
+ new Thread() {
+ @Override
+ public void run() {
+ monitor.run();
+ latch.countDown();
+ final long elapsed = SystemClock.uptimeMillis() - now;
+ if (elapsed > PRE_DUMP_MONITOR_TIMEOUT_MS) {
+ Slog.i(TAG_WM, "Pre-dump acquired " + name + " in " + elapsed + "ms");
+ } else if (TAG_WM.equals(name)) {
+ // Window manager is the main client of SurfaceFlinger. If window manager
+ // is responsive, the stack traces of SurfaceFlinger may not be important.
+ shouldDumpSf[0] = false;
+ }
+ };
+ }.start();
+ }
+ try {
+ if (latch.await(PRE_DUMP_MONITOR_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ return;
+ }
+ } catch (InterruptedException ignored) { }
+ mLastPreDumpTimeMs = now;
+ Slog.i(TAG_WM, "Pre-dump for unresponsive");
+
+ final ArrayList<Integer> firstPids = new ArrayList<>(1);
+ firstPids.add(ActivityManagerService.MY_PID);
+ ArrayList<Integer> nativePids = null;
+ final int[] pids = shouldDumpSf[0]
+ ? Process.getPidsForCommands(new String[] { "/system/bin/surfaceflinger" })
+ : null;
+ if (pids != null) {
+ nativePids = new ArrayList<>(1);
+ for (int pid : pids) {
+ nativePids.add(pid);
+ }
+ }
+
+ final File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
+ null /* processCpuTracker */, null /* lastPids */, nativePids);
+ if (tracesFile != null) {
+ tracesFile.renameTo(new File(tracesFile.getParent(), tracesFile.getName() + "_pre"));
+ }
+ }
+
+ /**
* Notifies the window manager about an application that is not responding.
* Returns a new timeout to continue waiting in nanoseconds, or 0 to abort dispatch.
*
@@ -89,6 +177,9 @@
WindowState windowState = null;
boolean aboveSystem = false;
int windowPid = INVALID_PID;
+
+ preDumpIfLockTooSlow();
+
//TODO(b/141764879) Limit scope of wm lock when input calls notifyANR
synchronized (mService.mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index a8ff500..c4b67d7 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -151,10 +151,10 @@
private final UpdateInputWindows mUpdateInputWindows = new UpdateInputWindows();
- public InputMonitor(WindowManagerService service, int displayId) {
+ InputMonitor(WindowManagerService service, DisplayContent displayContent) {
mService = service;
- mDisplayContent = mService.mRoot.getDisplayContent(displayId);
- mDisplayId = displayId;
+ mDisplayContent = displayContent;
+ mDisplayId = displayContent.getDisplayId();
mInputTransaction = mService.mTransactionFactory.get();
mHandler = mService.mAnimationHandler;
mUpdateInputForAllWindowsConsumer = new UpdateInputForAllWindowsConsumer();
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 0b9be1a..6f7eeab 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -378,9 +378,10 @@
int getMode() {
final DisplayContent dc = mWindowContainer.getDisplayContent();
- if (dc.mOpeningApps.contains(mWindowContainer)) {
+ final ActivityRecord topActivity = mWindowContainer.getTopMostActivity();
+ if (dc.mOpeningApps.contains(topActivity)) {
return RemoteAnimationTarget.MODE_OPENING;
- } else if (dc.mChangingApps.contains(mWindowContainer)) {
+ } else if (dc.mChangingApps.contains(topActivity)) {
return RemoteAnimationTarget.MODE_CHANGING;
} else {
return RemoteAnimationTarget.MODE_CLOSING;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 704ab67..a7bf660 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1377,6 +1377,7 @@
for (int displayNdx = 0; displayNdx < displays.length; ++displayNdx) {
final Display display = displays[displayNdx];
final DisplayContent displayContent = new DisplayContent(display, this);
+ addChild(displayContent, POSITION_BOTTOM);
if (displayContent.mDisplayId == DEFAULT_DISPLAY) {
mDefaultDisplay = displayContent;
}
@@ -1445,6 +1446,7 @@
}
// The display hasn't been added to ActivityManager yet, create a new record now.
displayContent = new DisplayContent(display, this);
+ addChild(displayContent, POSITION_BOTTOM);
return displayContent;
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 914b95c..9a140da 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -94,6 +94,7 @@
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
import static java.lang.Integer.MAX_VALUE;
@@ -2555,6 +2556,16 @@
return getAppAnimationLayer(ANIMATION_LAYER_HOME);
}
+ @Override
+ Rect getAnimationBounds(int appStackClipMode) {
+ // TODO(b/131661052): we should remove appStackClipMode with hierarchical animations.
+ if (appStackClipMode == STACK_CLIP_BEFORE_ANIM && getStack() != null) {
+ // Using the stack bounds here effectively applies the clipping before animation.
+ return getStack().getBounds();
+ }
+ return super.getAnimationBounds(appStackClipMode);
+ }
+
boolean shouldAnimate() {
// Don't animate while the task runs recents animation but only if we are in the mode
// where we cancel with deferred screenshot, which means that the controller has
@@ -2568,6 +2579,18 @@
}
@Override
+ protected void onAnimationFinished() {
+ super.onAnimationFinished();
+ // TODO(b/142617871): we may need to add animation type parameter on onAnimationFinished to
+ // identify if the callback is for launch animation finish and then calling
+ // activity#onAnimationFinished.
+ final ActivityRecord activity = getTopMostActivity();
+ if (activity != null) {
+ activity.onAnimationFinished();
+ }
+ }
+
+ @Override
SurfaceControl.Builder makeSurface() {
return super.makeSurface().setMetadata(METADATA_TASK_ID, mTaskId);
}
@@ -2594,7 +2617,7 @@
@Override
RemoteAnimationTarget createRemoteAnimationTarget(
RemoteAnimationController.RemoteAnimationRecord record) {
- final ActivityRecord activity = getTopVisibleActivity();
+ final ActivityRecord activity = getTopMostActivity();
return activity != null ? activity.createRemoteAnimationTarget(record) : null;
}
@@ -2721,7 +2744,13 @@
getDimBounds(mTmpDimBoundsRect);
// Bounds need to be relative, as the dim layer is a child.
- mTmpDimBoundsRect.offsetTo(0, 0);
+ if (inFreeformWindowingMode()) {
+ getBounds(mTmpRect);
+ mTmpDimBoundsRect.offsetTo(mTmpDimBoundsRect.left - mTmpRect.left,
+ mTmpDimBoundsRect.top - mTmpRect.top);
+ } else {
+ mTmpDimBoundsRect.offsetTo(0, 0);
+ }
if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
scheduleAnimation();
}
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 6ff4b2e..137d122 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -121,7 +121,7 @@
mFindResults.resetTopWallpaper = true;
if (w.mActivityRecord != null && !w.mActivityRecord.isVisible()
- && !w.mActivityRecord.isAnimating(TRANSITION)) {
+ && !w.mActivityRecord.isAnimating(TRANSITION | PARENTS)) {
// If this window's app token is hidden and not animating, it is of no interest to us.
if (DEBUG_WALLPAPER) Slog.v(TAG, "Skipping hidden and not animating token: " + w);
@@ -139,7 +139,7 @@
}
final boolean keyguardGoingAwayWithWallpaper = (w.mActivityRecord != null
- && w.mActivityRecord.isAnimating(TRANSITION)
+ && w.mActivityRecord.isAnimating(TRANSITION | PARENTS)
&& AppTransition.isKeyguardGoingAwayTransit(w.mActivityRecord.getTransit())
&& (w.mActivityRecord.getTransitFlags()
& TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0);
@@ -162,9 +162,11 @@
final RecentsAnimationController recentsAnimationController =
mService.getRecentsAnimationController();
- final boolean animationWallpaper = w.mActivityRecord != null
- && w.mActivityRecord.getAnimation() != null
- && w.mActivityRecord.getAnimation().getShowWallpaper();
+ final WindowContainer animatingContainer =
+ w.mActivityRecord != null ? w.mActivityRecord.getAnimatingContainer() : null;
+ final boolean animationWallpaper = animatingContainer != null
+ && animatingContainer.getAnimation() != null
+ && animatingContainer.getAnimation().getShowWallpaper();
final boolean hasWallpaper = (w.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0
|| animationWallpaper;
final boolean isRecentsTransitionTarget = (recentsAnimationController != null
@@ -228,14 +230,14 @@
if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + ", obscured="
+ (wallpaperTarget != null ? Boolean.toString(wallpaperTarget.mObscured) : "??")
+ " animating=" + ((wallpaperTarget != null && wallpaperTarget.mActivityRecord != null)
- ? wallpaperTarget.mActivityRecord.isAnimating(TRANSITION) : null)
+ ? wallpaperTarget.mActivityRecord.isAnimating(TRANSITION | PARENTS) : null)
+ " prev=" + mPrevWallpaperTarget
+ " recentsAnimationWallpaperVisible=" + isAnimatingWithRecentsComponent);
return (wallpaperTarget != null
&& (!wallpaperTarget.mObscured
|| isAnimatingWithRecentsComponent
|| (wallpaperTarget.mActivityRecord != null
- && wallpaperTarget.mActivityRecord.isAnimating(TRANSITION))))
+ && wallpaperTarget.mActivityRecord.isAnimating(TRANSITION | PARENTS))))
|| mPrevWallpaperTarget != null;
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 5daf567..cefef37 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -777,7 +777,7 @@
* otherwise.
*/
boolean isWaitingForTransitionStart() {
- return false;
+ return getActivity(app -> app.isWaitingForTransitionStart()) != null;
}
/**
@@ -785,7 +785,7 @@
* {@code ActivityRecord#isAnimating(TRANSITION)}, {@code false} otherwise.
*/
boolean isAppTransitioning() {
- return getActivity(app -> app.isAnimating(TRANSITION)) != null;
+ return getActivity(app -> app.isAnimating(PARENTS | TRANSITION)) != null;
}
/**
@@ -1895,7 +1895,7 @@
if (adapter != null) {
startAnimation(getPendingTransaction(), adapter, !isVisible());
if (adapter.getShowWallpaper()) {
- mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+ getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
}
if (thumbnailAdapter != null) {
mThumbnail.startAnimation(
@@ -2040,7 +2040,8 @@
}
boolean okToDisplay() {
- return mDisplayContent != null && mDisplayContent.okToDisplay();
+ final DisplayContent dc = getDisplayContent();
+ return dc != null && dc.okToDisplay();
}
boolean okToAnimate() {
@@ -2048,7 +2049,8 @@
}
boolean okToAnimate(boolean ignoreFrozen) {
- return mDisplayContent != null && mDisplayContent.okToAnimate(ignoreFrozen);
+ final DisplayContent dc = getDisplayContent();
+ return dc != null && dc.okToAnimate(ignoreFrozen);
}
@Override
@@ -2090,6 +2092,21 @@
}
/**
+ * @return The {@link WindowContainer} which is running an animation.
+ *
+ * It traverses from the current container to its parents recursively. If nothing is animating,
+ * it will return {@code null}.
+ */
+ @Nullable
+ WindowContainer getAnimatingContainer() {
+ if (isAnimating()) {
+ return this;
+ }
+ final WindowContainer parent = getParent();
+ return (parent != null) ? parent.getAnimatingContainer() : null;
+ }
+
+ /**
* @see SurfaceAnimator#startDelayingAnimationStart
*/
void startDelayingAnimationStart() {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b542956..96bc8e9 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -670,7 +670,7 @@
void updateRequestedInsetsState(InsetsState state) {
// Only update the sources the client is actually controlling.
- for (int i = state.getSourcesCount(); i >= 0; i--) {
+ for (int i = state.getSourcesCount() - 1; i >= 0; i--) {
final InsetsSource source = state.sourceAt(i);
mRequestedInsetsState.addSource(source);
}
@@ -985,8 +985,11 @@
final int layoutXDiff;
final int layoutYDiff;
final WindowState imeWin = mWmService.mRoot.getCurrentInputMethodWindow();
+ final boolean isInputMethodAdjustTarget = windowsAreFloating
+ ? dc.mInputMethodTarget != null && task == dc.mInputMethodTarget.getTask()
+ : isInputMethodTarget();
final boolean isImeTarget =
- imeWin != null && imeWin.isVisibleNow() && isInputMethodTarget();
+ imeWin != null && imeWin.isVisibleNow() && isInputMethodAdjustTarget;
if (isFullscreenAndFillsDisplay || layoutInParentFrame()) {
// We use the parent frame as the containing frame for fullscreen and child windows
mWindowFrames.mContainingFrame.set(mWindowFrames.mParentFrame);
@@ -1017,7 +1020,8 @@
final int distanceToTop = Math.max(mWindowFrames.mContainingFrame.top
- mWindowFrames.mContentFrame.top, 0);
int offs = Math.min(bottomOverlap, distanceToTop);
- mWindowFrames.mContainingFrame.top -= offs;
+ mWindowFrames.mContainingFrame.offset(0, -offs);
+ mInsetFrame.offset(0, -offs);
}
} else if (!inPinnedWindowingMode() && mWindowFrames.mContainingFrame.bottom
> mWindowFrames.mParentFrame.bottom) {
@@ -1587,7 +1591,7 @@
// TODO: Can we consolidate this with #isVisible() or have a more appropriate name for this?
boolean isWinVisibleLw() {
return (mActivityRecord == null || mActivityRecord.mVisibleRequested
- || mActivityRecord.isAnimating(TRANSITION)) && isVisible();
+ || mActivityRecord.isAnimating(TRANSITION | PARENTS)) && isVisible();
}
/**
@@ -1813,7 +1817,7 @@
// Starting window that's exiting will be removed when the animation finishes.
// Mark all relevant flags for that onExitAnimationDone will proceed all the way
// to actually remove it.
- if (!visible && isVisibleNow && mActivityRecord.isAnimating(TRANSITION)) {
+ if (!visible && isVisibleNow && mActivityRecord.isAnimating(PARENTS | TRANSITION)) {
mAnimatingExit = true;
mRemoveOnExit = true;
mWindowRemovalAllowed = true;
@@ -2082,7 +2086,7 @@
this, mWinAnimator.mSurfaceController, mAnimatingExit, mRemoveOnExit,
mHasSurface, mWinAnimator.getShown(),
isAnimating(TRANSITION | PARENTS),
- mActivityRecord != null && mActivityRecord.isAnimating(TRANSITION),
+ mActivityRecord != null && mActivityRecord.isAnimating(PARENTS | TRANSITION),
mWillReplaceWindow,
mWmService.mDisplayFrozen, Debug.getCallers(6));
@@ -4301,7 +4305,7 @@
+ " tok.visible=" + (mActivityRecord != null && mActivityRecord.isVisible())
+ " animating=" + isAnimating(TRANSITION | PARENTS)
+ " tok animating="
- + (mActivityRecord != null && mActivityRecord.isAnimating(TRANSITION))
+ + (mActivityRecord != null && mActivityRecord.isAnimating(TRANSITION | PARENTS))
+ " Callers=" + Debug.getCallers(4));
}
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 1a11766..486616d 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1398,7 +1398,7 @@
mWin.getDisplayContent().adjustForImeIfNeeded();
}
- return mWin.isAnimating(TRANSITION | PARENTS);
+ return mWin.isAnimating(PARENTS);
}
void dumpDebug(ProtoOutputStream proto, long fieldId) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 398ce50..13246ba 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -367,6 +367,8 @@
private static final String TAG_TRANSFER_OWNERSHIP_BUNDLE = "transfer-ownership-bundle";
+ private static final String TAG_PROTECTED_PACKAGES = "protected-packages";
+
private static final int REQUEST_EXPIRE_PASSWORD = 5571;
private static final long MS_PER_DAY = TimeUnit.DAYS.toMillis(1);
@@ -742,6 +744,9 @@
// This is the list of component allowed to start lock task mode.
List<String> mLockTaskPackages = new ArrayList<>();
+ // List of packages protected by device owner
+ List<String> mProtectedPackages = new ArrayList<>();
+
// Bitfield of feature flags to be enabled during LockTask mode.
// We default on the power button menu, in order to be consistent with pre-P behaviour.
int mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
@@ -3245,6 +3250,13 @@
out.endTag(null, TAG_OWNER_INSTALLED_CA_CERT);
}
+ for (int i = 0, size = policy.mProtectedPackages.size(); i < size; i++) {
+ String packageName = policy.mProtectedPackages.get(i);
+ out.startTag(null, TAG_PROTECTED_PACKAGES);
+ out.attribute(null, ATTR_NAME, packageName);
+ out.endTag(null, TAG_PROTECTED_PACKAGES);
+ }
+
out.endTag(null, "policies");
out.endDocument();
@@ -3358,6 +3370,7 @@
policy.mAdminMap.clear();
policy.mAffiliationIds.clear();
policy.mOwnerInstalledCaCerts.clear();
+ policy.mProtectedPackages.clear();
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
@@ -3455,6 +3468,8 @@
policy.mCurrentInputMethodSet = true;
} else if (TAG_OWNER_INSTALLED_CA_CERT.equals(tag)) {
policy.mOwnerInstalledCaCerts.add(parser.getAttributeValue(null, ATTR_ALIAS));
+ } else if (TAG_PROTECTED_PACKAGES.equals(tag)) {
+ policy.mProtectedPackages.add(parser.getAttributeValue(null, ATTR_NAME));
} else {
Slog.w(LOG_TAG, "Unknown tag: " + tag);
XmlUtils.skipCurrentTag(parser);
@@ -3486,6 +3501,7 @@
updateMaximumTimeToLockLocked(userHandle);
updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle);
updateLockTaskFeaturesLocked(policy.mLockTaskFeatures, userHandle);
+ updateProtectedPackagesLocked(policy.mProtectedPackages);
if (policy.mStatusBarDisabled) {
setStatusBarDisabledInternal(policy.mStatusBarDisabled, userHandle);
}
@@ -3511,6 +3527,10 @@
}
}
+ private void updateProtectedPackagesLocked(List<String> packages) {
+ mInjector.getPackageManagerInternal().setDeviceOwnerProtectedPackages(packages);
+ }
+
private void updateLockTaskFeaturesLocked(int flags, int userId) {
long ident = mInjector.binderClearCallingIdentity();
try {
@@ -8349,6 +8369,8 @@
policy.mLockTaskPackages.clear();
updateLockTaskPackagesLocked(policy.mLockTaskPackages, userId);
policy.mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
+ policy.mProtectedPackages.clear();
+ updateProtectedPackagesLocked(policy.mProtectedPackages);
saveSettingsLocked(userId);
try {
@@ -9065,6 +9087,10 @@
pw.increaseIndent();
pw.print("mPasswordOwner="); pw.println(policy.mPasswordOwner);
pw.decreaseIndent();
+ pw.println();
+ pw.increaseIndent();
+ pw.print("mProtectedPackages="); pw.println(policy.mProtectedPackages);
+ pw.decreaseIndent();
}
}
@@ -14698,4 +14724,42 @@
return DevicePolicyConstants.loadFromString(
mInjector.settingsGlobalGetString(Global.DEVICE_POLICY_CONSTANTS));
}
+
+ @Override
+ public void setProtectedPackages(ComponentName who, List<String> packages) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ Preconditions.checkNotNull(packages, "packages is null");
+
+ enforceDeviceOwner(who);
+ synchronized (getLockObject()) {
+ final int userHandle = mInjector.userHandleGetCallingUserId();
+ setProtectedPackagesLocked(userHandle, packages);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_PACKAGES_PROTECTED)
+ .setAdmin(who)
+ .setStrings(packages.toArray(new String[packages.size()]))
+ .write();
+ }
+ }
+
+ private void setProtectedPackagesLocked(int userHandle, List<String> packages) {
+ final DevicePolicyData policy = getUserData(userHandle);
+ policy.mProtectedPackages = packages;
+
+ // Store the settings persistently.
+ saveSettingsLocked(userHandle);
+ updateProtectedPackagesLocked(packages);
+ }
+
+ @Override
+ public List<String> getProtectedPackages(ComponentName who) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
+
+ enforceDeviceOwner(who);
+ final int userHandle = mInjector.binderGetCallingUserHandle().getIdentifier();
+ synchronized (getLockObject()) {
+ final List<String> packages = getUserData(userHandle).mProtectedPackages;
+ return packages == null ? Collections.EMPTY_LIST : packages;
+ }
+ }
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 08221f9..bfec51c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -126,6 +126,7 @@
import com.android.server.os.BugreportManagerService;
import com.android.server.os.DeviceIdentifiersPolicyService;
import com.android.server.os.SchedulingPolicyService;
+import com.android.server.people.PeopleService;
import com.android.server.pm.BackgroundDexOptService;
import com.android.server.pm.CrossProfileAppsService;
import com.android.server.pm.DataLoaderManagerService;
@@ -1917,6 +1918,10 @@
t.traceBegin("StartCrossProfileAppsService");
mSystemServiceManager.startService(CrossProfileAppsService.class);
t.traceEnd();
+
+ t.traceBegin("StartPeopleService");
+ mSystemServiceManager.startService(PeopleService.class);
+ t.traceEnd();
}
if (!isWatch) {
diff --git a/services/people/Android.bp b/services/people/Android.bp
new file mode 100644
index 0000000..d64097a
--- /dev/null
+++ b/services/people/Android.bp
@@ -0,0 +1,5 @@
+java_library_static {
+ name: "services.people",
+ srcs: ["java/**/*.java"],
+ libs: ["services.core"],
+}
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
new file mode 100644
index 0000000..ef3da60
--- /dev/null
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.people;
+
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.IPredictionCallback;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * A service that manages the people and conversations provided by apps.
+ */
+public class PeopleService extends SystemService {
+
+ private static final String TAG = "PeopleService";
+
+ /**
+ * Initializes the system service.
+ *
+ * @param context The system server context.
+ */
+ public PeopleService(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ publishLocalService(PeopleServiceInternal.class, new LocalService());
+ }
+
+ @VisibleForTesting
+ final class LocalService extends PeopleServiceInternal {
+
+ private Map<AppPredictionSessionId, SessionInfo> mSessions = new ArrayMap<>();
+
+ @Override
+ public void onCreatePredictionSession(AppPredictionContext context,
+ AppPredictionSessionId sessionId) {
+ mSessions.put(sessionId, new SessionInfo(context));
+ }
+
+ @Override
+ public void notifyAppTargetEvent(AppPredictionSessionId sessionId, AppTargetEvent event) {
+ runForSession(sessionId,
+ sessionInfo -> sessionInfo.getPredictor().onAppTargetEvent(event));
+ }
+
+ @Override
+ public void notifyLaunchLocationShown(AppPredictionSessionId sessionId,
+ String launchLocation, ParceledListSlice targetIds) {
+ runForSession(sessionId,
+ sessionInfo -> sessionInfo.getPredictor().onLaunchLocationShown(
+ launchLocation, targetIds.getList()));
+ }
+
+ @Override
+ public void sortAppTargets(AppPredictionSessionId sessionId, ParceledListSlice targets,
+ IPredictionCallback callback) {
+ runForSession(sessionId,
+ sessionInfo -> sessionInfo.getPredictor().onSortAppTargets(
+ targets.getList(),
+ targetList -> invokePredictionCallback(callback, targetList)));
+ }
+
+ @Override
+ public void registerPredictionUpdates(AppPredictionSessionId sessionId,
+ IPredictionCallback callback) {
+ runForSession(sessionId, sessionInfo -> sessionInfo.addCallback(callback));
+ }
+
+ @Override
+ public void unregisterPredictionUpdates(AppPredictionSessionId sessionId,
+ IPredictionCallback callback) {
+ runForSession(sessionId, sessionInfo -> sessionInfo.removeCallback(callback));
+ }
+
+ @Override
+ public void requestPredictionUpdate(AppPredictionSessionId sessionId) {
+ runForSession(sessionId,
+ sessionInfo -> sessionInfo.getPredictor().onRequestPredictionUpdate());
+ }
+
+ @Override
+ public void onDestroyPredictionSession(AppPredictionSessionId sessionId) {
+ runForSession(sessionId, sessionInfo -> {
+ sessionInfo.onDestroy();
+ mSessions.remove(sessionId);
+ });
+ }
+
+ @VisibleForTesting
+ SessionInfo getSessionInfo(AppPredictionSessionId sessionId) {
+ return mSessions.get(sessionId);
+ }
+
+ private void runForSession(AppPredictionSessionId sessionId, Consumer<SessionInfo> method) {
+ SessionInfo sessionInfo = mSessions.get(sessionId);
+ if (sessionInfo == null) {
+ Slog.e(TAG, "Failed to find the session: " + sessionId);
+ return;
+ }
+ method.accept(sessionInfo);
+ }
+
+ private void invokePredictionCallback(IPredictionCallback callback,
+ List<AppTarget> targets) {
+ try {
+ callback.onResult(new ParceledListSlice<>(targets));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to calling callback" + e);
+ }
+ }
+ }
+}
diff --git a/services/people/java/com/android/server/people/SessionInfo.java b/services/people/java/com/android/server/people/SessionInfo.java
new file mode 100644
index 0000000..df7cedf
--- /dev/null
+++ b/services/people/java/com/android/server/people/SessionInfo.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.people;
+
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppTarget;
+import android.app.prediction.IPredictionCallback;
+import android.content.pm.ParceledListSlice;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.people.prediction.ConversationPredictor;
+
+import java.util.List;
+
+/** Manages the information and callbacks in an app prediction request session. */
+class SessionInfo {
+
+ private static final String TAG = "SessionInfo";
+
+ private final ConversationPredictor mConversationPredictor;
+ private final RemoteCallbackList<IPredictionCallback> mCallbacks =
+ new RemoteCallbackList<>();
+
+ SessionInfo(AppPredictionContext predictionContext) {
+ mConversationPredictor = new ConversationPredictor(predictionContext,
+ this::updatePredictions);
+ }
+
+ void addCallback(IPredictionCallback callback) {
+ mCallbacks.register(callback);
+ }
+
+ void removeCallback(IPredictionCallback callback) {
+ mCallbacks.unregister(callback);
+ }
+
+ ConversationPredictor getPredictor() {
+ return mConversationPredictor;
+ }
+
+ void onDestroy() {
+ mCallbacks.kill();
+ }
+
+ private void updatePredictions(List<AppTarget> targets) {
+ int callbackCount = mCallbacks.beginBroadcast();
+ for (int i = 0; i < callbackCount; i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).onResult(new ParceledListSlice<>(targets));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to calling callback" + e);
+ }
+ }
+ mCallbacks.finishBroadcast();
+ }
+}
diff --git a/services/people/java/com/android/server/people/prediction/ConversationPredictor.java b/services/people/java/com/android/server/people/prediction/ConversationPredictor.java
new file mode 100644
index 0000000..de71d29
--- /dev/null
+++ b/services/people/java/com/android/server/people/prediction/ConversationPredictor.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.people.prediction;
+
+import android.annotation.MainThread;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.AppTargetId;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.function.Consumer;
+
+/**
+ * Predictor that predicts the conversations or apps the user is most likely to open.
+ */
+public class ConversationPredictor {
+
+ private final AppPredictionContext mPredictionContext;
+ private final Consumer<List<AppTarget>> mUpdatePredictionsMethod;
+ private final ExecutorService mCallbackExecutor;
+
+ public ConversationPredictor(AppPredictionContext predictionContext,
+ Consumer<List<AppTarget>> updatePredictionsMethod) {
+ mPredictionContext = predictionContext;
+ mUpdatePredictionsMethod = updatePredictionsMethod;
+ mCallbackExecutor = Executors.newSingleThreadExecutor();
+ }
+
+ /**
+ * Called by the client app to indicate a target launch.
+ */
+ @MainThread
+ public void onAppTargetEvent(AppTargetEvent event) {
+ }
+
+ /**
+ * Called by the client app to indicate a particular location has been shown to the user.
+ */
+ @MainThread
+ public void onLaunchLocationShown(String launchLocation, List<AppTargetId> targetIds) {
+ }
+
+ /**
+ * Called by the client app to request sorting of the provided targets based on the prediction
+ * ranking.
+ */
+ @MainThread
+ public void onSortAppTargets(List<AppTarget> targets, Consumer<List<AppTarget>> callback) {
+ mCallbackExecutor.execute(() -> callback.accept(targets));
+ }
+
+ /**
+ * Called by the client app to request target predictions.
+ */
+ @MainThread
+ public void onRequestPredictionUpdate() {
+ List<AppTarget> targets = new ArrayList<>();
+ mCallbackExecutor.execute(() -> mUpdatePredictionsMethod.accept(targets));
+ }
+
+ @VisibleForTesting
+ public Consumer<List<AppTarget>> getUpdatePredictionsMethod() {
+ return mUpdatePredictionsMethod;
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
index 0504d14..067f23a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.appop;
import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
+import static android.app.AppOpsManager.FILTER_BY_UID;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_ERRORED;
import static android.app.AppOpsManager.MODE_FOREGROUND;
@@ -287,7 +289,7 @@
mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null);
AppOpsManager.HistoricalOps historicalOps = new AppOpsManager.HistoricalOps(0, 15000);
- historicalOps.increaseAccessCount(OP_READ_SMS, mMyUid, sMyPackageName,
+ historicalOps.increaseAccessCount(OP_READ_SMS, mMyUid, sMyPackageName, null,
AppOpsManager.UID_STATE_PERSISTENT, 0, 1);
mAppOpsService.addHistoricalOps(historicalOps);
@@ -300,8 +302,8 @@
});
// First, do a fetch to ensure it's written
- mAppOpsService.getHistoricalOps(mMyUid, sMyPackageName, null, 0, Long.MAX_VALUE, 0,
- callback);
+ mAppOpsService.getHistoricalOps(mMyUid, sMyPackageName, null, null,
+ FILTER_BY_UID | FILTER_BY_PACKAGE_NAME, 0, Long.MAX_VALUE, 0, callback);
latchRef.get().await(5, TimeUnit.SECONDS);
assertThat(latchRef.get().getCount()).isEqualTo(0);
@@ -312,8 +314,8 @@
latchRef.set(new CountDownLatch(1));
- mAppOpsService.getHistoricalOps(mMyUid, sMyPackageName, null, 0, Long.MAX_VALUE, 0,
- callback);
+ mAppOpsService.getHistoricalOps(mMyUid, sMyPackageName, null, null,
+ FILTER_BY_UID | FILTER_BY_PACKAGE_NAME, 0, Long.MAX_VALUE, 0, callback);
latchRef.get().await(5, TimeUnit.SECONDS);
assertThat(latchRef.get().getCount()).isEqualTo(0);
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java
new file mode 100644
index 0000000..80aec73
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java
@@ -0,0 +1,848 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.utils.quota;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.utils.quota.Category.SINGLE_CATEGORY;
+import static com.android.server.utils.quota.QuotaTracker.MAX_WINDOW_SIZE_MS;
+import static com.android.server.utils.quota.QuotaTracker.MIN_WINDOW_SIZE_MS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.AlarmManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.LongArrayQueue;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.utils.quota.CountQuotaTracker.ExecutionStats;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+/**
+ * Tests for {@link CountQuotaTracker}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CountQuotaTrackerTest {
+ private static final long SECOND_IN_MILLIS = 1000L;
+ private static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS;
+ private static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
+ private static final String TAG_CLEANUP = "*CountQuotaTracker.cleanup*";
+ private static final String TAG_QUOTA_CHECK = "*QuotaTracker.quota_check*";
+ private static final String TEST_PACKAGE = "com.android.frameworks.mockingservicestests";
+ private static final String TEST_TAG = "testing";
+ private static final int TEST_UID = 10987;
+ private static final int TEST_USER_ID = 0;
+
+ /** A {@link Category} to represent the ACTIVE standby bucket. */
+ private static final Category ACTIVE_BUCKET_CATEGORY = new Category("ACTIVE");
+
+ /** A {@link Category} to represent the WORKING_SET standby bucket. */
+ private static final Category WORKING_SET_BUCKET_CATEGORY = new Category("WORKING_SET");
+
+ /** A {@link Category} to represent the FREQUENT standby bucket. */
+ private static final Category FREQUENT_BUCKET_CATEGORY = new Category("FREQUENT");
+
+ /** A {@link Category} to represent the RARE standby bucket. */
+ private static final Category RARE_BUCKET_CATEGORY = new Category("RARE");
+
+ private CountQuotaTracker mQuotaTracker;
+ private final CategorizerForTest mCategorizer = new CategorizerForTest();
+ private final InjectorForTest mInjector = new InjectorForTest();
+ private final TestQuotaChangeListener mQuotaChangeListener = new TestQuotaChangeListener();
+ private BroadcastReceiver mReceiver;
+ private MockitoSession mMockingSession;
+ @Mock
+ private AlarmManager mAlarmManager;
+ @Mock
+ private Context mContext;
+
+ static class CategorizerForTest implements Categorizer {
+ private Category mCategoryToUse = SINGLE_CATEGORY;
+
+ @Override
+ public Category getCategory(int userId,
+ String packageName, String tag) {
+ return mCategoryToUse;
+ }
+ }
+
+ private static class InjectorForTest extends QuotaTracker.Injector {
+ private long mElapsedTime = SystemClock.elapsedRealtime();
+
+ @Override
+ long getElapsedRealtime() {
+ return mElapsedTime;
+ }
+
+ @Override
+ boolean isAlarmManagerReady() {
+ return true;
+ }
+ }
+
+ private static class TestQuotaChangeListener implements QuotaChangeListener {
+
+ @Override
+ public void onQuotaStateChanged(int userId, String packageName, String tag) {
+
+ }
+ }
+
+ @Before
+ public void setUp() {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .mockStatic(LocalServices.class)
+ .startMocking();
+
+ when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
+ when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager);
+
+ // Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
+ // in the past, and QuotaController sometimes floors values at 0, so if the test time
+ // causes sessions with negative timestamps, they will fail.
+ advanceElapsedClock(24 * HOUR_IN_MILLIS);
+
+ // Initialize real objects.
+ // Capture the listeners.
+ ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ mQuotaTracker = new CountQuotaTracker(mContext, mCategorizer, mInjector);
+ mQuotaTracker.setEnabled(true);
+ mQuotaTracker.setQuotaFree(false);
+ mQuotaTracker.registerQuotaChangeListener(mQuotaChangeListener);
+ verify(mContext, atLeastOnce()).registerReceiverAsUser(
+ receiverCaptor.capture(), eq(UserHandle.ALL), any(), any(), any());
+ mReceiver = receiverCaptor.getValue();
+ }
+
+ @After
+ public void tearDown() {
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
+ }
+
+ /**
+ * Returns true if the two {@link LongArrayQueue}s have the same size and the same elements in
+ * the same order.
+ */
+ private static boolean longArrayQueueEquals(LongArrayQueue queue1, LongArrayQueue queue2) {
+ if (queue1 == queue2) {
+ return true;
+ } else if (queue1 == null || queue2 == null) {
+ return false;
+ }
+ if (queue1.size() == queue2.size()) {
+ for (int i = 0; i < queue1.size(); ++i) {
+ if (queue1.get(i) != queue2.get(i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private void advanceElapsedClock(long incrementMs) {
+ mInjector.mElapsedTime += incrementMs;
+ }
+
+ private void logEvents(int count) {
+ logEvents(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, count);
+ }
+
+ private void logEvents(int userId, String pkgName, String tag, int count) {
+ for (int i = 0; i < count; ++i) {
+ mQuotaTracker.noteEvent(userId, pkgName, tag);
+ }
+ }
+
+ private void logEventAt(long timeElapsed) {
+ logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, timeElapsed);
+ }
+
+ private void logEventAt(int userId, String pkgName, String tag, long timeElapsed) {
+ long now = mInjector.getElapsedRealtime();
+ mInjector.mElapsedTime = timeElapsed;
+ mQuotaTracker.noteEvent(userId, pkgName, tag);
+ mInjector.mElapsedTime = now;
+ }
+
+ private void logEventsAt(int userId, String pkgName, String tag, long timeElapsed, int count) {
+ for (int i = 0; i < count; ++i) {
+ logEventAt(userId, pkgName, tag, timeElapsed);
+ }
+ }
+
+ @Test
+ public void testDeleteObsoleteEventsLocked() {
+ // Count window size should only apply to event list.
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 7, 2 * HOUR_IN_MILLIS);
+
+ final long now = mInjector.getElapsedRealtime();
+
+ logEventAt(now - 6 * HOUR_IN_MILLIS);
+ logEventAt(now - 5 * HOUR_IN_MILLIS);
+ logEventAt(now - 4 * HOUR_IN_MILLIS);
+ logEventAt(now - 3 * HOUR_IN_MILLIS);
+ logEventAt(now - 2 * HOUR_IN_MILLIS);
+ logEventAt(now - HOUR_IN_MILLIS);
+ logEventAt(now - 1);
+
+ LongArrayQueue expectedEvents = new LongArrayQueue();
+ expectedEvents.addLast(now - HOUR_IN_MILLIS);
+ expectedEvents.addLast(now - 1);
+
+ mQuotaTracker.deleteObsoleteEventsLocked();
+
+ LongArrayQueue remainingEvents = mQuotaTracker.getEvents(TEST_USER_ID, TEST_PACKAGE,
+ TEST_TAG);
+ assertTrue(longArrayQueueEquals(expectedEvents, remainingEvents));
+ }
+
+ @Test
+ public void testAppRemoval() {
+ final long now = mInjector.getElapsedRealtime();
+ logEventAt(TEST_USER_ID, "com.android.test.remove", "tag1", now - (6 * HOUR_IN_MILLIS));
+ logEventAt(TEST_USER_ID, "com.android.test.remove", "tag2",
+ now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS));
+ logEventAt(TEST_USER_ID, "com.android.test.remove", "tag3", now - (HOUR_IN_MILLIS));
+ // Test that another app isn't affected.
+ LongArrayQueue expected1 = new LongArrayQueue();
+ expected1.addLast(now - 10 * MINUTE_IN_MILLIS);
+ LongArrayQueue expected2 = new LongArrayQueue();
+ expected2.addLast(now - 70 * MINUTE_IN_MILLIS);
+ logEventAt(TEST_USER_ID, "com.android.test.stay", "tag1", now - 10 * MINUTE_IN_MILLIS);
+ logEventAt(TEST_USER_ID, "com.android.test.stay", "tag2", now - 70 * MINUTE_IN_MILLIS);
+
+ Intent removal = new Intent(Intent.ACTION_PACKAGE_FULLY_REMOVED,
+ Uri.fromParts("package", "com.android.test.remove", null));
+ removal.putExtra(Intent.EXTRA_UID, TEST_UID);
+ mReceiver.onReceive(mContext, removal);
+ assertNull(
+ mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.remove", "tag1"));
+ assertNull(
+ mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.remove", "tag2"));
+ assertNull(
+ mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.remove", "tag3"));
+ assertTrue(longArrayQueueEquals(expected1,
+ mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.stay", "tag1")));
+ assertTrue(longArrayQueueEquals(expected2,
+ mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.stay", "tag2")));
+ }
+
+ @Test
+ public void testUserRemoval() {
+ final long now = mInjector.getElapsedRealtime();
+ logEventAt(TEST_USER_ID, TEST_PACKAGE, "tag1", now - (6 * HOUR_IN_MILLIS));
+ logEventAt(TEST_USER_ID, TEST_PACKAGE, "tag2",
+ now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS));
+ logEventAt(TEST_USER_ID, TEST_PACKAGE, "tag3", now - (HOUR_IN_MILLIS));
+ // Test that another user isn't affected.
+ LongArrayQueue expected = new LongArrayQueue();
+ expected.addLast(now - (70 * MINUTE_IN_MILLIS));
+ expected.addLast(now - (10 * MINUTE_IN_MILLIS));
+ logEventAt(10, TEST_PACKAGE, "tag4", now - (70 * MINUTE_IN_MILLIS));
+ logEventAt(10, TEST_PACKAGE, "tag4", now - 10 * MINUTE_IN_MILLIS);
+
+ Intent removal = new Intent(Intent.ACTION_USER_REMOVED);
+ removal.putExtra(Intent.EXTRA_USER_HANDLE, TEST_USER_ID);
+ mReceiver.onReceive(mContext, removal);
+ assertNull(mQuotaTracker.getEvents(TEST_USER_ID, TEST_PACKAGE, "tag1"));
+ assertNull(mQuotaTracker.getEvents(TEST_USER_ID, TEST_PACKAGE, "tag2"));
+ assertNull(mQuotaTracker.getEvents(TEST_USER_ID, TEST_PACKAGE, "tag3"));
+ longArrayQueueEquals(expected, mQuotaTracker.getEvents(10, TEST_PACKAGE, "tag4"));
+ }
+
+ @Test
+ public void testUpdateExecutionStatsLocked_NoTimer() {
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 3, 24 * HOUR_IN_MILLIS);
+ final long now = mInjector.getElapsedRealtime();
+
+ // Added in chronological order.
+ logEventAt(now - 4 * HOUR_IN_MILLIS);
+ logEventAt(now - HOUR_IN_MILLIS);
+ logEventAt(now - 5 * MINUTE_IN_MILLIS);
+ logEventAt(now - MINUTE_IN_MILLIS);
+
+ // Test an app that hasn't had any activity.
+ ExecutionStats expectedStats = new ExecutionStats();
+ ExecutionStats inputStats = new ExecutionStats();
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS;
+ inputStats.countLimit = expectedStats.countLimit = 3;
+ // Invalid time is now +24 hours since there are no sessions at all for the app.
+ expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, "com.android.test.not.run", TEST_TAG,
+ inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ // Now test app that has had activity.
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = MINUTE_IN_MILLIS;
+ // Invalid time is now since there was an event exactly windowSizeMs ago.
+ expectedStats.expirationTimeElapsed = now;
+ expectedStats.countInWindow = 1;
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + 2 * MINUTE_IN_MILLIS;
+ expectedStats.countInWindow = 1;
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 4 * MINUTE_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + 3 * MINUTE_IN_MILLIS;
+ expectedStats.countInWindow = 1;
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 49 * MINUTE_IN_MILLIS;
+ // Invalid time is now +44 minutes since the earliest session in the window is now-5
+ // minutes.
+ expectedStats.expirationTimeElapsed = now + 44 * MINUTE_IN_MILLIS;
+ expectedStats.countInWindow = 2;
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 50 * MINUTE_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + 45 * MINUTE_IN_MILLIS;
+ expectedStats.countInWindow = 2;
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS;
+ // Invalid time is now since the event is at the very edge of the window
+ // cutoff time.
+ expectedStats.expirationTimeElapsed = now;
+ expectedStats.countInWindow = 3;
+ // App is at event count limit but the oldest session is at the edge of the window, so
+ // in quota time is now.
+ expectedStats.inQuotaTimeElapsed = now;
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
+ expectedStats.countInWindow = 3;
+ expectedStats.inQuotaTimeElapsed = now + HOUR_IN_MILLIS;
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * HOUR_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
+ expectedStats.countInWindow = 4;
+ expectedStats.inQuotaTimeElapsed = now + 4 * HOUR_IN_MILLIS;
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + 2 * HOUR_IN_MILLIS;
+ expectedStats.countInWindow = 4;
+ expectedStats.inQuotaTimeElapsed = now + 5 * HOUR_IN_MILLIS;
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+ assertEquals(expectedStats, inputStats);
+ }
+
+ /**
+ * Tests that getExecutionStatsLocked returns the correct stats.
+ */
+ @Test
+ public void testGetExecutionStatsLocked_Values() {
+ // The handler could cause changes to the cached stats, so prevent it from operating in
+ // this test.
+ Handler handler = mQuotaTracker.getHandler();
+ spyOn(handler);
+ doNothing().when(handler).handleMessage(any());
+
+ mQuotaTracker.setCountLimit(RARE_BUCKET_CATEGORY, 3, 24 * HOUR_IN_MILLIS);
+ mQuotaTracker.setCountLimit(FREQUENT_BUCKET_CATEGORY, 4, 8 * HOUR_IN_MILLIS);
+ mQuotaTracker.setCountLimit(WORKING_SET_BUCKET_CATEGORY, 9, 2 * HOUR_IN_MILLIS);
+ mQuotaTracker.setCountLimit(ACTIVE_BUCKET_CATEGORY, 10, 10 * MINUTE_IN_MILLIS);
+
+ final long now = mInjector.getElapsedRealtime();
+
+ logEventAt(now - 23 * HOUR_IN_MILLIS);
+ logEventAt(now - 7 * HOUR_IN_MILLIS);
+ logEventAt(now - 5 * HOUR_IN_MILLIS);
+ logEventAt(now - 2 * HOUR_IN_MILLIS);
+ logEventAt(now - 5 * MINUTE_IN_MILLIS);
+
+ ExecutionStats expectedStats = new ExecutionStats();
+
+ // Active
+ expectedStats.expirationTimeElapsed = now + 5 * MINUTE_IN_MILLIS;
+ expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
+ expectedStats.countLimit = 10;
+ expectedStats.countInWindow = 1;
+ mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY;
+ assertEquals(expectedStats,
+ mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+
+ // Working
+ expectedStats.expirationTimeElapsed = now;
+ expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
+ expectedStats.countLimit = 9;
+ expectedStats.countInWindow = 2;
+ mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY;
+ assertEquals(expectedStats,
+ mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+
+ // Frequent
+ expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
+ expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
+ expectedStats.countLimit = 4;
+ expectedStats.countInWindow = 4;
+ expectedStats.inQuotaTimeElapsed = now + HOUR_IN_MILLIS;
+ mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY;
+ assertEquals(expectedStats,
+ mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+
+ // Rare
+ expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
+ expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
+ expectedStats.countLimit = 3;
+ expectedStats.countInWindow = 5;
+ expectedStats.inQuotaTimeElapsed = now + 19 * HOUR_IN_MILLIS;
+ mCategorizer.mCategoryToUse = RARE_BUCKET_CATEGORY;
+ assertEquals(expectedStats,
+ mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+ }
+
+ /**
+ * Tests that getExecutionStatsLocked returns the correct stats soon after device startup.
+ */
+ @Test
+ public void testGetExecutionStatsLocked_Values_BeginningOfTime() {
+ // Set time to 3 minutes after boot.
+ mInjector.mElapsedTime = 3 * MINUTE_IN_MILLIS;
+
+ logEventAt(30_000);
+ logEventAt(MINUTE_IN_MILLIS);
+ logEventAt(2 * MINUTE_IN_MILLIS);
+
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
+
+ ExecutionStats expectedStats = new ExecutionStats();
+
+ expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
+ expectedStats.countLimit = 10;
+ expectedStats.countInWindow = 3;
+ expectedStats.expirationTimeElapsed = 2 * HOUR_IN_MILLIS + 30_000;
+ assertEquals(expectedStats,
+ mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+ }
+
+ @Test
+ public void testisWithinQuota_GlobalQuotaFree() {
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 2 * HOUR_IN_MILLIS);
+ mQuotaTracker.setQuotaFree(true);
+ assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, null));
+ assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, "com.android.random.app", null));
+ }
+
+ @Test
+ public void testisWithinQuota_UptcQuotaFree() {
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 2 * HOUR_IN_MILLIS);
+ mQuotaTracker.setQuotaFree(TEST_USER_ID, TEST_PACKAGE, true);
+ assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, null));
+ assertFalse(
+ mQuotaTracker.isWithinQuota(TEST_USER_ID, "com.android.random.app", null));
+ }
+
+ @Test
+ public void testisWithinQuota_UnderCount() {
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
+ logEvents(5);
+ assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+ }
+
+ @Test
+ public void testisWithinQuota_OverCount() {
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 25, HOUR_IN_MILLIS);
+ logEvents(TEST_USER_ID, "com.android.test.spam", TEST_TAG, 30);
+ assertFalse(mQuotaTracker.isWithinQuota(TEST_USER_ID, "com.android.test.spam", TEST_TAG));
+ }
+
+ @Test
+ public void testisWithinQuota_EqualsCount() {
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 25, HOUR_IN_MILLIS);
+ logEvents(25);
+ assertFalse(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+ }
+
+ @Test
+ public void testisWithinQuota_DifferentCategories() {
+ mQuotaTracker.setCountLimit(RARE_BUCKET_CATEGORY, 3, 24 * HOUR_IN_MILLIS);
+ mQuotaTracker.setCountLimit(FREQUENT_BUCKET_CATEGORY, 4, 24 * HOUR_IN_MILLIS);
+ mQuotaTracker.setCountLimit(WORKING_SET_BUCKET_CATEGORY, 5, 24 * HOUR_IN_MILLIS);
+ mQuotaTracker.setCountLimit(ACTIVE_BUCKET_CATEGORY, 6, 24 * HOUR_IN_MILLIS);
+
+ for (int i = 0; i < 7; ++i) {
+ logEvents(1);
+
+ mCategorizer.mCategoryToUse = RARE_BUCKET_CATEGORY;
+ assertEquals("Rare has incorrect quota status with " + (i + 1) + " events",
+ i < 2,
+ mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+ mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY;
+ assertEquals("Frequent has incorrect quota status with " + (i + 1) + " events",
+ i < 3,
+ mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+ mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY;
+ assertEquals("Working has incorrect quota status with " + (i + 1) + " events",
+ i < 4,
+ mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+ mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY;
+ assertEquals("Active has incorrect quota status with " + (i + 1) + " events",
+ i < 5,
+ mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+ }
+ }
+
+ @Test
+ public void testMaybeScheduleCleanupAlarmLocked() {
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 5, 24 * HOUR_IN_MILLIS);
+
+ // No sessions saved yet.
+ mQuotaTracker.maybeScheduleCleanupAlarmLocked();
+ verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_CLEANUP), any(), any());
+
+ // Test with only one timing session saved.
+ final long now = mInjector.getElapsedRealtime();
+ logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 6 * HOUR_IN_MILLIS);
+ mQuotaTracker.maybeScheduleCleanupAlarmLocked();
+ verify(mAlarmManager, timeout(1000).times(1))
+ .set(anyInt(), eq(now + 18 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any());
+
+ // Test with new (more recent) timing sessions saved. AlarmManger shouldn't be called again.
+ logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 3 * HOUR_IN_MILLIS);
+ logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS);
+ mQuotaTracker.maybeScheduleCleanupAlarmLocked();
+ verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 18 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any());
+ }
+
+ /**
+ * Tests that maybeScheduleStartAlarm schedules an alarm for the right time.
+ */
+ @Test
+ public void testMaybeScheduleStartAlarmLocked() {
+ // logEvent calls maybeScheduleCleanupAlarmLocked which interferes with these tests
+ // because it schedules an alarm too. Prevent it from doing so.
+ spyOn(mQuotaTracker);
+ doNothing().when(mQuotaTracker).maybeScheduleCleanupAlarmLocked();
+
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 8 * HOUR_IN_MILLIS);
+
+ // No sessions saved yet.
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ // Test with timing sessions out of window.
+ final long now = mInjector.getElapsedRealtime();
+ logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 10 * HOUR_IN_MILLIS, 20);
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ // Test with timing sessions in window but still in quota.
+ final long start = now - (6 * HOUR_IN_MILLIS);
+ final long expectedAlarmTime = start + 8 * HOUR_IN_MILLIS;
+ logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, start, 5);
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ // Add some more sessions, but still in quota.
+ logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 3 * HOUR_IN_MILLIS, 1);
+ logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS, 3);
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ // Test when out of quota.
+ logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS, 1);
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ verify(mAlarmManager, timeout(1000).times(1))
+ .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+
+ // Alarm already scheduled, so make sure it's not scheduled again.
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+ }
+
+ /** Tests that the start alarm is properly rescheduled if the app's category is changed. */
+ @Test
+ public void testMaybeScheduleStartAlarmLocked_CategoryChange() {
+ // logEvent calls maybeScheduleCleanupAlarmLocked which interferes with these tests
+ // because it schedules an alarm too. Prevent it from doing so.
+ spyOn(mQuotaTracker);
+ doNothing().when(mQuotaTracker).maybeScheduleCleanupAlarmLocked();
+
+ mQuotaTracker.setCountLimit(RARE_BUCKET_CATEGORY, 10, 24 * HOUR_IN_MILLIS);
+ mQuotaTracker.setCountLimit(FREQUENT_BUCKET_CATEGORY, 10, 8 * HOUR_IN_MILLIS);
+ mQuotaTracker.setCountLimit(WORKING_SET_BUCKET_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
+ mQuotaTracker.setCountLimit(ACTIVE_BUCKET_CATEGORY, 10, 10 * MINUTE_IN_MILLIS);
+
+ final long now = mInjector.getElapsedRealtime();
+
+ // Affects rare bucket
+ logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 12 * HOUR_IN_MILLIS, 9);
+ // Affects frequent and rare buckets
+ logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 4 * HOUR_IN_MILLIS, 4);
+ // Affects working, frequent, and rare buckets
+ final long outOfQuotaTime = now - HOUR_IN_MILLIS;
+ logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, outOfQuotaTime, 7);
+ // Affects all buckets
+ logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 5 * MINUTE_IN_MILLIS, 3);
+
+ InOrder inOrder = inOrder(mAlarmManager);
+
+ // Start in ACTIVE bucket.
+ mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY;
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ inOrder.verify(mAlarmManager, never())
+ .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ inOrder.verify(mAlarmManager, never()).cancel(any(AlarmManager.OnAlarmListener.class));
+
+ // And down from there.
+ final long expectedWorkingAlarmTime = outOfQuotaTime + (2 * HOUR_IN_MILLIS);
+ mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY;
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ inOrder.verify(mAlarmManager, timeout(1000).times(1))
+ .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+
+ final long expectedFrequentAlarmTime = outOfQuotaTime + (8 * HOUR_IN_MILLIS);
+ mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY;
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ inOrder.verify(mAlarmManager, timeout(1000).times(1))
+ .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+
+ final long expectedRareAlarmTime = outOfQuotaTime + (24 * HOUR_IN_MILLIS);
+ mCategorizer.mCategoryToUse = RARE_BUCKET_CATEGORY;
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ inOrder.verify(mAlarmManager, timeout(1000).times(1))
+ .set(anyInt(), eq(expectedRareAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+
+ // And back up again.
+ mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY;
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ inOrder.verify(mAlarmManager, timeout(1000).times(1))
+ .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+
+ mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY;
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ inOrder.verify(mAlarmManager, timeout(1000).times(1))
+ .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+
+ mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY;
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ inOrder.verify(mAlarmManager, timeout(1000).times(1))
+ .cancel(any(AlarmManager.OnAlarmListener.class));
+ inOrder.verify(mAlarmManager, timeout(1000).times(0))
+ .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ }
+
+ @Test
+ public void testConstantsUpdating_ValidValues() {
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 60_000);
+ assertEquals(0, mQuotaTracker.getLimit(SINGLE_CATEGORY));
+ assertEquals(60_000, mQuotaTracker.getWindowSizeMs(SINGLE_CATEGORY));
+ }
+
+ @Test
+ public void testConstantsUpdating_InvalidValues() {
+ // Test negatives.
+ try {
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, -1, 5000);
+ fail("Negative count limit didn't throw an exception");
+ } catch (IllegalArgumentException e) {
+ // Success
+ }
+ try {
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 1, -1);
+ fail("Negative count window size didn't throw an exception");
+ } catch (IllegalArgumentException e) {
+ // Success
+ }
+
+ // Test window sizes too low.
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 1);
+ assertEquals(MIN_WINDOW_SIZE_MS, mQuotaTracker.getWindowSizeMs(SINGLE_CATEGORY));
+
+ // Test window sizes too high.
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 365 * 24 * HOUR_IN_MILLIS);
+ assertEquals(MAX_WINDOW_SIZE_MS, mQuotaTracker.getWindowSizeMs(SINGLE_CATEGORY));
+ }
+
+ /** Tests that events aren't counted when global quota is free. */
+ @Test
+ public void testLogEvent_GlobalQuotaFree() {
+ mQuotaTracker.setQuotaFree(true);
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
+
+ ExecutionStats stats =
+ mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ assertEquals(0, stats.countInWindow);
+
+ for (int i = 0; i < 10; ++i) {
+ mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats);
+ assertEquals(0, stats.countInWindow);
+ }
+ }
+
+ /**
+ * Tests that events are counted when global quota is not free.
+ */
+ @Test
+ public void testLogEvent_GlobalQuotaNotFree() {
+ mQuotaTracker.setQuotaFree(false);
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
+
+ ExecutionStats stats =
+ mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ assertEquals(0, stats.countInWindow);
+
+ for (int i = 0; i < 10; ++i) {
+ mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats);
+ assertEquals(i + 1, stats.countInWindow);
+ }
+ }
+
+ /** Tests that events aren't counted when the uptc quota is free. */
+ @Test
+ public void testLogEvent_UptcQuotaFree() {
+ mQuotaTracker.setQuotaFree(TEST_USER_ID, TEST_PACKAGE, true);
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
+
+ ExecutionStats stats =
+ mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ assertEquals(0, stats.countInWindow);
+
+ for (int i = 0; i < 10; ++i) {
+ mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats);
+ assertEquals(0, stats.countInWindow);
+ }
+ }
+
+ /**
+ * Tests that events are counted when UPTC quota is not free.
+ */
+ @Test
+ public void testLogEvent_UptcQuotaNotFree() {
+ mQuotaTracker.setQuotaFree(TEST_USER_ID, TEST_PACKAGE, false);
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
+
+ ExecutionStats stats =
+ mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ assertEquals(0, stats.countInWindow);
+
+ for (int i = 0; i < 10; ++i) {
+ mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats);
+ assertEquals(i + 1, stats.countInWindow);
+ }
+ }
+
+ /**
+ * Tests that QuotaChangeListeners are notified when a UPTC reaches its count quota.
+ */
+ @Test
+ public void testTracking_OutOfQuota() {
+ spyOn(mQuotaChangeListener);
+
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
+ logEvents(9);
+
+ mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+
+ // Wait for some extra time to allow for processing.
+ verify(mQuotaChangeListener, timeout(3 * SECOND_IN_MILLIS).times(1))
+ .onQuotaStateChanged(eq(TEST_USER_ID), eq(TEST_PACKAGE), eq(TEST_TAG));
+ assertFalse(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+ }
+
+ /**
+ * Tests that QuotaChangeListeners are not incorrectly notified after a UPTC event is logged
+ * quota times.
+ */
+ @Test
+ public void testTracking_InQuota() {
+ spyOn(mQuotaChangeListener);
+
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 5, MINUTE_IN_MILLIS);
+
+ // Log an event once per minute. This is well below the quota, so listeners should not be
+ // notified.
+ for (int i = 0; i < 10; i++) {
+ advanceElapsedClock(MINUTE_IN_MILLIS);
+ mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ }
+
+ // Wait for some extra time to allow for processing.
+ verify(mQuotaChangeListener, timeout(3 * SECOND_IN_MILLIS).times(0))
+ .onQuotaStateChanged(eq(TEST_USER_ID), eq(TEST_PACKAGE), eq(TEST_TAG));
+ assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+ }
+}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 1232340..ace15eb 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -26,6 +26,7 @@
"services.core",
"services.devicepolicy",
"services.net",
+ "services.people",
"services.usage",
"guava",
"androidx.test.core",
diff --git a/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java
index e9c5ce7..d5483ff 100644
--- a/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java
@@ -19,8 +19,11 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.res.Resources;
import android.location.Country;
import android.location.CountryListener;
import android.location.ICountryListener;
@@ -31,6 +34,10 @@
import androidx.test.core.app.ApplicationProvider;
+import com.android.internal.R;
+import com.android.server.location.ComprehensiveCountryDetector;
+import com.android.server.location.CustomCountryDetectorTestClass;
+
import com.google.common.truth.Expect;
import org.junit.Before;
@@ -38,12 +45,18 @@
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.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class CountryDetectorServiceTest {
+ private static final String VALID_CUSTOM_TEST_CLASS =
+ "com.android.server.location.CustomCountryDetectorTestClass";
+ private static final String INVALID_CUSTOM_TEST_CLASS =
+ "com.android.server.location.MissingCountryDetectorTestClass";
+
private static class CountryListenerTester extends ICountryListener.Stub {
private Country mCountry;
@@ -83,12 +96,11 @@
}
}
- @Rule
- public final Expect expect = Expect.create();
- @Spy
- private Context mContext = ApplicationProvider.getApplicationContext();
- @Spy
- private Handler mHandler = new Handler(Looper.myLooper());
+ @Rule public final Expect expect = Expect.create();
+ @Spy private Context mContext = ApplicationProvider.getApplicationContext();
+ @Spy private Handler mHandler = new Handler(Looper.myLooper());
+ @Mock private Resources mResources;
+
private CountryDetectorServiceTester mCountryDetectorService;
@BeforeClass
@@ -108,10 +120,12 @@
message.getCallback().run();
return true;
}).when(mHandler).sendMessageAtTime(any(Message.class), anyLong());
+
+ doReturn(mResources).when(mContext).getResources();
}
@Test
- public void countryListener_add_successful() throws RemoteException {
+ public void addCountryListener_validListener_listenerAdded() throws RemoteException {
CountryListenerTester countryListener = new CountryListenerTester();
mCountryDetectorService.systemRunning();
@@ -122,7 +136,7 @@
}
@Test
- public void countryListener_remove_successful() throws RemoteException {
+ public void removeCountryListener_validListener_listenerRemoved() throws RemoteException {
CountryListenerTester countryListener = new CountryListenerTester();
mCountryDetectorService.systemRunning();
@@ -133,8 +147,31 @@
expect.that(mCountryDetectorService.isListenerSet()).isFalse();
}
+ @Test(expected = RemoteException.class)
+ public void addCountryListener_serviceNotReady_throwsException() throws RemoteException {
+ CountryListenerTester countryListener = new CountryListenerTester();
+
+ expect.that(mCountryDetectorService.isSystemReady()).isFalse();
+ mCountryDetectorService.addCountryListener(countryListener);
+ }
+
+ @Test(expected = RemoteException.class)
+ public void removeCountryListener_serviceNotReady_throwsException() throws RemoteException {
+ CountryListenerTester countryListener = new CountryListenerTester();
+
+ expect.that(mCountryDetectorService.isSystemReady()).isFalse();
+ mCountryDetectorService.removeCountryListener(countryListener);
+ }
+
@Test
- public void countryListener_notify_successful() throws RemoteException {
+ public void detectCountry_serviceNotReady_returnNull() {
+ expect.that(mCountryDetectorService.isSystemReady()).isFalse();
+
+ expect.that(mCountryDetectorService.detectCountry()).isNull();
+ }
+
+ @Test
+ public void notifyReceivers_twoListenersRegistered_bothNotified() throws RemoteException {
CountryListenerTester countryListenerA = new CountryListenerTester();
CountryListenerTester countryListenerB = new CountryListenerTester();
Country country = new Country("US", Country.COUNTRY_SOURCE_NETWORK);
@@ -151,4 +188,26 @@
expect.that(countryListenerA.getCountry().equalsIgnoreSource(country)).isTrue();
expect.that(countryListenerB.getCountry().equalsIgnoreSource(country)).isTrue();
}
+
+ @Test
+ public void initialize_deviceWithCustomDetector_useCustomDetectorClass() {
+ when(mResources.getString(R.string.config_customCountryDetector))
+ .thenReturn(VALID_CUSTOM_TEST_CLASS);
+
+ mCountryDetectorService.initialize();
+
+ expect.that(mCountryDetectorService.getCountryDetector())
+ .isInstanceOf(CustomCountryDetectorTestClass.class);
+ }
+
+ @Test
+ public void initialize_deviceWithInvalidCustomDetector_useDefaultDetector() {
+ when(mResources.getString(R.string.config_customCountryDetector))
+ .thenReturn(INVALID_CUSTOM_TEST_CLASS);
+
+ mCountryDetectorService.initialize();
+
+ expect.that(mCountryDetectorService.getCountryDetector())
+ .isInstanceOf(ComprehensiveCountryDetector.class);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index ee94dd6..a16e14f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -5525,6 +5525,36 @@
assertTrue(dpm.isPackageAllowedToAccessCalendar(testPackage));
}
+ public void testSetProtectedPackages_asDO() throws Exception {
+ final List<String> testPackages = new ArrayList<>();
+ testPackages.add("package_1");
+ testPackages.add("package_2");
+
+ mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
+ setDeviceOwner();
+
+ dpm.setProtectedPackages(admin1, testPackages);
+
+ verify(getServices().packageManagerInternal).setDeviceOwnerProtectedPackages(testPackages);
+
+ assertEquals(testPackages, dpm.getProtectedPackages(admin1));
+ }
+
+ public void testSetProtectedPackages_failingAsPO() throws Exception {
+ final List<String> testPackages = new ArrayList<>();
+ testPackages.add("package_1");
+ testPackages.add("package_2");
+
+ mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
+ setAsProfileOwner(admin1);
+
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.setProtectedPackages(admin1, testPackages));
+
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.getProtectedPackages(admin1));
+ }
+
private void configureProfileOwnerOfOrgOwnedDevice(ComponentName who, int userId) {
when(getServices().userManager.getProfileParent(eq(UserHandle.of(userId))))
.thenReturn(UserHandle.SYSTEM);
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java
new file mode 100644
index 0000000..94f68a5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.integrity.parser;
+
+import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
+import static com.android.server.integrity.parser.BinaryFileOperations.getBooleanValue;
+import static com.android.server.integrity.parser.BinaryFileOperations.getIntValue;
+import static com.android.server.integrity.parser.BinaryFileOperations.getStringValue;
+import static com.android.server.integrity.utils.TestUtils.getBits;
+import static com.android.server.integrity.utils.TestUtils.getBytes;
+import static com.android.server.integrity.utils.TestUtils.getValueBits;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.server.integrity.IntegrityUtils;
+import com.android.server.integrity.model.BitInputStream;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+@RunWith(JUnit4.class)
+public class BinaryFileOperationsTest {
+
+ private static final String IS_NOT_HASHED = "0";
+ private static final String IS_HASHED = "1";
+ private static final String PACKAGE_NAME = "com.test.app";
+ private static final String APP_CERTIFICATE = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+
+ @Test
+ public void testGetStringValue() throws IOException {
+ byte[] stringBytes =
+ getBytes(
+ IS_NOT_HASHED
+ + getBits(PACKAGE_NAME.length(), VALUE_SIZE_BITS)
+ + getValueBits(PACKAGE_NAME));
+ ByteBuffer rule = ByteBuffer.allocate(stringBytes.length);
+ rule.put(stringBytes);
+ BitInputStream inputStream = new BitInputStream(rule.array());
+
+ String resultString = getStringValue(inputStream);
+
+ assertThat(resultString).isEqualTo(PACKAGE_NAME);
+ }
+
+ @Test
+ public void testGetHashedStringValue() throws IOException {
+ byte[] ruleBytes =
+ getBytes(
+ IS_HASHED
+ + getBits(APP_CERTIFICATE.length(), VALUE_SIZE_BITS)
+ + getValueBits(APP_CERTIFICATE));
+ ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length);
+ rule.put(ruleBytes);
+ BitInputStream inputStream = new BitInputStream(rule.array());
+
+ String resultString = getStringValue(inputStream);
+
+ assertThat(resultString)
+ .isEqualTo(IntegrityUtils.getHexDigest(
+ APP_CERTIFICATE.getBytes(StandardCharsets.UTF_8)));
+ }
+
+ @Test
+ public void testGetStringValue_withSizeAndHashingInfo() throws IOException {
+ byte[] ruleBytes = getBytes(getValueBits(PACKAGE_NAME));
+ ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length);
+ rule.put(ruleBytes);
+ BitInputStream inputStream = new BitInputStream(rule.array());
+
+ String resultString = getStringValue(inputStream,
+ PACKAGE_NAME.length(), /* isHashedValue= */false);
+
+ assertThat(resultString).isEqualTo(PACKAGE_NAME);
+ }
+
+ @Test
+ public void testGetIntValue() throws IOException {
+ int randomValue = 15;
+ byte[] ruleBytes = getBytes(getBits(randomValue, /* numOfBits= */ 32));
+ ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length);
+ rule.put(ruleBytes);
+ BitInputStream inputStream = new BitInputStream(rule.array());
+
+ assertThat(getIntValue(inputStream)).isEqualTo(randomValue);
+ }
+
+ @Test
+ public void testGetBooleanValue_true() throws IOException {
+ String booleanValue = "1";
+ byte[] ruleBytes = getBytes(booleanValue);
+ ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length);
+ rule.put(ruleBytes);
+ BitInputStream inputStream = new BitInputStream(rule.array());
+
+ assertThat(getBooleanValue(inputStream)).isEqualTo(true);
+ }
+
+ @Test
+ public void testGetBooleanValue_false() throws IOException {
+ String booleanValue = "0";
+ byte[] ruleBytes = getBytes(booleanValue);
+ ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length);
+ rule.put(ruleBytes);
+ BitInputStream inputStream = new BitInputStream(rule.array());
+
+ assertThat(getBooleanValue(inputStream)).isEqualTo(false);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
index 97aa310..eb6698b 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
@@ -27,9 +27,9 @@
import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS;
import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS;
import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
-import static com.android.server.integrity.serializer.RuleBinarySerializer.END_INDEXING_KEY;
-import static com.android.server.integrity.serializer.RuleBinarySerializer.INDEXING_BLOCK_SIZE;
-import static com.android.server.integrity.serializer.RuleBinarySerializer.START_INDEXING_KEY;
+import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
+import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE;
+import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
import static com.android.server.integrity.utils.TestUtils.getBits;
import static com.android.server.integrity.utils.TestUtils.getBytes;
import static com.android.server.integrity.utils.TestUtils.getValueBits;
diff --git a/services/tests/servicestests/src/com/android/server/location/CustomCountryDetectorTestClass.java b/services/tests/servicestests/src/com/android/server/location/CustomCountryDetectorTestClass.java
new file mode 100644
index 0000000..e159012
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/location/CustomCountryDetectorTestClass.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import android.content.Context;
+import android.location.Country;
+
+public class CustomCountryDetectorTestClass extends CountryDetectorBase {
+ public CustomCountryDetectorTestClass(Context ctx) {
+ super(ctx);
+ }
+
+ @Override
+ public Country detectCountry() {
+ return null;
+ }
+
+ @Override
+ public void stop() {
+
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java
new file mode 100644
index 0000000..54c552b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * atest FrameworksServicesTests:RebootEscrowDataTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class RebootEscrowDataTest {
+ private static byte[] getTestSp() {
+ byte[] testSp = new byte[10];
+ for (int i = 0; i < testSp.length; i++) {
+ testSp[i] = (byte) i;
+ }
+ return testSp;
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void fromEntries_failsOnNull() throws Exception {
+ RebootEscrowData.fromSyntheticPassword((byte) 2, null);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void fromEncryptedData_failsOnNullData() throws Exception {
+ byte[] testSp = getTestSp();
+ RebootEscrowData expected = RebootEscrowData.fromSyntheticPassword((byte) 2, testSp);
+ SecretKeySpec key = RebootEscrowData.fromKeyBytes(expected.getKey());
+ RebootEscrowData.fromEncryptedData(key, null);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void fromEncryptedData_failsOnNullKey() throws Exception {
+ byte[] testSp = getTestSp();
+ RebootEscrowData expected = RebootEscrowData.fromSyntheticPassword((byte) 2, testSp);
+ RebootEscrowData.fromEncryptedData(null, expected.getBlob());
+ }
+
+ @Test
+ public void fromEntries_loopback_success() throws Exception {
+ byte[] testSp = getTestSp();
+ RebootEscrowData expected = RebootEscrowData.fromSyntheticPassword((byte) 2, testSp);
+
+ SecretKeySpec key = RebootEscrowData.fromKeyBytes(expected.getKey());
+ RebootEscrowData actual = RebootEscrowData.fromEncryptedData(key, expected.getBlob());
+
+ assertThat(actual.getSpVersion(), is(expected.getSpVersion()));
+ assertThat(actual.getIv(), is(expected.getIv()));
+ assertThat(actual.getKey(), is(expected.getKey()));
+ assertThat(actual.getBlob(), is(expected.getBlob()));
+ assertThat(actual.getSyntheticPassword(), is(expected.getSyntheticPassword()));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
new file mode 100644
index 0000000..78a5a0b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 static android.content.pm.UserInfo.FLAG_FULL;
+import static android.content.pm.UserInfo.FLAG_PRIMARY;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.hardware.rebootescrow.IRebootEscrow;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.widget.RebootEscrowListener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.ArrayList;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class RebootEscrowManagerTests {
+ protected static final int PRIMARY_USER_ID = 0;
+ protected static final int NONSECURE_USER_ID = 10;
+ private static final byte FAKE_SP_VERSION = 1;
+ private static final byte[] FAKE_AUTH_TOKEN = new byte[] {
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ };
+
+ private Context mContext;
+ private UserManager mUserManager;
+ private RebootEscrowManager.Callbacks mCallbacks;
+ private IRebootEscrow mRebootEscrow;
+
+ LockSettingsStorageTestable mStorage;
+
+ private RebootEscrowManager mService;
+
+ static class MockInjector extends RebootEscrowManager.Injector {
+ private final IRebootEscrow mRebootEscrow;
+ private final UserManager mUserManager;
+
+ MockInjector(Context context, UserManager userManager, IRebootEscrow rebootEscrow) {
+ super(context);
+ mRebootEscrow = rebootEscrow;
+ mUserManager = userManager;
+ }
+
+ @Override
+ public UserManager getUserManager() {
+ return mUserManager;
+ }
+
+ @Override
+ public IRebootEscrow getRebootEscrow() {
+ return mRebootEscrow;
+ }
+ }
+
+ @Before
+ public void setUp_baseServices() throws Exception {
+ mContext = mock(Context.class);
+ mUserManager = mock(UserManager.class);
+ mCallbacks = mock(RebootEscrowManager.Callbacks.class);
+ mRebootEscrow = mock(IRebootEscrow.class);
+
+ mStorage = new LockSettingsStorageTestable(mContext,
+ new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings"));
+
+ ArrayList<UserInfo> users = new ArrayList<>();
+ users.add(new UserInfo(PRIMARY_USER_ID, "primary", FLAG_PRIMARY));
+ users.add(new UserInfo(NONSECURE_USER_ID, "non-secure", FLAG_FULL));
+ when(mUserManager.getUsers()).thenReturn(users);
+ when(mCallbacks.isUserSecure(PRIMARY_USER_ID)).thenReturn(true);
+ when(mCallbacks.isUserSecure(NONSECURE_USER_ID)).thenReturn(false);
+ mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager, mRebootEscrow),
+ mCallbacks, mStorage);
+ }
+
+ @Test
+ public void prepareRebootEscrow_Success() throws Exception {
+ RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+ mService.setRebootEscrowListener(mockListener);
+ mService.prepareRebootEscrow();
+
+ clearInvocations(mRebootEscrow);
+ mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ verify(mockListener).onPreparedForReboot(eq(true));
+ verify(mRebootEscrow, never()).storeKey(any());
+ }
+
+ @Test
+ public void prepareRebootEscrow_ClearCredentials_Success() throws Exception {
+ RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+ mService.setRebootEscrowListener(mockListener);
+ mService.prepareRebootEscrow();
+ mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ verify(mockListener).onPreparedForReboot(eq(true));
+
+ clearInvocations(mRebootEscrow);
+ mService.clearRebootEscrow();
+ verify(mockListener).onPreparedForReboot(eq(false));
+ verify(mRebootEscrow).storeKey(eq(new byte[32]));
+ }
+
+ @Test
+ public void armService_Success() throws Exception {
+ RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+ mService.setRebootEscrowListener(mockListener);
+ mService.prepareRebootEscrow();
+
+ clearInvocations(mRebootEscrow);
+ mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ verify(mockListener).onPreparedForReboot(eq(true));
+ verify(mRebootEscrow, never()).storeKey(any());
+
+ assertTrue(mService.armRebootEscrowIfNeeded());
+ verify(mRebootEscrow).storeKey(any());
+ }
+
+ @Test
+ public void armService_NoInitialization_Failure() throws Exception {
+ assertFalse(mService.armRebootEscrowIfNeeded());
+ verifyNoMoreInteractions(mRebootEscrow);
+ }
+
+ @Test
+ public void armService_RebootEscrowServiceException_Failure() throws Exception {
+ doThrow(RemoteException.class).when(mRebootEscrow).storeKey(any());
+ assertFalse(mService.armRebootEscrowIfNeeded());
+ verifyNoMoreInteractions(mRebootEscrow);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
new file mode 100644
index 0000000..d3166b9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.people;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.AppTarget;
+import android.app.prediction.IPredictionCallback;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.Binder;
+import android.os.RemoteException;
+
+import com.android.server.LocalServices;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+@RunWith(JUnit4.class)
+public final class PeopleServiceTest {
+
+ private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
+ private static final int APP_PREDICTION_TARGET_COUNT = 4;
+ private static final String TEST_PACKAGE_NAME = "com.example";
+
+ private PeopleServiceInternal mServiceInternal;
+ private PeopleService.LocalService mLocalService;
+ private AppPredictionSessionId mSessionId;
+ private AppPredictionContext mPredictionContext;
+
+ @Mock private Context mContext;
+ @Mock private IPredictionCallback mCallback;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mContext.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(mCallback.asBinder()).thenReturn(new Binder());
+
+ PeopleService service = new PeopleService(mContext);
+ service.onStart();
+
+ mServiceInternal = LocalServices.getService(PeopleServiceInternal.class);
+ mLocalService = (PeopleService.LocalService) mServiceInternal;
+
+ mSessionId = new AppPredictionSessionId("abc");
+ mPredictionContext = new AppPredictionContext.Builder(mContext)
+ .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
+ .setPredictedTargetCount(APP_PREDICTION_TARGET_COUNT)
+ .build();
+ }
+
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(PeopleServiceInternal.class);
+ }
+
+ @Test
+ public void testRegisterCallbacks() throws RemoteException {
+ mServiceInternal.onCreatePredictionSession(mPredictionContext, mSessionId);
+
+ SessionInfo sessionInfo = mLocalService.getSessionInfo(mSessionId);
+
+ mServiceInternal.registerPredictionUpdates(mSessionId, mCallback);
+
+ Consumer<List<AppTarget>> updatePredictionMethod =
+ sessionInfo.getPredictor().getUpdatePredictionsMethod();
+ updatePredictionMethod.accept(new ArrayList<>());
+ updatePredictionMethod.accept(new ArrayList<>());
+
+ verify(mCallback, times(2)).onResult(any(ParceledListSlice.class));
+
+ mServiceInternal.unregisterPredictionUpdates(mSessionId, mCallback);
+
+ updatePredictionMethod.accept(new ArrayList<>());
+
+ // After the un-registration, the callback should no longer be called.
+ verify(mCallback, times(2)).onResult(any(ParceledListSlice.class));
+
+ mServiceInternal.onDestroyPredictionSession(mSessionId);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
index 1e760cc..ac27a08 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
@@ -139,18 +139,18 @@
assertEquals("A Name", mUserManagerService.getUserInfo(TEST_ID).name);
}
- /** Test UMS.getUserTypeForUser(). */
+ /** Test UMS.isUserOfType(). */
@Test
- public void testGetUserTypeForUser() throws Exception {
- final String typeSys = mUserManagerService.getUserTypeForUser(UserHandle.USER_SYSTEM);
- assertTrue("System user was of invalid type " + typeSys,
- typeSys.equals(USER_TYPE_SYSTEM_HEADLESS) || typeSys.equals(USER_TYPE_FULL_SYSTEM));
+ public void testIsUserOfType() throws Exception {
+ assertTrue("System user was of invalid type",
+ mUserManagerService.isUserOfType(UserHandle.USER_SYSTEM, USER_TYPE_SYSTEM_HEADLESS)
+ || mUserManagerService.isUserOfType(UserHandle.USER_SYSTEM, USER_TYPE_FULL_SYSTEM));
final int testId = 100;
final String typeName = "A type";
UserInfo userInfo = createUser(testId, 0, typeName);
mUserManagerService.putUserInfo(userInfo);
- assertEquals(typeName, mUserManagerService.getUserTypeForUser(testId));
+ assertTrue(mUserManagerService.isUserOfType(testId, typeName));
}
/** Tests upgradeIfNecessaryLP (but without locking) for upgrading from version 8 to 9+. */
@@ -169,22 +169,22 @@
mUserManagerService.upgradeIfNecessaryLP(null, versionToTest - 1);
- assertEquals(USER_TYPE_PROFILE_MANAGED, mUserManagerService.getUserTypeForUser(100));
+ assertTrue(mUserManagerService.isUserOfType(100, USER_TYPE_PROFILE_MANAGED));
assertTrue((mUserManagerService.getUserInfo(100).flags & FLAG_PROFILE) != 0);
- assertEquals(USER_TYPE_FULL_GUEST, mUserManagerService.getUserTypeForUser(101));
+ assertTrue(mUserManagerService.isUserOfType(101, USER_TYPE_FULL_GUEST));
- assertEquals(USER_TYPE_FULL_RESTRICTED, mUserManagerService.getUserTypeForUser(102));
+ assertTrue(mUserManagerService.isUserOfType(102, USER_TYPE_FULL_RESTRICTED));
assertTrue((mUserManagerService.getUserInfo(102).flags & FLAG_PROFILE) == 0);
- assertEquals(USER_TYPE_FULL_SECONDARY, mUserManagerService.getUserTypeForUser(103));
+ assertTrue(mUserManagerService.isUserOfType(103, USER_TYPE_FULL_SECONDARY));
assertTrue((mUserManagerService.getUserInfo(103).flags & FLAG_PROFILE) == 0);
- assertEquals(USER_TYPE_SYSTEM_HEADLESS, mUserManagerService.getUserTypeForUser(104));
+ assertTrue(mUserManagerService.isUserOfType(104, USER_TYPE_SYSTEM_HEADLESS));
- assertEquals(USER_TYPE_FULL_SYSTEM, mUserManagerService.getUserTypeForUser(105));
+ assertTrue(mUserManagerService.isUserOfType(105, USER_TYPE_FULL_SYSTEM));
- assertEquals(USER_TYPE_FULL_DEMO, mUserManagerService.getUserTypeForUser(106));
+ assertTrue(mUserManagerService.isUserOfType(106, USER_TYPE_FULL_DEMO));
}
/** Creates a UserInfo with the given flags and userType. */
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index a7275fc7..77376f0 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -341,8 +341,8 @@
assertThat(mUserManager.getUserBadgeNoBackgroundResId(userId))
.isEqualTo(userTypeDetails.getBadgeNoBackground());
assertThat(mUserManager.isProfile(userId)).isEqualTo(userTypeDetails.isProfile());
- assertThat(mUserManager.getUserTypeForUser(asHandle(userId)))
- .isEqualTo(userTypeDetails.getName());
+ assertThat(mUserManager.isUserOfType(asHandle(userId), userTypeDetails.getName()))
+ .isTrue();
final int badgeIndex = userInfo.profileBadge;
assertThat(mUserManager.getUserBadgeColor(userId)).isEqualTo(
diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java
index 1f312bf..d5cdbeb 100644
--- a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java
@@ -20,16 +20,19 @@
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.IntentSender;
import android.os.Handler;
import android.os.IPowerManager;
import android.os.IRecoverySystemProgressListener;
@@ -40,6 +43,8 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.widget.LockSettingsInternal;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -58,6 +63,7 @@
private Context mContext;
private IPowerManager mIPowerManager;
private FileWriter mUncryptUpdateFileWriter;
+ private LockSettingsInternal mLockSettingsInternal;
@Before
public void setup() {
@@ -65,6 +71,9 @@
mSystemProperties = new RecoverySystemServiceTestable.FakeSystemProperties();
mUncryptSocket = mock(RecoverySystemService.UncryptSocket.class);
mUncryptUpdateFileWriter = mock(FileWriter.class);
+ mLockSettingsInternal = mock(LockSettingsInternal.class);
+
+ when(mLockSettingsInternal.armRebootEscrow()).thenReturn(true);
Looper looper = InstrumentationRegistry.getContext().getMainLooper();
mIPowerManager = mock(IPowerManager.class);
@@ -72,7 +81,7 @@
new Handler(looper));
mRecoverySystemService = new RecoverySystemServiceTestable(mContext, mSystemProperties,
- powerManager, mUncryptUpdateFileWriter, mUncryptSocket);
+ powerManager, mUncryptUpdateFileWriter, mUncryptSocket, mLockSettingsInternal);
}
@Test
@@ -194,4 +203,100 @@
verify(mUncryptSocket).sendAck();
verify(mUncryptSocket).close();
}
+
+ @Test(expected = SecurityException.class)
+ public void requestLskf_protected() {
+ doThrow(SecurityException.class).when(mContext).enforceCallingOrSelfPermission(
+ eq(android.Manifest.permission.RECOVERY), any());
+ mRecoverySystemService.requestLskf("test", null);
+ }
+
+
+ @Test
+ public void requestLskf_nullToken_failure() {
+ assertThat(mRecoverySystemService.requestLskf(null, null), is(false));
+ }
+
+ @Test
+ public void requestLskf_success() throws Exception {
+ IntentSender intentSender = mock(IntentSender.class);
+ assertThat(mRecoverySystemService.requestLskf("test", intentSender), is(true));
+ mRecoverySystemService.onPreparedForReboot(true);
+ verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any());
+ }
+
+ @Test
+ public void requestLskf_subsequentRequestClearsPrepared() throws Exception {
+ IntentSender intentSender = mock(IntentSender.class);
+ assertThat(mRecoverySystemService.requestLskf("test", intentSender), is(true));
+ mRecoverySystemService.onPreparedForReboot(true);
+ verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any());
+
+ assertThat(mRecoverySystemService.requestLskf("test2", null), is(true));
+ assertThat(mRecoverySystemService.rebootWithLskf("test", null), is(false));
+ assertThat(mRecoverySystemService.rebootWithLskf("test2", "foobar"), is(false));
+
+ mRecoverySystemService.onPreparedForReboot(true);
+ assertThat(mRecoverySystemService.rebootWithLskf("test2", "foobar"), is(true));
+ verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any());
+ verify(mIPowerManager).reboot(anyBoolean(), eq("foobar"), anyBoolean());
+ }
+
+
+ @Test
+ public void requestLskf_requestedButNotPrepared() throws Exception {
+ IntentSender intentSender = mock(IntentSender.class);
+ assertThat(mRecoverySystemService.requestLskf("test", intentSender), is(true));
+ verify(intentSender, never()).sendIntent(any(), anyInt(), any(), any(), any());
+ }
+
+ @Test(expected = SecurityException.class)
+ public void clearLskf_protected() {
+ doThrow(SecurityException.class).when(mContext).enforceCallingOrSelfPermission(
+ eq(android.Manifest.permission.RECOVERY), any());
+ mRecoverySystemService.clearLskf();
+ }
+
+ @Test
+ public void clearLskf_requestedThenCleared() throws Exception {
+ IntentSender intentSender = mock(IntentSender.class);
+ assertThat(mRecoverySystemService.requestLskf("test", intentSender), is(true));
+ mRecoverySystemService.onPreparedForReboot(true);
+ verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any());
+
+ assertThat(mRecoverySystemService.clearLskf(), is(true));
+ verify(mLockSettingsInternal).clearRebootEscrow();
+ }
+
+ @Test
+ public void startup_setRebootEscrowListener() throws Exception {
+ mRecoverySystemService.onSystemServicesReady();
+ verify(mLockSettingsInternal).setRebootEscrowListener(any());
+ }
+
+ @Test(expected = SecurityException.class)
+ public void rebootWithLskf_protected() {
+ doThrow(SecurityException.class).when(mContext).enforceCallingOrSelfPermission(
+ eq(android.Manifest.permission.RECOVERY), any());
+ mRecoverySystemService.rebootWithLskf("test1", null);
+ }
+
+ @Test
+ public void rebootWithLskf_Success() throws Exception {
+ assertThat(mRecoverySystemService.requestLskf("test", null), is(true));
+ mRecoverySystemService.onPreparedForReboot(true);
+ assertThat(mRecoverySystemService.rebootWithLskf("test", "ab-update"), is(true));
+ verify(mIPowerManager).reboot(anyBoolean(), eq("ab-update"), anyBoolean());
+ }
+
+ @Test
+ public void rebootWithLskf_withoutPrepare_Failure() throws Exception {
+ assertThat(mRecoverySystemService.rebootWithLskf("test1", null), is(false));
+ }
+
+ @Test
+ public void rebootWithLskf_withNullUpdateToken_Failure() throws Exception {
+ assertThat(mRecoverySystemService.rebootWithLskf(null, null), is(false));
+ verifyNoMoreInteractions(mIPowerManager);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java
index a986b71..131e4f3 100644
--- a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java
@@ -19,6 +19,8 @@
import android.content.Context;
import android.os.PowerManager;
+import com.android.internal.widget.LockSettingsInternal;
+
import java.io.FileWriter;
public class RecoverySystemServiceTestable extends RecoverySystemService {
@@ -27,15 +29,17 @@
private final PowerManager mPowerManager;
private final FileWriter mUncryptPackageFileWriter;
private final UncryptSocket mUncryptSocket;
+ private final LockSettingsInternal mLockSettingsInternal;
MockInjector(Context context, FakeSystemProperties systemProperties,
PowerManager powerManager, FileWriter uncryptPackageFileWriter,
- UncryptSocket uncryptSocket) {
+ UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal) {
super(context);
mSystemProperties = systemProperties;
mPowerManager = powerManager;
mUncryptPackageFileWriter = uncryptPackageFileWriter;
mUncryptSocket = uncryptSocket;
+ mLockSettingsInternal = lockSettingsInternal;
}
@Override
@@ -76,13 +80,18 @@
@Override
public void threadSleep(long millis) {
}
+
+ @Override
+ public LockSettingsInternal getLockSettingsService() {
+ return mLockSettingsInternal;
+ }
}
RecoverySystemServiceTestable(Context context, FakeSystemProperties systemProperties,
PowerManager powerManager, FileWriter uncryptPackageFileWriter,
- UncryptSocket uncryptSocket) {
+ UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal) {
super(new MockInjector(context, systemProperties, powerManager, uncryptPackageFileWriter,
- uncryptSocket));
+ uncryptSocket, lockSettingsInternal));
}
public static class FakeSystemProperties {
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index 82f32f8..f8915c0 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -60,8 +60,6 @@
import android.os.IHwInterface;
import android.os.RemoteException;
-import androidx.test.runner.AndroidJUnit4;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -144,7 +142,11 @@
properties.maxSoundModels = 456;
properties.maxKeyPhrases = 567;
properties.maxUsers = 678;
- properties.recognitionModes = 789;
+ properties.recognitionModes =
+ android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER
+ | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION
+ | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION
+ | android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
properties.captureTransition = true;
properties.maxBufferMs = 321;
properties.concurrentCapture = supportConcurrentCapture;
@@ -162,7 +164,10 @@
assertEquals(456, properties.maxSoundModels);
assertEquals(567, properties.maxKeyPhrases);
assertEquals(678, properties.maxUsers);
- assertEquals(789, properties.recognitionModes);
+ assertEquals(RecognitionMode.GENERIC_TRIGGER
+ | RecognitionMode.USER_AUTHENTICATION
+ | RecognitionMode.USER_IDENTIFICATION
+ | RecognitionMode.VOICE_TRIGGER, properties.recognitionModes);
assertTrue(properties.captureTransition);
assertEquals(321, properties.maxBufferMs);
assertEquals(supportConcurrentCapture, properties.concurrentCapture);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 95617b1..172df99 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -492,23 +492,13 @@
return sbn;
}
-
private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
String groupKey, boolean isSummary) {
- return generateNotificationRecord(channel, id, groupKey, isSummary, false /* isBubble */);
- }
-
- private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
- String groupKey, boolean isSummary, boolean isBubble) {
Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
.setContentTitle("foo")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.setGroup(groupKey)
.setGroupSummary(isSummary);
- if (isBubble) {
- nb.setBubbleMetadata(getBasicBubbleMetadataBuilder().build());
- }
-
StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id,
"tag" + System.currentTimeMillis(), mUid, 0,
nb.build(), new UserHandle(mUid), null, 0);
@@ -521,11 +511,6 @@
private NotificationRecord generateNotificationRecord(NotificationChannel channel,
Notification.TvExtender extender) {
- return generateNotificationRecord(channel, extender, false /* isBubble */);
- }
-
- private NotificationRecord generateNotificationRecord(NotificationChannel channel,
- Notification.TvExtender extender, boolean isBubble) {
if (channel == null) {
channel = mTestNotificationChannel;
}
@@ -535,9 +520,6 @@
if (extender != null) {
nb.extend(extender);
}
- if (isBubble) {
- nb.setBubbleMetadata(getBasicBubbleMetadataBuilder().build());
- }
StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
nb.build(), new UserHandle(mUid), null, 0);
return new NotificationRecord(mContext, sbn, channel);
@@ -555,6 +537,26 @@
return new NotificationRecord(mContext, sbn, channel);
}
+ private NotificationRecord generateMessageBubbleNotifRecord(NotificationChannel channel,
+ String tag) {
+ return generateMessageBubbleNotifRecord(true, channel, 1, tag, null, false);
+ }
+
+ private NotificationRecord generateMessageBubbleNotifRecord(boolean addMetadata,
+ NotificationChannel channel, int id, String tag, String groupKey, boolean isSummary) {
+ if (channel == null) {
+ channel = mTestNotificationChannel;
+ }
+ if (tag == null) {
+ tag = "tag";
+ }
+ Notification.Builder nb = getMessageStyleNotifBuilder(addMetadata, groupKey, isSummary);
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id,
+ tag, mUid, 0,
+ nb.build(), new UserHandle(mUid), null, 0);
+ return new NotificationRecord(mContext, sbn, channel);
+ }
+
private Map<String, Answer> getSignalExtractorSideEffects() {
Map<String, Answer> answers = new ArrayMap<>();
@@ -610,23 +612,57 @@
false);
}
- private Notification.BubbleMetadata.Builder getBasicBubbleMetadataBuilder() {
+ private Notification.BubbleMetadata.Builder getBubbleMetadataBuilder() {
PendingIntent pi = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
return new Notification.BubbleMetadata.Builder()
.setIntent(pi)
.setIcon(Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon));
}
+ private Notification.Builder getMessageStyleNotifBuilder(boolean addBubbleMetadata,
+ String groupKey, boolean isSummary) {
+ // Give it a person
+ Person person = new Person.Builder()
+ .setName("bubblebot")
+ .build();
+ // It needs remote input to be bubble-able
+ RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
+ PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
+ Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
+ inputIntent).addRemoteInput(remoteInput)
+ .build();
+ // Make it messaging style
+ Notification.Builder nb = new Notification.Builder(mContext,
+ mTestNotificationChannel.getId())
+ .setContentTitle("foo")
+ .setStyle(new Notification.MessagingStyle(person)
+ .setConversationTitle("Bubble Chat")
+ .addMessage("Hello?",
+ SystemClock.currentThreadTimeMillis() - 300000, person)
+ .addMessage("Is it me you're looking for?",
+ SystemClock.currentThreadTimeMillis(), person)
+ )
+ .setActions(replyAction)
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setGroupSummary(isSummary);
+ if (groupKey != null) {
+ nb.setGroup(groupKey);
+ }
+ if (addBubbleMetadata) {
+ nb.setBubbleMetadata(getBubbleMetadataBuilder().build());
+ }
+ return nb;
+ }
+
private NotificationRecord addGroupWithBubblesAndValidateAdded(boolean summaryAutoCancel)
throws RemoteException {
- // Notification that has bubble metadata
- NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel, 1,
- "BUBBLE_GROUP", false /* isSummary */, true /* isBubble */);
+ String groupKey = "BUBBLE_GROUP";
- // Make the package foreground so that we're allowed to be a bubble
- when(mActivityManager.getPackageImportance(nrBubble.sbn.getPackageName())).thenReturn(
- IMPORTANCE_FOREGROUND);
+ // Notification that has bubble metadata
+ NotificationRecord nrBubble = generateMessageBubbleNotifRecord(true /* addMetadata */,
+ mTestNotificationChannel, 1 /* id */, "tag", groupKey, false /* isSummary */);
mBinderService.enqueueNotificationWithTag(PKG, PKG, nrBubble.sbn.getTag(),
nrBubble.sbn.getId(), nrBubble.sbn.getNotification(), nrBubble.sbn.getUserId());
@@ -637,9 +673,10 @@
assertEquals(1, notifsAfter.length);
assertTrue((notifsAfter[0].getNotification().flags & FLAG_BUBBLE) != 0);
- // Plain notification without bubble metadata
- NotificationRecord nrPlain = generateNotificationRecord(mTestNotificationChannel, 2,
- "BUBBLE_GROUP", false /* isSummary */, false /* isBubble */);
+ // Notification without bubble metadata
+ NotificationRecord nrPlain = generateMessageBubbleNotifRecord(false /* addMetadata */,
+ mTestNotificationChannel, 2 /* id */, "tag", groupKey, false /* isSummary */);
+
mBinderService.enqueueNotificationWithTag(PKG, PKG, nrPlain.sbn.getTag(),
nrPlain.sbn.getId(), nrPlain.sbn.getNotification(), nrPlain.sbn.getUserId());
waitForIdle();
@@ -648,8 +685,9 @@
assertEquals(2, notifsAfter.length);
// Summary notification for both of those
- NotificationRecord nrSummary = generateNotificationRecord(mTestNotificationChannel, 3,
- "BUBBLE_GROUP", true /* isSummary */, false /* isBubble */);
+ NotificationRecord nrSummary = generateMessageBubbleNotifRecord(false /* addMetadata */,
+ mTestNotificationChannel, 3 /* id */, "tag", groupKey, true /* isSummary */);
+
if (summaryAutoCancel) {
nrSummary.getNotification().flags |= FLAG_AUTO_CANCEL;
}
@@ -4708,13 +4746,8 @@
// Bubbles are allowed!
setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
- // Notif with bubble metadata but not our other misc requirements
- NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, true /* isBubble */);
-
- // Say we're foreground
- when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
- IMPORTANCE_FOREGROUND);
+ NotificationRecord nr =
+ generateMessageBubbleNotifRecord(mTestNotificationChannel, "testFlagBubble");
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
@@ -4732,13 +4765,8 @@
// Bubbles are allowed!
setUpPrefsForBubbles(true /* global */, false /* app */, true /* channel */);
- // Notif with bubble metadata but not our other misc requirements
- NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, true /* isBubble */);
-
- // Say we're foreground
- when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
- IMPORTANCE_FOREGROUND);
+ NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+ "testFlagBubble_noFlag_appNotAllowed");
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
@@ -4752,174 +4780,40 @@
}
@Test
- public void testFlagBubbleNotifs_flag_appForeground() throws RemoteException {
+ public void testFlagBubbleNotifs_noFlag_whenAppForeground() throws RemoteException {
// Bubbles are allowed!
setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
// Notif with bubble metadata but not our other misc requirements
- NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, true /* isBubble */);
+ Notification.Builder nb = new Notification.Builder(mContext,
+ mTestNotificationChannel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setBubbleMetadata(getBubbleMetadataBuilder().build());
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0,
+ nb.build(), new UserHandle(mUid), null, 0);
+ NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
// Say we're foreground
when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
IMPORTANCE_FOREGROUND);
-
- mBinderService.enqueueNotificationWithTag(PKG, PKG,
- nr.sbn.getTag(), nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
- waitForIdle();
-
- // yes allowed, yes foreground, yes bubble
- assertTrue(mService.getNotificationRecord(
- nr.sbn.getKey()).getNotification().isBubbleNotification());
- }
-
- @Test
- public void testFlagBubbleNotifs_noFlag_appNotForeground() throws RemoteException {
- // Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
-
- // Notif with bubble metadata but not our other misc requirements
- NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, true /* isBubble */);
-
- // Make sure we're NOT foreground
- when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
- IMPORTANCE_VISIBLE);
-
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
waitForIdle();
- // yes allowed but NOT foreground, no bubble
+ // if notif isn't configured properly it doesn't get to bubble just because app is
+ // foreground.
assertFalse(mService.getNotificationRecord(
nr.sbn.getKey()).getNotification().isBubbleNotification());
}
@Test
- public void testFlagBubbleNotifs_flag_previousForegroundFlag() throws RemoteException {
- // Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
-
- // Notif with bubble metadata but not our other misc requirements
- NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, true /* isBubble */);
-
- // Send notif when we're foreground
- when(mActivityManager.getPackageImportance(nr1.sbn.getPackageName())).thenReturn(
- IMPORTANCE_FOREGROUND);
- mBinderService.enqueueNotificationWithTag(PKG, PKG, nr1.sbn.getTag(),
- nr1.sbn.getId(), nr1.sbn.getNotification(), nr1.sbn.getUserId());
- waitForIdle();
-
- // yes allowed, yes foreground, yes bubble
- assertTrue(mService.getNotificationRecord(
- nr1.sbn.getKey()).getNotification().isBubbleNotification());
-
- // Send a new update when we're not foreground
- NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, true /* isBubble */);
-
- when(mActivityManager.getPackageImportance(nr2.sbn.getPackageName())).thenReturn(
- IMPORTANCE_VISIBLE);
- mBinderService.enqueueNotificationWithTag(PKG, PKG, nr2.sbn.getTag(),
- nr2.sbn.getId(), nr2.sbn.getNotification(), nr2.sbn.getUserId());
- waitForIdle();
-
- // yes allowed, previously foreground / flagged, yes bubble
- assertTrue(mService.getNotificationRecord(
- nr2.sbn.getKey()).getNotification().isBubbleNotification());
-
- StatusBarNotification[] notifs2 = mBinderService.getActiveNotifications(PKG);
- assertEquals(1, notifs2.length);
- assertEquals(1, mService.getNotificationRecordCount());
- }
-
- @Test
- public void testFlagBubbleNotifs_noFlag_previousForegroundFlag_afterRemoval()
- throws RemoteException {
- // Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
-
- // Notif with bubble metadata but not our other misc requirements
- NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, true /* isBubble */);
-
- // Send notif when we're foreground
- when(mActivityManager.getPackageImportance(nr1.sbn.getPackageName())).thenReturn(
- IMPORTANCE_FOREGROUND);
- mBinderService.enqueueNotificationWithTag(PKG, PKG, nr1.sbn.getTag(),
- nr1.sbn.getId(), nr1.sbn.getNotification(), nr1.sbn.getUserId());
- waitForIdle();
-
- // yes allowed, yes foreground, yes bubble
- assertTrue(mService.getNotificationRecord(
- nr1.sbn.getKey()).getNotification().isBubbleNotification());
-
- // Remove the bubble
- mBinderService.cancelNotificationWithTag(PKG, PKG, nr1.sbn.getTag(), nr1.sbn.getId(),
- nr1.sbn.getUserId());
- waitForIdle();
-
- StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
- assertEquals(0, notifs.length);
- assertEquals(0, mService.getNotificationRecordCount());
-
- // Send a new update when we're not foreground
- NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, true /* isBubble */);
-
- when(mActivityManager.getPackageImportance(nr2.sbn.getPackageName())).thenReturn(
- IMPORTANCE_VISIBLE);
- mBinderService.enqueueNotificationWithTag(PKG, PKG, nr2.sbn.getTag(),
- nr2.sbn.getId(), nr2.sbn.getNotification(), nr2.sbn.getUserId());
- waitForIdle();
-
- // yes allowed, but was removed & no foreground, so no bubble
- assertFalse(mService.getNotificationRecord(
- nr2.sbn.getKey()).getNotification().isBubbleNotification());
-
- StatusBarNotification[] notifs2 = mBinderService.getActiveNotifications(PKG);
- assertEquals(1, notifs2.length);
- assertEquals(1, mService.getNotificationRecordCount());
- }
-
- @Test
public void testFlagBubbleNotifs_flag_messaging() throws RemoteException {
// Bubbles are allowed!
setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
- // Give it bubble metadata
- Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
- // Give it a person
- Person person = new Person.Builder()
- .setName("bubblebot")
- .build();
- // It needs remote input to be bubble-able
- RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
- PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
- Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
- Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
- inputIntent).addRemoteInput(remoteInput)
- .build();
- // Make it messaging style
- Notification.Builder nb = new Notification.Builder(mContext,
- mTestNotificationChannel.getId())
- .setContentTitle("foo")
- .setBubbleMetadata(data)
- .setStyle(new Notification.MessagingStyle(person)
- .setConversationTitle("Bubble Chat")
- .addMessage("Hello?",
- SystemClock.currentThreadTimeMillis() - 300000, person)
- .addMessage("Is it me you're looking for?",
- SystemClock.currentThreadTimeMillis(), person)
- )
- .setActions(replyAction)
- .setSmallIcon(android.R.drawable.sym_def_app_icon);
-
- StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
- "testFlagBubbleNotifs_flag_messaging", mUid, 0,
- nb.build(), new UserHandle(mUid), null, 0);
- NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+ NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+ "testFlagBubbleNotifs_flag_messaging");
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
@@ -4927,7 +4821,7 @@
// yes allowed, yes messaging, yes bubble
assertTrue(mService.getNotificationRecord(
- sbn.getKey()).getNotification().isBubbleNotification());
+ nr.sbn.getKey()).getNotification().isBubbleNotification());
}
@Test
@@ -4936,7 +4830,7 @@
setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
// Give it bubble metadata
- Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+ Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
// Give it a person
Person person = new Person.Builder()
.setName("bubblebot")
@@ -4972,7 +4866,7 @@
setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
// Give it bubble metadata
- Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+ Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
// Give it a person
Person person = new Person.Builder()
.setName("bubblebot")
@@ -5005,7 +4899,7 @@
setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
// Give it bubble metadata
- Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+ Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
// Make it a phone call
Notification.Builder nb = new Notification.Builder(mContext,
mTestNotificationChannel.getId())
@@ -5036,7 +4930,7 @@
setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
// Give it bubble metadata
- Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+ Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
// Give it a person
Person person = new Person.Builder()
.setName("bubblebot")
@@ -5070,30 +4964,8 @@
// Bubbles are NOT allowed!
setUpPrefsForBubbles(false /* global */, true /* app */, true /* channel */);
- // Give it bubble metadata
- Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
- // Give it a person
- Person person = new Person.Builder()
- .setName("bubblebot")
- .build();
- // Make it messaging style
- Notification.Builder nb = new Notification.Builder(mContext,
- mTestNotificationChannel.getId())
- .setContentTitle("foo")
- .setBubbleMetadata(data)
- .setStyle(new Notification.MessagingStyle(person)
- .setConversationTitle("Bubble Chat")
- .addMessage("Hello?",
- SystemClock.currentThreadTimeMillis() - 300000, person)
- .addMessage("Is it me you're looking for?",
- SystemClock.currentThreadTimeMillis(), person)
- )
- .setSmallIcon(android.R.drawable.sym_def_app_icon);
-
- StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
- "testFlagBubbleNotifs_noFlag_messaging_appNotAllowed", mUid, 0,
- nb.build(), new UserHandle(mUid), null, 0);
- NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+ NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+ "testFlagBubbleNotifs_noFlag_messaging_appNotAllowed");
// Post the notification
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
@@ -5102,7 +4974,7 @@
// not allowed, no bubble
assertFalse(mService.getNotificationRecord(
- sbn.getKey()).getNotification().isBubbleNotification());
+ nr.sbn.getKey()).getNotification().isBubbleNotification());
}
@Test
@@ -5110,8 +4982,14 @@
// Bubbles are allowed!
setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
- // Notif WITHOUT bubble metadata
- NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
+ // Messaging notif WITHOUT bubble metadata
+ Notification.Builder nb = getMessageStyleNotifBuilder(false /* addBubbleMetadata */,
+ null /* groupKey */, false /* isSummary */);
+
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+ "testFlagBubbleNotifs_noFlag_notBubble", mUid, 0,
+ nb.build(), new UserHandle(mUid), null, 0);
+ NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
// Post the notification
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
@@ -5128,39 +5006,17 @@
// Bubbles are allowed except on this channel
setUpPrefsForBubbles(true /* global */, true /* app */, false /* channel */);
- // Give it bubble metadata
- Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
- // Give it a person
- Person person = new Person.Builder()
- .setName("bubblebot")
- .build();
- // Make it messaging style
- Notification.Builder nb = new Notification.Builder(mContext,
- mTestNotificationChannel.getId())
- .setContentTitle("foo")
- .setBubbleMetadata(data)
- .setStyle(new Notification.MessagingStyle(person)
- .setConversationTitle("Bubble Chat")
- .addMessage("Hello?",
- SystemClock.currentThreadTimeMillis() - 300000, person)
- .addMessage("Is it me you're looking for?",
- SystemClock.currentThreadTimeMillis(), person)
- )
- .setSmallIcon(android.R.drawable.sym_def_app_icon);
-
- StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
- "testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed", mUid, 0,
- nb.build(), new UserHandle(mUid), null, 0);
- NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+ NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+ "testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed");
// Post the notification
- mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
waitForIdle();
// channel not allowed, no bubble
assertFalse(mService.getNotificationRecord(
- sbn.getKey()).getNotification().isBubbleNotification());
+ nr.sbn.getKey()).getNotification().isBubbleNotification());
}
@Test
@@ -5169,7 +5025,7 @@
setUpPrefsForBubbles(false /* global */, true /* app */, true /* channel */);
// Give it bubble metadata
- Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+ Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
// Give it a person
Person person = new Person.Builder()
.setName("bubblebot")
@@ -5205,7 +5061,7 @@
setUpPrefsForBubbles(true /* global */, true /* app */, false /* channel */);
// Give it bubble metadata
- Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+ Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
// Give it a person
Person person = new Person.Builder()
.setName("bubblebot")
@@ -5412,13 +5268,9 @@
// Bubbles are allowed!
setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
- // Notif with bubble metadata but not our other misc requirements
- NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, true /* isBubble */);
-
- // Say we're foreground
- when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
- IMPORTANCE_FOREGROUND);
+ // Notif with bubble metadata
+ NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+ "testNotificationBubbleChanged_false");
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
@@ -5447,9 +5299,9 @@
// Bubbles are allowed!
setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
- // Plain notification that has bubble metadata
+ // Notif that is not a bubble
NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, true /* isBubble */);
+ 1, null, false);
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
waitForIdle();
@@ -5459,9 +5311,12 @@
assertEquals(1, notifsBefore.length);
assertEquals((notifsBefore[0].getNotification().flags & FLAG_BUBBLE), 0);
- // Make the package foreground so that we're allowed to be a bubble
- when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
- IMPORTANCE_FOREGROUND);
+ // Update the notification to be message style / meet bubble requirements
+ NotificationRecord nr2 = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+ nr.sbn.getTag());
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr2.sbn.getTag(),
+ nr2.sbn.getId(), nr2.sbn.getNotification(), nr2.sbn.getUserId());
+ waitForIdle();
// Reset as this is called when the notif is first sent
reset(mListeners);
@@ -5482,8 +5337,7 @@
setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
// Notif that is not a bubble
- NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, true /* isBubble */);
+ NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
waitForIdle();
@@ -5681,26 +5535,17 @@
// Bubbles are allowed!
setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
- // Plain notification that has bubble metadata
- NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, true /* isBubble */);
+ // And we are low ram
+ when(mActivityManager.isLowRamDevice()).thenReturn(true);
+
+ // Notification that would typically bubble
+ NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+ "testNotificationBubbles_disabled_lowRamDevice");
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
waitForIdle();
- // Would be a normal notification because wouldn't have met requirements to bubble
- StatusBarNotification[] notifsBefore = mBinderService.getActiveNotifications(PKG);
- assertEquals(1, notifsBefore.length);
- assertEquals((notifsBefore[0].getNotification().flags & FLAG_BUBBLE), 0);
-
- // Make the package foreground so that we're allowed to be a bubble
- when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
- IMPORTANCE_FOREGROUND);
-
- // And we are low ram
- when(mActivityManager.isLowRamDevice()).thenReturn(true);
-
- // We wouldn't be a bubble because the notification didn't meet requirements (low ram)
+ // But we wouldn't be a bubble because the device is low ram & all bubbles are disabled.
StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
assertEquals(1, notifsAfter.length);
assertEquals((notifsAfter[0].getNotification().flags & FLAG_BUBBLE), 0);
@@ -5774,50 +5619,23 @@
// Bubbles are allowed!
setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
- // Give it bubble metadata
- Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder()
- .setSuppressNotification(true)
- .setAutoExpandBubble(true).build();
- // Give it a person
- Person person = new Person.Builder()
- .setName("bubblebot")
- .build();
- // It needs remote input to be bubble-able
- RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
- PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
- Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
- Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
- inputIntent).addRemoteInput(remoteInput)
- .build();
- // Make it messaging style
- Notification.Builder nb = new Notification.Builder(mContext,
- mTestNotificationChannel.getId())
- .setContentTitle("foo")
- .setBubbleMetadata(data)
- .setStyle(new Notification.MessagingStyle(person)
- .setConversationTitle("Bubble Chat")
- .addMessage("Hello?",
- SystemClock.currentThreadTimeMillis() - 300000, person)
- .addMessage("Is it me you're looking for?",
- SystemClock.currentThreadTimeMillis(), person)
- )
- .setActions(replyAction)
- .setSmallIcon(android.R.drawable.sym_def_app_icon);
-
- StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
- nb.build(), new UserHandle(mUid), null, 0);
- NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+ NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+ "testNotificationBubbles_flagAutoExpandForeground_fails_notForeground");
+ // Modify metadata flags
+ nr.sbn.getNotification().getBubbleMetadata().setFlags(
+ Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE
+ | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
// Ensure we're not foreground
when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
IMPORTANCE_VISIBLE);
- mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
waitForIdle();
// yes allowed, yes messaging, yes bubble
- Notification notif = mService.getNotificationRecord(sbn.getKey()).getNotification();
+ Notification notif = mService.getNotificationRecord(nr.sbn.getKey()).getNotification();
assertTrue(notif.isBubbleNotification());
// Our flags should have failed since we're not foreground
@@ -5831,53 +5649,26 @@
// Bubbles are allowed!
setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
- // Give it bubble metadata
- Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder()
- .setSuppressNotification(true)
- .setAutoExpandBubble(true).build();
- // Give it a person
- Person person = new Person.Builder()
- .setName("bubblebot")
- .build();
- // It needs remote input to be bubble-able
- RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
- PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
- Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
- Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
- inputIntent).addRemoteInput(remoteInput)
- .build();
- // Make it messaging style
- Notification.Builder nb = new Notification.Builder(mContext,
- mTestNotificationChannel.getId())
- .setContentTitle("foo")
- .setBubbleMetadata(data)
- .setStyle(new Notification.MessagingStyle(person)
- .setConversationTitle("Bubble Chat")
- .addMessage("Hello?",
- SystemClock.currentThreadTimeMillis() - 300000, person)
- .addMessage("Is it me you're looking for?",
- SystemClock.currentThreadTimeMillis(), person)
- )
- .setActions(replyAction)
- .setSmallIcon(android.R.drawable.sym_def_app_icon);
-
- StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
- nb.build(), new UserHandle(mUid), null, 0);
- NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+ NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+ "testNotificationBubbles_flagAutoExpandForeground_succeeds_foreground");
+ // Modify metadata flags
+ nr.sbn.getNotification().getBubbleMetadata().setFlags(
+ Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE
+ | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
// Ensure we are in the foreground
when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
IMPORTANCE_FOREGROUND);
- mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
waitForIdle();
// yes allowed, yes messaging, yes bubble
- Notification notif = mService.getNotificationRecord(sbn.getKey()).getNotification();
+ Notification notif = mService.getNotificationRecord(nr.sbn.getKey()).getNotification();
assertTrue(notif.isBubbleNotification());
- // Our flags should have failed since we are foreground
+ // Our flags should have passed since we are foreground
assertTrue(notif.getBubbleMetadata().getAutoExpandBubble());
assertTrue(notif.getBubbleMetadata().isNotificationSuppressed());
}
@@ -6004,7 +5795,7 @@
@Test
public void testNotificationHistory_addNoisyNotification() throws Exception {
NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, false);
+ null /* tvExtender */);
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
waitForIdle();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 5aece45..0527561 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -594,6 +594,8 @@
@Test
public void layoutWindowLw_withForwardInset_SoftInputAdjustResize() {
+ assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_NONE);
+
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_RESIZE;
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 05d1c76..e507508 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -408,6 +408,16 @@
}
}
+ /**
+ * Throws if caller doesn't hold the given lock.
+ * @param lock the lock
+ */
+ static void checkHoldsLock(Object lock) {
+ if (!Thread.holdsLock(lock)) {
+ throw new IllegalStateException("Caller doesn't hold global lock.");
+ }
+ }
+
protected class TestActivityTaskManagerService extends ActivityTaskManagerService {
// ActivityStackSupervisor may be created more than once while setting up AMS and ATMS.
// We keep the reference in order to prevent creating it twice.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index e5121b9..77af5ee 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -72,7 +72,7 @@
private final DisplayInfo mInfo;
private boolean mCanRotate = true;
private int mWindowingMode = WINDOWING_MODE_FULLSCREEN;
- private int mPosition = POSITION_TOP;
+ private int mPosition = POSITION_BOTTOM;
private final ActivityTaskManagerService mService;
private boolean mSystemDecorations = false;
@@ -127,13 +127,13 @@
return this;
}
TestDisplayContent build() {
+ SystemServicesTestRule.checkHoldsLock(mService.mGlobalLock);
+
final int displayId = SystemServicesTestRule.sNextDisplayId++;
final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
mInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
- final TestDisplayContent newDisplay;
- synchronized (mService.mGlobalLock) {
- newDisplay = new TestDisplayContent(mService.mStackSupervisor, display);
- }
+ final TestDisplayContent newDisplay =
+ new TestDisplayContent(mService.mStackSupervisor, display);
// disable the normal system decorations
final DisplayPolicy displayPolicy = newDisplay.mDisplayContent.getDisplayPolicy();
spyOn(displayPolicy);
@@ -153,6 +153,10 @@
doReturn(false).when(newDisplay.mDisplayContent)
.handlesOrientationChangeFromDescendant();
}
+ // Please add stubbing before this line. Services will start using this display in other
+ // threads immediately after adding it to hierarchy. Calling doAnswer() type of stubbing
+ // reduces chance of races, but still doesn't eliminate race conditions.
+ mService.mRootWindowContainer.addChild(newDisplay, mPosition);
return newDisplay;
}
}
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 248b06b..d3cd3cb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -21,6 +21,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.WindowManager.TRANSIT_TASK_OPEN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyFloat;
@@ -49,7 +50,12 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.IBinder;
+import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
@@ -782,6 +788,63 @@
assertEquals(newDc, activity.mDisplayContent);
}
+ @Test
+ public void testTaskCanApplyAnimation() {
+ final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+ final Task task = createTaskInStack(stack, 0 /* userId */);
+ final ActivityRecord activity =
+ WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
+ verifyWindowContainerApplyAnimation(task, activity);
+ }
+
+ @Test
+ public void testStackCanApplyAnimation() {
+ final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+ final ActivityRecord activity = WindowTestUtils.createActivityRecordInTask(mDisplayContent,
+ createTaskInStack(stack, 0 /* userId */));
+ verifyWindowContainerApplyAnimation(stack, activity);
+ }
+
+ private void verifyWindowContainerApplyAnimation(WindowContainer wc, ActivityRecord act) {
+ // Initial remote animation for app transition.
+ final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+ new IRemoteAnimationRunner.Stub() {
+ @Override
+ public void onAnimationStart(RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ IRemoteAnimationFinishedCallback finishedCallback) {
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void onAnimationCancelled() {
+ }
+ }, 0, 0, false);
+ adapter.setCallingPidUid(123, 456);
+ wc.getDisplayContent().prepareAppTransition(TRANSIT_TASK_OPEN, false);
+ wc.getDisplayContent().mAppTransition.overridePendingAppTransitionRemote(adapter);
+ spyOn(wc);
+ doReturn(true).when(wc).okToAnimate();
+
+ // Make sure animating state is as expected after applied animation.
+ assertTrue(wc.applyAnimation(null, TRANSIT_TASK_OPEN, true, false));
+ assertEquals(wc.getTopMostActivity(), act);
+ assertTrue(wc.isAnimating());
+ assertTrue(act.isAnimating(PARENTS));
+
+ // Make sure animation finish callback will be received and reset animating state after
+ // animation finish.
+ wc.getDisplayContent().mAppTransition.goodToGo(TRANSIT_TASK_OPEN, act,
+ mDisplayContent.mOpeningApps);
+ verify(wc).onAnimationFinished();
+ assertFalse(wc.isAnimating());
+ assertFalse(act.isAnimating(PARENTS));
+ }
+
/* Used so we can gain access to some protected members of the {@link WindowContainer} class */
private static class TestWindowContainer extends WindowContainer<TestWindowContainer> {
private final int mLayer;
diff --git a/services/usage/OWNERS b/services/usage/OWNERS
new file mode 100644
index 0000000..9daa093
--- /dev/null
+++ b/services/usage/OWNERS
@@ -0,0 +1,4 @@
+mwachens@google.com
+varunshah@google.com
+huiyu@google.com
+yamasani@google.com
diff --git a/telephony/OWNERS b/telephony/OWNERS
index 2a6e8de..58a7ea0 100644
--- a/telephony/OWNERS
+++ b/telephony/OWNERS
@@ -14,4 +14,5 @@
refuhoo@google.com
paulye@google.com
nazaninb@google.com
-sarahchin@google.com
\ No newline at end of file
+sarahchin@google.com
+dbright@google.com
diff --git a/telephony/common/com/android/internal/telephony/PackageChangeReceiver.java b/telephony/common/com/android/internal/telephony/PackageChangeReceiver.java
index 922af12..0b47547 100644
--- a/telephony/common/com/android/internal/telephony/PackageChangeReceiver.java
+++ b/telephony/common/com/android/internal/telephony/PackageChangeReceiver.java
@@ -24,17 +24,17 @@
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.Looper;
import android.os.UserHandle;
-import com.android.internal.os.BackgroundThread;
-
/**
* Helper class for monitoring the state of packages: adding, removing,
* updating, and disappearing and reappearing on the SD card.
*/
public abstract class PackageChangeReceiver extends BroadcastReceiver {
static final IntentFilter sPackageIntentFilter = new IntentFilter();
+ private static HandlerThread sHandlerThread;
static {
sPackageIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
sPackageIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
@@ -43,28 +43,24 @@
sPackageIntentFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
sPackageIntentFilter.addDataScheme("package");
}
+ // Keep an instance of Context around as long as we still want the receiver:
+ // if the instance of Context gets garbage-collected, it'll unregister the receiver, so only
+ // unset when we want to unregister.
Context mRegisteredContext;
/**
- * To register the intents that needed for monitoring the state of packages
+ * To register the intents that needed for monitoring the state of packages. Once this method
+ * has been called on an instance of {@link PackageChangeReceiver}, all subsequent calls must
+ * have the same {@code user} argument.
*/
public void register(@NonNull Context context, @Nullable Looper thread,
@Nullable UserHandle user) {
if (mRegisteredContext != null) {
throw new IllegalStateException("Already registered");
}
- Handler handler = (thread == null) ? BackgroundThread.getHandler() : new Handler(thread);
- mRegisteredContext = context;
- if (handler != null) {
- if (user != null) {
- context.registerReceiverAsUser(this, user, sPackageIntentFilter, null, handler);
- } else {
- context.registerReceiver(this, sPackageIntentFilter,
- null, handler);
- }
- } else {
- throw new NullPointerException();
- }
+ Handler handler = new Handler(thread == null ? getStaticLooper() : thread);
+ mRegisteredContext = user == null ? context : context.createContextAsUser(user, 0);
+ mRegisteredContext.registerReceiver(this, sPackageIntentFilter, null, handler);
}
/**
@@ -78,6 +74,14 @@
mRegisteredContext = null;
}
+ private static synchronized Looper getStaticLooper() {
+ if (sHandlerThread == null) {
+ sHandlerThread = new HandlerThread(PackageChangeReceiver.class.getSimpleName());
+ sHandlerThread.start();
+ }
+ return sHandlerThread.getLooper();
+ }
+
/**
* This method is invoked when receive the Intent.ACTION_PACKAGE_ADDED
*/
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index 0275cb9..b302589 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -58,6 +58,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
/**
* Class for managing the primary application that we will deliver SMS/MMS messages to
@@ -248,6 +249,7 @@
private static Collection<SmsApplicationData> getApplicationCollectionInternal(
Context context, int userId) {
PackageManager packageManager = context.getPackageManager();
+ UserHandle userHandle = UserHandle.of(userId);
// Get the list of apps registered for SMS
Intent intent = new Intent(Intents.SMS_DELIVER_ACTION);
@@ -256,7 +258,7 @@
}
List<ResolveInfo> smsReceivers = packageManager.queryBroadcastReceiversAsUser(intent,
PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
- userId);
+ userHandle);
HashMap<String, SmsApplicationData> receivers = new HashMap<String, SmsApplicationData>();
@@ -283,7 +285,7 @@
intent.setDataAndType(null, "application/vnd.wap.mms-message");
List<ResolveInfo> mmsReceivers = packageManager.queryBroadcastReceiversAsUser(intent,
PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
- userId);
+ userHandle);
for (ResolveInfo resolveInfo : mmsReceivers) {
final ActivityInfo activityInfo = resolveInfo.activityInfo;
if (activityInfo == null) {
@@ -325,7 +327,7 @@
Uri.fromParts(SCHEME_SMSTO, "", null));
List<ResolveInfo> sendToActivities = packageManager.queryIntentActivitiesAsUser(intent,
PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
- userId);
+ userHandle);
for (ResolveInfo resolveInfo : sendToActivities) {
final ActivityInfo activityInfo = resolveInfo.activityInfo;
if (activityInfo == null) {
@@ -343,7 +345,7 @@
List<ResolveInfo> smsAppChangedReceivers =
packageManager.queryBroadcastReceiversAsUser(intent,
PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplicationCollectionInternal smsAppChangedActivities=" +
smsAppChangedReceivers);
@@ -370,7 +372,7 @@
List<ResolveInfo> providerChangedReceivers =
packageManager.queryBroadcastReceiversAsUser(intent,
PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplicationCollectionInternal providerChangedActivities=" +
providerChangedReceivers);
@@ -397,7 +399,7 @@
List<ResolveInfo> simFullReceivers =
packageManager.queryBroadcastReceiversAsUser(intent,
PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplicationCollectionInternal simFullReceivers="
+ simFullReceivers);
@@ -626,7 +628,8 @@
}
// We only make the change if the new package is valid
- PackageManager packageManager = context.getPackageManager();
+ PackageManager packageManager =
+ context.createContextAsUser(userHandle, 0).getPackageManager();
Collection<SmsApplicationData> applications = getApplicationCollectionInternal(
context, userId);
SmsApplicationData oldAppData = oldPackageName != null ?
@@ -637,8 +640,7 @@
AppOpsManager appOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
if (oldPackageName != null) {
try {
- int uid = packageManager.getPackageInfoAsUser(
- oldPackageName, 0, userId).applicationInfo.uid;
+ int uid = packageManager.getPackageInfo(oldPackageName, 0).applicationInfo.uid;
setExclusiveAppops(oldPackageName, appOps, uid, AppOpsManager.MODE_DEFAULT);
} catch (NameNotFoundException e) {
Rlog.w(LOG_TAG, "Old SMS package not found: " + oldPackageName);
@@ -802,9 +804,16 @@
}
private void onPackageChanged() {
- PackageManager packageManager = mContext.getPackageManager();
+ int userId;
+ try {
+ userId = getSendingUser().getIdentifier();
+ } catch (NullPointerException e) {
+ // This should never happen in prod -- unit tests will put the receiver into a
+ // unusual state where the pending result is null, which produces a NPE when calling
+ // getSendingUserId. Just pretend like it's the system user for testing.
+ userId = UserHandle.USER_SYSTEM;
+ }
Context userContext = mContext;
- final int userId = getSendingUserId();
if (userId != UserHandle.USER_SYSTEM) {
try {
userContext = mContext.createPackageContextAsUser(mContext.getPackageName(), 0,
@@ -815,10 +824,11 @@
}
}
}
+ PackageManager packageManager = userContext.getPackageManager();
// Ensure this component is still configured as the preferred activity
ComponentName componentName = getDefaultSendToApplication(userContext, true);
if (componentName != null) {
- configurePreferredActivity(packageManager, componentName, userId);
+ configurePreferredActivity(packageManager, componentName);
}
}
}
@@ -830,41 +840,36 @@
@UnsupportedAppUsage
private static void configurePreferredActivity(PackageManager packageManager,
- ComponentName componentName, int userId) {
+ ComponentName componentName) {
// Add the four activity preferences we want to direct to this app.
- replacePreferredActivity(packageManager, componentName, userId, SCHEME_SMS);
- replacePreferredActivity(packageManager, componentName, userId, SCHEME_SMSTO);
- replacePreferredActivity(packageManager, componentName, userId, SCHEME_MMS);
- replacePreferredActivity(packageManager, componentName, userId, SCHEME_MMSTO);
+ replacePreferredActivity(packageManager, componentName, SCHEME_SMS);
+ replacePreferredActivity(packageManager, componentName, SCHEME_SMSTO);
+ replacePreferredActivity(packageManager, componentName, SCHEME_MMS);
+ replacePreferredActivity(packageManager, componentName, SCHEME_MMSTO);
}
/**
* Updates the ACTION_SENDTO activity to the specified component for the specified scheme.
*/
private static void replacePreferredActivity(PackageManager packageManager,
- ComponentName componentName, int userId, String scheme) {
+ ComponentName componentName, String scheme) {
// Build the set of existing activities that handle this scheme
Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts(scheme, "", null));
- List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivitiesAsUser(
- intent, PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_RESOLVED_FILTER,
- userId);
+ List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivities(
+ intent, PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_RESOLVED_FILTER);
- // Build the set of ComponentNames for these activities
- final int n = resolveInfoList.size();
- ComponentName[] set = new ComponentName[n];
- for (int i = 0; i < n; i++) {
- ResolveInfo info = resolveInfoList.get(i);
- set[i] = new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
- }
+ List<ComponentName> components = resolveInfoList.stream().map(info ->
+ new ComponentName(info.activityInfo.packageName, info.activityInfo.name))
+ .collect(Collectors.toList());
// Update the preferred SENDTO activity for the specified scheme
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_SENDTO);
intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
intentFilter.addDataScheme(scheme);
- packageManager.replacePreferredActivityAsUser(intentFilter,
+ packageManager.replacePreferredActivity(intentFilter,
IntentFilter.MATCH_CATEGORY_SCHEME + IntentFilter.MATCH_ADJUSTMENT_NORMAL,
- set, componentName, userId);
+ components, componentName);
}
/**
diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java
index e523fba..ce32dce 100644
--- a/telephony/java/android/telephony/CellIdentity.java
+++ b/telephony/java/android/telephony/CellIdentity.java
@@ -185,6 +185,14 @@
@SystemApi
public abstract @NonNull CellLocation asCellLocation();
+ /**
+ * Create and a return a new instance of CellIdentity with location-identifying information
+ * removed.
+ *
+ * @hide
+ */
+ public abstract @NonNull CellIdentity sanitizeLocationInfo();
+
@Override
public boolean equals(Object other) {
if (!(other instanceof CellIdentity)) {
@@ -312,4 +320,23 @@
return true;
}
+ /** @hide */
+ public static CellIdentity create(android.hardware.radio.V1_5.CellIdentity ci) {
+ if (ci == null) return null;
+ switch (ci.getDiscriminator()) {
+ case android.hardware.radio.V1_5.CellIdentity.hidl_discriminator.gsm:
+ return new CellIdentityGsm(ci.gsm());
+ case android.hardware.radio.V1_5.CellIdentity.hidl_discriminator.cdma:
+ return new CellIdentityCdma(ci.cdma());
+ case android.hardware.radio.V1_5.CellIdentity.hidl_discriminator.lte:
+ return new CellIdentityLte(ci.lte());
+ case android.hardware.radio.V1_5.CellIdentity.hidl_discriminator.wcdma:
+ return new CellIdentityWcdma(ci.wcdma());
+ case android.hardware.radio.V1_5.CellIdentity.hidl_discriminator.tdscdma:
+ return new CellIdentityTdscdma(ci.tdscdma());
+ case android.hardware.radio.V1_5.CellIdentity.hidl_discriminator.nr:
+ return new CellIdentityNr(ci.nr());
+ default: return null;
+ }
+ }
}
diff --git a/telephony/java/android/telephony/CellIdentityCdma.java b/telephony/java/android/telephony/CellIdentityCdma.java
index 54236b42..1a6bf33 100644
--- a/telephony/java/android/telephony/CellIdentityCdma.java
+++ b/telephony/java/android/telephony/CellIdentityCdma.java
@@ -128,7 +128,8 @@
}
/** @hide */
- public CellIdentityCdma sanitizeLocationInfo() {
+ @Override
+ public @NonNull CellIdentityCdma sanitizeLocationInfo() {
return new CellIdentityCdma(CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
mAlphaLong, mAlphaShort);
diff --git a/telephony/java/android/telephony/CellIdentityGsm.java b/telephony/java/android/telephony/CellIdentityGsm.java
index 4e4454d..2ecdfce 100644
--- a/telephony/java/android/telephony/CellIdentityGsm.java
+++ b/telephony/java/android/telephony/CellIdentityGsm.java
@@ -104,7 +104,8 @@
}
/** @hide */
- public CellIdentityGsm sanitizeLocationInfo() {
+ @Override
+ public @NonNull CellIdentityGsm sanitizeLocationInfo() {
return new CellIdentityGsm(CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
CellInfo.UNAVAILABLE, mMccStr, mMncStr, mAlphaLong, mAlphaShort);
}
diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java
index c3fc73b..15c9175 100644
--- a/telephony/java/android/telephony/CellIdentityLte.java
+++ b/telephony/java/android/telephony/CellIdentityLte.java
@@ -121,7 +121,8 @@
}
/** @hide */
- public CellIdentityLte sanitizeLocationInfo() {
+ @Override
+ public @NonNull CellIdentityLte sanitizeLocationInfo() {
return new CellIdentityLte(CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
mMccStr, mMncStr, mAlphaLong, mAlphaShort);
diff --git a/telephony/java/android/telephony/CellIdentityNr.java b/telephony/java/android/telephony/CellIdentityNr.java
index e3fec7b..f08a580 100644
--- a/telephony/java/android/telephony/CellIdentityNr.java
+++ b/telephony/java/android/telephony/CellIdentityNr.java
@@ -69,7 +69,8 @@
}
/** @hide */
- public CellIdentityNr sanitizeLocationInfo() {
+ @Override
+ public @NonNull CellIdentityNr sanitizeLocationInfo() {
return new CellIdentityNr(CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
mMccStr, mMncStr, CellInfo.UNAVAILABLE, mAlphaLong, mAlphaShort);
}
diff --git a/telephony/java/android/telephony/CellIdentityTdscdma.java b/telephony/java/android/telephony/CellIdentityTdscdma.java
index 8f812b6..4bb3a95 100644
--- a/telephony/java/android/telephony/CellIdentityTdscdma.java
+++ b/telephony/java/android/telephony/CellIdentityTdscdma.java
@@ -97,7 +97,8 @@
}
/** @hide */
- public CellIdentityTdscdma sanitizeLocationInfo() {
+ @Override
+ public @NonNull CellIdentityTdscdma sanitizeLocationInfo() {
return new CellIdentityTdscdma(mMccStr, mMncStr, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, mAlphaLong, mAlphaShort);
}
diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java
index fbb43c9..4ecd134 100644
--- a/telephony/java/android/telephony/CellIdentityWcdma.java
+++ b/telephony/java/android/telephony/CellIdentityWcdma.java
@@ -98,7 +98,8 @@
}
/** @hide */
- public CellIdentityWcdma sanitizeLocationInfo() {
+ @Override
+ public @NonNull CellIdentityWcdma sanitizeLocationInfo() {
return new CellIdentityWcdma(CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, mMccStr, mMncStr,
mAlphaLong, mAlphaShort);
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index aff1391..be85b30 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -360,6 +360,12 @@
*/
public static final int OUTGOING_EMERGENCY_CALL_PLACED = 80;
+ /**
+ * Indicates that incoming call was rejected by the modem before the call went in ringing
+ */
+ public static final int INCOMING_AUTO_REJECTED = 81;
+
+
//*********************************************************************************************
// When adding a disconnect type:
// 1) Update toString() with the newly added disconnect type.
@@ -536,6 +542,8 @@
return "WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION";
case OUTGOING_EMERGENCY_CALL_PLACED:
return "OUTGOING_EMERGENCY_CALL_PLACED";
+ case INCOMING_AUTO_REJECTED:
+ return "INCOMING_AUTO_REJECTED";
default:
return "INVALID: " + cause;
}
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index fc717e7..cbd5ed6 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -46,13 +46,17 @@
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = "DOMAIN_", value = {DOMAIN_CS, DOMAIN_PS})
+ @IntDef(prefix = "DOMAIN_", value = {DOMAIN_UNKNOWN, DOMAIN_CS, DOMAIN_PS, DOMAIN_CS_PS})
public @interface Domain {}
+ /** Unknown / Unspecified domain */
+ public static final int DOMAIN_UNKNOWN = 0;
/** Circuit switching domain */
- public static final int DOMAIN_CS = 1;
+ public static final int DOMAIN_CS = android.hardware.radio.V1_5.Domain.CS;
/** Packet switching domain */
- public static final int DOMAIN_PS = 2;
+ public static final int DOMAIN_PS = android.hardware.radio.V1_5.Domain.PS;
+ /** Applicable to both CS and PS Domain */
+ public static final int DOMAIN_CS_PS = DOMAIN_CS | DOMAIN_PS;
/**
* Network registration state
@@ -504,11 +508,21 @@
}
}
+ /** @hide */
+ static @NonNull String domainToString(@Domain int domain) {
+ switch (domain) {
+ case DOMAIN_CS: return "CS";
+ case DOMAIN_PS: return "PS";
+ case DOMAIN_CS_PS: return "CS_PS";
+ default: return "UNKNOWN";
+ }
+ }
+
@NonNull
@Override
public String toString() {
return new StringBuilder("NetworkRegistrationInfo{")
- .append(" domain=").append((mDomain == DOMAIN_CS) ? "CS" : "PS")
+ .append(" domain=").append(domainToString(mDomain))
.append(" transportType=").append(
AccessNetworkConstants.transportTypeToString(mTransportType))
.append(" registrationState=").append(registrationStateToString(mRegistrationState))
@@ -646,7 +660,7 @@
* .build();
* </code></pre>
*/
- public static final class Builder{
+ public static final class Builder {
@Domain
private int mDomain;
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index e455fab..5b12aed 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -545,7 +545,7 @@
* @see #STATE_EMERGENCY_ONLY
* @see #STATE_POWER_OFF
*
- * @return current data registration state {@link RegState}
+ * @return current data registration state
*
* @hide
*/
@@ -562,7 +562,7 @@
* @see #STATE_EMERGENCY_ONLY
* @see #STATE_POWER_OFF
*
- * @return current data registration state {@link RegState}
+ * @return current data registration state
*
* @hide
*/
@@ -1886,7 +1886,7 @@
synchronized (mNetworkRegistrationInfos) {
for (NetworkRegistrationInfo networkRegistrationInfo : mNetworkRegistrationInfos) {
- if (networkRegistrationInfo.getDomain() == domain) {
+ if ((networkRegistrationInfo.getDomain() & domain) != 0) {
list.add(new NetworkRegistrationInfo(networkRegistrationInfo));
}
}
@@ -1902,7 +1902,6 @@
* @param transportType The transport type
* @return The matching {@link NetworkRegistrationInfo}
* @hide
- *
*/
@Nullable
@SystemApi
@@ -1911,7 +1910,7 @@
synchronized (mNetworkRegistrationInfos) {
for (NetworkRegistrationInfo networkRegistrationInfo : mNetworkRegistrationInfos) {
if (networkRegistrationInfo.getTransportType() == transportType
- && networkRegistrationInfo.getDomain() == domain) {
+ && (networkRegistrationInfo.getDomain() & domain) != 0) {
return new NetworkRegistrationInfo(networkRegistrationInfo);
}
}
diff --git a/telephony/java/android/telephony/SmsCbMessage.java b/telephony/java/android/telephony/SmsCbMessage.java
index 045d1eb..3c67094 100644
--- a/telephony/java/android/telephony/SmsCbMessage.java
+++ b/telephony/java/android/telephony/SmsCbMessage.java
@@ -557,7 +557,7 @@
public ContentValues getContentValues() {
ContentValues cv = new ContentValues(16);
cv.put(CellBroadcasts.SLOT_INDEX, mSlotIndex);
- cv.put(CellBroadcasts.SUB_ID, mSubId);
+ cv.put(CellBroadcasts.SUBSCRIPTION_ID, mSubId);
cv.put(CellBroadcasts.GEOGRAPHICAL_SCOPE, mGeographicalScope);
if (mLocation.getPlmn() != null) {
cv.put(CellBroadcasts.PLMN, mLocation.getPlmn());
@@ -621,7 +621,7 @@
int format = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_FORMAT));
int priority = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_PRIORITY));
int slotIndex = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SLOT_INDEX));
- int subId = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SUB_ID));
+ int subId = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SUBSCRIPTION_ID));
String plmn;
int plmnColumn = cursor.getColumnIndex(CellBroadcasts.PLMN);
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 4c8a81b..22eed6e 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1199,12 +1199,17 @@
}
/**
- * Get the active SubscriptionInfo associated with the iccId
+ * Gets an active SubscriptionInfo {@link SubscriptionInfo} associated with the Sim IccId.
+ *
* @param iccId the IccId of SIM card
* @return SubscriptionInfo, maybe null if its not active
+ *
* @hide
*/
- public SubscriptionInfo getActiveSubscriptionInfoForIccIndex(String iccId) {
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @Nullable
+ @SystemApi
+ public SubscriptionInfo getActiveSubscriptionInfoForIcc(@NonNull String iccId) {
if (VDBG) logd("[getActiveSubscriptionInfoForIccIndex]+ iccId=" + iccId);
if (iccId == null) {
logd("[getActiveSubscriptionInfoForIccIndex]- null iccid");
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 034fc22..dbfb6a2 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -1056,6 +1056,11 @@
}
/** @hide */
+ public boolean isEmergencyApn() {
+ return hasApnType(TYPE_EMERGENCY);
+ }
+
+ /** @hide */
public boolean canHandleType(@ApnType int type) {
if (!mCarrierEnabled) {
return false;
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index 998b39d..bc60d81 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -279,6 +279,14 @@
"android.telephony.ims.extra.ADDITIONAL_SIP_INVITE_FIELDS";
/**
+ * CallDisconnectCause: Specify call disconnect cause. This extra should be a code
+ * corresponding to ImsReasonInfo and should only be populated in the case that the
+ * call has already been missed
+ */
+ public static final String EXTRA_CALL_DISCONNECT_CAUSE =
+ "android.telephony.ims.extra.CALL_DISCONNECT_CAUSE";
+
+ /**
* Extra key which the RIL can use to indicate the radio technology used for a call.
* Valid values are:
* {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE},
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 3994f9b..284544b 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -554,4 +554,5 @@
int RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG = 1101;
int RIL_UNSOL_EMERGENCY_NUMBER_LIST = 1102;
int RIL_UNSOL_UICC_APPLICATIONS_ENABLEMENT_CHANGED = 1103;
+ int RIL_UNSOL_REGISTRATION_FAILED = 1104;
}
diff --git a/tests/TelephonyCommonTests/Android.bp b/tests/TelephonyCommonTests/Android.bp
index ae647eb..4f7569d 100644
--- a/tests/TelephonyCommonTests/Android.bp
+++ b/tests/TelephonyCommonTests/Android.bp
@@ -38,7 +38,7 @@
// Uncomment this and comment out the jarjar rule if you want to attach a debugger and step
// through the renamed classes.
- //platform_apis: true,
+ // platform_apis: true,
libs: [
"android.test.runner",
diff --git a/tests/TelephonyCommonTests/jarjar-rules.txt b/tests/TelephonyCommonTests/jarjar-rules.txt
index fe34719..4d1115f 100644
--- a/tests/TelephonyCommonTests/jarjar-rules.txt
+++ b/tests/TelephonyCommonTests/jarjar-rules.txt
@@ -1 +1,3 @@
rule com.android.internal.telephony.SmsApplication* com.android.internal.telephony.tests.SmsApplication@1
+rule android.telephony.PackageChangeReceiver* com.android.internal.telephony.tests.PackageChangeReceiver@1
+rule com.android.internal.os.BackgroundThread* com.android.internal.telephony.tests.BackgroundThread@1
diff --git a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
index 6d0ee18..83fd208 100644
--- a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
+++ b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
@@ -17,26 +17,34 @@
package com.android.internal.telephony.tests;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNotNull;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.Manifest;
import android.app.AppOpsManager;
import android.app.role.RoleManager;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.net.Uri;
+import android.os.Handler;
import android.os.UserHandle;
import android.provider.Telephony;
import android.telephony.TelephonyManager;
@@ -48,11 +56,15 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
/**
* Unit tests for the {@link SmsApplication} utility class
@@ -80,6 +92,13 @@
AppOpsManager.OPSTR_READ_CELL_BROADCASTS
};
+ private static final Set<String> SCHEMES_FOR_PREFERRED_APP = Arrays.stream(new String[]{
+ "mms",
+ "mmsto",
+ "sms",
+ "smsto"
+ }).collect(Collectors.toSet());
+
@Mock private Context mContext;
@Mock private TelephonyManager mTelephonyManager;
@Mock private RoleManager mRoleManager;
@@ -94,13 +113,16 @@
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mContext.getSystemService(RoleManager.class)).thenReturn(mRoleManager);
when(mContext.getSystemService(AppOpsManager.class)).thenReturn(mAppOpsManager);
+ when(mContext.createContextAsUser(isNotNull(), anyInt())).thenReturn(mContext);
doAnswer(invocation -> getResolveInfosForIntent(invocation.getArgument(0)))
.when(mPackageManager)
- .queryBroadcastReceiversAsUser(nullable(Intent.class), anyInt(), anyInt());
+ .queryBroadcastReceiversAsUser(nullable(Intent.class), anyInt(),
+ nullable(UserHandle.class));
doAnswer(invocation -> getResolveInfosForIntent(invocation.getArgument(0)))
.when(mPackageManager)
- .queryIntentActivitiesAsUser(nullable(Intent.class), anyInt(), anyInt());
+ .queryIntentActivitiesAsUser(nullable(Intent.class), anyInt(),
+ nullable(UserHandle.class));
doAnswer(invocation -> getResolveInfosForIntent(invocation.getArgument(0)))
.when(mPackageManager)
.queryIntentServicesAsUser(nullable(Intent.class), anyInt(),
@@ -137,6 +159,37 @@
AppOpsManager.MODE_ALLOWED);
}
+ @Test
+ public void testPackageChanged() throws Exception {
+ setupPackageInfosForCoreApps();
+ SmsApplication.initSmsPackageMonitor(mContext);
+ verify(mContext).createContextAsUser(eq(UserHandle.ALL), anyInt());
+ ArgumentCaptor<BroadcastReceiver> captor = ArgumentCaptor.forClass(BroadcastReceiver.class);
+ verify(mContext).registerReceiver(captor.capture(), isNotNull(),
+ isNull(), nullable(Handler.class));
+ BroadcastReceiver smsPackageMonitor = captor.getValue();
+
+ Intent packageChangedIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED);
+ packageChangedIntent.setData(
+ Uri.fromParts("package", TEST_COMPONENT_NAME.getPackageName(), null));
+ smsPackageMonitor.onReceive(mContext, packageChangedIntent);
+
+ ArgumentCaptor<IntentFilter> intentFilterCaptor =
+ ArgumentCaptor.forClass(IntentFilter.class);
+ verify(mPackageManager, times(SCHEMES_FOR_PREFERRED_APP.size()))
+ .replacePreferredActivity(intentFilterCaptor.capture(),
+ eq(IntentFilter.MATCH_CATEGORY_SCHEME
+ | IntentFilter.MATCH_ADJUSTMENT_NORMAL),
+ isNotNull(List.class),
+ eq(new ComponentName(TEST_COMPONENT_NAME.getPackageName(), SEND_TO_NAME)));
+
+ Set<String> capturedSchemes = intentFilterCaptor.getAllValues().stream()
+ .map(intentFilter -> intentFilter.getDataScheme(0))
+ .collect(Collectors.toSet());
+ assertEquals(SCHEMES_FOR_PREFERRED_APP.size(), capturedSchemes.size());
+ assertTrue(SCHEMES_FOR_PREFERRED_APP.containsAll(capturedSchemes));
+ }
+
private void setupPackageInfosForCoreApps() throws Exception {
PackageInfo phonePackageInfo = new PackageInfo();
ApplicationInfo phoneApplicationInfo = new ApplicationInfo();
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index c557656..74e2a098 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -1641,8 +1641,14 @@
ParsedResource* out_resource) {
out_resource->name.type = ResourceType::kStyleable;
- // Declare-styleable is kPrivate by default, because it technically only exists in R.java.
- out_resource->visibility_level = Visibility::Level::kPublic;
+ if (!options_.preserve_visibility_of_styleables) {
+ // This was added in change Idd21b5de4d20be06c6f8c8eb5a22ccd68afc4927 to mimic aapt1, but no one
+ // knows exactly what for.
+ //
+ // FWIW, styleables only appear in generated R classes. For custom views these should always be
+ // package-private (to be used only by the view class); themes are a different story.
+ out_resource->visibility_level = Visibility::Level::kPublic;
+ }
// Declare-styleable only ends up in default config;
if (out_resource->config != ConfigDescription::DefaultConfig()) {
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 06bb0c9..9d3ecc8 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -46,6 +46,12 @@
*/
bool error_on_positional_arguments = true;
+ /**
+ * If true, apply the same visibility rules for styleables as are used for
+ * all other resources. Otherwise, all styleables will be made public.
+ */
+ bool preserve_visibility_of_styleables = false;
+
// If visibility was forced, we need to use it when creating a new resource and also error if we
// try to parse the <public>, <public-group>, <java-symbol> or <symbol> tags.
Maybe<Visibility::Level> visibility;
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 4237469..24531bc 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -614,6 +614,32 @@
EXPECT_THAT(styleable->entries[2].name, Eq(make_value(test::ParseNameOrDie("attr/baz"))));
}
+TEST_F(ResourceParserTest, ParseDeclareStyleablePreservingVisibility) {
+ StringInputStream input(R"(
+ <resources>
+ <declare-styleable name="foo">
+ <attr name="myattr" />
+ </declare-styleable>
+ <declare-styleable name="bar">
+ <attr name="myattr" />
+ </declare-styleable>
+ <public type="styleable" name="bar" />
+ </resources>)");
+ ResourceParser parser(context_->GetDiagnostics(), &table_, Source{"test"},
+ ConfigDescription::DefaultConfig(),
+ ResourceParserOptions{.preserve_visibility_of_styleables = true});
+
+ xml::XmlPullParser xml_parser(&input);
+ ASSERT_TRUE(parser.Parse(&xml_parser));
+
+ EXPECT_EQ(
+ table_.FindResource(test::ParseNameOrDie("styleable/foo")).value().entry->visibility.level,
+ Visibility::Level::kUndefined);
+ EXPECT_EQ(
+ table_.FindResource(test::ParseNameOrDie("styleable/bar")).value().entry->visibility.level,
+ Visibility::Level::kPublic);
+}
+
TEST_F(ResourceParserTest, ParsePrivateAttributesDeclareStyleable) {
std::string input = R"(
<declare-styleable xmlns:privAndroid="http://schemas.android.com/apk/prv/res/android"
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index d50b1de..3268653 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -159,6 +159,7 @@
ResourceParserOptions parser_options;
parser_options.error_on_positional_arguments = !options.legacy_mode;
+ parser_options.preserve_visibility_of_styleables = options.preserve_visibility_of_styleables;
parser_options.translatable = translatable_file;
// If visibility was forced, we need to use it when creating a new resource and also error if
diff --git a/tools/aapt2/cmd/Compile.h b/tools/aapt2/cmd/Compile.h
index d3456b2..1752a1a 100644
--- a/tools/aapt2/cmd/Compile.h
+++ b/tools/aapt2/cmd/Compile.h
@@ -35,6 +35,8 @@
bool pseudolocalize = false;
bool no_png_crunch = false;
bool legacy_mode = false;
+ // See comments on aapt::ResourceParserOptions.
+ bool preserve_visibility_of_styleables = false;
bool verbose = false;
};
@@ -56,6 +58,11 @@
AddOptionalSwitch("--no-crunch", "Disables PNG processing", &options_.no_png_crunch);
AddOptionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings",
&options_.legacy_mode);
+ AddOptionalSwitch("--preserve-visibility-of-styleables",
+ "If specified, apply the same visibility rules for\n"
+ "styleables as are used for all other resources.\n"
+ "Otherwise, all stylesables will be made public.",
+ &options_.preserve_visibility_of_styleables);
AddOptionalFlag("--visibility",
"Sets the visibility of the compiled resources to the specified\n"
"level. Accepted levels: public, private, default", &visibility_);
diff --git a/wifi/Android.bp b/wifi/Android.bp
index 3934af1..634f674 100644
--- a/wifi/Android.bp
+++ b/wifi/Android.bp
@@ -48,6 +48,7 @@
"//frameworks/opt/net/wifi/tests/wifitests:__subpackages__",
"//frameworks/opt/net/wifi/libs/WifiTrackerLib/tests",
+ "//external/robolectric-shadows:__subpackages__",
]
java_library {
diff --git a/wifi/java/android/net/wifi/SoftApCapability.java b/wifi/java/android/net/wifi/SoftApCapability.java
index c4474e2..2bbe7d2 100644
--- a/wifi/java/android/net/wifi/SoftApCapability.java
+++ b/wifi/java/android/net/wifi/SoftApCapability.java
@@ -61,11 +61,20 @@
*/
public static final int SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT = 1 << 1;
+
+ /**
+ * Support for WPA3 Simultaneous Authentication of Equals (WPA3-SAE).
+ *
+ * flag when {@link config_wifi_softap_sae_supported)} is true.
+ */
+ public static final int SOFTAP_FEATURE_WPA3_SAE = 1 << 2;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = { "SOFTAP_FEATURE_" }, value = {
SOFTAP_FEATURE_ACS_OFFLOAD,
SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT,
+ SOFTAP_FEATURE_WPA3_SAE,
})
public @interface HotspotFeatures {}
diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java
index 05e245b..65e9b79 100644
--- a/wifi/java/android/net/wifi/SoftApConfiguration.java
+++ b/wifi/java/android/net/wifi/SoftApConfiguration.java
@@ -25,6 +25,7 @@
import android.os.Parcelable;
import android.text.TextUtils;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
@@ -55,6 +56,11 @@
@SystemApi
public final class SoftApConfiguration implements Parcelable {
+ @VisibleForTesting
+ static final int PSK_MIN_LEN = 8;
+ @VisibleForTesting
+ static final int PSK_MAX_LEN = 63;
+
/**
* 2GHz band.
* @hide
@@ -142,9 +148,10 @@
private final @Nullable MacAddress mBssid;
/**
- * Pre-shared key for WPA2-PSK encryption (non-null enables WPA2-PSK).
+ * Pre-shared key for WPA2-PSK or WPA3-SAE-Transition or WPA3-SAE encryption which depends on
+ * the security type.
*/
- private final @Nullable String mWpa2Passphrase;
+ private final @Nullable String mPassphrase;
/**
* This is a network that does not broadcast its SSID, so an
@@ -186,20 +193,30 @@
public static final int SECURITY_TYPE_WPA2_PSK = 1;
/** @hide */
+ @SystemApi
+ public static final int SECURITY_TYPE_WPA3_SAE_TRANSITION = 2;
+
+ /** @hide */
+ @SystemApi
+ public static final int SECURITY_TYPE_WPA3_SAE = 3;
+
+ /** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = { "SECURITY_TYPE" }, value = {
+ @IntDef(prefix = { "SECURITY_TYPE_" }, value = {
SECURITY_TYPE_OPEN,
SECURITY_TYPE_WPA2_PSK,
+ SECURITY_TYPE_WPA3_SAE_TRANSITION,
+ SECURITY_TYPE_WPA3_SAE,
})
public @interface SecurityType {}
/** Private constructor for Builder and Parcelable implementation. */
private SoftApConfiguration(@Nullable String ssid, @Nullable MacAddress bssid,
- @Nullable String wpa2Passphrase, boolean hiddenSsid, @BandType int band, int channel,
+ @Nullable String passphrase, boolean hiddenSsid, @BandType int band, int channel,
@SecurityType int securityType, int maxNumberOfClients) {
mSsid = ssid;
mBssid = bssid;
- mWpa2Passphrase = wpa2Passphrase;
+ mPassphrase = passphrase;
mHiddenSsid = hiddenSsid;
mBand = band;
mChannel = channel;
@@ -218,7 +235,7 @@
SoftApConfiguration other = (SoftApConfiguration) otherObj;
return Objects.equals(mSsid, other.mSsid)
&& Objects.equals(mBssid, other.mBssid)
- && Objects.equals(mWpa2Passphrase, other.mWpa2Passphrase)
+ && Objects.equals(mPassphrase, other.mPassphrase)
&& mHiddenSsid == other.mHiddenSsid
&& mBand == other.mBand
&& mChannel == other.mChannel
@@ -228,7 +245,7 @@
@Override
public int hashCode() {
- return Objects.hash(mSsid, mBssid, mWpa2Passphrase, mHiddenSsid,
+ return Objects.hash(mSsid, mBssid, mPassphrase, mHiddenSsid,
mBand, mChannel, mSecurityType, mMaxNumberOfClients);
}
@@ -237,8 +254,8 @@
StringBuilder sbuf = new StringBuilder();
sbuf.append("ssid=").append(mSsid);
if (mBssid != null) sbuf.append(" \n bssid=").append(mBssid.toString());
- sbuf.append(" \n Wpa2Passphrase =").append(
- TextUtils.isEmpty(mWpa2Passphrase) ? "<empty>" : "<non-empty>");
+ sbuf.append(" \n Passphrase =").append(
+ TextUtils.isEmpty(mPassphrase) ? "<empty>" : "<non-empty>");
sbuf.append(" \n HiddenSsid =").append(mHiddenSsid);
sbuf.append(" \n Band =").append(mBand);
sbuf.append(" \n Channel =").append(mChannel);
@@ -251,7 +268,7 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mSsid);
dest.writeParcelable(mBssid, flags);
- dest.writeString(mWpa2Passphrase);
+ dest.writeString(mPassphrase);
dest.writeBoolean(mHiddenSsid);
dest.writeInt(mBand);
dest.writeInt(mChannel);
@@ -299,13 +316,26 @@
return mBssid;
}
+ // TODO: Remove it after update the caller
/**
* Returns String set to be passphrase for the WPA2-PSK AP.
- * {@link Builder#setWpa2Passphrase(String)}.
+ * {@link #setWpa2Passphrase(String)}.
*/
@Nullable
public String getWpa2Passphrase() {
- return mWpa2Passphrase;
+ if (mSecurityType == SECURITY_TYPE_WPA2_PSK) {
+ return mPassphrase;
+ }
+ return null;
+ }
+
+ /**
+ * Returns String set to be passphrase for current AP.
+ * {@link #setPassphrase(String, @SecurityType int)}.
+ */
+ @Nullable
+ public String getPassphrase() {
+ return mPassphrase;
}
/**
@@ -360,23 +390,12 @@
public static final class Builder {
private String mSsid;
private MacAddress mBssid;
- private String mWpa2Passphrase;
+ private String mPassphrase;
private boolean mHiddenSsid;
private int mBand;
private int mChannel;
private int mMaxNumberOfClients;
-
- private int setSecurityType() {
- int securityType = SECURITY_TYPE_OPEN;
- if (!TextUtils.isEmpty(mWpa2Passphrase)) { // WPA2-PSK network.
- securityType = SECURITY_TYPE_WPA2_PSK;
- }
- return securityType;
- }
-
- private void clearAllPassphrase() {
- mWpa2Passphrase = null;
- }
+ private int mSecurityType;
/**
* Constructs a Builder with default values (see {@link Builder}).
@@ -384,11 +403,12 @@
public Builder() {
mSsid = null;
mBssid = null;
- mWpa2Passphrase = null;
+ mPassphrase = null;
mHiddenSsid = false;
mBand = BAND_2GHZ;
mChannel = 0;
mMaxNumberOfClients = 0;
+ mSecurityType = SECURITY_TYPE_OPEN;
}
/**
@@ -399,11 +419,12 @@
mSsid = other.mSsid;
mBssid = other.mBssid;
- mWpa2Passphrase = other.mWpa2Passphrase;
+ mPassphrase = other.mPassphrase;
mHiddenSsid = other.mHiddenSsid;
mBand = other.mBand;
mChannel = other.mChannel;
mMaxNumberOfClients = other.mMaxNumberOfClients;
+ mSecurityType = other.mSecurityType;
}
/**
@@ -413,8 +434,8 @@
*/
@NonNull
public SoftApConfiguration build() {
- return new SoftApConfiguration(mSsid, mBssid, mWpa2Passphrase,
- mHiddenSsid, mBand, mChannel, setSecurityType(), mMaxNumberOfClients);
+ return new SoftApConfiguration(mSsid, mBssid, mPassphrase,
+ mHiddenSsid, mBand, mChannel, mSecurityType, mMaxNumberOfClients);
}
/**
@@ -461,6 +482,7 @@
return this;
}
+ // TODO: Remove it after update the caller
/**
* Specifies that this AP should use WPA2-PSK with the given ASCII WPA2 passphrase.
* When set to null, an open network is created.
@@ -473,15 +495,47 @@
*/
@NonNull
public Builder setWpa2Passphrase(@Nullable String passphrase) {
- if (passphrase != null) {
+ return setPassphrase(passphrase, SECURITY_TYPE_WPA2_PSK);
+ }
+
+ /**
+ * Specifies that this AP should use specific security type with the given ASCII passphrase.
+ *
+ * @param securityType one of the security types from {@link @SecurityType}.
+ * @param passphrase The passphrase to use for sepcific {@link @SecurityType} configuration
+ * or null with {@link @SecurityType#SECURITY_TYPE_OPEN}.
+ *
+ * @return Builder for chaining.
+ * @throws IllegalArgumentException when the passphrase length is invalid and
+ * {@code securityType} is not {@link @SecurityType#SECURITY_TYPE_OPEN}
+ * or non-null passphrase and {@code securityType} is
+ * {@link @SecurityType#SECURITY_TYPE_OPEN}.
+ */
+ @NonNull
+ public Builder setPassphrase(@Nullable String passphrase, @SecurityType int securityType) {
+ if (securityType == SECURITY_TYPE_OPEN) {
+ if (passphrase != null) {
+ throw new IllegalArgumentException(
+ "passphrase should be null when security type is open");
+ }
+ } else {
+ Preconditions.checkStringNotEmpty(passphrase);
final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder();
if (!asciiEncoder.canEncode(passphrase)) {
throw new IllegalArgumentException("passphrase not ASCII encodable");
}
- Preconditions.checkStringNotEmpty(passphrase);
+ if (securityType == SECURITY_TYPE_WPA2_PSK
+ || securityType == SECURITY_TYPE_WPA3_SAE_TRANSITION) {
+ if (passphrase.length() < PSK_MIN_LEN || passphrase.length() > PSK_MAX_LEN) {
+ throw new IllegalArgumentException(
+ "Password size must be at least " + PSK_MIN_LEN
+ + " and no more than " + PSK_MAX_LEN
+ + " for WPA2_PSK and WPA3_SAE_TRANSITION Mode");
+ }
+ }
}
- clearAllPassphrase();
- mWpa2Passphrase = passphrase;
+ mSecurityType = securityType;
+ mPassphrase = passphrase;
return this;
}
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index 148e8a4..41f7c6e 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -1336,20 +1336,18 @@
}
/**
- * If the current authentication method needs SIM card.
- * @return true if the credential information require SIM card for current authentication
+ * Utility method to determine whether the configuration's authentication method is SIM-based.
+ *
+ * @return true if the credential information requires SIM card for current authentication
* method, otherwise it returns false.
- * @hide
*/
- public boolean requireSimCredential() {
+ public boolean isAuthenticationSimBased() {
if (mEapMethod == Eap.SIM || mEapMethod == Eap.AKA || mEapMethod == Eap.AKA_PRIME) {
return true;
}
if (mEapMethod == Eap.PEAP) {
- if (mPhase2Method == Phase2.SIM || mPhase2Method == Phase2.AKA
- || mPhase2Method == Phase2.AKA_PRIME) {
- return true;
- }
+ return mPhase2Method == Phase2.SIM || mPhase2Method == Phase2.AKA
+ || mPhase2Method == Phase2.AKA_PRIME;
}
return false;
}
diff --git a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
index 1f60103..acd3343 100644
--- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
@@ -25,8 +25,12 @@
import org.junit.Test;
+import java.util.Random;
+
@SmallTest
public class SoftApConfigurationTest {
+ private static final String TEST_CHAR_SET_AS_STRING = "abcdefghijklmnopqrstuvwxyz0123456789";
+
private SoftApConfiguration parcelUnparcel(SoftApConfiguration configIn) {
Parcel parcel = Parcel.obtain();
parcel.writeParcelable(configIn, 0);
@@ -37,6 +41,25 @@
return configOut;
}
+ /**
+ * Helper method to generate random string.
+ *
+ * Note: this method has limited use as a random string generator.
+ * The characters used in this method do no not cover all valid inputs.
+ * @param length number of characters to generate for the string
+ * @return String generated string of random characters
+ */
+ private String generateRandomString(int length) {
+ Random random = new Random();
+ StringBuilder stringBuilder = new StringBuilder(length);
+ int index = -1;
+ while (stringBuilder.length() < length) {
+ index = random.nextInt(TEST_CHAR_SET_AS_STRING.length());
+ stringBuilder.append(TEST_CHAR_SET_AS_STRING.charAt(index));
+ }
+ return stringBuilder.toString();
+ }
+
@Test
public void testBasicSettings() {
SoftApConfiguration original = new SoftApConfiguration.Builder()
@@ -45,7 +68,7 @@
.build();
assertThat(original.getSsid()).isEqualTo("ssid");
assertThat(original.getBssid()).isEqualTo(MacAddress.fromString("11:22:33:44:55:66"));
- assertThat(original.getWpa2Passphrase()).isNull();
+ assertThat(original.getPassphrase()).isNull();
assertThat(original.getSecurityType()).isEqualTo(SoftApConfiguration.SECURITY_TYPE_OPEN);
assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_2GHZ);
assertThat(original.getChannel()).isEqualTo(0);
@@ -66,9 +89,9 @@
@Test
public void testWpa2() {
SoftApConfiguration original = new SoftApConfiguration.Builder()
- .setWpa2Passphrase("secretsecret")
+ .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
.build();
- assertThat(original.getWpa2Passphrase()).isEqualTo("secretsecret");
+ assertThat(original.getPassphrase()).isEqualTo("secretsecret");
assertThat(original.getSecurityType()).isEqualTo(
SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_2GHZ);
@@ -90,13 +113,12 @@
@Test
public void testWpa2WithAllFieldCustomized() {
SoftApConfiguration original = new SoftApConfiguration.Builder()
- .setWpa2Passphrase("secretsecret")
- .setBand(SoftApConfiguration.BAND_ANY)
+ .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
.setChannel(149, SoftApConfiguration.BAND_5GHZ)
.setHiddenSsid(true)
.setMaxNumberOfClients(10)
.build();
- assertThat(original.getWpa2Passphrase()).isEqualTo("secretsecret");
+ assertThat(original.getPassphrase()).isEqualTo("secretsecret");
assertThat(original.getSecurityType()).isEqualTo(
SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_5GHZ);
@@ -114,4 +136,98 @@
assertThat(copy).isEqualTo(original);
assertThat(copy.hashCode()).isEqualTo(original.hashCode());
}
+
+ @Test
+ public void testWpa3Sae() {
+ SoftApConfiguration original = new SoftApConfiguration.Builder()
+ .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA3_SAE)
+ .setChannel(149, SoftApConfiguration.BAND_5GHZ)
+ .setHiddenSsid(true)
+ .build();
+ assertThat(original.getPassphrase()).isEqualTo("secretsecret");
+ assertThat(original.getSecurityType()).isEqualTo(
+ SoftApConfiguration.SECURITY_TYPE_WPA3_SAE);
+ assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_5GHZ);
+ assertThat(original.getChannel()).isEqualTo(149);
+ assertThat(original.isHiddenSsid()).isEqualTo(true);
+
+
+ SoftApConfiguration unparceled = parcelUnparcel(original);
+ assertThat(unparceled).isNotSameAs(original);
+ assertThat(unparceled).isEqualTo(original);
+ assertThat(unparceled.hashCode()).isEqualTo(original.hashCode());
+
+ SoftApConfiguration copy = new SoftApConfiguration.Builder(original).build();
+ assertThat(copy).isNotSameAs(original);
+ assertThat(copy).isEqualTo(original);
+ assertThat(copy.hashCode()).isEqualTo(original.hashCode());
+ }
+
+ @Test
+ public void testWpa3SaeTransition() {
+ SoftApConfiguration original = new SoftApConfiguration.Builder()
+ .setPassphrase("secretsecret",
+ SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION)
+ .setChannel(149, SoftApConfiguration.BAND_5GHZ)
+ .setHiddenSsid(true)
+ .build();
+ assertThat(original.getSecurityType()).isEqualTo(
+ SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION);
+ assertThat(original.getPassphrase()).isEqualTo("secretsecret");
+ assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_5GHZ);
+ assertThat(original.getChannel()).isEqualTo(149);
+ assertThat(original.isHiddenSsid()).isEqualTo(true);
+
+
+ SoftApConfiguration unparceled = parcelUnparcel(original);
+ assertThat(unparceled).isNotSameAs(original);
+ assertThat(unparceled).isEqualTo(original);
+ assertThat(unparceled.hashCode()).isEqualTo(original.hashCode());
+
+ SoftApConfiguration copy = new SoftApConfiguration.Builder(original).build();
+ assertThat(copy).isNotSameAs(original);
+ assertThat(copy).isEqualTo(original);
+ assertThat(copy.hashCode()).isEqualTo(original.hashCode());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidShortPasswordLengthForWpa2() {
+ SoftApConfiguration original = new SoftApConfiguration.Builder()
+ .setPassphrase(generateRandomString(SoftApConfiguration.PSK_MIN_LEN - 1),
+ SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+ .setChannel(149, SoftApConfiguration.BAND_5GHZ)
+ .setHiddenSsid(true)
+ .build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidLongPasswordLengthForWpa2() {
+ SoftApConfiguration original = new SoftApConfiguration.Builder()
+ .setPassphrase(generateRandomString(SoftApConfiguration.PSK_MAX_LEN + 1),
+ SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+ .setChannel(149, SoftApConfiguration.BAND_5GHZ)
+ .setHiddenSsid(true)
+ .build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidShortPasswordLengthForWpa3SaeTransition() {
+ SoftApConfiguration original = new SoftApConfiguration.Builder()
+ .setPassphrase(generateRandomString(SoftApConfiguration.PSK_MIN_LEN - 1),
+ SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION)
+ .setChannel(149, SoftApConfiguration.BAND_5GHZ)
+ .setHiddenSsid(true)
+ .build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidLongPasswordLengthForWpa3SaeTransition() {
+ SoftApConfiguration original = new SoftApConfiguration.Builder()
+ .setPassphrase(generateRandomString(SoftApConfiguration.PSK_MAX_LEN + 1),
+ SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION)
+ .setChannel(149, SoftApConfiguration.BAND_5GHZ)
+ .setHiddenSsid(true)
+ .build();
+ }
+
}