Merge "[Magnifier-57] Add API to set overlay"
diff --git a/Android.bp b/Android.bp
index abeeb43..1012bb81 100644
--- a/Android.bp
+++ b/Android.bp
@@ -536,6 +536,7 @@
"telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl",
"telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl",
"telephony/java/android/telephony/mbms/vendor/IMbmsGroupCallService.aidl",
+ "telephony/java/android/telephony/ICellInfoCallback.aidl",
"telephony/java/android/telephony/INetworkService.aidl",
"telephony/java/android/telephony/INetworkServiceCallback.aidl",
"telephony/java/com/android/ims/internal/IImsCallSession.aidl",
@@ -1700,3 +1701,20 @@
"core/java/android/annotation/NonNull.java",
],
}
+
+filegroup {
+ name: "framework-media-annotation-srcs",
+ srcs: [
+ "core/java/android/annotation/CallbackExecutor.java",
+ "core/java/android/annotation/DrawableRes.java",
+ "core/java/android/annotation/IntDef.java",
+ "core/java/android/annotation/LongDef.java",
+ "core/java/android/annotation/NonNull.java",
+ "core/java/android/annotation/Nullable.java",
+ "core/java/android/annotation/RequiresPermission.java",
+ "core/java/android/annotation/SdkConstant.java",
+ "core/java/android/annotation/StringDef.java",
+ "core/java/android/annotation/UnsupportedAppUsage.java",
+ ],
+}
+
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
index 35d3802..f60cbee 100644
--- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
@@ -169,6 +169,25 @@
}
@Test
+ public void testCreate_PrecomputedText_NoStyled_Greedy_NoHyphenation_DirDifferent() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ final PrecomputedText text = makeMeasured(
+ mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT,
+ Layout.BREAK_STRATEGY_SIMPLE, Layout.HYPHENATION_FREQUENCY_NONE);
+ Canvas.freeTextLayoutCaches();
+ state.resumeTiming();
+
+ StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
+ .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+ .setTextDirection(TextDirectionHeuristics.RTL)
+ .build();
+ }
+ }
+
+ @Test
public void testCreate_PrecomputedText_NoStyled_Greedy_Hyphenation() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
diff --git a/api/current.txt b/api/current.txt
index d083a31..eb1849f 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -142,6 +142,7 @@
field public static final java.lang.String SET_WALLPAPER = "android.permission.SET_WALLPAPER";
field public static final java.lang.String SET_WALLPAPER_HINTS = "android.permission.SET_WALLPAPER_HINTS";
field public static final java.lang.String SIGNAL_PERSISTENT_PROCESSES = "android.permission.SIGNAL_PERSISTENT_PROCESSES";
+ field public static final java.lang.String SMS_FINANCIAL_TRANSACTIONS = "android.permission.SMS_FINANCIAL_TRANSACTIONS";
field public static final java.lang.String STATUS_BAR = "android.permission.STATUS_BAR";
field public static final java.lang.String SYSTEM_ALERT_WINDOW = "android.permission.SYSTEM_ALERT_WINDOW";
field public static final java.lang.String TRANSMIT_IR = "android.permission.TRANSMIT_IR";
@@ -4226,8 +4227,10 @@
public class AppComponentFactory {
ctor public AppComponentFactory();
+ method public android.content.pm.ApplicationInfo getApplicationInfo();
method public android.app.Activity instantiateActivity(java.lang.ClassLoader, java.lang.String, android.content.Intent) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
method public android.app.Application instantiateApplication(java.lang.ClassLoader, java.lang.String) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public java.lang.ClassLoader instantiateClassLoader(java.lang.ClassLoader);
method public android.content.ContentProvider instantiateProvider(java.lang.ClassLoader, java.lang.String) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
method public android.content.BroadcastReceiver instantiateReceiver(java.lang.ClassLoader, java.lang.String, android.content.Intent) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
method public android.app.Service instantiateService(java.lang.ClassLoader, java.lang.String, android.content.Intent) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
@@ -5370,6 +5373,7 @@
field public static final android.os.Parcelable.Creator<android.app.Notification.Action> CREATOR;
field public static final int SEMANTIC_ACTION_ARCHIVE = 5; // 0x5
field public static final int SEMANTIC_ACTION_CALL = 10; // 0xa
+ field public static final int SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION = 11; // 0xb
field public static final int SEMANTIC_ACTION_DELETE = 4; // 0x4
field public static final int SEMANTIC_ACTION_MARK_AS_READ = 2; // 0x2
field public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3; // 0x3
@@ -9475,6 +9479,7 @@
public abstract class Context {
ctor public Context();
+ method public abstract boolean bindIsolatedService(android.content.Intent, android.content.ServiceConnection, int, java.lang.String);
method public abstract boolean bindService(android.content.Intent, android.content.ServiceConnection, int);
method public abstract int checkCallingOrSelfPermission(java.lang.String);
method public abstract int checkCallingOrSelfUriPermission(android.net.Uri, int);
@@ -9686,6 +9691,7 @@
public class ContextWrapper extends android.content.Context {
ctor public ContextWrapper(android.content.Context);
method protected void attachBaseContext(android.content.Context);
+ method public boolean bindIsolatedService(android.content.Intent, android.content.ServiceConnection, int, java.lang.String);
method public boolean bindService(android.content.Intent, android.content.ServiceConnection, int);
method public int checkCallingOrSelfPermission(java.lang.String);
method public int checkCallingOrSelfUriPermission(android.net.Uri, int);
@@ -10179,6 +10185,7 @@
field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER";
field public static final java.lang.String EXTRA_COMPONENT_NAME = "android.intent.extra.COMPONENT_NAME";
field public static final java.lang.String EXTRA_CONTENT_ANNOTATIONS = "android.intent.extra.CONTENT_ANNOTATIONS";
+ field public static final java.lang.String EXTRA_CONTENT_QUERY = "android.intent.extra.CONTENT_QUERY";
field public static final java.lang.String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED";
field public static final java.lang.String EXTRA_DOCK_STATE = "android.intent.extra.DOCK_STATE";
field public static final int EXTRA_DOCK_STATE_CAR = 2; // 0x2
@@ -11134,6 +11141,7 @@
field public int[] gids;
field public int installLocation;
field public android.content.pm.InstrumentationInfo[] instrumentation;
+ field public boolean isApex;
field public long lastUpdateTime;
field public java.lang.String packageName;
field public android.content.pm.PermissionInfo[] permissions;
@@ -11525,6 +11533,7 @@
field public static final int INSTALL_REASON_UNKNOWN = 0; // 0x0
field public static final int INSTALL_REASON_USER = 4; // 0x4
field public static final int MATCH_ALL = 131072; // 0x20000
+ field public static final int MATCH_APEX = 1073741824; // 0x40000000
field public static final int MATCH_DEFAULT_ONLY = 65536; // 0x10000
field public static final int MATCH_DIRECT_BOOT_AUTO = 268435456; // 0x10000000
field public static final int MATCH_DIRECT_BOOT_AWARE = 524288; // 0x80000
@@ -23248,6 +23257,7 @@
method public android.media.VolumeShaper createVolumeShaper(android.media.VolumeShaper.Configuration);
method protected void finalize();
method public void flush();
+ method public android.media.AudioAttributes getAudioAttributes();
method public int getAudioFormat();
method public int getAudioSessionId();
method public int getBufferCapacityInFrames();
@@ -24462,6 +24472,7 @@
}
public static final class MediaExtractor.CasInfo {
+ method public byte[] getPrivateData();
method public android.media.MediaCas.Session getSession();
method public int getSystemId();
}
@@ -28978,7 +28989,7 @@
public class WifiManager {
method public deprecated int addNetwork(android.net.wifi.WifiConfiguration);
- method public boolean addNetworkSuggestions(java.util.List<android.net.wifi.WifiNetworkSuggestion>, android.app.PendingIntent);
+ method public boolean addNetworkSuggestions(java.util.List<android.net.wifi.WifiNetworkSuggestion>);
method public void addOrUpdatePasspointConfiguration(android.net.wifi.hotspot2.PasspointConfiguration);
method public static int calculateSignalLevel(int, int);
method public deprecated void cancelWps(android.net.wifi.WifiManager.WpsCallback);
@@ -28998,11 +29009,14 @@
method public boolean is5GHzBandSupported();
method public boolean isDeviceToApRttSupported();
method public boolean isEnhancedPowerReportingSupported();
+ method public boolean isOweSupported();
method public boolean isP2pSupported();
method public boolean isPreferredNetworkOffloadSupported();
method public deprecated boolean isScanAlwaysAvailable();
method public boolean isTdlsSupported();
method public boolean isWifiEnabled();
+ method public boolean isWpa3SaeSupported();
+ method public boolean isWpa3SuiteBSupported();
method public deprecated boolean pingSupplicant();
method public deprecated boolean reassociate();
method public deprecated boolean reconnect();
@@ -29019,9 +29033,11 @@
method public deprecated int updateNetwork(android.net.wifi.WifiConfiguration);
field public static final java.lang.String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK";
field public static final java.lang.String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE";
+ field public static final java.lang.String ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION = "android.net.wifi.action.WIFI_NETWORK_SUGGESTION_POST_CONNECTION";
field public static final deprecated int ERROR_AUTHENTICATING = 1; // 0x1
field public static final deprecated java.lang.String EXTRA_BSSID = "bssid";
field public static final java.lang.String EXTRA_NETWORK_INFO = "networkInfo";
+ field public static final java.lang.String EXTRA_NETWORK_SUGGESTION = "android.net.wifi.extra.NETWORK_SUGGESTION";
field public static final java.lang.String EXTRA_NEW_RSSI = "newRssi";
field public static final deprecated java.lang.String EXTRA_NEW_STATE = "newState";
field public static final java.lang.String EXTRA_PREVIOUS_WIFI_STATE = "previous_wifi_state";
@@ -43324,6 +43340,7 @@
public class PhoneStateListener {
ctor public PhoneStateListener();
+ ctor public PhoneStateListener(java.util.concurrent.Executor);
method public void onCallForwardingIndicatorChanged(boolean);
method public void onCallStateChanged(int, java.lang.String);
method public void onCellInfoChanged(java.util.List<android.telephony.CellInfo>);
@@ -43549,6 +43566,7 @@
method public java.lang.String getCountryIso();
method public int getDataRoaming();
method public java.lang.CharSequence getDisplayName();
+ method public java.lang.String getGroupUuid();
method public java.lang.String getIccId();
method public int getIconTint();
method public deprecated int getMcc();
@@ -43556,7 +43574,6 @@
method public deprecated int getMnc();
method public java.lang.String getMncString();
method public java.lang.String getNumber();
- method public int getParentSubId();
method public int getSimSlotIndex();
method public int getSubscriptionId();
method public boolean isEmbedded();
@@ -43590,6 +43607,7 @@
method public static boolean isValidSubscriptionId(int);
method public void removeOnOpportunisticSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener);
method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
+ method public java.lang.String setSubscriptionGroup(int[]);
method public void setSubscriptionOverrideCongested(int, boolean, long);
method public void setSubscriptionOverrideUnmetered(int, boolean, long);
method public void setSubscriptionPlans(int, java.util.List<android.telephony.SubscriptionPlan>);
@@ -43721,6 +43739,7 @@
method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle);
method public boolean isWorldPhone();
method public void listen(android.telephony.PhoneStateListener, int);
+ method public void requestCellInfoUpdate(java.util.concurrent.Executor, android.telephony.TelephonyManager.CellInfoCallback);
method public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback);
method public void sendDialerSpecialCode(java.lang.String);
method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
@@ -43823,6 +43842,11 @@
field public static final java.lang.String VVM_TYPE_OMTP = "vvm_type_omtp";
}
+ public static abstract class TelephonyManager.CellInfoCallback {
+ ctor public TelephonyManager.CellInfoCallback();
+ method public abstract void onCellInfo(java.util.List<android.telephony.CellInfo>);
+ }
+
public static abstract class TelephonyManager.UssdResponseCallback {
ctor public TelephonyManager.UssdResponseCallback();
method public void onReceiveUssdResponse(android.telephony.TelephonyManager, java.lang.String, java.lang.CharSequence);
@@ -43911,6 +43935,7 @@
method public java.lang.String getApnName();
method public int getApnTypeBitmask();
method public int getAuthType();
+ method public int getCarrierId();
method public java.lang.String getEntryName();
method public int getId();
method public deprecated java.net.InetAddress getMmsProxyAddress();
@@ -43961,6 +43986,7 @@
method public android.telephony.data.ApnSetting.Builder setApnTypeBitmask(int);
method public android.telephony.data.ApnSetting.Builder setAuthType(int);
method public android.telephony.data.ApnSetting.Builder setCarrierEnabled(boolean);
+ method public android.telephony.data.ApnSetting.Builder setCarrierId(int);
method public android.telephony.data.ApnSetting.Builder setEntryName(java.lang.String);
method public deprecated android.telephony.data.ApnSetting.Builder setMmsProxyAddress(java.net.InetAddress);
method public android.telephony.data.ApnSetting.Builder setMmsProxyAddress(java.lang.String);
@@ -47054,6 +47080,7 @@
public class TimeUtils {
method public static java.util.TimeZone getTimeZone(int, boolean, long, java.lang.String);
method public static java.lang.String getTimeZoneDatabaseVersion();
+ method public static java.util.List<java.lang.String> getTimeZoneIdsForCountryCode(java.lang.String);
}
public class TimingLogger {
@@ -51801,13 +51828,13 @@
package android.view.intelligence {
public final class IntelligenceManager {
- method public void disableContentCapture();
method public android.content.ComponentName getIntelligenceServiceComponentName();
method public boolean isContentCaptureEnabled();
method public android.view.ViewStructure newVirtualViewStructure(android.view.autofill.AutofillId, int);
method public void notifyViewAppeared(android.view.ViewStructure);
method public void notifyViewDisappeared(android.view.autofill.AutofillId);
method public void notifyViewTextChanged(android.view.autofill.AutofillId, java.lang.CharSequence, int);
+ method public void setContentCaptureEnabled(boolean);
field public static final int FLAG_USER_INPUT = 1; // 0x1
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 5c4efcd..d7265c7 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -58,6 +58,7 @@
field public static final java.lang.String CONNECTIVITY_USE_RESTRICTED_NETWORKS = "android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS";
field public static final java.lang.String CONTROL_DISPLAY_SATURATION = "android.permission.CONTROL_DISPLAY_SATURATION";
field public static final java.lang.String CONTROL_INCALL_EXPERIENCE = "android.permission.CONTROL_INCALL_EXPERIENCE";
+ field public static final java.lang.String CONTROL_KEYGUARD_SECURE_NOTIFICATIONS = "android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS";
field public static final java.lang.String CONTROL_LOCATION_UPDATES = "android.permission.CONTROL_LOCATION_UPDATES";
field public static final java.lang.String CONTROL_VPN = "android.permission.CONTROL_VPN";
field public static final java.lang.String CRYPT_KEEPER = "android.permission.CRYPT_KEEPER";
@@ -123,6 +124,7 @@
field public static final java.lang.String MOUNT_FORMAT_FILESYSTEMS = "android.permission.MOUNT_FORMAT_FILESYSTEMS";
field public static final java.lang.String MOUNT_UNMOUNT_FILESYSTEMS = "android.permission.MOUNT_UNMOUNT_FILESYSTEMS";
field public static final java.lang.String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE";
+ field public static final java.lang.String NETWORK_MANAGED_PROVISIONING = "android.permission.NETWORK_MANAGED_PROVISIONING";
field public static final java.lang.String NETWORK_SETUP_WIZARD = "android.permission.NETWORK_SETUP_WIZARD";
field public static final java.lang.String NOTIFICATION_DURING_SETUP = "android.permission.NOTIFICATION_DURING_SETUP";
field public static final java.lang.String NOTIFY_TV_INPUTS = "android.permission.NOTIFY_TV_INPUTS";
@@ -137,6 +139,7 @@
field public static final java.lang.String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE";
field public static final java.lang.String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT";
field public static final java.lang.String QUERY_TIME_ZONE_RULES = "android.permission.QUERY_TIME_ZONE_RULES";
+ field public static final java.lang.String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
field public static final java.lang.String READ_CONTENT_RATING_SYSTEMS = "android.permission.READ_CONTENT_RATING_SYSTEMS";
field public static final java.lang.String READ_DREAM_STATE = "android.permission.READ_DREAM_STATE";
field public static final java.lang.String READ_INSTALL_SESSIONS = "android.permission.READ_INSTALL_SESSIONS";
@@ -437,6 +440,8 @@
}
public class KeyguardManager {
+ method public void setPrivateNotificationsAllowed(boolean);
+ method public boolean getPrivateNotificationsAllowed();
method public android.content.Intent createConfirmFactoryResetCredentialIntent(java.lang.CharSequence, java.lang.CharSequence, java.lang.CharSequence);
method public void requestDismissKeyguard(android.app.Activity, java.lang.CharSequence, android.app.KeyguardManager.KeyguardDismissCallback);
}
@@ -1205,6 +1210,7 @@
public abstract class PackageManager {
method public abstract void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
method public abstract boolean arePermissionsIndividuallyControlled();
+ method public boolean canSuspendPackage(java.lang.String);
method public abstract java.util.List<android.content.IntentFilter> getAllIntentFilters(java.lang.String);
method public android.content.pm.dex.ArtManager getArtManager();
method public abstract java.lang.String getDefaultBrowserPackageNameAsUser(int);
@@ -3639,17 +3645,16 @@
public class WifiManager {
method public void connect(android.net.wifi.WifiConfiguration, android.net.wifi.WifiManager.ActionListener);
+ method public java.util.List<android.net.wifi.WifiConfiguration> getAllMatchingWifiConfigs(java.util.List<android.net.wifi.ScanResult>);
+ method public java.util.List<android.net.wifi.hotspot2.OsuProvider> getMatchingOsuProviders(java.util.List<android.net.wifi.ScanResult>);
method public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks();
method public android.net.wifi.WifiConfiguration getWifiApConfiguration();
method public int getWifiApState();
method public boolean isDeviceToApRttSupported();
method public boolean isDeviceToDeviceRttSupported();
- method public boolean isOweSupported();
method public boolean isPortableHotspotSupported();
method public boolean isWifiApEnabled();
method public boolean isWifiScannerSupported();
- method public boolean isWpa3SaeSupported();
- method public boolean isWpa3SuiteBSupported();
method public void registerNetworkRequestMatchCallback(android.net.wifi.WifiManager.NetworkRequestMatchCallback, android.os.Handler);
method public boolean setWifiApConfiguration(android.net.wifi.WifiConfiguration);
method public boolean startScan(android.os.WorkSource);
@@ -4109,6 +4114,7 @@
}
public final class PowerManager {
+ method public void dream(long);
method public int getPowerSaveMode();
method public boolean setDynamicPowerSavings(boolean, int);
method public boolean setPowerSaveMode(boolean);
@@ -4545,10 +4551,6 @@
public final class Settings {
field public static final java.lang.String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
field public static final java.lang.String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS";
- field public static final int USER_SETUP_PERSONALIZATION_COMPLETE = 10; // 0xa
- field public static final int USER_SETUP_PERSONALIZATION_NOT_STARTED = 0; // 0x0
- field public static final int USER_SETUP_PERSONALIZATION_PAUSED = 2; // 0x2
- field public static final int USER_SETUP_PERSONALIZATION_STARTED = 1; // 0x1
}
public static final class Settings.Global extends android.provider.Settings.NameValueTable {
@@ -4592,6 +4594,10 @@
field public static final java.lang.String LOCK_SCREEN_SHOW_NOTIFICATIONS = "lock_screen_show_notifications";
field public static final java.lang.String MANUAL_RINGER_TOGGLE_COUNT = "manual_ringer_toggle_count";
field public static final java.lang.String USER_SETUP_COMPLETE = "user_setup_complete";
+ field public static final int USER_SETUP_PERSONALIZATION_COMPLETE = 10; // 0xa
+ field public static final int USER_SETUP_PERSONALIZATION_NOT_STARTED = 0; // 0x0
+ field public static final int USER_SETUP_PERSONALIZATION_PAUSED = 2; // 0x2
+ field public static final int USER_SETUP_PERSONALIZATION_STARTED = 1; // 0x1
field public static final java.lang.String USER_SETUP_PERSONALIZATION_STATE = "user_setup_personalization_state";
field public static final java.lang.String VOLUME_HUSH_GESTURE = "volume_hush_gesture";
}
@@ -4653,6 +4659,11 @@
field public static final int ID_TYPE_SERIAL = 1; // 0x1
}
+ public class DeviceIdAttestationException extends java.lang.Exception {
+ ctor public DeviceIdAttestationException(java.lang.String);
+ ctor public DeviceIdAttestationException(java.lang.String, java.lang.Throwable);
+ }
+
}
package android.security.keystore.recovery {
@@ -4783,6 +4794,16 @@
}
+package android.service.carrier {
+
+ public abstract class ApnService extends android.app.Service {
+ ctor public ApnService();
+ method public abstract java.util.List<android.content.ContentValues> onRestoreApns(int);
+ method public android.os.IBinder onBind(android.content.Intent);
+ }
+
+}
+
package android.service.euicc {
public final class EuiccProfileInfo implements android.os.Parcelable {
@@ -4850,6 +4871,7 @@
method public abstract void onStartOtaIfNecessary(int, android.service.euicc.EuiccService.OtaStatusChangedCallback);
method public abstract int onSwitchToSubscription(int, java.lang.String, boolean);
method public abstract int onUpdateSubscriptionNickname(int, java.lang.String, java.lang.String);
+ field public static final java.lang.String ACTION_BIND_CARRIER_PROVISIONING_SERVICE = "android.service.euicc.action.BIND_CARRIER_PROVISIONING_SERVICE";
field public static final java.lang.String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
field public static final java.lang.String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
field public static final java.lang.String ACTION_RESOLVE_CONFIRMATION_CODE = "android.service.euicc.action.RESOLVE_CONFIRMATION_CODE";
@@ -5461,6 +5483,7 @@
method public static android.os.PersistableBundle getDefaultConfig();
method public void overrideConfig(int, android.os.PersistableBundle);
method public void updateConfigForPhoneId(int, java.lang.String);
+ field public static final java.lang.String KEY_CARRIER_SETUP_APP_STRING = "carrier_setup_app_string";
field public static final java.lang.String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING = "config_plans_package_override_string";
}
@@ -5535,8 +5558,10 @@
public class PhoneStateListener {
method public void onRadioPowerStateChanged(int);
method public void onSrvccStateChanged(int);
+ method public void onVoiceActivationStateChanged(int);
field public static final int LISTEN_RADIO_POWER_STATE_CHANGED = 8388608; // 0x800000
field public static final int LISTEN_SRVCC_STATE_CHANGED = 16384; // 0x4000
+ field public static final int LISTEN_VOICE_ACTIVATION_STATE = 131072; // 0x20000
}
public class ServiceState implements android.os.Parcelable {
@@ -5693,6 +5718,7 @@
method public deprecated boolean isVisualVoicemailEnabled(android.telecom.PhoneAccountHandle);
method public boolean needsOtaServiceProvisioning();
method public boolean rebootRadio();
+ method public void requestCellInfoUpdate(android.os.WorkSource, java.util.concurrent.Executor, android.telephony.TelephonyManager.CellInfoCallback);
method public boolean resetRadioConfig();
method public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>);
method public void setCarrierDataEnabled(boolean);
@@ -5987,6 +6013,7 @@
field public static final int EUICC_OTA_STATUS_UNAVAILABLE = 5; // 0x5
field public static final int EUICC_OTA_SUCCEEDED = 3; // 0x3
field public static final java.lang.String EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS";
+ field public static final java.lang.String EXTRA_FORCE_PROVISION = "android.telephony.euicc.extra.FORCE_PROVISION";
}
public static abstract class EuiccManager.OtaStatus implements java.lang.annotation.Annotation {
@@ -6600,6 +6627,22 @@
method public void receiveSessionModifyResponse(int, android.telecom.VideoProfile, android.telecom.VideoProfile);
}
+ public class ProvisioningManager {
+ method public static android.telephony.ims.ProvisioningManager createForSubscriptionId(android.content.Context, int);
+ method public int getProvisioningIntValue(int);
+ method public java.lang.String getProvisioningStringValue(int);
+ method public void registerProvisioningChangedCallback(java.util.concurrent.Executor, android.telephony.ims.ProvisioningManager.Callback);
+ method public int setProvisioningIntValue(int, int);
+ method public int setProvisioningStringValue(int, java.lang.String);
+ method public void unregisterProvisioningChangedCallback(android.telephony.ims.ProvisioningManager.Callback);
+ }
+
+ public static class ProvisioningManager.Callback {
+ ctor public ProvisioningManager.Callback();
+ method public void onProvisioningIntChanged(int, int);
+ method public void onProvisioningStringChanged(int, java.lang.String);
+ }
+
}
package android.telephony.ims.feature {
@@ -6667,7 +6710,7 @@
field public static final int PROCESS_CALL_IMS = 0; // 0x0
}
- public static class MmTelFeature.MmTelCapabilities {
+ public static class MmTelFeature.MmTelCapabilities extends android.telephony.ims.feature.ImsFeature.Capabilities {
ctor public MmTelFeature.MmTelCapabilities();
ctor public deprecated MmTelFeature.MmTelCapabilities(android.telephony.ims.feature.ImsFeature.Capabilities);
ctor public MmTelFeature.MmTelCapabilities(int);
@@ -7018,6 +7061,7 @@
method public java.util.Set<android.content.ComponentName> getContentCaptureDisabledActivities();
method public java.util.Set<java.lang.String> getContentCaptureDisabledPackages();
method public void setActivityContentCaptureEnabled(android.content.ComponentName, boolean);
+ method public void setContentCaptureWhitelist(java.util.List<java.lang.String>, java.util.List<android.content.ComponentName>);
method public void setPackageContentCaptureEnabled(java.lang.String, boolean);
}
@@ -7410,6 +7454,7 @@
method public default void onMovedToDisplay(int, android.content.res.Configuration);
method public abstract void onOverScrolled(int, int, boolean, boolean);
method public default void onProvideAutofillVirtualStructure(android.view.ViewStructure, int);
+ method public default boolean onProvideContentCaptureStructure(android.view.ViewStructure, int);
method public abstract void onProvideVirtualStructure(android.view.ViewStructure);
method public abstract void onScrollChanged(int, int, int, int);
method public abstract void onSizeChanged(int, int, int, int);
diff --git a/api/test-current.txt b/api/test-current.txt
index 5531014..1c01cf1 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1037,6 +1037,11 @@
field public static final int ID_TYPE_SERIAL = 1; // 0x1
}
+ public class DeviceIdAttestationException extends java.lang.Exception {
+ ctor public DeviceIdAttestationException(java.lang.String);
+ ctor public DeviceIdAttestationException(java.lang.String, java.lang.Throwable);
+ }
+
public static final class KeyGenParameterSpec.Builder {
method public android.security.keystore.KeyGenParameterSpec.Builder setUniqueIdIncluded(boolean);
}
diff --git a/cmds/incident_helper/tests/ih_util_test.cpp b/cmds/incident_helper/tests/ih_util_test.cpp
index efe714d..670881a 100644
--- a/cmds/incident_helper/tests/ih_util_test.cpp
+++ b/cmds/incident_helper/tests/ih_util_test.cpp
@@ -17,7 +17,6 @@
#include "ih_util.h"
#include <android-base/file.h>
-#include <android-base/test_utils.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <string>
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index e92cf94..1c3ebd8 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -82,15 +82,17 @@
Status::EX_SECURITY,
"Calling process does not have permission to get local data.");
}
+ break;
case DEST_EXPLICIT:
if (callingUid != AID_SHELL && callingUid != AID_ROOT && callingUid != AID_STATSD &&
- callingUid != AID_SYSTEM) {
+ callingUid != AID_SYSTEM) {
ALOGW("Calling pid %d and uid %d does not have permission to get explicit data.",
callingPid, callingUid);
return Status::fromExceptionCode(
Status::EX_SECURITY,
"Calling process does not have permission to get explicit data.");
}
+ break;
}
return Status::ok();
}
@@ -298,7 +300,7 @@
}
return NO_ERROR;
- }
+ } break;
default: { return BnIncidentManager::onTransact(code, data, reply, flags); }
}
}
diff --git a/cmds/incidentd/tests/FdBuffer_test.cpp b/cmds/incidentd/tests/FdBuffer_test.cpp
index 9d208df..3f92c2a 100644
--- a/cmds/incidentd/tests/FdBuffer_test.cpp
+++ b/cmds/incidentd/tests/FdBuffer_test.cpp
@@ -17,13 +17,13 @@
#include "FdBuffer.h"
#include "incidentd_util.h"
-#include <android-base/file.h>
-#include <android-base/test_utils.h>
#include <fcntl.h>
-#include <gtest/gtest.h>
#include <signal.h>
#include <string.h>
+#include <android-base/file.h>
+#include <gtest/gtest.h>
+
using namespace android;
using namespace android::base;
using namespace android::os::incidentd;
diff --git a/cmds/incidentd/tests/Reporter_test.cpp b/cmds/incidentd/tests/Reporter_test.cpp
index 1086908..f54f738 100644
--- a/cmds/incidentd/tests/Reporter_test.cpp
+++ b/cmds/incidentd/tests/Reporter_test.cpp
@@ -19,12 +19,12 @@
#include <android/os/BnIncidentReportStatusListener.h>
#include <frameworks/base/libs/incident/proto/android/os/header.pb.h>
-#include <android-base/file.h>
-#include <android-base/test_utils.h>
#include <dirent.h>
+#include <string.h>
+
+#include <android-base/file.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
-#include <string.h>
using namespace android;
using namespace android::base;
@@ -197,4 +197,4 @@
EXPECT_EQ(1, metadata.request_size());
EXPECT_TRUE(metadata.use_dropbox());
EXPECT_EQ(0, metadata.sections_size());
-}
\ No newline at end of file
+}
diff --git a/cmds/incidentd/tests/Throttler_test.cpp b/cmds/incidentd/tests/Throttler_test.cpp
index 8488c99..b35228c 100644
--- a/cmds/incidentd/tests/Throttler_test.cpp
+++ b/cmds/incidentd/tests/Throttler_test.cpp
@@ -16,7 +16,6 @@
#include "Throttler.h"
-#include <android-base/test_utils.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
diff --git a/cmds/screencap/Android.bp b/cmds/screencap/Android.bp
new file mode 100644
index 0000000..248c675
--- /dev/null
+++ b/cmds/screencap/Android.bp
@@ -0,0 +1,21 @@
+cc_binary {
+ name: "screencap",
+
+ srcs: ["screencap.cpp"],
+
+ shared_libs: [
+ "libcutils",
+ "libutils",
+ "libbinder",
+ "libhwui",
+ "libui",
+ "libgui",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wunused",
+ "-Wunreachable-code",
+ ],
+}
diff --git a/cmds/screencap/Android.mk b/cmds/screencap/Android.mk
deleted file mode 100644
index 72e3c56..0000000
--- a/cmds/screencap/Android.mk
+++ /dev/null
@@ -1,21 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= \
- screencap.cpp
-
-LOCAL_SHARED_LIBRARIES := \
- libcutils \
- libutils \
- libbinder \
- libhwui \
- libui \
- libgui
-
-LOCAL_MODULE:= screencap
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
-
-include $(BUILD_EXECUTABLE)
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index eb498f5..a981997 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -370,11 +370,9 @@
// This skips the uid map if it's an empty config.
if (it->second->getNumMetrics() > 0) {
uint64_t uidMapToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_ID_UID_MAP);
- if (it->second->hashStringInReport()) {
- mUidMap->appendUidMap(dumpTimeStampNs, key, &str_set, proto);
- } else {
- mUidMap->appendUidMap(dumpTimeStampNs, key, nullptr, proto);
- }
+ mUidMap->appendUidMap(
+ dumpTimeStampNs, key, it->second->hashStringInReport() ? &str_set : nullptr,
+ it->second->versionStringsInReport(), it->second->installerInReport(), proto);
proto->end(uidMapToken);
}
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 27685fc..7fa05be 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -787,21 +787,24 @@
}
Status StatsService::informAllUidData(const vector<int32_t>& uid, const vector<int64_t>& version,
- const vector<String16>& app) {
+ const vector<String16>& version_string,
+ const vector<String16>& app,
+ const vector<String16>& installer) {
ENFORCE_UID(AID_SYSTEM);
VLOG("StatsService::informAllUidData was called");
- mUidMap->updateMap(getElapsedRealtimeNs(), uid, version, app);
+ mUidMap->updateMap(getElapsedRealtimeNs(), uid, version, version_string, app, installer);
VLOG("StatsService::informAllUidData succeeded");
return Status::ok();
}
-Status StatsService::informOnePackage(const String16& app, int32_t uid, int64_t version) {
+Status StatsService::informOnePackage(const String16& app, int32_t uid, int64_t version,
+ const String16& version_string, const String16& installer) {
ENFORCE_UID(AID_SYSTEM);
VLOG("StatsService::informOnePackage was called");
- mUidMap->updateApp(getElapsedRealtimeNs(), app, uid, version);
+ mUidMap->updateApp(getElapsedRealtimeNs(), app, uid, version, version_string, installer);
return Status::ok();
}
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 4a5f05f..cd4d601 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -73,8 +73,10 @@
virtual Status informAlarmForSubscriberTriggeringFired();
virtual Status informAllUidData(const vector<int32_t>& uid, const vector<int64_t>& version,
- const vector<String16>& app);
- virtual Status informOnePackage(const String16& app, int32_t uid, int64_t version);
+ const vector<String16>& version_string,
+ const vector<String16>& app, const vector<String16>& installer);
+ virtual Status informOnePackage(const String16& app, int32_t uid, int64_t version,
+ const String16& version_string, const String16& installer);
virtual Status informOnePackageRemoved(const String16& app, int32_t uid);
virtual Status informDeviceShutdown();
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 21e7203..537e51e 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -27,6 +27,7 @@
import "frameworks/base/core/proto/android/bluetooth/enums.proto";
import "frameworks/base/core/proto/android/os/enums.proto";
import "frameworks/base/core/proto/android/server/enums.proto";
+import "frameworks/base/core/proto/android/server/location/enums.proto";
import "frameworks/base/core/proto/android/service/procstats_enum.proto";
import "frameworks/base/core/proto/android/stats/enums.proto";
import "frameworks/base/core/proto/android/stats/launcher/launcher.proto";
@@ -118,7 +119,7 @@
ResourceConfigurationChanged resource_configuration_changed = 66;
BluetoothEnabledStateChanged bluetooth_enabled_state_changed = 67;
BluetoothConnectionStateChanged bluetooth_connection_state_changed = 68;
- // 69 is blank but need not be.
+ GpsSignalQualityChanged gps_signal_quality_changed = 69;
UsbConnectorStateChanged usb_connector_state_changed = 70;
SpeakerImpedanceReported speaker_impedance_reported = 71;
HardwareFailed hardware_failed = 72;
@@ -147,10 +148,18 @@
PhoneStateChanged phone_state_changed = 95;
UserRestrictionChanged user_restriction_changed = 96;
SettingsUIChanged settings_ui_changed = 97;
+ ConnectivityStateChanged connectivity_state_changed = 98;
+ // TODO: service state change is very noisy shortly after boot, as well
+ // as at other transitions - coming out of doze, device plugged in, etc.
+ // Consider removing this if it becomes a problem
+ ServiceStateChanged service_state_changed = 99;
+ ServiceLaunchReported service_launch_reported = 100;
+ PhenotypeFlagStateChanged phenotype_flag_state_changed = 101;
+ BinaryPushStateChanged binary_push_state_changed = 102;
}
// Pulled events will start at field 10000.
- // Next: 10038
+ // Next: 10043
oneof pulled {
WifiBytesTransfer wifi_bytes_transfer = 10000;
WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001;
@@ -191,6 +200,10 @@
NativeProcessMemoryState native_process_memory_state = 10036;
CpuTimePerThreadFreq cpu_time_per_thread_freq = 10037;
OnDevicePowerMeasurement on_device_power_measurement = 10038;
+ DeviceCalculatedPowerUse device_calculated_power_use = 10039;
+ DeviceCalculatedPowerBlameUid device_calculated_power_blame_uid = 10040;
+ DeviceCalculatedPowerBlameOther device_calculated_power_blame_other = 10041;
+ ProcessMemoryHighWaterMark process_memory_high_water_mark = 10042;
}
// DO NOT USE field numbers above 100,000 in AOSP.
@@ -485,7 +498,6 @@
optional State state = 3;
}
-
/**
* Logs when GPS state changes.
*
@@ -502,6 +514,16 @@
optional State state = 2;
}
+/**
+ * Logs when GPS signal quality.
+ *
+ * Logged from:
+ * /frameworks/base/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
+ */
+message GpsSignalQualityChanged {
+ optional android.server.location.GpsSignalQualityEnum level = 1;
+}
+
/**
* Logs when a sync manager sync state changes.
@@ -1573,6 +1595,8 @@
FOREGROUND = 2;
}
optional ForegroundState foreground_state = 7;
+
+ optional android.server.ErrorSource error_source = 8;
}
/**
@@ -1591,6 +1615,8 @@
// The pid if available. -1 means not available.
optional sint32 pid = 4;
+
+ optional android.server.ErrorSource error_source = 5;
}
/**
@@ -1628,6 +1654,10 @@
FOREGROUND = 2;
}
optional ForegroundState foreground_state = 6;
+
+ optional android.server.ErrorSource error_source = 7;
+
+ optional string package_name = 8;
}
/**
@@ -2129,6 +2159,94 @@
optional int64 visible_millis = 16;
}
+/*
+ * Logs when a flag flip state changes.
+ * Logged in P/h.
+ */
+message PhenotypeFlagStateChanged {
+ // Mendel configuration name.
+ optional string mendel_config_name = 1;
+ // State
+ enum State {
+ STATE_UNKNOWN = 0;
+ COMMITTED = 1;
+ }
+ optional State state = 2;
+}
+
+/*
+ * Logs when a binary push state changes.
+ * Logged in Play store
+ */
+message BinaryPushStateChanged {
+ // Binary package name.
+ optional string binary_name = 1;
+ // Version code.
+ optional int64 version = 2;
+ // State
+ enum State {
+ STATE_UNKNOWN = 0;
+ DOWNLOAD_START = 1;
+ DOWNLOAD_DONE = 2;
+ INSTALL_START = 3;
+ INSTALL_DONE = 4;
+ REBOOT_START = 5;
+ REBOOT_DONE = 6;
+ }
+ optional State state = 3;
+}
+
+/*
+ * Logs when a connection becomes available and lost.
+ * Logged in StatsCompanionService.java
+ */
+message ConnectivityStateChanged {
+ // Id of the network.
+ optional int32 net_id = 1;
+
+ enum State {
+ UNKNOWN = 0;
+ CONNECTED = 1;
+ DISCONNECTED = 2;
+ }
+ // Connected state of a network.
+ optional State state = 2;
+}
+
+/**
+ * Logs when a service starts and stops.
+ * Logged from:
+ * services/core/java/com/android/server/am/ActiveServices.java
+ */
+message ServiceStateChanged {
+
+ optional int32 uid = 1 [(is_uid) = true];
+
+ optional string package_name = 2;
+
+ optional string service_name = 3;
+
+ enum State {
+ START = 1;
+ STOP = 2;
+ }
+
+ optional State state = 4;
+}
+
+/**
+ * Logs when a service is launched.
+ * Logged from:
+ * services/core/java/com/android/server/am/ActiveServices.java
+ */
+message ServiceLaunchReported {
+
+ optional int32 uid = 1 [(is_uid) = true];
+
+ optional string package_name = 2;
+
+ optional string service_name = 3;
+}
//////////////////////////////////////////////////////////////////////
// Pulled atoms below this line //
@@ -2420,7 +2538,8 @@
// RSS high watermark.
// Peak RSS usage of the process. Value is read from the VmHWM field in /proc/PID/status or
// from memory.max_usage_in_bytes under /dev/memcg if the device uses per-app memory cgroups.
- optional int64 rss_high_watermark_in_bytes = 9;
+ // Deprecated: use ProcessMemoryHighWaterMark atom instead.
+ optional int64 rss_high_watermark_in_bytes = 9 [deprecated = true];
// Elapsed real time when the process started.
// Value is read from /proc/PID/stat, field 22. 0 if read from per-app memory cgroups.
@@ -2448,7 +2567,8 @@
// RSS high watermark.
// Peak RSS usage of the process. Value is read from the VmHWM field in /proc/PID/status.
- optional int64 rss_high_watermark_in_bytes = 6;
+ // Deprecated: use ProcessMemoryHighWaterMark atom instead.
+ optional int64 rss_high_watermark_in_bytes = 6 [deprecated = true];
// Elapsed real time when the process started.
// Value is read from /proc/PID/stat, field 22.
@@ -2456,6 +2576,22 @@
}
/*
+ * Logs the memory high-water mark for a process.
+ * Recorded in ActivityManagerService.
+ */
+message ProcessMemoryHighWaterMark {
+ // The uid if available. -1 means not available.
+ optional int32 uid = 1 [(is_uid) = true];
+
+ // The process name. Provided by ActivityManagerService or read from /proc/PID/cmdline.
+ optional string process_name = 2;
+
+ // RSS high-water mark. Peak RSS usage of the process. Read from the VmHWM field in
+ // /proc/PID/status.
+ optional int64 rss_high_water_mark_in_bytes = 3;
+}
+
+/*
* Elapsed real time from SystemClock.
*/
message SystemElapsedRealtime {
@@ -3169,15 +3305,75 @@
// UID that owns the process.
optional int32 uid = 1 [(is_uid) = true];
// ID of the process.
- optional uint32 process_id = 2;
+ optional int32 process_id = 2;
// ID of the thread.
- optional uint32 thread_id = 3;
+ optional int32 thread_id = 3;
// Name of the process taken from `/proc/$PID/cmdline`.
optional string process_name = 4;
// Name of the thread taken from `/proc/$PID/task/$TID/comm`
optional string thread_name = 5;
// What frequency the CPU was running at, in KHz
- optional uint32 frequency_khz = 6;
+ optional int32 frequency_khz = 6;
// Time spent in frequency in milliseconds, since thread start.
- optional uint32 time_millis = 7;
+ optional int32 time_millis = 7;
+}
+
+/**
+ * Pulls on-device BatteryStats power use calculations for the overall device.
+ */
+message DeviceCalculatedPowerUse {
+ // Power used by the device in mAh, as computed by BatteryStats, since BatteryStats last reset
+ // (i.e. roughly since device was last significantly charged).
+ // Currently, this is BatteryStatsHelper.getComputedPower() (not getTotalPower()).
+ optional float computed_power_milli_amp_hours = 1;
+}
+
+/**
+ * Pulls on-device BatteryStats power use calculations broken down by uid.
+ * This atom should be complemented by DeviceCalculatedPowerBlameOther, which contains the power use
+ * that is attributed to non-uid items. They must all be included to get the total power use.
+ */
+message DeviceCalculatedPowerBlameUid {
+ // Uid being blamed. Note: isolated uids have already been mapped to host uid.
+ optional int32 uid = 1 [(is_uid) = true];
+
+ // Power used by this uid in mAh, as computed by BatteryStats, since BatteryStats last reset
+ // (i.e. roughly since device was last significantly charged).
+ optional float power_milli_amp_hours = 2;
+}
+
+/**
+ * Pulls on-device BatteryStats power use calculations that are not due to a uid, broken down by
+ * drain type.
+ * This atom should be complemented by DeviceCalculatedPowerBlameUid, which contains the blame that
+ * is attributed uids. They must all be included to get the total power use.
+ */
+message DeviceCalculatedPowerBlameOther {
+ // The type of item whose power use is being reported.
+ enum DrainType {
+ AMBIENT_DISPLAY = 0;
+ // reserved 1; reserved "APP"; // Logged instead in DeviceCalculatedPowerBlameUid.
+ BLUETOOTH = 2;
+ CAMERA = 3;
+ // Cell-standby
+ CELL = 4;
+ FLASHLIGHT = 5;
+ IDLE = 6;
+ MEMORY = 7;
+ // Amount that total computed drain exceeded the drain estimated using the
+ // battery level changes and capacity.
+ OVERCOUNTED = 8;
+ PHONE = 9;
+ SCREEN = 10;
+ // Amount that total computed drain was below the drain estimated using the
+ // battery level changes and capacity.
+ UNACCOUNTED = 11;
+ // reserved 12; reserved "USER"; // Entire drain for a user. This is NOT supported.
+ WIFI = 13;
+ }
+ optional DrainType drain_type = 1;
+
+ // Power used by this item in mAh, as computed by BatteryStats, since BatteryStats last reset
+ // (i.e. roughly since device was last significantly charged).
+ optional float power_milli_amp_hours = 2;
}
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 8378ae1..a375dd6 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -180,6 +180,11 @@
{2, 7},
1 * NS_PER_SEC,
new StatsCompanionServicePuller(android::util::NATIVE_PROCESS_MEMORY_STATE)}},
+ {android::util::PROCESS_MEMORY_HIGH_WATER_MARK,
+ {{3},
+ {2},
+ 1 * NS_PER_SEC,
+ new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_HIGH_WATER_MARK)}},
// temperature
{android::util::TEMPERATURE, {{}, {}, 1 * NS_PER_SEC, new ResourceThermalManagerPuller()}},
// binder_calls
@@ -243,6 +248,20 @@
{2, 3, 4, 5, 6},
1 * NS_PER_SEC,
new StatsCompanionServicePuller(android::util::CPU_TIME_PER_THREAD_FREQ)}},
+ // DeviceCalculatedPowerUse.
+ {android::util::DEVICE_CALCULATED_POWER_USE,
+ {{}, {}, 1 * NS_PER_SEC,
+ new StatsCompanionServicePuller(android::util::DEVICE_CALCULATED_POWER_USE)}},
+ // DeviceCalculatedPowerBlameUid.
+ {android::util::DEVICE_CALCULATED_POWER_BLAME_UID,
+ {{}, {}, // BatteryStats already merged isolated with host ids so it's unnecessary here.
+ 1 * NS_PER_SEC,
+ new StatsCompanionServicePuller(android::util::DEVICE_CALCULATED_POWER_BLAME_UID)}},
+ // DeviceCalculatedPowerBlameOther.
+ {android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER,
+ {{}, {},
+ 1 * NS_PER_SEC,
+ new StatsCompanionServicePuller(android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER)}},
};
StatsPullerManager::StatsPullerManager() : mNextPullTimeNs(NO_ALARM_UPDATE) {
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index febb922..625294c 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -156,9 +156,6 @@
FieldValue(Field(mTagId, getSimpleField(1)), Value(speakerImpedance.speakerLocation)));
mValues.push_back(
FieldValue(Field(mTagId, getSimpleField(2)), Value(speakerImpedance.milliOhms)));
- if (!mValues.empty()) {
- mValues.back().mField.decorateLastPos(1);
- }
}
LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
@@ -173,9 +170,6 @@
FieldValue(Field(mTagId, getSimpleField(2)), Value(hardwareFailed.hardwareLocation)));
mValues.push_back(
FieldValue(Field(mTagId, getSimpleField(3)), Value(int32_t(hardwareFailed.errorCode))));
- if (!mValues.empty()) {
- mValues.back().mField.decorateLastPos(1);
- }
}
LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
@@ -190,9 +184,6 @@
FieldValue(Field(mTagId, getSimpleField(2)), Value(physicalDropDetected.accelPeak)));
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(3)),
Value(physicalDropDetected.freefallDuration)));
- if (!mValues.empty()) {
- mValues.back().mField.decorateLastPos(1);
- }
}
LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
@@ -205,10 +196,6 @@
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 1)),
Value(chargeCycles.cycleBucket[i])));
}
-
- if (!mValues.empty()) {
- mValues.back().mField.decorateLastPos(1);
- }
}
LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
@@ -231,10 +218,6 @@
Value(batteryHealthSnapshotArgs.resistanceMicroOhm)));
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(7)),
Value(batteryHealthSnapshotArgs.levelPercent)));
-
- if (!mValues.empty()) {
- mValues.back().mField.decorateLastPos(1);
- }
}
LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, const SlowIo& slowIo) {
@@ -247,10 +230,6 @@
FieldValue(Field(mTagId, getSimpleField(1)), Value(int32_t(slowIo.operation))));
pos[0]++;
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), Value(slowIo.count)));
-
- if (!mValues.empty()) {
- mValues.back().mField.decorateLastPos(1);
- }
}
LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
@@ -261,10 +240,6 @@
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(1)),
Value(batteryCausedShutdown.voltageMicroV)));
-
- if (!mValues.empty()) {
- mValues.back().mField.decorateLastPos(1);
- }
}
LogEvent::LogEvent(int32_t tagId, int64_t timestampNs) : LogEvent(tagId, timestampNs, 0) {}
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index 05103a9..3a34743 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -140,7 +140,7 @@
// Adjust start for partial bucket
mCurrentBucketStartTimeNs = startTimeNs;
- if (mIsPulled) {
+ if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) {
pullAndMatchEventsLocked(startTimeNs);
}
@@ -320,11 +320,11 @@
triggerPuller = mCondition && mCurrentSlicedBucket->empty();
break;
}
- case GaugeMetric::ALL_CONDITION_CHANGES: {
- triggerPuller = true;
+ case GaugeMetric::CONDITION_CHANGE_TO_TRUE: {
+ triggerPuller = mCondition;
break;
}
- case GaugeMetric::CONDITION_CHANGE_TO_TRUE: {
+ case GaugeMetric::FIRST_N_SAMPLES: {
triggerPuller = mCondition;
break;
}
@@ -352,7 +352,7 @@
VLOG("GaugeMetric %lld onConditionChanged", (long long)mMetricId);
flushIfNeededLocked(eventTimeNs);
mCondition = conditionMet;
- if (mIsPulled) {
+ if (mIsPulled && mTriggerAtomId == -1) {
pullAndMatchEventsLocked(eventTimeNs);
} // else: Push mode. No need to proactively pull the gauge data.
}
@@ -365,7 +365,7 @@
// If the condition is sliced, mCondition is true if any of the dimensions is true. And we will
// pull for every dimension.
mCondition = overallCondition;
- if (mIsPulled) {
+ if (mIsPulled && mTriggerAtomId == -1) {
pullAndMatchEventsLocked(eventTimeNs);
} // else: Push mode. No need to proactively pull the gauge data.
}
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index 5866139..6e3530b 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -80,7 +80,7 @@
}
flushCurrentBucketLocked(eventTimeNs);
mCurrentBucketStartTimeNs = eventTimeNs;
- if (mIsPulled) {
+ if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) {
pullAndMatchEventsLocked(eventTimeNs);
}
};
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 4244d5b..ac34f47 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -77,6 +77,8 @@
mActivationAtomTrackerToMetricMap, mMetricIndexesWithActivation, mNoReportMetricIds);
mHashStringsInReport = config.hash_strings_in_metric_report();
+ mVersionStringsInReport = config.version_strings_in_metric_report();
+ mInstallerInReport = config.installer_in_metric_report();
if (config.allowed_log_source_size() == 0) {
mConfigValid = false;
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index a4672b6..a31efbd 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -83,6 +83,14 @@
return mHashStringsInReport;
};
+ inline bool versionStringsInReport() const {
+ return mVersionStringsInReport;
+ };
+
+ inline bool installerInReport() const {
+ return mInstallerInReport;
+ };
+
void refreshTtl(const int64_t currentTimestampNs) {
if (mTtlNs > 0) {
mTtlEndNs = currentTimestampNs + mTtlNs;
@@ -126,6 +134,8 @@
bool mConfigValid = false;
bool mHashStringsInReport = false;
+ bool mVersionStringsInReport = false;
+ bool mInstallerInReport = false;
const int64_t mTtlNs;
int64_t mTtlEndNs;
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 47b0376..4ac55b5 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -535,9 +535,13 @@
int triggerTrackerIndex;
int triggerAtomId = -1;
- if (pullTagId != -1 && metric.has_trigger_event()) {
- // event_trigger should be used with ALL_CONDITION_CHANGES
- if (metric.sampling_type() != GaugeMetric::ALL_CONDITION_CHANGES) {
+ if (metric.has_trigger_event()) {
+ if (pullTagId == -1) {
+ ALOGW("Pull atom not specified for trigger");
+ return false;
+ }
+ // event_trigger should be used with FIRST_N_SAMPLES
+ if (metric.sampling_type() != GaugeMetric::FIRST_N_SAMPLES) {
return false;
}
if (!handlePullMetricTriggerWithLogTrackers(metric.trigger_event(), metricIndex,
@@ -549,6 +553,12 @@
triggerAtomId = *(triggerAtomMatcher->getAtomIds().begin());
}
+ if (!metric.has_trigger_event() && pullTagId != -1 &&
+ metric.sampling_type() == GaugeMetric::FIRST_N_SAMPLES) {
+ ALOGW("FIRST_N_SAMPLES is only for pushed event or pull_on_trigger");
+ return false;
+ }
+
int conditionIndex = -1;
if (metric.has_condition()) {
bool good = handleMetricWithConditions(
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index 37a0067..59f3f04 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -49,6 +49,10 @@
const int FIELD_ID_SNAPSHOT_PACKAGE_UID = 3;
const int FIELD_ID_SNAPSHOT_PACKAGE_DELETED = 4;
const int FIELD_ID_SNAPSHOT_PACKAGE_NAME_HASH = 5;
+const int FIELD_ID_SNAPSHOT_PACKAGE_VERSION_STRING = 6;
+const int FIELD_ID_SNAPSHOT_PACKAGE_VERSION_STRING_HASH = 7;
+const int FIELD_ID_SNAPSHOT_PACKAGE_INSTALLER = 8;
+const int FIELD_ID_SNAPSHOT_PACKAGE_INSTALLER_HASH = 9;
const int FIELD_ID_SNAPSHOT_TIMESTAMP = 1;
const int FIELD_ID_SNAPSHOT_PACKAGE_INFO = 2;
const int FIELD_ID_SNAPSHOTS = 1;
@@ -60,6 +64,10 @@
const int FIELD_ID_CHANGE_NEW_VERSION = 5;
const int FIELD_ID_CHANGE_PREV_VERSION = 6;
const int FIELD_ID_CHANGE_PACKAGE_HASH = 7;
+const int FIELD_ID_CHANGE_NEW_VERSION_STRING = 8;
+const int FIELD_ID_CHANGE_PREV_VERSION_STRING = 9;
+const int FIELD_ID_CHANGE_NEW_VERSION_STRING_HASH = 10;
+const int FIELD_ID_CHANGE_PREV_VERSION_STRING_HASH = 11;
UidMap::UidMap() : mBytesUsed(0) {}
@@ -104,7 +112,8 @@
}
void UidMap::updateMap(const int64_t& timestamp, const vector<int32_t>& uid,
- const vector<int64_t>& versionCode, const vector<String16>& packageName) {
+ const vector<int64_t>& versionCode, const vector<String16>& versionString,
+ const vector<String16>& packageName, const vector<String16>& installer) {
vector<wp<PackageInfoListener>> broadcastList;
{
lock_guard<mutex> lock(mMutex); // Exclusively lock for updates.
@@ -121,7 +130,9 @@
mMap.clear();
for (size_t j = 0; j < uid.size(); j++) {
string package = string(String8(packageName[j]).string());
- mMap[std::make_pair(uid[j], package)] = AppData(versionCode[j]);
+ mMap[std::make_pair(uid[j], package)] =
+ AppData(versionCode[j], string(String8(versionString[j]).string()),
+ string(String8(installer[j]).string()));
}
for (const auto& kv : deletedApps) {
@@ -150,23 +161,30 @@
}
void UidMap::updateApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid,
- const int64_t& versionCode) {
+ const int64_t& versionCode, const String16& versionString,
+ const String16& installer) {
vector<wp<PackageInfoListener>> broadcastList;
string appName = string(String8(app_16).string());
{
lock_guard<mutex> lock(mMutex);
int32_t prevVersion = 0;
+ string prevVersionString = "";
+ string newVersionString = string(String8(versionString).string());
bool found = false;
auto it = mMap.find(std::make_pair(uid, appName));
if (it != mMap.end()) {
found = true;
prevVersion = it->second.versionCode;
+ prevVersionString = it->second.versionString;
it->second.versionCode = versionCode;
+ it->second.versionString = newVersionString;
+ it->second.installer = string(String8(installer).string());
it->second.deleted = false;
}
if (!found) {
// Otherwise, we need to add an app at this uid.
- mMap[std::make_pair(uid, appName)] = AppData(versionCode);
+ mMap[std::make_pair(uid, appName)] =
+ AppData(versionCode, newVersionString, string(String8(installer).string()));
} else {
// Only notify the listeners if this is an app upgrade. If this app is being installed
// for the first time, then we don't notify the listeners.
@@ -174,7 +192,8 @@
// app after deletion.
getListenerListCopyLocked(&broadcastList);
}
- mChanges.emplace_back(false, timestamp, appName, uid, versionCode, prevVersion);
+ mChanges.emplace_back(false, timestamp, appName, uid, versionCode, newVersionString,
+ prevVersion, prevVersionString);
mBytesUsed += kBytesChangeRecord;
ensureBytesUsedBelowLimit();
StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
@@ -226,10 +245,12 @@
lock_guard<mutex> lock(mMutex);
int64_t prevVersion = 0;
+ string prevVersionString = "";
auto key = std::make_pair(uid, app);
auto it = mMap.find(key);
if (it != mMap.end() && !it->second.deleted) {
prevVersion = it->second.versionCode;
+ prevVersionString = it->second.versionString;
it->second.deleted = true;
mDeletedApps.push_back(key);
}
@@ -240,7 +261,7 @@
mMap.erase(oldest);
StatsdStats::getInstance().noteUidMapAppDeletionDropped();
}
- mChanges.emplace_back(true, timestamp, app, uid, 0, prevVersion);
+ mChanges.emplace_back(true, timestamp, app, uid, 0, "", prevVersion, prevVersionString);
mBytesUsed += kBytesChangeRecord;
ensureBytesUsedBelowLimit();
StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
@@ -315,8 +336,9 @@
return mBytesUsed;
}
-void UidMap::appendUidMap(const int64_t& timestamp, const ConfigKey& key,
- std::set<string> *str_set, ProtoOutputStream* proto) {
+void UidMap::appendUidMap(const int64_t& timestamp, const ConfigKey& key, std::set<string>* str_set,
+ bool includeVersionStrings, bool includeInstaller,
+ ProtoOutputStream* proto) {
lock_guard<mutex> lock(mMutex); // Lock for updates
for (const ChangeRecord& record : mChanges) {
@@ -330,8 +352,22 @@
str_set->insert(record.package);
proto->write(FIELD_TYPE_UINT64 | FIELD_ID_CHANGE_PACKAGE_HASH,
(long long)Hash64(record.package));
+ if (includeVersionStrings) {
+ str_set->insert(record.versionString);
+ proto->write(FIELD_TYPE_UINT64 | FIELD_ID_CHANGE_NEW_VERSION_STRING_HASH,
+ (long long)Hash64(record.versionString));
+ str_set->insert(record.prevVersionString);
+ proto->write(FIELD_TYPE_UINT64 | FIELD_ID_CHANGE_PREV_VERSION_STRING_HASH,
+ (long long)Hash64(record.prevVersionString));
+ }
} else {
proto->write(FIELD_TYPE_STRING | FIELD_ID_CHANGE_PACKAGE, record.package);
+ if (includeVersionStrings) {
+ proto->write(FIELD_TYPE_STRING | FIELD_ID_CHANGE_NEW_VERSION_STRING,
+ record.versionString);
+ proto->write(FIELD_TYPE_STRING | FIELD_ID_CHANGE_PREV_VERSION_STRING,
+ record.prevVersionString);
+ }
}
proto->write(FIELD_TYPE_INT32 | FIELD_ID_CHANGE_UID, (int)record.uid);
@@ -354,8 +390,26 @@
str_set->insert(kv.first.second);
proto->write(FIELD_TYPE_UINT64 | FIELD_ID_SNAPSHOT_PACKAGE_NAME_HASH,
(long long)Hash64(kv.first.second));
+ if (includeVersionStrings) {
+ str_set->insert(kv.second.versionString);
+ proto->write(FIELD_TYPE_UINT64 | FIELD_ID_SNAPSHOT_PACKAGE_VERSION_STRING_HASH,
+ (long long)Hash64(kv.second.versionString));
+ }
+ if (includeInstaller) {
+ str_set->insert(kv.second.installer);
+ proto->write(FIELD_TYPE_UINT64 | FIELD_ID_SNAPSHOT_PACKAGE_INSTALLER_HASH,
+ (long long)Hash64(kv.second.installer));
+ }
} else {
proto->write(FIELD_TYPE_STRING | FIELD_ID_SNAPSHOT_PACKAGE_NAME, kv.first.second);
+ if (includeVersionStrings) {
+ proto->write(FIELD_TYPE_STRING | FIELD_ID_SNAPSHOT_PACKAGE_VERSION_STRING,
+ kv.second.versionString);
+ }
+ if (includeInstaller) {
+ proto->write(FIELD_TYPE_STRING | FIELD_ID_SNAPSHOT_PACKAGE_INSTALLER,
+ kv.second.installer);
+ }
}
proto->write(FIELD_TYPE_INT64 | FIELD_ID_SNAPSHOT_PACKAGE_VERSION,
@@ -391,8 +445,9 @@
for (const auto& kv : mMap) {
if (!kv.second.deleted) {
- dprintf(out, "%s, v%" PRId64 " (%i)\n", kv.first.second.c_str(), kv.second.versionCode,
- kv.first.first);
+ dprintf(out, "%s, v%" PRId64 ", %s, %s (%i)\n", kv.first.second.c_str(),
+ kv.second.versionCode, kv.second.versionString.c_str(),
+ kv.second.installer.c_str(), kv.first.first);
}
}
}
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
index 4598369..75ff507 100644
--- a/cmds/statsd/src/packages/UidMap.h
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -44,12 +44,16 @@
struct AppData {
int64_t versionCode;
+ string versionString;
+ string installer;
bool deleted;
// Empty constructor needed for unordered map.
AppData() {
}
- AppData(const int64_t v) : versionCode(v), deleted(false){};
+
+ AppData(const int64_t v, const string& versionString, const string& installer)
+ : versionCode(v), versionString(versionString), installer(installer), deleted(false){};
};
// When calling appendUidMap, we retrieve all the ChangeRecords since the last
@@ -61,15 +65,20 @@
const int32_t uid;
const int64_t version;
const int64_t prevVersion;
+ const string versionString;
+ const string prevVersionString;
ChangeRecord(const bool isDeletion, const int64_t timestampNs, const string& package,
- const int32_t uid, const int64_t version, const int64_t prevVersion)
+ const int32_t uid, const int64_t version, const string versionString,
+ const int64_t prevVersion, const string prevVersionString)
: deletion(isDeletion),
timestampNs(timestampNs),
package(package),
uid(uid),
version(version),
- prevVersion(prevVersion) {
+ prevVersion(prevVersion),
+ versionString(versionString),
+ prevVersionString(prevVersionString) {
}
};
@@ -87,10 +96,12 @@
* tuple, ie. uid[j] corresponds to packageName[j] with versionCode[j].
*/
void updateMap(const int64_t& timestamp, const vector<int32_t>& uid,
- const vector<int64_t>& versionCode, const vector<String16>& packageName);
+ const vector<int64_t>& versionCode, const vector<String16>& versionString,
+ const vector<String16>& packageName, const vector<String16>& installer);
void updateApp(const int64_t& timestamp, const String16& packageName, const int32_t& uid,
- const int64_t& versionCode);
+ const int64_t& versionCode, const String16& versionString,
+ const String16& installer);
void removeApp(const int64_t& timestamp, const String16& packageName, const int32_t& uid);
// Returns true if the given uid contains the specified app (eg. com.google.android.gms).
@@ -127,8 +138,9 @@
// Gets all snapshots and changes that have occurred since the last output.
// If every config key has received a change or snapshot record, then this
// record is deleted.
- void appendUidMap(const int64_t& timestamp, const ConfigKey& key,
- std::set<string> *str_set, util::ProtoOutputStream* proto);
+ void appendUidMap(const int64_t& timestamp, const ConfigKey& key, std::set<string>* str_set,
+ bool includeVersionStrings, bool includeInstaller,
+ util::ProtoOutputStream* proto);
// Forces the output to be cleared. We still generate a snapshot based on the current state.
// This results in extra data uploaded but helps us reconstruct the uid mapping on the server
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index 5d0f3d1..32ee5af 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -233,6 +233,14 @@
optional bool deleted = 4;
optional uint64 name_hash = 5;
+
+ optional string version_string = 6;
+
+ optional uint64 version_string_hash = 7;
+
+ optional string installer = 8;
+
+ optional uint64 installer_hash = 9;
}
optional int64 elapsed_timestamp_nanos = 1;
@@ -250,6 +258,10 @@
optional int64 new_version = 5;
optional int64 prev_version = 6;
optional uint64 app_hash = 7;
+ optional string new_version_string = 8;
+ optional string prev_version_string = 9;
+ optional uint64 new_version_string_hash = 10;
+ optional uint64 prev_version_string_hash = 11;
}
repeated Change changes = 2;
}
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index aa789c7..61854a4 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -233,8 +233,9 @@
enum SamplingType {
RANDOM_ONE_SAMPLE = 1;
- ALL_CONDITION_CHANGES = 2;
+ ALL_CONDITION_CHANGES = 2 [deprecated = true];
CONDITION_CHANGE_TO_TRUE = 3;
+ FIRST_N_SAMPLES = 4;
}
optional SamplingType sampling_type = 9 [default = RANDOM_ONE_SAMPLE] ;
@@ -409,6 +410,10 @@
repeated MetricActivation metric_activation = 17;
+ optional bool version_strings_in_metric_report = 18;
+
+ optional bool installer_in_metric_report = 19;
+
// Field number 1000 is reserved for later use.
reserved 1000;
}
diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp
index 4c6671d..2b9528f 100644
--- a/cmds/statsd/tests/LogEntryMatcher_test.cpp
+++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp
@@ -148,8 +148,12 @@
uidMap.updateMap(
1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */,
+ {android::String16("v1"), android::String16("v1"), android::String16("v2"),
+ android::String16("v1"), android::String16("v2")},
{android::String16("pkg0"), android::String16("pkg1"), android::String16("pkg1"),
- android::String16("Pkg2"), android::String16("PkG3")} /* package name list */);
+ android::String16("Pkg2"), android::String16("PkG3")} /* package name list */,
+ {android::String16(""), android::String16(""), android::String16(""),
+ android::String16(""), android::String16("")});
EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
@@ -297,8 +301,12 @@
UidMap uidMap;
uidMap.updateMap(
1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */,
+ {android::String16("v1"), android::String16("v1"), android::String16("v2"),
+ android::String16("v1"), android::String16("v2")},
{android::String16("pkg0"), android::String16("pkg1"), android::String16("pkg1"),
- android::String16("Pkg2"), android::String16("PkG3")} /* package name list */);
+ android::String16("Pkg2"), android::String16("PkG3")} /* package name list */,
+ {android::String16(""), android::String16(""), android::String16(""),
+ android::String16(""), android::String16("")});
AttributionNodeInternal attribution_node1;
attribution_node1.set_uid(1111);
@@ -372,8 +380,12 @@
UidMap uidMap;
uidMap.updateMap(
1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */,
+ {android::String16("v1"), android::String16("v1"), android::String16("v2"),
+ android::String16("v1"), android::String16("v2")},
{android::String16("pkg0"), android::String16("pkg1"), android::String16("pkg1"),
- android::String16("Pkg2"), android::String16("PkG3")} /* package name list */);
+ android::String16("Pkg2"), android::String16("PkG3")} /* package name list */,
+ {android::String16(""), android::String16(""), android::String16(""),
+ android::String16(""), android::String16("")});
AttributionNodeInternal attribution_node1;
attribution_node1.set_uid(1067);
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index 8864252..355df29 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -153,7 +153,8 @@
// Setup simple config key corresponding to empty config.
sp<UidMap> m = new UidMap();
sp<StatsPullerManager> pullerManager = new StatsPullerManager();
- m->updateMap(1, {1, 2}, {1, 2}, {String16("p1"), String16("p2")});
+ m->updateMap(1, {1, 2}, {1, 2}, {String16("v1"), String16("v2")},
+ {String16("p1"), String16("p2")}, {String16(""), String16("")});
sp<AlarmMonitor> anomalyAlarmMonitor;
sp<AlarmMonitor> subscriberAlarmMonitor;
int broadcastCount = 0;
@@ -182,7 +183,8 @@
// Setup simple config key corresponding to empty config.
sp<UidMap> m = new UidMap();
sp<StatsPullerManager> pullerManager = new StatsPullerManager();
- m->updateMap(1, {1, 2}, {1, 2}, {String16("p1"), String16("p2")});
+ m->updateMap(1, {1, 2}, {1, 2}, {String16("v1"), String16("v2")},
+ {String16("p1"), String16("p2")}, {String16(""), String16("")});
sp<AlarmMonitor> anomalyAlarmMonitor;
sp<AlarmMonitor> subscriberAlarmMonitor;
int broadcastCount = 0;
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
index 99082cc..f0d9cf1 100644
--- a/cmds/statsd/tests/UidMap_test.cpp
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -71,14 +71,20 @@
vector<int32_t> uids;
vector<int64_t> versions;
vector<String16> apps;
+ vector<String16> versionStrings;
+ vector<String16> installers;
uids.push_back(1000);
uids.push_back(1000);
+ versionStrings.push_back(String16("v1"));
+ versionStrings.push_back(String16("v1"));
+ installers.push_back(String16(""));
+ installers.push_back(String16(""));
apps.push_back(String16(kApp1.c_str()));
apps.push_back(String16(kApp2.c_str()));
versions.push_back(4);
versions.push_back(5);
- m.updateMap(1, uids, versions, apps);
+ m.updateMap(1, uids, versions, versionStrings, apps, installers);
EXPECT_TRUE(m.hasApp(1000, kApp1));
EXPECT_TRUE(m.hasApp(1000, kApp2));
EXPECT_FALSE(m.hasApp(1000, "not.app"));
@@ -97,14 +103,20 @@
vector<int32_t> uids;
vector<int64_t> versions;
vector<String16> apps;
+ vector<String16> versionStrings;
+ vector<String16> installers;
uids.push_back(1000);
uids.push_back(1000);
+ versionStrings.push_back(String16("v1"));
+ versionStrings.push_back(String16("v1"));
+ installers.push_back(String16(""));
+ installers.push_back(String16(""));
apps.push_back(String16(kApp1.c_str()));
apps.push_back(String16(kApp2.c_str()));
versions.push_back(4);
versions.push_back(5);
- m.updateMap(1, uids, versions, apps);
+ m.updateMap(1, uids, versions, versionStrings, apps, installers);
std::set<string> name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
EXPECT_EQ(name_set.size(), 2u);
@@ -112,7 +124,7 @@
EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
// Update the app1 version.
- m.updateApp(2, String16(kApp1.c_str()), 1000, 40);
+ m.updateApp(2, String16(kApp1.c_str()), 1000, 40, String16("v40"), String16(""));
EXPECT_EQ(40, m.getAppVersion(1000, kApp1));
name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
@@ -138,14 +150,15 @@
TEST(UidMapTest, TestUpdateApp) {
UidMap m;
- m.updateMap(1, {1000, 1000}, {4, 5}, {String16(kApp1.c_str()), String16(kApp2.c_str())});
+ m.updateMap(1, {1000, 1000}, {4, 5}, {String16("v4"), String16("v5")},
+ {String16(kApp1.c_str()), String16(kApp2.c_str())}, {String16(""), String16("")});
std::set<string> name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
EXPECT_EQ(name_set.size(), 2u);
EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
// Adds a new name for uid 1000.
- m.updateApp(2, String16("NeW_aPP1_NAmE"), 1000, 40);
+ m.updateApp(2, String16("NeW_aPP1_NAmE"), 1000, 40, String16("v40"), String16(""));
name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
EXPECT_EQ(name_set.size(), 3u);
EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
@@ -154,7 +167,7 @@
EXPECT_TRUE(name_set.find("new_app1_name") != name_set.end());
// This name is also reused by another uid 2000.
- m.updateApp(3, String16("NeW_aPP1_NAmE"), 2000, 1);
+ m.updateApp(3, String16("NeW_aPP1_NAmE"), 2000, 1, String16("v1"), String16(""));
name_set = m.getAppNamesFromUid(2000, true /* returnNormalized */);
EXPECT_EQ(name_set.size(), 1u);
EXPECT_TRUE(name_set.find("NeW_aPP1_NAmE") == name_set.end());
@@ -185,21 +198,26 @@
vector<int32_t> uids;
vector<int64_t> versions;
vector<String16> apps;
+ vector<String16> versionStrings;
+ vector<String16> installers;
uids.push_back(1000);
apps.push_back(String16(kApp2.c_str()));
+ versionStrings.push_back(String16("v1"));
+ installers.push_back(String16(""));
versions.push_back(5);
- m.updateMap(1, uids, versions, apps);
+ m.updateMap(1, uids, versions, versionStrings, apps, installers);
// Set the last timestamp for this config key to be newer.
m.mLastUpdatePerConfigKey[config1] = 2;
ProtoOutputStream proto;
- m.appendUidMap(3, config1, nullptr, &proto);
+ m.appendUidMap(3, config1, nullptr, true, true, &proto);
// Check there's still a uidmap attached this one.
UidMapping results;
protoOutputStreamToUidMapping(&proto, &results);
EXPECT_EQ(1, results.snapshots_size());
+ EXPECT_EQ("v1", results.snapshots(0).package_info(0).version_string());
}
TEST(UidMapTest, TestRemovedAppRetained) {
@@ -209,15 +227,19 @@
m.OnConfigUpdated(config1);
vector<int32_t> uids;
vector<int64_t> versions;
+ vector<String16> versionStrings;
+ vector<String16> installers;
vector<String16> apps;
uids.push_back(1000);
apps.push_back(String16(kApp2.c_str()));
versions.push_back(5);
- m.updateMap(1, uids, versions, apps);
+ versionStrings.push_back(String16("v5"));
+ installers.push_back(String16(""));
+ m.updateMap(1, uids, versions, versionStrings, apps, installers);
m.removeApp(2, String16(kApp2.c_str()), 1000);
ProtoOutputStream proto;
- m.appendUidMap(3, config1, nullptr, &proto);
+ m.appendUidMap(3, config1, nullptr, true, true, &proto);
// Snapshot should still contain this item as deleted.
UidMapping results;
@@ -233,30 +255,34 @@
m.OnConfigUpdated(config1);
vector<int32_t> uids;
vector<int64_t> versions;
+ vector<String16> versionStrings;
+ vector<String16> installers;
vector<String16> apps;
const int maxDeletedApps = StatsdStats::kMaxDeletedAppsInUidMap;
for (int j = 0; j < maxDeletedApps + 10; j++) {
uids.push_back(j);
apps.push_back(String16(kApp1.c_str()));
versions.push_back(j);
+ versionStrings.push_back(String16("v"));
+ installers.push_back(String16(""));
}
- m.updateMap(1, uids, versions, apps);
+ m.updateMap(1, uids, versions, versionStrings, apps, installers);
// First, verify that we have the expected number of items.
UidMapping results;
ProtoOutputStream proto;
- m.appendUidMap(3, config1, nullptr, &proto);
+ m.appendUidMap(3, config1, nullptr, true, true, &proto);
protoOutputStreamToUidMapping(&proto, &results);
EXPECT_EQ(maxDeletedApps + 10, results.snapshots(0).package_info_size());
// Now remove all the apps.
- m.updateMap(1, uids, versions, apps);
+ m.updateMap(1, uids, versions, versionStrings, apps, installers);
for (int j = 0; j < maxDeletedApps + 10; j++) {
m.removeApp(4, String16(kApp1.c_str()), j);
}
proto.clear();
- m.appendUidMap(5, config1, nullptr, &proto);
+ m.appendUidMap(5, config1, nullptr, true, true, &proto);
// Snapshot drops the first nine items.
protoOutputStreamToUidMapping(&proto, &results);
EXPECT_EQ(maxDeletedApps, results.snapshots(0).package_info_size());
@@ -272,6 +298,8 @@
vector<int32_t> uids;
vector<int64_t> versions;
+ vector<String16> versionStrings;
+ vector<String16> installers;
vector<String16> apps;
uids.push_back(1000);
uids.push_back(1000);
@@ -279,45 +307,49 @@
apps.push_back(String16(kApp2.c_str()));
versions.push_back(4);
versions.push_back(5);
- m.updateMap(1, uids, versions, apps);
+ versionStrings.push_back(String16("v4"));
+ versionStrings.push_back(String16("v5"));
+ installers.push_back(String16(""));
+ installers.push_back(String16(""));
+ m.updateMap(1, uids, versions, versionStrings, apps, installers);
ProtoOutputStream proto;
- m.appendUidMap(2, config1, nullptr, &proto);
+ m.appendUidMap(2, config1, nullptr, true, true, &proto);
UidMapping results;
protoOutputStreamToUidMapping(&proto, &results);
EXPECT_EQ(1, results.snapshots_size());
// We have to keep at least one snapshot in memory at all times.
proto.clear();
- m.appendUidMap(2, config1, nullptr, &proto);
+ m.appendUidMap(2, config1, nullptr, true, true, &proto);
protoOutputStreamToUidMapping(&proto, &results);
EXPECT_EQ(1, results.snapshots_size());
// Now add another configuration.
m.OnConfigUpdated(config2);
- m.updateApp(5, String16(kApp1.c_str()), 1000, 40);
+ m.updateApp(5, String16(kApp1.c_str()), 1000, 40, String16("v40"), String16(""));
EXPECT_EQ(1U, m.mChanges.size());
proto.clear();
- m.appendUidMap(6, config1, nullptr, &proto);
+ m.appendUidMap(6, config1, nullptr, true, true, &proto);
protoOutputStreamToUidMapping(&proto, &results);
EXPECT_EQ(1, results.snapshots_size());
EXPECT_EQ(1, results.changes_size());
EXPECT_EQ(1U, m.mChanges.size());
// Add another delta update.
- m.updateApp(7, String16(kApp2.c_str()), 1001, 41);
+ m.updateApp(7, String16(kApp2.c_str()), 1001, 41, String16("v41"), String16(""));
EXPECT_EQ(2U, m.mChanges.size());
// We still can't remove anything.
proto.clear();
- m.appendUidMap(8, config1, nullptr, &proto);
+ m.appendUidMap(8, config1, nullptr, true, true, &proto);
protoOutputStreamToUidMapping(&proto, &results);
EXPECT_EQ(1, results.snapshots_size());
EXPECT_EQ(1, results.changes_size());
EXPECT_EQ(2U, m.mChanges.size());
proto.clear();
- m.appendUidMap(9, config2, nullptr, &proto);
+ m.appendUidMap(9, config2, nullptr, true, true, &proto);
protoOutputStreamToUidMapping(&proto, &results);
EXPECT_EQ(1, results.snapshots_size());
EXPECT_EQ(2, results.changes_size());
@@ -335,19 +367,23 @@
vector<int32_t> uids;
vector<int64_t> versions;
vector<String16> apps;
+ vector<String16> versionStrings;
+ vector<String16> installers;
uids.push_back(1000);
apps.push_back(String16(kApp1.c_str()));
versions.push_back(1);
- m.updateMap(1, uids, versions, apps);
+ versionStrings.push_back(String16("v1"));
+ installers.push_back(String16(""));
+ m.updateMap(1, uids, versions, versionStrings, apps, installers);
- m.updateApp(3, String16(kApp1.c_str()), 1000, 40);
+ m.updateApp(3, String16(kApp1.c_str()), 1000, 40, String16("v40"), String16(""));
ProtoOutputStream proto;
vector<uint8_t> bytes;
- m.appendUidMap(2, config1, nullptr, &proto);
+ m.appendUidMap(2, config1, nullptr, true, true, &proto);
size_t prevBytes = m.mBytesUsed;
- m.appendUidMap(4, config1, nullptr, &proto);
+ m.appendUidMap(4, config1, nullptr, true, true, &proto);
EXPECT_TRUE(m.mBytesUsed < prevBytes);
}
@@ -361,21 +397,27 @@
size_t startBytes = m.mBytesUsed;
vector<int32_t> uids;
vector<int64_t> versions;
+ vector<String16> versionStrings;
+ vector<String16> installers;
vector<String16> apps;
for (int i = 0; i < 100; i++) {
uids.push_back(1);
buf = "EXTREMELY_LONG_STRING_FOR_APP_TO_WASTE_MEMORY." + to_string(i);
apps.push_back(String16(buf.c_str()));
versions.push_back(1);
+ versionStrings.push_back(String16("v1"));
+ installers.push_back(String16(""));
}
- m.updateMap(1, uids, versions, apps);
+ m.updateMap(1, uids, versions, versionStrings, apps, installers);
- m.updateApp(3, String16("EXTREMELY_LONG_STRING_FOR_APP_TO_WASTE_MEMORY.0"), 1000, 2);
+ m.updateApp(3, String16("EXTREMELY_LONG_STRING_FOR_APP_TO_WASTE_MEMORY.0"), 1000, 2,
+ String16("v2"), String16(""));
EXPECT_EQ(1U, m.mChanges.size());
// Now force deletion by limiting the memory to hold one delta change.
- m.maxBytesOverride = 80; // Since the app string alone requires >45 characters.
- m.updateApp(5, String16("EXTREMELY_LONG_STRING_FOR_APP_TO_WASTE_MEMORY.0"), 1000, 4);
+ m.maxBytesOverride = 120; // Since the app string alone requires >45 characters.
+ m.updateApp(5, String16("EXTREMELY_LONG_STRING_FOR_APP_TO_WASTE_MEMORY.0"), 1000, 4,
+ String16("v4"), String16(""));
EXPECT_EQ(1U, m.mChanges.size());
}
diff --git a/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp b/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp
index 5c47af7..a9841c9 100644
--- a/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp
@@ -69,8 +69,10 @@
// Here it assumes that GMS core has two uids.
processor->getUidMap()->updateMap(
1, {222, 444, 111, 333}, {1, 1, 2, 2},
+ {String16("v1"), String16("v1"), String16("v2"), String16("v2")},
{String16("com.android.gmscore"), String16("com.android.gmscore"), String16("app1"),
- String16("APP3")});
+ String16("APP3")},
+ {String16(""), String16(""), String16(""), String16("")});
// GMS core node is in the middle.
std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"),
@@ -215,8 +217,10 @@
// Here it assumes that GMS core has two uids.
processor->getUidMap()->updateMap(
1, {222, 444, 111, 333}, {1, 1, 2, 2},
+ {String16("v1"), String16("v1"), String16("v2"), String16("v2")},
{String16("com.android.gmscore"), String16("com.android.gmscore"), String16("app1"),
- String16("APP3")});
+ String16("APP3")},
+ {String16(""), String16(""), String16(""), String16("")});
// GMS core node is in the middle.
std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"),
diff --git a/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp b/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
index 71afedf..3af8212 100644
--- a/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
+++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
@@ -89,7 +89,7 @@
TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) {
for (const auto& sampling_type :
- { GaugeMetric::ALL_CONDITION_CHANGES, GaugeMetric:: RANDOM_ONE_SAMPLE }) {
+ { GaugeMetric::FIRST_N_SAMPLES, GaugeMetric:: RANDOM_ONE_SAMPLE }) {
auto config = CreateStatsdConfigForPushedEvent(sampling_type);
int64_t bucketStartTimeNs = 10000000000;
int64_t bucketSizeNs =
@@ -170,7 +170,7 @@
data.dimensions_in_what().value_tuple().dimensions_value(0).field());
EXPECT_EQ(appUid1, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
EXPECT_EQ(3, data.bucket_info_size());
- if (sampling_type == GaugeMetric::ALL_CONDITION_CHANGES) {
+ if (sampling_type == GaugeMetric::FIRST_N_SAMPLES) {
EXPECT_EQ(2, data.bucket_info(0).atom_size());
EXPECT_EQ(2, data.bucket_info(0).elapsed_timestamp_nanos_size());
EXPECT_EQ(0, data.bucket_info(0).wall_clock_timestamp_nanos_size());
diff --git a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
index 3cb553f..2b0285b 100644
--- a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
@@ -132,7 +132,8 @@
service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
// This is a new installation, so there shouldn't be a split (should be same as the without
// split case).
- service.mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2);
+ service.mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"),
+ String16(""));
// Goes into the second bucket.
service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 3).get());
@@ -145,11 +146,13 @@
SendConfig(service, MakeConfig());
int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are
// initialized with.
- service.mUidMap->updateMap(start, {1}, {1}, {String16(kApp1.c_str())});
+ service.mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())},
+ {String16("")});
// Force the uidmap to update at timestamp 2.
service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
- service.mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2);
+ service.mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"),
+ String16(""));
// Goes into the second bucket.
service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 3).get());
@@ -168,7 +171,8 @@
SendConfig(service, MakeConfig());
int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are
// initialized with.
- service.mUidMap->updateMap(start, {1}, {1}, {String16(kApp1.c_str())});
+ service.mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())},
+ {String16("")});
// Force the uidmap to update at timestamp 2.
service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
@@ -189,13 +193,14 @@
TEST(PartialBucketE2eTest, TestValueMetricWithoutMinPartialBucket) {
StatsService service(nullptr);
// Partial buckets don't occur when app is first installed.
- service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1);
+ service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
SendConfig(service, MakeValueMetricConfig(0));
int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are
// initialized with.
service.mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
- service.mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2);
+ service.mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2,
+ String16("v2"), String16(""));
ConfigMetricsReport report =
GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100, true);
@@ -206,14 +211,15 @@
TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket) {
StatsService service(nullptr);
// Partial buckets don't occur when app is first installed.
- service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1);
+ service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
SendConfig(service, MakeValueMetricConfig(60 * NS_PER_SEC /* One minute */));
int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are
// initialized with.
const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2;
service.mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
- service.mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2);
+ service.mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"),
+ String16(""));
ConfigMetricsReport report =
GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true);
@@ -229,13 +235,14 @@
TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket) {
StatsService service(nullptr);
// Partial buckets don't occur when app is first installed.
- service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1);
+ service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
SendConfig(service, MakeGaugeMetricConfig(0));
int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are
// initialized with.
service.mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
- service.mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2);
+ service.mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2,
+ String16("v2"), String16(""));
ConfigMetricsReport report =
GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100, true);
@@ -246,14 +253,15 @@
TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket) {
StatsService service(nullptr);
// Partial buckets don't occur when app is first installed.
- service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1);
+ service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
SendConfig(service, MakeGaugeMetricConfig(60 * NS_PER_SEC /* One minute */));
int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are
// initialized with.
const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2;
service.mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
- service.mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2);
+ service.mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"),
+ String16(""));
ConfigMetricsReport report =
GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true);
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index 60bd4a7..67a9f7f 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -589,7 +589,7 @@
GaugeMetric metric;
metric.set_id(metricId);
metric.set_bucket(ONE_MINUTE);
- metric.set_sampling_type(GaugeMetric::ALL_CONDITION_CHANGES);
+ metric.set_sampling_type(GaugeMetric::FIRST_N_SAMPLES);
metric.mutable_gauge_fields_filter()->set_include_all(false);
auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields();
gaugeFieldMatcher->set_field(tagId);
@@ -608,15 +608,6 @@
.WillOnce(Invoke([](int tagId, int64_t timeNs,
vector<std::shared_ptr<LogEvent>>* data) {
data->clear();
- shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 3);
- event->write(3);
- event->init();
- data->push_back(event);
- return true;
- }))
- .WillOnce(Invoke([](int tagId, int64_t timeNs,
- vector<std::shared_ptr<LogEvent>>* data) {
- data->clear();
shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
event->write(4);
event->init();
@@ -631,7 +622,8 @@
event->init();
data->push_back(event);
return true;
- }));
+ }))
+ .WillOnce(Return(true));
int triggerId = 5;
GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
@@ -640,43 +632,28 @@
pullerManager);
vector<shared_ptr<LogEvent>> allData;
- allData.clear();
- EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+ EXPECT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size());
LogEvent trigger(triggerId, bucketStartTimeNs + 10);
trigger.init();
gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger);
- EXPECT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size());
+ EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size());
trigger.setElapsedTimestampNs(bucketStartTimeNs + 20);
gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger);
- EXPECT_EQ(3UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size());
+ EXPECT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size());
+ trigger.setElapsedTimestampNs(bucket2StartTimeNs + 1);
+ gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger);
- allData.clear();
- shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
- event->write(10);
- event->init();
- allData.push_back(event);
-
- gaugeProducer.onDataPulled(allData);
- EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
- auto it = gaugeProducer.mCurrentSlicedBucket->begin()->second.front().mFields->begin();
- EXPECT_EQ(INT, it->mValue.getType());
- EXPECT_EQ(10, it->mValue.int_value);
EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size());
- EXPECT_EQ(3UL, gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms.size());
- EXPECT_EQ(3, gaugeProducer.mPastBuckets.begin()
+ EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms.size());
+ EXPECT_EQ(4, gaugeProducer.mPastBuckets.begin()
->second.back()
.mGaugeAtoms[0]
.mFields->begin()
->mValue.int_value);
- EXPECT_EQ(4, gaugeProducer.mPastBuckets.begin()
- ->second.back()
- .mGaugeAtoms[1]
- .mFields->begin()
- ->mValue.int_value);
EXPECT_EQ(5, gaugeProducer.mPastBuckets.begin()
->second.back()
- .mGaugeAtoms[2]
+ .mGaugeAtoms[1]
.mFields->begin()
->mValue.int_value);
}
@@ -685,7 +662,7 @@
GaugeMetric metric;
metric.set_id(metricId);
metric.set_bucket(ONE_MINUTE);
- metric.set_sampling_type(GaugeMetric::ALL_CONDITION_CHANGES);
+ metric.set_sampling_type(GaugeMetric::FIRST_N_SAMPLES);
metric.mutable_gauge_fields_filter()->set_include_all(true);
auto dimensionMatcher = metric.mutable_dimensions_in_what();
// use field 1 as dimension.
@@ -731,7 +708,8 @@
event->init();
data->push_back(event);
return true;
- }));
+ }))
+ .WillOnce(Return(true));
int triggerId = 5;
GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
@@ -740,30 +718,21 @@
pullerManager);
vector<shared_ptr<LogEvent>> allData;
- allData.clear();
- EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
- LogEvent trigger(triggerId, bucketStartTimeNs + 10);
+ LogEvent trigger(triggerId, bucketStartTimeNs + 3);
trigger.init();
gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger);
+ EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+ trigger.setElapsedTimestampNs(bucketStartTimeNs + 10);
+ gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger);
EXPECT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->size());
EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size());
trigger.setElapsedTimestampNs(bucketStartTimeNs + 20);
gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger);
EXPECT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size());
+ trigger.setElapsedTimestampNs(bucket2StartTimeNs + 1);
+ gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger);
- allData.clear();
- shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
- event->write(4);
- event->write(11);
- event->init();
- allData.push_back(event);
-
- gaugeProducer.onDataPulled(allData);
- EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
- auto it = gaugeProducer.mCurrentSlicedBucket->begin()->second.front().mFields->begin();
- EXPECT_EQ(INT, it->mValue.getType());
- EXPECT_EQ(11, it->mValue.int_value);
EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.size());
auto bucketIt = gaugeProducer.mPastBuckets.begin();
EXPECT_EQ(1UL, bucketIt->second.back().mGaugeAtoms.size());
diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt
index 851e35b..8a770b9 100644
--- a/config/hiddenapi-light-greylist.txt
+++ b/config/hiddenapi-light-greylist.txt
@@ -1320,18 +1320,6 @@
Landroid/security/Credentials;->convertToPem([Ljava/security/cert/Certificate;)[B
Landroid/security/IKeyChainService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/security/IKeyChainService;
Landroid/security/IKeyChainService;->requestPrivateKey(Ljava/lang/String;)Ljava/lang/String;
-Landroid/security/IKeystoreService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/security/IKeystoreService;
-Landroid/security/IKeystoreService;->clear_uid(J)I
-Landroid/security/IKeystoreService;->del(Ljava/lang/String;I)I
-Landroid/security/IKeystoreService;->exist(Ljava/lang/String;I)I
-Landroid/security/IKeystoreService;->generateKey(Ljava/lang/String;Landroid/security/keymaster/KeymasterArguments;[BIILandroid/security/keymaster/KeyCharacteristics;)I
-Landroid/security/IKeystoreService;->get(Ljava/lang/String;I)[B
-Landroid/security/IKeystoreService;->getState(I)I
-Landroid/security/IKeystoreService;->insert(Ljava/lang/String;[BII)I
-Landroid/security/IKeystoreService;->is_hardware_backed(Ljava/lang/String;)I
-Landroid/security/IKeystoreService;->list(Ljava/lang/String;I)[Ljava/lang/String;
-Landroid/security/IKeystoreService;->reset()I
-Landroid/security/IKeystoreService;->ungrant(Ljava/lang/String;I)I
Landroid/security/keymaster/KeymasterBlobArgument;-><init>(ILandroid/os/Parcel;)V
Landroid/security/keymaster/KeymasterBlobArgument;-><init>(I[B)V
Landroid/security/keymaster/KeymasterBlobArgument;->blob:[B
@@ -1343,6 +1331,17 @@
Landroid/security/keymaster/KeymasterLongArgument;-><init>(IJ)V
Landroid/security/keymaster/KeymasterLongArgument;-><init>(ILandroid/os/Parcel;)V
Landroid/security/keymaster/KeymasterLongArgument;->value:J
+Landroid/security/keystore/IKeystoreService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/security/keystore/IKeystoreService;
+Landroid/security/keystore/IKeystoreService;->clear_uid(J)I
+Landroid/security/keystore/IKeystoreService;->del(Ljava/lang/String;I)I
+Landroid/security/keystore/IKeystoreService;->exist(Ljava/lang/String;I)I
+Landroid/security/keystore/IKeystoreService;->get(Ljava/lang/String;I)[B
+Landroid/security/keystore/IKeystoreService;->getState(I)I
+Landroid/security/keystore/IKeystoreService;->insert(Ljava/lang/String;[BII)I
+Landroid/security/keystore/IKeystoreService;->is_hardware_backed(Ljava/lang/String;)I
+Landroid/security/keystore/IKeystoreService;->list(Ljava/lang/String;I)[Ljava/lang/String;
+Landroid/security/keystore/IKeystoreService;->reset()I
+Landroid/security/keystore/IKeystoreService;->ungrant(Ljava/lang/String;I)I
Landroid/service/carrier/ICarrierMessagingCallback$Stub;-><init>()V
Landroid/service/carrier/ICarrierMessagingService;->filterSms(Landroid/service/carrier/MessagePdu;Ljava/lang/String;IILandroid/service/carrier/ICarrierMessagingCallback;)V
Landroid/service/dreams/IDreamManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/service/dreams/IDreamManager;
@@ -3569,7 +3568,6 @@
Lcom/android/internal/telephony/SubscriptionController;->getDefaultSmsSubId()I
Lcom/android/internal/telephony/SubscriptionController;->getDefaultSubId()I
Lcom/android/internal/telephony/SubscriptionController;->getDefaultVoiceSubId()I
-Lcom/android/internal/telephony/SubscriptionController;->getDummySubIds(I)[I
Lcom/android/internal/telephony/SubscriptionController;->getInstance()Lcom/android/internal/telephony/SubscriptionController;
Lcom/android/internal/telephony/SubscriptionController;->getPhoneId(I)I
Lcom/android/internal/telephony/SubscriptionController;->getSubId(I)[I
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 14597ee..550e795 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -6171,6 +6171,7 @@
libcore.reflect.TypeVariableImpl
libcore.reflect.Types
libcore.reflect.WildcardTypeImpl
+libcore.timezone.TimeZoneDataFiles
libcore.util.BasicLruCache
libcore.util.CharsetUtils
libcore.util.CollectionUtils
@@ -6180,7 +6181,6 @@
libcore.util.NativeAllocationRegistry$CleanerThunk
libcore.util.Objects
libcore.util.SneakyThrow
-libcore.util.TimeZoneDataFiles
libcore.util.ZoneInfo
libcore.util.ZoneInfo$CheckedArithmeticException
libcore.util.ZoneInfo$WallTime
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 0e5b976..1edd7f5 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -161,6 +161,13 @@
public abstract List<ProcessMemoryState> getMemoryStateForProcesses();
/**
+ * Returns a list that contains the memory high-water mark for currently running processes.
+ *
+ * Only processes managed by ActivityManagerService are included.
+ */
+ public abstract List<ProcessMemoryHighWaterMark> getMemoryHighWaterMarkForProcesses();
+
+ /**
* Checks to see if the calling pid is allowed to handle the user. Returns adjusted user id as
* needed.
*/
diff --git a/core/java/android/app/AppComponentFactory.java b/core/java/android/app/AppComponentFactory.java
index cfaeec9..ae63291 100644
--- a/core/java/android/app/AppComponentFactory.java
+++ b/core/java/android/app/AppComponentFactory.java
@@ -20,6 +20,7 @@
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
/**
* Interface used to control the instantiation of manifest elements.
@@ -33,6 +34,17 @@
public class AppComponentFactory {
/**
+ * Allows application to override the creation of the default class loader.
+ * This can be used to perform things such as dependency injection or setting up
+ * a custom class loader hierarchy.
+ *
+ * @param cl The default classloader instantiated by platform.
+ */
+ public @NonNull ClassLoader instantiateClassLoader(@NonNull ClassLoader cl) {
+ return cl;
+ }
+
+ /**
* Allows application to override the creation of the application object. This can be used to
* perform things such as dependency injection or class loader changes to these
* classes.
@@ -121,6 +133,19 @@
return (ContentProvider) cl.loadClass(className).newInstance();
}
+ private ApplicationInfo mApplicationInfo = null;
+
+ void setApplicationInfo(ApplicationInfo info) {
+ mApplicationInfo = info;
+ }
+
+ /**
+ * Returns the ApplicationInfo associated with this package.
+ */
+ public ApplicationInfo getApplicationInfo() {
+ return mApplicationInfo;
+ }
+
/**
* @hide
*/
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 2be5dc9..0fb0f16 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -462,9 +462,11 @@
public static final int OP_USE_BIOMETRIC = 78;
/** @hide Physical activity recognition. */
public static final int OP_ACTIVITY_RECOGNITION = 79;
+ /** @hide Financial app sms read. */
+ public static final int OP_SMS_FINANCIAL_TRANSACTIONS = 80;
/** @hide */
@UnsupportedAppUsage
- public static final int _NUM_OP = 80;
+ public static final int _NUM_OP = 81;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -715,6 +717,10 @@
/** @hide Recognize physical activity. */
public static final String OPSTR_ACTIVITY_RECOGNITION = "android:activity_recognition";
+ /** @hide Financial app read sms. */
+ public static final String OPSTR_SMS_FINANCIAL_TRANSACTIONS =
+ "android:sms_financial_transactions";
+
// Warning: If an permission is added here it also has to be added to
// com.android.packageinstaller.permission.utils.EventLogger
private static final int[] RUNTIME_AND_APPOP_PERMISSIONS_OPS = {
@@ -765,6 +771,7 @@
OP_WRITE_SETTINGS,
OP_REQUEST_INSTALL_PACKAGES,
OP_START_FOREGROUND,
+ OP_SMS_FINANCIAL_TRANSACTIONS,
};
/**
@@ -856,6 +863,7 @@
OP_COARSE_LOCATION, // BLUETOOTH_SCAN
OP_USE_BIOMETRIC, // BIOMETRIC
OP_ACTIVITY_RECOGNITION, // ACTIVITY_RECOGNITION
+ OP_SMS_FINANCIAL_TRANSACTIONS, // SMS_FINANCIAL_TRANSACTIONS
};
/**
@@ -942,6 +950,7 @@
OPSTR_BLUETOOTH_SCAN,
OPSTR_USE_BIOMETRIC,
OPSTR_ACTIVITY_RECOGNITION,
+ OPSTR_SMS_FINANCIAL_TRANSACTIONS,
};
/**
@@ -1000,7 +1009,7 @@
"WRITE_WALLPAPER",
"ASSIST_STRUCTURE",
"ASSIST_SCREENSHOT",
- "OP_READ_PHONE_STATE",
+ "READ_PHONE_STATE",
"ADD_VOICEMAIL",
"USE_SIP",
"PROCESS_OUTGOING_CALLS",
@@ -1029,6 +1038,7 @@
"BLUETOOTH_SCAN",
"USE_BIOMETRIC",
"ACTIVITY_RECOGNITION",
+ "SMS_FINANCIAL_TRANSACTIONS",
};
/**
@@ -1117,6 +1127,7 @@
null, // no permission for OP_BLUETOOTH_SCAN
Manifest.permission.USE_BIOMETRIC,
Manifest.permission.ACTIVITY_RECOGNITION,
+ Manifest.permission.SMS_FINANCIAL_TRANSACTIONS,
};
/**
@@ -1205,6 +1216,7 @@
null, // maybe should be UserManager.DISALLOW_SHARE_LOCATION, //BLUETOOTH_SCAN
null, // USE_BIOMETRIC
null, // ACTIVITY_RECOGNITION
+ UserManager.DISALLOW_SMS, // SMS_FINANCIAL_TRANSACTIONS
};
/**
@@ -1292,6 +1304,7 @@
true, // BLUETOOTH_SCAN
false, // USE_BIOMETRIC
false, // ACTIVITY_RECOGNITION
+ false, // SMS_FINANCIAL_TRANSACTIONS
};
/**
@@ -1378,6 +1391,7 @@
AppOpsManager.MODE_ALLOWED, // BLUETOOTH_SCAN
AppOpsManager.MODE_ALLOWED, // USE_BIOMETRIC
AppOpsManager.MODE_ALLOWED, // ACTIVITY_RECOGNITION
+ AppOpsManager.MODE_DEFAULT, // SMS_FINANCIAL_TRANSACTIONS
};
/**
@@ -1468,6 +1482,7 @@
false, // BLUETOOTH_SCAN
false, // USE_BIOMETRIC
false, // ACTIVITY_RECOGNITION
+ false, // SMS_FINANCIAL_TRANSACTIONS
};
/**
@@ -1625,9 +1640,6 @@
case AppOpsManager.OP_READ_CALL_LOG:
case AppOpsManager.OP_WRITE_CALL_LOG:
case AppOpsManager.OP_PROCESS_OUTGOING_CALLS: {
- if (sSmsAndCallLogRestrictionEnabled.get() < 0) {
- startWatchingSmsRestrictionEnabled();
- }
if (sSmsAndCallLogRestrictionEnabled.get() == 1) {
return AppOpsManager.MODE_DEFAULT;
}
@@ -1640,26 +1652,24 @@
private static final AtomicInteger sSmsAndCallLogRestrictionEnabled = new AtomicInteger(-1);
// STOPSHIP b/118520006: Hardcode the default values once the feature is stable.
- private static void startWatchingSmsRestrictionEnabled() {
+ static {
final Context context = ActivityThread.currentApplication();
- if (context == null) {
- // Should never happen
- return;
+ if (context != null) {
+ sSmsAndCallLogRestrictionEnabled.set(ActivityThread.currentActivityThread()
+ .getIntCoreSetting(Settings.Global.SMS_ACCESS_RESTRICTION_ENABLED, 0));
+
+ final Uri uri =
+ Settings.Global.getUriFor(Settings.Global.SMS_ACCESS_RESTRICTION_ENABLED);
+ context.getContentResolver().registerContentObserver(uri, false, new ContentObserver(
+ context.getMainThreadHandler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ sSmsAndCallLogRestrictionEnabled.set(Settings.Global.getInt(
+ context.getContentResolver(),
+ Settings.Global.SMS_ACCESS_RESTRICTION_ENABLED, 0));
+ }
+ });
}
-
- sSmsAndCallLogRestrictionEnabled.set(ActivityThread.currentActivityThread()
- .getIntCoreSetting(Settings.Global.SMS_ACCESS_RESTRICTION_ENABLED, 0));
-
- final Uri uri = Settings.Global.getUriFor(Settings.Global.SMS_ACCESS_RESTRICTION_ENABLED);
- context.getContentResolver().registerContentObserver(uri, false, new ContentObserver(
- context.getMainThreadHandler()) {
- @Override
- public void onChange(boolean selfChange) {
- sSmsAndCallLogRestrictionEnabled.set(Settings.Global.getInt(
- context.getContentResolver(),
- Settings.Global.SMS_ACCESS_RESTRICTION_ENABLED, 0));
- }
- });
}
/**
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index fcd9a05..8bb704d 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2277,6 +2277,15 @@
}
@Override
+ public boolean canSuspendPackage(String packageName) {
+ try {
+ return mPM.canSuspendPackageForUser(packageName, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
public Bundle getSuspendedPackageAppExtras() {
final PersistableBundle extras;
try {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index dc707e8..9837deb 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -140,6 +140,13 @@
throw new ReceiverCallNotAllowedException(
"BroadcastReceiver components are not allowed to bind to services");
}
+
+ @Override
+ public boolean bindIsolatedService(Intent service, ServiceConnection conn, int flags,
+ String instanceName) {
+ throw new ReceiverCallNotAllowedException(
+ "BroadcastReceiver components are not allowed to bind to services");
+ }
}
/**
@@ -1630,14 +1637,25 @@
public boolean bindService(Intent service, ServiceConnection conn,
int flags) {
warnIfCallingFromSystemProcess();
- return bindServiceCommon(service, conn, flags, mMainThread.getHandler(), getUser());
+ return bindServiceCommon(service, conn, flags, null, mMainThread.getHandler(), getUser());
+ }
+
+ @Override
+ public boolean bindIsolatedService(Intent service, ServiceConnection conn,
+ int flags, String instanceName) {
+ warnIfCallingFromSystemProcess();
+ if (instanceName == null) {
+ throw new NullPointerException("null instanceName");
+ }
+ return bindServiceCommon(service, conn, flags, instanceName, mMainThread.getHandler(),
+ getUser());
}
/** @hide */
@Override
public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
UserHandle user) {
- return bindServiceCommon(service, conn, flags, mMainThread.getHandler(), user);
+ return bindServiceCommon(service, conn, flags, null, mMainThread.getHandler(), user);
}
/** @hide */
@@ -1647,7 +1665,7 @@
if (handler == null) {
throw new IllegalArgumentException("handler must not be null.");
}
- return bindServiceCommon(service, conn, flags, handler, user);
+ return bindServiceCommon(service, conn, flags, null, handler, user);
}
/** @hide */
@@ -1669,7 +1687,8 @@
return mMainThread.getHandler();
}
- private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags, Handler
+ private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags,
+ String instanceName, Handler
handler, UserHandle user) {
// Keep this in sync with DevicePolicyManager.bindDeviceAdminServiceAsUser.
IServiceConnection sd;
@@ -1690,10 +1709,10 @@
flags |= BIND_WAIVE_PRIORITY;
}
service.prepareToLeaveProcess(this);
- int res = ActivityManager.getService().bindService(
+ int res = ActivityManager.getService().bindIsolatedService(
mMainThread.getApplicationThread(), getActivityToken(), service,
service.resolveTypeIfNeeded(getContentResolver()),
- sd, flags, getOpPackageName(), user.getIdentifier());
+ sd, flags, instanceName, getOpPackageName(), user.getIdentifier());
if (res < 0) {
throw new SecurityException(
"Not allowed to bind to service " + service);
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index e2312a5..f27c667 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -133,9 +133,13 @@
in String resolvedType, boolean requireForeground, in String callingPackage, int userId);
int stopService(in IApplicationThread caller, in Intent service,
in String resolvedType, int userId);
+ // Currently keeping old bindService because it is on the greylist
int bindService(in IApplicationThread caller, in IBinder token, in Intent service,
in String resolvedType, in IServiceConnection connection, int flags,
in String callingPackage, int userId);
+ int bindIsolatedService(in IApplicationThread caller, in IBinder token, in Intent service,
+ in String resolvedType, in IServiceConnection connection, int flags,
+ in String instanceName, in String callingPackage, int userId);
boolean unbindService(in IServiceConnection connection);
void publishService(in IBinder token, in Intent intent, in IBinder service);
void setDebugApp(in String packageName, boolean waitForDebugger, boolean persistent);
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index bd9cf6d..e508d42 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -88,6 +88,8 @@
ParceledListSlice getRecentNotifyingAppsForUser(int userId);
int getBlockedAppCount(int userId);
boolean areChannelsBypassingDnd();
+ int getAppsBypassingDndCount(int uid);
+ ParceledListSlice getNotificationChannelsBypassingDnd(String pkg, int userId);
// TODO: Remove this when callers have been migrated to the equivalent
// INotificationListener method.
@@ -172,4 +174,7 @@
void revokeNotificationDelegate(String callingPkg);
String getNotificationDelegate(String callingPkg);
boolean canNotifyAsPackage(String callingPkg, String targetPkg);
+
+ void setPrivateNotificationsAllowed(boolean allow);
+ boolean getPrivateNotificationsAllowed();
}
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 853fccf..f8309bc 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -62,6 +62,7 @@
private final IWindowManager mWM;
private final IActivityManager mAm;
private final ITrustManager mTrustManager;
+ private final INotificationManager mNotificationManager;
/**
* Intent used to prompt user for device credentials.
@@ -219,6 +220,45 @@
return intent;
}
+ /**
+ * Controls whether notifications can be shown atop a securely locked screen in their full
+ * private form (same as when the device is unlocked).
+ *
+ * <p>Other sources like the DevicePolicyManger and Settings app can modify this configuration.
+ * The result is that private notifications are only shown if all sources allow it.
+ *
+ * @param allow secure notifications can be shown if {@code true},
+ * secure notifications cannot be shown if {@code false}
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)
+ @SystemApi
+ public void setPrivateNotificationsAllowed(boolean allow) {
+ try {
+ mNotificationManager.setPrivateNotificationsAllowed(allow);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns whether notifications can be shown atop a securely locked screen in their full
+ * private form (same as when the device is unlocked).
+ *
+ * @return {@code true} if secure notifications can be shown, {@code false} otherwise.
+ * By default, private notifications are allowed.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)
+ @SystemApi
+ public boolean getPrivateNotificationsAllowed() {
+ try {
+ return mNotificationManager.getPrivateNotificationsAllowed();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private String getSettingsPackageForIntent(Intent intent) {
List<ResolveInfo> resolveInfos = mContext.getPackageManager()
.queryIntentActivities(intent, PackageManager.MATCH_SYSTEM_ONLY);
@@ -335,6 +375,8 @@
mAm = ActivityManager.getService();
mTrustManager = ITrustManager.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.TRUST_SERVICE));
+ mNotificationManager = INotificationManager.Stub.asInterface(
+ ServiceManager.getServiceOrThrow(Context.NOTIFICATION_SERVICE));
}
/**
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index b827d01..da4f77b 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -117,6 +117,7 @@
private File mCredentialProtectedDataDirFile;
@UnsupportedAppUsage
private final ClassLoader mBaseClassLoader;
+ private ClassLoader mDefaultClassLoader;
private final boolean mSecurityViolation;
private final boolean mIncludeCode;
private final boolean mRegisterPackage;
@@ -224,9 +225,10 @@
mSecurityViolation = false;
mIncludeCode = true;
mRegisterPackage = false;
- mClassLoader = ClassLoader.getSystemClassLoader();
mResources = Resources.getSystem();
- mAppComponentFactory = createAppFactory(mApplicationInfo, mClassLoader);
+ mDefaultClassLoader = ClassLoader.getSystemClassLoader();
+ mAppComponentFactory = createAppFactory(mApplicationInfo, mDefaultClassLoader);
+ mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader);
}
/**
@@ -235,15 +237,21 @@
void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) {
assert info.packageName.equals("android");
mApplicationInfo = info;
- mClassLoader = classLoader;
- mAppComponentFactory = createAppFactory(info, classLoader);
+ mDefaultClassLoader = classLoader;
+ mAppComponentFactory = createAppFactory(info, mDefaultClassLoader);
+ mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader);
}
private AppComponentFactory createAppFactory(ApplicationInfo appInfo, ClassLoader cl) {
if (appInfo.appComponentFactory != null && cl != null) {
try {
- return (AppComponentFactory) cl.loadClass(appInfo.appComponentFactory)
- .newInstance();
+ AppComponentFactory factory = (AppComponentFactory) cl.loadClass(
+ appInfo.appComponentFactory).newInstance();
+ // Pass a copy of ApplicationInfo to the factory. Copying protects the framework
+ // from apps which would override the factory and change ApplicationInfo contents.
+ // ApplicationInfo is used to set up the default class loader.
+ factory.setApplicationInfo(new ApplicationInfo(appInfo));
+ return factory;
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
Slog.e(TAG, "Unable to instantiate appComponentFactory", e);
}
@@ -357,7 +365,7 @@
getClassLoader());
}
}
- mAppComponentFactory = createAppFactory(aInfo, mClassLoader);
+ mAppComponentFactory = createAppFactory(aInfo, mDefaultClassLoader);
}
private void setApplicationInfo(ApplicationInfo aInfo) {
@@ -633,11 +641,12 @@
}
if (mBaseClassLoader != null) {
- mClassLoader = mBaseClassLoader;
+ mDefaultClassLoader = mBaseClassLoader;
} else {
- mClassLoader = ClassLoader.getSystemClassLoader();
+ mDefaultClassLoader = ClassLoader.getSystemClassLoader();
}
- mAppComponentFactory = createAppFactory(mApplicationInfo, mClassLoader);
+ mAppComponentFactory = createAppFactory(mApplicationInfo, mDefaultClassLoader);
+ mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader);
return;
}
@@ -715,9 +724,9 @@
// call System.loadLibrary() on a classloader from a LoadedApk with
// mIncludeCode == false).
if (!mIncludeCode) {
- if (mClassLoader == null) {
+ if (mDefaultClassLoader == null) {
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
- mClassLoader = ApplicationLoaders.getDefault().getClassLoader(
+ mDefaultClassLoader = ApplicationLoaders.getDefault().getClassLoader(
"" /* codePath */, mApplicationInfo.targetSdkVersion, isBundledApp,
librarySearchPath, libraryPermittedPath, mBaseClassLoader,
null /* classLoaderName */);
@@ -725,6 +734,10 @@
mAppComponentFactory = AppComponentFactory.DEFAULT;
}
+ if (mClassLoader == null) {
+ mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader);
+ }
+
return;
}
@@ -741,16 +754,16 @@
", JNI path: " + librarySearchPath);
boolean needToSetupJitProfiles = false;
- if (mClassLoader == null) {
+ if (mDefaultClassLoader == null) {
// Temporarily disable logging of disk reads on the Looper thread
// as this is early and necessary.
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
- mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip,
+ mDefaultClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip,
mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
libraryPermittedPath, mBaseClassLoader,
mApplicationInfo.classLoaderName);
- mAppComponentFactory = createAppFactory(mApplicationInfo, mClassLoader);
+ mAppComponentFactory = createAppFactory(mApplicationInfo, mDefaultClassLoader);
StrictMode.setThreadPolicy(oldPolicy);
// Setup the class loader paths for profiling.
@@ -761,7 +774,7 @@
// Temporarily disable logging of disk reads on the Looper thread as this is necessary
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
try {
- ApplicationLoaders.getDefault().addNative(mClassLoader, libPaths);
+ ApplicationLoaders.getDefault().addNative(mDefaultClassLoader, libPaths);
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
@@ -799,7 +812,7 @@
if (!extraLibPaths.isEmpty()) {
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
try {
- ApplicationLoaders.getDefault().addNative(mClassLoader, extraLibPaths);
+ ApplicationLoaders.getDefault().addNative(mDefaultClassLoader, extraLibPaths);
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
@@ -807,7 +820,7 @@
if (addedPaths != null && addedPaths.size() > 0) {
final String add = TextUtils.join(File.pathSeparator, addedPaths);
- ApplicationLoaders.getDefault().addPath(mClassLoader, add);
+ ApplicationLoaders.getDefault().addPath(mDefaultClassLoader, add);
// Setup the new code paths for profiling.
needToSetupJitProfiles = true;
}
@@ -824,6 +837,13 @@
if (needToSetupJitProfiles && !ActivityThread.isSystem()) {
setupJitProfileSupport();
}
+
+ // Call AppComponentFactory to select/create the main class loader of this app.
+ // Since this may call code in the app, mDefaultClassLoader must be fully set up
+ // before invoking the factory.
+ if (mClassLoader == null) {
+ mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader);
+ }
}
@UnsupportedAppUsage
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index df37a02..450efdf 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -207,7 +207,8 @@
private static final int MAX_REPLY_HISTORY = 5;
/**
- * Maximum numbers of action buttons in a notification.
+ * Maximum number of (generic) action buttons in a notification (contextual action buttons are
+ * handled separately).
* @hide
*/
public static final int MAX_ACTION_BUTTONS = 3;
@@ -1421,6 +1422,12 @@
*/
public static final int SEMANTIC_ACTION_CALL = 10;
+ /**
+ * {@code SemanticAction}: Contextual action - dependent on the current notification. E.g.
+ * open a Map application with an address shown in the notification.
+ */
+ public static final int SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION = 11;
+
private final Bundle mExtras;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private Icon mIcon;
@@ -2042,7 +2049,8 @@
SEMANTIC_ACTION_UNMUTE,
SEMANTIC_ACTION_THUMBS_UP,
SEMANTIC_ACTION_THUMBS_DOWN,
- SEMANTIC_ACTION_CALL
+ SEMANTIC_ACTION_CALL,
+ SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION
})
@Retention(RetentionPolicy.SOURCE)
public @interface SemanticAction {}
@@ -4962,6 +4970,18 @@
result);
}
+ private static List<Notification.Action> filterOutContextualActions(
+ List<Notification.Action> actions) {
+ List<Notification.Action> nonContextualActions = new ArrayList<>();
+ for (Notification.Action action : actions) {
+ if (action.getSemanticAction()
+ != Action.SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION) {
+ nonContextualActions.add(action);
+ }
+ }
+ return nonContextualActions;
+ }
+
private RemoteViews applyStandardTemplateWithActions(int layoutId,
StandardTemplateParams p, TemplateBindResult result) {
RemoteViews big = applyStandardTemplate(layoutId, p, result);
@@ -4970,7 +4990,11 @@
boolean validRemoteInput = false;
- int N = mActions.size();
+ // In the UI contextual actions appear separately from the standard actions, so we
+ // filter them out here.
+ List<Notification.Action> nonContextualActions = filterOutContextualActions(mActions);
+
+ int N = nonContextualActions.size();
boolean emphazisedMode = mN.fullScreenIntent != null && !p.ambient;
big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode);
if (N > 0) {
@@ -4979,7 +5003,8 @@
big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 0);
if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS;
for (int i=0; i<N; i++) {
- Action action = mActions.get(i);
+ Action action = nonContextualActions.get(i);
+
boolean actionHasValidInput = hasValidRemoteInput(action);
validRemoteInput |= actionHasValidInput;
diff --git a/core/java/android/app/ProcessMemoryHighWaterMark.java b/core/java/android/app/ProcessMemoryHighWaterMark.java
new file mode 100644
index 0000000..5fea8ef
--- /dev/null
+++ b/core/java/android/app/ProcessMemoryHighWaterMark.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The memory high-water mark value for a process.
+ * {@hide}
+ */
+public final class ProcessMemoryHighWaterMark implements Parcelable {
+ public final int uid;
+ public final String processName;
+ public final long rssHighWaterMarkInBytes;
+
+ public ProcessMemoryHighWaterMark(int uid, String processName, long rssHighWaterMarkInBytes) {
+ this.uid = uid;
+ this.processName = processName;
+ this.rssHighWaterMarkInBytes = rssHighWaterMarkInBytes;
+ }
+
+ private ProcessMemoryHighWaterMark(Parcel in) {
+ uid = in.readInt();
+ processName = in.readString();
+ rssHighWaterMarkInBytes = in.readLong();
+ }
+
+ public static final Creator<ProcessMemoryHighWaterMark> CREATOR =
+ new Creator<ProcessMemoryHighWaterMark>() {
+ @Override
+ public ProcessMemoryHighWaterMark createFromParcel(Parcel in) {
+ return new ProcessMemoryHighWaterMark(in);
+ }
+
+ @Override
+ public ProcessMemoryHighWaterMark[] newArray(int size) {
+ return new ProcessMemoryHighWaterMark[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(uid);
+ parcel.writeString(processName);
+ parcel.writeLong(rssHighWaterMarkInBytes);
+ }
+}
diff --git a/core/java/android/app/ProcessMemoryState.java b/core/java/android/app/ProcessMemoryState.java
index d149243..9df4fff 100644
--- a/core/java/android/app/ProcessMemoryState.java
+++ b/core/java/android/app/ProcessMemoryState.java
@@ -32,6 +32,7 @@
public final long rssInBytes;
public final long cacheInBytes;
public final long swapInBytes;
+ // TODO(rslawik): Delete this field once ProcessMemoryHighWaterMark is ready.
public final long rssHighWatermarkInBytes;
public final long startTimeNanos;
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 4de1dfc..949cdd6 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -42,6 +42,7 @@
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.DeadObjectException;
@@ -3243,8 +3244,18 @@
Objects.requireNonNull(uri);
Objects.requireNonNull(size);
+ // Older apps might be relying on mutable results, so only consider
+ // giving them hardware bitmaps once they target Q or higher. If modern
+ // apps need mutable thumbnails, they can always roll their own logic.
+ final int allocator;
+ if (getTargetSdkVersion() >= Build.VERSION_CODES.Q) {
+ allocator = ImageDecoder.ALLOCATOR_DEFAULT;
+ } else {
+ allocator = ImageDecoder.ALLOCATOR_SOFTWARE;
+ }
+
try (ContentProviderClient client = acquireContentProviderClient(uri)) {
- return loadThumbnail(client, uri, size, signal, ImageDecoder.ALLOCATOR_DEFAULT);
+ return loadThumbnail(client, uri, size, signal, allocator);
}
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 9f8ae0b..004417b 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2906,8 +2906,9 @@
* @param flags Operation options for the binding. May be 0,
* {@link #BIND_AUTO_CREATE}, {@link #BIND_DEBUG_UNBIND},
* {@link #BIND_NOT_FOREGROUND}, {@link #BIND_ABOVE_CLIENT},
- * {@link #BIND_ALLOW_OOM_MANAGEMENT}, or
- * {@link #BIND_WAIVE_PRIORITY}.
+ * {@link #BIND_ALLOW_OOM_MANAGEMENT}, {@link #BIND_WAIVE_PRIORITY}.
+ * {@link #BIND_IMPORTANT}, or
+ * {@link #BIND_ADJUST_WITH_ACTIVITY}.
* @return {@code true} if the system is in the process of bringing up a
* service that your client has permission to bind to; {@code false}
* if the system couldn't find the service or if your client doesn't
@@ -2923,11 +2924,38 @@
* @see #BIND_AUTO_CREATE
* @see #BIND_DEBUG_UNBIND
* @see #BIND_NOT_FOREGROUND
+ * @see #BIND_ABOVE_CLIENT
+ * @see #BIND_ALLOW_OOM_MANAGEMENT
+ * @see #BIND_WAIVE_PRIORITY
+ * @see #BIND_IMPORTANT
+ * @see #BIND_ADJUST_WITH_ACTIVITY
*/
public abstract boolean bindService(@RequiresPermission Intent service,
@NonNull ServiceConnection conn, @BindServiceFlags int flags);
/**
+ * Variation of {@link #bindService} that, in the specific case of isolated
+ * services, allows the caller to generate multiple instances of a service
+ * from a single component declaration.
+ *
+ * @param service Identifies the service to connect to. The Intent must
+ * specify an explicit component name.
+ * @param conn Receives information as the service is started and stopped.
+ * This must be a valid ServiceConnection object; it must not be null.
+ * @param flags Operation options for the binding as per {@link #bindService}.
+ * @param instanceName Unique identifier for the service instance. Each unique
+ * name here will result in a different service instance being created.
+ * @return Returns success of binding as per {@link #bindService}.
+ *
+ * @throws SecurityException If the caller does not have permission to access the service
+ *
+ * @see #bindService
+ */
+ public abstract boolean bindIsolatedService(@RequiresPermission Intent service,
+ @NonNull ServiceConnection conn, @BindServiceFlags int flags,
+ @NonNull String instanceName);
+
+ /**
* Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userHandle
* argument for use by system server and other multi-user aware code.
* @hide
@@ -2941,7 +2969,7 @@
}
/**
- * Same as {@link #bindService(Intent, ServiceConnection, int, UserHandle)}, but with an
+ * Same as {@link #bindServiceAsUser(Intent, ServiceConnection, int, UserHandle)}, but with an
* explicit non-null Handler to run the ServiceConnection callbacks on.
*
* @hide
@@ -4307,6 +4335,16 @@
/**
* Use with {@link #getSystemService(String)} to retrieve a
+ * {android.os.IIdmap2} for managing idmap files (used by overlay
+ * packages).
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ public static final String IDMAP_SERVICE = "idmap";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
* {@link VrManager} for accessing the VR service.
*
* @see #getSystemService(String)
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index bfad2b4..88696b0 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -705,6 +705,12 @@
return mBase.bindService(service, conn, flags);
}
+ @Override
+ public boolean bindIsolatedService(Intent service, ServiceConnection conn,
+ int flags, String instanceName) {
+ return mBase.bindIsolatedService(service, conn, flags, instanceName);
+ }
+
/** @hide */
@Override
public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 02f38a7..10c6bc6 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5229,6 +5229,17 @@
public static final String EXTRA_QUIET_MODE = "android.intent.extra.QUIET_MODE";
/**
+ * Optional CharSequence extra to provide a search query.
+ *
+ * <p>Applicable to {@link Intent} with actions:
+ * <ul>
+ * <li>{@link Intent#ACTION_GET_CONTENT}</li>
+ * <li>{@link Intent#ACTION_OPEN_DOCUMENT}</li>
+ * </ul>
+ */
+ public static final String EXTRA_CONTENT_QUERY = "android.intent.extra.CONTENT_QUERY";
+
+ /**
* Used as an int extra field in {@link #ACTION_MEDIA_RESOURCE_GRANTED}
* intents to specify the resource type granted. Possible values are
* {@link #EXTRA_MEDIA_RESOURCE_TYPE_VIDEO_CODEC} or
@@ -7508,7 +7519,7 @@
*
* @see #putExtra(String, String)
*/
- public String getStringExtra(String name) {
+ public @Nullable String getStringExtra(String name) {
return mExtras == null ? null : mExtras.getString(name);
}
@@ -7522,7 +7533,7 @@
*
* @see #putExtra(String, CharSequence)
*/
- public CharSequence getCharSequenceExtra(String name) {
+ public @Nullable CharSequence getCharSequenceExtra(String name) {
return mExtras == null ? null : mExtras.getCharSequence(name);
}
@@ -7536,7 +7547,7 @@
*
* @see #putExtra(String, Parcelable)
*/
- public <T extends Parcelable> T getParcelableExtra(String name) {
+ public @Nullable <T extends Parcelable> T getParcelableExtra(String name) {
return mExtras == null ? null : mExtras.<T>getParcelable(name);
}
@@ -7550,7 +7561,7 @@
*
* @see #putExtra(String, Parcelable[])
*/
- public Parcelable[] getParcelableArrayExtra(String name) {
+ public @Nullable Parcelable[] getParcelableArrayExtra(String name) {
return mExtras == null ? null : mExtras.getParcelableArray(name);
}
@@ -7565,7 +7576,7 @@
*
* @see #putParcelableArrayListExtra(String, ArrayList)
*/
- public <T extends Parcelable> ArrayList<T> getParcelableArrayListExtra(String name) {
+ public @Nullable <T extends Parcelable> ArrayList<T> getParcelableArrayListExtra(String name) {
return mExtras == null ? null : mExtras.<T>getParcelableArrayList(name);
}
@@ -7579,7 +7590,7 @@
*
* @see #putExtra(String, Serializable)
*/
- public Serializable getSerializableExtra(String name) {
+ public @Nullable Serializable getSerializableExtra(String name) {
return mExtras == null ? null : mExtras.getSerializable(name);
}
@@ -7594,7 +7605,7 @@
*
* @see #putIntegerArrayListExtra(String, ArrayList)
*/
- public ArrayList<Integer> getIntegerArrayListExtra(String name) {
+ public @Nullable ArrayList<Integer> getIntegerArrayListExtra(String name) {
return mExtras == null ? null : mExtras.getIntegerArrayList(name);
}
@@ -7609,7 +7620,7 @@
*
* @see #putStringArrayListExtra(String, ArrayList)
*/
- public ArrayList<String> getStringArrayListExtra(String name) {
+ public @Nullable ArrayList<String> getStringArrayListExtra(String name) {
return mExtras == null ? null : mExtras.getStringArrayList(name);
}
@@ -7624,7 +7635,7 @@
*
* @see #putCharSequenceArrayListExtra(String, ArrayList)
*/
- public ArrayList<CharSequence> getCharSequenceArrayListExtra(String name) {
+ public @Nullable ArrayList<CharSequence> getCharSequenceArrayListExtra(String name) {
return mExtras == null ? null : mExtras.getCharSequenceArrayList(name);
}
@@ -7638,7 +7649,7 @@
*
* @see #putExtra(String, boolean[])
*/
- public boolean[] getBooleanArrayExtra(String name) {
+ public @Nullable boolean[] getBooleanArrayExtra(String name) {
return mExtras == null ? null : mExtras.getBooleanArray(name);
}
@@ -7652,7 +7663,7 @@
*
* @see #putExtra(String, byte[])
*/
- public byte[] getByteArrayExtra(String name) {
+ public @Nullable byte[] getByteArrayExtra(String name) {
return mExtras == null ? null : mExtras.getByteArray(name);
}
@@ -7666,7 +7677,7 @@
*
* @see #putExtra(String, short[])
*/
- public short[] getShortArrayExtra(String name) {
+ public @Nullable short[] getShortArrayExtra(String name) {
return mExtras == null ? null : mExtras.getShortArray(name);
}
@@ -7680,7 +7691,7 @@
*
* @see #putExtra(String, char[])
*/
- public char[] getCharArrayExtra(String name) {
+ public @Nullable char[] getCharArrayExtra(String name) {
return mExtras == null ? null : mExtras.getCharArray(name);
}
@@ -7694,7 +7705,7 @@
*
* @see #putExtra(String, int[])
*/
- public int[] getIntArrayExtra(String name) {
+ public @Nullable int[] getIntArrayExtra(String name) {
return mExtras == null ? null : mExtras.getIntArray(name);
}
@@ -7708,7 +7719,7 @@
*
* @see #putExtra(String, long[])
*/
- public long[] getLongArrayExtra(String name) {
+ public @Nullable long[] getLongArrayExtra(String name) {
return mExtras == null ? null : mExtras.getLongArray(name);
}
@@ -7722,7 +7733,7 @@
*
* @see #putExtra(String, float[])
*/
- public float[] getFloatArrayExtra(String name) {
+ public @Nullable float[] getFloatArrayExtra(String name) {
return mExtras == null ? null : mExtras.getFloatArray(name);
}
@@ -7736,7 +7747,7 @@
*
* @see #putExtra(String, double[])
*/
- public double[] getDoubleArrayExtra(String name) {
+ public @Nullable double[] getDoubleArrayExtra(String name) {
return mExtras == null ? null : mExtras.getDoubleArray(name);
}
@@ -7750,7 +7761,7 @@
*
* @see #putExtra(String, String[])
*/
- public String[] getStringArrayExtra(String name) {
+ public @Nullable String[] getStringArrayExtra(String name) {
return mExtras == null ? null : mExtras.getStringArray(name);
}
@@ -7764,7 +7775,7 @@
*
* @see #putExtra(String, CharSequence[])
*/
- public CharSequence[] getCharSequenceArrayExtra(String name) {
+ public @Nullable CharSequence[] getCharSequenceArrayExtra(String name) {
return mExtras == null ? null : mExtras.getCharSequenceArray(name);
}
@@ -7778,7 +7789,7 @@
*
* @see #putExtra(String, Bundle)
*/
- public Bundle getBundleExtra(String name) {
+ public @Nullable Bundle getBundleExtra(String name) {
return mExtras == null ? null : mExtras.getBundle(name);
}
@@ -8584,7 +8595,7 @@
* @see #removeExtra
* @see #getStringExtra(String)
*/
- public @NonNull Intent putExtra(String name, String value) {
+ public @NonNull Intent putExtra(String name, @Nullable String value) {
if (mExtras == null) {
mExtras = new Bundle();
}
@@ -8607,7 +8618,7 @@
* @see #removeExtra
* @see #getCharSequenceExtra(String)
*/
- public @NonNull Intent putExtra(String name, CharSequence value) {
+ public @NonNull Intent putExtra(String name, @Nullable CharSequence value) {
if (mExtras == null) {
mExtras = new Bundle();
}
@@ -8630,7 +8641,7 @@
* @see #removeExtra
* @see #getParcelableExtra(String)
*/
- public @NonNull Intent putExtra(String name, Parcelable value) {
+ public @NonNull Intent putExtra(String name, @Nullable Parcelable value) {
if (mExtras == null) {
mExtras = new Bundle();
}
@@ -8653,7 +8664,7 @@
* @see #removeExtra
* @see #getParcelableArrayExtra(String)
*/
- public @NonNull Intent putExtra(String name, Parcelable[] value) {
+ public @NonNull Intent putExtra(String name, @Nullable Parcelable[] value) {
if (mExtras == null) {
mExtras = new Bundle();
}
@@ -8677,7 +8688,7 @@
* @see #getParcelableArrayListExtra(String)
*/
public @NonNull Intent putParcelableArrayListExtra(String name,
- ArrayList<? extends Parcelable> value) {
+ @Nullable ArrayList<? extends Parcelable> value) {
if (mExtras == null) {
mExtras = new Bundle();
}
@@ -8700,7 +8711,8 @@
* @see #removeExtra
* @see #getIntegerArrayListExtra(String)
*/
- public @NonNull Intent putIntegerArrayListExtra(String name, ArrayList<Integer> value) {
+ public @NonNull Intent putIntegerArrayListExtra(String name,
+ @Nullable ArrayList<Integer> value) {
if (mExtras == null) {
mExtras = new Bundle();
}
@@ -8723,7 +8735,7 @@
* @see #removeExtra
* @see #getStringArrayListExtra(String)
*/
- public @NonNull Intent putStringArrayListExtra(String name, ArrayList<String> value) {
+ public @NonNull Intent putStringArrayListExtra(String name, @Nullable ArrayList<String> value) {
if (mExtras == null) {
mExtras = new Bundle();
}
@@ -8747,7 +8759,7 @@
* @see #getCharSequenceArrayListExtra(String)
*/
public @NonNull Intent putCharSequenceArrayListExtra(String name,
- ArrayList<CharSequence> value) {
+ @Nullable ArrayList<CharSequence> value) {
if (mExtras == null) {
mExtras = new Bundle();
}
@@ -8770,7 +8782,7 @@
* @see #removeExtra
* @see #getSerializableExtra(String)
*/
- public @NonNull Intent putExtra(String name, Serializable value) {
+ public @NonNull Intent putExtra(String name, @Nullable Serializable value) {
if (mExtras == null) {
mExtras = new Bundle();
}
@@ -8793,7 +8805,7 @@
* @see #removeExtra
* @see #getBooleanArrayExtra(String)
*/
- public @NonNull Intent putExtra(String name, boolean[] value) {
+ public @NonNull Intent putExtra(String name, @Nullable boolean[] value) {
if (mExtras == null) {
mExtras = new Bundle();
}
@@ -8816,7 +8828,7 @@
* @see #removeExtra
* @see #getByteArrayExtra(String)
*/
- public @NonNull Intent putExtra(String name, byte[] value) {
+ public @NonNull Intent putExtra(String name, @Nullable byte[] value) {
if (mExtras == null) {
mExtras = new Bundle();
}
@@ -8839,7 +8851,7 @@
* @see #removeExtra
* @see #getShortArrayExtra(String)
*/
- public @NonNull Intent putExtra(String name, short[] value) {
+ public @NonNull Intent putExtra(String name, @Nullable short[] value) {
if (mExtras == null) {
mExtras = new Bundle();
}
@@ -8862,7 +8874,7 @@
* @see #removeExtra
* @see #getCharArrayExtra(String)
*/
- public @NonNull Intent putExtra(String name, char[] value) {
+ public @NonNull Intent putExtra(String name, @Nullable char[] value) {
if (mExtras == null) {
mExtras = new Bundle();
}
@@ -8885,7 +8897,7 @@
* @see #removeExtra
* @see #getIntArrayExtra(String)
*/
- public @NonNull Intent putExtra(String name, int[] value) {
+ public @NonNull Intent putExtra(String name, @Nullable int[] value) {
if (mExtras == null) {
mExtras = new Bundle();
}
@@ -8908,7 +8920,7 @@
* @see #removeExtra
* @see #getLongArrayExtra(String)
*/
- public @NonNull Intent putExtra(String name, long[] value) {
+ public @NonNull Intent putExtra(String name, @Nullable long[] value) {
if (mExtras == null) {
mExtras = new Bundle();
}
@@ -8931,7 +8943,7 @@
* @see #removeExtra
* @see #getFloatArrayExtra(String)
*/
- public @NonNull Intent putExtra(String name, float[] value) {
+ public @NonNull Intent putExtra(String name, @Nullable float[] value) {
if (mExtras == null) {
mExtras = new Bundle();
}
@@ -8954,7 +8966,7 @@
* @see #removeExtra
* @see #getDoubleArrayExtra(String)
*/
- public @NonNull Intent putExtra(String name, double[] value) {
+ public @NonNull Intent putExtra(String name, @Nullable double[] value) {
if (mExtras == null) {
mExtras = new Bundle();
}
@@ -8977,7 +8989,7 @@
* @see #removeExtra
* @see #getStringArrayExtra(String)
*/
- public @NonNull Intent putExtra(String name, String[] value) {
+ public @NonNull Intent putExtra(String name, @Nullable String[] value) {
if (mExtras == null) {
mExtras = new Bundle();
}
@@ -9000,7 +9012,7 @@
* @see #removeExtra
* @see #getCharSequenceArrayExtra(String)
*/
- public @NonNull Intent putExtra(String name, CharSequence[] value) {
+ public @NonNull Intent putExtra(String name, @Nullable CharSequence[] value) {
if (mExtras == null) {
mExtras = new Bundle();
}
@@ -9023,7 +9035,7 @@
* @see #removeExtra
* @see #getBundleExtra(String)
*/
- public @NonNull Intent putExtra(String name, Bundle value) {
+ public @NonNull Intent putExtra(String name, @Nullable Bundle value) {
if (mExtras == null) {
mExtras = new Bundle();
}
diff --git a/core/java/android/content/MimeTypeFilter.java b/core/java/android/content/MimeTypeFilter.java
new file mode 100644
index 0000000..1c26fd9
--- /dev/null
+++ b/core/java/android/content/MimeTypeFilter.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.content;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.ArrayList;
+
+/**
+ * Provides utility methods for matching MIME type filters used in ContentProvider.
+ *
+ * <p>Wildcards are allowed only instead of the entire type or subtype with a tree prefix.
+ * Eg. image\/*, *\/* is a valid filter and will match image/jpeg, but image/j* is invalid and
+ * it will not match image/jpeg. Suffixes and parameters are not supported, and they are treated
+ * as part of the subtype during matching. Neither type nor subtype can be empty.
+ *
+ * <p><em>Note: MIME type matching in the Android framework is case-sensitive, unlike the formal
+ * RFC definitions. As a result, you should always write these elements with lower case letters,
+ * or use {@link android.content.Intent#normalizeMimeType} to ensure that they are converted to
+ * lower case.</em>
+ *
+ * <p>MIME types can be null or ill-formatted. In such case they won't match anything.
+ *
+ * <p>MIME type filters must be correctly formatted, or an exception will be thrown.
+ * Copied from support library.
+ * {@hide}
+ */
+public final class MimeTypeFilter {
+
+ private MimeTypeFilter() {
+ }
+
+ private static boolean mimeTypeAgainstFilter(
+ @NonNull String[] mimeTypeParts, @NonNull String[] filterParts) {
+ if (filterParts.length != 2) {
+ throw new IllegalArgumentException(
+ "Ill-formatted MIME type filter. Must be type/subtype.");
+ }
+ if (filterParts[0].isEmpty() || filterParts[1].isEmpty()) {
+ throw new IllegalArgumentException(
+ "Ill-formatted MIME type filter. Type or subtype empty.");
+ }
+ if (mimeTypeParts.length != 2) {
+ return false;
+ }
+ if (!"*".equals(filterParts[0])
+ && !filterParts[0].equals(mimeTypeParts[0])) {
+ return false;
+ }
+ if (!"*".equals(filterParts[1])
+ && !filterParts[1].equals(mimeTypeParts[1])) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Matches one nullable MIME type against one MIME type filter.
+ * @return True if the {@code mimeType} matches the {@code filter}.
+ */
+ public static boolean matches(@Nullable String mimeType, @NonNull String filter) {
+ if (mimeType == null) {
+ return false;
+ }
+
+ final String[] mimeTypeParts = mimeType.split("/");
+ final String[] filterParts = filter.split("/");
+
+ return mimeTypeAgainstFilter(mimeTypeParts, filterParts);
+ }
+
+ /**
+ * Matches one nullable MIME type against an array of MIME type filters.
+ * @return The first matching filter, or null if nothing matches.
+ */
+ @Nullable
+ public static String matches(
+ @Nullable String mimeType, @NonNull String[] filters) {
+ if (mimeType == null) {
+ return null;
+ }
+
+ final String[] mimeTypeParts = mimeType.split("/");
+ for (String filter : filters) {
+ final String[] filterParts = filter.split("/");
+ if (mimeTypeAgainstFilter(mimeTypeParts, filterParts)) {
+ return filter;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Matches multiple MIME types against an array of MIME type filters.
+ * @return The first matching MIME type, or null if nothing matches.
+ */
+ @Nullable
+ public static String matches(
+ @Nullable String[] mimeTypes, @NonNull String filter) {
+ if (mimeTypes == null) {
+ return null;
+ }
+
+ final String[] filterParts = filter.split("/");
+ for (String mimeType : mimeTypes) {
+ final String[] mimeTypeParts = mimeType.split("/");
+ if (mimeTypeAgainstFilter(mimeTypeParts, filterParts)) {
+ return mimeType;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Matches multiple MIME types against an array of MIME type filters.
+ * @return The list of matching MIME types, or empty array if nothing matches.
+ */
+ @NonNull
+ public static String[] matchesMany(
+ @Nullable String[] mimeTypes, @NonNull String filter) {
+ if (mimeTypes == null) {
+ return new String[] {};
+ }
+
+ final ArrayList<String> list = new ArrayList<>();
+ final String[] filterParts = filter.split("/");
+ for (String mimeType : mimeTypes) {
+ final String[] mimeTypeParts = mimeType.split("/");
+ if (mimeTypeAgainstFilter(mimeTypeParts, filterParts)) {
+ list.add(mimeType);
+ }
+ }
+
+ return list.toArray(new String[list.size()]);
+ }
+}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index a87ee57..d0eff2e 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -276,6 +276,8 @@
in PersistableBundle appExtras, in PersistableBundle launcherExtras,
in SuspendDialogInfo dialogInfo, String callingPackage, int userId);
+ boolean canSuspendPackageForUser(String packageName, int userId);
+
boolean isPackageSuspendedForUser(String packageName, int userId);
PersistableBundle getSuspendedPackageAppExtras(String packageName, int userId);
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 9e20503..ecdd810 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -18,6 +18,7 @@
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
+import android.apex.ApexInfo;
import android.os.Parcel;
import android.os.Parcelable;
@@ -390,6 +391,11 @@
@Nullable
public String compileSdkVersionCodename;
+ /**
+ * Whether the package is an APEX package.
+ */
+ public boolean isApex;
+
public PackageInfo() {
}
@@ -472,6 +478,7 @@
} else {
dest.writeInt(0);
}
+ dest.writeBoolean(isApex);
}
public static final Parcelable.Creator<PackageInfo> CREATOR
@@ -533,7 +540,7 @@
if (hasSigningInfo != 0) {
signingInfo = SigningInfo.CREATOR.createFromParcel(source);
}
-
+ isApex = source.readBoolean();
// The component lists were flattened with the redundant ApplicationInfo
// instances omitted. Distribute the canonical one here as appropriate.
if (applicationInfo != null) {
@@ -544,6 +551,15 @@
}
}
+ /**
+ * @hide
+ */
+ public PackageInfo(ApexInfo apexInfo) {
+ packageName = apexInfo.packageName;
+ setLongVersionCode(apexInfo.versionCode);
+ isApex = true;
+ }
+
private void propagateApplicationInfo(ApplicationInfo appInfo, ComponentInfo[] components) {
if (components != null) {
for (ComponentInfo ci : components) {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index d3e4045..361beba 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -145,6 +145,7 @@
MATCH_FACTORY_ONLY,
MATCH_DEBUG_TRIAGED_MISSING,
MATCH_INSTANT,
+ MATCH_APEX,
GET_DISABLED_COMPONENTS,
GET_DISABLED_UNTIL_USED_COMPONENTS,
GET_UNINSTALLED_PACKAGES,
@@ -540,6 +541,17 @@
public static final int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS = 0x20000000;
/**
+ * {@link PackageInfo} flag: include APEX packages that are currently
+ * installed. In APEX terminology, this corresponds to packages that are
+ * currently active, i.e. mounted and available to other processes of the OS.
+ * In particular, this flag alone will not match APEX files that are staged
+ * for activation at next reboot.
+ * TODO(b/119767311): include uninstalled/inactive APEX if
+ * MATCH_UNINSTALLED_PACKAGES is set.
+ */
+ public static final int MATCH_APEX = 0x40000000;
+
+ /**
* Flag for {@link #addCrossProfileIntentFilter}: if this flag is set: when
* resolving an intent that matches the {@code CrossProfileIntentFilter},
* the current profile will be skipped. Only activities in the target user
@@ -5797,6 +5809,30 @@
}
/**
+ * Returns whether or not a given package can be suspended via a call to {@link
+ * #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle,
+ * SuspendDialogInfo) setPackagesSuspended}. The platform prevents suspending certain critical
+ * packages to keep the device in a functioning state, e.g. the default dialer.
+ * Apps need to hold {@link Manifest.permission#SUSPEND_APPS SUSPEND_APPS} to call this api.
+ *
+ * <p>
+ * Note that this set of critical packages can change with time, so <em>a value of {@code true}
+ * returned by this api does not guarantee that a following call to {@link
+ * #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle,
+ * SuspendDialogInfo) setPackagesSuspended} for the same package will succeed</em>, especially
+ * if considerable time elapsed between the two calls.
+ *
+ * @param packageName The package to check.
+ * @return {@code true} if the given package can be suspended, {@code false} otherwise.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.SUSPEND_APPS)
+ public boolean canSuspendPackage(@NonNull String packageName) {
+ throw new UnsupportedOperationException("canSuspendPackage not implemented");
+ }
+
+ /**
* @see #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, String)
* @param packageName The name of the package to get the suspended status of.
* @param userId The user id.
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 5f23749..4371c77 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -58,6 +58,7 @@
public final class AssetManager implements AutoCloseable {
private static final String TAG = "AssetManager";
private static final boolean DEBUG_REFS = false;
+ private static final boolean FEATURE_FLAG_IDMAP2 = false;
private static final String FRAMEWORK_APK_PATH = "/system/framework/framework-res.apk";
@@ -195,13 +196,23 @@
return;
}
- // Make sure that all IDMAPs are up to date.
- nativeVerifySystemIdmaps();
try {
final ArrayList<ApkAssets> apkAssets = new ArrayList<>();
apkAssets.add(ApkAssets.loadFromPath(FRAMEWORK_APK_PATH, true /*system*/));
- loadStaticRuntimeOverlays(apkAssets);
+ if (FEATURE_FLAG_IDMAP2) {
+ final String[] systemIdmapPaths =
+ nativeCreateIdmapsForStaticOverlaysTargetingAndroid();
+ if (systemIdmapPaths == null) {
+ throw new IOException("idmap2 scan failed");
+ }
+ for (String idmapPath : systemIdmapPaths) {
+ apkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, true /*system*/));
+ }
+ } else {
+ nativeVerifySystemIdmaps();
+ loadStaticRuntimeOverlays(apkAssets);
+ }
sSystemApkAssetsSet = new ArraySet<>(apkAssets);
sSystemApkAssets = apkAssets.toArray(new ApkAssets[apkAssets.size()]);
@@ -1404,6 +1415,7 @@
private static native long nativeAssetGetRemainingLength(long assetPtr);
private static native void nativeVerifySystemIdmaps();
+ private static native String[] nativeCreateIdmapsForStaticOverlaysTargetingAndroid();
// Global debug native methods.
/**
diff --git a/core/java/android/database/TranslatingCursor.java b/core/java/android/database/TranslatingCursor.java
new file mode 100644
index 0000000..58e65b2
--- /dev/null
+++ b/core/java/android/database/TranslatingCursor.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.database;
+
+import android.annotation.NonNull;
+import android.content.ContentResolver;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.os.CancellationSignal;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Cursor that supports deprecation of {@code _data} like columns which represent raw filepaths,
+ * typically by replacing values with fake paths that the OS then offers to redirect to
+ * {@link ContentResolver#openFileDescriptor(Uri, String)}, which developers
+ * should be using directly.
+ *
+ * @hide
+ */
+public class TranslatingCursor extends CrossProcessCursorWrapper {
+ public static class Config {
+ public final Uri baseUri;
+ public final String idColumn;
+ public final String[] filePathColumns;
+
+ public Config(Uri baseUri, String idColumn, String... filePathColumns) {
+ this.baseUri = baseUri;
+ this.idColumn = idColumn;
+ this.filePathColumns = filePathColumns;
+ }
+ }
+
+ public interface Translator {
+ String translate(String data, long id);
+ }
+
+ private final @NonNull Config mConfig;
+ private final @NonNull Translator mTranslator;
+ private final boolean mDropLast;
+
+ private final int mIdIndex;
+ private final int[] mFilePathColIndices;
+
+ private TranslatingCursor(@NonNull Cursor cursor, @NonNull Config config,
+ @NonNull Translator translator, boolean dropLast) {
+ super(cursor);
+
+ mConfig = Objects.requireNonNull(config);
+ mTranslator = Objects.requireNonNull(translator);
+ mDropLast = dropLast;
+
+ mIdIndex = cursor.getColumnIndexOrThrow(config.idColumn);
+ mFilePathColIndices = new int[config.filePathColumns.length];
+ for (int i = mFilePathColIndices.length - 1; i >= 0; --i) {
+ mFilePathColIndices[i] = cursor.getColumnIndex(config.filePathColumns[i]);
+ }
+ }
+
+ @Override
+ public int getColumnCount() {
+ if (mDropLast) {
+ return super.getColumnCount() - 1;
+ } else {
+ return super.getColumnCount();
+ }
+ }
+
+ @Override
+ public String[] getColumnNames() {
+ if (mDropLast) {
+ return Arrays.copyOfRange(super.getColumnNames(), 0, super.getColumnCount() - 1);
+ } else {
+ return super.getColumnNames();
+ }
+ }
+
+ public static Cursor query(@NonNull Config config, @NonNull Translator translator,
+ SQLiteQueryBuilder qb, SQLiteDatabase db, String[] projectionIn, String selection,
+ String[] selectionArgs, String groupBy, String having, String sortOrder, String limit,
+ CancellationSignal signal) {
+ final boolean requestedId = ArrayUtils.isEmpty(projectionIn)
+ || ArrayUtils.contains(projectionIn, config.idColumn);
+ final boolean requestedData = ArrayUtils.isEmpty(projectionIn)
+ || ArrayUtils.containsAny(projectionIn, config.filePathColumns);
+
+ // If caller didn't request data, we have nothing to redirect
+ if (!requestedData || !ContentResolver.DEPRECATE_DATA_COLUMNS) {
+ return qb.query(db, projectionIn, selection, selectionArgs,
+ groupBy, having, sortOrder, limit, signal);
+ }
+
+ // If caller didn't request id, we need to splice it in
+ if (!requestedId) {
+ projectionIn = ArrayUtils.appendElement(String.class, projectionIn,
+ config.idColumn);
+ }
+
+ final Cursor c = qb.query(db, projectionIn, selection, selectionArgs,
+ groupBy, having, sortOrder);
+ return new TranslatingCursor(c, config, translator, !requestedId);
+ }
+
+ @Override
+ public void fillWindow(int position, CursorWindow window) {
+ // Fill window directly to ensure data is rewritten
+ DatabaseUtils.cursorFillWindow(this, position, window);
+ }
+
+ @Override
+ public CursorWindow getWindow() {
+ // Returning underlying window risks leaking data
+ return null;
+ }
+
+ @Override
+ public Cursor getWrappedCursor() {
+ throw new UnsupportedOperationException(
+ "Returning underlying cursor risks leaking data");
+ }
+
+ @Override
+ public double getDouble(int columnIndex) {
+ if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ throw new IllegalArgumentException();
+ } else {
+ return super.getDouble(columnIndex);
+ }
+ }
+
+ @Override
+ public float getFloat(int columnIndex) {
+ if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ throw new IllegalArgumentException();
+ } else {
+ return super.getFloat(columnIndex);
+ }
+ }
+
+ @Override
+ public int getInt(int columnIndex) {
+ if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ throw new IllegalArgumentException();
+ } else {
+ return super.getInt(columnIndex);
+ }
+ }
+
+ @Override
+ public long getLong(int columnIndex) {
+ if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ throw new IllegalArgumentException();
+ } else {
+ return super.getLong(columnIndex);
+ }
+ }
+
+ @Override
+ public short getShort(int columnIndex) {
+ if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ throw new IllegalArgumentException();
+ } else {
+ return super.getShort(columnIndex);
+ }
+ }
+
+ @Override
+ public String getString(int columnIndex) {
+ if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ return mTranslator.translate(super.getString(columnIndex), super.getLong(mIdIndex));
+ } else {
+ return super.getString(columnIndex);
+ }
+ }
+
+ @Override
+ public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
+ if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ throw new IllegalArgumentException();
+ } else {
+ super.copyStringToBuffer(columnIndex, buffer);
+ }
+ }
+
+ @Override
+ public byte[] getBlob(int columnIndex) {
+ if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ throw new IllegalArgumentException();
+ } else {
+ return super.getBlob(columnIndex);
+ }
+ }
+
+ @Override
+ public int getType(int columnIndex) {
+ if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ return Cursor.FIELD_TYPE_STRING;
+ } else {
+ return super.getType(columnIndex);
+ }
+ }
+
+ @Override
+ public boolean isNull(int columnIndex) {
+ if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ return getString(columnIndex) == null;
+ } else {
+ return super.isNull(columnIndex);
+ }
+ }
+}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 7952c41..bd149fd 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -426,6 +426,31 @@
}
/**
+ * Authenticates for the given user.
+ * @param cancel An object that can be used to cancel authentication
+ * @param executor An executor to handle callback events
+ * @param callback An object to receive authentication events
+ * @param userId The user to authenticate
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ public void authenticateUser(@NonNull CancellationSignal cancel,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AuthenticationCallback callback,
+ int userId) {
+ if (cancel == null) {
+ throw new IllegalArgumentException("Must supply a cancellation signal");
+ }
+ if (executor == null) {
+ throw new IllegalArgumentException("Must supply an executor");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("Must supply a callback");
+ }
+ authenticateInternal(null /* crypto */, cancel, executor, callback, userId);
+ }
+
+ /**
* This call warms up the biometric hardware, displays a system-provided dialog, and starts
* scanning for a biometric. It terminates when {@link
* AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when {@link
@@ -465,7 +490,7 @@
if (callback == null) {
throw new IllegalArgumentException("Must supply a callback");
}
- authenticateInternal(crypto, cancel, executor, callback);
+ authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId());
}
/**
@@ -502,7 +527,7 @@
if (callback == null) {
throw new IllegalArgumentException("Must supply a callback");
}
- authenticateInternal(null /* crypto */, cancel, executor, callback);
+ authenticateInternal(null /* crypto */, cancel, executor, callback, mContext.getUserId());
}
private void cancelAuthentication() {
@@ -518,7 +543,8 @@
private void authenticateInternal(@Nullable CryptoObject crypto,
@NonNull CancellationSignal cancel,
@NonNull @CallbackExecutor Executor executor,
- @NonNull AuthenticationCallback callback) {
+ @NonNull AuthenticationCallback callback,
+ int userId) {
try {
if (cancel.isCanceled()) {
Log.w(TAG, "Authentication already canceled");
@@ -531,7 +557,7 @@
mExecutor = executor;
mAuthenticationCallback = callback;
final long sessionId = crypto != null ? crypto.getOpId() : 0;
- mService.authenticate(mToken, sessionId, mContext.getUserId(),
+ mService.authenticate(mToken, sessionId, userId,
mBiometricServiceReceiver, 0 /* flags */, mContext.getOpPackageName(),
mBundle, mDialogReceiver);
} catch (RemoteException e) {
diff --git a/core/java/android/hardware/display/ColorDisplayManager.java b/core/java/android/hardware/display/ColorDisplayManager.java
new file mode 100644
index 0000000..0a76c2b
--- /dev/null
+++ b/core/java/android/hardware/display/ColorDisplayManager.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.display;
+
+import android.content.Context;
+
+import com.android.internal.R;
+
+/**
+ * Manages the display's color transforms and modes.
+ * @hide
+ */
+public final class ColorDisplayManager {
+
+ /**
+ * Returns {@code true} if Night Display is supported by the device.
+ */
+ public static boolean isNightDisplayAvailable(Context context) {
+ return context.getResources().getBoolean(R.bool.config_nightDisplayAvailable);
+ }
+}
diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java
index 2717c4e..a83a33e 100644
--- a/core/java/android/hardware/location/ContextHubClient.java
+++ b/core/java/android/hardware/location/ContextHubClient.java
@@ -19,7 +19,6 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.app.PendingIntent;
-import android.content.Intent;
import android.os.RemoteException;
import com.android.internal.util.Preconditions;
@@ -49,13 +48,25 @@
*/
private final ContextHubInfo mAttachedHub;
- private final CloseGuard mCloseGuard = CloseGuard.get();
+ private final CloseGuard mCloseGuard;
private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
- /* package */ ContextHubClient(ContextHubInfo hubInfo) {
+ /*
+ * True if this is a persistent client (i.e. does not have to close the connection when the
+ * resource is freed from the system).
+ */
+ private final boolean mPersistent;
+
+ /* package */ ContextHubClient(ContextHubInfo hubInfo, boolean persistent) {
mAttachedHub = hubInfo;
- mCloseGuard.open("close");
+ mPersistent = persistent;
+ if (mPersistent) {
+ mCloseGuard = null;
+ } else {
+ mCloseGuard = CloseGuard.get();
+ mCloseGuard.open("close");
+ }
}
/**
@@ -88,11 +99,18 @@
* Closes the connection for this client and the Context Hub Service.
*
* When this function is invoked, the messaging associated with this client is invalidated.
- * All futures messages targeted for this client are dropped at the service.
+ * All futures messages targeted for this client are dropped at the service, and the
+ * ContextHubClient is unregistered from the service.
+ *
+ * If this object has a PendingIntent, i.e. the object was generated via
+ * {@link ContextHubManager.createClient(PendingIntent, ContextHubInfo, long)}, then the
+ * Intent events corresponding to the PendingIntent will no longer be triggered.
*/
public void close() {
if (!mIsClosed.getAndSet(true)) {
- mCloseGuard.close();
+ if (mCloseGuard != null) {
+ mCloseGuard.close();
+ }
try {
mClientProxy.close();
} catch (RemoteException e) {
@@ -102,72 +120,6 @@
}
/**
- * Registers to receive persistent intents for a given nanoapp.
- *
- * This method should be used if the caller wants to receive notifications even after the
- * process exits. The client must have an open connection with the Context Hub Service (i.e. it
- * cannot have been closed through the {@link #close()} method). Only one PendingIntent can be
- * registered at a time for a single ContextHubClient, and the PendingIntent cannot be
- * registered if already registered by a ContextHubClient. If registered successfully, intents
- * will be delivered regarding events for the specified nanoapp from the attached Context Hub.
- * Any unicast messages for this client will also be delivered. The intent will have an extra
- * {@link ContextHubManager.EXTRA_CONTEXT_HUB_INFO} of type {@link ContextHubInfo}, which
- * describes the Context Hub the intent event was for. The intent will also have an extra
- * {@link ContextHubManager.EXTRA_EVENT_TYPE} of type {@link ContextHubManager.Event}, which
- * will contain the type of the event. See {@link ContextHubManager.Event} for description of
- * each event type, along with event-specific extra fields. A client can use
- * {@link ContextHubIntentEvent.fromIntent(Intent)} to parse the Intent generated by the event.
- *
- * When the intent is received, this client can be recreated through
- * {@link ContextHubManager.createClient(PendingIntent, ContextHubInfo,
- * ContextHubClientCallback, Exectutor)}. When recreated, the client can be treated as the
- * same endpoint entity from a nanoapp's perspective, and can be continued to be used to send
- * messages even if the original process has exited.
- *
- * Intents will be delivered until it is unregistered through
- * {@link #unregisterIntent(PendingIntent)}. Note that the registration of this client will
- * continued to be maintained at the Context Hub Service until
- * {@link #unregisterIntent(PendingIntent)} is called for registered intents.
- *
- * @param pendingIntent the PendingIntent to register for this client
- * @param nanoAppId the unique ID of the nanoapp to receive events for
- * @return true on success, false otherwise
- *
- * @hide
- */
- @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
- public boolean registerIntent(@NonNull PendingIntent pendingIntent, long nanoAppId) {
- Preconditions.checkNotNull(pendingIntent, "PendingIntent cannot be null");
-
- try {
- return mClientProxy.registerIntent(pendingIntent, nanoAppId);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Unregisters an intent previously registered via {@link #registerIntent(PendingIntent, long)}.
- * If this intent has not been registered for this client, this method returns false.
- *
- * @param pendingIntent the PendingIntent to unregister
- *
- * @return true on success, false otherwise
- *
- * @hide
- */
- @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
- public boolean unregisterIntent(@NonNull PendingIntent pendingIntent) {
- Preconditions.checkNotNull(pendingIntent, "PendingIntent cannot be null");
-
- try {
- return mClientProxy.unregisterIntent(pendingIntent);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* Sends a message to a nanoapp through the Context Hub Service.
*
* This function returns RESULT_SUCCESS if the message has reached the HAL, but
@@ -200,7 +152,9 @@
if (mCloseGuard != null) {
mCloseGuard.warnIfOpen();
}
- close();
+ if (!mPersistent) {
+ close();
+ }
} finally {
super.finalize();
}
diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java
index 36123e3..51daa92 100644
--- a/core/java/android/hardware/location/ContextHubInfo.java
+++ b/core/java/android/hardware/location/ContextHubInfo.java
@@ -15,6 +15,7 @@
*/
package android.hardware.location;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.hardware.contexthub.V1_0.ContextHub;
import android.os.Parcel;
@@ -267,6 +268,34 @@
return retVal;
}
+ @Override
+ public boolean equals(@Nullable Object object) {
+ if (object == this) {
+ return true;
+ }
+
+ boolean isEqual = false;
+ if (object instanceof ContextHubInfo) {
+ ContextHubInfo other = (ContextHubInfo) object;
+ isEqual = (other.getId() == mId)
+ && other.getName().equals(mName)
+ && other.getVendor().equals(mVendor)
+ && other.getToolchain().equals(mToolchain)
+ && (other.getToolchainVersion() == mToolchainVersion)
+ && (other.getStaticSwVersion() == getStaticSwVersion())
+ && (other.getChrePlatformId() == mChrePlatformId)
+ && (other.getPeakMips() == mPeakMips)
+ && (other.getStoppedPowerDrawMw() == mStoppedPowerDrawMw)
+ && (other.getSleepPowerDrawMw() == mSleepPowerDrawMw)
+ && (other.getPeakPowerDrawMw() == mPeakPowerDrawMw)
+ && (other.getMaxPacketLengthBytes() == mMaxPacketLengthBytes)
+ && Arrays.equals(other.getSupportedSensors(), mSupportedSensors)
+ && Arrays.equals(other.getMemoryRegions(), mMemoryRegions);
+ }
+
+ return isEqual;
+ }
+
private ContextHubInfo(Parcel in) {
mId = in.readInt();
mName = in.readString();
diff --git a/core/java/android/hardware/location/ContextHubIntentEvent.java b/core/java/android/hardware/location/ContextHubIntentEvent.java
index 96e7496..539c494 100644
--- a/core/java/android/hardware/location/ContextHubIntentEvent.java
+++ b/core/java/android/hardware/location/ContextHubIntentEvent.java
@@ -16,6 +16,7 @@
package android.hardware.location;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.Intent;
@@ -23,8 +24,9 @@
/**
* A helper class to retrieve information about a Intent event received for a PendingIntent
- * registered through {@link ContextHubClient.registerIntent(PendingIntent, long)}. This object
- * can only be created through the factory method {@link ContextHubIntentEvent.fromIntent(Intent)}.
+ * registered with {@link ContextHubManager.createClient(ContextHubInfo, PendingIntent, long)}.
+ * This object can only be created through the factory method
+ * {@link ContextHubIntentEvent.fromIntent(Intent)}.
*
* @hide
*/
@@ -76,7 +78,7 @@
/**
* Creates a ContextHubIntentEvent object from an Intent received through a PendingIntent
- * registered through {@link ContextHubClient.registerIntent(PendingIntent, long)}.
+ * registered with {@link ContextHubManager.createClient(ContextHubInfo, PendingIntent, long)}.
*
* @param intent the Intent object from an Intent event
* @return the ContextHubIntentEvent object describing the event
@@ -206,6 +208,37 @@
return out + "]";
}
+ @Override
+ public boolean equals(@Nullable Object object) {
+ if (object == this) {
+ return true;
+ }
+
+ boolean isEqual = false;
+ if (object instanceof ContextHubIntentEvent) {
+ ContextHubIntentEvent other = (ContextHubIntentEvent) object;
+ if (other.getEventType() == mEventType
+ && other.getContextHubInfo().equals(mContextHubInfo)) {
+ isEqual = true;
+ try {
+ if (mEventType != ContextHubManager.EVENT_HUB_RESET) {
+ isEqual &= (other.getNanoAppId() == mNanoAppId);
+ }
+ if (mEventType == ContextHubManager.EVENT_NANOAPP_ABORTED) {
+ isEqual &= (other.getNanoAppAbortCode() == mNanoAppAbortCode);
+ }
+ if (mEventType == ContextHubManager.EVENT_NANOAPP_MESSAGE) {
+ isEqual &= other.getNanoAppMessage().equals(mNanoAppMessage);
+ }
+ } catch (UnsupportedOperationException e) {
+ isEqual = false;
+ }
+ }
+ }
+
+ return isEqual;
+ }
+
private static void hasExtraOrThrow(Intent intent, String extra) {
if (!intent.hasExtra(extra)) {
throw new IllegalArgumentException("Intent did not have extra: " + extra);
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 9acefa5..88fb3de 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -25,6 +25,7 @@
import android.annotation.SystemService;
import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
@@ -757,13 +758,13 @@
Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null");
Preconditions.checkNotNull(executor, "Executor cannot be null");
- ContextHubClient client = new ContextHubClient(hubInfo);
+ ContextHubClient client = new ContextHubClient(hubInfo, false /* persistent */);
IContextHubClientCallback clientInterface = createClientCallback(
client, callback, executor);
IContextHubClient clientProxy;
try {
- clientProxy = mService.createClient(clientInterface, hubInfo.getId());
+ clientProxy = mService.createClient(hubInfo.getId(), clientInterface);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -793,43 +794,55 @@
}
/**
- * Creates a ContextHubClient based on an Intent received by the Context Hub Service.
+ * Creates a ContextHubClient that will receive notifications based on Intent events.
*
- * This method is intended to be used after receiving an Intent received as a result of
- * {@link ContextHubClient.registerIntent(PendingIntent, long)}, and must have been created
- * through {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)} or
- * equivalent at an earlier time.
+ * This method should be used instead of {@link #createClient(ContextHubInfo,
+ * ContextHubClientCallback)} and the equivalent API if the caller wants to preserve the
+ * messaging endpoint of a ContextHubClient, even after a process exits. If the PendingIntent
+ * with the provided nanoapp has already been registered at the service previously, then the
+ * same ContextHubClient will be regenerated without creating a new client connection at the
+ * service. Note that the PendingIntent, nanoapp, and Context Hub must all match in identifying
+ * a previously registered ContextHubClient. If a client is regenerated, it can be treated as
+ * the same endpoint entity from a nanoapp's perspective, and can be continued to be
+ * used to send messages even if the original process has exited.
*
- * @param pendingIntent the PendingIntent that has been registered with a client
+ * If registered successfully, intents will be delivered regarding events or messages from the
+ * specified nanoapp from the attached Context Hub. The intent will have an extra
+ * {@link ContextHubManager.EXTRA_CONTEXT_HUB_INFO} of type {@link ContextHubInfo}, which
+ * describes the Context Hub the intent event was for. The intent will also have an extra
+ * {@link ContextHubManager.EXTRA_EVENT_TYPE} of type {@link ContextHubManager.Event}, which
+ * will contain the type of the event. See {@link ContextHubManager.Event} for description of
+ * each event type, along with event-specific extra fields. The client can also use
+ * {@link ContextHubIntentEvent.fromIntent(Intent)} to parse the Intent generated by the event.
+ *
+ * Intent events will be delivered until it is unregistered through
+ * {@link ContextHubClient.close()}. Note that the registration of this
+ * ContextHubClient at the Context Hub Service will continued to be maintained until
+ * {@link ContextHubClient.close()} is called.
+ *
* @param hubInfo the hub to attach this client to
- * @param callback the notification callback to register
- * @param executor the executor to invoke the callback
+ * @param pendingIntent the PendingIntent to register to the client
+ * @param nanoAppId the ID of the nanoapp that Intent events will be generated for
* @return the registered client object
*
- * @throws IllegalArgumentException if hubInfo does not represent a valid hub, or pendingIntent
- * was not associated with a client
- * @throws IllegalStateException if the client is already registered to a valid callback
- * @throws NullPointerException if pendingIntent, hubInfo, callback, or executor is null
+ * @throws IllegalArgumentException if hubInfo does not represent a valid hub
+ * @throws IllegalStateException if there were too many registered clients at the service
+ * @throws NullPointerException if pendingIntent or hubInfo is null
*
* @hide
*/
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
@NonNull public ContextHubClient createClient(
- @NonNull PendingIntent pendingIntent, @NonNull ContextHubInfo hubInfo,
- @NonNull ContextHubClientCallback callback,
- @NonNull @CallbackExecutor Executor executor) {
- Preconditions.checkNotNull(pendingIntent, "PendingIntent cannot be null");
- Preconditions.checkNotNull(callback, "Callback cannot be null");
- Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null");
- Preconditions.checkNotNull(executor, "Executor cannot be null");
+ @NonNull ContextHubInfo hubInfo, @NonNull PendingIntent pendingIntent, long nanoAppId) {
+ Preconditions.checkNotNull(pendingIntent);
+ Preconditions.checkNotNull(hubInfo);
- ContextHubClient client = new ContextHubClient(hubInfo);
- IContextHubClientCallback clientInterface = createClientCallback(
- client, callback, executor);
+ ContextHubClient client = new ContextHubClient(hubInfo, true /* persistent */);
IContextHubClient clientProxy;
try {
- clientProxy = mService.bindClient(pendingIntent, clientInterface, hubInfo.getId());
+ clientProxy = mService.createPendingIntentClient(
+ hubInfo.getId(), pendingIntent, nanoAppId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -839,30 +852,6 @@
}
/**
- * Equivalent to {@link #createClient(PendingIntent, ContextHubInfo, ContextHubClientCallback,
- * Executor)} with the executor using the main thread's Looper.
- *
- * @param pendingIntent the PendingIntent that has been registered with a client
- * @param hubInfo the hub to attach this client to
- * @param callback the notification callback to register
- * @return the registered client object
- *
- * @throws IllegalArgumentException if hubInfo does not represent a valid hub, or pendingIntent
- * was not associated with a client
- * @throws IllegalStateException if the client is already registered to a valid callback
- * @throws NullPointerException if pendingIntent, hubInfo, or callback is null
- *
- * @hide
- */
- @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
- @NonNull public ContextHubClient createClient(
- @NonNull PendingIntent pendingIntent, @NonNull ContextHubInfo hubInfo,
- @NonNull ContextHubClientCallback callback) {
- return createClient(
- pendingIntent, hubInfo, callback, new HandlerExecutor(Handler.getMain()));
- }
-
- /**
* Unregister a callback for receive messages from the context hub.
*
* @see Callback
diff --git a/core/java/android/hardware/location/IContextHubClient.aidl b/core/java/android/hardware/location/IContextHubClient.aidl
index b539414..e33545c 100644
--- a/core/java/android/hardware/location/IContextHubClient.aidl
+++ b/core/java/android/hardware/location/IContextHubClient.aidl
@@ -29,10 +29,4 @@
// Closes the connection with the Context Hub
void close();
-
- // Registers a PendingIntent with the client
- boolean registerIntent(in PendingIntent pendingIntent, long nanoAppId);
-
- // Unregisters a PendingIntent from the client
- boolean unregisterIntent(in PendingIntent pendingIntent);
}
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index 9b0acdf..04cc563 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -59,12 +59,11 @@
int sendMessage(int contextHubHandle, int nanoAppHandle, in ContextHubMessage msg);
// Creates a client to send and receive messages
- IContextHubClient createClient(in IContextHubClientCallback client, int contextHubId);
+ IContextHubClient createClient(int contextHubId, in IContextHubClientCallback client);
- // Binds an existing client to a new callback interface, provided a previously registered
- // PendingIntent
- IContextHubClient bindClient(
- in PendingIntent pendingIntent, in IContextHubClientCallback client, int contextHubId);
+ // Creates a PendingIntent-based client to send and receive messages
+ IContextHubClient createPendingIntentClient(
+ int contextHubId, in PendingIntent pendingIntent, long nanoAppId);
// Returns a list of ContextHub objects of available hubs
List<ContextHubInfo> getContextHubs();
diff --git a/core/java/android/hardware/location/MemoryRegion.java b/core/java/android/hardware/location/MemoryRegion.java
index 857434e..3d9e859 100644
--- a/core/java/android/hardware/location/MemoryRegion.java
+++ b/core/java/android/hardware/location/MemoryRegion.java
@@ -16,6 +16,7 @@
package android.hardware.location;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -106,6 +107,25 @@
}
@Override
+ public boolean equals(@Nullable Object object) {
+ if (object == this) {
+ return true;
+ }
+
+ boolean isEqual = false;
+ if (object instanceof MemoryRegion) {
+ MemoryRegion other = (MemoryRegion) object;
+ isEqual = (other.getCapacityBytes() == mSizeBytes)
+ && (other.getFreeCapacityBytes() == mSizeBytesFree)
+ && (other.isReadable() == mIsReadable)
+ && (other.isWritable() == mIsWritable)
+ && (other.isExecutable() == mIsExecutable);
+ }
+
+ return isEqual;
+ }
+
+ @Override
public int describeContents() {
return 0;
}
diff --git a/core/java/android/hardware/location/NanoAppMessage.java b/core/java/android/hardware/location/NanoAppMessage.java
index 6635258..9f90d59 100644
--- a/core/java/android/hardware/location/NanoAppMessage.java
+++ b/core/java/android/hardware/location/NanoAppMessage.java
@@ -15,10 +15,13 @@
*/
package android.hardware.location;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Arrays;
+
/**
* A class describing messages send to or from nanoapps through the Context Hub Service.
*
@@ -168,4 +171,22 @@
return ret;
}
+
+ @Override
+ public boolean equals(@Nullable Object object) {
+ if (object == this) {
+ return true;
+ }
+
+ boolean isEqual = false;
+ if (object instanceof NanoAppMessage) {
+ NanoAppMessage other = (NanoAppMessage) object;
+ isEqual = (other.getNanoAppId() == mNanoAppId)
+ && (other.getMessageType() == mMessageType)
+ && (other.isBroadcastMessage() == mIsBroadcasted)
+ && Arrays.equals(other.getMessageBody(), mMessageBody);
+ }
+
+ return isEqual;
+ }
}
diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java
index 4cd0001..4e551756 100644
--- a/core/java/android/net/MacAddress.java
+++ b/core/java/android/net/MacAddress.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -27,6 +28,8 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.net.Inet6Address;
+import java.net.UnknownHostException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Random;
@@ -408,4 +411,34 @@
Preconditions.checkNotNull(mask);
return (mAddr & mask.mAddr) == (baseAddress.mAddr & mask.mAddr);
}
+
+ /**
+ * Create a link-local Inet6Address from the MAC address. The EUI-48 MAC address is converted
+ * to an EUI-64 MAC address per RFC 4291. The resulting EUI-64 is used to construct a link-local
+ * IPv6 address per RFC 4862.
+ *
+ * @return A link-local Inet6Address constructed from the MAC address.
+ * @hide
+ */
+ public @Nullable Inet6Address getLinkLocalIpv6FromEui48Mac() {
+ byte[] macEui48Bytes = toByteArray();
+ byte[] addr = new byte[16];
+
+ addr[0] = (byte) 0xfe;
+ addr[1] = (byte) 0x80;
+ addr[8] = (byte) (macEui48Bytes[0] ^ (byte) 0x02); // flip the link-local bit
+ addr[9] = macEui48Bytes[1];
+ addr[10] = macEui48Bytes[2];
+ addr[11] = (byte) 0xff;
+ addr[12] = (byte) 0xfe;
+ addr[13] = macEui48Bytes[3];
+ addr[14] = macEui48Bytes[4];
+ addr[15] = macEui48Bytes[5];
+
+ try {
+ return Inet6Address.getByAddress(null, addr, 0);
+ } catch (UnknownHostException e) {
+ return null;
+ }
+ }
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 9cf7de5..c437dde 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -2421,7 +2421,7 @@
public static final IntToString[] HISTORY_EVENT_INT_FORMATTERS = new IntToString[] {
sUidToString, sUidToString, sUidToString, sUidToString, sUidToString, sUidToString,
- sUidToString, sUidToString, sUidToString, sUidToString, sUidToString, sUidToString,
+ sUidToString, sUidToString, sUidToString, sUidToString, sUidToString, sIntToString,
sUidToString, sUidToString, sUidToString, sUidToString, sUidToString, sUidToString,
sUidToString, sUidToString, sUidToString, sIntToString
};
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index da4b823..c7184c0 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -382,7 +382,9 @@
/**
* Sets the work source for this thread.
*
- * <p>All the following binder calls on this thread will use the provided work source.
+ * <p>All the following binder calls on this thread will use the provided work source. If this
+ * is called during an on-going binder transaction, all the following binder calls will use the
+ * work source until the end of the transaction.
*
* <p>The concept of worksource is similar to {@link WorkSource}. However, for performance
* reasons, we only support one UID. This UID represents the original user responsible for the
@@ -390,20 +392,20 @@
*
* <p>A typical use case would be
* <pre>
- * Binder.setThreadWorkSource(uid);
+ * long token = Binder.setCallingWorkSourceUid(uid);
* try {
* // Call an API.
* } finally {
- * Binder.clearThreadWorkSource();
+ * Binder.restoreCallingWorkSource(token);
* }
* </pre>
*
* @param workSource The original UID responsible for the binder call.
- * @return The previously set work source.
+ * @return token to restore original work source.
* @hide
**/
@CriticalNative
- public static final native int setThreadWorkSource(int workSource);
+ public static final native long setCallingWorkSourceUid(int workSource);
/**
* Returns the work source set by the caller.
@@ -416,16 +418,34 @@
* @hide
*/
@CriticalNative
- public static final native int getThreadWorkSource();
+ public static final native int getCallingWorkSourceUid();
/**
* Clears the work source on this thread.
*
- * @return The previously set work source.
+ * @return token to restore original work source.
* @hide
**/
@CriticalNative
- public static final native int clearThreadWorkSource();
+ public static final native long clearCallingWorkSource();
+
+ /**
+ * Restores the work source on this thread using a token returned by
+ * {@link #setCallingWorkSourceUid(int) or {@link clearCallingWorkSource()}.
+ *
+ * <p>A typical use case would be
+ * <pre>
+ * long token = Binder.setCallingWorkSourceUid(uid);
+ * try {
+ * // Call an API.
+ * } finally {
+ * Binder.restoreCallingWorkSource(token);
+ * }
+ * </pre>
+ * @hide
+ **/
+ @CriticalNative
+ public static final native void restoreCallingWorkSource(long token);
/**
* Flush any Binder commands pending in the current thread to the kernel
@@ -586,7 +606,7 @@
*
* <li>By default, this listener will propagate the worksource if the outgoing call happens on
* the same thread as the incoming binder call.
- * <li>Custom attribution can be done by calling {@link ThreadLocalWorkSourceUid#set(int)}.
+ * <li>Custom attribution can be done by calling {@link ThreadLocalWorkSource#setUid(int)}.
* @hide
*/
public static class PropagateWorkSourceTransactListener implements ProxyTransactListener {
@@ -595,12 +615,11 @@
// Note that {@link Binder#getCallingUid()} is already set to the UID of the current
// process when this method is called.
//
- // We use ThreadLocalWorkSourceUid instead. It also allows feature owners to set
- // {@link ThreadLocalWorkSourceUid#set(int) manually to attribute resources to a UID.
- int uid = ThreadLocalWorkSourceUid.get();
- if (uid >= 0) {
- int originalUid = Binder.setThreadWorkSource(uid);
- return Integer.valueOf(originalUid);
+ // We use ThreadLocalWorkSource instead. It also allows feature owners to set
+ // {@link ThreadLocalWorkSource#set(int) manually to attribute resources to a UID.
+ int uid = ThreadLocalWorkSource.getUid();
+ if (uid != ThreadLocalWorkSource.UID_NONE) {
+ return Binder.setCallingWorkSourceUid(uid);
}
return null;
}
@@ -608,8 +627,8 @@
@Override
public void onTransactEnded(Object session) {
if (session != null) {
- int uid = (int) session;
- Binder.setThreadWorkSource(uid);
+ long token = (long) session;
+ Binder.restoreCallingWorkSource(token);
}
}
}
@@ -897,11 +916,11 @@
// Log any exceptions as warnings, don't silently suppress them.
// If the call was FLAG_ONEWAY then these exceptions disappear into the ether.
final boolean tracingEnabled = Binder.isTracingEnabled();
+ final long origWorkSource = ThreadLocalWorkSource.setUid(Binder.getCallingUid());
try {
if (tracingEnabled) {
Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, getClass().getName() + ":" + code);
}
- ThreadLocalWorkSourceUid.set(Binder.getCallingUid());
res = onTransact(code, data, reply, flags);
} catch (RemoteException|RuntimeException e) {
if (observer != null) {
@@ -922,7 +941,7 @@
}
res = true;
} finally {
- ThreadLocalWorkSourceUid.clear();
+ ThreadLocalWorkSource.restore(origWorkSource);
if (tracingEnabled) {
Trace.traceEnd(Trace.TRACE_TAG_ALWAYS);
}
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 1f47f93..28ea553 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -1390,3 +1390,4 @@
}
}
}
+
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index f3a9a50..e8704af 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -739,7 +739,7 @@
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
- msg.workSourceUid = ThreadLocalWorkSourceUid.get();
+ msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl
index 124f207..74d434c 100644
--- a/core/java/android/os/IStatsManager.aidl
+++ b/core/java/android/os/IStatsManager.aidl
@@ -62,12 +62,15 @@
* Inform statsd what the version and package are for each uid. Note that each array should
* have the same number of elements, and version[i] and package[i] correspond to uid[i].
*/
- oneway void informAllUidData(in int[] uid, in long[] version, in String[] app);
+ oneway void informAllUidData(in int[] uid, in long[] version, in String[] version_string,
+ in String[] app, in String[] installer);
/**
- * Inform statsd what the uid and version are for one app that was updated.
+ * Inform statsd what the uid, version, version_string, and installer are for one app that was
+ * updated.
*/
- oneway void informOnePackage(in String app, in int uid, in long version);
+ oneway void informOnePackage(in String app, in int uid, in long version,
+ in String version_string, in String installer);
/**
* Inform stats that an app was removed.
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index 5b8abab..a8d1215 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -204,8 +204,8 @@
if (observer != null) {
token = observer.messageDispatchStarting();
}
+ long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
try {
- ThreadLocalWorkSourceUid.set(msg.workSourceUid);
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
@@ -217,7 +217,7 @@
}
throw exception;
} finally {
- ThreadLocalWorkSourceUid.clear();
+ ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 126588a..44b9e311 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -1056,6 +1056,9 @@
/**
* Internal class representing a remote status read by
* {@link ParcelFileDescriptor#readCommStatus(FileDescriptor, byte[])}.
+ *
+ * Warning: this must be kept in sync with ParcelFileDescriptorStatus at
+ * frameworks/native/libs/binder/Parcel.cpp
*/
private static class Status {
/** Special value indicating remote side died. */
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index a307cd8..1c1db68 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -24,6 +24,7 @@
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.content.Context;
+import android.service.dreams.Sandman;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
@@ -1001,6 +1002,29 @@
}
/**
+ * Requests the device to start dreaming.
+ * <p>
+ * If dream can not be started, for example if another {@link PowerManager} transition is in
+ * progress, does nothing. Unlike {@link #nap(long)}, this does not put device to sleep when
+ * dream ends.
+ * </p><p>
+ * Requires the {@link android.Manifest.permission#WRITE_DREAM_STATE} permission.
+ * </p>
+ *
+ * @param time The time when the request to nap was issued, in the
+ * {@link SystemClock#uptimeMillis()} time base. This timestamp may be used to correctly
+ * order the dream request with other power management functions. It should be set
+ * to the timestamp of the input event that caused the request to dream.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE)
+ public void dream(long time) {
+ Sandman.startDreamByUserRequest(mContext);
+ }
+
+ /**
* Boosts the brightness of the screen to maximum for a predetermined
* period of time. This is used to make the screen more readable in bright
* daylight for a short duration.
diff --git a/core/java/android/os/StatsLogEventWrapper.java b/core/java/android/os/StatsLogEventWrapper.java
index 72e1ab9..866bd9a 100644
--- a/core/java/android/os/StatsLogEventWrapper.java
+++ b/core/java/android/os/StatsLogEventWrapper.java
@@ -104,14 +104,6 @@
}
/**
- * Write a double value.
- */
- public void writeDouble(double val) {
- mTypes.add(EVENT_TYPE_DOUBLE);
- mValues.add(val);
- }
-
- /**
* Write a storage value.
*/
public void writeStorage(byte[] val) {
diff --git a/core/java/android/os/ThreadLocalWorkSource.java b/core/java/android/os/ThreadLocalWorkSource.java
new file mode 100644
index 0000000..894b1cc4
--- /dev/null
+++ b/core/java/android/os/ThreadLocalWorkSource.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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;
+
+/**
+ * Tracks who triggered the work currently executed on this thread.
+ *
+ * <p>ThreadLocalWorkSource is automatically updated inside system server for incoming/outgoing
+ * binder calls and messages posted to handler threads.
+ *
+ * <p>ThreadLocalWorkSource can also be set manually if needed to refine the WorkSource.
+ *
+ * <p>Example:
+ * <ul>
+ * <li>Bluetooth process calls {@link PowerManager#isInteractive()} API on behalf of app foo.
+ * <li>ThreadLocalWorkSource will be automatically set to the UID of foo.
+ * <li>Any code on the thread handling {@link PowerManagerService#isInteractive()} can call
+ * {@link ThreadLocalWorkSource#getUid()} to blame any resource used to handle this call.
+ * <li>If a message is posted from the binder thread, the code handling the message can also call
+ * {@link ThreadLocalWorkSource#getUid()} and it will return the UID of foo since the work source is
+ * automatically propagated.
+ * </ul>
+ *
+ * @hide Only for use within system server.
+ */
+public final class ThreadLocalWorkSource {
+ public static final int UID_NONE = Message.UID_NONE;
+ private static final ThreadLocal<Integer> sWorkSourceUid =
+ ThreadLocal.withInitial(() -> UID_NONE);
+
+ /**
+ * Returns the UID to blame for the code currently executed on this thread.
+ *
+ * <p>This UID is set automatically by common frameworks (e.g. Binder and Handler frameworks)
+ * and automatically propagated inside system server.
+ * <p>It can also be set manually using {@link #setUid(int)}.
+ */
+ public static int getUid() {
+ return sWorkSourceUid.get();
+ }
+
+ /**
+ * Sets the UID to blame for the code currently executed on this thread.
+ *
+ * <p>Inside system server, this UID will be automatically propagated.
+ * <p>It will be used to attribute future resources used on this thread (e.g. binder
+ * transactions or processing handler messages) and on any other threads the UID is propagated
+ * to.
+ *
+ * @return a token that can be used to restore the state.
+ */
+ public static long setUid(int uid) {
+ final long token = getToken();
+ sWorkSourceUid.set(uid);
+ return token;
+ }
+
+ /**
+ * Restores the state using the provided token.
+ */
+ public static void restore(long token) {
+ sWorkSourceUid.set(parseUidFromToken(token));
+ }
+
+ /**
+ * Clears the stored work source uid.
+ *
+ * <p>This method should be used when we do not know who to blame. If the UID to blame is the
+ * UID of the current process, it is better to attribute the work to the current process
+ * explicitly instead of clearing the work source:
+ *
+ * <pre>
+ * ThreadLocalWorkSource.setUid(Process.myUid());
+ * </pre>
+ *
+ * @return a token that can be used to restore the state.
+ **/
+ public static long clear() {
+ return setUid(UID_NONE);
+ }
+
+ private static int parseUidFromToken(long token) {
+ return (int) token;
+ }
+
+ private static long getToken() {
+ return sWorkSourceUid.get();
+ }
+
+ private ThreadLocalWorkSource() {
+ }
+}
diff --git a/core/java/android/os/ThreadLocalWorkSourceUid.java b/core/java/android/os/ThreadLocalWorkSourceUid.java
deleted file mode 100644
index df1d275..0000000
--- a/core/java/android/os/ThreadLocalWorkSourceUid.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-/**
- * @hide Only for use within system server.
- */
-public final class ThreadLocalWorkSourceUid {
- public static final int UID_NONE = Message.UID_NONE;
- private static final ThreadLocal<Integer> sWorkSourceUid =
- ThreadLocal.withInitial(() -> UID_NONE);
-
- /** Returns the original work source uid. */
- public static int get() {
- return sWorkSourceUid.get();
- }
-
- /** Sets the original work source uid. */
- public static void set(int uid) {
- sWorkSourceUid.set(uid);
- }
-
- /** Clears the stored work source uid. */
- public static void clear() {
- sWorkSourceUid.set(UID_NONE);
- }
-
- private ThreadLocalWorkSourceUid() {
- }
-}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index d679fc7..423ce77 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -268,6 +268,9 @@
public static final int ENCRYPTION_STATE_ERROR_CORRUPT =
IVold.ENCRYPTION_STATE_ERROR_CORRUPT;
+ /** @hide Prefix used in sandboxIds for apps with sharedUserIds */
+ public static final String SHARED_SANDBOX_PREFIX = "shared-";
+
private static volatile IStorageManager sStorageManager = null;
private final Context mContext;
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 67e52aa..16d454d 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -16,12 +16,11 @@
package android.provider;
-import static android.system.OsConstants.SEEK_SET;
-
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
import static com.android.internal.util.Preconditions.checkCollectionNotEmpty;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.content.ContentProviderClient;
@@ -29,13 +28,12 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.MimeTypeFilter;
import android.content.pm.ResolveInfo;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.graphics.ImageDecoder;
-import android.graphics.Matrix;
import android.graphics.Point;
import android.media.ExifInterface;
import android.net.Uri;
@@ -50,20 +48,13 @@
import android.os.ParcelableException;
import android.os.RemoteException;
import android.os.storage.StorageVolume;
-import android.system.ErrnoException;
-import android.system.Os;
import android.util.DataUnit;
import android.util.Log;
-import android.util.Size;
-import libcore.io.IoUtils;
-
-import java.io.BufferedInputStream;
import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -113,6 +104,54 @@
public static final String EXTRA_TARGET_URI = "android.content.extra.TARGET_URI";
/**
+ * Key for {@link DocumentsProvider} to query display name is matched.
+ * The match of display name is partial matching and case-insensitive.
+ * Ex: The value is "o", the display name of the results will contain
+ * both "foo" and "Open".
+ *
+ * @see DocumentsProvider#querySearchDocuments(String, String[],
+ * Bundle)
+ * {@hide}
+ */
+ public static final String QUERY_ARG_DISPLAY_NAME = "android:query-arg-display-name";
+
+ /**
+ * Key for {@link DocumentsProvider} to query mime types is matched.
+ * The value is a string array, it can support different mime types.
+ * Each items will be treated as "OR" condition. Ex: {"image/*" ,
+ * "video/*"}. The mime types of the results will contain both image
+ * type and video type.
+ *
+ * @see DocumentsProvider#querySearchDocuments(String, String[],
+ * Bundle)
+ * {@hide}
+ */
+ public static final String QUERY_ARG_MIME_TYPES = "android:query-arg-mime-types";
+
+ /**
+ * Key for {@link DocumentsProvider} to query the file size in bytes is
+ * larger than the value.
+ *
+ * @see DocumentsProvider#querySearchDocuments(String, String[],
+ * Bundle)
+ * {@hide}
+ */
+ public static final String QUERY_ARG_FILE_SIZE_OVER = "android:query-arg-file-size-over";
+
+ /**
+ * Key for {@link DocumentsProvider} to query the last modified time
+ * is newer than the value. The unit is in milliseconds since
+ * January 1, 1970 00:00:00.0 UTC.
+ *
+ * @see DocumentsProvider#querySearchDocuments(String, String[],
+ * Bundle)
+ * @see Document#COLUMN_LAST_MODIFIED
+ * {@hide}
+ */
+ public static final String QUERY_ARG_LAST_MODIFIED_AFTER =
+ "android:query-arg-last-modified-after";
+
+ /**
* Sets the desired initial location visible to user when file chooser is shown.
*
* <p>Applicable to {@link Intent} with actions:
@@ -929,6 +968,89 @@
}
/**
+ * Check if the values match the query arguments.
+ *
+ * @param queryArgs the query arguments
+ * @param displayName the display time to check against
+ * @param mimeType the mime type to check against
+ * @param lastModified the last modified time to check against
+ * @param size the size to check against
+ * @hide
+ */
+ public static boolean matchSearchQueryArguments(Bundle queryArgs, String displayName,
+ String mimeType, long lastModified, long size) {
+ if (queryArgs == null) {
+ return true;
+ }
+
+ final String argDisplayName = queryArgs.getString(QUERY_ARG_DISPLAY_NAME, "");
+ if (!argDisplayName.isEmpty()) {
+ // TODO (118795812) : Enhance the search string handled in DocumentsProvider
+ if (!displayName.toLowerCase().contains(argDisplayName.toLowerCase())) {
+ return false;
+ }
+ }
+
+ final long argFileSize = queryArgs.getLong(QUERY_ARG_FILE_SIZE_OVER, -1 /* defaultValue */);
+ if (argFileSize != -1 && size < argFileSize) {
+ return false;
+ }
+
+ final long argLastModified = queryArgs.getLong(QUERY_ARG_LAST_MODIFIED_AFTER,
+ -1 /* defaultValue */);
+ if (argLastModified != -1 && lastModified < argLastModified) {
+ return false;
+ }
+
+ final String[] argMimeTypes = queryArgs.getStringArray(QUERY_ARG_MIME_TYPES);
+ if (argMimeTypes != null && argMimeTypes.length > 0) {
+ mimeType = Intent.normalizeMimeType(mimeType);
+ for (String type : argMimeTypes) {
+ if (MimeTypeFilter.matches(mimeType, Intent.normalizeMimeType(type))) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Get the handled query arguments from the query bundle. The handled arguments are
+ * {@link DocumentsContract#QUERY_ARG_DISPLAY_NAME},
+ * {@link DocumentsContract#QUERY_ARG_MIME_TYPES},
+ * {@link DocumentsContract#QUERY_ARG_FILE_SIZE_OVER} and
+ * {@link DocumentsContract#QUERY_ARG_LAST_MODIFIED_AFTER}.
+ *
+ * @param queryArgs the query arguments to be parsed.
+ * @return the handled query arguments
+ * @hide
+ */
+ public static String[] getHandledQueryArguments(Bundle queryArgs) {
+ if (queryArgs == null) {
+ return new String[0];
+ }
+
+ final ArrayList<String> args = new ArrayList<>();
+ if (queryArgs.keySet().contains(QUERY_ARG_DISPLAY_NAME)) {
+ args.add(QUERY_ARG_DISPLAY_NAME);
+ }
+
+ if (queryArgs.keySet().contains(QUERY_ARG_FILE_SIZE_OVER)) {
+ args.add(QUERY_ARG_FILE_SIZE_OVER);
+ }
+
+ if (queryArgs.keySet().contains(QUERY_ARG_LAST_MODIFIED_AFTER)) {
+ args.add(QUERY_ARG_LAST_MODIFIED_AFTER);
+ }
+
+ if (queryArgs.keySet().contains(QUERY_ARG_MIME_TYPES)) {
+ args.add(QUERY_ARG_MIME_TYPES);
+ }
+ return args.toArray(new String[0]);
+ }
+
+ /**
* Test if the given URI represents a {@link Document} backed by a
* {@link DocumentsProvider}.
*
@@ -1052,6 +1174,15 @@
return searchDocumentsUri.getQueryParameter(PARAM_QUERY);
}
+ /**
+ * Extract the search query from a Bundle
+ * {@link #QUERY_ARG_DISPLAY_NAME}.
+ * {@hide}
+ */
+ public static String getSearchDocumentsQuery(@NonNull Bundle bundle) {
+ return bundle.getString(QUERY_ARG_DISPLAY_NAME, "" /* defaultValue */);
+ }
+
/** {@hide} */
@UnsupportedAppUsage
public static Uri setManageMode(Uri uri) {
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 68f8acd..58f8213 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -32,7 +32,6 @@
import static android.provider.DocumentsContract.buildTreeDocumentUri;
import static android.provider.DocumentsContract.getDocumentId;
import static android.provider.DocumentsContract.getRootId;
-import static android.provider.DocumentsContract.getSearchDocumentsQuery;
import static android.provider.DocumentsContract.getTreeDocumentId;
import static android.provider.DocumentsContract.isTreeUri;
@@ -47,6 +46,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.MimeTypeFilter;
import android.content.UriMatcher;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
@@ -651,6 +651,55 @@
}
/**
+ * Return documents that match the given query under the requested
+ * root. The returned documents should be sorted by relevance in descending
+ * order. How documents are matched against the query string is an
+ * implementation detail left to each provider, but it's suggested that at
+ * least {@link Document#COLUMN_DISPLAY_NAME} be matched in a
+ * case-insensitive fashion.
+ * <p>
+ * If your provider is cloud-based, and you have some data cached or pinned
+ * locally, you may return the local data immediately, setting
+ * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
+ * you are still fetching additional data. Then, when the network data is
+ * available, you can send a change notification to trigger a requery and
+ * return the complete contents.
+ * <p>
+ * To support change notifications, you must
+ * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
+ * Uri, such as {@link DocumentsContract#buildSearchDocumentsUri(String,
+ * String, String)}. Then you can call {@link ContentResolver#notifyChange(Uri,
+ * android.database.ContentObserver, boolean)} with that Uri to send change
+ * notifications.
+ *
+ * @param rootId the root to search under.
+ * @param projection list of {@link Document} columns to put into the
+ * cursor. If {@code null} all supported columns should be
+ * included.
+ * @param queryArgs the query arguments.
+ * {@link DocumentsContract#QUERY_ARG_DISPLAY_NAME},
+ * {@link DocumentsContract#QUERY_ARG_MIME_TYPES},
+ * {@link DocumentsContract#QUERY_ARG_FILE_SIZE_OVER},
+ * {@link DocumentsContract#QUERY_ARG_LAST_MODIFIED_AFTER}.
+ * @return cursor containing search result. Include
+ * {@link ContentResolver#EXTRA_HONORED_ARGS} in {@link Cursor}
+ * extras {@link Bundle} when any QUERY_ARG_* value was honored
+ * during the preparation of the results.
+ *
+ * @see ContentResolver#EXTRA_HONORED_ARGS
+ * @see DocumentsContract#EXTRA_LOADING
+ * @see DocumentsContract#EXTRA_INFO
+ * @see DocumentsContract#EXTRA_ERROR
+ * {@hide}
+ */
+ @SuppressWarnings("unused")
+ public Cursor querySearchDocuments(String rootId, String[] projection, Bundle queryArgs)
+ throws FileNotFoundException {
+ return querySearchDocuments(rootId, DocumentsContract.getSearchDocumentsQuery(queryArgs),
+ projection);
+ }
+
+ /**
* Ejects the root. Throws {@link IllegalStateException} if ejection failed.
*
* @param rootId the root to be ejected.
@@ -795,7 +844,7 @@
* {@link #queryDocument(String, String[])},
* {@link #queryRecentDocuments(String, String[])},
* {@link #queryRoots(String[])}, and
- * {@link #querySearchDocuments(String, String, String[])}.
+ * {@link #querySearchDocuments(String, String[], Bundle)}.
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection,
@@ -812,7 +861,7 @@
* @see #queryRecentDocuments(String, String[], Bundle, CancellationSignal)
* @see #queryDocument(String, String[])
* @see #queryChildDocuments(String, String[], String)
- * @see #querySearchDocuments(String, String, String[])
+ * @see #querySearchDocuments(String, String[], Bundle)
*/
@Override
public final Cursor query(
@@ -825,8 +874,7 @@
return queryRecentDocuments(
getRootId(uri), projection, queryArgs, cancellationSignal);
case MATCH_SEARCH:
- return querySearchDocuments(
- getRootId(uri), getSearchDocumentsQuery(uri), projection);
+ return querySearchDocuments(getRootId(uri), projection, queryArgs);
case MATCH_DOCUMENT:
case MATCH_DOCUMENT_TREE:
enforceTree(uri);
@@ -1301,7 +1349,7 @@
final long flags =
cursor.getLong(cursor.getColumnIndexOrThrow(Document.COLUMN_FLAGS));
if ((flags & Document.FLAG_VIRTUAL_DOCUMENT) == 0 && mimeType != null &&
- mimeTypeMatches(mimeTypeFilter, mimeType)) {
+ MimeTypeFilter.matches(mimeType, mimeTypeFilter)) {
return new String[] { mimeType };
}
}
@@ -1354,21 +1402,4 @@
// For any other yet unhandled case, let the provider subclass handle it.
return openTypedDocument(documentId, mimeTypeFilter, opts, signal);
}
-
- /**
- * @hide
- */
- public static boolean mimeTypeMatches(String filter, String test) {
- if (test == null) {
- return false;
- } else if (filter == null || "*/*".equals(filter)) {
- return true;
- } else if (filter.equals(test)) {
- return true;
- } else if (filter.endsWith("/*")) {
- return filter.regionMatches(0, test, 0, filter.indexOf('/'));
- } else {
- return false;
- }
- }
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 0402222..b266648 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1825,53 +1825,6 @@
})
public @interface ResetMode{}
-
- /**
- * Indicates that the user has not started setup personalization.
- * One of the possible states for {@link Secure#USER_SETUP_PERSONALIZATION_STATE}.
- *
- * @hide
- */
- @SystemApi
- public static final int USER_SETUP_PERSONALIZATION_NOT_STARTED = 0;
-
- /**
- * Indicates that the user has not yet completed setup personalization.
- * One of the possible states for {@link Secure#USER_SETUP_PERSONALIZATION_STATE}.
- *
- * @hide
- */
- @SystemApi
- public static final int USER_SETUP_PERSONALIZATION_STARTED = 1;
-
- /**
- * Indicates that the user has snoozed personalization and will complete it later.
- * One of the possible states for {@link Secure#USER_SETUP_PERSONALIZATION_STATE}.
- *
- * @hide
- */
- @SystemApi
- public static final int USER_SETUP_PERSONALIZATION_PAUSED = 2;
-
- /**
- * Indicates that the user has completed setup personalization.
- * One of the possible states for {@link Secure#USER_SETUP_PERSONALIZATION_STATE}.
- *
- * @hide
- */
- @SystemApi
- public static final int USER_SETUP_PERSONALIZATION_COMPLETE = 10;
-
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({
- USER_SETUP_PERSONALIZATION_NOT_STARTED,
- USER_SETUP_PERSONALIZATION_STARTED,
- USER_SETUP_PERSONALIZATION_PAUSED,
- USER_SETUP_PERSONALIZATION_COMPLETE
- })
- public @interface UserSetupPersonalization {}
-
/**
* Activity Extra: Number of certificates
* <p>
@@ -5650,6 +5603,52 @@
public static final String USER_SETUP_COMPLETE = "user_setup_complete";
/**
+ * Indicates that the user has not started setup personalization.
+ * One of the possible states for {@link #USER_SETUP_PERSONALIZATION_STATE}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int USER_SETUP_PERSONALIZATION_NOT_STARTED = 0;
+
+ /**
+ * Indicates that the user has not yet completed setup personalization.
+ * One of the possible states for {@link #USER_SETUP_PERSONALIZATION_STATE}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int USER_SETUP_PERSONALIZATION_STARTED = 1;
+
+ /**
+ * Indicates that the user has snoozed personalization and will complete it later.
+ * One of the possible states for {@link #USER_SETUP_PERSONALIZATION_STATE}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int USER_SETUP_PERSONALIZATION_PAUSED = 2;
+
+ /**
+ * Indicates that the user has completed setup personalization.
+ * One of the possible states for {@link #USER_SETUP_PERSONALIZATION_STATE}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int USER_SETUP_PERSONALIZATION_COMPLETE = 10;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ USER_SETUP_PERSONALIZATION_NOT_STARTED,
+ USER_SETUP_PERSONALIZATION_STARTED,
+ USER_SETUP_PERSONALIZATION_PAUSED,
+ USER_SETUP_PERSONALIZATION_COMPLETE
+ })
+ public @interface UserSetupPersonalization {}
+
+ /**
* Defines the user's current state of device personalization.
* The possible states are defined in {@link UserSetupPersonalization}.
*
@@ -10325,6 +10324,18 @@
private static final Validator WIFI_PNO_FREQUENCY_CULLING_ENABLED_VALIDATOR =
BOOLEAN_VALIDATOR;
+ /**
+ * Setting to enable including recency information when determining pno network priorities.
+ * Disabled by default, and setting it to 1 will enable it.
+ * The value is boolean (0 or 1).
+ * @hide
+ */
+ public static final String WIFI_PNO_RECENCY_SORTING_ENABLED =
+ "wifi_pno_recency_sorting_enabled";
+
+ private static final Validator WIFI_PNO_RECENCY_SORTING_ENABLED_VALIDATOR =
+ BOOLEAN_VALIDATOR;
+
/**
* The maximum number of times we will retry a connection to an access
* point for which we have failed in acquiring an IP address from DHCP.
@@ -12800,6 +12811,8 @@
VALIDATORS.put(DEVICE_DEMO_MODE, BOOLEAN_VALIDATOR);
VALIDATORS.put(WIFI_PNO_FREQUENCY_CULLING_ENABLED,
WIFI_PNO_FREQUENCY_CULLING_ENABLED_VALIDATOR);
+ VALIDATORS.put(WIFI_PNO_RECENCY_SORTING_ENABLED,
+ WIFI_PNO_RECENCY_SORTING_ENABLED_VALIDATOR);
}
/**
diff --git a/core/java/android/rolecontrollerservice/IRoleControllerService.aidl b/core/java/android/rolecontrollerservice/IRoleControllerService.aidl
index 0000b9f..ac5be06 100644
--- a/core/java/android/rolecontrollerservice/IRoleControllerService.aidl
+++ b/core/java/android/rolecontrollerservice/IRoleControllerService.aidl
@@ -30,4 +30,6 @@
in IRoleManagerCallback callback);
void onClearRoleHolders(in String roleName, in IRoleManagerCallback callback);
+
+ void onGrantDefaultRoles(in IRoleManagerCallback callback);
}
diff --git a/core/java/android/rolecontrollerservice/RoleControllerService.java b/core/java/android/rolecontrollerservice/RoleControllerService.java
index da11bca..44c45bb 100644
--- a/core/java/android/rolecontrollerservice/RoleControllerService.java
+++ b/core/java/android/rolecontrollerservice/RoleControllerService.java
@@ -89,6 +89,13 @@
RoleControllerService.this.onClearRoleHolders(roleName,
new RoleManagerCallbackDelegate(callback));
}
+
+ @Override
+ public void onGrantDefaultRoles(IRoleManagerCallback callback) {
+ Preconditions.checkNotNull(callback, "callback cannot be null");
+ RoleControllerService.this.onGrantDefaultRoles(
+ new RoleManagerCallbackDelegate(callback));
+ }
};
}
@@ -133,6 +140,16 @@
public abstract void onClearRoleHolders(@NonNull String roleName,
@NonNull RoleManagerCallback callback);
+ /**
+ * Called by system to grant default permissions and roles.
+ * <p>
+ * This is typically when creating a new user or upgrading either system or
+ * permission controller package
+ *
+ * @param callback the callback for whether this call is successful
+ */
+ public abstract void onGrantDefaultRoles(@NonNull RoleManagerCallback callback);
+
private static class RoleManagerCallbackDelegate implements RoleManagerCallback {
private IRoleManagerCallback mCallback;
diff --git a/core/java/android/security/keymaster/ExportResult.java b/core/java/android/security/keymaster/ExportResult.java
index c104671..1ab79fb 100644
--- a/core/java/android/security/keymaster/ExportResult.java
+++ b/core/java/android/security/keymaster/ExportResult.java
@@ -28,6 +28,11 @@
public final int resultCode;
public final byte[] exportData;
+ public ExportResult(int resultCode) {
+ this.resultCode = resultCode;
+ this.exportData = new byte[0];
+ }
+
@UnsupportedAppUsage
public static final Parcelable.Creator<ExportResult> CREATOR = new
Parcelable.Creator<ExportResult>() {
diff --git a/core/java/android/security/keymaster/KeyCharacteristics.java b/core/java/android/security/keymaster/KeyCharacteristics.java
index 555863e..a4fe75d 100644
--- a/core/java/android/security/keymaster/KeyCharacteristics.java
+++ b/core/java/android/security/keymaster/KeyCharacteristics.java
@@ -52,6 +52,14 @@
readFromParcel(in);
}
+ /**
+ * Makes a shallow copy of other by copying the other's references to the KeymasterArguments
+ */
+ public void shallowCopyFrom(KeyCharacteristics other) {
+ this.swEnforced = other.swEnforced;
+ this.hwEnforced = other.hwEnforced;
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/security/keymaster/KeymasterCertificateChain.java b/core/java/android/security/keymaster/KeymasterCertificateChain.java
index 243b9fe..00a1a1c 100644
--- a/core/java/android/security/keymaster/KeymasterCertificateChain.java
+++ b/core/java/android/security/keymaster/KeymasterCertificateChain.java
@@ -54,6 +54,14 @@
readFromParcel(in);
}
+ /**
+ * Makes a shallow copy of other by copying the reference to the certificate chain list.
+ * @param other
+ */
+ public void shallowCopyFrom(KeymasterCertificateChain other) {
+ this.mCertificates = other.mCertificates;
+ }
+
public List<byte[]> getCertificates() {
return mCertificates;
}
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index f4dcce1..15ded8d 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -154,7 +154,7 @@
// User authenticators.
public static final int HW_AUTH_PASSWORD = 1 << 0;
- public static final int HW_AUTH_FINGERPRINT = 1 << 1;
+ public static final int HW_AUTH_BIOMETRIC = 1 << 1;
// Error codes.
public static final int KM_ERROR_OK = 0;
diff --git a/core/java/android/security/keymaster/OperationResult.java b/core/java/android/security/keymaster/OperationResult.java
index 2943211..bc4f360 100644
--- a/core/java/android/security/keymaster/OperationResult.java
+++ b/core/java/android/security/keymaster/OperationResult.java
@@ -59,6 +59,10 @@
this.outParams = outParams;
}
+ public OperationResult(int resultCode) {
+ this(resultCode, null, 0, 0, null, null);
+ }
+
protected OperationResult(Parcel in) {
resultCode = in.readInt();
token = in.readStrongBinder();
diff --git a/core/java/android/service/carrier/ApnService.java b/core/java/android/service/carrier/ApnService.java
new file mode 100644
index 0000000..d53eb37
--- /dev/null
+++ b/core/java/android/service/carrier/ApnService.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.carrier;
+
+import android.annotation.SystemApi;
+import android.annotation.WorkerThread;
+import android.app.Service;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.internal.telephony.IApnSourceService;
+
+import java.util.List;
+
+/**
+ * A service that the system can call to restore default APNs.
+ * <p>
+ * To extend this class, specify the full name of your implementation in the resource file
+ * {@code packages/providers/TelephonyProvider/res/values/config.xml} as the
+ * {@code apn_source_service}.
+ * </p>
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class ApnService extends Service {
+
+ private static final String LOG_TAG = "ApnService";
+
+ private final IApnSourceService.Stub mBinder = new IApnSourceService.Stub() {
+ /**
+ * Retreive APNs for the default slot index.
+ */
+ @Override
+ public ContentValues[] getApns(int subId) {
+ try {
+ List<ContentValues> apns = ApnService.this.onRestoreApns(subId);
+ return apns.toArray(new ContentValues[apns.size()]);
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Error in getApns for subId=" + subId + ": " + e.getMessage(), e);
+ return null;
+ }
+ }
+ };
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ /**
+ * Override this method to restore default user APNs with a carrier service instead of the
+ * built in platform xml APNs list.
+ * <p>
+ * This method is called by the TelephonyProvider when the user requests restoring the default
+ * APNs. It should return a list of ContentValues representing the default APNs for the given
+ * subId.
+ */
+ @WorkerThread
+ public abstract List<ContentValues> onRestoreApns(int subId);
+}
diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java
index b87faef..49a7320 100644
--- a/core/java/android/service/euicc/EuiccService.java
+++ b/core/java/android/service/euicc/EuiccService.java
@@ -82,6 +82,13 @@
// LUI actions. These are passthroughs of the corresponding EuiccManager actions.
/**
+ * Action used to bind the carrier app and get the activation code from the carrier app. This
+ * activation code will be used to download the eSIM profile during eSIM activation flow.
+ */
+ public static final String ACTION_BIND_CARRIER_PROVISIONING_SERVICE =
+ "android.service.euicc.action.BIND_CARRIER_PROVISIONING_SERVICE";
+
+ /**
* @see android.telephony.euicc.EuiccManager#ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS
* The difference is this one is used by system to bring up the LUI.
*/
diff --git a/core/java/android/service/intelligence/InteractionContext.java b/core/java/android/service/intelligence/InteractionContext.java
index c1803ad..0cc377b 100644
--- a/core/java/android/service/intelligence/InteractionContext.java
+++ b/core/java/android/service/intelligence/InteractionContext.java
@@ -37,7 +37,7 @@
/**
* Flag used to indicate that the app explicitly disabled content capture for the activity
* (using
- * {@link android.view.intelligence.IntelligenceManager#disableContentCapture()}),
+ * {@link android.view.intelligence.IntelligenceManager#setContentCaptureEnabled()}),
* in which case the service will just receive activity-level events.
*/
public static final int FLAG_DISABLED_BY_APP = 0x1;
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index e5fd292..2d5f3bf 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1270,7 +1270,13 @@
*/
public float getLineLeft(int line) {
final int dir = getParagraphDirection(line);
- final Alignment align = getParagraphAlignment(line);
+ Alignment align = getParagraphAlignment(line);
+ // Before Q, StaticLayout.Builder.setAlignment didn't check whether the input alignment
+ // is null. And when it is null, the old behavior is the same as ALIGN_CENTER.
+ // To keep consistency, we convert a null alignment to ALIGN_CENTER.
+ if (align == null) {
+ align = Alignment.ALIGN_CENTER;
+ }
// First convert combinations of alignment and direction settings to
// three basic cases: ALIGN_LEFT, ALIGN_RIGHT and ALIGN_CENTER.
@@ -1319,7 +1325,13 @@
*/
public float getLineRight(int line) {
final int dir = getParagraphDirection(line);
- final Alignment align = getParagraphAlignment(line);
+ Alignment align = getParagraphAlignment(line);
+ // Before Q, StaticLayout.Builder.setAlignment didn't check whether the input alignment
+ // is null. And when it is null, the old behavior is the same as ALIGN_CENTER.
+ // To keep consistency, we convert a null alignment to ALIGN_CENTER.
+ if (align == null) {
+ align = Alignment.ALIGN_CENTER;
+ }
final Alignment resultAlign;
switch(align) {
@@ -2360,6 +2372,52 @@
public Directions(int[] dirs) {
mDirections = dirs;
}
+
+ /**
+ * Returns number of BiDi runs.
+ *
+ * @hide
+ */
+ public @IntRange(from = 0) int getRunCount() {
+ return mDirections.length / 2;
+ }
+
+ /**
+ * Returns the start offset of the BiDi run.
+ *
+ * @param runIndex the index of the BiDi run
+ * @return the start offset of the BiDi run.
+ * @hide
+ */
+ public @IntRange(from = 0) int getRunStart(@IntRange(from = 0) int runIndex) {
+ return mDirections[runIndex * 2];
+ }
+
+ /**
+ * Returns the length of the BiDi run.
+ *
+ * Note that this method may return too large number due to reducing the number of object
+ * allocations. The too large number means the remaining part is assigned to this run. The
+ * caller must clamp the returned value.
+ *
+ * @param runIndex the index of the BiDi run
+ * @return the length of the BiDi run.
+ * @hide
+ */
+ public @IntRange(from = 0) int getRunLength(@IntRange(from = 0) int runIndex) {
+ return mDirections[runIndex * 2 + 1] & RUN_LENGTH_MASK;
+ }
+
+ /**
+ * Returns true if the BiDi run is RTL.
+ *
+ * @param runIndex the index of the BiDi run
+ * @return true if the BiDi run is RTL.
+ * @hide
+ */
+ public boolean isRunRtl(int runIndex) {
+ return (mDirections[runIndex * 2 + 1] & RUN_RTL_FLAG) != 0;
+ }
}
/**
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 44dfd11..6eb433a 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -16,6 +16,7 @@
package android.text;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
@@ -51,6 +52,8 @@
public class TextLine {
private static final boolean DEBUG = false;
+ private static final char TAB_CHAR = '\t';
+
private TextPaint mPaint;
@UnsupportedAppUsage
private CharSequence mText;
@@ -198,7 +201,7 @@
}
}
- mCharsValid = hasReplacement || hasTabs || directions != Layout.DIRS_ALL_LEFT_TO_RIGHT;
+ mCharsValid = hasReplacement;
if (mCharsValid) {
if (mChars == null || mChars.length < mLen) {
@@ -232,6 +235,10 @@
mEllipsisEnd = ellipsisStart != ellipsisEnd ? ellipsisEnd : 0;
}
+ private char charAt(int i) {
+ return mCharsValid ? mChars[i] : mText.charAt(i + mStart);
+ }
+
/**
* Justify the line to the given width.
*/
@@ -261,51 +268,23 @@
* @param bottom the bottom of the line
*/
void draw(Canvas c, float x, int top, int y, int bottom) {
- if (!mHasTabs) {
- if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
- drawRun(c, 0, mLen, false, x, top, y, bottom, false);
- return;
- }
- if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
- drawRun(c, 0, mLen, true, x, top, y, bottom, false);
- return;
- }
- }
-
float h = 0;
- int[] runs = mDirections.mDirections;
+ final int runCount = mDirections.getRunCount();
+ for (int runIndex = 0; runIndex < runCount; runIndex++) {
+ final int runStart = mDirections.getRunStart(runIndex);
+ final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
+ final boolean runIsRtl = mDirections.isRunRtl(runIndex);
- int lastRunIndex = runs.length - 2;
- for (int i = 0; i < runs.length; i += 2) {
- int runStart = runs[i];
- int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
- if (runLimit > mLen) {
- runLimit = mLen;
- }
- boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
-
- int segstart = runStart;
+ int segStart = runStart;
for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
- int codept = 0;
- if (mHasTabs && j < runLimit) {
- codept = mChars[j];
- if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) {
- codept = Character.codePointAt(mChars, j);
- if (codept > 0xFFFF) {
- ++j;
- continue;
- }
- }
- }
+ if (j == runLimit || charAt(j) == TAB_CHAR) {
+ h += drawRun(c, segStart, j, runIsRtl, x + h, top, y, bottom,
+ runIndex != (runCount - 1) || j != mLen);
- if (j == runLimit || codept == '\t') {
- h += drawRun(c, segstart, j, runIsRtl, x+h, top, y, bottom,
- i != lastRunIndex || j != mLen);
-
- if (codept == '\t') {
+ if (j != runLimit) { // charAt(j) == TAB_CHAR
h = mDir * nextTab(h * mDir);
}
- segstart = j + 1;
+ segStart = j + 1;
}
}
}
@@ -323,75 +302,81 @@
}
/**
- * Returns information about a position on the line.
+ * Returns the signed graphical offset from the leading margin.
*
- * @param offset the line-relative character offset, between 0 and the
- * line length, inclusive
- * @param trailing true to measure the trailing edge of the character
- * before offset, false to measure the leading edge of the character
- * at offset.
- * @param fmi receives metrics information about the requested
- * character, can be null.
- * @return the signed offset from the leading margin to the requested
- * character edge.
+ * Following examples are all for measuring offset=3. LX(e.g. L0, L1, ...) denotes a
+ * character which has LTR BiDi property. On the other hand, RX(e.g. R0, R1, ...) denotes a
+ * character which has RTL BiDi property. Assuming all character has 1em width.
+ *
+ * Example 1: All LTR chars within LTR context
+ * Input Text (logical) : L0 L1 L2 L3 L4 L5 L6 L7 L8
+ * Input Text (visual) : L0 L1 L2 L3 L4 L5 L6 L7 L8
+ * Output(trailing=true) : |--------| (Returns 3em)
+ * Output(trailing=false): |--------| (Returns 3em)
+ *
+ * Example 2: All RTL chars within RTL context.
+ * Input Text (logical) : R0 R1 R2 R3 R4 R5 R6 R7 R8
+ * Input Text (visual) : R8 R7 R6 R5 R4 R3 R2 R1 R0
+ * Output(trailing=true) : |--------| (Returns -3em)
+ * Output(trailing=false): |--------| (Returns -3em)
+ *
+ * Example 3: BiDi chars within LTR context.
+ * Input Text (logical) : L0 L1 L2 R3 R4 R5 L6 L7 L8
+ * Input Text (visual) : L0 L1 L2 R5 R4 R3 L6 L7 L8
+ * Output(trailing=true) : |-----------------| (Returns 6em)
+ * Output(trailing=false): |--------| (Returns 3em)
+ *
+ * Example 4: BiDi chars within RTL context.
+ * Input Text (logical) : L0 L1 L2 R3 R4 R5 L6 L7 L8
+ * Input Text (visual) : L6 L7 L8 R5 R4 R3 L0 L1 L2
+ * Output(trailing=true) : |-----------------| (Returns -6em)
+ * Output(trailing=false): |--------| (Returns -3em)
+ *
+ * @param offset the line-relative character offset, between 0 and the line length, inclusive
+ * @param trailing no effect if the offset is not on the BiDi transition offset. If the offset
+ * is on the BiDi transition offset and true is passed, the offset is regarded
+ * as the edge of the trailing run's edge. If false, the offset is regarded as
+ * the edge of the preceding run's edge. See example above.
+ * @param fmi receives metrics information about the requested character, can be null
+ * @return the signed graphical offset from the leading margin to the requested character edge.
+ * The positive value means the offset is right from the leading edge. The negative
+ * value means the offset is left from the leading edge.
*/
- public float measure(int offset, boolean trailing, FontMetricsInt fmi) {
- int target = trailing ? offset - 1 : offset;
+ public float measure(@IntRange(from = 0) int offset, boolean trailing,
+ @NonNull FontMetricsInt fmi) {
+ if (offset > mLen) {
+ throw new IndexOutOfBoundsException(
+ "offset(" + offset + ") should be less than line limit(" + mLen + ")");
+ }
+ final int target = trailing ? offset - 1 : offset;
if (target < 0) {
return 0;
}
float h = 0;
+ for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
+ final int runStart = mDirections.getRunStart(runIndex);
+ final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
+ final boolean runIsRtl = mDirections.isRunRtl(runIndex);
- if (!mHasTabs) {
- if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
- return measureRun(0, offset, mLen, false, fmi);
- }
- if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
- return measureRun(0, offset, mLen, true, fmi);
- }
- }
-
- char[] chars = mChars;
- int[] runs = mDirections.mDirections;
- for (int i = 0; i < runs.length; i += 2) {
- int runStart = runs[i];
- int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
- if (runLimit > mLen) {
- runLimit = mLen;
- }
- boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
-
- int segstart = runStart;
+ int segStart = runStart;
for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
- int codept = 0;
- if (mHasTabs && j < runLimit) {
- codept = chars[j];
- if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) {
- codept = Character.codePointAt(chars, j);
- if (codept > 0xFFFF) {
- ++j;
- continue;
- }
- }
- }
+ if (j == runLimit || charAt(j) == TAB_CHAR) {
+ final boolean targetIsInThisSegment = target >= segStart && target < j;
+ final boolean sameDirection = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
- if (j == runLimit || codept == '\t') {
- boolean inSegment = target >= segstart && target < j;
-
- boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
- if (inSegment && advance) {
- return h + measureRun(segstart, offset, j, runIsRtl, fmi);
+ if (targetIsInThisSegment && sameDirection) {
+ return h + measureRun(segStart, offset, j, runIsRtl, fmi);
}
- float w = measureRun(segstart, j, j, runIsRtl, fmi);
- h += advance ? w : -w;
+ final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi);
+ h += sameDirection ? segmentWidth : -segmentWidth;
- if (inSegment) {
- return h + measureRun(segstart, offset, j, runIsRtl, null);
+ if (targetIsInThisSegment) {
+ return h + measureRun(segStart, offset, j, runIsRtl, null);
}
- if (codept == '\t') {
+ if (j != runLimit) { // charAt(j) == TAB_CHAR
if (offset == j) {
return h;
}
@@ -401,7 +386,7 @@
}
}
- segstart = j + 1;
+ segStart = j + 1;
}
}
}
@@ -426,62 +411,29 @@
}
float h = 0;
+ for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
+ final int runStart = mDirections.getRunStart(runIndex);
+ final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
+ final boolean runIsRtl = mDirections.isRunRtl(runIndex);
- if (!mHasTabs) {
- if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
- for (int offset = 0; offset <= mLen; ++offset) {
- measurement[offset] = measureRun(0, offset, mLen, false, fmi);
- }
- return measurement;
- }
- if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
- for (int offset = 0; offset <= mLen; ++offset) {
- measurement[offset] = measureRun(0, offset, mLen, true, fmi);
- }
- return measurement;
- }
- }
-
- char[] chars = mChars;
- int[] runs = mDirections.mDirections;
- for (int i = 0; i < runs.length; i += 2) {
- int runStart = runs[i];
- int runLimit = runStart + (runs[i + 1] & Layout.RUN_LENGTH_MASK);
- if (runLimit > mLen) {
- runLimit = mLen;
- }
- boolean runIsRtl = (runs[i + 1] & Layout.RUN_RTL_FLAG) != 0;
-
- int segstart = runStart;
+ int segStart = runStart;
for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; ++j) {
- int codept = 0;
- if (mHasTabs && j < runLimit) {
- codept = chars[j];
- if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) {
- codept = Character.codePointAt(chars, j);
- if (codept > 0xFFFF) {
- ++j;
- continue;
- }
- }
- }
-
- if (j == runLimit || codept == '\t') {
- float oldh = h;
- boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
- float w = measureRun(segstart, j, j, runIsRtl, fmi);
+ if (j == runLimit || charAt(j) == TAB_CHAR) {
+ final float oldh = h;
+ final boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
+ final float w = measureRun(segStart, j, j, runIsRtl, fmi);
h += advance ? w : -w;
- float baseh = advance ? oldh : h;
+ final float baseh = advance ? oldh : h;
FontMetricsInt crtfmi = advance ? fmi : null;
- for (int offset = segstart; offset <= j && offset <= mLen; ++offset) {
- if (target[offset] >= segstart && target[offset] < j) {
+ for (int offset = segStart; offset <= j && offset <= mLen; ++offset) {
+ if (target[offset] >= segStart && target[offset] < j) {
measurement[offset] =
- baseh + measureRun(segstart, offset, j, runIsRtl, crtfmi);
+ baseh + measureRun(segStart, offset, j, runIsRtl, crtfmi);
}
}
- if (codept == '\t') {
+ if (j != runLimit) { // charAt(j) == TAB_CHAR
if (target[j] == j) {
measurement[j] = h;
}
@@ -491,7 +443,7 @@
}
}
- segstart = j + 1;
+ segStart = j + 1;
}
}
}
@@ -863,7 +815,6 @@
} else {
final int delta = mStart;
if (mComputed == null) {
- // TODO: Enable measured getRunAdvance for ReplacementSpan and RTL text.
return wp.getRunAdvance(mText, delta + start, delta + end,
delta + contextStart, delta + contextEnd, runIsRtl, delta + offset);
} else {
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index bad26ed..0cb7c28 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -46,7 +46,7 @@
DEFAULT_FLAGS.put("settings_systemui_theme", "true");
DEFAULT_FLAGS.put("settings_dynamic_homepage", "true");
DEFAULT_FLAGS.put("settings_mobile_network_v2", "true");
- DEFAULT_FLAGS.put("settings_data_usage_v2", "false");
+ DEFAULT_FLAGS.put("settings_data_usage_v2", "true");
DEFAULT_FLAGS.put("settings_seamless_transfer", "false");
DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "false");
DEFAULT_FLAGS.put(EMERGENCY_DIAL_SHORTCUTS, "true");
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 0e25038..717a858 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -16,16 +16,24 @@
package android.util;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.os.SystemClock;
+import libcore.util.CountryTimeZones;
+import libcore.util.CountryTimeZones.TimeZoneMapping;
import libcore.util.TimeZoneFinder;
import libcore.util.ZoneInfoDB;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Calendar;
+import java.util.Collections;
import java.util.Date;
+import java.util.List;
+
/**
* A class containing utility methods related to time zones.
*/
@@ -65,6 +73,38 @@
}
/**
+ * Returns time zone IDs for time zones known to be associated with a country.
+ *
+ * <p>The list returned may be different from other on-device sources like
+ * {@link android.icu.util.TimeZone#getRegion(String)} as it can be curated to avoid
+ * contentious mappings.
+ *
+ * @param countryCode the ISO 3166-1 alpha-2 code for the country as can be obtained using
+ * {@link java.util.Locale#getCountry()}
+ * @return IDs that can be passed to {@link java.util.TimeZone#getTimeZone(String)} or similar
+ * methods, or {@code null} if the countryCode is unrecognized
+ */
+ public static @Nullable List<String> getTimeZoneIdsForCountryCode(@NonNull String countryCode) {
+ if (countryCode == null) {
+ throw new NullPointerException("countryCode == null");
+ }
+ TimeZoneFinder timeZoneFinder = TimeZoneFinder.getInstance();
+ CountryTimeZones countryTimeZones =
+ timeZoneFinder.lookupCountryTimeZones(countryCode.toLowerCase());
+ if (countryTimeZones == null) {
+ return null;
+ }
+
+ List<String> timeZoneIds = new ArrayList<>();
+ for (TimeZoneMapping timeZoneMapping : countryTimeZones.getTimeZoneMappings()) {
+ if (timeZoneMapping.showInPicker) {
+ timeZoneIds.add(timeZoneMapping.timeZoneId);
+ }
+ }
+ return Collections.unmodifiableList(timeZoneIds);
+ }
+
+ /**
* Returns a String indicating the version of the time zone database currently
* in use. The format of the string is dependent on the underlying time zone
* database implementation, but will typically contain the year in which the database
diff --git a/core/java/android/view/DisplayListCanvas.java b/core/java/android/view/DisplayListCanvas.java
new file mode 100644
index 0000000..03d9955
--- /dev/null
+++ b/core/java/android/view/DisplayListCanvas.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.UnsupportedAppUsage;
+import android.graphics.BaseRecordingCanvas;
+import android.graphics.CanvasProperty;
+import android.graphics.Paint;
+import android.os.Build;
+
+/**
+ * This class exists temporarily to workaround broken apps
+ *
+ * b/119066174
+ *
+ * @hide
+ */
+public abstract class DisplayListCanvas extends BaseRecordingCanvas {
+
+ /** @hide */
+ protected DisplayListCanvas(long nativeCanvas) {
+ super(nativeCanvas);
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public abstract void drawRoundRect(CanvasProperty<Float> left, CanvasProperty<Float> top,
+ CanvasProperty<Float> right, CanvasProperty<Float> bottom, CanvasProperty<Float> rx,
+ CanvasProperty<Float> ry, CanvasProperty<Paint> paint);
+
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public abstract void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy,
+ CanvasProperty<Float> radius, CanvasProperty<Paint> paint);
+}
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 07a57fb..4b8b7f3 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -58,10 +58,33 @@
void dispatchGetNewSurface();
/**
- * Tell the window that it is either gaining or losing focus. Keep it up
- * to date on the current state showing navigational focus (touch mode) too.
+ * Tell the window that it is either gaining or losing focus.
+ *
+ * @param hasFocus {@code true} if window has focus, {@code false} otherwise.
+ * @param inTouchMode {@code true} if screen is in touch mode, {@code false} otherwise.
+ * @param reportToClient {@code true} when need to report to child view with
+ * {@link View#onWindowFocusChanged(boolean)}, {@code false} otherwise.
+ * <p>
+ * Note: In the previous design, there is only one window focus state tracked by
+ * WindowManagerService.
+ * For multi-display, the window focus state is tracked by each display independently.
+ * <p>
+ * It will introduce a problem if the window was already focused on one display and then
+ * switched to another display, since the window focus state on each display is independent,
+ * there is no global window focus state in WindowManagerService, so the window focus state of
+ * the former display remains unchanged.
+ * <p>
+ * When switched back to former display, some flows that rely on the global window focus state
+ * in view root will be missed due to the window focus state remaining unchanged.
+ * (i.e: Showing single IME window when switching between displays.)
+ * <p>
+ * To solve the problem, WindowManagerService tracks the top focused display change and then
+ * callbacks to the client via this method to make sure that the client side will request the
+ * IME on the top focused display, and then set {@param reportToClient} as {@code false} to
+ * ignore reporting to the application, since its focus remains unchanged on its display.
+ *
*/
- void windowFocusChanged(boolean hasFocus, boolean inTouchMode);
+ void windowFocusChanged(boolean hasFocus, boolean inTouchMode, boolean reportToClient);
void closeSystemDialogs(String reason);
diff --git a/services/core/java/com/android/server/input/InputApplicationHandle.java b/core/java/android/view/InputApplicationHandle.java
similarity index 97%
rename from services/core/java/com/android/server/input/InputApplicationHandle.java
rename to core/java/android/view/InputApplicationHandle.java
index 3cf7edc..dc1e505 100644
--- a/services/core/java/com/android/server/input/InputApplicationHandle.java
+++ b/core/java/android/view/InputApplicationHandle.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.input;
+package android.view;
/**
* Functions as a handle for an application that can receive input.
diff --git a/core/java/android/view/InputChannel.java b/core/java/android/view/InputChannel.java
index b2dd6ac..84c8e7a 100644
--- a/core/java/android/view/InputChannel.java
+++ b/core/java/android/view/InputChannel.java
@@ -18,6 +18,7 @@
import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
+import android.os.IBinder;
import android.os.Parcelable;
import android.util.Slog;
@@ -50,15 +51,17 @@
@SuppressWarnings("unused")
@UnsupportedAppUsage
private long mPtr; // used by native code
-
+
private static native InputChannel[] nativeOpenInputChannelPair(String name);
-
+
private native void nativeDispose(boolean finalized);
private native void nativeTransferTo(InputChannel other);
private native void nativeReadFromParcel(Parcel parcel);
private native void nativeWriteToParcel(Parcel parcel);
private native void nativeDup(InputChannel target);
-
+ private native IBinder nativeGetToken();
+ private native void nativeSetToken(IBinder token);
+
private native String nativeGetName();
/**
@@ -159,14 +162,22 @@
}
nativeWriteToParcel(out);
-
+
if ((flags & PARCELABLE_WRITE_RETURN_VALUE) != 0) {
dispose();
}
}
-
+
@Override
public String toString() {
return getName();
}
+
+ public IBinder getToken() {
+ return nativeGetToken();
+ }
+
+ public void setToken(IBinder token) {
+ nativeSetToken(token);
+ }
}
diff --git a/services/core/java/com/android/server/input/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
similarity index 93%
rename from services/core/java/com/android/server/input/InputWindowHandle.java
rename to core/java/android/view/InputWindowHandle.java
index bb29bf8..621ee89 100644
--- a/services/core/java/com/android/server/input/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.input;
+package android.view;
import android.graphics.Region;
import android.view.IWindow;
@@ -34,9 +34,6 @@
// The input application handle.
public final InputApplicationHandle inputApplicationHandle;
- // The window manager's window state.
- public final Object windowState;
-
// The client window.
public final IWindow clientWindow;
@@ -97,9 +94,8 @@
private native void nativeDispose();
public InputWindowHandle(InputApplicationHandle inputApplicationHandle,
- Object windowState, IWindow clientWindow, int displayId) {
+ IWindow clientWindow, int displayId) {
this.inputApplicationHandle = inputApplicationHandle;
- this.windowState = windowState;
this.clientWindow = clientWindow;
this.displayId = displayId;
}
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index fa30221..4a5ccdf 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -218,11 +218,6 @@
layoutRight = end - paddingEnd;
end = layoutLeft = layoutRight - child.getMeasuredWidth();
}
- if (child == mAudiblyAlertedIcon) {
- int paddingEnd = mContentEndMargin;
- layoutRight = end - paddingEnd;
- end = layoutLeft = layoutRight - child.getMeasuredWidth();
- }
if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
int ltrLeft = layoutLeft;
layoutLeft = getWidth() - layoutRight;
diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
index 9d31bd1..78ad0da 100644
--- a/core/java/android/view/RenderNodeAnimator.java
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -293,6 +293,12 @@
setTarget(canvas.mNode);
}
+ /** @hide */
+ @UnsupportedAppUsage
+ public void setTarget(DisplayListCanvas canvas) {
+ setTarget((RecordingCanvas) canvas);
+ }
+
private void setTarget(RenderNode node) {
checkMutable();
if (mTarget != null) {
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 3d16eb8..a7a5024 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -153,6 +153,9 @@
private static native Display.HdrCapabilities nativeGetHdrCapabilities(IBinder displayToken);
+ private static native void nativeSetInputWindowInfo(long transactionObj, long nativeObject,
+ InputWindowHandle handle);
+
private final CloseGuard mCloseGuard = CloseGuard.get();
private final String mName;
@@ -1459,6 +1462,12 @@
return this;
}
+ public Transaction setInputWindowInfo(SurfaceControl sc, InputWindowHandle handle) {
+ sc.checkNotReleased();
+ nativeSetInputWindowInfo(mNativeObject, sc.mNativeObject, handle);
+ return this;
+ }
+
@UnsupportedAppUsage
public Transaction setMatrix(SurfaceControl sc,
float dsdx, float dtdx, float dtdy, float dsdy) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ec16828..5f1336f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3406,18 +3406,33 @@
private static final int PFLAG4_NOTIFIED_CONTENT_CAPTURE_ADDED = 0x20;
private static final int PFLAG4_LAST_CONTENT_CAPTURE_NOTIFICATION_TYPE = 0x40;
+ /* End of masks for mPrivateFlags4 */
+
private static final int CONTENT_CAPTURE_NOTIFICATION_TYPE_APPEARED = 1;
private static final int CONTENT_CAPTURE_NOTIFICATION_TYPE_DISAPPEARED = 0;
- /** @hide */
@IntDef(flag = true, prefix = { "CONTENT_CAPTURE_NOTIFICATION_TYPE_" }, value = {
CONTENT_CAPTURE_NOTIFICATION_TYPE_APPEARED,
CONTENT_CAPTURE_NOTIFICATION_TYPE_DISAPPEARED
})
@Retention(RetentionPolicy.SOURCE)
- public @interface ContentCaptureNotificationType {}
+ private @interface ContentCaptureNotificationType {}
- /* End of masks for mPrivateFlags4 */
+ /** @hide */
+ protected static final int VIEW_STRUCTURE_FOR_ASSIST = 0;
+ /** @hide */
+ protected static final int VIEW_STRUCTURE_FOR_AUTOFILL = 1;
+ /** @hide */
+ protected static final int VIEW_STRUCTURE_FOR_CONTENT_CAPTURE = 2;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "VIEW_STRUCTURE_FOR" }, value = {
+ VIEW_STRUCTURE_FOR_ASSIST,
+ VIEW_STRUCTURE_FOR_AUTOFILL,
+ VIEW_STRUCTURE_FOR_CONTENT_CAPTURE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ViewStructureType {}
/**
* Always allow a user to over-scroll this view, provided it is a
@@ -4717,6 +4732,16 @@
private TouchDelegate mTouchDelegate = null;
/**
+ * While touch exploration is in use, set to true when hovering across boundaries and
+ * inside the touch area of the delegate at receiving {@link MotionEvent#ACTION_HOVER_ENTER}
+ * or {@link MotionEvent#ACTION_HOVER_MOVE}. False when leaving boundaries or receiving a
+ * {@link MotionEvent#ACTION_HOVER_EXIT}.
+ * Note that children of view group are excluded in the touch area.
+ * @see #dispatchTouchExplorationHoverEvent
+ */
+ private boolean mHoveringTouchDelegate = false;
+
+ /**
* Solid color to use as a background when creating the drawing cache. Enables
* the cache to use 16 bit bitmaps instead of 32 bit.
*/
@@ -8043,8 +8068,7 @@
* fills in all data that can be inferred from the view itself.
*/
public void onProvideStructure(ViewStructure structure) {
- onProvideStructureForAssistOrAutofillOrViewCapture(structure, /* forAutofill = */ false,
- /* forViewCapture= */ false, /* flags= */ 0);
+ onProvideStructure(structure, VIEW_STRUCTURE_FOR_ASSIST, /* flags= */ 0);
}
/**
@@ -8117,8 +8141,7 @@
* @see #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
*/
public void onProvideAutofillStructure(ViewStructure structure, @AutofillFlags int flags) {
- onProvideStructureForAssistOrAutofillOrViewCapture(structure, /* forAutofill = */ true,
- /* forViewCapture= */ false, flags);
+ onProvideStructure(structure, VIEW_STRUCTURE_FOR_AUTOFILL, flags);
}
/**
@@ -8150,13 +8173,13 @@
* virtual views are rendered.
*/
public boolean onProvideContentCaptureStructure(@NonNull ViewStructure structure, int flags) {
- onProvideStructureForAssistOrAutofillOrViewCapture(structure, /* forAutofill = */ false,
- /* forViewCapture= */ true, flags);
+ onProvideStructure(structure, VIEW_STRUCTURE_FOR_CONTENT_CAPTURE, flags);
return true;
}
- private void onProvideStructureForAssistOrAutofillOrViewCapture(ViewStructure structure,
- boolean forAutofill, boolean forViewCapture, @AutofillFlags int flags) {
+ /** @hide */
+ protected void onProvideStructure(@NonNull ViewStructure structure,
+ @ViewStructureType int viewFor, int flags) {
final int id = mID;
if (id != NO_ID && !isViewIdGenerated(id)) {
String pkg, type, entry;
@@ -8172,11 +8195,13 @@
} else {
structure.setId(id, null, null, null);
}
- if (forViewCapture) {
+ if (viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
+ //TODO(b/111276913): STOPSHIP - don't set it if not needed
structure.setDataIsSensitive(false);
}
- if (forAutofill || forViewCapture) {
+ if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
+ || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
final @AutofillType int autofillType = getAutofillType();
// Don't need to fill autofill info if view does not support it.
// For example, only TextViews that are editable support autofill
@@ -8190,7 +8215,8 @@
int ignoredParentLeft = 0;
int ignoredParentTop = 0;
- if (forAutofill && (flags & AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) == 0) {
+ if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
+ && (flags & AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) == 0) {
View parentGroup = null;
ViewParent viewParent = getParent();
@@ -8213,7 +8239,8 @@
structure.setDimens(ignoredParentLeft + mLeft, ignoredParentTop + mTop, mScrollX, mScrollY,
mRight - mLeft, mBottom - mTop);
- if (!forAutofill) {
+ if (viewFor == VIEW_STRUCTURE_FOR_ASSIST
+ || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
if (!hasIdentityMatrix()) {
structure.setTransformation(getMatrix());
}
@@ -8907,10 +8934,9 @@
}
/**
- * Helper used to notify the {@link IntelligenceManager}anager when the view is removed or
+ * Helper used to notify the {@link IntelligenceManager} when the view is removed or
* added, based on whether it's laid out and visible, and without knowing if the parent removed
- * it from the view
- * hierarchy.
+ * it from the view hierarchy.
*/
// TODO(b/111276913): make sure the current algorithm covers all cases. For example, it should
// probably be called every time notifyEnterOrExitForAutoFillIfNeeded() is called as well.
@@ -9084,7 +9110,7 @@
* {@link #onProvideVirtualStructure}.
*/
public void dispatchProvideStructure(ViewStructure structure) {
- dispatchProvideStructureForAssistOrAutofill(structure, false, 0);
+ dispatchProvideStructure(structure, VIEW_STRUCTURE_FOR_ASSIST, /* flags= */ 0);
}
/**
@@ -9126,12 +9152,12 @@
*/
public void dispatchProvideAutofillStructure(@NonNull ViewStructure structure,
@AutofillFlags int flags) {
- dispatchProvideStructureForAssistOrAutofill(structure, true, flags);
+ dispatchProvideStructure(structure, VIEW_STRUCTURE_FOR_AUTOFILL, flags);
}
- private void dispatchProvideStructureForAssistOrAutofill(ViewStructure structure,
- boolean forAutofill, @AutofillFlags int flags) {
- if (forAutofill) {
+ private void dispatchProvideStructure(@NonNull ViewStructure structure,
+ @ViewStructureType int viewFor, @AutofillFlags int flags) {
+ if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
structure.setAutofillId(getAutofillId());
onProvideAutofillStructure(structure, flags);
onProvideAutofillVirtualStructure(structure, flags);
@@ -13949,6 +13975,96 @@
}
/**
+ * Dispatching hover events to {@link TouchDelegate} to improve accessibility.
+ * <p>
+ * This method is dispatching hover events to the delegate target to support explore by touch.
+ * Similar to {@link ViewGroup#dispatchTouchEvent}, this method send proper hover events to
+ * the delegate target according to the pointer and the touch area of the delegate while touch
+ * exploration enabled.
+ * </p>
+ *
+ * @param event The motion event dispatch to the delegate target.
+ * @return True if the event was handled, false otherwise.
+ *
+ * @see #onHoverEvent
+ */
+ private boolean dispatchTouchExplorationHoverEvent(MotionEvent event) {
+ final AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
+ if (!manager.isEnabled() || !manager.isTouchExplorationEnabled()) {
+ return false;
+ }
+
+ final boolean oldHoveringTouchDelegate = mHoveringTouchDelegate;
+ final int action = event.getActionMasked();
+ boolean pointInDelegateRegion = false;
+ boolean handled = false;
+
+ final AccessibilityNodeInfo.TouchDelegateInfo info = mTouchDelegate.getTouchDelegateInfo();
+ for (int i = 0; i < info.getRegionCount(); i++) {
+ Region r = info.getRegionAt(i);
+ if (r.contains((int) event.getX(), (int) event.getY())) {
+ pointInDelegateRegion = true;
+ }
+ }
+
+ // Explore by touch should dispatch events to children under the pointer first if any
+ // before dispatching to TouchDelegate. For non-hoverable views that do not consume
+ // hover events but receive accessibility focus, it should also not delegate to these
+ // views when hovered.
+ if (!oldHoveringTouchDelegate) {
+ if ((action == MotionEvent.ACTION_HOVER_ENTER
+ || action == MotionEvent.ACTION_HOVER_MOVE)
+ && !pointInHoveredChild(event)
+ && pointInDelegateRegion) {
+ mHoveringTouchDelegate = true;
+ }
+ } else {
+ if (action == MotionEvent.ACTION_HOVER_EXIT
+ || (action == MotionEvent.ACTION_HOVER_MOVE
+ && (pointInHoveredChild(event) || !pointInDelegateRegion))) {
+ mHoveringTouchDelegate = false;
+ }
+ }
+ switch (action) {
+ case MotionEvent.ACTION_HOVER_MOVE:
+ if (oldHoveringTouchDelegate && mHoveringTouchDelegate) {
+ // Inside bounds, dispatch as is.
+ handled = mTouchDelegate.onTouchExplorationHoverEvent(event);
+ } else if (!oldHoveringTouchDelegate && mHoveringTouchDelegate) {
+ // Moving inbound, synthesize hover enter.
+ MotionEvent eventNoHistory = (event.getHistorySize() == 0)
+ ? event : MotionEvent.obtainNoHistory(event);
+ eventNoHistory.setAction(MotionEvent.ACTION_HOVER_ENTER);
+ handled = mTouchDelegate.onTouchExplorationHoverEvent(eventNoHistory);
+ eventNoHistory.setAction(action);
+ handled |= mTouchDelegate.onTouchExplorationHoverEvent(eventNoHistory);
+ } else if (oldHoveringTouchDelegate && !mHoveringTouchDelegate) {
+ // Moving outbound, synthesize hover exit.
+ final boolean hoverExitPending = event.isHoverExitPending();
+ event.setHoverExitPending(true);
+ mTouchDelegate.onTouchExplorationHoverEvent(event);
+ MotionEvent eventNoHistory = (event.getHistorySize() == 0)
+ ? event : MotionEvent.obtainNoHistory(event);
+ eventNoHistory.setHoverExitPending(hoverExitPending);
+ eventNoHistory.setAction(MotionEvent.ACTION_HOVER_EXIT);
+ mTouchDelegate.onTouchExplorationHoverEvent(eventNoHistory);
+ } // else: outside bounds, do nothing.
+ break;
+ case MotionEvent.ACTION_HOVER_ENTER:
+ if (!oldHoveringTouchDelegate && mHoveringTouchDelegate) {
+ handled = mTouchDelegate.onTouchExplorationHoverEvent(event);
+ }
+ break;
+ case MotionEvent.ACTION_HOVER_EXIT:
+ if (oldHoveringTouchDelegate) {
+ mTouchDelegate.onTouchExplorationHoverEvent(event);
+ }
+ break;
+ }
+ return handled;
+ }
+
+ /**
* Implement this method to handle hover events.
* <p>
* This method is called whenever a pointer is hovering into, over, or out of the
@@ -13985,15 +14101,8 @@
* @see #onHoverChanged
*/
public boolean onHoverEvent(MotionEvent event) {
- // Explore by touch should dispatch events to children under pointer first if any before
- // dispatching to TouchDelegate. For children non-hoverable that will not consume events,
- // it should also not delegate when they got the pointer hovered.
- if (mTouchDelegate != null && !pointInHoveredChild(event)) {
- final AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
- if (manager.isEnabled() && manager.isTouchExplorationEnabled()
- && mTouchDelegate.onTouchExplorationHoverEvent(event)) {
- return true;
- }
+ if (mTouchDelegate != null && dispatchTouchExplorationHoverEvent(event)) {
+ return true;
}
// The root view may receive hover (or touch) events that are outside the bounds of
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index a23d68b..484c6f3 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1449,6 +1449,9 @@
}
if (mStopped) {
+ if (mSurfaceHolder != null) {
+ notifySurfaceDestroyed();
+ }
destroySurface();
}
}
@@ -2393,13 +2396,7 @@
}
mIsCreating = false;
} else if (hadSurface) {
- mSurfaceHolder.ungetCallbacks();
- SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
- if (callbacks != null) {
- for (SurfaceHolder.Callback c : callbacks) {
- c.surfaceDestroyed(mSurfaceHolder);
- }
- }
+ notifySurfaceDestroyed();
mSurfaceHolder.mSurfaceLock.lock();
try {
mSurfaceHolder.mSurface = new Surface();
@@ -2667,6 +2664,16 @@
mIsInTraversal = false;
}
+ private void notifySurfaceDestroyed() {
+ mSurfaceHolder.ungetCallbacks();
+ SurfaceHolder.Callback[] callbacks = mSurfaceHolder.getCallbacks();
+ if (callbacks != null) {
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceDestroyed(mSurfaceHolder);
+ }
+ }
+ }
+
private void maybeHandleWindowMove(Rect frame) {
// TODO: Well, we are checking whether the frame has changed similarly
@@ -2695,7 +2702,7 @@
}
}
- private void handleWindowFocusChanged() {
+ private void handleWindowFocusChanged(boolean reportToClient) {
final boolean hasWindowFocus;
final boolean inTouchMode;
synchronized (this) {
@@ -2730,8 +2737,9 @@
} catch (RemoteException ex) {
}
// Retry in a bit.
- mHandler.sendMessageDelayed(mHandler.obtainMessage(
- MSG_WINDOW_FOCUS_CHANGED), 500);
+ final Message msg = mHandler.obtainMessage(MSG_WINDOW_FOCUS_CHANGED);
+ msg.arg1 = reportToClient ? 1 : 0;
+ mHandler.sendMessageDelayed(msg, 500);
return;
}
}
@@ -2748,9 +2756,15 @@
}
if (mView != null) {
mAttachInfo.mKeyDispatchState.reset();
- mView.dispatchWindowFocusChanged(hasWindowFocus);
- mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
-
+ // We dispatch onWindowFocusChanged to child view only when window is gaining /
+ // losing focus.
+ // If the focus is updated from top display change but window focus on the display
+ // remains unchanged, will not callback onWindowFocusChanged again since it may
+ // be redundant & can affect the state when it callbacks.
+ if (reportToClient) {
+ mView.dispatchWindowFocusChanged(hasWindowFocus);
+ mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
+ }
if (mAttachInfo.mTooltipHost != null) {
mAttachInfo.mTooltipHost.hideTooltip();
}
@@ -4340,7 +4354,7 @@
}
break;
case MSG_WINDOW_FOCUS_CHANGED: {
- handleWindowFocusChanged();
+ handleWindowFocusChanged(msg.arg1 != 0 /* reportToClient */);
} break;
case MSG_DIE:
doDie();
@@ -7263,7 +7277,7 @@
}
if (stage != null) {
- handleWindowFocusChanged();
+ handleWindowFocusChanged(true /* reportToClient */);
stage.deliver(q);
} else {
finishInputEvent(q);
@@ -7580,6 +7594,11 @@
}
public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
+ windowFocusChanged(hasFocus, inTouchMode, true /* reportToClient */);
+ }
+
+ public void windowFocusChanged(boolean hasFocus, boolean inTouchMode,
+ boolean reportToClient) {
synchronized (this) {
mWindowFocusChanged = true;
mUpcomingWindowFocus = hasFocus;
@@ -7587,6 +7606,7 @@
}
Message msg = Message.obtain();
msg.what = MSG_WINDOW_FOCUS_CHANGED;
+ msg.arg1 = reportToClient ? 1 : 0;
mHandler.sendMessage(msg);
}
@@ -8131,10 +8151,11 @@
}
@Override
- public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
+ public void windowFocusChanged(boolean hasFocus, boolean inTouchMode,
+ boolean reportToClient) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
- viewAncestor.windowFocusChanged(hasFocus, inTouchMode);
+ viewAncestor.windowFocusChanged(hasFocus, inTouchMode, reportToClient);
}
}
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 4a7e783..a8debbd 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -77,11 +77,17 @@
CONSUMED = new WindowInsets((Insets) null, null, null, false, false, null);
}
- /** @hide */
+ /**
+ * Construct a new WindowInsets from individual insets.
+ *
+ * A {@code null} inset indicates that the respective inset is consumed.
+ *
+ * @hide
+ */
public WindowInsets(Rect systemWindowInsets, Rect windowDecorInsets, Rect stableInsets,
boolean isRound, boolean alwaysConsumeNavBar, DisplayCutout displayCutout) {
- this(Insets.of(systemWindowInsets), Insets.of(windowDecorInsets), Insets.of(stableInsets),
- isRound, alwaysConsumeNavBar, displayCutout);
+ this(insetsOrNull(systemWindowInsets), insetsOrNull(windowDecorInsets),
+ insetsOrNull(stableInsets), isRound, alwaysConsumeNavBar, displayCutout);
}
private WindowInsets(Insets systemWindowInsets, Insets windowDecorInsets,
@@ -673,6 +679,10 @@
return Insets.of(newLeft, newTop, newRight, newBottom);
}
+ private static Insets insetsOrNull(Rect insets) {
+ return insets != null ? Insets.of(insets) : null;
+ }
+
/**
* @return whether system window insets have been consumed.
*/
diff --git a/core/java/android/view/intelligence/IntelligenceManager.java b/core/java/android/view/intelligence/IntelligenceManager.java
index c02fb32..dfa52d9 100644
--- a/core/java/android/view/intelligence/IntelligenceManager.java
+++ b/core/java/android/view/intelligence/IntelligenceManager.java
@@ -45,6 +45,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.List;
import java.util.Set;
/**
@@ -86,6 +87,13 @@
*/
public static final int STATE_ACTIVE = 2;
+ /**
+ * Session is disabled.
+ *
+ * @hide
+ */
+ public static final int STATE_DISABLED = 3;
+
private static final String BG_THREAD_NAME = "intel_svc_streamer_thread";
/**
@@ -166,13 +174,7 @@
public void send(int resultCode, Bundle resultData)
throws RemoteException {
synchronized (mLock) {
- if (resultCode > 0) {
- mState = STATE_ACTIVE;
- } else {
- // TODO(b/111276913): handle other cases like disabled by
- // service
- resetStateLocked();
- }
+ mState = resultCode;
if (VERBOSE) {
Log.v(TAG, "onActivityStarted() result: code=" + resultCode
+ ", id=" + mId
@@ -195,6 +197,7 @@
private void handleSendEvent(@NonNull ContentCaptureEvent event) {
+ //TODO(b/111276913): make a copy and don't use lock
synchronized (mLock) {
mEvents.add(event);
final int numberEvents = mEvents.size();
@@ -203,9 +206,13 @@
// Typically happens on system apps that are started before the system service
// is ready (like com.android.settings/.FallbackHome)
//TODO(b/111276913): try to ignore session while system is not ready / boot
- // not complete instead.
- Log.w(TAG, "Closing session for " + getActivityDebugNameLocked()
- + " after " + numberEvents + " delayed events");
+ // not complete instead. Similarly, the manager service should return right away
+ // when the user does not have a service set
+ if (VERBOSE) {
+ Log.v(TAG, "Closing session for " + getActivityDebugNameLocked()
+ + " after " + numberEvents + " delayed events and state "
+ + getStateAsString(mState));
+ }
// TODO(b/111276913): blacklist activity / use special flag to indicate that
// when it's launched again
resetStateLocked();
@@ -380,16 +387,16 @@
//TODO(b/111276913): properly implement by checking if it was explicitly disabled by
// service, or if service is not set
// (and probably renamign to isEnabledLocked()
- return mService != null;
+ return mService != null && mState != STATE_DISABLED;
}
/**
- * Called by apps to disable content capture.
+ * Called by apps to explicitly enabled or disable content capture.
*
* <p><b>Note: </b> this call is not persisted accross reboots, so apps should typically call
* it on {@link android.app.Activity#onCreate(android.os.Bundle, android.os.PersistableBundle)}.
*/
- public void disableContentCapture() {
+ public void setContentCaptureEnabled(boolean enabled) {
//TODO(b/111276913): implement
}
@@ -414,6 +421,35 @@
/**
* Called by the the service {@link android.service.intelligence.IntelligenceService}
+ * to explicitly limit content capture to the given packages and activities.
+ *
+ * <p>When the whitelist is set, it overrides the values passed to
+ * {@link #setActivityContentCaptureEnabled(ComponentName, boolean)}
+ * and {@link #setPackageContentCaptureEnabled(String, boolean)}.
+ *
+ * <p>To reset the whitelist, call it passing {@code null} to both arguments.
+ *
+ * <p>Useful when the service wants to restrict content capture to a category of apps, like
+ * chat apps. For example, if the service wants to support view captures on all activities of
+ * app {@code ChatApp1} and just activities {@code act1} and {@code act2} of {@code ChatApp2},
+ * it would call: {@code setContentCaptureWhitelist(Arrays.asList("ChatApp1"),
+ * Arrays.asList(new ComponentName("ChatApp2", "act1"),
+ * new ComponentName("ChatApp2", "act2")));}
+ *
+ * @throws UnsupportedOperationException if not called by the UID that owns the
+ * {@link android.service.intelligence.IntelligenceService} associated with the
+ * current user.
+ *
+ * @hide
+ */
+ @SystemApi
+ public void setContentCaptureWhitelist(@Nullable List<String> packages,
+ @Nullable List<ComponentName> activities) {
+ //TODO(b/111276913): implement
+ }
+
+ /**
+ * Called by the the service {@link android.service.intelligence.IntelligenceService}
* to define whether content capture should be enabled for activities of the app with such
* {@code packageName}.
*
@@ -509,6 +545,8 @@
return "WAITING_FOR_SERVER";
case STATE_ACTIVE:
return "ACTIVE";
+ case STATE_DISABLED:
+ return "DISABLED";
default:
return "INVALID:" + state;
}
diff --git a/core/java/android/view/textclassifier/ModelFileManager.java b/core/java/android/view/textclassifier/ModelFileManager.java
index 896b516..8558a46 100644
--- a/core/java/android/view/textclassifier/ModelFileManager.java
+++ b/core/java/android/view/textclassifier/ModelFileManager.java
@@ -251,6 +251,9 @@
if (!mLanguageIndependent && model.mLanguageIndependent) {
return true;
}
+ if (mLanguageIndependent && !model.mLanguageIndependent) {
+ return false;
+ }
// A higher-version model is preferred.
if (mVersion > model.getVersion()) {
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index f343a52..1093719 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -2695,6 +2695,11 @@
}
@Override
+ public boolean onProvideContentCaptureStructure(ViewStructure structure, int flags) {
+ return mProvider.getViewDelegate().onProvideContentCaptureStructure(structure, flags);
+ }
+
+ @Override
public void autofill(SparseArray<AutofillValue>values) {
mProvider.getViewDelegate().autofill(values);
}
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 00e782b..ceada07 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -341,6 +341,12 @@
return true; // true is the default value returned by View.isVisibleToUserForAutofill()
}
+ default boolean onProvideContentCaptureStructure(
+ @SuppressWarnings("unused") android.view.ViewStructure structure,
+ @SuppressWarnings("unused") int flags) {
+ return false; // WebView provides virtual views and is responsible to notify manager
+ }
+
public AccessibilityNodeProvider getAccessibilityNodeProvider();
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info);
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index f3fe16e..ddff858 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -1309,13 +1309,23 @@
@Override
public void onProvideAutofillStructure(ViewStructure structure, int flags) {
super.onProvideAutofillStructure(structure, flags);
+ }
- final Adapter adapter = getAdapter();
- if (adapter == null) return;
+ /** @hide */
+ @Override
+ protected void onProvideStructure(@NonNull ViewStructure structure,
+ @ViewStructureType int viewFor, int flags) {
+ super.onProvideStructure(structure, viewFor, flags);
- final CharSequence[] options = adapter.getAutofillOptions();
- if (options != null) {
- structure.setAutofillOptions(options);
+ if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
+ || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
+ final Adapter adapter = getAdapter();
+ if (adapter == null) return;
+
+ final CharSequence[] options = adapter.getAutofillOptions();
+ if (options != null) {
+ structure.setAutofillOptions(options);
+ }
}
}
}
diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java
index b754d84..eb35587 100644
--- a/core/java/android/widget/AppSecurityPermissions.java
+++ b/core/java/android/widget/AppSecurityPermissions.java
@@ -16,302 +16,22 @@
*/
package android.widget;
-import android.annotation.UnsupportedAppUsage;
-import android.app.AlertDialog;
import android.content.Context;
-import android.content.DialogInterface;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageItemInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PermissionGroupInfo;
-import android.content.pm.PermissionInfo;
import android.graphics.drawable.Drawable;
-import android.os.Parcel;
-import android.os.UserHandle;
-import android.text.SpannableStringBuilder;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
import com.android.internal.R;
-import java.text.Collator;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
/**
- * This class contains the SecurityPermissions view implementation.
- * Initially the package's advanced or dangerous security permissions
- * are displayed under categorized
- * groups. Clicking on the additional permissions presents
- * extended information consisting of all groups and permissions.
- * To use this view define a LinearLayout or any ViewGroup and add this
- * view by instantiating AppSecurityPermissions and invoking getPermissionsView.
+ * Allows the device admin to show certain dialogs. Should be integrated into settings.
*
+ * @deprecated
* {@hide}
*/
+@Deprecated
public class AppSecurityPermissions {
- public static final int WHICH_NEW = 1<<2;
- public static final int WHICH_ALL = 0xffff;
-
- private final static String TAG = "AppSecurityPermissions";
- private final static boolean localLOGV = false;
- private final Context mContext;
- private final LayoutInflater mInflater;
- private final PackageManager mPm;
- private final Map<String, MyPermissionGroupInfo> mPermGroups
- = new HashMap<String, MyPermissionGroupInfo>();
- private final List<MyPermissionGroupInfo> mPermGroupsList
- = new ArrayList<MyPermissionGroupInfo>();
- private final PermissionGroupInfoComparator mPermGroupComparator =
- new PermissionGroupInfoComparator();
- private final PermissionInfoComparator mPermComparator = new PermissionInfoComparator();
- private final List<MyPermissionInfo> mPermsList = new ArrayList<MyPermissionInfo>();
- private final CharSequence mNewPermPrefix;
- private String mPackageName;
-
- /** @hide */
- static class MyPermissionGroupInfo extends PermissionGroupInfo {
- CharSequence mLabel;
-
- final ArrayList<MyPermissionInfo> mNewPermissions = new ArrayList<MyPermissionInfo>();
- final ArrayList<MyPermissionInfo> mAllPermissions = new ArrayList<MyPermissionInfo>();
-
- MyPermissionGroupInfo(PermissionInfo perm) {
- name = perm.packageName;
- packageName = perm.packageName;
- }
-
- MyPermissionGroupInfo(PermissionGroupInfo info) {
- super(info);
- }
-
- public Drawable loadGroupIcon(Context context, PackageManager pm) {
- if (icon != 0) {
- return loadUnbadgedIcon(pm);
- } else {
- return context.getDrawable(R.drawable.ic_perm_device_info);
- }
- }
- }
-
- /** @hide */
- private static class MyPermissionInfo extends PermissionInfo {
- CharSequence mLabel;
-
- /**
- * PackageInfo.requestedPermissionsFlags for the new package being installed.
- */
- int mNewReqFlags;
-
- /**
- * PackageInfo.requestedPermissionsFlags for the currently installed
- * package, if it is installed.
- */
- int mExistingReqFlags;
-
- /**
- * True if this should be considered a new permission.
- */
- boolean mNew;
-
- MyPermissionInfo(PermissionInfo info) {
- super(info);
- }
- }
-
- /** @hide */
- public static class PermissionItemView extends LinearLayout implements View.OnClickListener {
- MyPermissionGroupInfo mGroup;
- MyPermissionInfo mPerm;
- AlertDialog mDialog;
- private boolean mShowRevokeUI = false;
- private String mPackageName;
-
- public PermissionItemView(Context context, AttributeSet attrs) {
- super(context, attrs);
- setClickable(true);
- }
-
- public void setPermission(MyPermissionGroupInfo grp, MyPermissionInfo perm,
- boolean first, CharSequence newPermPrefix, String packageName,
- boolean showRevokeUI) {
- mGroup = grp;
- mPerm = perm;
- mShowRevokeUI = showRevokeUI;
- mPackageName = packageName;
-
- ImageView permGrpIcon = findViewById(R.id.perm_icon);
- TextView permNameView = findViewById(R.id.perm_name);
-
- PackageManager pm = getContext().getPackageManager();
- Drawable icon = null;
- if (first) {
- icon = grp.loadGroupIcon(getContext(), pm);
- }
- CharSequence label = perm.mLabel;
- if (perm.mNew && newPermPrefix != null) {
- // If this is a new permission, format it appropriately.
- SpannableStringBuilder builder = new SpannableStringBuilder();
- Parcel parcel = Parcel.obtain();
- TextUtils.writeToParcel(newPermPrefix, parcel, 0);
- parcel.setDataPosition(0);
- CharSequence newStr = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
- parcel.recycle();
- builder.append(newStr);
- builder.append(label);
- label = builder;
- }
-
- permGrpIcon.setImageDrawable(icon);
- permNameView.setText(label);
- setOnClickListener(this);
- if (localLOGV) Log.i(TAG, "Made perm item " + perm.name
- + ": " + label + " in group " + grp.name);
- }
-
- @Override
- public void onClick(View v) {
- if (mGroup != null && mPerm != null) {
- if (mDialog != null) {
- mDialog.dismiss();
- }
- PackageManager pm = getContext().getPackageManager();
- AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
- builder.setTitle(mGroup.mLabel);
- if (mPerm.descriptionRes != 0) {
- builder.setMessage(mPerm.loadDescription(pm));
- } else {
- CharSequence appName;
- try {
- ApplicationInfo app = pm.getApplicationInfo(mPerm.packageName, 0);
- appName = app.loadLabel(pm);
- } catch (NameNotFoundException e) {
- appName = mPerm.packageName;
- }
- StringBuilder sbuilder = new StringBuilder(128);
- sbuilder.append(getContext().getString(
- R.string.perms_description_app, appName));
- sbuilder.append("\n\n");
- sbuilder.append(mPerm.name);
- builder.setMessage(sbuilder.toString());
- }
- builder.setCancelable(true);
- builder.setIcon(mGroup.loadGroupIcon(getContext(), pm));
- addRevokeUIIfNecessary(builder);
- mDialog = builder.show();
- mDialog.setCanceledOnTouchOutside(true);
- }
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- if (mDialog != null) {
- mDialog.dismiss();
- }
- }
-
- private void addRevokeUIIfNecessary(AlertDialog.Builder builder) {
- if (!mShowRevokeUI) {
- return;
- }
-
- final boolean isRequired =
- ((mPerm.mExistingReqFlags & PackageInfo.REQUESTED_PERMISSION_REQUIRED) != 0);
-
- if (isRequired) {
- return;
- }
-
- DialogInterface.OnClickListener ocl = new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- PackageManager pm = getContext().getPackageManager();
- pm.revokeRuntimePermission(mPackageName, mPerm.name,
- new UserHandle(mContext.getUserId()));
- PermissionItemView.this.setVisibility(View.GONE);
- }
- };
- builder.setNegativeButton(R.string.revoke, ocl);
- builder.setPositiveButton(R.string.ok, null);
- }
- }
-
- private AppSecurityPermissions(Context context) {
- mContext = context;
- mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mPm = mContext.getPackageManager();
- // Pick up from framework resources instead.
- mNewPermPrefix = mContext.getText(R.string.perms_new_perm_prefix);
- }
-
- @UnsupportedAppUsage
- public AppSecurityPermissions(Context context, String packageName) {
- this(context);
- mPackageName = packageName;
- Set<MyPermissionInfo> permSet = new HashSet<MyPermissionInfo>();
- PackageInfo pkgInfo;
- try {
- pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
- } catch (NameNotFoundException e) {
- Log.w(TAG, "Couldn't retrieve permissions for package:"+packageName);
- return;
- }
- // Extract all user permissions
- if((pkgInfo.applicationInfo != null) && (pkgInfo.applicationInfo.uid != -1)) {
- getAllUsedPermissions(pkgInfo.applicationInfo.uid, permSet);
- }
- mPermsList.addAll(permSet);
- setPermissions(mPermsList);
- }
-
- public AppSecurityPermissions(Context context, PackageInfo info) {
- this(context);
- Set<MyPermissionInfo> permSet = new HashSet<MyPermissionInfo>();
- if(info == null) {
- return;
- }
- mPackageName = info.packageName;
-
- // Convert to a PackageInfo
- PackageInfo installedPkgInfo = null;
- // Get requested permissions
- if (info.requestedPermissions != null) {
- try {
- installedPkgInfo = mPm.getPackageInfo(info.packageName,
- PackageManager.GET_PERMISSIONS);
- } catch (NameNotFoundException e) {
- }
- extractPerms(info, permSet, installedPkgInfo);
- }
- // Get permissions related to shared user if any
- if (info.sharedUserId != null) {
- int sharedUid;
- try {
- sharedUid = mPm.getUidForSharedUser(info.sharedUserId);
- getAllUsedPermissions(sharedUid, permSet);
- } catch (NameNotFoundException e) {
- Log.w(TAG, "Couldn't retrieve shared user id for: " + info.packageName);
- }
- }
- // Retrieve list of permissions
- mPermsList.addAll(permSet);
- setPermissions(mPermsList);
- }
-
/**
* Utility to retrieve a view displaying a single permission. This provides
* the old UI layout for permissions; it is only here for the device admin
@@ -327,197 +47,6 @@
description, dangerous, icon);
}
- private void getAllUsedPermissions(int sharedUid, Set<MyPermissionInfo> permSet) {
- String sharedPkgList[] = mPm.getPackagesForUid(sharedUid);
- if(sharedPkgList == null || (sharedPkgList.length == 0)) {
- return;
- }
- for(String sharedPkg : sharedPkgList) {
- getPermissionsForPackage(sharedPkg, permSet);
- }
- }
-
- private void getPermissionsForPackage(String packageName, Set<MyPermissionInfo> permSet) {
- try {
- PackageInfo pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
- extractPerms(pkgInfo, permSet, pkgInfo);
- } catch (NameNotFoundException e) {
- Log.w(TAG, "Couldn't retrieve permissions for package: " + packageName);
- }
- }
-
- private void extractPerms(PackageInfo info, Set<MyPermissionInfo> permSet,
- PackageInfo installedPkgInfo) {
- String[] strList = info.requestedPermissions;
- int[] flagsList = info.requestedPermissionsFlags;
- if ((strList == null) || (strList.length == 0)) {
- return;
- }
- for (int i=0; i<strList.length; i++) {
- String permName = strList[i];
- try {
- PermissionInfo tmpPermInfo = mPm.getPermissionInfo(permName, 0);
- if (tmpPermInfo == null) {
- continue;
- }
- int existingIndex = -1;
- if (installedPkgInfo != null
- && installedPkgInfo.requestedPermissions != null) {
- for (int j=0; j<installedPkgInfo.requestedPermissions.length; j++) {
- if (permName.equals(installedPkgInfo.requestedPermissions[j])) {
- existingIndex = j;
- break;
- }
- }
- }
- final int existingFlags = existingIndex >= 0 ?
- installedPkgInfo.requestedPermissionsFlags[existingIndex] : 0;
- if (!isDisplayablePermission(tmpPermInfo, flagsList[i], existingFlags)) {
- // This is not a permission that is interesting for the user
- // to see, so skip it.
- continue;
- }
- final String origGroupName = tmpPermInfo.group;
- String groupName = origGroupName;
- if (groupName == null) {
- groupName = tmpPermInfo.packageName;
- tmpPermInfo.group = groupName;
- }
- MyPermissionGroupInfo group = mPermGroups.get(groupName);
- if (group == null) {
- PermissionGroupInfo grp = null;
- if (origGroupName != null) {
- grp = mPm.getPermissionGroupInfo(origGroupName, 0);
- }
- if (grp != null) {
- group = new MyPermissionGroupInfo(grp);
- } else {
- // We could be here either because the permission
- // didn't originally specify a group or the group it
- // gave couldn't be found. In either case, we consider
- // its group to be the permission's package name.
- tmpPermInfo.group = tmpPermInfo.packageName;
- group = mPermGroups.get(tmpPermInfo.group);
- if (group == null) {
- group = new MyPermissionGroupInfo(tmpPermInfo);
- }
- group = new MyPermissionGroupInfo(tmpPermInfo);
- }
- mPermGroups.put(tmpPermInfo.group, group);
- }
- final boolean newPerm = installedPkgInfo != null
- && (existingFlags&PackageInfo.REQUESTED_PERMISSION_GRANTED) == 0;
- MyPermissionInfo myPerm = new MyPermissionInfo(tmpPermInfo);
- myPerm.mNewReqFlags = flagsList[i];
- myPerm.mExistingReqFlags = existingFlags;
- // This is a new permission if the app is already installed and
- // doesn't currently hold this permission.
- myPerm.mNew = newPerm;
- permSet.add(myPerm);
- } catch (NameNotFoundException e) {
- Log.i(TAG, "Ignoring unknown permission:"+permName);
- }
- }
- }
-
- @UnsupportedAppUsage
- public int getPermissionCount() {
- return getPermissionCount(WHICH_ALL);
- }
-
- private List<MyPermissionInfo> getPermissionList(MyPermissionGroupInfo grp, int which) {
- if (which == WHICH_NEW) {
- return grp.mNewPermissions;
- } else {
- return grp.mAllPermissions;
- }
- }
-
- public int getPermissionCount(int which) {
- int N = 0;
- for (int i=0; i<mPermGroupsList.size(); i++) {
- N += getPermissionList(mPermGroupsList.get(i), which).size();
- }
- return N;
- }
-
- @UnsupportedAppUsage
- public View getPermissionsView() {
- return getPermissionsView(WHICH_ALL, false);
- }
-
- public View getPermissionsViewWithRevokeButtons() {
- return getPermissionsView(WHICH_ALL, true);
- }
-
- public View getPermissionsView(int which) {
- return getPermissionsView(which, false);
- }
-
- private View getPermissionsView(int which, boolean showRevokeUI) {
- LinearLayout permsView = (LinearLayout) mInflater.inflate(R.layout.app_perms_summary, null);
- LinearLayout displayList = permsView.findViewById(R.id.perms_list);
- View noPermsView = permsView.findViewById(R.id.no_permissions);
-
- displayPermissions(mPermGroupsList, displayList, which, showRevokeUI);
- if (displayList.getChildCount() <= 0) {
- noPermsView.setVisibility(View.VISIBLE);
- }
-
- return permsView;
- }
-
- /**
- * Utility method that displays permissions from a map containing group name and
- * list of permission descriptions.
- */
- private void displayPermissions(List<MyPermissionGroupInfo> groups,
- LinearLayout permListView, int which, boolean showRevokeUI) {
- permListView.removeAllViews();
-
- int spacing = (int)(8*mContext.getResources().getDisplayMetrics().density);
-
- for (int i=0; i<groups.size(); i++) {
- MyPermissionGroupInfo grp = groups.get(i);
- final List<MyPermissionInfo> perms = getPermissionList(grp, which);
- for (int j=0; j<perms.size(); j++) {
- MyPermissionInfo perm = perms.get(j);
- View view = getPermissionItemView(grp, perm, j == 0,
- which != WHICH_NEW ? mNewPermPrefix : null, showRevokeUI);
- LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
- if (j == 0) {
- lp.topMargin = spacing;
- }
- if (j == grp.mAllPermissions.size()-1) {
- lp.bottomMargin = spacing;
- }
- if (permListView.getChildCount() == 0) {
- lp.topMargin *= 2;
- }
- permListView.addView(view, lp);
- }
- }
- }
-
- private PermissionItemView getPermissionItemView(MyPermissionGroupInfo grp,
- MyPermissionInfo perm, boolean first, CharSequence newPermPrefix, boolean showRevokeUI) {
- return getPermissionItemView(mContext, mInflater, grp, perm, first, newPermPrefix,
- mPackageName, showRevokeUI);
- }
-
- private static PermissionItemView getPermissionItemView(Context context, LayoutInflater inflater,
- MyPermissionGroupInfo grp, MyPermissionInfo perm, boolean first,
- CharSequence newPermPrefix, String packageName, boolean showRevokeUI) {
- PermissionItemView permView = (PermissionItemView)inflater.inflate(
- (perm.flags & PermissionInfo.FLAG_COSTS_MONEY) != 0
- ? R.layout.app_permission_item_money : R.layout.app_permission_item,
- null);
- permView.setPermission(grp, perm, first, newPermPrefix, packageName, showRevokeUI);
- return permView;
- }
-
private static View getPermissionItemViewOld(Context context, LayoutInflater inflater,
CharSequence grpName, CharSequence permList, boolean dangerous, Drawable icon) {
View permView = inflater.inflate(R.layout.app_permission_item_old, null);
@@ -536,116 +65,4 @@
}
return permView;
}
-
- private boolean isDisplayablePermission(PermissionInfo pInfo, int newReqFlags,
- int existingReqFlags) {
- final int base = pInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
- final boolean isNormal = (base == PermissionInfo.PROTECTION_NORMAL);
-
- // We do not show normal permissions in the UI.
- if (isNormal) {
- return false;
- }
-
- final boolean isDangerous = (base == PermissionInfo.PROTECTION_DANGEROUS)
- || ((pInfo.protectionLevel&PermissionInfo.PROTECTION_FLAG_PRE23) != 0);
- final boolean isRequired =
- ((newReqFlags&PackageInfo.REQUESTED_PERMISSION_REQUIRED) != 0);
- final boolean isDevelopment =
- ((pInfo.protectionLevel&PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0);
- final boolean wasGranted =
- ((existingReqFlags&PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0);
- final boolean isGranted =
- ((newReqFlags&PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0);
-
- // Dangerous and normal permissions are always shown to the user if the permission
- // is required, or it was previously granted
- if (isDangerous && (isRequired || wasGranted || isGranted)) {
- return true;
- }
-
- // Development permissions are only shown to the user if they are already
- // granted to the app -- if we are installing an app and they are not
- // already granted, they will not be granted as part of the install.
- if (isDevelopment && wasGranted) {
- if (localLOGV) Log.i(TAG, "Special perm " + pInfo.name
- + ": protlevel=0x" + Integer.toHexString(pInfo.protectionLevel));
- return true;
- }
- return false;
- }
-
- private static class PermissionGroupInfoComparator implements Comparator<MyPermissionGroupInfo> {
- private final Collator sCollator = Collator.getInstance();
- @Override
- public final int compare(MyPermissionGroupInfo a, MyPermissionGroupInfo b) {
- return sCollator.compare(a.mLabel, b.mLabel);
- }
- }
-
- private static class PermissionInfoComparator implements Comparator<MyPermissionInfo> {
- private final Collator sCollator = Collator.getInstance();
- PermissionInfoComparator() {
- }
- public final int compare(MyPermissionInfo a, MyPermissionInfo b) {
- return sCollator.compare(a.mLabel, b.mLabel);
- }
- }
-
- private void addPermToList(List<MyPermissionInfo> permList,
- MyPermissionInfo pInfo) {
- if (pInfo.mLabel == null) {
- pInfo.mLabel = pInfo.loadSafeLabel(mPm, 20000, PackageItemInfo.SAFE_LABEL_FLAG_TRIM
- | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE);
- }
- int idx = Collections.binarySearch(permList, pInfo, mPermComparator);
- if(localLOGV) Log.i(TAG, "idx="+idx+", list.size="+permList.size());
- if (idx < 0) {
- idx = -idx-1;
- permList.add(idx, pInfo);
- }
- }
-
- private void setPermissions(List<MyPermissionInfo> permList) {
- if (permList != null) {
- // First pass to group permissions
- for (MyPermissionInfo pInfo : permList) {
- if(localLOGV) Log.i(TAG, "Processing permission:"+pInfo.name);
- if(!isDisplayablePermission(pInfo, pInfo.mNewReqFlags, pInfo.mExistingReqFlags)) {
- if(localLOGV) Log.i(TAG, "Permission:"+pInfo.name+" is not displayable");
- continue;
- }
- MyPermissionGroupInfo group = mPermGroups.get(pInfo.group);
- if (group != null) {
- pInfo.mLabel = pInfo.loadSafeLabel(mPm, 20000,
- PackageItemInfo.SAFE_LABEL_FLAG_TRIM
- | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE);
- addPermToList(group.mAllPermissions, pInfo);
- if (pInfo.mNew) {
- addPermToList(group.mNewPermissions, pInfo);
- }
- }
- }
- }
-
- for (MyPermissionGroupInfo pgrp : mPermGroups.values()) {
- if (pgrp.labelRes != 0 || pgrp.nonLocalizedLabel != null) {
- pgrp.mLabel = pgrp.loadSafeLabel(mPm, 20000, PackageItemInfo.SAFE_LABEL_FLAG_TRIM
- | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE);
- } else {
- ApplicationInfo app;
- try {
- app = mPm.getApplicationInfo(pgrp.packageName, 0);
- pgrp.mLabel = app.loadSafeLabel(mPm, 20000, PackageItemInfo.SAFE_LABEL_FLAG_TRIM
- | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE);
- } catch (NameNotFoundException e) {
- pgrp.mLabel = pgrp.loadSafeLabel(mPm, 20000,
- PackageItemInfo.SAFE_LABEL_FLAG_TRIM
- | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE);
- }
- }
- mPermGroupsList.add(pgrp);
- }
- Collections.sort(mPermGroupsList, mPermGroupComparator);
- }
}
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 8d09489..d35bec8 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -578,11 +578,16 @@
stream.addProperty("checked", isChecked());
}
- @Override
- public void onProvideAutofillStructure(ViewStructure structure, int flags) {
- super.onProvideAutofillStructure(structure, flags);
- structure.setDataIsSensitive(!mCheckedFromResource);
+ /** @hide */
+ @Override
+ protected void onProvideStructure(@NonNull ViewStructure structure,
+ @ViewStructureType int viewFor, int flags) {
+ super.onProvideStructure(structure, viewFor, flags);
+
+ if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
+ structure.setDataIsSensitive(!mCheckedFromResource);
+ }
}
@Override
diff --git a/core/java/android/widget/MediaControlView2.java b/core/java/android/widget/MediaControlView2.java
deleted file mode 100644
index f52854a..0000000
--- a/core/java/android/widget/MediaControlView2.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.widget;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.media.SessionToken2;
-import android.media.session.MediaController;
-import android.media.update.ApiLoader;
-import android.media.update.MediaControlView2Provider;
-import android.media.update.ViewGroupHelper;
-import android.util.AttributeSet;
-import android.view.View;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-// TODO: Use link annotation to refer VideoView2 once VideoView2 became unhidden.
-/**
- * @hide
- * A View that contains the controls for MediaPlayer2.
- * It provides a wide range of UI including buttons such as "Play/Pause", "Rewind", "Fast Forward",
- * "Subtitle", "Full Screen", and it is also possible to add multiple custom buttons.
- *
- * <p>
- * <em> MediaControlView2 can be initialized in two different ways: </em>
- * 1) When VideoView2 is initialized, it automatically initializes a MediaControlView2 instance and
- * adds it to the view.
- * 2) Initialize MediaControlView2 programmatically and add it to a ViewGroup instance.
- *
- * In the first option, VideoView2 automatically connects MediaControlView2 to MediaController,
- * which is necessary to communicate with MediaSession2. In the second option, however, the
- * developer needs to manually retrieve a MediaController instance and set it to MediaControlView2
- * by calling setController(MediaController controller).
- *
- * <p>
- * There is no separate method that handles the show/hide behavior for MediaControlView2. Instead,
- * one can directly change the visibility of this view by calling View.setVisibility(int). The
- * values supported are View.VISIBLE and View.GONE.
- * In addition, the following customization is supported:
- * Set focus to the play/pause button by calling requestPlayButtonFocus().
- *
- * <p>
- * It is also possible to add custom buttons with custom icons and actions inside MediaControlView2.
- * Those buttons will be shown when the overflow button is clicked.
- * See VideoView2#setCustomActions for more details on how to add.
- */
-public class MediaControlView2 extends ViewGroupHelper<MediaControlView2Provider> {
- /** @hide */
- @IntDef({
- BUTTON_PLAY_PAUSE,
- BUTTON_FFWD,
- BUTTON_REW,
- BUTTON_NEXT,
- BUTTON_PREV,
- BUTTON_SUBTITLE,
- BUTTON_FULL_SCREEN,
- BUTTON_OVERFLOW,
- BUTTON_MUTE,
- BUTTON_ASPECT_RATIO,
- BUTTON_SETTINGS
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface Button {}
-
- /**
- * MediaControlView2 button value for playing and pausing media.
- * @hide
- */
- public static final int BUTTON_PLAY_PAUSE = 1;
- /**
- * MediaControlView2 button value for jumping 30 seconds forward.
- * @hide
- */
- public static final int BUTTON_FFWD = 2;
- /**
- * MediaControlView2 button value for jumping 10 seconds backward.
- * @hide
- */
- public static final int BUTTON_REW = 3;
- /**
- * MediaControlView2 button value for jumping to next media.
- * @hide
- */
- public static final int BUTTON_NEXT = 4;
- /**
- * MediaControlView2 button value for jumping to previous media.
- * @hide
- */
- public static final int BUTTON_PREV = 5;
- /**
- * MediaControlView2 button value for showing/hiding subtitle track.
- * @hide
- */
- public static final int BUTTON_SUBTITLE = 6;
- /**
- * MediaControlView2 button value for toggling full screen.
- * @hide
- */
- public static final int BUTTON_FULL_SCREEN = 7;
- /**
- * MediaControlView2 button value for showing/hiding overflow buttons.
- * @hide
- */
- public static final int BUTTON_OVERFLOW = 8;
- /**
- * MediaControlView2 button value for muting audio.
- * @hide
- */
- public static final int BUTTON_MUTE = 9;
- /**
- * MediaControlView2 button value for adjusting aspect ratio of view.
- * @hide
- */
- public static final int BUTTON_ASPECT_RATIO = 10;
- /**
- * MediaControlView2 button value for showing/hiding settings page.
- * @hide
- */
- public static final int BUTTON_SETTINGS = 11;
-
- public MediaControlView2(@NonNull Context context) {
- this(context, null);
- }
-
- public MediaControlView2(@NonNull Context context, @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public MediaControlView2(@NonNull Context context, @Nullable AttributeSet attrs,
- int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public MediaControlView2(@NonNull Context context, @Nullable AttributeSet attrs,
- int defStyleAttr, int defStyleRes) {
- super((instance, superProvider, privateProvider) ->
- ApiLoader.getProvider().createMediaControlView2(
- (MediaControlView2) instance, superProvider, privateProvider,
- attrs, defStyleAttr, defStyleRes),
- context, attrs, defStyleAttr, defStyleRes);
- mProvider.initialize(attrs, defStyleAttr, defStyleRes);
- }
-
- /**
- * Sets MediaSession2 token to control corresponding MediaSession2.
- */
- public void setMediaSessionToken(SessionToken2 token) {
- mProvider.setMediaSessionToken_impl(token);
- }
-
- /**
- * Registers a callback to be invoked when the fullscreen mode should be changed.
- * @param l The callback that will be run
- */
- public void setOnFullScreenListener(OnFullScreenListener l) {
- mProvider.setOnFullScreenListener_impl(l);
- }
-
- /**
- * @hide TODO: remove once the implementation is revised
- */
- public void setController(MediaController controller) {
- mProvider.setController_impl(controller);
- }
-
- /**
- * Changes the visibility state of an individual button. Default value is View.Visible.
- *
- * @param button the {@code Button} assigned to individual buttons
- * <ul>
- * <li>{@link #BUTTON_PLAY_PAUSE}
- * <li>{@link #BUTTON_FFWD}
- * <li>{@link #BUTTON_REW}
- * <li>{@link #BUTTON_NEXT}
- * <li>{@link #BUTTON_PREV}
- * <li>{@link #BUTTON_SUBTITLE}
- * <li>{@link #BUTTON_FULL_SCREEN}
- * <li>{@link #BUTTON_MUTE}
- * <li>{@link #BUTTON_OVERFLOW}
- * <li>{@link #BUTTON_ASPECT_RATIO}
- * <li>{@link #BUTTON_SETTINGS}
- * </ul>
- * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
- * @hide
- */
- public void setButtonVisibility(@Button int button, @Visibility int visibility) {
- mProvider.setButtonVisibility_impl(button, visibility);
- }
-
- /**
- * Requests focus for the play/pause button.
- */
- public void requestPlayButtonFocus() {
- mProvider.requestPlayButtonFocus_impl();
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- mProvider.onLayout_impl(changed, l, t, r, b);
- }
-
- /**
- * Interface definition of a callback to be invoked to inform the fullscreen mode is changed.
- * Application should handle the fullscreen mode accordingly.
- */
- public interface OnFullScreenListener {
- /**
- * Called to indicate a fullscreen mode change.
- */
- void onFullScreen(View view, boolean fullScreen);
- }
-}
diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java
index 327a5c1..ab12eac 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -17,6 +17,7 @@
package android.widget;
import android.annotation.IdRes;
+import android.annotation.NonNull;
import android.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.TypedArray;
@@ -424,10 +425,15 @@
}
}
+ /** @hide */
@Override
- public void onProvideAutofillStructure(ViewStructure structure, int flags) {
- super.onProvideAutofillStructure(structure, flags);
- structure.setDataIsSensitive(mCheckedId != mInitialCheckedId);
+ protected void onProvideStructure(@NonNull ViewStructure structure,
+ @ViewStructureType int viewFor, int flags) {
+ super.onProvideStructure(structure, viewFor, flags);
+
+ if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
+ structure.setDataIsSensitive(mCheckedId != mInitialCheckedId);
+ }
}
@Override
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 3bdd7b8..35be766 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -6076,7 +6076,7 @@
if (needEditableForNotification) {
sendAfterTextChanged((Editable) text);
} else {
- notifyManagersAfterTextChanged();
+ notifyListeningManagersAfterTextChanged();
}
// SelectionModifierCursorController depends on textCanBeSelected, which depends on text
@@ -10124,13 +10124,16 @@
}
}
- // Always notify AutoFillManager - it will return right away if autofill is disabled.
- notifyManagersAfterTextChanged();
+ notifyListeningManagersAfterTextChanged();
hideErrorIfUnchanged();
}
- private void notifyManagersAfterTextChanged() {
+ /**
+ * Notify managers (such as {@link AutofillManager} and {@link IntelligenceManager}) that are
+ * interested on text changes.
+ */
+ private void notifyListeningManagersAfterTextChanged() {
// Autofill
if (isAutofillable()) {
@@ -10911,34 +10914,17 @@
return TextView.class.getName();
}
+ /** @hide */
@Override
- public void onProvideStructure(ViewStructure structure) {
- super.onProvideStructure(structure);
- onProvideStructureForAssistOrAutofillOrViewCapture(structure, /* forAutofill = */ false,
- /* forViewCapture= */ false);
- }
+ protected void onProvideStructure(@NonNull ViewStructure structure,
+ @ViewStructureType int viewFor, int flags) {
+ super.onProvideStructure(structure, viewFor, flags);
- @Override
- public void onProvideAutofillStructure(ViewStructure structure, int flags) {
- super.onProvideAutofillStructure(structure, flags);
- onProvideStructureForAssistOrAutofillOrViewCapture(structure, /* forAutofill = */ true,
- /* forViewCapture= */ false);
- }
-
- @Override
- public boolean onProvideContentCaptureStructure(ViewStructure structure, int flags) {
- final boolean notifyManager = super.onProvideContentCaptureStructure(structure, flags);
- onProvideStructureForAssistOrAutofillOrViewCapture(structure, /* forAutofill = */ false,
- /* forViewCapture= */ true);
- return notifyManager;
- }
-
- private void onProvideStructureForAssistOrAutofillOrViewCapture(ViewStructure structure,
- boolean forAutofill, boolean forViewCapture) {
final boolean isPassword = hasPasswordTransformationMethod()
|| isPasswordInputType(getInputType());
- if (forAutofill || forViewCapture) {
- if (forAutofill) {
+ if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
+ || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
+ if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId);
}
if (mTextId != ResourceId.ID_NULL) {
@@ -10953,7 +10939,8 @@
}
}
- if (!isPassword || forAutofill || forViewCapture) {
+ if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
+ || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
if (mLayout == null) {
assumeLayout();
}
@@ -10962,7 +10949,7 @@
if (lineCount <= 1) {
// Simple case: this is a single line.
final CharSequence text = getText();
- if (forAutofill) {
+ if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
structure.setText(text);
} else {
structure.setText(text, getSelectionStart(), getSelectionEnd());
@@ -11026,7 +11013,7 @@
text = text.subSequence(expandedTopChar, expandedBottomChar);
}
- if (forAutofill) {
+ if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
structure.setText(text);
} else {
structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar);
@@ -11042,7 +11029,8 @@
}
}
- if (!forAutofill) {
+ if (viewFor == VIEW_STRUCTURE_FOR_ASSIST
+ || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
// Extract style information that applies to the TextView as a whole.
int style = 0;
int typefaceStyle = getTypefaceStyle();
@@ -11070,7 +11058,8 @@
structure.setTextStyle(getTextSize(), getCurrentTextColor(),
AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
}
- if (forAutofill || forViewCapture) {
+ if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
+ || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
structure.setMinTextEms(getMinEms());
structure.setMaxTextEms(getMaxEms());
int maxLength = -1;
diff --git a/core/java/android/widget/VideoView2.java b/core/java/android/widget/VideoView2.java
deleted file mode 100644
index 0724294a..0000000
--- a/core/java/android/widget/VideoView2.java
+++ /dev/null
@@ -1,452 +0,0 @@
-/*
- * 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.
- */
-
-package android.widget;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
-import android.content.Context;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.media.DataSourceDesc;
-import android.media.MediaItem2;
-import android.media.MediaMetadata2;
-import android.media.MediaPlayer2;
-import android.media.SessionToken2;
-import android.media.session.MediaController;
-import android.media.session.PlaybackState;
-import android.media.update.ApiLoader;
-import android.media.update.VideoView2Provider;
-import android.media.update.ViewGroupHelper;
-import android.net.Uri;
-import android.os.Bundle;
-import android.util.AttributeSet;
-import android.view.View;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Executor;
-
-// TODO: Replace MediaSession wtih MediaSession2 once MediaSession2 is submitted.
-/**
- * @hide
- * Displays a video file. VideoView2 class is a View class which is wrapping {@link MediaPlayer2}
- * so that developers can easily implement a video rendering application.
- *
- * <p>
- * <em> Data sources that VideoView2 supports : </em>
- * VideoView2 can play video files and audio-only files as
- * well. It can load from various sources such as resources or content providers. The supported
- * media file formats are the same as {@link MediaPlayer2}.
- *
- * <p>
- * <em> View type can be selected : </em>
- * VideoView2 can render videos on top of TextureView as well as
- * SurfaceView selectively. The default is SurfaceView and it can be changed using
- * {@link #setViewType(int)} method. Using SurfaceView is recommended in most cases for saving
- * battery. TextureView might be preferred for supporting various UIs such as animation and
- * translucency.
- *
- * <p>
- * <em> Differences between {@link VideoView} class : </em>
- * VideoView2 covers and inherits the most of
- * VideoView's functionalities. The main differences are
- * <ul>
- * <li> VideoView2 inherits FrameLayout and renders videos using SurfaceView and TextureView
- * selectively while VideoView inherits SurfaceView class.
- * <li> VideoView2 is integrated with MediaControlView2 and a default MediaControlView2 instance is
- * attached to VideoView2 by default. If a developer does not want to use the default
- * MediaControlView2, needs to set enableControlView attribute to false. For instance,
- * <pre>
- * <VideoView2
- * android:id="@+id/video_view"
- * xmlns:widget="http://schemas.android.com/apk/com.android.media.update"
- * widget:enableControlView="false" />
- * </pre>
- * If a developer wants to attach a customed MediaControlView2, then set enableControlView attribute
- * to false and assign the customed media control widget using {@link #setMediaControlView2}.
- * <li> VideoView2 is integrated with MediaPlayer2 while VideoView is integrated with MediaPlayer.
- * <li> VideoView2 is integrated with MediaSession and so it responses with media key events.
- * A VideoView2 keeps a MediaSession instance internally and connects it to a corresponding
- * MediaControlView2 instance.
- * </p>
- * </ul>
- *
- * <p>
- * <em> Audio focus and audio attributes : </em>
- * By default, VideoView2 requests audio focus with
- * {@link AudioManager#AUDIOFOCUS_GAIN}. Use {@link #setAudioFocusRequest(int)} to change this
- * behavior. The default {@link AudioAttributes} used during playback have a usage of
- * {@link AudioAttributes#USAGE_MEDIA} and a content type of
- * {@link AudioAttributes#CONTENT_TYPE_MOVIE}, use {@link #setAudioAttributes(AudioAttributes)} to
- * modify them.
- *
- * <p>
- * Note: VideoView2 does not retain its full state when going into the background. In particular, it
- * does not restore the current play state, play position, selected tracks. Applications should save
- * and restore these on their own in {@link android.app.Activity#onSaveInstanceState} and
- * {@link android.app.Activity#onRestoreInstanceState}.
- */
-public class VideoView2 extends ViewGroupHelper<VideoView2Provider> {
- /** @hide */
- @IntDef({
- VIEW_TYPE_TEXTUREVIEW,
- VIEW_TYPE_SURFACEVIEW
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface ViewType {}
-
- /**
- * Indicates video is rendering on SurfaceView.
- *
- * @see #setViewType
- */
- public static final int VIEW_TYPE_SURFACEVIEW = 1;
-
- /**
- * Indicates video is rendering on TextureView.
- *
- * @see #setViewType
- */
- public static final int VIEW_TYPE_TEXTUREVIEW = 2;
-
- public VideoView2(@NonNull Context context) {
- this(context, null);
- }
-
- public VideoView2(@NonNull Context context, @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public VideoView2(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public VideoView2(
- @NonNull Context context, @Nullable AttributeSet attrs,
- int defStyleAttr, int defStyleRes) {
- super((instance, superProvider, privateProvider) ->
- ApiLoader.getProvider().createVideoView2(
- (VideoView2) instance, superProvider, privateProvider,
- attrs, defStyleAttr, defStyleRes),
- context, attrs, defStyleAttr, defStyleRes);
- mProvider.initialize(attrs, defStyleAttr, defStyleRes);
- }
-
- /**
- * Sets MediaControlView2 instance. It will replace the previously assigned MediaControlView2
- * instance if any.
- *
- * @param mediaControlView a media control view2 instance.
- * @param intervalMs a time interval in milliseconds until VideoView2 hides MediaControlView2.
- */
- public void setMediaControlView2(MediaControlView2 mediaControlView, long intervalMs) {
- mProvider.setMediaControlView2_impl(mediaControlView, intervalMs);
- }
-
- /**
- * Returns MediaControlView2 instance which is currently attached to VideoView2 by default or by
- * {@link #setMediaControlView2} method.
- */
- public MediaControlView2 getMediaControlView2() {
- return mProvider.getMediaControlView2_impl();
- }
-
- /**
- * Sets MediaMetadata2 instance. It will replace the previously assigned MediaMetadata2 instance
- * if any.
- *
- * @param metadata a MediaMetadata2 instance.
- * @hide
- */
- public void setMediaMetadata(MediaMetadata2 metadata) {
- mProvider.setMediaMetadata_impl(metadata);
- }
-
- /**
- * Returns MediaMetadata2 instance which is retrieved from MediaPlayer2 inside VideoView2 by
- * default or by {@link #setMediaMetadata} method.
- * @hide
- */
- public MediaMetadata2 getMediaMetadata() {
- // TODO: add to Javadoc whether this value can be null or not when integrating with
- // MediaSession2.
- return mProvider.getMediaMetadata_impl();
- }
-
- /**
- * Returns MediaController instance which is connected with MediaSession that VideoView2 is
- * using. This method should be called when VideoView2 is attached to window, or it throws
- * IllegalStateException, since internal MediaSession instance is not available until
- * this view is attached to window. Please check {@link android.view.View#isAttachedToWindow}
- * before calling this method.
- *
- * @throws IllegalStateException if interal MediaSession is not created yet.
- * @hide TODO: remove
- */
- @UnsupportedAppUsage
- public MediaController getMediaController() {
- return mProvider.getMediaController_impl();
- }
-
- /**
- * Returns {@link android.media.SessionToken2} so that developers create their own
- * {@link android.media.MediaController2} instance. This method should be called when VideoView2
- * is attached to window, or it throws IllegalStateException.
- *
- * @throws IllegalStateException if interal MediaSession is not created yet.
- */
- public SessionToken2 getMediaSessionToken() {
- return mProvider.getMediaSessionToken_impl();
- }
-
- /**
- * Shows or hides closed caption or subtitles if there is any.
- * The first subtitle track will be chosen if there multiple subtitle tracks exist.
- * Default behavior of VideoView2 is not showing subtitle.
- * @param enable shows closed caption or subtitles if this value is true, or hides.
- */
- public void setSubtitleEnabled(boolean enable) {
- mProvider.setSubtitleEnabled_impl(enable);
- }
-
- /**
- * Returns true if showing subtitle feature is enabled or returns false.
- * Although there is no subtitle track or closed caption, it can return true, if the feature
- * has been enabled by {@link #setSubtitleEnabled}.
- */
- public boolean isSubtitleEnabled() {
- return mProvider.isSubtitleEnabled_impl();
- }
-
- /**
- * Sets playback speed.
- *
- * It is expressed as a multiplicative factor, where normal speed is 1.0f. If it is less than
- * or equal to zero, it will be just ignored and nothing will be changed. If it exceeds the
- * maximum speed that internal engine supports, system will determine best handling or it will
- * be reset to the normal speed 1.0f.
- * @param speed the playback speed. It should be positive.
- */
- // TODO: Support this via MediaController2.
- public void setSpeed(float speed) {
- mProvider.setSpeed_impl(speed);
- }
-
- /**
- * Sets which type of audio focus will be requested during the playback, or configures playback
- * to not request audio focus. Valid values for focus requests are
- * {@link AudioManager#AUDIOFOCUS_GAIN}, {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT},
- * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}, and
- * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}. Or use
- * {@link AudioManager#AUDIOFOCUS_NONE} to express that audio focus should not be
- * requested when playback starts. You can for instance use this when playing a silent animation
- * through this class, and you don't want to affect other audio applications playing in the
- * background.
- *
- * @param focusGain the type of audio focus gain that will be requested, or
- * {@link AudioManager#AUDIOFOCUS_NONE} to disable the use audio focus during
- * playback.
- */
- public void setAudioFocusRequest(int focusGain) {
- mProvider.setAudioFocusRequest_impl(focusGain);
- }
-
- /**
- * Sets the {@link AudioAttributes} to be used during the playback of the video.
- *
- * @param attributes non-null <code>AudioAttributes</code>.
- */
- public void setAudioAttributes(@NonNull AudioAttributes attributes) {
- mProvider.setAudioAttributes_impl(attributes);
- }
-
- /**
- * Sets video path.
- *
- * @param path the path of the video.
- *
- * @hide TODO remove
- */
- @UnsupportedAppUsage
- public void setVideoPath(String path) {
- mProvider.setVideoPath_impl(path);
- }
-
- /**
- * Sets video URI.
- *
- * @param uri the URI of the video.
- *
- * @hide TODO remove
- */
- public void setVideoUri(Uri uri) {
- mProvider.setVideoUri_impl(uri);
- }
-
- /**
- * Sets video URI using specific headers.
- *
- * @param uri the URI of the video.
- * @param headers the headers for the URI request.
- * Note that the cross domain redirection is allowed by default, but that can be
- * changed with key/value pairs through the headers parameter with
- * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value
- * to disallow or allow cross domain redirection.
- *
- * @hide TODO remove
- */
- public void setVideoUri(Uri uri, Map<String, String> headers) {
- mProvider.setVideoUri_impl(uri, headers);
- }
-
- /**
- * Sets {@link MediaItem2} object to render using VideoView2. Alternative way to set media
- * object to VideoView2 is {@link #setDataSource}.
- * @param mediaItem the MediaItem2 to play
- * @see #setDataSource
- */
- public void setMediaItem(@NonNull MediaItem2 mediaItem) {
- mProvider.setMediaItem_impl(mediaItem);
- }
-
- /**
- * Sets {@link DataSourceDesc} object to render using VideoView2.
- * @param dataSource the {@link DataSourceDesc} object to play.
- * @see #setMediaItem
- */
- public void setDataSource(@NonNull DataSourceDesc dataSource) {
- mProvider.setDataSource_impl(dataSource);
- }
-
- /**
- * Selects which view will be used to render video between SurfacView and TextureView.
- *
- * @param viewType the view type to render video
- * <ul>
- * <li>{@link #VIEW_TYPE_SURFACEVIEW}
- * <li>{@link #VIEW_TYPE_TEXTUREVIEW}
- * </ul>
- */
- public void setViewType(@ViewType int viewType) {
- mProvider.setViewType_impl(viewType);
- }
-
- /**
- * Returns view type.
- *
- * @return view type. See {@see setViewType}.
- */
- @ViewType
- public int getViewType() {
- return mProvider.getViewType_impl();
- }
-
- /**
- * Sets custom actions which will be shown as custom buttons in {@link MediaControlView2}.
- *
- * @param actionList A list of {@link PlaybackState.CustomAction}. The return value of
- * {@link PlaybackState.CustomAction#getIcon()} will be used to draw buttons
- * in {@link MediaControlView2}.
- * @param executor executor to run callbacks on.
- * @param listener A listener to be called when a custom button is clicked.
- * @hide TODO remove
- */
- public void setCustomActions(List<PlaybackState.CustomAction> actionList,
- Executor executor, OnCustomActionListener listener) {
- mProvider.setCustomActions_impl(actionList, executor, listener);
- }
-
- /**
- * Registers a callback to be invoked when a view type change is done.
- * {@see #setViewType(int)}
- * @param l The callback that will be run
- * @hide
- */
- @VisibleForTesting
- @UnsupportedAppUsage
- public void setOnViewTypeChangedListener(OnViewTypeChangedListener l) {
- mProvider.setOnViewTypeChangedListener_impl(l);
- }
-
- /**
- * Registers a callback to be invoked when the fullscreen mode should be changed.
- * @param l The callback that will be run
- * @hide TODO remove
- */
- public void setFullScreenRequestListener(OnFullScreenRequestListener l) {
- mProvider.setFullScreenRequestListener_impl(l);
- }
-
- /**
- * Interface definition of a callback to be invoked when the view type has been changed.
- *
- * @hide
- */
- @VisibleForTesting
- public interface OnViewTypeChangedListener {
- /**
- * Called when the view type has been changed.
- * @see #setViewType(int)
- * @param view the View whose view type is changed
- * @param viewType
- * <ul>
- * <li>{@link #VIEW_TYPE_SURFACEVIEW}
- * <li>{@link #VIEW_TYPE_TEXTUREVIEW}
- * </ul>
- */
- @UnsupportedAppUsage
- void onViewTypeChanged(View view, @ViewType int viewType);
- }
-
- /**
- * Interface definition of a callback to be invoked to inform the fullscreen mode is changed.
- * Application should handle the fullscreen mode accordingly.
- * @hide TODO remove
- */
- public interface OnFullScreenRequestListener {
- /**
- * Called to indicate a fullscreen mode change.
- */
- void onFullScreenRequest(View view, boolean fullScreen);
- }
-
- /**
- * Interface definition of a callback to be invoked to inform that a custom action is performed.
- * @hide TODO remove
- */
- public interface OnCustomActionListener {
- /**
- * Called to indicate that a custom action is performed.
- *
- * @param action The action that was originally sent in the
- * {@link PlaybackState.CustomAction}.
- * @param extras Optional extras.
- */
- void onCustomAction(String action, Bundle extras);
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- mProvider.onLayout_impl(changed, l, t, r, b);
- }
-}
diff --git a/core/java/com/android/internal/app/ColorDisplayController.java b/core/java/com/android/internal/app/ColorDisplayController.java
index 7515180..213bb75 100644
--- a/core/java/com/android/internal/app/ColorDisplayController.java
+++ b/core/java/com/android/internal/app/ColorDisplayController.java
@@ -560,13 +560,6 @@
}
/**
- * Returns {@code true} if Night display is supported by the device.
- */
- public static boolean isAvailable(Context context) {
- return context.getResources().getBoolean(R.bool.config_nightDisplayAvailable);
- }
-
- /**
* Callback invoked whenever the Night display settings are changed.
*/
public interface Callback {
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index 81dab2f..8bc90a8 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -389,14 +389,18 @@
* @param query the search condition used to match file names
* @param projection projection of the returned cursor
* @param exclusion absolute file paths to exclude from result
- * @return cursor containing search result
+ * @param queryArgs the query arguments for search
+ * @return cursor containing search result. Include
+ * {@link ContentResolver#EXTRA_HONORED_ARGS} in {@link Cursor}
+ * extras {@link Bundle} when any QUERY_ARG_* value was honored
+ * during the preparation of the results.
* @throws FileNotFoundException when root folder doesn't exist or search fails
+ *
+ * @see ContentResolver#EXTRA_HONORED_ARGS
*/
protected final Cursor querySearchDocuments(
- File folder, String query, String[] projection, Set<String> exclusion)
+ File folder, String[] projection, Set<String> exclusion, Bundle queryArgs)
throws FileNotFoundException {
-
- query = query.toLowerCase();
final MatrixCursor result = new MatrixCursor(resolveProjection(projection));
final LinkedList<File> pending = new LinkedList<>();
pending.add(folder);
@@ -407,11 +411,18 @@
pending.add(child);
}
}
- if (file.getName().toLowerCase().contains(query)
- && !exclusion.contains(file.getAbsolutePath())) {
+ if (!exclusion.contains(file.getAbsolutePath()) && matchSearchQueryArguments(file,
+ queryArgs)) {
includeFile(result, null, file);
}
}
+
+ final String[] handledQueryArgs = DocumentsContract.getHandledQueryArguments(queryArgs);
+ if (handledQueryArgs.length > 0) {
+ final Bundle extras = new Bundle();
+ extras.putStringArray(ContentResolver.EXTRA_HONORED_ARGS, handledQueryArgs);
+ result.setExtras(extras);
+ }
return result;
}
@@ -457,6 +468,34 @@
}
}
+ /**
+ * Test if the file matches the query arguments.
+ *
+ * @param file the file to test
+ * @param queryArgs the query arguments
+ */
+ private boolean matchSearchQueryArguments(File file, Bundle queryArgs) {
+ if (file == null) {
+ return false;
+ }
+
+ final String fileMimeType;
+ final String fileName = file.getName();
+
+ if (file.isDirectory()) {
+ fileMimeType = DocumentsContract.Document.MIME_TYPE_DIR;
+ } else {
+ int dotPos = fileName.lastIndexOf('.');
+ if (dotPos < 0) {
+ return false;
+ }
+ final String extension = fileName.substring(dotPos + 1);
+ fileMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+ }
+ return DocumentsContract.matchSearchQueryArguments(queryArgs, fileName, fileMimeType,
+ file.lastModified(), file.length());
+ }
+
private void scanFile(File visibleFile) {
final Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(Uri.fromFile(visibleFile));
diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java
index 0baf73c..02c9542 100644
--- a/core/java/com/android/internal/os/BatterySipper.java
+++ b/core/java/com/android/internal/os/BatterySipper.java
@@ -130,6 +130,10 @@
public double wakeLockPowerMah;
public double wifiPowerMah;
+ // ****************
+ // This list must be kept current with atoms.proto (frameworks/base/cmds/statsd/src/atoms.proto)
+ // so the ordinal values (and therefore the order) must never change.
+ // ****************
public enum DrainType {
AMBIENT_DISPLAY,
@UnsupportedAppUsage
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index 70fc72f..34e8ed4 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -421,7 +421,7 @@
}
protected int getWorkSourceUid() {
- return Binder.getThreadWorkSource();
+ return Binder.getCallingWorkSourceUid();
}
protected long getElapsedRealtimeMicro() {
diff --git a/core/java/com/android/internal/os/KernelCpuThreadReader.java b/core/java/com/android/internal/os/KernelCpuThreadReader.java
index 7c82a7e..ade5d05 100644
--- a/core/java/com/android/internal/os/KernelCpuThreadReader.java
+++ b/core/java/com/android/internal/os/KernelCpuThreadReader.java
@@ -21,6 +21,7 @@
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
import java.io.IOException;
import java.nio.file.DirectoryStream;
@@ -33,6 +34,16 @@
* Given a process, will iterate over the child threads of the process, and return the CPU usage
* statistics for each child thread. The CPU usage statistics contain the amount of time spent in a
* frequency band.
+ *
+ * <p>Frequencies are bucketed together to reduce the amount of data created. This means that we
+ * return {@link #NUM_BUCKETS} frequencies instead of the full number. Frequencies are reported as
+ * the lowest frequency in that range. Frequencies are spread as evenly as possible across the
+ * buckets. The buckets do not cross over the little/big frequencies reported.
+ *
+ * <p>N.B.: In order to bucket across little/big frequencies correctly, we assume that the {@code
+ * time_in_state} file contains every little core frequency in ascending order, followed by every
+ * big core frequency in ascending order. This assumption might not hold for devices with different
+ * kernel implementations of the {@code time_in_state} file generation.
*/
public class KernelCpuThreadReader {
@@ -80,6 +91,11 @@
DEFAULT_PROC_PATH.resolve("self/time_in_state");
/**
+ * Number of frequency buckets
+ */
+ private static final int NUM_BUCKETS = 8;
+
+ /**
* Where the proc filesystem is mounted
*/
private final Path mProcPath;
@@ -95,6 +111,11 @@
*/
private final ProcTimeInStateReader mProcTimeInStateReader;
+ /**
+ * Used to sort frequencies and usage times into buckets
+ */
+ private final FrequencyBucketCreator mFrequencyBucketCreator;
+
private KernelCpuThreadReader() throws IOException {
this(DEFAULT_PROC_PATH, DEFAULT_INITIAL_TIME_IN_STATE_PATH);
}
@@ -111,12 +132,10 @@
mProcPath = procPath;
mProcTimeInStateReader = new ProcTimeInStateReader(initialTimeInStatePath);
- // Copy mProcTimeInState's frequencies, casting the longs to ints
- long[] frequenciesKhz = mProcTimeInStateReader.getFrequenciesKhz();
- mFrequenciesKhz = new int[frequenciesKhz.length];
- for (int i = 0; i < frequenciesKhz.length; i++) {
- mFrequenciesKhz[i] = (int) frequenciesKhz[i];
- }
+ // Copy mProcTimeInState's frequencies and initialize bucketing
+ final long[] frequenciesKhz = mProcTimeInStateReader.getFrequenciesKhz();
+ mFrequencyBucketCreator = new FrequencyBucketCreator(frequenciesKhz, NUM_BUCKETS);
+ mFrequenciesKhz = mFrequencyBucketCreator.getBucketMinFrequencies(frequenciesKhz);
}
/**
@@ -228,12 +247,7 @@
if (cpuUsagesLong == null) {
return null;
}
-
- // Convert long[] to int[]
- final int[] cpuUsages = new int[cpuUsagesLong.length];
- for (int i = 0; i < cpuUsagesLong.length; i++) {
- cpuUsages[i] = (int) cpuUsagesLong[i];
- }
+ int[] cpuUsages = mFrequencyBucketCreator.getBucketedValues(cpuUsagesLong);
return new ThreadCpuUsage(threadId, threadName, cpuUsages);
}
@@ -266,6 +280,132 @@
}
/**
+ * Puts frequencies and usage times into buckets
+ */
+ @VisibleForTesting
+ public static class FrequencyBucketCreator {
+ private final int mNumBuckets;
+ private final int mNumFrequencies;
+ private final int mBigFrequenciesStartIndex;
+ private final int mLittleNumBuckets;
+ private final int mBigNumBuckets;
+ private final int mLittleBucketSize;
+ private final int mBigBucketSize;
+
+ /**
+ * Buckets based of a list of frequencies
+ *
+ * @param frequencies the frequencies to base buckets off
+ * @param numBuckets how many buckets to create
+ */
+ @VisibleForTesting
+ public FrequencyBucketCreator(long[] frequencies, int numBuckets) {
+ Preconditions.checkArgument(numBuckets > 0);
+
+ mNumFrequencies = frequencies.length;
+ mBigFrequenciesStartIndex = getBigFrequenciesStartIndex(frequencies);
+
+ final int littleNumBuckets;
+ final int bigNumBuckets;
+ if (mBigFrequenciesStartIndex < frequencies.length) {
+ littleNumBuckets = numBuckets / 2;
+ bigNumBuckets = numBuckets - littleNumBuckets;
+ } else {
+ // If we've got no big frequencies, set all buckets to little frequencies
+ littleNumBuckets = numBuckets;
+ bigNumBuckets = 0;
+ }
+
+ // Ensure that we don't have more buckets than frequencies
+ mLittleNumBuckets = Math.min(littleNumBuckets, mBigFrequenciesStartIndex);
+ mBigNumBuckets = Math.min(
+ bigNumBuckets, frequencies.length - mBigFrequenciesStartIndex);
+ mNumBuckets = mLittleNumBuckets + mBigNumBuckets;
+
+ // Set the size of each little and big bucket. If they have no buckets, the size is zero
+ mLittleBucketSize = mLittleNumBuckets == 0 ? 0 :
+ mBigFrequenciesStartIndex / mLittleNumBuckets;
+ mBigBucketSize = mBigNumBuckets == 0 ? 0 :
+ (frequencies.length - mBigFrequenciesStartIndex) / mBigNumBuckets;
+ }
+
+ /**
+ * Find the index where frequencies change from little core to big core
+ */
+ @VisibleForTesting
+ public static int getBigFrequenciesStartIndex(long[] frequenciesKhz) {
+ for (int i = 0; i < frequenciesKhz.length - 1; i++) {
+ if (frequenciesKhz[i] > frequenciesKhz[i + 1]) {
+ return i + 1;
+ }
+ }
+
+ return frequenciesKhz.length;
+ }
+
+ /**
+ * Get the minimum frequency in each bucket
+ */
+ @VisibleForTesting
+ public int[] getBucketMinFrequencies(long[] frequenciesKhz) {
+ Preconditions.checkArgument(frequenciesKhz.length == mNumFrequencies);
+ // If there's only one bucket, we bucket everything together so the first bucket is the
+ // min frequency
+ if (mNumBuckets == 1) {
+ return new int[]{(int) frequenciesKhz[0]};
+ }
+
+ final int[] bucketMinFrequencies = new int[mNumBuckets];
+ // Initialize little buckets min frequencies
+ for (int i = 0; i < mLittleNumBuckets; i++) {
+ bucketMinFrequencies[i] = (int) frequenciesKhz[i * mLittleBucketSize];
+ }
+ // Initialize big buckets min frequencies
+ for (int i = 0; i < mBigNumBuckets; i++) {
+ final int frequencyIndex = mBigFrequenciesStartIndex + i * mBigBucketSize;
+ bucketMinFrequencies[mLittleNumBuckets + i] = (int) frequenciesKhz[frequencyIndex];
+ }
+ return bucketMinFrequencies;
+ }
+
+ /**
+ * Put an array of values into buckets. This takes a {@code long[]} and returns {@code
+ * int[]} as everywhere this method is used will have to do the conversion anyway, so we
+ * save time by doing it here instead
+ *
+ * @param values the values to bucket
+ * @return the bucketed usage times
+ */
+ @VisibleForTesting
+ public int[] getBucketedValues(long[] values) {
+ Preconditions.checkArgument(values.length == mNumFrequencies);
+ final int[] bucketed = new int[mNumBuckets];
+
+ // If there's only one bucket, add all frequencies in
+ if (mNumBuckets == 1) {
+ for (int i = 0; i < values.length; i++) {
+ bucketed[0] += values[i];
+ }
+ return bucketed;
+ }
+
+ // Initialize the little buckets
+ for (int i = 0; i < mBigFrequenciesStartIndex; i++) {
+ final int bucketIndex = Math.min(i / mLittleBucketSize, mLittleNumBuckets - 1);
+ bucketed[bucketIndex] += values[i];
+ }
+ // Initialize the big buckets
+ for (int i = mBigFrequenciesStartIndex; i < values.length; i++) {
+ final int bucketIndex = Math.min(
+ mLittleNumBuckets + (i - mBigFrequenciesStartIndex) / mBigBucketSize,
+ mNumBuckets - 1);
+ bucketed[bucketIndex] += values[i];
+ }
+ return bucketed;
+ }
+ }
+
+ /**
* CPU usage of a process
*/
public static class ProcessCpuUsage {
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 8f87f91..8bdb000 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -173,12 +173,13 @@
}
native private static void nativePreloadAppProcessHALs();
+ native private static void nativePreloadOpenGL();
private static void preloadOpenGL() {
String driverPackageName = SystemProperties.get(PROPERTY_GFX_DRIVER);
if (!SystemProperties.getBoolean(PROPERTY_DISABLE_OPENGL_PRELOADING, false) &&
(driverPackageName == null || driverPackageName.isEmpty())) {
- EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
+ nativePreloadOpenGL();
}
}
diff --git a/core/java/com/android/internal/util/OWNERS b/core/java/com/android/internal/util/OWNERS
index e65d114..8b9acd3 100644
--- a/core/java/com/android/internal/util/OWNERS
+++ b/core/java/com/android/internal/util/OWNERS
@@ -1,4 +1,4 @@
per-file AsyncChannel* = lorenzo@google.com, satk@google.com, etancohen@google.com
-per-file BitUtils*, MessageUtils*, Protocol*, RingBuffer*, TokenBucket* = jchalard@google.com, lorenzo@google.com, satk@google.com
+per-file MessageUtils*, Protocol*, RingBuffer*, TokenBucket* = jchalard@google.com, lorenzo@google.com, satk@google.com
per-file Protocol* = etancohen@google.com, lorenzo@google.com
per-file State* = jchalard@google.com, lorenzo@google.com, satk@google.com
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index e3490f1..137ca7f 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -16,7 +16,6 @@
package com.android.internal.view;
-import android.annotation.UnsupportedAppUsage;
import android.graphics.Rect;
import android.hardware.input.InputManager;
import android.os.Bundle;
@@ -66,7 +65,7 @@
}
@Override
- public void windowFocusChanged(boolean hasFocus, boolean touchEnabled) {
+ public void windowFocusChanged(boolean hasFocus, boolean touchEnabled, boolean reportToClient) {
}
@Override
diff --git a/core/java/com/android/internal/widget/SubtitleView.java b/core/java/com/android/internal/widget/SubtitleView.java
index 1107828..21e63c5 100644
--- a/core/java/com/android/internal/widget/SubtitleView.java
+++ b/core/java/com/android/internal/widget/SubtitleView.java
@@ -58,7 +58,7 @@
/** Reusable spannable string builder used for holding text. */
private final SpannableStringBuilder mText = new SpannableStringBuilder();
- private Alignment mAlignment;
+ private Alignment mAlignment = Alignment.ALIGN_CENTER;
private TextPaint mTextPaint;
private Paint mPaint;
diff --git a/core/jni/android_hardware_input_InputApplicationHandle.cpp b/core/jni/android_hardware_input_InputApplicationHandle.cpp
index 8ace8da..5887fa7 100644
--- a/core/jni/android_hardware_input_InputApplicationHandle.cpp
+++ b/core/jni/android_hardware_input_InputApplicationHandle.cpp
@@ -135,13 +135,13 @@
LOG_FATAL_IF(! (var), "Unable to find field " fieldName);
int register_android_server_InputApplicationHandle(JNIEnv* env) {
- int res = jniRegisterNativeMethods(env, "com/android/server/input/InputApplicationHandle",
+ int res = jniRegisterNativeMethods(env, "android/view/InputApplicationHandle",
gInputApplicationHandleMethods, NELEM(gInputApplicationHandleMethods));
(void) res; // Faked use when LOG_NDEBUG.
LOG_FATAL_IF(res < 0, "Unable to register native methods.");
jclass clazz;
- FIND_CLASS(clazz, "com/android/server/input/InputApplicationHandle");
+ FIND_CLASS(clazz, "android/view/InputApplicationHandle");
GET_FIELD_ID(gInputApplicationHandleClassInfo.ptr, clazz,
"ptr", "J");
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index f4829ad..6ecb5de 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -80,51 +80,47 @@
JNIEnv* env = AndroidRuntime::getJNIEnv();
jobject obj = env->NewLocalRef(mObjWeak);
if (!obj) {
- releaseInfo();
+ releaseChannel();
return false;
}
- if (!mInfo) {
- mInfo = new InputWindowInfo();
- } else {
- mInfo->touchableRegion.clear();
- }
+ mInfo.touchableRegion.clear();
jobject inputChannelObj = env->GetObjectField(obj,
gInputWindowHandleClassInfo.inputChannel);
if (inputChannelObj) {
- mInfo->inputChannel = android_view_InputChannel_getInputChannel(env, inputChannelObj);
+ mInfo.inputChannel = android_view_InputChannel_getInputChannel(env, inputChannelObj);
env->DeleteLocalRef(inputChannelObj);
} else {
- mInfo->inputChannel.clear();
+ mInfo.inputChannel.clear();
}
jstring nameObj = jstring(env->GetObjectField(obj,
gInputWindowHandleClassInfo.name));
if (nameObj) {
const char* nameStr = env->GetStringUTFChars(nameObj, NULL);
- mInfo->name = nameStr;
+ mInfo.name = nameStr;
env->ReleaseStringUTFChars(nameObj, nameStr);
env->DeleteLocalRef(nameObj);
} else {
- mInfo->name = "<null>";
+ mInfo.name = "<null>";
}
- mInfo->layoutParamsFlags = env->GetIntField(obj,
+ mInfo.layoutParamsFlags = env->GetIntField(obj,
gInputWindowHandleClassInfo.layoutParamsFlags);
- mInfo->layoutParamsType = env->GetIntField(obj,
+ mInfo.layoutParamsType = env->GetIntField(obj,
gInputWindowHandleClassInfo.layoutParamsType);
- mInfo->dispatchingTimeout = env->GetLongField(obj,
+ mInfo.dispatchingTimeout = env->GetLongField(obj,
gInputWindowHandleClassInfo.dispatchingTimeoutNanos);
- mInfo->frameLeft = env->GetIntField(obj,
+ mInfo.frameLeft = env->GetIntField(obj,
gInputWindowHandleClassInfo.frameLeft);
- mInfo->frameTop = env->GetIntField(obj,
+ mInfo.frameTop = env->GetIntField(obj,
gInputWindowHandleClassInfo.frameTop);
- mInfo->frameRight = env->GetIntField(obj,
+ mInfo.frameRight = env->GetIntField(obj,
gInputWindowHandleClassInfo.frameRight);
- mInfo->frameBottom = env->GetIntField(obj,
+ mInfo.frameBottom = env->GetIntField(obj,
gInputWindowHandleClassInfo.frameBottom);
- mInfo->scaleFactor = env->GetFloatField(obj,
+ mInfo.scaleFactor = env->GetFloatField(obj,
gInputWindowHandleClassInfo.scaleFactor);
jobject regionObj = env->GetObjectField(obj,
@@ -133,30 +129,30 @@
SkRegion* region = android_graphics_Region_getSkRegion(env, regionObj);
for (SkRegion::Iterator it(*region); !it.done(); it.next()) {
const SkIRect& rect = it.rect();
- mInfo->addTouchableRegion(Rect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom));
+ mInfo.addTouchableRegion(Rect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom));
}
env->DeleteLocalRef(regionObj);
}
- mInfo->visible = env->GetBooleanField(obj,
+ mInfo.visible = env->GetBooleanField(obj,
gInputWindowHandleClassInfo.visible);
- mInfo->canReceiveKeys = env->GetBooleanField(obj,
+ mInfo.canReceiveKeys = env->GetBooleanField(obj,
gInputWindowHandleClassInfo.canReceiveKeys);
- mInfo->hasFocus = env->GetBooleanField(obj,
+ mInfo.hasFocus = env->GetBooleanField(obj,
gInputWindowHandleClassInfo.hasFocus);
- mInfo->hasWallpaper = env->GetBooleanField(obj,
+ mInfo.hasWallpaper = env->GetBooleanField(obj,
gInputWindowHandleClassInfo.hasWallpaper);
- mInfo->paused = env->GetBooleanField(obj,
+ mInfo.paused = env->GetBooleanField(obj,
gInputWindowHandleClassInfo.paused);
- mInfo->layer = env->GetIntField(obj,
+ mInfo.layer = env->GetIntField(obj,
gInputWindowHandleClassInfo.layer);
- mInfo->ownerPid = env->GetIntField(obj,
+ mInfo.ownerPid = env->GetIntField(obj,
gInputWindowHandleClassInfo.ownerPid);
- mInfo->ownerUid = env->GetIntField(obj,
+ mInfo.ownerUid = env->GetIntField(obj,
gInputWindowHandleClassInfo.ownerUid);
- mInfo->inputFeatures = env->GetIntField(obj,
+ mInfo.inputFeatures = env->GetIntField(obj,
gInputWindowHandleClassInfo.inputFeatures);
- mInfo->displayId = env->GetIntField(obj,
+ mInfo.displayId = env->GetIntField(obj,
gInputWindowHandleClassInfo.displayId);
env->DeleteLocalRef(obj);
@@ -225,20 +221,20 @@
LOG_FATAL_IF(! (var), "Unable to find field " fieldName);
int register_android_server_InputWindowHandle(JNIEnv* env) {
- int res = jniRegisterNativeMethods(env, "com/android/server/input/InputWindowHandle",
+ int res = jniRegisterNativeMethods(env, "android/view/InputWindowHandle",
gInputWindowHandleMethods, NELEM(gInputWindowHandleMethods));
(void) res; // Faked use when LOG_NDEBUG.
LOG_FATAL_IF(res < 0, "Unable to register native methods.");
jclass clazz;
- FIND_CLASS(clazz, "com/android/server/input/InputWindowHandle");
+ FIND_CLASS(clazz, "android/view/InputWindowHandle");
GET_FIELD_ID(gInputWindowHandleClassInfo.ptr, clazz,
"ptr", "J");
GET_FIELD_ID(gInputWindowHandleClassInfo.inputApplicationHandle,
clazz,
- "inputApplicationHandle", "Lcom/android/server/input/InputApplicationHandle;");
+ "inputApplicationHandle", "Landroid/view/InputApplicationHandle;");
GET_FIELD_ID(gInputWindowHandleClassInfo.inputChannel, clazz,
"inputChannel", "Landroid/view/InputChannel;");
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index b2d44e7..7b564ae 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -24,9 +24,13 @@
#include <sys/system_properties.h>
#include <sys/types.h>
#include <sys/wait.h>
+#include <unistd.h>
#include <private/android_filesystem_config.h> // for AID_SYSTEM
+#include <sstream>
+#include <string>
+
#include "android-base/logging.h"
#include "android-base/properties.h"
#include "android-base/stringprintf.h"
@@ -38,6 +42,7 @@
#include "androidfw/AssetManager2.h"
#include "androidfw/AttributeResolution.h"
#include "androidfw/MutexGuard.h"
+#include "androidfw/PosixUtils.h"
#include "androidfw/ResourceTypes.h"
#include "core_jni_helpers.h"
#include "jni.h"
@@ -54,6 +59,7 @@
extern "C" int capset(cap_user_header_t hdrp, const cap_user_data_t datap);
using ::android::base::StringPrintf;
+using ::android::util::ExecuteBinary;
namespace android {
@@ -161,18 +167,20 @@
argv[argc++] = AssetManager::IDMAP_DIR;
// Directories to scan for overlays: if OVERLAY_THEME_DIR_PROPERTY is defined,
- // use OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in addition to OVERLAY_DIR.
+ // use VENDOR_OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in
+ // addition to VENDOR_OVERLAY_DIR.
std::string overlay_theme_path = base::GetProperty(AssetManager::OVERLAY_THEME_DIR_PROPERTY,
"");
if (!overlay_theme_path.empty()) {
- overlay_theme_path = std::string(AssetManager::OVERLAY_DIR) + "/" + overlay_theme_path;
+ overlay_theme_path =
+ std::string(AssetManager::VENDOR_OVERLAY_DIR) + "/" + overlay_theme_path;
if (stat(overlay_theme_path.c_str(), &st) == 0) {
argv[argc++] = overlay_theme_path.c_str();
}
}
- if (stat(AssetManager::OVERLAY_DIR, &st) == 0) {
- argv[argc++] = AssetManager::OVERLAY_DIR;
+ if (stat(AssetManager::VENDOR_OVERLAY_DIR, &st) == 0) {
+ argv[argc++] = AssetManager::VENDOR_OVERLAY_DIR;
}
if (stat(AssetManager::PRODUCT_OVERLAY_DIR, &st) == 0) {
@@ -200,6 +208,75 @@
}
}
+static jobjectArray NativeCreateIdmapsForStaticOverlaysTargetingAndroid(JNIEnv* env,
+ jclass /*clazz*/) {
+ // --input-directory can be given multiple times, but idmap2 expects the directory to exist
+ std::vector<std::string> input_dirs;
+ struct stat st;
+ if (stat(AssetManager::VENDOR_OVERLAY_DIR, &st) == 0) {
+ input_dirs.push_back(AssetManager::VENDOR_OVERLAY_DIR);
+ }
+
+ if (stat(AssetManager::PRODUCT_OVERLAY_DIR, &st) == 0) {
+ input_dirs.push_back(AssetManager::PRODUCT_OVERLAY_DIR);
+ }
+
+ if (stat(AssetManager::PRODUCT_SERVICES_OVERLAY_DIR, &st) == 0) {
+ input_dirs.push_back(AssetManager::PRODUCT_SERVICES_OVERLAY_DIR);
+ }
+
+ if (input_dirs.empty()) {
+ LOG(WARNING) << "no directories for idmap2 to scan";
+ return env->NewObjectArray(0, g_stringClass, nullptr);
+ }
+
+ std::vector<std::string> argv{"/system/bin/idmap2",
+ "scan",
+ "--recursive",
+ "--target-package-name", "android",
+ "--target-apk-path", "/system/framework/framework-res.apk",
+ "--output-directory", "/data/resource-cache"};
+
+ for (const auto& dir : input_dirs) {
+ argv.push_back("--input-directory");
+ argv.push_back(dir);
+ }
+
+ const auto result = ExecuteBinary(argv);
+
+ if (!result) {
+ LOG(ERROR) << "failed to execute idmap2";
+ return nullptr;
+ }
+
+ if (result->status != 0) {
+ LOG(ERROR) << "idmap2: " << result->stderr;
+ return nullptr;
+ }
+
+ std::vector<std::string> idmap_paths;
+ std::istringstream input(result->stdout);
+ std::string path;
+ while (std::getline(input, path)) {
+ idmap_paths.push_back(path);
+ }
+
+ jobjectArray array = env->NewObjectArray(idmap_paths.size(), g_stringClass, nullptr);
+ if (array == nullptr) {
+ return nullptr;
+ }
+ for (size_t i = 0; i < idmap_paths.size(); i++) {
+ const std::string path = idmap_paths[i];
+ jstring java_string = env->NewStringUTF(path.c_str());
+ if (env->ExceptionCheck()) {
+ return nullptr;
+ }
+ env->SetObjectArrayElement(array, i, java_string);
+ env->DeleteLocalRef(java_string);
+ }
+ return array;
+}
+
static jint CopyValue(JNIEnv* env, ApkAssetsCookie cookie, const Res_value& value, uint32_t ref,
uint32_t type_spec_flags, ResTable_config* config, jobject out_typed_value) {
env->SetIntField(out_typed_value, gTypedValueOffsets.mType, value.dataType);
@@ -1405,6 +1482,8 @@
// System/idmap related methods.
{"nativeVerifySystemIdmaps", "()V", (void*)NativeVerifySystemIdmaps},
+ {"nativeCreateIdmapsForStaticOverlaysTargetingAndroid", "()[Ljava/lang/String;",
+ (void*)NativeCreateIdmapsForStaticOverlaysTargetingAndroid},
// Global management/debug methods.
{"getGlobalAssetCount", "()I", (void*)NativeGetGlobalAssetCount},
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index fd042b3..4f8bbc1 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -904,19 +904,24 @@
return IPCThreadState::self()->getStrictModePolicy();
}
-static jint android_os_Binder_setThreadWorkSource(jint workSource)
+static jlong android_os_Binder_setCallingWorkSourceUid(jint workSource)
{
- return IPCThreadState::self()->setWorkSource(workSource);
+ return IPCThreadState::self()->setCallingWorkSourceUid(workSource);
}
-static jint android_os_Binder_getThreadWorkSource()
+static jlong android_os_Binder_getCallingWorkSourceUid()
{
- return IPCThreadState::self()->getWorkSource();
+ return IPCThreadState::self()->getCallingWorkSourceUid();
}
-static jint android_os_Binder_clearThreadWorkSource()
+static jlong android_os_Binder_clearCallingWorkSource()
{
- return IPCThreadState::self()->clearWorkSource();
+ return IPCThreadState::self()->clearCallingWorkSource();
+}
+
+static void android_os_Binder_restoreCallingWorkSource(long token)
+{
+ IPCThreadState::self()->restoreCallingWorkSource(token);
}
static void android_os_Binder_flushPendingCommands(JNIEnv* env, jobject clazz)
@@ -962,11 +967,12 @@
// @CriticalNative
{ "getThreadStrictModePolicy", "()I", (void*)android_os_Binder_getThreadStrictModePolicy },
// @CriticalNative
- { "setThreadWorkSource", "(I)I", (void*)android_os_Binder_setThreadWorkSource },
+ { "setCallingWorkSourceUid", "(I)J", (void*)android_os_Binder_setCallingWorkSourceUid },
// @CriticalNative
- { "getThreadWorkSource", "()I", (void*)android_os_Binder_getThreadWorkSource },
+ { "getCallingWorkSourceUid", "()I", (void*)android_os_Binder_getCallingWorkSourceUid },
// @CriticalNative
- { "clearThreadWorkSource", "()I", (void*)android_os_Binder_clearThreadWorkSource },
+ { "clearCallingWorkSource", "()J", (void*)android_os_Binder_clearCallingWorkSource },
+ { "restoreCallingWorkSource", "(J)V", (void*)android_os_Binder_restoreCallingWorkSource },
{ "flushPendingCommands", "()V", (void*)android_os_Binder_flushPendingCommands },
{ "getNativeBBinderHolder", "()J", (void*)android_os_Binder_getNativeBBinderHolder },
{ "getNativeFinalizer", "()J", (void*)android_os_Binder_getNativeFinalizer },
diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index 2f17907..fb6be6b 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -249,6 +249,24 @@
}
}
+static jobject android_view_InputChannel_nativeGetToken(JNIEnv* env, jobject obj) {
+ NativeInputChannel* nativeInputChannel =
+ android_view_InputChannel_getNativeInputChannel(env, obj);
+ if (nativeInputChannel) {
+ return javaObjectForIBinder(env, nativeInputChannel->getInputChannel()->getToken());
+ }
+ return 0;
+}
+
+static void android_view_InputChannel_nativeSetToken(JNIEnv* env, jobject obj, jobject tokenObj) {
+ NativeInputChannel* nativeInputChannel =
+ android_view_InputChannel_getNativeInputChannel(env, obj);
+ sp<IBinder> token = ibinderForJavaObject(env, tokenObj);
+ if (nativeInputChannel != nullptr) {
+ nativeInputChannel->getInputChannel()->setToken(token);
+ }
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gInputChannelMethods[] = {
@@ -267,6 +285,10 @@
(void*)android_view_InputChannel_nativeGetName },
{ "nativeDup", "(Landroid/view/InputChannel;)V",
(void*)android_view_InputChannel_nativeDup },
+ { "nativeGetToken", "()Landroid/os/IBinder;",
+ (void*)android_view_InputChannel_nativeGetToken },
+ { "nativeSetToken", "(Landroid/os/IBinder;)V",
+ (void*)android_view_InputChannel_nativeSetToken }
};
int register_android_view_InputChannel(JNIEnv* env) {
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 4eda3ab..ec9c860 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -19,6 +19,7 @@
#include "android_os_Parcel.h"
#include "android_util_Binder.h"
+#include "android_hardware_input_InputWindowHandle.h"
#include "android/graphics/Bitmap.h"
#include "android/graphics/GraphicsJNI.h"
#include "android/graphics/Region.h"
@@ -324,6 +325,18 @@
transaction->setAlpha(ctrl, alpha);
}
+static void nativeSetInputWindowInfo(JNIEnv* env, jclass clazz, jlong transactionObj,
+ jlong nativeObject, jobject inputWindow) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+ sp<NativeInputWindowHandle> handle = android_server_InputWindowHandle_getHandle(
+ env, inputWindow);
+ handle->updateInfo();
+
+ SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+ transaction->setInputWindowInfo(ctrl, *handle->getInfo());
+}
+
static void nativeSetColor(JNIEnv* env, jclass clazz, jlong transactionObj,
jlong nativeObject, jfloatArray fColor) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -930,6 +943,8 @@
(void*)nativeScreenshot },
{"nativeCaptureLayers", "(Landroid/os/IBinder;Landroid/graphics/Rect;F)Landroid/graphics/GraphicBuffer;",
(void*)nativeCaptureLayers },
+ {"nativeSetInputWindowInfo", "(JJLandroid/view/InputWindowHandle;)V",
+ (void*)nativeSetInputWindowInfo },
};
int register_android_view_SurfaceControl(JNIEnv* env)
diff --git a/core/jni/com_android_internal_os_ZygoteInit.cpp b/core/jni/com_android_internal_os_ZygoteInit.cpp
index 258a55c..ac0e600 100644
--- a/core/jni/com_android_internal_os_ZygoteInit.cpp
+++ b/core/jni/com_android_internal_os_ZygoteInit.cpp
@@ -16,21 +16,58 @@
#define LOG_TAG "Zygote"
+#include <EGL/egl.h>
#include <ui/GraphicBufferMapper.h>
#include "core_jni_helpers.h"
namespace {
+// Shadow call stack (SCS) is a security mitigation that uses a separate stack
+// (the SCS) for return addresses. In versions of Android newer than P, the
+// compiler cooperates with the system to ensure that the SCS address is always
+// stored in register x18, as long as the app was compiled with a new enough
+// compiler and does not use features that rely on SP-HALs (this restriction is
+// because the SP-HALs might not preserve x18 due to potentially having been
+// compiled with an old compiler as a consequence of Treble; it generally means
+// that the app must be a system app without a UI). This struct is used to
+// temporarily store the address on the stack while preloading the SP-HALs, so
+// that such apps can use the same zygote as everything else.
+struct ScopedSCSExit {
+#ifdef __aarch64__
+ void* scs;
+
+ ScopedSCSExit() {
+ __asm__ __volatile__("str x18, [%0]" ::"r"(&scs));
+ }
+
+ ~ScopedSCSExit() {
+ __asm__ __volatile__("ldr x18, [%0]; str xzr, [%0]" ::"r"(&scs));
+ }
+#else
+ // Silence unused variable warnings in non-SCS builds.
+ ScopedSCSExit() {}
+ ~ScopedSCSExit() {}
+#endif
+};
+
void android_internal_os_ZygoteInit_nativePreloadAppProcessHALs(JNIEnv* env, jclass) {
+ ScopedSCSExit x;
android::GraphicBufferMapper::preloadHal();
// Add preloading here for other HALs that are (a) always passthrough, and
// (b) loaded by most app processes.
}
+void android_internal_os_ZygoteInit_nativePreloadOpenGL(JNIEnv* env, jclass) {
+ ScopedSCSExit x;
+ eglGetDisplay(EGL_DEFAULT_DISPLAY);
+}
+
const JNINativeMethod gMethods[] = {
{ "nativePreloadAppProcessHALs", "()V",
(void*)android_internal_os_ZygoteInit_nativePreloadAppProcessHALs },
+ { "nativePreloadOpenGL", "()V",
+ (void*)android_internal_os_ZygoteInit_nativePreloadOpenGL },
};
} // anonymous namespace
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 2465759..a398e49 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -85,7 +85,7 @@
// See AssetManager.cpp for more details on overlay-subdir.
static const char* kOverlayDir = "/system/vendor/overlay/";
static const char* kVendorOverlayDir = "/vendor/overlay";
- static const char* kOverlaySubdir = "/system/vendor/overlay-subdir/";
+ static const char* kVendorOverlaySubdir = "/system/vendor/overlay-subdir/";
static const char* kSystemProductOverlayDir = "/system/product/overlay/";
static const char* kProductOverlayDir = "/product/overlay";
static const char* kSystemProductServicesOverlayDir = "/system/product_services/overlay/";
@@ -93,7 +93,7 @@
static const char* kApkSuffix = ".apk";
if ((android::base::StartsWith(path, kOverlayDir)
- || android::base::StartsWith(path, kOverlaySubdir)
+ || android::base::StartsWith(path, kVendorOverlaySubdir)
|| android::base::StartsWith(path, kVendorOverlayDir)
|| android::base::StartsWith(path, kSystemProductOverlayDir)
|| android::base::StartsWith(path, kProductOverlayDir)
diff --git a/core/proto/android/server/enums.proto b/core/proto/android/server/enums.proto
index ef02438..89f7010 100644
--- a/core/proto/android/server/enums.proto
+++ b/core/proto/android/server/enums.proto
@@ -28,3 +28,13 @@
// Device idle mode - active in full mode.
DEVICE_IDLE_MODE_DEEP = 2;
}
+
+enum ErrorSource {
+ ERROR_SOURCE_UNKNOWN = 0;
+ // Data app
+ DATA_APP = 1;
+ // System app
+ SYSTEM_APP = 2;
+ // System server.
+ SYSTEM_SERVER = 3;
+}
diff --git a/core/proto/android/server/location/enums.proto b/core/proto/android/server/location/enums.proto
new file mode 100644
index 0000000..b6dc589
--- /dev/null
+++ b/core/proto/android/server/location/enums.proto
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package android.server.location;
+
+option java_outer_classname = "ServerLocationProtoEnums";
+option java_multiple_files = true;
+
+// GPS Signal Quality levels,
+// primarily used by location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
+enum GpsSignalQualityEnum {
+ GPS_SIGNAL_QUALITY_UNKNOWN = -1;
+ GPS_SIGNAL_QUALITY_POOR = 0;
+ GPS_SIGNAL_QUALITY_GOOD = 1;
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2976879..533ce64 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -359,6 +359,7 @@
<protected-broadcast android:name="android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST" />
<protected-broadcast android:name="android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION" />
<protected-broadcast android:name="android.net.wifi.action.PASSPOINT_LAUNCH_OSU_VIEW" />
+ <protected-broadcast android:name="android.net.wifi.action.WIFI_NETWORK_SUGGESTION_POST_CONNECTION" />
<protected-broadcast android:name="android.net.wifi.supplicant.CONNECTION_CHANGE" />
<protected-broadcast android:name="android.net.wifi.supplicant.STATE_CHANGE" />
<protected-broadcast android:name="android.net.wifi.p2p.STATE_CHANGED" />
@@ -642,7 +643,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.READ_CONTACTS"
- android:permissionGroup="android.permission-group.CONTACTS"
android:label="@string/permlab_readContacts"
android:description="@string/permdesc_readContacts"
android:protectionLevel="dangerous" />
@@ -651,7 +651,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.WRITE_CONTACTS"
- android:permissionGroup="android.permission-group.CONTACTS"
android:label="@string/permlab_writeContacts"
android:description="@string/permdesc_writeContacts"
android:protectionLevel="dangerous" />
@@ -673,7 +672,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.READ_CALENDAR"
- android:permissionGroup="android.permission-group.CALENDAR"
android:label="@string/permlab_readCalendar"
android:description="@string/permdesc_readCalendar"
android:protectionLevel="dangerous" />
@@ -682,7 +680,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.WRITE_CALENDAR"
- android:permissionGroup="android.permission-group.CALENDAR"
android:label="@string/permlab_writeCalendar"
android:description="@string/permdesc_writeCalendar"
android:protectionLevel="dangerous" />
@@ -704,7 +701,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.SEND_SMS"
- android:permissionGroup="android.permission-group.SMS"
android:label="@string/permlab_sendSms"
android:description="@string/permdesc_sendSms"
android:permissionFlags="costsMoney"
@@ -714,7 +710,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.RECEIVE_SMS"
- android:permissionGroup="android.permission-group.SMS"
android:label="@string/permlab_receiveSms"
android:description="@string/permdesc_receiveSms"
android:protectionLevel="dangerous"/>
@@ -723,7 +718,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.READ_SMS"
- android:permissionGroup="android.permission-group.SMS"
android:label="@string/permlab_readSms"
android:description="@string/permdesc_readSms"
android:protectionLevel="dangerous" />
@@ -732,7 +726,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.RECEIVE_WAP_PUSH"
- android:permissionGroup="android.permission-group.SMS"
android:label="@string/permlab_receiveWapPush"
android:description="@string/permdesc_receiveWapPush"
android:protectionLevel="dangerous" />
@@ -741,12 +734,11 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.RECEIVE_MMS"
- android:permissionGroup="android.permission-group.SMS"
android:label="@string/permlab_receiveMms"
android:description="@string/permdesc_receiveMms"
android:protectionLevel="dangerous" />
- <!-- @TestApi Allows an application to read previously received cell broadcast
+ <!-- @SystemApi @TestApi Allows an application to read previously received cell broadcast
messages and to register a content observer to get notifications when
a cell broadcast has been received and added to the database. For
emergency alerts, the database is updated immediately after the
@@ -759,7 +751,6 @@
<p>Protection level: dangerous
@hide Pending API council approval -->
<permission android:name="android.permission.READ_CELL_BROADCASTS"
- android:permissionGroup="android.permission-group.SMS"
android:label="@string/permlab_readCellBroadcasts"
android:description="@string/permdesc_readCellBroadcasts"
android:protectionLevel="dangerous" />
@@ -801,7 +792,6 @@
@deprecated replaced by new strongly-typed permission groups in Q.
-->
<permission android:name="android.permission.READ_EXTERNAL_STORAGE"
- android:permissionGroup="android.permission-group.STORAGE"
android:label="@string/permlab_sdcardRead"
android:description="@string/permdesc_sdcardRead"
android:protectionLevel="normal" />
@@ -822,7 +812,6 @@
@deprecated replaced by new strongly-typed permission groups in Q.
-->
<permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
- android:permissionGroup="android.permission-group.STORAGE"
android:label="@string/permlab_sdcardWrite"
android:description="@string/permdesc_sdcardWrite"
android:protectionLevel="normal" />
@@ -838,14 +827,12 @@
<!-- Allows an application to read the user's shared audio collection. -->
<permission android:name="android.permission.READ_MEDIA_AUDIO"
- android:permissionGroup="android.permission-group.MEDIA_AURAL"
android:label="@string/permlab_audioRead"
android:description="@string/permdesc_audioRead"
android:protectionLevel="dangerous" />
<!-- Allows an application to modify the user's shared audio collection. -->
<permission android:name="android.permission.WRITE_MEDIA_AUDIO"
- android:permissionGroup="android.permission-group.MEDIA_AURAL"
android:label="@string/permlab_audioWrite"
android:description="@string/permdesc_audioWrite"
android:protectionLevel="dangerous" />
@@ -861,28 +848,24 @@
<!-- Allows an application to read the user's shared images collection. -->
<permission android:name="android.permission.READ_MEDIA_IMAGES"
- android:permissionGroup="android.permission-group.MEDIA_VISUAL"
android:label="@string/permlab_imagesRead"
android:description="@string/permdesc_imagesRead"
android:protectionLevel="dangerous" />
<!-- Allows an application to modify the user's shared images collection. -->
<permission android:name="android.permission.WRITE_MEDIA_IMAGES"
- android:permissionGroup="android.permission-group.MEDIA_VISUAL"
android:label="@string/permlab_imagesWrite"
android:description="@string/permdesc_imagesWrite"
android:protectionLevel="dangerous" />
<!-- Allows an application to read the user's shared video collection. -->
<permission android:name="android.permission.READ_MEDIA_VIDEO"
- android:permissionGroup="android.permission-group.MEDIA_VISUAL"
android:label="@string/permlab_videoRead"
android:description="@string/permdesc_videoRead"
android:protectionLevel="dangerous" />
<!-- Allows an application to modify the user's shared video collection. -->
<permission android:name="android.permission.WRITE_MEDIA_VIDEO"
- android:permissionGroup="android.permission-group.MEDIA_VISUAL"
android:label="@string/permlab_videoWrite"
android:description="@string/permdesc_videoWrite"
android:protectionLevel="dangerous" />
@@ -890,7 +873,6 @@
<!-- Allows an application to access any geographic locations persisted in the
user's shared collection. -->
<permission android:name="android.permission.ACCESS_MEDIA_LOCATION"
- android:permissionGroup="android.permission-group.MEDIA_VISUAL"
android:label="@string/permlab_mediaLocation"
android:description="@string/permdesc_mediaLocation"
android:protectionLevel="dangerous" />
@@ -921,7 +903,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.ACCESS_FINE_LOCATION"
- android:permissionGroup="android.permission-group.LOCATION"
android:label="@string/permlab_accessFineLocation"
android:description="@string/permdesc_accessFineLocation"
android:backgroundPermission="android.permission.ACCESS_BACKGROUND_LOCATION"
@@ -932,7 +913,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.ACCESS_COARSE_LOCATION"
- android:permissionGroup="android.permission-group.LOCATION"
android:label="@string/permlab_accessCoarseLocation"
android:description="@string/permdesc_accessCoarseLocation"
android:backgroundPermission="android.permission.ACCESS_BACKGROUND_LOCATION"
@@ -945,7 +925,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"
- android:permissionGroup="android.permission-group.LOCATION"
android:label="@string/permlab_accessBackgroundLocation"
android:description="@string/permdesc_accessBackgroundLocation"
android:protectionLevel="dangerous|instant" />
@@ -986,7 +965,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.READ_CALL_LOG"
- android:permissionGroup="android.permission-group.CALL_LOG"
android:label="@string/permlab_readCallLog"
android:description="@string/permdesc_readCallLog"
android:protectionLevel="dangerous" />
@@ -1005,7 +983,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.WRITE_CALL_LOG"
- android:permissionGroup="android.permission-group.CALL_LOG"
android:label="@string/permlab_writeCallLog"
android:description="@string/permdesc_writeCallLog"
android:protectionLevel="dangerous" />
@@ -1016,7 +993,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.PROCESS_OUTGOING_CALLS"
- android:permissionGroup="android.permission-group.CALL_LOG"
android:label="@string/permlab_processOutgoingCalls"
android:description="@string/permdesc_processOutgoingCalls"
android:protectionLevel="dangerous" />
@@ -1048,7 +1024,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.READ_PHONE_STATE"
- android:permissionGroup="android.permission-group.PHONE"
android:label="@string/permlab_readPhoneState"
android:description="@string/permdesc_readPhoneState"
android:protectionLevel="dangerous" />
@@ -1057,7 +1032,6 @@
granted by {@link #READ_PHONE_STATE} but is exposed to instant applications.
<p>Protection level: dangerous-->
<permission android:name="android.permission.READ_PHONE_NUMBERS"
- android:permissionGroup="android.permission-group.PHONE"
android:label="@string/permlab_readPhoneNumbers"
android:description="@string/permdesc_readPhoneNumbers"
android:protectionLevel="dangerous|instant" />
@@ -1067,7 +1041,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.CALL_PHONE"
- android:permissionGroup="android.permission-group.PHONE"
android:permissionFlags="costsMoney"
android:label="@string/permlab_callPhone"
android:description="@string/permdesc_callPhone"
@@ -1077,7 +1050,6 @@
<p>Protection level: dangerous
-->
<permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL"
- android:permissionGroup="android.permission-group.PHONE"
android:label="@string/permlab_addVoicemail"
android:description="@string/permdesc_addVoicemail"
android:protectionLevel="dangerous" />
@@ -1086,7 +1058,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.USE_SIP"
- android:permissionGroup="android.permission-group.PHONE"
android:description="@string/permdesc_use_sip"
android:label="@string/permlab_use_sip"
android:protectionLevel="dangerous"/>
@@ -1095,7 +1066,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.ANSWER_PHONE_CALLS"
- android:permissionGroup="android.permission-group.PHONE"
android:label="@string/permlab_answerPhoneCalls"
android:description="@string/permdesc_answerPhoneCalls"
android:protectionLevel="dangerous|runtime" />
@@ -1123,7 +1093,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.ACCEPT_HANDOVER"
- android:permissionGroup="android.permission-group.PHONE"
android.label="@string/permlab_acceptHandover"
android:description="@string/permdesc_acceptHandovers"
android:protectionLevel="dangerous" />
@@ -1147,7 +1116,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.RECORD_AUDIO"
- android:permissionGroup="android.permission-group.MICROPHONE"
android:label="@string/permlab_recordAudio"
android:description="@string/permdesc_recordAudio"
android:protectionLevel="dangerous|instant"/>
@@ -1169,7 +1137,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.ACTIVITY_RECOGNITION"
- android:permissionGroup="android.permission-group.ACTIVITY_RECOGNITION"
android:label="@string/permlab_activityRecognition"
android:description="@string/permdesc_activityRecognition"
android:protectionLevel="dangerous|instant" />
@@ -1218,7 +1185,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.CAMERA"
- android:permissionGroup="android.permission-group.CAMERA"
android:label="@string/permlab_camera"
android:description="@string/permdesc_camera"
android:protectionLevel="dangerous|instant" />
@@ -1242,7 +1208,6 @@
measure what is happening inside his/her body, such as heart rate.
<p>Protection level: dangerous -->
<permission android:name="android.permission.BODY_SENSORS"
- android:permissionGroup="android.permission-group.SENSORS"
android:label="@string/permlab_bodySensors"
android:description="@string/permdesc_bodySensors"
android:protectionLevel="dangerous" />
@@ -1576,6 +1541,14 @@
<permission android:name="android.permission.NETWORK_SETUP_WIZARD"
android:protectionLevel="signature|setup" />
+ <!-- Allows Managed Provisioning to call methods in Networking services
+ <p>Not for use by any other third-party or privileged applications.
+ @SystemApi
+ @hide This should only be used by ManagedProvisioning app.
+ -->
+ <permission android:name="android.permission.NETWORK_MANAGED_PROVISIONING"
+ android:protectionLevel="signature|privileged" />
+
<!-- #SystemApi @hide Allows applications to access information about LoWPAN interfaces.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.ACCESS_LOWPAN_STATE"
@@ -1721,7 +1694,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.GET_ACCOUNTS"
- android:permissionGroup="android.permission-group.CONTACTS"
android:protectionLevel="dangerous"
android:description="@string/permdesc_getAccounts"
android:label="@string/permlab_getAccounts" />
@@ -3851,6 +3823,11 @@
<permission android:name="android.permission.CONTROL_KEYGUARD"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows an application to control keyguard features like secure notifications.
+ @hide -->
+ <permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"
+ android:protectionLevel="signature|privileged" />
+
<!-- Allows an application to listen to trust changes. Only allowed for system processes.
@hide -->
<permission android:name="android.permission.TRUST_LISTENER"
@@ -4226,6 +4203,10 @@
<permission android:name="android.permission.MANAGE_ACCESSIBILITY"
android:protectionLevel="signature|setup" />
+ <!-- Allows financial apps to read filtered sms messages. -->
+ <permission android:name="android.permission.SMS_FINANCIAL_TRANSACTIONS"
+ android:protectionLevel="signature|appop" />
+
<application android:process="system"
android:persistent="true"
android:hasCode="false"
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index 4ee9731..433ae39 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -116,17 +116,6 @@
android:contentDescription="@string/expand_button_content_description_collapsed"
/>
<ImageView
- android:id="@+id/profile_badge"
- android:layout_width="@dimen/notification_badge_size"
- android:layout_height="@dimen/notification_badge_size"
- android:layout_gravity="center"
- android:layout_marginStart="4dp"
- android:paddingTop="1dp"
- android:scaleType="fitCenter"
- android:visibility="gone"
- android:contentDescription="@string/notification_work_profile_content_description"
- />
- <ImageView
android:id="@+id/alerted_icon"
android:layout_width="@dimen/notification_alerted_size"
android:layout_height="@dimen/notification_alerted_size"
@@ -138,6 +127,17 @@
android:contentDescription="@string/notification_alerted_content_description"
android:src="@drawable/ic_notifications_alerted"
android:tint="@color/notification_secondary_text_color_light"
+ />
+ <ImageView
+ android:id="@+id/profile_badge"
+ android:layout_width="@dimen/notification_badge_size"
+ android:layout_height="@dimen/notification_badge_size"
+ android:layout_gravity="center"
+ android:layout_marginStart="4dp"
+ android:paddingTop="1dp"
+ android:scaleType="fitCenter"
+ android:visibility="gone"
+ android:contentDescription="@string/notification_work_profile_content_description"
/>
<LinearLayout
android:id="@+id/app_ops"
diff --git a/core/res/res/values-night/themes_device_defaults.xml b/core/res/res/values-night/themes_device_defaults.xml
index 931674a..84c6446 100644
--- a/core/res/res/values-night/themes_device_defaults.xml
+++ b/core/res/res/values-night/themes_device_defaults.xml
@@ -52,6 +52,9 @@
<!-- DeviceDefault theme for a window that should look like the Settings app. -->
<style name="Theme.DeviceDefault.Settings" parent="Theme.DeviceDefault">
<item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
+ <item name="colorBackground">@color/primary_dark_device_default_settings</item>
+
+ <item name="listDivider">@color/list_divider_color_dark</item>
</style>
<!-- Theme for the dialog shown when an app crashes or ANRs. -->
diff --git a/core/res/res/values/colors_car.xml b/core/res/res/values/colors_car.xml
index 32671ac8..ea7c009 100644
--- a/core/res/res/values/colors_car.xml
+++ b/core/res/res/values/colors_car.xml
@@ -284,4 +284,8 @@
<color name="car_red_500a">#ffd50000</color>
<color name="car_red_a700">#ffd50000</color>
+
+ <color name="car_keyboard_divider_line">#38ffffff</color>
+ <color name="car_keyboard_text_primary_color">@color/car_grey_50</color>
+ <color name="car_keyboard_text_secondary_color">#8af8f9fa</color>
</resources>
diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml
index 0fe80a1..ded916f 100644
--- a/core/res/res/values/colors_device_defaults.xml
+++ b/core/res/res/values/colors_device_defaults.xml
@@ -42,4 +42,7 @@
<!-- Error color -->
<color name="error_color_device_default_dark">@color/error_color_material_dark</color>
<color name="error_color_device_default_light">@color/error_color_material_light</color>
+
+ <color name="list_divider_color_light">#64000000</color>
+ <color name="list_divider_color_dark">#85ffffff</color>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 26f3370..829d6f5 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -55,6 +55,8 @@
<item><xliff:g id="id">@string/status_bar_hotspot</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_mobile</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_airplane</xliff:g></item>
+ <item><xliff:g id="id">@string/status_bar_microphone</xliff:g></item>
+ <item><xliff:g id="id">@string/status_bar_camera</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_battery</xliff:g></item>
</string-array>
@@ -87,6 +89,8 @@
<string translatable="false" name="status_bar_mobile">mobile</string>
<string translatable="false" name="status_bar_vpn">vpn</string>
<string translatable="false" name="status_bar_ethernet">ethernet</string>
+ <string translatable="false" name="status_bar_microphone">microphone</string>
+ <string translatable="false" name="status_bar_camera">camera</string>
<string translatable="false" name="status_bar_airplane">airplane</string>
<!-- Flag indicating whether the surface flinger has limited
diff --git a/core/res/res/values/dimens_car.xml b/core/res/res/values/dimens_car.xml
index c1ca33e..5014a29 100644
--- a/core/res/res/values/dimens_car.xml
+++ b/core/res/res/values/dimens_car.xml
@@ -34,7 +34,6 @@
<!-- The diff between keyline 1 and keyline 3. -->
<dimen name="car_keyline_1_keyline_3_diff">88dp</dimen>
<dimen name="car_dialog_action_bar_height">@dimen/car_card_action_bar_height</dimen>
- <dimen name="car_primary_icon_size">44dp</dimen>
<!-- Text size for car -->
<dimen name="car_title_size">32sp</dimen>
@@ -56,16 +55,19 @@
<!-- Common icon size for car app -->
<dimen name="car_icon_size">56dp</dimen>
+ <dimen name="car_primary_icon_size">44dp</dimen>
+ <dimen name="car_secondary_icon_size">36dp</dimen>
- <dimen name="car_card_header_height">96dp</dimen>
- <dimen name="car_card_action_bar_height">96dp</dimen>
+ <dimen name="car_card_header_height">76dp</dimen>
+ <dimen name="car_card_action_bar_height">76dp</dimen>
<!-- Paddings -->
- <dimen name="car_padding_1">4dp</dimen>
- <dimen name="car_padding_2">10dp</dimen>
- <dimen name="car_padding_3">16dp</dimen>
- <dimen name="car_padding_4">28dp</dimen>
- <dimen name="car_padding_5">32dp</dimen>
+ <dimen name="car_padding_0">4dp</dimen>
+ <dimen name="car_padding_1">8dp</dimen>
+ <dimen name="car_padding_2">16dp</dimen>
+ <dimen name="car_padding_3">28dp</dimen>
+ <dimen name="car_padding_4">32dp</dimen>
+ <dimen name="car_padding_5">64dp</dimen>
<!-- Radius -->
<dimen name="car_radius_1">4dp</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 69c4abc..b25e7a8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2810,6 +2810,8 @@
<java-symbol type="string" name="status_bar_mobile" />
<java-symbol type="string" name="status_bar_ethernet" />
<java-symbol type="string" name="status_bar_vpn" />
+ <java-symbol type="string" name="status_bar_microphone" />
+ <java-symbol type="string" name="status_bar_camera" />
<!-- Locale picker -->
<java-symbol type="id" name="locale_search_menu" />
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 3385527..fa009bd 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -1475,6 +1475,8 @@
<!-- Toolbar attributes -->
<item name="toolbarStyle">@style/Widget.DeviceDefault.Toolbar</item>
+
+ <item name="listDivider">@color/list_divider_color_light</item>
</style>
<!-- @hide DeviceDefault theme for a window that should use Settings theme colors
diff --git a/core/tests/coretests/src/android/os/BinderTest.java b/core/tests/coretests/src/android/os/BinderTest.java
index 1beb598..534c5cd 100644
--- a/core/tests/coretests/src/android/os/BinderTest.java
+++ b/core/tests/coretests/src/android/os/BinderTest.java
@@ -21,17 +21,26 @@
import junit.framework.TestCase;
public class BinderTest extends TestCase {
+ private static final int UID = 100;
@SmallTest
public void testSetWorkSource() throws Exception {
- Binder.setThreadWorkSource(100);
- assertEquals(100, Binder.getThreadWorkSource());
+ Binder.setCallingWorkSourceUid(UID);
+ assertEquals(UID, Binder.getCallingWorkSourceUid());
}
@SmallTest
public void testClearWorkSource() throws Exception {
- Binder.setThreadWorkSource(100);
- Binder.clearThreadWorkSource();
- assertEquals(-1, Binder.getThreadWorkSource());
+ Binder.setCallingWorkSourceUid(UID);
+ Binder.clearCallingWorkSource();
+ assertEquals(-1, Binder.getCallingWorkSourceUid());
+ }
+
+ @SmallTest
+ public void testRestoreWorkSource() throws Exception {
+ Binder.setCallingWorkSourceUid(UID);
+ long token = Binder.clearCallingWorkSource();
+ Binder.restoreCallingWorkSource(token);
+ assertEquals(UID, Binder.getCallingWorkSourceUid());
}
}
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 8c91c37..002b6a8 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -494,6 +494,7 @@
Settings.Global.WIFI_IS_UNUSABLE_EVENT_METRICS_ENABLED,
Settings.Global.WIFI_LINK_SPEED_METRICS_ENABLED,
Settings.Global.WIFI_PNO_FREQUENCY_CULLING_ENABLED,
+ Settings.Global.WIFI_PNO_RECENCY_SORTING_ENABLED,
Settings.Global.WIFI_MAX_DHCP_RETRY_COUNT,
Settings.Global.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS,
Settings.Global.WIFI_NETWORK_SHOW_RSSI,
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
new file mode 100644
index 0000000..1c2df2c
--- /dev/null
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class WindowInsetsTest {
+
+ @Test
+ public void systemWindowInsets_afterConsuming_isConsumed() {
+ assertTrue(new WindowInsets(new Rect(1, 2, 3, 4), null, null, false, false, null)
+ .consumeSystemWindowInsets().isConsumed());
+ }
+
+ @Test
+ public void multiNullConstructor_isConsumed() {
+ assertTrue(new WindowInsets(null, null, null, false, false, null).isConsumed());
+ }
+
+ @Test
+ public void singleNullConstructor_isConsumed() {
+ assertTrue(new WindowInsets((Rect) null).isConsumed());
+ }
+
+}
diff --git a/core/tests/coretests/src/android/view/textclassifier/ModelFileManagerTest.java b/core/tests/coretests/src/android/view/textclassifier/ModelFileManagerTest.java
index f2efabf..88d162b 100644
--- a/core/tests/coretests/src/android/view/textclassifier/ModelFileManagerTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/ModelFileManagerTest.java
@@ -203,6 +203,28 @@
}
@Test
+ public void findBestModel_languageIsMoreImportantThanVersion_bestModelComesFirst() {
+ ModelFileManager.ModelFile matchLocaleModel =
+ new ModelFileManager.ModelFile(
+ new File("/path/b"), 1,
+ Collections.singletonList(Locale.forLanguageTag("ja")), false);
+
+ ModelFileManager.ModelFile languageIndependentModel =
+ new ModelFileManager.ModelFile(
+ new File("/path/a"), 2,
+ Collections.emptyList(), true);
+ when(mModelFileSupplier.get())
+ .thenReturn(
+ Arrays.asList(matchLocaleModel, languageIndependentModel));
+
+ ModelFileManager.ModelFile bestModelFile =
+ mModelFileManager.findBestModelFile(
+ LocaleList.forLanguageTags("ja"));
+
+ assertThat(bestModelFile).isEqualTo(matchLocaleModel);
+ }
+
+ @Test
public void modelFileEquals() {
ModelFileManager.ModelFile modelA =
new ModelFileManager.ModelFile(
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
index e81f678..7467114 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
@@ -41,6 +41,7 @@
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.text.format.DateUtils;
+import android.util.StatsLog;
import junit.framework.TestCase;
@@ -258,6 +259,36 @@
assertThat(time).isEqualTo(TIME_STATE_FOREGROUND_MS);
}
+ @Test
+ public void testDrainTypesSyncedWithProto() {
+ assertEquals(BatterySipper.DrainType.AMBIENT_DISPLAY.ordinal(),
+ StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__AMBIENT_DISPLAY);
+ // AtomsProto has no "APP"
+ assertEquals(BatterySipper.DrainType.BLUETOOTH.ordinal(),
+ StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__BLUETOOTH);
+ assertEquals(BatterySipper.DrainType.CAMERA.ordinal(),
+ StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__CAMERA);
+ assertEquals(BatterySipper.DrainType.CELL.ordinal(),
+ StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__CELL);
+ assertEquals(BatterySipper.DrainType.FLASHLIGHT.ordinal(),
+ StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__FLASHLIGHT);
+ assertEquals(BatterySipper.DrainType.IDLE.ordinal(),
+ StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__IDLE);
+ assertEquals(BatterySipper.DrainType.MEMORY.ordinal(),
+ StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__MEMORY);
+ assertEquals(BatterySipper.DrainType.OVERCOUNTED.ordinal(),
+ StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__OVERCOUNTED);
+ assertEquals(BatterySipper.DrainType.PHONE.ordinal(),
+ StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__PHONE);
+ assertEquals(BatterySipper.DrainType.SCREEN.ordinal(),
+ StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__SCREEN);
+ assertEquals(BatterySipper.DrainType.UNACCOUNTED.ordinal(),
+ StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__UNACCOUNTED);
+ // AtomsProto has no "USER"
+ assertEquals(BatterySipper.DrainType.WIFI.ordinal(),
+ StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER__DRAIN_TYPE__WIFI);
+ }
+
private BatterySipper createTestSmearBatterySipper(long activityTime, double totalPowerMah,
int uidCode, boolean isUidNull) {
final BatterySipper sipper = mock(BatterySipper.class);
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
index b9ef434..c866bc4 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
@@ -16,6 +16,7 @@
package com.android.internal.os;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -139,4 +140,133 @@
assertEquals(threadCount, THREAD_IDS.length);
}
+
+ @Test
+ public void testBucketSetup_simple() {
+ long[] frequencies = {1, 2, 3, 4, 1, 2, 3, 4};
+ KernelCpuThreadReader.FrequencyBucketCreator
+ frequencyBucketCreator = new KernelCpuThreadReader.FrequencyBucketCreator(
+ frequencies, 4);
+ assertArrayEquals(
+ new int[]{1, 3, 1, 3},
+ frequencyBucketCreator.getBucketMinFrequencies(frequencies));
+ assertArrayEquals(
+ new int[]{2, 2, 2, 2},
+ frequencyBucketCreator.getBucketedValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
+ }
+
+ @Test
+ public void testBucketSetup_noBig() {
+ long[] frequencies = {1, 2, 3, 4, 5, 6, 7, 8};
+ KernelCpuThreadReader.FrequencyBucketCreator
+ frequencyBucketCreator = new KernelCpuThreadReader.FrequencyBucketCreator(
+ frequencies, 4);
+ assertArrayEquals(
+ new int[]{1, 3, 5, 7},
+ frequencyBucketCreator.getBucketMinFrequencies(frequencies));
+ assertArrayEquals(
+ new int[]{2, 2, 2, 2},
+ frequencyBucketCreator.getBucketedValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
+ }
+
+ @Test
+ public void testBucketSetup_moreLittle() {
+ long[] frequencies = {1, 2, 3, 4, 5, 1, 2, 3};
+ KernelCpuThreadReader.FrequencyBucketCreator
+ frequencyBucketCreator = new KernelCpuThreadReader.FrequencyBucketCreator(
+ frequencies, 4);
+ assertArrayEquals(
+ new int[]{1, 3, 1, 2},
+ frequencyBucketCreator.getBucketMinFrequencies(frequencies));
+ assertArrayEquals(
+ new int[]{2, 3, 1, 2},
+ frequencyBucketCreator.getBucketedValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
+ }
+
+ @Test
+ public void testBucketSetup_moreBig() {
+ long[] frequencies = {1, 2, 3, 1, 2, 3, 4, 5};
+ KernelCpuThreadReader.FrequencyBucketCreator
+ frequencyBucketCreator = new KernelCpuThreadReader.FrequencyBucketCreator(
+ frequencies, 4);
+ assertArrayEquals(
+ new int[]{1, 2, 1, 3},
+ frequencyBucketCreator.getBucketMinFrequencies(frequencies));
+ assertArrayEquals(
+ new int[]{1, 2, 2, 3},
+ frequencyBucketCreator.getBucketedValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
+ }
+
+ @Test
+ public void testBucketSetup_equalBuckets() {
+ long[] frequencies = {1, 2, 3, 4, 1, 2, 3, 4};
+ KernelCpuThreadReader.FrequencyBucketCreator
+ frequencyBucketCreator = new KernelCpuThreadReader.FrequencyBucketCreator(
+ frequencies, 8);
+ assertArrayEquals(
+ new int[]{1, 2, 3, 4, 1, 2, 3, 4},
+ frequencyBucketCreator.getBucketMinFrequencies(frequencies));
+ assertArrayEquals(
+ new int[]{1, 1, 1, 1, 1, 1, 1, 1},
+ frequencyBucketCreator.getBucketedValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
+ }
+
+ @Test
+ public void testBucketSetup_moreBigBucketsThanFrequencies() {
+ long[] frequencies = {1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3};
+ KernelCpuThreadReader.FrequencyBucketCreator
+ frequencyBucketCreator = new KernelCpuThreadReader.FrequencyBucketCreator(
+ frequencies, 8);
+ assertArrayEquals(
+ new int[]{1, 3, 5, 7, 1, 2, 3},
+ frequencyBucketCreator.getBucketMinFrequencies(frequencies));
+ assertArrayEquals(
+ new int[]{2, 2, 2, 3, 1, 1, 1},
+ frequencyBucketCreator.getBucketedValues(
+ new long[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}));
+ }
+
+ @Test
+ public void testBucketSetup_oneBucket() {
+ long[] frequencies = {1, 2, 3, 4, 2, 3, 4, 5};
+ KernelCpuThreadReader.FrequencyBucketCreator
+ frequencyBucketCreator = new KernelCpuThreadReader.FrequencyBucketCreator(
+ frequencies, 1);
+ assertArrayEquals(
+ new int[]{1},
+ frequencyBucketCreator.getBucketMinFrequencies(frequencies));
+ assertArrayEquals(
+ new int[]{8},
+ frequencyBucketCreator.getBucketedValues(
+ new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
+ }
+
+
+ @Test
+ public void testGetBigFrequenciesStartIndex_simple() {
+ assertEquals(
+ 3, KernelCpuThreadReader.FrequencyBucketCreator.getBigFrequenciesStartIndex(
+ new long[]{1, 2, 3, 1, 2, 3}));
+ }
+
+ @Test
+ public void testGetBigFrequenciesStartIndex_moreLittle() {
+ assertEquals(
+ 4, KernelCpuThreadReader.FrequencyBucketCreator.getBigFrequenciesStartIndex(
+ new long[]{1, 2, 3, 4, 1, 2}));
+ }
+
+ @Test
+ public void testGetBigFrequenciesStartIndex_moreBig() {
+ assertEquals(
+ 2, KernelCpuThreadReader.FrequencyBucketCreator.getBigFrequenciesStartIndex(
+ new long[]{1, 2, 1, 2, 3, 4}));
+ }
+
+ @Test
+ public void testGetBigFrequenciesStartIndex_noBig() {
+ assertEquals(
+ 4, KernelCpuThreadReader.FrequencyBucketCreator.getBigFrequenciesStartIndex(
+ new long[]{1, 2, 3, 4}));
+ }
}
diff --git a/core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java b/core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java
index 01382aa..4e0f2a8 100644
--- a/core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java
+++ b/core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java
@@ -81,7 +81,7 @@
@Test
public void testSharedInstalledPrimary() throws Exception {
- assertEquals("1001", getContent(getKernelPackageFile("shared:android.uid.phone", "appid")));
+ assertEquals("1001", getContent(getKernelPackageFile("shared-android.uid.phone", "appid")));
}
@Test
@@ -92,7 +92,7 @@
@Test
public void testSharedInstalledAll() throws Exception {
- assertEquals("", getContent(getKernelPackageFile("shared:android.uid.phone",
+ assertEquals("", getContent(getKernelPackageFile("shared-android.uid.phone",
"excluded_userids")));
}
diff --git a/data/etc/Android.mk b/data/etc/Android.mk
index 936ad22..d24c140 100644
--- a/data/etc/Android.mk
+++ b/data/etc/Android.mk
@@ -47,3 +47,11 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/sysconfig
LOCAL_SRC_FILES := $(LOCAL_MODULE)
include $(BUILD_PREBUILT)
+
+########################
+include $(CLEAR_VARS)
+LOCAL_MODULE := com.android.timezone.updater.xml
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_RELATIVE_PATH := permissions
+LOCAL_SRC_FILES := $(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
diff --git a/data/etc/com.android.timezone.updater.xml b/data/etc/com.android.timezone.updater.xml
new file mode 100644
index 0000000..60a66e2
--- /dev/null
+++ b/data/etc/com.android.timezone.updater.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<permissions>
+ <privapp-permissions package="com.android.timezone.updater">
+ <permission name="android.permission.QUERY_TIME_ZONE_RULES" />
+ <permission name="android.permission.UPDATE_TIME_ZONE_RULES" />
+ </privapp-permissions>
+</permissions>
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 68f24fb..a4c5ed2 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -173,6 +173,7 @@
<assign-permission name="android.permission.ACCESS_LOWPAN_STATE" uid="lowpan" />
<assign-permission name="android.permission.MANAGE_LOWPAN_INTERFACES" uid="lowpan" />
+ <assign-permission name="android.permission.BATTERY_STATS" uid="statsd" />
<assign-permission name="android.permission.DUMP" uid="statsd" />
<assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="statsd" />
<assign-permission name="android.permission.STATSCOMPANION" uid="statsd" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index c7945bd02..9e4ea32 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -96,6 +96,7 @@
<permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
<permission name="android.permission.MANAGE_USERS"/>
<permission name="android.permission.MASTER_CLEAR"/>
+ <permission name="android.permission.NETWORK_MANAGED_PROVISIONING"/>
<permission name="android.permission.PERFORM_CDMA_PROVISIONING"/>
<permission name="android.permission.SET_TIME"/>
<permission name="android.permission.SET_TIME_ZONE"/>
@@ -194,6 +195,8 @@
<privapp-permissions package="com.android.providers.calendar">
<permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/>
+ <permission name="android.permission.INTERACT_ACROSS_USERS" />
+ <permission name="android.permission.MANAGE_USERS" />
<permission name="android.permission.UPDATE_APP_OPS_STATS"/>
<permission name="android.permission.USE_RESERVED_DISK"/>
</privapp-permissions>
@@ -372,6 +375,7 @@
<permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/>
<permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
<permission name="android.permission.CONNECTIVITY_INTERNAL"/>
+ <permission name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"/>
<permission name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"/>
<permission name="android.permission.CONTROL_VPN"/>
<permission name="android.permission.DUMP"/>
diff --git a/graphics/java/android/graphics/RecordingCanvas.java b/graphics/java/android/graphics/RecordingCanvas.java
index fd5d624..67ad404 100644
--- a/graphics/java/android/graphics/RecordingCanvas.java
+++ b/graphics/java/android/graphics/RecordingCanvas.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.Pools.SynchronizedPool;
+import android.view.DisplayListCanvas;
import android.view.TextureLayer;
import dalvik.annotation.optimization.CriticalNative;
@@ -34,7 +35,7 @@
* {@link RenderNode#endRecording()} is called. It must not be retained beyond that as it is
* internally reused.
*/
-public final class RecordingCanvas extends BaseRecordingCanvas {
+public final class RecordingCanvas extends DisplayListCanvas {
// The recording canvas pool should be large enough to handle a deeply nested
// view hierarchy because display lists are generated recursively.
private static final int POOL_LIMIT = 25;
@@ -89,7 +90,8 @@
// Constructors
///////////////////////////////////////////////////////////////////////////
- private RecordingCanvas(@NonNull RenderNode node, int width, int height) {
+ /** @hide */
+ protected RecordingCanvas(@NonNull RenderNode node, int width, int height) {
super(nCreateDisplayListCanvas(node.mNativeRenderNode, width, height));
mDensity = 0; // disable bitmap density scaling
}
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index c10e482..6d58d95 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -23,6 +23,7 @@
import android.app.KeyguardManager;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Binder;
import android.os.IBinder;
@@ -38,11 +39,13 @@
import android.security.keymaster.KeymasterCertificateChain;
import android.security.keymaster.KeymasterDefs;
import android.security.keymaster.OperationResult;
+import android.security.keystore.IKeystoreService;
import android.security.keystore.KeyExpiredException;
import android.security.keystore.KeyNotYetValidException;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
+import android.security.keystore.KeystoreResponse;
import android.security.keystore.StrongBoxUnavailableException;
import android.security.keystore.UserNotAuthenticatedException;
import android.util.Log;
@@ -52,8 +55,12 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
+import java.util.Arrays;
+import java.util.Date;
import java.util.List;
import java.util.Locale;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
import sun.security.util.ObjectIdentifier;
import sun.security.x509.AlgorithmId;
@@ -303,6 +310,31 @@
}
}
+ /**
+ * List uids of all keys that are auth bound to the current user.
+ * Only system is allowed to call this method.
+ */
+ @UnsupportedAppUsage
+ public int[] listUidsOfAuthBoundKeys() {
+ final int MAX_RESULT_SIZE = 100;
+ int[] uidsOut = new int[MAX_RESULT_SIZE];
+ try {
+ int rc = mBinder.listUidsOfAuthBoundKeys(uidsOut);
+ if (rc != NO_ERROR) {
+ Log.w(TAG, String.format("listUidsOfAuthBoundKeys failed with error code %d", rc));
+ return null;
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ return null;
+ } catch (android.os.ServiceSpecificException e) {
+ Log.w(TAG, "KeyStore exception", e);
+ return null;
+ }
+ // Remove any 0 entries
+ return Arrays.stream(uidsOut).filter(x -> x > 0).toArray();
+ }
+
public String[] list(String prefix) {
return list(prefix, UID_SELF);
}
@@ -451,27 +483,107 @@
public boolean addRngEntropy(byte[] data, int flags) {
try {
- return mBinder.addRngEntropy(data, flags) == NO_ERROR;
+ KeystoreResultPromise promise = new KeystoreResultPromise();
+ int errorCode = mBinder.addRngEntropy(promise, data, flags);
+ if (errorCode == NO_ERROR) {
+ return promise.getFuture().get().getErrorCode() == NO_ERROR;
+ } else {
+ return false;
+ }
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return false;
+ } catch (ExecutionException | InterruptedException e) {
+ Log.e(TAG, "AddRngEntropy completed with exception", e);
+ return false;
}
}
+ private class KeyCharacteristicsCallbackResult {
+ private KeystoreResponse keystoreResponse;
+ private KeyCharacteristics keyCharacteristics;
+
+ public KeyCharacteristicsCallbackResult(KeystoreResponse keystoreResponse,
+ KeyCharacteristics keyCharacteristics) {
+ this.keystoreResponse = keystoreResponse;
+ this.keyCharacteristics = keyCharacteristics;
+ }
+
+ public KeystoreResponse getKeystoreResponse() {
+ return keystoreResponse;
+ }
+
+ public void setKeystoreResponse(KeystoreResponse keystoreResponse) {
+ this.keystoreResponse = keystoreResponse;
+ }
+
+ public KeyCharacteristics getKeyCharacteristics() {
+ return keyCharacteristics;
+ }
+
+ public void setKeyCharacteristics(KeyCharacteristics keyCharacteristics) {
+ this.keyCharacteristics = keyCharacteristics;
+ }
+ }
+
+ private class KeyCharacteristicsPromise
+ extends android.security.keystore.IKeystoreKeyCharacteristicsCallback.Stub {
+ final private CompletableFuture<KeyCharacteristicsCallbackResult> future =
+ new CompletableFuture<KeyCharacteristicsCallbackResult>();
+ @Override
+ public void onFinished(KeystoreResponse keystoreResponse,
+ KeyCharacteristics keyCharacteristics)
+ throws android.os.RemoteException {
+ future.complete(
+ new KeyCharacteristicsCallbackResult(keystoreResponse, keyCharacteristics));
+ }
+ public final CompletableFuture<KeyCharacteristicsCallbackResult> getFuture() {
+ return future;
+ }
+ };
+
+ private int generateKeyInternal(String alias, KeymasterArguments args, byte[] entropy, int uid,
+ int flags, KeyCharacteristics outCharacteristics)
+ throws RemoteException, ExecutionException, InterruptedException {
+ KeyCharacteristicsPromise promise = new KeyCharacteristicsPromise();
+ int error = mBinder.generateKey(promise, alias, args, entropy, uid, flags);
+ if (error != NO_ERROR) {
+ Log.e(TAG, "generateKeyInternal failed on request " + error);
+ return error;
+ }
+
+ KeyCharacteristicsCallbackResult result = promise.getFuture().get();
+ error = result.getKeystoreResponse().getErrorCode();
+ if (error != NO_ERROR) {
+ Log.e(TAG, "generateKeyInternal failed on response " + error);
+ return error;
+ }
+ KeyCharacteristics characteristics = result.getKeyCharacteristics();
+ if (characteristics == null) {
+ Log.e(TAG, "generateKeyInternal got empty key cheractariestics " + error);
+ return SYSTEM_ERROR;
+ }
+ outCharacteristics.shallowCopyFrom(characteristics);
+ return NO_ERROR;
+ }
+
public int generateKey(String alias, KeymasterArguments args, byte[] entropy, int uid,
int flags, KeyCharacteristics outCharacteristics) {
try {
entropy = entropy != null ? entropy : new byte[0];
args = args != null ? args : new KeymasterArguments();
- int error = mBinder.generateKey(alias, args, entropy, uid, flags, outCharacteristics);
+ int error = generateKeyInternal(alias, args, entropy, uid, flags, outCharacteristics);
if (error == KEY_ALREADY_EXISTS) {
mBinder.del(alias, uid);
- error = mBinder.generateKey(alias, args, entropy, uid, flags, outCharacteristics);
+ error = generateKeyInternal(alias, args, entropy, uid, flags, outCharacteristics);
}
return error;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return SYSTEM_ERROR;
+ } catch (ExecutionException | InterruptedException e) {
+ Log.e(TAG, "generateKey completed with exception", e);
+ return SYSTEM_ERROR;
}
}
@@ -485,10 +597,24 @@
try {
clientId = clientId != null ? clientId : new KeymasterBlob(new byte[0]);
appId = appId != null ? appId : new KeymasterBlob(new byte[0]);
- return mBinder.getKeyCharacteristics(alias, clientId, appId, uid, outCharacteristics);
+ KeyCharacteristicsPromise promise = new KeyCharacteristicsPromise();
+ int error = mBinder.getKeyCharacteristics(promise, alias, clientId, appId, uid);
+ if (error != NO_ERROR) return error;
+
+ KeyCharacteristicsCallbackResult result = promise.getFuture().get();
+ error = result.getKeystoreResponse().getErrorCode();
+ if (error != NO_ERROR) return error;
+
+ KeyCharacteristics characteristics = result.getKeyCharacteristics();
+ if (characteristics == null) return SYSTEM_ERROR;
+ outCharacteristics.shallowCopyFrom(characteristics);
+ return NO_ERROR;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return SYSTEM_ERROR;
+ } catch (ExecutionException | InterruptedException e) {
+ Log.e(TAG, "GetKeyCharacteristics completed with exception", e);
+ return SYSTEM_ERROR;
}
}
@@ -497,20 +623,40 @@
return getKeyCharacteristics(alias, clientId, appId, UID_SELF, outCharacteristics);
}
+ private int importKeyInternal(String alias, KeymasterArguments args, int format, byte[] keyData,
+ int uid, int flags, KeyCharacteristics outCharacteristics)
+ throws RemoteException, ExecutionException, InterruptedException {
+ KeyCharacteristicsPromise promise = new KeyCharacteristicsPromise();
+ int error = mBinder.importKey(promise, alias, args, format, keyData, uid, flags);
+ if (error != NO_ERROR) return error;
+
+ KeyCharacteristicsCallbackResult result = promise.getFuture().get();
+ error = result.getKeystoreResponse().getErrorCode();
+ if (error != NO_ERROR) return error;
+
+ KeyCharacteristics characteristics = result.getKeyCharacteristics();
+ if (characteristics == null) return SYSTEM_ERROR;
+ outCharacteristics.shallowCopyFrom(characteristics);
+ return NO_ERROR;
+ }
+
public int importKey(String alias, KeymasterArguments args, int format, byte[] keyData,
int uid, int flags, KeyCharacteristics outCharacteristics) {
try {
- int error = mBinder.importKey(alias, args, format, keyData, uid, flags,
+ int error = importKeyInternal(alias, args, format, keyData, uid, flags,
outCharacteristics);
if (error == KEY_ALREADY_EXISTS) {
mBinder.del(alias, uid);
- error = mBinder.importKey(alias, args, format, keyData, uid, flags,
+ error = importKeyInternal(alias, args, format, keyData, uid, flags,
outCharacteristics);
}
return error;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return SYSTEM_ERROR;
+ } catch (ExecutionException | InterruptedException e) {
+ Log.e(TAG, "ImportKey completed with exception", e);
+ return SYSTEM_ERROR;
}
}
@@ -555,11 +701,9 @@
args.addEnum(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA_2_384);
args.addEnum(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA_2_512);
args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
- args.addUnsignedLong(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
- KeymasterArguments.UINT64_MAX_VALUE);
- args.addUnsignedLong(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
- KeymasterArguments.UINT64_MAX_VALUE);
- args.addUnsignedLong(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, BigInteger.ZERO);
+ args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, new Date(Long.MAX_VALUE));
+ args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, new Date(Long.MAX_VALUE));
+ args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, new Date(0));
return args;
}
@@ -578,34 +722,79 @@
return true;
}
+ private int importWrappedKeyInternal(String wrappedKeyAlias, byte[] wrappedKey,
+ String wrappingKeyAlias,
+ byte[] maskingKey, KeymasterArguments args, long rootSid, long fingerprintSid,
+ KeyCharacteristics outCharacteristics)
+ throws RemoteException, ExecutionException, InterruptedException {
+ KeyCharacteristicsPromise promise = new KeyCharacteristicsPromise();
+ int error = mBinder.importWrappedKey(promise, wrappedKeyAlias, wrappedKey, wrappingKeyAlias,
+ maskingKey, args, rootSid, fingerprintSid);
+ if (error != NO_ERROR) return error;
+
+ KeyCharacteristicsCallbackResult result = promise.getFuture().get();
+ error = result.getKeystoreResponse().getErrorCode();
+ if (error != NO_ERROR) return error;
+
+ KeyCharacteristics characteristics = result.getKeyCharacteristics();
+ if (characteristics == null) return SYSTEM_ERROR;
+ outCharacteristics.shallowCopyFrom(characteristics);
+ return NO_ERROR;
+ }
+
public int importWrappedKey(String wrappedKeyAlias, byte[] wrappedKey,
String wrappingKeyAlias,
byte[] maskingKey, KeymasterArguments args, long rootSid, long fingerprintSid, int uid,
KeyCharacteristics outCharacteristics) {
+ // TODO b/119217337 uid parameter gets silently ignored.
try {
- int error = mBinder.importWrappedKey(wrappedKeyAlias, wrappedKey, wrappingKeyAlias,
+ int error = importWrappedKeyInternal(wrappedKeyAlias, wrappedKey, wrappingKeyAlias,
maskingKey, args, rootSid, fingerprintSid, outCharacteristics);
if (error == KEY_ALREADY_EXISTS) {
- mBinder.del(wrappedKeyAlias, -1);
- error = mBinder.importWrappedKey(wrappedKeyAlias, wrappedKey, wrappingKeyAlias,
+ mBinder.del(wrappedKeyAlias, UID_SELF);
+ error = importWrappedKeyInternal(wrappedKeyAlias, wrappedKey, wrappingKeyAlias,
maskingKey, args, rootSid, fingerprintSid, outCharacteristics);
}
return error;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return SYSTEM_ERROR;
+ } catch (ExecutionException | InterruptedException e) {
+ Log.e(TAG, "ImportWrappedKey completed with exception", e);
+ return SYSTEM_ERROR;
}
}
+ private class ExportKeyPromise
+ extends android.security.keystore.IKeystoreExportKeyCallback.Stub {
+ final private CompletableFuture<ExportResult> future = new CompletableFuture<ExportResult>();
+ @Override
+ public void onFinished(ExportResult exportKeyResult) throws android.os.RemoteException {
+ future.complete(exportKeyResult);
+ }
+ public final CompletableFuture<ExportResult> getFuture() {
+ return future;
+ }
+ };
+
public ExportResult exportKey(String alias, int format, KeymasterBlob clientId,
KeymasterBlob appId, int uid) {
try {
clientId = clientId != null ? clientId : new KeymasterBlob(new byte[0]);
appId = appId != null ? appId : new KeymasterBlob(new byte[0]);
- return mBinder.exportKey(alias, format, clientId, appId, uid);
+ ExportKeyPromise promise = new ExportKeyPromise();
+ int error = mBinder.exportKey(promise, alias, format, clientId, appId, uid);
+ if (error == NO_ERROR) {
+ return promise.getFuture().get();
+ } else {
+ return new ExportResult(error);
+ }
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return null;
+ } catch (ExecutionException | InterruptedException e) {
+ Log.e(TAG, "ExportKey completed with exception", e);
+ return null;
}
}
public ExportResult exportKey(String alias, int format, KeymasterBlob clientId,
@@ -613,15 +802,37 @@
return exportKey(alias, format, clientId, appId, UID_SELF);
}
+ private class OperationPromise
+ extends android.security.keystore.IKeystoreOperationResultCallback.Stub {
+ final private CompletableFuture<OperationResult> future = new CompletableFuture<OperationResult>();
+ @Override
+ public void onFinished(OperationResult operationResult) throws android.os.RemoteException {
+ future.complete(operationResult);
+ }
+ public final CompletableFuture<OperationResult> getFuture() {
+ return future;
+ }
+ };
+
public OperationResult begin(String alias, int purpose, boolean pruneable,
KeymasterArguments args, byte[] entropy, int uid) {
try {
args = args != null ? args : new KeymasterArguments();
entropy = entropy != null ? entropy : new byte[0];
- return mBinder.begin(getToken(), alias, purpose, pruneable, args, entropy, uid);
+ OperationPromise promise = new OperationPromise();
+ int errorCode = mBinder.begin(promise, getToken(), alias, purpose, pruneable, args,
+ entropy, uid);
+ if (errorCode == NO_ERROR) {
+ return promise.getFuture().get();
+ } else {
+ return new OperationResult(errorCode);
+ }
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return null;
+ } catch (ExecutionException | InterruptedException e) {
+ Log.e(TAG, "Begin completed with exception", e);
+ return null;
}
}
@@ -636,10 +847,19 @@
try {
arguments = arguments != null ? arguments : new KeymasterArguments();
input = input != null ? input : new byte[0];
- return mBinder.update(token, arguments, input);
+ OperationPromise promise = new OperationPromise();
+ int errorCode = mBinder.update(promise, token, arguments, input);
+ if (errorCode == NO_ERROR) {
+ return promise.getFuture().get();
+ } else {
+ return new OperationResult(errorCode);
+ }
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return null;
+ } catch (ExecutionException | InterruptedException e) {
+ Log.e(TAG, "Update completed with exception", e);
+ return null;
}
}
@@ -649,10 +869,19 @@
arguments = arguments != null ? arguments : new KeymasterArguments();
entropy = entropy != null ? entropy : new byte[0];
signature = signature != null ? signature : new byte[0];
- return mBinder.finish(token, arguments, signature, entropy);
+ OperationPromise promise = new OperationPromise();
+ int errorCode = mBinder.finish(promise, token, arguments, signature, entropy);
+ if (errorCode == NO_ERROR) {
+ return promise.getFuture().get();
+ } else {
+ return new OperationResult(errorCode);
+ }
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return null;
+ } catch (ExecutionException | InterruptedException e) {
+ Log.e(TAG, "Finish completed with exception", e);
+ return null;
}
}
@@ -660,12 +889,33 @@
return finish(token, arguments, signature, null);
}
+ private class KeystoreResultPromise
+ extends android.security.keystore.IKeystoreResponseCallback.Stub {
+ final private CompletableFuture<KeystoreResponse> future = new CompletableFuture<KeystoreResponse>();
+ @Override
+ public void onFinished(KeystoreResponse keystoreResponse) throws android.os.RemoteException {
+ future.complete(keystoreResponse);
+ }
+ public final CompletableFuture<KeystoreResponse> getFuture() {
+ return future;
+ }
+ };
+
public int abort(IBinder token) {
try {
- return mBinder.abort(token);
+ KeystoreResultPromise promise = new KeystoreResultPromise();
+ int errorCode = mBinder.abort(promise, token);
+ if (errorCode == NO_ERROR) {
+ return promise.getFuture().get().getErrorCode();
+ } else {
+ return errorCode;
+ }
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return SYSTEM_ERROR;
+ } catch (ExecutionException | InterruptedException e) {
+ Log.e(TAG, "Abort completed with exception", e);
+ return SYSTEM_ERROR;
}
}
@@ -747,6 +997,47 @@
return onUserPasswordChanged(UserHandle.getUserId(Process.myUid()), newPassword);
}
+ private class KeyAttestationCallbackResult {
+ private KeystoreResponse keystoreResponse;
+ private KeymasterCertificateChain certificateChain;
+
+ public KeyAttestationCallbackResult(KeystoreResponse keystoreResponse,
+ KeymasterCertificateChain certificateChain) {
+ this.keystoreResponse = keystoreResponse;
+ this.certificateChain = certificateChain;
+ }
+
+ public KeystoreResponse getKeystoreResponse() {
+ return keystoreResponse;
+ }
+
+ public void setKeystoreResponse(KeystoreResponse keystoreResponse) {
+ this.keystoreResponse = keystoreResponse;
+ }
+
+ public KeymasterCertificateChain getCertificateChain() {
+ return certificateChain;
+ }
+
+ public void setCertificateChain(KeymasterCertificateChain certificateChain) {
+ this.certificateChain = certificateChain;
+ }
+ }
+
+ private class CertificateChainPromise
+ extends android.security.keystore.IKeystoreCertificateChainCallback.Stub {
+ final private CompletableFuture<KeyAttestationCallbackResult> future = new CompletableFuture<KeyAttestationCallbackResult>();
+ @Override
+ public void onFinished(KeystoreResponse keystoreResponse,
+ KeymasterCertificateChain certificateChain) throws android.os.RemoteException {
+ future.complete(new KeyAttestationCallbackResult(keystoreResponse, certificateChain));
+ }
+ public final CompletableFuture<KeyAttestationCallbackResult> getFuture() {
+ return future;
+ }
+ };
+
+
public int attestKey(
String alias, KeymasterArguments params, KeymasterCertificateChain outChain) {
try {
@@ -756,10 +1047,21 @@
if (outChain == null) {
outChain = new KeymasterCertificateChain();
}
- return mBinder.attestKey(alias, params, outChain);
+ CertificateChainPromise promise = new CertificateChainPromise();
+ int error = mBinder.attestKey(promise, alias, params);
+ if (error != NO_ERROR) return error;
+ KeyAttestationCallbackResult result = promise.getFuture().get();
+ error = result.getKeystoreResponse().getErrorCode();
+ if (error == NO_ERROR) {
+ outChain.shallowCopyFrom(result.getCertificateChain());
+ }
+ return error;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return SYSTEM_ERROR;
+ } catch (ExecutionException | InterruptedException e) {
+ Log.e(TAG, "AttestKey completed with exception", e);
+ return SYSTEM_ERROR;
}
}
@@ -771,10 +1073,21 @@
if (outChain == null) {
outChain = new KeymasterCertificateChain();
}
- return mBinder.attestDeviceIds(params, outChain);
+ CertificateChainPromise promise = new CertificateChainPromise();
+ int error = mBinder.attestDeviceIds(promise, params);
+ if (error != NO_ERROR) return error;
+ KeyAttestationCallbackResult result = promise.getFuture().get();
+ error = result.getKeystoreResponse().getErrorCode();
+ if (error == NO_ERROR) {
+ outChain.shallowCopyFrom(result.getCertificateChain());
+ }
+ return error;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return SYSTEM_ERROR;
+ } catch (ExecutionException | InterruptedException e) {
+ Log.e(TAG, "AttestDevicdeIds completed with exception", e);
+ return SYSTEM_ERROR;
}
}
@@ -941,7 +1254,7 @@
return new UserNotAuthenticatedException();
}
- long fingerprintOnlySid = getFingerprintOnlySid();
+ final long fingerprintOnlySid = getFingerprintOnlySid();
if ((fingerprintOnlySid != 0)
&& (keySids.contains(KeymasterArguments.toUint64(fingerprintOnlySid)))) {
// One of the key's SIDs is the current fingerprint SID -- user can be
@@ -949,6 +1262,14 @@
return new UserNotAuthenticatedException();
}
+ final long faceOnlySid = getFaceOnlySid();
+ if ((faceOnlySid != 0)
+ && (keySids.contains(KeymasterArguments.toUint64(faceOnlySid)))) {
+ // One of the key's SIDs is the current face SID -- user can be
+ // authenticated against that SID.
+ return new UserNotAuthenticatedException();
+ }
+
// None of the key's SIDs can ever be authenticated
return new KeyPermanentlyInvalidatedException();
}
@@ -959,6 +1280,21 @@
}
}
+ private long getFaceOnlySid() {
+ final PackageManager packageManager = mContext.getPackageManager();
+ if (!packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)) {
+ return 0;
+ }
+ FaceManager faceManager = mContext.getSystemService(FaceManager.class);
+ if (faceManager == null) {
+ return 0;
+ }
+
+ // TODO: Restore USE_BIOMETRIC or USE_BIOMETRIC_INTERNAL permission check in
+ // FaceManager.getAuthenticatorId once the ID is no longer needed here.
+ return faceManager.getAuthenticatorId();
+ }
+
private long getFingerprintOnlySid() {
final PackageManager packageManager = mContext.getPackageManager();
if (!packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
index 7bbc099..a2d2355 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -182,8 +182,8 @@
KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED);
boolean invalidatedByBiometricEnrollment = false;
- if (keymasterSwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_FINGERPRINT
- || keymasterHwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_FINGERPRINT) {
+ if (keymasterSwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_BIOMETRIC
+ || keymasterHwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_BIOMETRIC) {
// Fingerprint-only key; will be invalidated if the root SID isn't in the SID list.
invalidatedByBiometricEnrollment = keymasterSecureUserIds != null
&& !keymasterSecureUserIds.isEmpty()
diff --git a/keystore/java/android/security/keystore/DeviceIdAttestationException.java b/keystore/java/android/security/keystore/DeviceIdAttestationException.java
index e18d193..13f50b1 100644
--- a/keystore/java/android/security/keystore/DeviceIdAttestationException.java
+++ b/keystore/java/android/security/keystore/DeviceIdAttestationException.java
@@ -16,11 +16,16 @@
package android.security.keystore;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+
/**
* Thrown when {@link AttestationUtils} is unable to attest the given device ids.
*
* @hide
*/
+@SystemApi
+@TestApi
public class DeviceIdAttestationException extends Exception {
/**
* Constructs a new {@code DeviceIdAttestationException} with the current stack trace and the
diff --git a/keystore/java/android/security/keystore/KeymasterUtils.java b/keystore/java/android/security/keystore/KeymasterUtils.java
index f829bb7..52896b5 100644
--- a/keystore/java/android/security/keystore/KeymasterUtils.java
+++ b/keystore/java/android/security/keystore/KeymasterUtils.java
@@ -16,7 +16,7 @@
package android.security.keystore;
-import android.app.ActivityManager;
+import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.security.GateKeeper;
import android.security.KeyStore;
@@ -24,6 +24,8 @@
import android.security.keymaster.KeymasterDefs;
import java.security.ProviderException;
+import java.util.ArrayList;
+import java.util.List;
/**
* @hide
@@ -121,35 +123,44 @@
if (spec.getUserAuthenticationValidityDurationSeconds() == -1) {
// Every use of this key needs to be authorized by the user. This currently means
- // fingerprint-only auth.
+ // fingerprint or face auth.
FingerprintManager fingerprintManager =
KeyStore.getApplicationContext().getSystemService(FingerprintManager.class);
+ FaceManager faceManager =
+ KeyStore.getApplicationContext().getSystemService(FaceManager.class);
// TODO: Restore USE_FINGERPRINT permission check in
// FingerprintManager.getAuthenticatorId once the ID is no longer needed here.
- long fingerprintOnlySid =
+ final long fingerprintOnlySid =
(fingerprintManager != null) ? fingerprintManager.getAuthenticatorId() : 0;
- if (fingerprintOnlySid == 0) {
+ final long faceOnlySid =
+ (faceManager != null) ? faceManager.getAuthenticatorId() : 0;
+
+ if (fingerprintOnlySid == 0 && faceOnlySid == 0) {
throw new IllegalStateException(
- "At least one fingerprint must be enrolled to create keys requiring user"
+ "At least one biometric must be enrolled to create keys requiring user"
+ " authentication for every use");
}
- long sid;
+ List<Long> sids = new ArrayList<>();
if (spec.getBoundToSpecificSecureUserId() != GateKeeper.INVALID_SECURE_USER_ID) {
- sid = spec.getBoundToSpecificSecureUserId();
+ sids.add(spec.getBoundToSpecificSecureUserId());
} else if (spec.isInvalidatedByBiometricEnrollment()) {
- // The fingerprint-only SID will change on fingerprint enrollment or removal of all,
- // enrolled fingerprints, invalidating the key.
- sid = fingerprintOnlySid;
+ // The biometric-only SIDs will change on biometric enrollment or removal of all
+ // enrolled templates, invalidating the key.
+ sids.add(fingerprintOnlySid);
+ sids.add(faceOnlySid);
} else {
// The root SID will *not* change on fingerprint enrollment, or removal of all
// enrolled fingerprints, allowing the key to remain valid.
- sid = getRootSid();
+ sids.add(getRootSid());
}
- args.addUnsignedLong(
- KeymasterDefs.KM_TAG_USER_SECURE_ID, KeymasterArguments.toUint64(sid));
- args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, KeymasterDefs.HW_AUTH_FINGERPRINT);
+ for (int i = 0; i < sids.size(); i++) {
+ args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
+ KeymasterArguments.toUint64(sids.get(i)));
+ }
+ args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, KeymasterDefs.HW_AUTH_BIOMETRIC);
+
if (spec.isUserAuthenticationValidWhileOnBody()) {
throw new ProviderException("Key validity extension while device is on-body is not "
+ "supported for keys requiring fingerprint authentication");
@@ -166,7 +177,7 @@
args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
KeymasterArguments.toUint64(sid));
args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE,
- KeymasterDefs.HW_AUTH_PASSWORD | KeymasterDefs.HW_AUTH_FINGERPRINT);
+ KeymasterDefs.HW_AUTH_PASSWORD | KeymasterDefs.HW_AUTH_BIOMETRIC);
args.addUnsignedInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT,
spec.getUserAuthenticationValidityDurationSeconds());
if (spec.isUserAuthenticationValidWhileOnBody()) {
diff --git a/keystore/java/android/security/keystore/KeystoreResponse.java b/keystore/java/android/security/keystore/KeystoreResponse.java
new file mode 100644
index 0000000..3a229cb
--- /dev/null
+++ b/keystore/java/android/security/keystore/KeystoreResponse.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.security.keystore;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ParcelFormatException;
+
+/**
+ * The Java side of the KeystoreResponse.
+ * <p>
+ * Serialization code for this and subclasses must be kept in sync with system/security/keystore.
+ * @hide
+ */
+public class KeystoreResponse implements Parcelable {
+ public final int error_code_;
+ public final String error_msg_;
+
+ public static final Parcelable.Creator<KeystoreResponse> CREATOR = new
+ Parcelable.Creator<KeystoreResponse>() {
+ @Override
+ public KeystoreResponse createFromParcel(Parcel in) {
+ final int error_code = in.readInt();
+ final String error_msg = in.readString();
+ return new KeystoreResponse(error_code, error_msg);
+ }
+
+ @Override
+ public KeystoreResponse[] newArray(int size) {
+ return new KeystoreResponse[size];
+ }
+ };
+
+ protected KeystoreResponse(int error_code, String error_msg) {
+ this.error_code_ = error_code;
+ this.error_msg_ = error_msg;
+ }
+
+ /**
+ * @return the error_code_
+ */
+ public final int getErrorCode() {
+ return error_code_;
+ }
+
+ /**
+ * @return the error_msg_
+ */
+ public final String getErrorMessage() {
+ return error_msg_;
+ }
+
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(error_code_);
+ out.writeString(error_msg_);
+ }
+}
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index 843c146..1cb0d25 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -72,7 +72,7 @@
const char* AssetManager::RESOURCES_FILENAME = "resources.arsc";
const char* AssetManager::IDMAP_BIN = "/system/bin/idmap";
-const char* AssetManager::OVERLAY_DIR = "/vendor/overlay";
+const char* AssetManager::VENDOR_OVERLAY_DIR = "/vendor/overlay";
const char* AssetManager::PRODUCT_OVERLAY_DIR = "/product/overlay";
const char* AssetManager::PRODUCT_SERVICES_OVERLAY_DIR = "/product_services/overlay";
const char* AssetManager::OVERLAY_THEME_DIR_PROPERTY = "ro.boot.vendor.overlay.theme";
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 9e69488..7ab12b1 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -637,6 +637,7 @@
new_entry->key = new_key;
new_entry->key_pool = nullptr;
new_entry->type_pool = nullptr;
+ new_entry->style = resid;
new_entry->value.copyFrom_dtoh(map_entry->value);
status_t err = entry.dynamic_ref_table->lookupResourceValue(&new_entry->value);
if (err != NO_ERROR) {
@@ -695,6 +696,7 @@
new_entry->key_pool = nullptr;
new_entry->type_pool = nullptr;
new_entry->value.copyFrom_dtoh(map_entry->value);
+ new_entry->style = resid;
status_t err = entry.dynamic_ref_table->lookupResourceValue(&new_entry->value);
if (err != NO_ERROR) {
LOG(ERROR) << base::StringPrintf(
@@ -731,6 +733,7 @@
new_entry->key_pool = nullptr;
new_entry->type_pool = nullptr;
new_entry->value.copyFrom_dtoh(map_entry->value);
+ new_entry->style = resid;
status_t err = entry.dynamic_ref_table->lookupResourceValue(&new_entry->value);
if (err != NO_ERROR) {
LOG(ERROR) << base::StringPrintf("Failed to resolve value t=0x%02x d=0x%08x for key 0x%08x.",
@@ -1183,8 +1186,32 @@
continue;
}
- // The package id of the attribute needs to be rewritten to the package id of the value in
- // the destination
+ // If the attribute value represents an attribute or reference, the package id of the
+ // value needs to be rewritten to the package id of the value in the destination
+ uint32_t attribue_data = entry.value.data;
+ if ((entry.value.dataType == Res_value::TYPE_ATTRIBUTE
+ || entry.value.dataType == Res_value::TYPE_REFERENCE
+ || entry.value.dataType == Res_value::TYPE_DYNAMIC_ATTRIBUTE
+ || entry.value.dataType == Res_value::TYPE_DYNAMIC_REFERENCE)
+ && attribue_data != 0x0) {
+
+ // Determine the package id of the reference in the destination AssetManager
+ auto value_package_map = src_asset_cookie_id_map.find(entry.cookie);
+ if (value_package_map == src_asset_cookie_id_map.end()) {
+ continue;
+ }
+
+ auto value_dest_package = value_package_map->second.find(
+ get_package_id(entry.value.data));
+ if (value_dest_package == value_package_map->second.end()) {
+ continue;
+ }
+
+ attribue_data = fix_package_id(entry.value.data, value_dest_package->second);
+ }
+
+ // The package id of the attribute needs to be rewritten to the package id of the
+ // attribute in the destination
int attribute_dest_package_id = p;
if (attribute_dest_package_id != 0x01) {
// Find the cookie of the attribute resource id
@@ -1206,29 +1233,6 @@
attribute_dest_package_id = attribute_dest_package->second;
}
- // If the attribute value represents an attribute or reference, the package id of the
- // value needs to be rewritten to the package id of the value in the destination
- uint32_t attribue_data = entry.value.data;
- if (entry.value.dataType == Res_value::TYPE_DYNAMIC_ATTRIBUTE
- || entry.value.dataType == Res_value::TYPE_DYNAMIC_REFERENCE
- || entry.value.dataType == Res_value::TYPE_ATTRIBUTE
- || entry.value.dataType == Res_value::TYPE_REFERENCE) {
-
- // Determine the package id of the reference in the destination AssetManager
- auto value_package_map = src_asset_cookie_id_map.find(entry.cookie);
- if (value_package_map == src_asset_cookie_id_map.end()) {
- continue;
- }
-
- auto value_dest_package = value_package_map->second.find(
- get_package_id(entry.value.data));
- if (value_dest_package == value_package_map->second.end()) {
- continue;
- }
-
- attribue_data = fix_package_id(entry.value.data, value_dest_package->second);
- }
-
// Lazily instantiate the destination package
std::unique_ptr<Package>& dest_package = packages_[attribute_dest_package_id];
if (dest_package == nullptr) {
diff --git a/libs/androidfw/AttributeResolution.cpp b/libs/androidfw/AttributeResolution.cpp
index f912af4..57e3491 100644
--- a/libs/androidfw/AttributeResolution.cpp
+++ b/libs/androidfw/AttributeResolution.cpp
@@ -310,7 +310,8 @@
type_set_flags = style_flags;
value = entry->value;
if (kDebugStyles) {
- ALOGI("-> From style: type=0x%x, data=0x%08x", value.dataType, value.data);
+ ALOGI("-> From style: type=0x%x, data=0x%08x, style=0x%08x", value.dataType, value.data,
+ entry->style);
}
}
}
@@ -388,7 +389,6 @@
// out_indices must NOT be nullptr.
out_indices[indices_idx] = ii;
}
-
out_values += STYLE_NUM_ENTRIES;
}
diff --git a/libs/androidfw/include/androidfw/AssetManager.h b/libs/androidfw/include/androidfw/AssetManager.h
index cdb87bc..e22e2d2 100644
--- a/libs/androidfw/include/androidfw/AssetManager.h
+++ b/libs/androidfw/include/androidfw/AssetManager.h
@@ -59,13 +59,13 @@
public:
static const char* RESOURCES_FILENAME;
static const char* IDMAP_BIN;
- static const char* OVERLAY_DIR;
+ static const char* VENDOR_OVERLAY_DIR;
static const char* PRODUCT_OVERLAY_DIR;
static const char* PRODUCT_SERVICES_OVERLAY_DIR;
/*
* If OVERLAY_THEME_DIR_PROPERTY is set, search for runtime resource overlay
- * APKs in OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in addition to
- * OVERLAY_DIR.
+ * APKs in VENDOR_OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in
+ * addition to VENDOR_OVERLAY_DIR.
*/
static const char* OVERLAY_THEME_DIR_PROPERTY;
static const char* TARGET_PACKAGE_NAME;
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index 5312b06..0d49298 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -49,6 +49,9 @@
Res_value value;
+ // The resource ID of the origin style associated with the given entry.
+ uint32_t style;
+
// Which ApkAssets this entry came from.
ApkAssetsCookie cookie;
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index f1cc569..5449a54 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -298,11 +298,13 @@
EXPECT_EQ(Res_value::TYPE_INT_DEC, bag_two->entries[0].value.dataType);
EXPECT_EQ(1u, bag_two->entries[0].value.data);
EXPECT_EQ(0, bag_two->entries[0].cookie);
+ EXPECT_EQ(app::R::style::StyleOne, bag_two->entries[0].style);
// attr_two should be overridden from StyleOne by StyleTwo.
EXPECT_EQ(app::R::attr::attr_two, bag_two->entries[1].key);
EXPECT_EQ(Res_value::TYPE_STRING, bag_two->entries[1].value.dataType);
EXPECT_EQ(0, bag_two->entries[1].cookie);
+ EXPECT_EQ(app::R::style::StyleTwo, bag_two->entries[1].style);
EXPECT_EQ(std::string("string"), GetStringFromPool(assetmanager.GetStringPoolForCookie(0),
bag_two->entries[1].value.data));
@@ -312,21 +314,25 @@
EXPECT_EQ(Res_value::TYPE_ATTRIBUTE, bag_two->entries[2].value.dataType);
EXPECT_EQ(app::R::attr::attr_indirect, bag_two->entries[2].value.data);
EXPECT_EQ(0, bag_two->entries[2].cookie);
+ EXPECT_EQ(app::R::style::StyleTwo, bag_two->entries[2].style);
EXPECT_EQ(app::R::attr::attr_five, bag_two->entries[3].key);
EXPECT_EQ(Res_value::TYPE_REFERENCE, bag_two->entries[3].value.dataType);
EXPECT_EQ(app::R::string::string_one, bag_two->entries[3].value.data);
EXPECT_EQ(0, bag_two->entries[3].cookie);
+ EXPECT_EQ(app::R::style::StyleTwo, bag_two->entries[3].style);
EXPECT_EQ(app::R::attr::attr_indirect, bag_two->entries[4].key);
EXPECT_EQ(Res_value::TYPE_INT_DEC, bag_two->entries[4].value.dataType);
EXPECT_EQ(3u, bag_two->entries[4].value.data);
EXPECT_EQ(0, bag_two->entries[4].cookie);
+ EXPECT_EQ(app::R::style::StyleTwo, bag_two->entries[4].style);
EXPECT_EQ(app::R::attr::attr_empty, bag_two->entries[5].key);
EXPECT_EQ(Res_value::TYPE_NULL, bag_two->entries[5].value.dataType);
EXPECT_EQ(Res_value::DATA_NULL_EMPTY, bag_two->entries[5].value.data);
EXPECT_EQ(0, bag_two->entries[5].cookie);
+ EXPECT_EQ(app::R::style::StyleTwo, bag_two->entries[5].style);
}
TEST_F(AssetManager2Test, MergeStylesCircularDependency) {
diff --git a/libs/hwui/OWNERS b/libs/hwui/OWNERS
new file mode 100644
index 0000000..936ba5c
--- /dev/null
+++ b/libs/hwui/OWNERS
@@ -0,0 +1,6 @@
+jreck@google.com
+njawad@google.com
+djsollen@google.com
+stani@google.com
+scroggo@google.com
+reed@google.com
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index d401b38..6ae5999 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -164,7 +164,11 @@
if (surface) {
mRenderThread.requireGlContext();
- mEglSurface = mEglManager.createSurface(surface, colorMode);
+ auto newSurface = mEglManager.createSurface(surface, colorMode);
+ if (!newSurface) {
+ return false;
+ }
+ mEglSurface = newSurface.unwrap();
}
if (colorMode == ColorMode::SRGB) {
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index d4ffddd..65ced6a 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -261,7 +261,7 @@
}
}
-EGLSurface EglManager::createSurface(EGLNativeWindowType window, ColorMode colorMode) {
+Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window, ColorMode colorMode) {
LOG_ALWAYS_FATAL_IF(!hasEglContext(), "Not initialized");
bool wideColorGamut = colorMode == ColorMode::WideColorGamut && EglExtensions.glColorSpace &&
@@ -311,9 +311,9 @@
EGLSurface surface = eglCreateWindowSurface(
mEglDisplay, wideColorGamut ? mEglConfigWideGamut : mEglConfig, window, attribs);
- LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE,
- "Failed to create EGLSurface for window %p, eglErr = %s", (void*)window,
- eglErrorString());
+ if (surface == EGL_NO_SURFACE) {
+ return Error<EGLint> { eglGetError() };
+ }
if (mSwapBehavior != SwapBehavior::Preserved) {
LOG_ALWAYS_FATAL_IF(eglSurfaceAttrib(mEglDisplay, surface, EGL_SWAP_BEHAVIOR,
diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h
index 55c81d4..2a44f7e 100644
--- a/libs/hwui/renderthread/EglManager.h
+++ b/libs/hwui/renderthread/EglManager.h
@@ -25,6 +25,7 @@
#include <ui/GraphicBuffer.h>
#include <utils/StrongPointer.h>
#include "IRenderPipeline.h"
+#include "utils/Result.h"
namespace android {
namespace uirenderer {
@@ -47,7 +48,7 @@
bool hasEglContext();
- EGLSurface createSurface(EGLNativeWindowType window, ColorMode colorMode);
+ Result<EGLSurface, EGLint> createSurface(EGLNativeWindowType window, ColorMode colorMode);
void destroySurface(EGLSurface surface);
void destroy();
diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
index 0795208..a6073eb 100644
--- a/libs/hwui/tests/unit/RenderNodeTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -321,7 +321,7 @@
// Check that the VD is in the dislay list, and the layer update queue contains the correct
// damage rect.
EXPECT_TRUE(rootNode->getDisplayList()->hasVectorDrawables());
- EXPECT_FALSE(info.layerUpdateQueue->entries().empty());
+ ASSERT_FALSE(info.layerUpdateQueue->entries().empty());
EXPECT_EQ(rootNode.get(), info.layerUpdateQueue->entries().at(0).renderNode.get());
EXPECT_EQ(uirenderer::Rect(0, 0, 200, 400), info.layerUpdateQueue->entries().at(0).damage);
canvasContext->destroy();
diff --git a/libs/hwui/utils/Result.h b/libs/hwui/utils/Result.h
new file mode 100644
index 0000000..7f33f2e
--- /dev/null
+++ b/libs/hwui/utils/Result.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <variant>
+#include <log/log.h>
+
+namespace android::uirenderer {
+
+template <typename E>
+struct Error {
+ E error;
+};
+
+template <typename R, typename E>
+class Result {
+public:
+ Result(const R& r) : result(std::forward<R>(r)) {}
+ Result(R&& r) : result(std::forward<R>(r)) {}
+ Result(Error<E>&& error) : result(std::forward<Error<E>>(error)) {}
+
+ operator bool() const {
+ return result.index() == 0;
+ }
+
+ R unwrap() const {
+ LOG_ALWAYS_FATAL_IF(result.index() == 1, "unwrap called on error value!");
+ return std::get<R>(result);
+ }
+
+ E error() const {
+ LOG_ALWAYS_FATAL_IF(result.index() == 0, "No error to get from Result");
+ return std::get<Error<E>>(result).error;
+ }
+
+private:
+ std::variant<R, Error<E>> result;
+};
+
+}; // namespace android::uirenderer
diff --git a/libs/services/src/os/StatsLogEventWrapper.cpp b/libs/services/src/os/StatsLogEventWrapper.cpp
index a1a6d9f..04c4629 100644
--- a/libs/services/src/os/StatsLogEventWrapper.cpp
+++ b/libs/services/src/os/StatsLogEventWrapper.cpp
@@ -85,9 +85,6 @@
case StatsLogValue::FLOAT:
mElements.push_back(StatsLogValue(in->readFloat()));
break;
- case StatsLogValue::DOUBLE:
- mElements.push_back(StatsLogValue(in->readDouble()));
- break;
case StatsLogValue::STORAGE:
mElements.push_back(StatsLogValue());
mElements.back().setType(StatsLogValue::STORAGE);
diff --git a/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
index 8a02a82..057a4ae 100644
--- a/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
+++ b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
@@ -20,9 +20,12 @@
import android.os.connectivity.GpsBatteryStats;
import android.os.SystemProperties;
+import android.server.location.ServerLocationProtoEnums;
+
import android.text.format.DateUtils;
import android.util.Base64;
import android.util.Log;
+import android.util.StatsLog;
import android.util.TimeUtils;
import java.util.Arrays;
@@ -39,11 +42,17 @@
private static final String TAG = GnssMetrics.class.getSimpleName();
+ /* Constant which indicates GPS signal quality is as yet unknown */
+ public static final int GPS_SIGNAL_QUALITY_UNKNOWN =
+ ServerLocationProtoEnums.GPS_SIGNAL_QUALITY_UNKNOWN; // -1
+
/* Constant which indicates GPS signal quality is poor */
- public static final int GPS_SIGNAL_QUALITY_POOR = 0;
+ public static final int GPS_SIGNAL_QUALITY_POOR =
+ ServerLocationProtoEnums.GPS_SIGNAL_QUALITY_POOR; // 0
/* Constant which indicates GPS signal quality is good */
- public static final int GPS_SIGNAL_QUALITY_GOOD = 1;
+ public static final int GPS_SIGNAL_QUALITY_GOOD =
+ ServerLocationProtoEnums.GPS_SIGNAL_QUALITY_GOOD; // 1
/* Number of GPS signal quality levels */
public static final int NUM_GPS_SIGNAL_QUALITY_LEVELS = GPS_SIGNAL_QUALITY_GOOD + 1;
@@ -329,11 +338,15 @@
/* Last reported Top Four Average CN0 */
private double mLastAverageCn0;
+ /* Last reported signal quality bin (based on Top Four Average CN0) */
+ private int mLastSignalLevel;
+
public GnssPowerMetrics(IBatteryStats stats) {
mBatteryStats = stats;
// Used to initialize the variable to a very small value (unachievable in practice) so that
// the first CNO report will trigger an update to BatteryStats
mLastAverageCn0 = -100.0;
+ mLastSignalLevel = GPS_SIGNAL_QUALITY_UNKNOWN;
}
/**
@@ -384,8 +397,13 @@
if (Math.abs(avgCn0 - mLastAverageCn0) < REPORTING_THRESHOLD_DB_HZ) {
return;
}
+ int signalLevel = getSignalLevel(avgCn0);
+ if (signalLevel != mLastSignalLevel) {
+ StatsLog.write(StatsLog.GPS_SIGNAL_QUALITY_CHANGED, signalLevel);
+ mLastSignalLevel = signalLevel;
+ }
try {
- mBatteryStats.noteGpsSignalQuality(getSignalLevel(avgCn0));
+ mBatteryStats.noteGpsSignalQuality(signalLevel);
mLastAverageCn0 = avgCn0;
} catch (Exception e) {
Log.w(TAG, "Exception", e);
diff --git a/media/java/android/media/AudioPresentation.java b/media/java/android/media/AudioPresentation.java
index 1cc650b..823af65 100644
--- a/media/java/android/media/AudioPresentation.java
+++ b/media/java/android/media/AudioPresentation.java
@@ -18,7 +18,6 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.TestApi;
import android.icu.util.ULocale;
import java.lang.annotation.Retention;
@@ -172,6 +171,10 @@
return localeLabels;
}
+ private Map<ULocale, String> getULabels() {
+ return mLabels;
+ }
+
/**
* @return the locale corresponding to audio presentation's ISO 639-1/639-2 language code.
*/
@@ -231,17 +234,24 @@
AudioPresentation obj = (AudioPresentation) o;
return mPresentationId == obj.getPresentationId()
&& mProgramId == obj.getProgramId()
- && mLanguage == obj.getULocale()
+ && mLanguage.equals(obj.getULocale())
&& mMasteringIndication == obj.getMasteringIndication()
&& mAudioDescriptionAvailable == obj.hasAudioDescription()
&& mSpokenSubtitlesAvailable == obj.hasSpokenSubtitles()
&& mDialogueEnhancementAvailable == obj.hasDialogueEnhancement()
- && mLabels.equals(obj.getLabels());
+ && mLabels.equals(obj.getULabels());
}
@Override
public int hashCode() {
- return Objects.hashCode(mPresentationId);
+ return Objects.hash(mPresentationId,
+ mProgramId,
+ mLanguage.hashCode(),
+ mMasteringIndication,
+ mAudioDescriptionAvailable,
+ mSpokenSubtitlesAvailable,
+ mDialogueEnhancementAvailable,
+ mLabels.hashCode());
}
/**
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 3ec595d..d37f8ab 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -16,23 +16,13 @@
package android.media;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
-import java.lang.Math;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.NioUtils;
-import java.util.LinkedList;
-import java.util.concurrent.Executor;
-
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
-import android.os.Build;
import android.os.Binder;
+import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@@ -43,6 +33,15 @@
import com.android.internal.annotations.GuardedBy;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.NioUtils;
+import java.util.LinkedList;
+import java.util.concurrent.Executor;
+
/**
* The AudioTrack class manages and plays a single audio resource for Java applications.
* It allows streaming of PCM audio buffers to the audio sink for playback. This is
@@ -372,6 +371,10 @@
*/
private int mAudioFormat; // initialized by all constructors via audioParamCheck()
/**
+ * The AudioAttributes used in configuration.
+ */
+ private AudioAttributes mConfiguredAudioAttributes;
+ /**
* Audio session ID
*/
private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
@@ -571,6 +574,8 @@
super(attributes, AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK);
// mState already == STATE_UNINITIALIZED
+ mConfiguredAudioAttributes = attributes; // object copy not needed, immutable.
+
if (format == null) {
throw new IllegalArgumentException("Illegal null AudioFormat");
}
@@ -1302,6 +1307,23 @@
}
/**
+ * Returns the {@link AudioAttributes} used in configuration.
+ * If a {@code streamType} is used instead of an {@code AudioAttributes}
+ * to configure the AudioTrack
+ * (the use of {@code streamType} for configuration is deprecated),
+ * then the {@code AudioAttributes}
+ * equivalent to the {@code streamType} is returned.
+ * @return The {@code AudioAttributes} used to configure the AudioTrack.
+ * @throws IllegalStateException If the track is not initialized.
+ */
+ public @NonNull AudioAttributes getAudioAttributes() {
+ if (mState == STATE_UNINITIALIZED || mConfiguredAudioAttributes == null) {
+ throw new IllegalStateException("track not initialized");
+ }
+ return mConfiguredAudioAttributes;
+ }
+
+ /**
* Returns the configured audio data encoding. See {@link AudioFormat#ENCODING_PCM_8BIT},
* {@link AudioFormat#ENCODING_PCM_16BIT}, and {@link AudioFormat#ENCODING_PCM_FLOAT}.
*/
diff --git a/media/java/android/media/MediaBrowser2.java b/media/java/android/media/MediaBrowser2.java
deleted file mode 100644
index 452371a..0000000
--- a/media/java/android/media/MediaBrowser2.java
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * 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.
- */
-
-package android.media;
-
-import android.annotation.CallbackExecutor;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.media.MediaLibraryService2.MediaLibrarySession;
-import android.media.MediaSession2.ControllerInfo;
-import android.media.update.ApiLoader;
-import android.media.update.MediaBrowser2Provider;
-import android.os.Bundle;
-
-import java.util.List;
-import java.util.concurrent.Executor;
-
-/**
- * @hide
- * Browses media content offered by a {@link MediaLibraryService2}.
- */
-public class MediaBrowser2 extends MediaController2 {
- // Equals to the ((MediaBrowser2Provider) getProvider())
- private final MediaBrowser2Provider mProvider;
-
- /**
- * Callback to listen events from {@link MediaLibraryService2}.
- */
- public static class BrowserCallback extends MediaController2.ControllerCallback {
- /**
- * Called with the result of {@link #getLibraryRoot(Bundle)}.
- * <p>
- * {@code rootMediaId} and {@code rootExtra} can be {@code null} if the library root isn't
- * available.
- *
- * @param browser the browser for this event
- * @param rootHints rootHints that you previously requested.
- * @param rootMediaId media id of the library root. Can be {@code null}
- * @param rootExtra extra of the library root. Can be {@code null}
- */
- public void onGetLibraryRootDone(@NonNull MediaBrowser2 browser, @Nullable Bundle rootHints,
- @Nullable String rootMediaId, @Nullable Bundle rootExtra) { }
-
- /**
- * Called when there's change in the parent's children.
- * <p>
- * This API is called when the library service called
- * {@link MediaLibrarySession#notifyChildrenChanged(ControllerInfo, String, int, Bundle)} or
- * {@link MediaLibrarySession#notifyChildrenChanged(String, int, Bundle)} for the parent.
- *
- * @param browser the browser for this event
- * @param parentId parent id that you've specified with {@link #subscribe(String, Bundle)}
- * @param itemCount number of children
- * @param extras extra bundle from the library service. Can be differ from extras that
- * you've specified with {@link #subscribe(String, Bundle)}.
- */
- public void onChildrenChanged(@NonNull MediaBrowser2 browser, @NonNull String parentId,
- int itemCount, @Nullable Bundle extras) { }
-
- /**
- * Called when the list of items has been returned by the library service for the previous
- * {@link MediaBrowser2#getChildren(String, int, int, Bundle)}.
- *
- * @param browser the browser for this event
- * @param parentId parent id
- * @param page page number that you've specified with
- * {@link #getChildren(String, int, int, Bundle)}
- * @param pageSize page size that you've specified with
- * {@link #getChildren(String, int, int, Bundle)}
- * @param result result. Can be {@code null}
- * @param extras extra bundle from the library service
- */
- public void onGetChildrenDone(@NonNull MediaBrowser2 browser, @NonNull String parentId,
- int page, int pageSize, @Nullable List<MediaItem2> result,
- @Nullable Bundle extras) { }
-
- /**
- * Called when the item has been returned by the library service for the previous
- * {@link MediaBrowser2#getItem(String)} call.
- * <p>
- * Result can be null if there had been error.
- *
- * @param browser the browser for this event
- * @param mediaId media id
- * @param result result. Can be {@code null}
- */
- public void onGetItemDone(@NonNull MediaBrowser2 browser, @NonNull String mediaId,
- @Nullable MediaItem2 result) { }
-
- /**
- * Called when there's change in the search result requested by the previous
- * {@link MediaBrowser2#search(String, Bundle)}.
- *
- * @param browser the browser for this event
- * @param query search query that you've specified with {@link #search(String, Bundle)}
- * @param itemCount The item count for the search result
- * @param extras extra bundle from the library service
- */
- public void onSearchResultChanged(@NonNull MediaBrowser2 browser, @NonNull String query,
- int itemCount, @Nullable Bundle extras) { }
-
- /**
- * Called when the search result has been returned by the library service for the previous
- * {@link MediaBrowser2#getSearchResult(String, int, int, Bundle)}.
- * <p>
- * Result can be null if there had been error.
- *
- * @param browser the browser for this event
- * @param query search query that you've specified with
- * {@link #getSearchResult(String, int, int, Bundle)}
- * @param page page number that you've specified with
- * {@link #getSearchResult(String, int, int, Bundle)}
- * @param pageSize page size that you've specified with
- * {@link #getSearchResult(String, int, int, Bundle)}
- * @param result result. Can be {@code null}.
- * @param extras extra bundle from the library service
- */
- public void onGetSearchResultDone(@NonNull MediaBrowser2 browser, @NonNull String query,
- int page, int pageSize, @Nullable List<MediaItem2> result,
- @Nullable Bundle extras) { }
- }
-
- public MediaBrowser2(@NonNull Context context, @NonNull SessionToken2 token,
- @NonNull @CallbackExecutor Executor executor, @NonNull BrowserCallback callback) {
- super(context, token, executor, callback);
- mProvider = (MediaBrowser2Provider) getProvider();
- }
-
- @Override
- MediaBrowser2Provider createProvider(Context context, SessionToken2 token,
- Executor executor, ControllerCallback callback) {
- return ApiLoader.getProvider().createMediaBrowser2(
- context, this, token, executor, (BrowserCallback) callback);
- }
-
- /**
- * Get the library root. Result would be sent back asynchronously with the
- * {@link BrowserCallback#onGetLibraryRootDone(MediaBrowser2, Bundle, String, Bundle)}.
- *
- * @param rootHints hint for the root
- * @see BrowserCallback#onGetLibraryRootDone(MediaBrowser2, Bundle, String, Bundle)
- */
- public void getLibraryRoot(@Nullable Bundle rootHints) {
- mProvider.getLibraryRoot_impl(rootHints);
- }
-
- /**
- * Subscribe to a parent id for the change in its children. When there's a change,
- * {@link BrowserCallback#onChildrenChanged(MediaBrowser2, String, int, Bundle)} will be called
- * with the bundle that you've specified. You should call
- * {@link #getChildren(String, int, int, Bundle)} to get the actual contents for the parent.
- *
- * @param parentId parent id
- * @param extras extra bundle
- */
- public void subscribe(@NonNull String parentId, @Nullable Bundle extras) {
- mProvider.subscribe_impl(parentId, extras);
- }
-
- /**
- * Unsubscribe for changes to the children of the parent, which was previously subscribed with
- * {@link #subscribe(String, Bundle)}.
- * <p>
- * This unsubscribes all previous subscription with the parent id, regardless of the extra
- * that was previously sent to the library service.
- *
- * @param parentId parent id
- */
- public void unsubscribe(@NonNull String parentId) {
- mProvider.unsubscribe_impl(parentId);
- }
-
- /**
- * Get list of children under the parent. Result would be sent back asynchronously with the
- * {@link BrowserCallback#onGetChildrenDone(MediaBrowser2, String, int, int, List, Bundle)}.
- *
- * @param parentId parent id for getting the children.
- * @param page page number to get the result. Starts from {@code 1}
- * @param pageSize page size. Should be greater or equal to {@code 1}
- * @param extras extra bundle
- */
- public void getChildren(@NonNull String parentId, int page, int pageSize,
- @Nullable Bundle extras) {
- mProvider.getChildren_impl(parentId, page, pageSize, extras);
- }
-
- /**
- * Get the media item with the given media id. Result would be sent back asynchronously with the
- * {@link BrowserCallback#onGetItemDone(MediaBrowser2, String, MediaItem2)}.
- *
- * @param mediaId media id for specifying the item
- */
- public void getItem(@NonNull String mediaId) {
- mProvider.getItem_impl(mediaId);
- }
-
- /**
- * Send a search request to the library service. When the search result is changed,
- * {@link BrowserCallback#onSearchResultChanged(MediaBrowser2, String, int, Bundle)} will be
- * called. You should call {@link #getSearchResult(String, int, int, Bundle)} to get the actual
- * search result.
- *
- * @param query search query. Should not be an empty string.
- * @param extras extra bundle
- */
- public void search(@NonNull String query, @Nullable Bundle extras) {
- mProvider.search_impl(query, extras);
- }
-
- /**
- * Get the search result from lhe library service. Result would be sent back asynchronously with
- * the
- * {@link BrowserCallback#onGetSearchResultDone(MediaBrowser2, String, int, int, List, Bundle)}.
- *
- * @param query search query that you've specified with {@link #search(String, Bundle)}
- * @param page page number to get search result. Starts from {@code 1}
- * @param pageSize page size. Should be greater or equal to {@code 1}
- * @param extras extra bundle
- */
- public void getSearchResult(@NonNull String query, int page, int pageSize,
- @Nullable Bundle extras) {
- mProvider.getSearchResult_impl(query, page, pageSize, extras);
- }
-}
diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java
index 591f33f..a633e5f 100644
--- a/media/java/android/media/MediaController2.java
+++ b/media/java/android/media/MediaController2.java
@@ -41,9 +41,8 @@
/**
* @hide
- * Allows an app to interact with an active {@link MediaSession2} or a
- * {@link MediaSessionService2} in any status. Media buttons and other commands can be sent to
- * the session.
+ * Allows an app to interact with an active {@link MediaSession2} in any status. Media buttons and
+ * other commands can be sent to the session.
* <p>
* When you're done, use {@link #close()} to clean up resources. This also helps session service
* to be destroyed when there's no controller associated with it.
@@ -51,12 +50,6 @@
* When controlling {@link MediaSession2}, the controller will be available immediately after
* the creation.
* <p>
- * When controlling {@link MediaSessionService2}, the {@link MediaController2} would be
- * available only if the session service allows this controller by
- * {@link MediaSession2.SessionCallback#onConnect(MediaSession2, ControllerInfo)} for the service.
- * Wait {@link ControllerCallback#onConnected(MediaController2, SessionCommandGroup2)} or
- * {@link ControllerCallback#onDisconnected(MediaController2)} for the result.
- * <p>
* A controller can be created through token from {@link MediaSessionManager} if you hold the
* signature|privileged permission "android.permission.MEDIA_CONTENT_CONTROL" permission or are
* an enabled notification listener or by getting a {@link SessionToken2} directly the
@@ -65,7 +58,6 @@
* MediaController2 objects are thread-safe.
* <p>
* @see MediaSession2
- * @see MediaSessionService2
*/
public class MediaController2 implements AutoCloseable {
/**
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 24b7f36..cdbc7b44 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -418,9 +418,6 @@
/**
* Returns the status code for the key
- * @return one of {@link #STATUS_USABLE}, {@link #STATUS_EXPIRED},
- * {@link #STATUS_OUTPUT_NOT_ALLOWED}, {@link #STATUS_PENDING}
- * or {@link #STATUS_INTERNAL_ERROR}.
*/
@KeyStatusCode
public int getStatusCode() { return mStatusCode; }
@@ -654,13 +651,7 @@
* can be queried using {@link #getSecurityLevel}. A session
* ID is returned.
*
- * @param level the new security level, one of
- * {@link #SECURITY_LEVEL_SW_SECURE_CRYPTO},
- * {@link #SECURITY_LEVEL_SW_SECURE_DECODE},
- * {@link #SECURITY_LEVEL_HW_SECURE_CRYPTO},
- * {@link #SECURITY_LEVEL_HW_SECURE_DECODE} or
- * {@link #SECURITY_LEVEL_HW_SECURE_ALL}.
- *
+ * @param level the new security level
* @throws NotProvisionedException if provisioning is needed
* @throws ResourceBusyException if required resources are in use
* @throws IllegalArgumentException if the requested security level is
@@ -790,9 +781,6 @@
/**
* Get the type of the request
- * @return one of {@link #REQUEST_TYPE_INITIAL},
- * {@link #REQUEST_TYPE_RENEWAL}, {@link #REQUEST_TYPE_RELEASE},
- * {@link #REQUEST_TYPE_NONE} or {@link #REQUEST_TYPE_UPDATE}
*/
@RequestType
public int getRequestType() { return mRequestType; }
@@ -1051,8 +1039,7 @@
* an inactive offline license are not usable for decryption.
*
* @param keySetId selects the offline license
- * @return the offline license state, one of {@link #OFFLINE_LICENSE_USABLE},
- * {@link #OFFLINE_LICENSE_INACTIVE} or {@link #OFFLINE_LICENSE_STATE_UNKNOWN}.
+ * @return the offline license state
* @throws IllegalArgumentException if the keySetId does not refer to an
* offline license.
*/
@@ -1191,9 +1178,7 @@
* enforcing compliance with HDCP requirements. Trusted enforcement of
* HDCP policies must be handled by the DRM system.
* <p>
- * @return one of {@link #HDCP_LEVEL_UNKNOWN}, {@link #HDCP_NONE},
- * {@link #HDCP_V1}, {@link #HDCP_V2}, {@link #HDCP_V2_1}, {@link #HDCP_V2_2}
- * or {@link #HDCP_NO_DIGITAL_OUTPUT}.
+ * @return the connected HDCP level
*/
@HdcpLevel
public native int getConnectedHdcpLevel();
@@ -1204,9 +1189,7 @@
* that may be connected. If multiple HDCP-capable interfaces are present,
* it indicates the highest of the maximum HDCP levels of all interfaces.
* <p>
- * @return one of {@link #HDCP_LEVEL_UNKNOWN}, {@link #HDCP_NONE},
- * {@link #HDCP_V1}, {@link #HDCP_V2}, {@link #HDCP_V2_1}, {@link #HDCP_V2_2}
- * or {@link #HDCP_NO_DIGITAL_OUTPUT}.
+ * @return the maximum supported HDCP level
*/
@HdcpLevel
public native int getMaxHdcpLevel();
@@ -1296,10 +1279,7 @@
* time a session is opened using {@link #openSession}.
* @param sessionId the session to query.
* <p>
- * @return one of {@link #SECURITY_LEVEL_UNKNOWN},
- * {@link #SECURITY_LEVEL_SW_SECURE_CRYPTO}, {@link #SECURITY_LEVEL_SW_SECURE_DECODE},
- * {@link #SECURITY_LEVEL_HW_SECURE_CRYPTO}, {@link #SECURITY_LEVEL_HW_SECURE_DECODE} or
- * {@link #SECURITY_LEVEL_HW_SECURE_ALL}.
+ * @return the security level of the session
*/
@SecurityLevel
public native int getSecurityLevel(@NonNull byte[] sessionId);
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index 4919eeb..c203fa9 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -272,10 +272,12 @@
public static final class CasInfo {
private final int mSystemId;
private final MediaCas.Session mSession;
+ private final byte[] mPrivateData;
- CasInfo(int systemId, @Nullable MediaCas.Session session) {
+ CasInfo(int systemId, @Nullable MediaCas.Session session, @Nullable byte[] privateData) {
mSystemId = systemId;
mSession = session;
+ mPrivateData = privateData;
}
/**
@@ -288,10 +290,30 @@
}
/**
+ * Retrieves the private data in the CA_Descriptor associated with a track.
+ * Some CAS systems may need this to initialize the CAS plugin object. This
+ * private data can only be retrieved before a valid {@link MediaCas} object
+ * is set on the extractor.
+ * <p>
+ * @see MediaExtractor#setMediaCas
+ * <p>
+ * @return a byte array containing the private data. A null return value
+ * indicates that the private data is unavailable. An empty array,
+ * on the other hand, indicates that the private data is empty
+ * (zero in length).
+ */
+ @Nullable
+ public byte[] getPrivateData() {
+ return mPrivateData;
+ }
+
+ /**
* Retrieves the {@link MediaCas.Session} associated with a track. The
* session is needed to initialize a descrambler in order to decode the
- * scrambled track.
+ * scrambled track. The session object can only be retrieved after a valid
+ * {@link MediaCas} object is set on the extractor.
* <p>
+ * @see MediaExtractor#setMediaCas
* @see MediaDescrambler#setMediaCasSession
* <p>
* @return a {@link MediaCas.Session} object associated with a track.
@@ -321,6 +343,13 @@
if (formatMap.containsKey(MediaFormat.KEY_CA_SYSTEM_ID)) {
int systemId = ((Integer)formatMap.get(MediaFormat.KEY_CA_SYSTEM_ID)).intValue();
MediaCas.Session session = null;
+ byte[] privateData = null;
+ if (formatMap.containsKey(MediaFormat.KEY_CA_PRIVATE_DATA)) {
+ ByteBuffer buf = (ByteBuffer) formatMap.get(MediaFormat.KEY_CA_PRIVATE_DATA);
+ buf.rewind();
+ privateData = new byte[buf.remaining()];
+ buf.get(privateData);
+ }
if (mMediaCas != null && formatMap.containsKey(MediaFormat.KEY_CA_SESSION_ID)) {
ByteBuffer buf = (ByteBuffer) formatMap.get(MediaFormat.KEY_CA_SESSION_ID);
buf.rewind();
@@ -328,7 +357,7 @@
buf.get(sessionId);
session = mMediaCas.createFromSessionId(toByteArray(sessionId));
}
- return new CasInfo(systemId, session);
+ return new CasInfo(systemId, session, privateData);
}
return null;
}
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index d10cbbc..5dee16e 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -919,7 +919,7 @@
* a media track.
* <p>
* This key is set by {@link MediaExtractor} if the track is scrambled with a conditional
- * access system.
+ * access system, regardless of the presence of a valid {@link MediaCas} object.
* <p>
* The associated value is an integer.
* @hide
@@ -930,13 +930,25 @@
* A key describing the {@link MediaCas.Session} object associated with a media track.
* <p>
* This key is set by {@link MediaExtractor} if the track is scrambled with a conditional
- * access system.
+ * access system, after it receives a valid {@link MediaCas} object.
* <p>
* The associated value is a ByteBuffer.
* @hide
*/
public static final String KEY_CA_SESSION_ID = "ca-session-id";
+
+ /**
+ * A key describing the private data in the CA_descriptor associated with a media track.
+ * <p>
+ * This key is set by {@link MediaExtractor} if the track is scrambled with a conditional
+ * access system, before it receives a valid {@link MediaCas} object.
+ * <p>
+ * The associated value is a ByteBuffer.
+ * @hide
+ */
+ public static final String KEY_CA_PRIVATE_DATA = "ca-private-data";
+
/* package private */ MediaFormat(Map<String, Object> map) {
mMap = map;
}
diff --git a/media/java/android/media/MediaLibraryService2.java b/media/java/android/media/MediaLibraryService2.java
deleted file mode 100644
index f29d386..0000000
--- a/media/java/android/media/MediaLibraryService2.java
+++ /dev/null
@@ -1,422 +0,0 @@
-/*
- * 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.
- */
-
-package android.media;
-
-import android.annotation.CallbackExecutor;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.PendingIntent;
-import android.media.MediaLibraryService2.MediaLibrarySession.Builder;
-import android.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback;
-import android.media.MediaSession2.ControllerInfo;
-import android.media.update.ApiLoader;
-import android.media.update.MediaLibraryService2Provider.LibraryRootProvider;
-import android.media.update.MediaLibraryService2Provider.MediaLibrarySessionProvider;
-import android.media.update.MediaSessionService2Provider;
-import android.os.Bundle;
-
-import java.util.List;
-import java.util.concurrent.Executor;
-
-/**
- * @hide
- * Base class for media library services.
- * <p>
- * Media library services enable applications to browse media content provided by an application
- * and ask the application to start playing it. They may also be used to control content that
- * is already playing by way of a {@link MediaSession2}.
- * <p>
- * When extending this class, also add the following to your {@code AndroidManifest.xml}.
- * <pre>
- * <service android:name="component_name_of_your_implementation" >
- * <intent-filter>
- * <action android:name="android.media.MediaLibraryService2" />
- * </intent-filter>
- * </service></pre>
- * <p>
- * The {@link MediaLibraryService2} class derives from {@link MediaSessionService2}. IDs shouldn't
- * be shared between the {@link MediaSessionService2} and {@link MediaSession2}. By
- * default, an empty string will be used for ID of the service. If you want to specify an ID,
- * declare metadata in the manifest as follows.
- *
- * @see MediaSessionService2
- */
-public abstract class MediaLibraryService2 extends MediaSessionService2 {
- /**
- * This is the interface name that a service implementing a session service should say that it
- * support -- that is, this is the action it uses for its intent filter.
- */
- public static final String SERVICE_INTERFACE = "android.media.MediaLibraryService2";
-
- /**
- * Session for the {@link MediaLibraryService2}. Build this object with
- * {@link Builder} and return in {@link #onCreateSession(String)}.
- */
- public static final class MediaLibrarySession extends MediaSession2 {
- private final MediaLibrarySessionProvider mProvider;
-
- /**
- * Callback for the {@link MediaLibrarySession}.
- */
- public static class MediaLibrarySessionCallback extends MediaSession2.SessionCallback {
- public MediaLibrarySessionCallback() {
- super();
- }
-
- /**
- * Called to get the root information for browsing by a particular client.
- * <p>
- * The implementation should verify that the client package has permission
- * to access browse media information before returning the root id; it
- * should return null if the client is not allowed to access this
- * information.
- *
- * @param session the session for this event
- * @param controllerInfo information of the controller requesting access to browse media.
- * @param rootHints An optional bundle of service-specific arguments to send
- * to the media library service when connecting and retrieving the
- * root id for browsing, or null if none. The contents of this
- * bundle may affect the information returned when browsing.
- * @return The {@link LibraryRoot} for accessing this app's content or null.
- * @see LibraryRoot#EXTRA_RECENT
- * @see LibraryRoot#EXTRA_OFFLINE
- * @see LibraryRoot#EXTRA_SUGGESTED
- */
- public @Nullable LibraryRoot onGetLibraryRoot(@NonNull MediaLibrarySession session,
- @NonNull ControllerInfo controllerInfo, @Nullable Bundle rootHints) {
- return null;
- }
-
- /**
- * Called to get an item. Return result here for the browser.
- * <p>
- * Return {@code null} for no result or error.
- *
- * @param session the session for this event
- * @param mediaId item id to get media item.
- * @return a media item. {@code null} for no result or error.
- */
- public @Nullable MediaItem2 onGetItem(@NonNull MediaLibrarySession session,
- @NonNull ControllerInfo controllerInfo, @NonNull String mediaId) {
- return null;
- }
-
- /**
- * Called to get children of given parent id. Return the children here for the browser.
- * <p>
- * Return an empty list for no children, and return {@code null} for the error.
- *
- * @param session the session for this event
- * @param parentId parent id to get children
- * @param page number of page
- * @param pageSize size of the page
- * @param extras extra bundle
- * @return list of children. Can be {@code null}.
- */
- public @Nullable List<MediaItem2> onGetChildren(@NonNull MediaLibrarySession session,
- @NonNull ControllerInfo controller, @NonNull String parentId, int page,
- int pageSize, @Nullable Bundle extras) {
- return null;
- }
-
- /**
- * Called when a controller subscribes to the parent.
- * <p>
- * It's your responsibility to keep subscriptions by your own and call
- * {@link MediaLibrarySession#notifyChildrenChanged(ControllerInfo, String, int, Bundle)}
- * when the parent is changed.
- *
- * @param session the session for this event
- * @param controller controller
- * @param parentId parent id
- * @param extras extra bundle
- */
- public void onSubscribe(@NonNull MediaLibrarySession session,
- @NonNull ControllerInfo controller, @NonNull String parentId,
- @Nullable Bundle extras) {
- }
-
- /**
- * Called when a controller unsubscribes to the parent.
- *
- * @param session the session for this event
- * @param controller controller
- * @param parentId parent id
- */
- public void onUnsubscribe(@NonNull MediaLibrarySession session,
- @NonNull ControllerInfo controller, @NonNull String parentId) {
- }
-
- /**
- * Called when a controller requests search.
- *
- * @param session the session for this event
- * @param query The search query sent from the media browser. It contains keywords
- * separated by space.
- * @param extras The bundle of service-specific arguments sent from the media browser.
- */
- public void onSearch(@NonNull MediaLibrarySession session,
- @NonNull ControllerInfo controllerInfo, @NonNull String query,
- @Nullable Bundle extras) {
- }
-
- /**
- * Called to get the search result. Return search result here for the browser which has
- * requested search previously.
- * <p>
- * Return an empty list for no search result, and return {@code null} for the error.
- *
- * @param session the session for this event
- * @param controllerInfo Information of the controller requesting the search result.
- * @param query The search query which was previously sent through
- * {@link #onSearch(MediaLibrarySession, ControllerInfo, String, Bundle)}.
- * @param page page number. Starts from {@code 1}.
- * @param pageSize page size. Should be greater or equal to {@code 1}.
- * @param extras The bundle of service-specific arguments sent from the media browser.
- * @return search result. {@code null} for error.
- */
- public @Nullable List<MediaItem2> onGetSearchResult(
- @NonNull MediaLibrarySession session, @NonNull ControllerInfo controllerInfo,
- @NonNull String query, int page, int pageSize, @Nullable Bundle extras) {
- return null;
- }
- }
-
- /**
- * Builder for {@link MediaLibrarySession}.
- */
- // Override all methods just to show them with the type instead of generics in Javadoc.
- // This workarounds javadoc issue described in the MediaSession2.BuilderBase.
- public static final class Builder extends BuilderBase<MediaLibrarySession, Builder,
- MediaLibrarySessionCallback> {
- // Builder requires MediaLibraryService2 instead of Context just to ensure that the
- // builder can be only instantiated within the MediaLibraryService2.
- // Ideally it's better to make it inner class of service to enforce, it violates API
- // guideline that Builders should be the inner class of the building target.
- public Builder(@NonNull MediaLibraryService2 service,
- @NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull MediaLibrarySessionCallback callback) {
- super((instance) -> ApiLoader.getProvider().createMediaLibraryService2Builder(
- service, (Builder) instance, callbackExecutor, callback));
- }
-
- @Override
- public Builder setPlayer(@NonNull MediaPlayerBase player) {
- return super.setPlayer(player);
- }
-
- @Override
- public Builder setPlaylistAgent(@NonNull MediaPlaylistAgent playlistAgent) {
- return super.setPlaylistAgent(playlistAgent);
- }
-
- @Override
- public Builder setVolumeProvider(@Nullable VolumeProvider2 volumeProvider) {
- return super.setVolumeProvider(volumeProvider);
- }
-
- @Override
- public Builder setSessionActivity(@Nullable PendingIntent pi) {
- return super.setSessionActivity(pi);
- }
-
- @Override
- public Builder setId(@NonNull String id) {
- return super.setId(id);
- }
-
- @Override
- public Builder setSessionCallback(@NonNull @CallbackExecutor Executor executor,
- @NonNull MediaLibrarySessionCallback callback) {
- return super.setSessionCallback(executor, callback);
- }
-
- @Override
- public MediaLibrarySession build() {
- return super.build();
- }
- }
-
- /**
- * @hide
- */
- public MediaLibrarySession(MediaLibrarySessionProvider provider) {
- super(provider);
- mProvider = provider;
- }
-
- /**
- * Notify the controller of the change in a parent's children.
- * <p>
- * If the controller hasn't subscribed to the parent, the API will do nothing.
- * <p>
- * Controllers will use {@link MediaBrowser2#getChildren(String, int, int, Bundle)} to get
- * the list of children.
- *
- * @param controller controller to notify
- * @param parentId parent id with changes in its children
- * @param itemCount number of children.
- * @param extras extra information from session to controller
- */
- public void notifyChildrenChanged(@NonNull ControllerInfo controller,
- @NonNull String parentId, int itemCount, @Nullable Bundle extras) {
- mProvider.notifyChildrenChanged_impl(controller, parentId, itemCount, extras);
- }
-
- /**
- * Notify all controllers that subscribed to the parent about change in the parent's
- * children, regardless of the extra bundle supplied by
- * {@link MediaBrowser2#subscribe(String, Bundle)}.
- *
- * @param parentId parent id
- * @param itemCount number of children
- * @param extras extra information from session to controller
- */
- // This is for the backward compatibility.
- public void notifyChildrenChanged(@NonNull String parentId, int itemCount,
- @Nullable Bundle extras) {
- mProvider.notifyChildrenChanged_impl(parentId, itemCount, extras);
- }
-
- /**
- * Notify controller about change in the search result.
- *
- * @param controller controller to notify
- * @param query previously sent search query from the controller.
- * @param itemCount the number of items that have been found in the search.
- * @param extras extra bundle
- */
- public void notifySearchResultChanged(@NonNull ControllerInfo controller,
- @NonNull String query, int itemCount, @NonNull Bundle extras) {
- mProvider.notifySearchResultChanged_impl(controller, query, itemCount, extras);
- }
- }
-
- @Override
- MediaSessionService2Provider createProvider() {
- return ApiLoader.getProvider().createMediaLibraryService2(this);
- }
-
- /**
- * Called when another app requested to start this service.
- * <p>
- * Library service will accept or reject the connection with the
- * {@link MediaLibrarySessionCallback} in the created session.
- * <p>
- * Service wouldn't run if {@code null} is returned or session's ID doesn't match with the
- * expected ID that you've specified through the AndroidManifest.xml.
- * <p>
- * This method will be called on the main thread.
- *
- * @param sessionId session id written in the AndroidManifest.xml.
- * @return a new library session
- * @see Builder
- * @see #getSession()
- * @throws RuntimeException if returned session is invalid
- */
- @Override
- public @NonNull abstract MediaLibrarySession onCreateSession(String sessionId);
-
- /**
- * Contains information that the library service needs to send to the client when
- * {@link MediaBrowser2#getLibraryRoot(Bundle)} is called.
- */
- public static final class LibraryRoot {
- /**
- * The lookup key for a boolean that indicates whether the library service should return a
- * librar root for recently played media items.
- *
- * <p>When creating a media browser for a given media library service, this key can be
- * supplied as a root hint for retrieving media items that are recently played.
- * If the media library service can provide such media items, the implementation must return
- * the key in the root hint when
- * {@link MediaLibrarySessionCallback#onGetLibraryRoot(MediaLibrarySession, ControllerInfo, Bundle)}
- * is called back.
- *
- * <p>The root hint may contain multiple keys.
- *
- * @see #EXTRA_OFFLINE
- * @see #EXTRA_SUGGESTED
- */
- public static final String EXTRA_RECENT = "android.media.extra.RECENT";
-
- /**
- * The lookup key for a boolean that indicates whether the library service should return a
- * library root for offline media items.
- *
- * <p>When creating a media browser for a given media library service, this key can be
- * supplied as a root hint for retrieving media items that are can be played without an
- * internet connection.
- * If the media library service can provide such media items, the implementation must return
- * the key in the root hint when
- * {@link MediaLibrarySessionCallback#onGetLibraryRoot(MediaLibrarySession, ControllerInfo, Bundle)}
- * is called back.
- *
- * <p>The root hint may contain multiple keys.
- *
- * @see #EXTRA_RECENT
- * @see #EXTRA_SUGGESTED
- */
- public static final String EXTRA_OFFLINE = "android.media.extra.OFFLINE";
-
- /**
- * The lookup key for a boolean that indicates whether the library service should return a
- * library root for suggested media items.
- *
- * <p>When creating a media browser for a given media library service, this key can be
- * supplied as a root hint for retrieving the media items suggested by the media library
- * service. The list of media items is considered ordered by relevance, first being the top
- * suggestion.
- * If the media library service can provide such media items, the implementation must return
- * the key in the root hint when
- * {@link MediaLibrarySessionCallback#onGetLibraryRoot(MediaLibrarySession, ControllerInfo, Bundle)}
- * is called back.
- *
- * <p>The root hint may contain multiple keys.
- *
- * @see #EXTRA_RECENT
- * @see #EXTRA_OFFLINE
- */
- public static final String EXTRA_SUGGESTED = "android.media.extra.SUGGESTED";
-
- private final LibraryRootProvider mProvider;
-
- /**
- * Constructs a library root.
- * @param rootId The root id for browsing.
- * @param extras Any extras about the library service.
- */
- public LibraryRoot(@NonNull String rootId, @Nullable Bundle extras) {
- mProvider = ApiLoader.getProvider().createMediaLibraryService2LibraryRoot(
- this, rootId, extras);
- }
-
- /**
- * Gets the root id for browsing.
- */
- public String getRootId() {
- return mProvider.getRootId_impl();
- }
-
- /**
- * Gets any extras about the library service.
- */
- public Bundle getExtras() {
- return mProvider.getExtras_impl();
- }
- }
-}
diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java
index 4e90162..a80511a 100644
--- a/media/java/android/media/MediaPlayer2.java
+++ b/media/java/android/media/MediaPlayer2.java
@@ -21,22 +21,59 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
+import android.content.ContentResolver;
import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.graphics.Rect;
import android.graphics.SurfaceTexture;
+import android.media.MediaPlayer2Proto.PlayerMessage;
+import android.media.MediaPlayer2Proto.Value;
+import android.net.Uri;
import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
import android.os.PersistableBundle;
+import android.os.PowerManager;
+import android.util.Log;
+import android.util.Pair;
import android.view.Surface;
import android.view.SurfaceHolder;
+import com.android.framework.protobuf.InvalidProtocolBufferException;
+import com.android.internal.annotations.GuardedBy;
+
import dalvik.system.CloseGuard;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.net.HttpCookie;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Queue;
import java.util.UUID;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;
-
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
/**
* @hide
@@ -225,26 +262,108 @@
* successful transition. Any other value will be an error. Call {@link #getState()} to
* determine the current state. </p>
*/
-public abstract class MediaPlayer2 implements AutoCloseable
+public class MediaPlayer2 implements AutoCloseable
, AudioRouting {
+ static {
+ System.loadLibrary("media2_jni");
+ native_init();
+ }
+
+ private static native void native_init();
+
+ private static final int NEXT_SOURCE_STATE_ERROR = -1;
+ private static final int NEXT_SOURCE_STATE_INIT = 0;
+ private static final int NEXT_SOURCE_STATE_PREPARING = 1;
+ private static final int NEXT_SOURCE_STATE_PREPARED = 2;
+
+ private static final String TAG = "MediaPlayer2";
+
+ private Context mContext;
+
+ private long mNativeContext; // accessed by native methods
+ private long mNativeSurfaceTexture; // accessed by native methods
+ private int mListenerContext; // accessed by native methods
+ private SurfaceHolder mSurfaceHolder;
+ private PowerManager.WakeLock mWakeLock = null;
+ private boolean mScreenOnWhilePlaying;
+ private boolean mStayAwake;
+
+ private final Object mSrcLock = new Object();
+ //--- guarded by |mSrcLock| start
+ private SourceInfo mCurrentSourceInfo;
+ private final Queue<SourceInfo> mNextSourceInfos = new ConcurrentLinkedQueue<>();
+ //--- guarded by |mSrcLock| end
+ private final AtomicLong mSrcIdGenerator = new AtomicLong(0);
+
+ private volatile float mVolume = 1.0f;
+ private VideoSize mVideoSize = new VideoSize(0, 0);
+
+ // TODO: create per-source drm fields in SourceInfo
+ // Modular DRM
+ private final Object mDrmLock = new Object();
+ //--- guarded by |mDrmLock| start
+ private UUID mDrmUUID;
+ private DrmInfo mDrmInfo;
+ private MediaDrm mDrmObj;
+ private byte[] mDrmSessionId;
+ private boolean mDrmInfoResolved;
+ private boolean mActiveDrmScheme;
+ private boolean mDrmConfigAllowed;
+ private boolean mDrmProvisioningInProgress;
+ private boolean mPrepareDrmInProgress;
+ private ProvisioningThread mDrmProvisioningThread;
+ //--- guarded by |mDrmLock| end
+
+ // Creating a dummy audio track, used for keeping session id alive
+ private final Object mSessionIdLock = new Object();
+ @GuardedBy("mSessionIdLock")
+ private AudioTrack mDummyAudioTrack;
+
+ private HandlerThread mHandlerThread;
+ private final TaskHandler mTaskHandler;
+ private final Object mTaskLock = new Object();
+ @GuardedBy("mTaskLock")
+ private final List<Task> mPendingTasks = new LinkedList<>();
+ @GuardedBy("mTaskLock")
+ private Task mCurrentTask;
+
+ @GuardedBy("mTaskLock")
+ boolean mIsPreviousCommandSeekTo = false;
+ // |mPreviousSeekPos| and |mPreviousSeekMode| are valid only when |mIsPreviousCommandSeekTo|
+ // is true, and they are accessed on |mHandlerThread| only.
+ long mPreviousSeekPos = -1;
+ int mPreviousSeekMode = SEEK_PREVIOUS_SYNC;
+
+ @GuardedBy("this")
+ private boolean mReleased;
+
private final CloseGuard mGuard = CloseGuard.get();
/**
- * Create a MediaPlayer2 object.
- *
- * @return A MediaPlayer2 object created
+ * Default constructor.
+ * <p>When done with the MediaPlayer2, you should call {@link #close()},
+ * to free the resources. If not released, too many MediaPlayer2 instances may
+ * result in an exception.</p>
*/
- public static final MediaPlayer2 create(Context context) {
- return new MediaPlayer2Impl(context);
+ public MediaPlayer2(Context context) {
+ mGuard.open("close");
+
+ mContext = context;
+ mHandlerThread = new HandlerThread("MediaPlayer2TaskThread");
+ mHandlerThread.start();
+ Looper looper = mHandlerThread.getLooper();
+ mTaskHandler = new TaskHandler(this, looper);
+ AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ int sessionId = am.generateAudioSessionId();
+ keepAudioSessionIdAlive(sessionId);
+
+ /* Native setup requires a weak reference to our object.
+ * It's easier to create it here than in C++.
+ */
+ native_setup(sessionId, new WeakReference<MediaPlayer2>(this));
}
- /**
- * @hide
- */
- // add hidden empty constructor so it doesn't show in SDK
- public MediaPlayer2() {
- mGuard.open("close");
- }
+ private native void native_setup(int sessionId, Object mediaplayer2This);
/**
* Releases the resources held by this {@code MediaPlayer2} object.
@@ -275,8 +394,41 @@
synchronized (mGuard) {
mGuard.close();
}
+ release();
}
+ private synchronized void release() {
+ if (mReleased) {
+ return;
+ }
+ stayAwake(false);
+ updateSurfaceScreenOn();
+ synchronized (mEventCbLock) {
+ mEventCallbackRecords.clear();
+ }
+ if (mHandlerThread != null) {
+ mHandlerThread.quitSafely();
+ mHandlerThread = null;
+ }
+
+ // Modular DRM clean up
+ mOnDrmConfigHelper = null;
+ synchronized (mDrmEventCbLock) {
+ mDrmEventCallbackRecords.clear();
+ }
+ resetDrmState();
+
+ native_release();
+
+ synchronized (mSessionIdLock) {
+ mDummyAudioTrack.release();
+ }
+
+ mReleased = true;
+ }
+
+ private native void native_release();
+
// Have to declare protected for finalize() since it is protected
// in the base class Object.
@Override
@@ -286,8 +438,51 @@
}
close();
+ native_finalize();
}
+ private native void native_finalize();
+
+ /**
+ * Resets the MediaPlayer2 to its uninitialized state. After calling
+ * this method, you will have to initialize it again by setting the
+ * data source and calling prepare().
+ */
+ // This is a synchronous call.
+ public void reset() {
+ synchronized (mEventCbLock) {
+ mEventCallbackRecords.clear();
+ }
+ synchronized (mDrmEventCbLock) {
+ mDrmEventCallbackRecords.clear();
+ }
+ synchronized (mSrcLock) {
+ mCurrentSourceInfo = null;
+ mNextSourceInfos.clear();
+ }
+
+ synchronized (mTaskLock) {
+ mPendingTasks.clear();
+ mIsPreviousCommandSeekTo = false;
+ }
+
+ stayAwake(false);
+ native_reset();
+
+ AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ int sessionId = am.generateAudioSessionId();
+ keepAudioSessionIdAlive(sessionId);
+
+ // make sure none of the listeners get called anymore
+ if (mTaskHandler != null) {
+ mTaskHandler.removeCallbacksAndMessages(null);
+ }
+
+ resetDrmState();
+ }
+
+ private native void native_reset();
+
/**
* Starts or resumes playback. If playback had previously been paused,
* playback will continue from where it was paused. If playback had
@@ -297,39 +492,78 @@
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
*/
// This is an asynchronous call.
- public abstract Object play();
+ public Object play() {
+ return addTask(new Task(CALL_COMPLETED_PLAY, false) {
+ @Override
+ void process() {
+ stayAwake(true);
+ native_start();
+ }
+ });
+ }
+
+ private native void native_start() throws IllegalStateException;
/**
* Prepares the player for playback, asynchronously.
*
- * After setting the datasource and the display surface, you need to
- * call prepare().
+ * After setting the datasource and the display surface, you need to call prepare().
*
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
*/
// This is an asynchronous call.
- public abstract Object prepare();
+ public Object prepare() {
+ return addTask(new Task(CALL_COMPLETED_PREPARE, true) {
+ @Override
+ void process() {
+ native_prepare();
+ }
+ });
+ }
+
+ private native void native_prepare();
/**
* Pauses playback. Call play() to resume.
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
*/
// This is an asynchronous call.
- public abstract Object pause();
+ public Object pause() {
+ return addTask(new Task(CALL_COMPLETED_PAUSE, false) {
+ @Override
+ void process() {
+ stayAwake(false);
+
+ native_pause();
+ }
+ });
+ }
+
+ private native void native_pause() throws IllegalStateException;
/**
* Tries to play next data source if applicable.
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
*/
// This is an asynchronous call.
- public abstract Object skipToNext();
+ public Object skipToNext() {
+ return addTask(new Task(CALL_COMPLETED_SKIP_TO_NEXT, false) {
+ @Override
+ void process() {
+ if (getState() == PLAYER_STATE_PLAYING) {
+ pause();
+ }
+ playNextDataSource();
+ }
+ });
+ }
/**
* Gets the current playback position.
*
* @return the current position in milliseconds
*/
- public abstract long getCurrentPosition();
+ public native long getCurrentPosition();
/**
* Gets the duration of the file.
@@ -337,7 +571,7 @@
* @return the duration in milliseconds, if no duration is available
* (for example, if streaming live content), -1 is returned.
*/
- public abstract long getDuration();
+ public native long getDuration();
/**
* Gets the current buffered media source position received through progressive downloading.
@@ -347,7 +581,18 @@
*
* @return the current buffered media source position in milliseconds
*/
- public abstract long getBufferedPosition();
+ public long getBufferedPosition() {
+ // Use cached buffered percent for now.
+ int bufferedPercentage;
+ synchronized (mSrcLock) {
+ if (mCurrentSourceInfo == null) {
+ bufferedPercentage = 0;
+ } else {
+ bufferedPercentage = mCurrentSourceInfo.mBufferedPercentage.get();
+ }
+ }
+ return getDuration() * bufferedPercentage / 100;
+ }
/**
* MediaPlayer2 has not been prepared or just has been reset.
@@ -396,7 +641,11 @@
*
* @return the current player state.
*/
- public abstract @MediaPlayer2State int getState();
+ public @MediaPlayer2State int getState() {
+ return native_getState();
+ }
+
+ private native int native_getState();
/**
* Sets the audio attributes for this MediaPlayer2.
@@ -407,13 +656,31 @@
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
*/
// This is an asynchronous call.
- public abstract Object setAudioAttributes(@NonNull AudioAttributes attributes);
+ public Object setAudioAttributes(@NonNull AudioAttributes attributes) {
+ return addTask(new Task(CALL_COMPLETED_SET_AUDIO_ATTRIBUTES, false) {
+ @Override
+ void process() {
+ if (attributes == null) {
+ final String msg = "Cannot set AudioAttributes to null";
+ throw new IllegalArgumentException(msg);
+ }
+ native_setAudioAttributes(attributes);
+ }
+ });
+ }
+
+ // return true if the parameter is set successfully, false otherwise
+ private native boolean native_setAudioAttributes(AudioAttributes audioAttributes);
/**
* Gets the audio attributes for this MediaPlayer2.
* @return attributes a set of audio attributes
*/
- public abstract @Nullable AudioAttributes getAudioAttributes();
+ public @NonNull AudioAttributes getAudioAttributes() {
+ return native_getAudioAttributes();
+ }
+
+ private native AudioAttributes native_getAudioAttributes();
/**
* Sets the data source as described by a DataSourceDesc.
@@ -422,7 +689,23 @@
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
*/
// This is an asynchronous call.
- public abstract Object setDataSource(@NonNull DataSourceDesc dsd);
+ public Object setDataSource(@NonNull DataSourceDesc dsd) {
+ return addTask(new Task(CALL_COMPLETED_SET_DATA_SOURCE, false) {
+ @Override
+ void process() throws IOException {
+ checkArgument(dsd != null, "the DataSourceDesc cannot be null");
+ int state = getState();
+ if (state != PLAYER_STATE_ERROR && state != PLAYER_STATE_IDLE) {
+ throw new IllegalStateException("called in wrong state " + state);
+ }
+
+ synchronized (mSrcLock) {
+ mCurrentSourceInfo = new SourceInfo(dsd);
+ handleDataSource(true /* isCurrent */, dsd, mCurrentSourceInfo.mId);
+ }
+ }
+ });
+ }
/**
* Sets a single data source as described by a DataSourceDesc which will be played
@@ -432,7 +715,19 @@
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
*/
// This is an asynchronous call.
- public abstract Object setNextDataSource(@NonNull DataSourceDesc dsd);
+ public Object setNextDataSource(@NonNull DataSourceDesc dsd) {
+ return addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCE, false) {
+ @Override
+ void process() {
+ checkArgument(dsd != null, "the DataSourceDesc cannot be null");
+ synchronized (mSrcLock) {
+ mNextSourceInfos.clear();
+ mNextSourceInfos.add(new SourceInfo(dsd));
+ }
+ prepareNextDataSource();
+ }
+ });
+ }
/**
* Sets a list of data sources to be played sequentially after current data source is done.
@@ -441,21 +736,367 @@
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
*/
// This is an asynchronous call.
- public abstract Object setNextDataSources(@NonNull List<DataSourceDesc> dsds);
+ public Object setNextDataSources(@NonNull List<DataSourceDesc> dsds) {
+ return addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCES, false) {
+ @Override
+ void process() {
+ if (dsds == null || dsds.size() == 0) {
+ throw new IllegalArgumentException("data source list cannot be null or empty.");
+ }
+ for (DataSourceDesc dsd : dsds) {
+ if (dsd == null) {
+ throw new IllegalArgumentException(
+ "DataSourceDesc in the source list cannot be null.");
+ }
+ }
+
+ synchronized (mSrcLock) {
+ mNextSourceInfos.clear();
+ for (DataSourceDesc dsd : dsds) {
+ mNextSourceInfos.add(new SourceInfo(dsd));
+ }
+ }
+ prepareNextDataSource();
+ }
+ });
+ }
/**
* Removes all data sources pending to be played.
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
*/
// This is an asynchronous call.
- public abstract Object clearNextDataSources();
+ public Object clearNextDataSources() {
+ return addTask(new Task(CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES, false) {
+ @Override
+ void process() {
+ mNextSourceInfos.clear();
+ }
+ });
+ }
/**
* Gets the current data source as described by a DataSourceDesc.
*
* @return the current DataSourceDesc
*/
- public abstract @NonNull DataSourceDesc getCurrentDataSource();
+ public DataSourceDesc getCurrentDataSource() {
+ synchronized (mSrcLock) {
+ return mCurrentSourceInfo == null ? null : mCurrentSourceInfo.mDSD;
+ }
+ }
+
+ private void handleDataSource(boolean isCurrent, @NonNull DataSourceDesc dsd, long srcId)
+ throws IOException {
+ checkArgument(dsd != null, "the DataSourceDesc cannot be null");
+
+ switch (dsd.getType()) {
+ case DataSourceDesc.TYPE_CALLBACK:
+ handleDataSource(isCurrent,
+ srcId,
+ dsd.getMedia2DataSource(),
+ dsd.getStartPosition(),
+ dsd.getEndPosition());
+ break;
+
+ case DataSourceDesc.TYPE_FD:
+ handleDataSource(isCurrent,
+ srcId,
+ dsd.getFileDescriptor(),
+ dsd.getFileDescriptorOffset(),
+ dsd.getFileDescriptorLength(),
+ dsd.getStartPosition(),
+ dsd.getEndPosition());
+ break;
+
+ case DataSourceDesc.TYPE_URI:
+ handleDataSource(isCurrent,
+ srcId,
+ dsd.getUriContext(),
+ dsd.getUri(),
+ dsd.getUriHeaders(),
+ dsd.getUriCookies(),
+ dsd.getStartPosition(),
+ dsd.getEndPosition());
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /**
+ * To provide cookies for the subsequent HTTP requests, you can install your own default cookie
+ * handler and use other variants of setDataSource APIs instead. Alternatively, you can use
+ * this API to pass the cookies as a list of HttpCookie. If the app has not installed
+ * a CookieHandler already, this API creates a CookieManager and populates its CookieStore with
+ * the provided cookies. If the app has installed its own handler already, this API requires the
+ * handler to be of CookieManager type such that the API can update the manager’s CookieStore.
+ *
+ * <p><strong>Note</strong> that the cross domain redirection is allowed by default,
+ * but that can be changed with key/value pairs through the headers parameter with
+ * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
+ * disallow or allow cross domain redirection.
+ *
+ * @throws IllegalArgumentException if cookies are provided and the installed handler is not
+ * a CookieManager
+ * @throws IllegalStateException if it is called in an invalid state
+ * @throws NullPointerException if context or uri is null
+ * @throws IOException if uri has a file scheme and an I/O error occurs
+ */
+ private void handleDataSource(
+ boolean isCurrent, long srcId,
+ @NonNull Context context, @NonNull Uri uri,
+ @Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies,
+ long startPos, long endPos)
+ throws IOException {
+ // The context and URI usually belong to the calling user. Get a resolver for that user.
+ final ContentResolver resolver = context.getContentResolver();
+ final String scheme = uri.getScheme();
+ if (ContentResolver.SCHEME_FILE.equals(scheme)) {
+ handleDataSource(isCurrent, srcId, uri.getPath(), null, null, startPos, endPos);
+ return;
+ }
+
+ final int ringToneType = RingtoneManager.getDefaultType(uri);
+ try {
+ AssetFileDescriptor afd;
+ // Try requested Uri locally first
+ if (ContentResolver.SCHEME_CONTENT.equals(scheme) && ringToneType != -1) {
+ afd = RingtoneManager.openDefaultRingtoneUri(context, uri);
+ if (attemptDataSource(isCurrent, srcId, afd, startPos, endPos)) {
+ return;
+ }
+ final Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(
+ context, ringToneType);
+ afd = resolver.openAssetFileDescriptor(actualUri, "r");
+ } else {
+ afd = resolver.openAssetFileDescriptor(uri, "r");
+ }
+ if (attemptDataSource(isCurrent, srcId, afd, startPos, endPos)) {
+ return;
+ }
+ } catch (NullPointerException | SecurityException | IOException ex) {
+ Log.w(TAG, "Couldn't open " + uri + ": " + ex);
+ // Fallback to media server
+ }
+ handleDataSource(isCurrent, srcId, uri.toString(), headers, cookies, startPos, endPos);
+ }
+
+ private boolean attemptDataSource(boolean isCurrent, long srcId, AssetFileDescriptor afd,
+ long startPos, long endPos) throws IOException {
+ try {
+ if (afd.getDeclaredLength() < 0) {
+ handleDataSource(isCurrent,
+ srcId,
+ afd.getFileDescriptor(),
+ 0,
+ DataSourceDesc.LONG_MAX,
+ startPos,
+ endPos);
+ } else {
+ handleDataSource(isCurrent,
+ srcId,
+ afd.getFileDescriptor(),
+ afd.getStartOffset(),
+ afd.getDeclaredLength(),
+ startPos,
+ endPos);
+ }
+ return true;
+ } catch (NullPointerException | SecurityException | IOException ex) {
+ Log.w(TAG, "Couldn't open srcId:" + srcId + ": " + ex);
+ return false;
+ } finally {
+ if (afd != null) {
+ afd.close();
+ }
+ }
+ }
+
+ private void handleDataSource(
+ boolean isCurrent, long srcId,
+ String path, Map<String, String> headers, List<HttpCookie> cookies,
+ long startPos, long endPos)
+ throws IOException {
+ String[] keys = null;
+ String[] values = null;
+
+ if (headers != null) {
+ keys = new String[headers.size()];
+ values = new String[headers.size()];
+
+ int i = 0;
+ for (Map.Entry<String, String> entry: headers.entrySet()) {
+ keys[i] = entry.getKey();
+ values[i] = entry.getValue();
+ ++i;
+ }
+ }
+ handleDataSource(isCurrent, srcId, path, keys, values, cookies, startPos, endPos);
+ }
+
+ private void handleDataSource(boolean isCurrent, long srcId,
+ String path, String[] keys, String[] values, List<HttpCookie> cookies,
+ long startPos, long endPos)
+ throws IOException {
+ final Uri uri = Uri.parse(path);
+ final String scheme = uri.getScheme();
+ if ("file".equals(scheme)) {
+ path = uri.getPath();
+ } else if (scheme != null) {
+ // handle non-file sources
+ Media2Utils.storeCookies(cookies);
+ nativeHandleDataSourceUrl(
+ isCurrent,
+ srcId,
+ Media2HTTPService.createHTTPService(path),
+ path,
+ keys,
+ values,
+ startPos,
+ endPos);
+ return;
+ }
+
+ final File file = new File(path);
+ if (file.exists()) {
+ FileInputStream is = new FileInputStream(file);
+ FileDescriptor fd = is.getFD();
+ handleDataSource(isCurrent, srcId, fd, 0, DataSourceDesc.LONG_MAX, startPos, endPos);
+ is.close();
+ } else {
+ throw new IOException("handleDataSource failed.");
+ }
+ }
+
+ private native void nativeHandleDataSourceUrl(
+ boolean isCurrent, long srcId,
+ Media2HTTPService httpService, String path, String[] keys, String[] values,
+ long startPos, long endPos)
+ throws IOException;
+
+ /**
+ * Sets the data source (FileDescriptor) to use. The FileDescriptor must be
+ * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility
+ * to close the file descriptor. It is safe to do so as soon as this call returns.
+ *
+ * @throws IllegalStateException if it is called in an invalid state
+ * @throws IllegalArgumentException if fd is not a valid FileDescriptor
+ * @throws IOException if fd can not be read
+ */
+ private void handleDataSource(
+ boolean isCurrent, long srcId,
+ FileDescriptor fd, long offset, long length,
+ long startPos, long endPos) throws IOException {
+ nativeHandleDataSourceFD(isCurrent, srcId, fd, offset, length, startPos, endPos);
+ }
+
+ private native void nativeHandleDataSourceFD(boolean isCurrent, long srcId,
+ FileDescriptor fd, long offset, long length,
+ long startPos, long endPos) throws IOException;
+
+ /**
+ * @throws IllegalStateException if it is called in an invalid state
+ * @throws IllegalArgumentException if dataSource is not a valid Media2DataSource
+ */
+ private void handleDataSource(boolean isCurrent, long srcId, Media2DataSource dataSource,
+ long startPos, long endPos) {
+ nativeHandleDataSourceCallback(isCurrent, srcId, dataSource, startPos, endPos);
+ }
+
+ private native void nativeHandleDataSourceCallback(
+ boolean isCurrent, long srcId, Media2DataSource dataSource,
+ long startPos, long endPos);
+
+ // return true if there is a next data source, false otherwise.
+ // This function should be always called on |mHandlerThread|.
+ private boolean prepareNextDataSource() {
+ HandlerThread handlerThread = mHandlerThread;
+ if (handlerThread != null && Looper.myLooper() != handlerThread.getLooper()) {
+ Log.e(TAG, "prepareNextDataSource: called on wrong looper");
+ }
+
+ boolean hasNextDSD;
+ int state = getState();
+ synchronized (mSrcLock) {
+ hasNextDSD = !mNextSourceInfos.isEmpty();
+ if (state == PLAYER_STATE_ERROR || state == PLAYER_STATE_IDLE) {
+ // Current source has not been prepared yet.
+ return hasNextDSD;
+ }
+
+ SourceInfo nextSource = mNextSourceInfos.peek();
+ if (!hasNextDSD || nextSource.mStateAsNextSource != NEXT_SOURCE_STATE_INIT) {
+ // There is no next source or it's in preparing or prepared state.
+ return hasNextDSD;
+ }
+
+ try {
+ nextSource.mStateAsNextSource = NEXT_SOURCE_STATE_PREPARING;
+ handleDataSource(false /* isCurrent */, nextSource.mDSD, nextSource.mId);
+ } catch (Exception e) {
+ Message msg = mTaskHandler.obtainMessage(
+ MEDIA_ERROR, MEDIA_ERROR_IO, MEDIA_ERROR_UNKNOWN, null);
+ mTaskHandler.handleMessage(msg, nextSource.mId);
+
+ mNextSourceInfos.poll();
+ return prepareNextDataSource();
+ }
+ }
+ return hasNextDSD;
+ }
+
+ // This function should be always called on |mHandlerThread|.
+ private void playNextDataSource() {
+ HandlerThread handlerThread = mHandlerThread;
+ if (handlerThread != null && Looper.myLooper() != handlerThread.getLooper()) {
+ Log.e(TAG, "playNextDataSource: called on wrong looper");
+ }
+
+ boolean hasNextDSD = false;
+ synchronized (mSrcLock) {
+ if (!mNextSourceInfos.isEmpty()) {
+ hasNextDSD = true;
+ SourceInfo nextSourceInfo = mNextSourceInfos.peek();
+ if (nextSourceInfo.mStateAsNextSource == NEXT_SOURCE_STATE_PREPARED) {
+ // Switch to next source only when it has been prepared.
+ mCurrentSourceInfo = mNextSourceInfos.poll();
+
+ long srcId = mCurrentSourceInfo.mId;
+ try {
+ nativePlayNextDataSource(srcId);
+ } catch (Exception e) {
+ Message msg2 = mTaskHandler.obtainMessage(
+ MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null);
+ mTaskHandler.handleMessage(msg2, srcId);
+ // Keep |mNextSourcePlayPending|
+ hasNextDSD = prepareNextDataSource();
+ }
+ if (hasNextDSD) {
+ stayAwake(true);
+
+ // Now a new current src is playing.
+ // Wait for MEDIA_INFO_DATA_SOURCE_START to prepare next source.
+ }
+ } else if (nextSourceInfo.mStateAsNextSource == NEXT_SOURCE_STATE_INIT) {
+ hasNextDSD = prepareNextDataSource();
+ }
+ }
+ }
+
+ if (!hasNextDSD) {
+ sendEvent(new EventNotifier() {
+ @Override
+ public void notify(EventCallback callback) {
+ callback.onInfo(
+ MediaPlayer2.this, null, MEDIA_INFO_DATA_SOURCE_LIST_END, 0);
+ }
+ });
+ }
+ }
+
+ private native void nativePlayNextDataSource(long srcId);
/**
* Configures the player to loop on the current data source.
@@ -463,7 +1104,16 @@
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
*/
// This is an asynchronous call.
- public abstract Object loopCurrent(boolean loop);
+ public Object loopCurrent(boolean loop) {
+ return addTask(new Task(CALL_COMPLETED_LOOP_CURRENT, false) {
+ @Override
+ void process() {
+ setLooping(loop);
+ }
+ });
+ }
+
+ private native void setLooping(boolean looping);
/**
* Sets the volume of the audio of the media to play, expressed as a linear multiplier
@@ -476,14 +1126,26 @@
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
*/
// This is an asynchronous call.
- public abstract Object setPlayerVolume(float volume);
+ public Object setPlayerVolume(float volume) {
+ return addTask(new Task(CALL_COMPLETED_SET_PLAYER_VOLUME, false) {
+ @Override
+ void process() {
+ mVolume = volume;
+ native_setVolume(volume);
+ }
+ });
+ }
+
+ private native void native_setVolume(float volume);
/**
* Returns the current volume of this player.
* Note that it does not take into account the associated stream volume.
* @return the player volume.
*/
- public abstract float getPlayerVolume();
+ public float getPlayerVolume() {
+ return mVolume;
+ }
/**
* @return the maximum volume that can be used in {@link #setPlayerVolume(float)}.
@@ -505,7 +1167,20 @@
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
*/
// This is an asynchronous call.
- public abstract Object notifyWhenCommandLabelReached(@NonNull Object label);
+ public Object notifyWhenCommandLabelReached(@NonNull Object label) {
+ return addTask(new Task(CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED, false) {
+ @Override
+ void process() {
+ sendEvent(new EventNotifier() {
+ @Override
+ public void notify(EventCallback callback) {
+ callback.onCommandLabelReached(
+ MediaPlayer2.this, label);
+ }
+ });
+ }
+ });
+ }
/**
* Sets the {@link SurfaceHolder} to use for displaying the video
@@ -520,7 +1195,22 @@
* @param sh the SurfaceHolder to use for video display
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
*/
- public abstract Object setDisplay(SurfaceHolder sh);
+ public Object setDisplay(SurfaceHolder sh) {
+ return addTask(new Task(CALL_COMPLETED_SET_DISPLAY, false) {
+ @Override
+ void process() {
+ mSurfaceHolder = sh;
+ Surface surface;
+ if (sh != null) {
+ surface = sh.getSurface();
+ } else {
+ surface = null;
+ }
+ native_setVideoSurface(surface);
+ updateSurfaceScreenOn();
+ }
+ });
+ }
/**
* Sets the {@link Surface} to be used as the sink for the video portion of
@@ -541,7 +1231,21 @@
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
*/
// This is an asynchronous call.
- public abstract Object setSurface(Surface surface);
+ public Object setSurface(Surface surface) {
+ return addTask(new Task(CALL_COMPLETED_SET_SURFACE, false) {
+ @Override
+ void process() {
+ if (mScreenOnWhilePlaying && surface != null) {
+ Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective for Surface");
+ }
+ mSurfaceHolder = null;
+ native_setVideoSurface(surface);
+ updateSurfaceScreenOn();
+ }
+ });
+ }
+
+ private native void native_setVideoSurface(Surface surface);
/**
* Set the low-level power management behavior for this MediaPlayer2. This
@@ -562,7 +1266,42 @@
* @see android.os.PowerManager
*/
// This is an asynchronous call.
- public abstract Object setWakeMode(Context context, int mode);
+ public Object setWakeMode(Context context, int mode) {
+ return addTask(new Task(CALL_COMPLETED_SET_WAKE_MODE, false) {
+ @Override
+ void process() {
+ boolean washeld = false;
+
+ if (mWakeLock != null) {
+ if (mWakeLock.isHeld()) {
+ washeld = true;
+ mWakeLock.release();
+ }
+ mWakeLock = null;
+ }
+
+ PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ ActivityManager am =
+ (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ List<RunningAppProcessInfo> runningAppsProcInfo = am.getRunningAppProcesses();
+ int pid = android.os.Process.myPid();
+ String name = "pid " + String.valueOf(pid);
+ if (runningAppsProcInfo != null) {
+ for (RunningAppProcessInfo procInfo : runningAppsProcInfo) {
+ if (procInfo.pid == pid) {
+ name = procInfo.processName;
+ break;
+ }
+ }
+ }
+ mWakeLock = pm.newWakeLock(mode | PowerManager.ON_AFTER_RELEASE, name);
+ mWakeLock.setReferenceCounted(false);
+ if (washeld) {
+ mWakeLock.acquire();
+ }
+ }
+ });
+ }
/**
* Control whether we should use the attached SurfaceHolder to keep the
@@ -575,7 +1314,39 @@
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
*/
// This is an asynchronous call.
- public abstract Object setScreenOnWhilePlaying(boolean screenOn);
+ public Object setScreenOnWhilePlaying(boolean screenOn) {
+ return addTask(new Task(CALL_COMPLETED_SET_SCREEN_ON_WHILE_PLAYING, false) {
+ @Override
+ void process() {
+ if (mScreenOnWhilePlaying != screenOn) {
+ if (screenOn && mSurfaceHolder == null) {
+ Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective"
+ + " without a SurfaceHolder");
+ }
+ mScreenOnWhilePlaying = screenOn;
+ updateSurfaceScreenOn();
+ }
+ }
+ });
+ }
+
+ private void stayAwake(boolean awake) {
+ if (mWakeLock != null) {
+ if (awake && !mWakeLock.isHeld()) {
+ mWakeLock.acquire();
+ } else if (!awake && mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ }
+ mStayAwake = awake;
+ updateSurfaceScreenOn();
+ }
+
+ private void updateSurfaceScreenOn() {
+ if (mSurfaceHolder != null) {
+ mSurfaceHolder.setKeepScreenOn(mScreenOnWhilePlaying && mStayAwake);
+ }
+ }
/**
* Cancels a pending command.
@@ -584,17 +1355,26 @@
* @return {@code false} if the task could not be cancelled; {@code true} otherwise.
*/
// This is a synchronous call.
- public abstract boolean cancelCommand(Object token);
+ public boolean cancelCommand(Object token) {
+ synchronized (mTaskLock) {
+ return mPendingTasks.remove(token);
+ }
+ }
/**
* Discards all pending commands.
*/
// This is a synchronous call.
- public abstract void clearPendingCommands();
+ public void clearPendingCommands() {
+ synchronized (mTaskLock) {
+ mPendingTasks.clear();
+ }
+ }
//--------------------------------------------------------------------------
// Explicit Routing
//--------------------
+ private AudioDeviceInfo mPreferredDevice = null;
/**
* Specifies an audio device (via an {@link AudioDeviceInfo} object) to route
@@ -606,14 +1386,28 @@
*/
// This is a synchronous call.
@Override
- public abstract boolean setPreferredDevice(AudioDeviceInfo deviceInfo);
+ public boolean setPreferredDevice(AudioDeviceInfo deviceInfo) {
+ boolean status = native_setPreferredDevice(deviceInfo);
+ if (status) {
+ synchronized (this) {
+ mPreferredDevice = deviceInfo;
+ }
+ }
+ return status;
+ }
+
+ private native boolean native_setPreferredDevice(AudioDeviceInfo device);
/**
* Returns the selected output specified by {@link #setPreferredDevice}. Note that this
* is not guaranteed to correspond to the actual device being used for playback.
*/
@Override
- public abstract AudioDeviceInfo getPreferredDevice();
+ public AudioDeviceInfo getPreferredDevice() {
+ synchronized (this) {
+ return mPreferredDevice;
+ }
+ }
/**
* Returns an {@link AudioDeviceInfo} identifying the current routing of this MediaPlayer2
@@ -622,7 +1416,7 @@
* selected device when the player was last active.
*/
@Override
- public abstract AudioDeviceInfo getRoutedDevice();
+ public native AudioDeviceInfo getRoutedDevice();
/**
* Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing
@@ -634,8 +1428,16 @@
*/
// This is a synchronous call.
@Override
- public abstract void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener,
- Handler handler);
+ public void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener,
+ Handler handler) {
+ if (listener == null) {
+ throw new IllegalArgumentException("addOnRoutingChangedListener: listener is NULL");
+ }
+ RoutingDelegate routingDelegate = new RoutingDelegate(this, listener, handler);
+ native_addDeviceCallback(routingDelegate);
+ }
+
+ private native void native_addDeviceCallback(RoutingDelegate rd);
/**
* Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added
@@ -645,7 +1447,14 @@
*/
// This is a synchronous call.
@Override
- public abstract void removeOnRoutingChangedListener(
+ public void removeOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("removeOnRoutingChangedListener: listener is NULL");
+ }
+ native_removeDeviceCallback(listener);
+ }
+
+ private native void native_removeDeviceCallback(
AudioRouting.OnRoutingChangedListener listener);
/**
@@ -658,7 +1467,9 @@
* notification {@code EventCallback.onVideoSizeChanged} when the size
* is available.
*/
- public abstract VideoSize getVideoSize();
+ public VideoSize getVideoSize() {
+ return mVideoSize;
+ }
/**
* Return Metrics data about the current player.
@@ -669,7 +1480,13 @@
*
* Additional vendor-specific fields may also be present in the return value.
*/
- public abstract PersistableBundle getMetrics();
+ public PersistableBundle getMetrics() {
+ PersistableBundle bundle = native_getMetrics();
+ return bundle;
+ }
+
+ private native PersistableBundle native_getMetrics();
+
/**
* Gets the current buffering management params used by the source component.
@@ -682,9 +1499,7 @@
*/
// TODO: make it public when ready
@NonNull
- BufferingParams getBufferingParams() {
- return new BufferingParams.Builder().build();
- }
+ native BufferingParams getBufferingParams();
/**
* Sets buffering management params.
@@ -698,7 +1513,18 @@
*/
// TODO: make it public when ready
// This is an asynchronous call.
- abstract Object setBufferingParams(@NonNull BufferingParams params);
+ Object setBufferingParams(@NonNull BufferingParams params) {
+ return addTask(new Task(CALL_COMPLETED_SET_BUFFERING_PARAMS, false) {
+ @Override
+ void process() {
+ checkArgument(params != null, "the BufferingParams cannot be null");
+ native_setBufferingParams(params);
+ }
+ });
+ }
+
+ private native void native_setBufferingParams(@NonNull BufferingParams params);
+
/**
* Sets playback rate using {@link PlaybackParams}. The object sets its internal
@@ -710,7 +1536,17 @@
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
*/
// This is an asynchronous call.
- public abstract Object setPlaybackParams(@NonNull PlaybackParams params);
+ public Object setPlaybackParams(@NonNull PlaybackParams params) {
+ return addTask(new Task(CALL_COMPLETED_SET_PLAYBACK_PARAMS, false) {
+ @Override
+ void process() {
+ checkArgument(params != null, "the PlaybackParams cannot be null");
+ native_setPlaybackParams(params);
+ }
+ });
+ }
+
+ private native void native_setPlaybackParams(@NonNull PlaybackParams params);
/**
* Gets the playback params, containing the current playback rate.
@@ -719,7 +1555,7 @@
* @throws IllegalStateException if the internal player engine has not been initialized.
*/
@NonNull
- public abstract PlaybackParams getPlaybackParams();
+ public native PlaybackParams getPlaybackParams();
/**
* Sets A/V sync mode.
@@ -728,7 +1564,17 @@
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
*/
// This is an asynchronous call.
- public abstract Object setSyncParams(@NonNull SyncParams params);
+ public Object setSyncParams(@NonNull SyncParams params) {
+ return addTask(new Task(CALL_COMPLETED_SET_SYNC_PARAMS, false) {
+ @Override
+ void process() {
+ checkArgument(params != null, "the SyncParams cannot be null");
+ native_setSyncParams(params);
+ }
+ });
+ }
+
+ private native void native_setSyncParams(@NonNull SyncParams params);
/**
* Gets the A/V sync mode.
@@ -737,7 +1583,7 @@
* @throws IllegalStateException if the internal player engine has not been initialized.
*/
@NonNull
- public abstract SyncParams getSyncParams();
+ public native SyncParams getSyncParams();
/**
* Moves the media to specified time position.
@@ -821,7 +1667,47 @@
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
*/
// This is an asynchronous call.
- public abstract Object seekTo(long msec, @SeekMode int mode);
+ public Object seekTo(long msec, @SeekMode int mode) {
+ return addTask(new Task(CALL_COMPLETED_SEEK_TO, true) {
+ @Override
+ void process() {
+ if (mode < SEEK_PREVIOUS_SYNC || mode > SEEK_CLOSEST) {
+ final String msg = "Illegal seek mode: " + mode;
+ throw new IllegalArgumentException(msg);
+ }
+ // TODO: pass long to native, instead of truncating here.
+ long posMs = msec;
+ if (posMs > Integer.MAX_VALUE) {
+ Log.w(TAG, "seekTo offset " + posMs + " is too large, cap to "
+ + Integer.MAX_VALUE);
+ posMs = Integer.MAX_VALUE;
+ } else if (posMs < Integer.MIN_VALUE) {
+ Log.w(TAG, "seekTo offset " + posMs + " is too small, cap to "
+ + Integer.MIN_VALUE);
+ posMs = Integer.MIN_VALUE;
+ }
+
+ synchronized (mTaskLock) {
+ if (mIsPreviousCommandSeekTo
+ && mPreviousSeekPos == posMs
+ && mPreviousSeekMode == mode) {
+ throw new CommandSkippedException(
+ "same as previous seekTo");
+ }
+ }
+
+ native_seekTo(posMs, mode);
+
+ synchronized (mTaskLock) {
+ mIsPreviousCommandSeekTo = true;
+ mPreviousSeekPos = posMs;
+ mPreviousSeekMode = mode;
+ }
+ }
+ });
+ }
+
+ private native void native_seekTo(long msec, int mode);
/**
* Get current playback position as a {@link MediaTimestamp}.
@@ -842,25 +1728,25 @@
* @see MediaTimestamp
*/
@Nullable
- public abstract MediaTimestamp getTimestamp();
-
- /**
- * Resets the MediaPlayer2 to its uninitialized state. After calling
- * this method, you will have to initialize it again by setting the
- * data source and calling prepare().
- */
- // This is a synchronous call.
- public abstract void reset();
+ public MediaTimestamp getTimestamp() {
+ try {
+ // TODO: get the timestamp from native side
+ return new MediaTimestamp(
+ getCurrentPosition() * 1000L,
+ System.nanoTime(),
+ getState() == PLAYER_STATE_PLAYING ? getPlaybackParams().getSpeed() : 0.f);
+ } catch (IllegalStateException e) {
+ return null;
+ }
+ }
/**
* Checks whether the MediaPlayer2 is looping or non-looping.
*
* @return true if the MediaPlayer2 is currently looping, false otherwise
- * @hide
*/
- public boolean isLooping() {
- return false;
- }
+ // This is a synchronous call.
+ public native boolean isLooping();
/**
* Sets the audio session ID.
@@ -875,19 +1761,32 @@
* When created, a MediaPlayer2 instance automatically generates its own audio session ID.
* However, it is possible to force this player to be part of an already existing audio session
* by calling this method.
- * This method must be called before one of the overloaded <code> setDataSource </code> methods.
+ * This method must be called when player is in {@link #PLAYER_STATE_IDLE} or
+ * {@link #PLAYER_STATE_PREPARED} state in order to have sessionId take effect.
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
*/
// This is an asynchronous call.
- public abstract Object setAudioSessionId(int sessionId);
+ public Object setAudioSessionId(int sessionId) {
+ keepAudioSessionIdAlive(sessionId);
+ return addTask(new Task(CALL_COMPLETED_SET_AUDIO_SESSION_ID, false) {
+ @Override
+ void process() {
+ native_setAudioSessionId(sessionId);
+ }
+ });
+ }
+
+ private native void native_setAudioSessionId(int sessionId);
/**
* Returns the audio session ID.
*
* @return the audio session ID. {@see #setAudioSessionId(int)}
- * Note that the audio session ID is 0 only if a problem occured when the MediaPlayer2 was contructed.
+ * Note that the audio session ID is 0 only if a problem occured when the MediaPlayer2 was
+ * contructed.
*/
- public abstract int getAudioSessionId();
+ // This is a synchronous call.
+ public native int getAudioSessionId();
/**
* Attaches an auxiliary effect to the player. A typical auxiliary effect is a reverberation
@@ -905,8 +1804,16 @@
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
*/
// This is an asynchronous call.
- public abstract Object attachAuxEffect(int effectId);
+ public Object attachAuxEffect(int effectId) {
+ return addTask(new Task(CALL_COMPLETED_ATTACH_AUX_EFFECT, false) {
+ @Override
+ void process() {
+ native_attachAuxEffect(effectId);
+ }
+ });
+ }
+ private native void native_attachAuxEffect(int effectId);
/**
* Sets the send level of the player to the attached auxiliary effect.
@@ -922,20 +1829,72 @@
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
*/
// This is an asynchronous call.
- public abstract Object setAuxEffectSendLevel(float level);
+ public Object setAuxEffectSendLevel(float level) {
+ return addTask(new Task(CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL, false) {
+ @Override
+ void process() {
+ native_setAuxEffectSendLevel(level);
+ }
+ });
+ }
+
+ private native void native_setAuxEffectSendLevel(float level);
+
+ private static native void native_stream_event_onTearDown(
+ long nativeCallbackPtr, long userDataPtr);
+ private static native void native_stream_event_onStreamPresentationEnd(
+ long nativeCallbackPtr, long userDataPtr);
+ private static native void native_stream_event_onStreamDataRequest(
+ long jAudioTrackPtr, long nativeCallbackPtr, long userDataPtr);
+
+ /* Do not change these values (starting with INVOKE_ID) without updating
+ * their counterparts in include/media/mediaplayer2.h!
+ */
+ private static final int INVOKE_ID_GET_TRACK_INFO = 1;
+ private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE = 2;
+ private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE_FD = 3;
+ private static final int INVOKE_ID_SELECT_TRACK = 4;
+ private static final int INVOKE_ID_DESELECT_TRACK = 5;
+ private static final int INVOKE_ID_GET_SELECTED_TRACK = 7;
+
+ /**
+ * Invoke a generic method on the native player using opaque protocol
+ * buffer message for the request and reply. Both payloads' format is a
+ * convention between the java caller and the native player.
+ *
+ * @param msg PlayerMessage for the extension.
+ *
+ * @return PlayerMessage with the data returned by the
+ * native player.
+ */
+ private PlayerMessage invoke(PlayerMessage msg) {
+ byte[] ret = native_invoke(msg.toByteArray());
+ if (ret == null) {
+ return null;
+ }
+ try {
+ return PlayerMessage.parseFrom(ret);
+ } catch (InvalidProtocolBufferException e) {
+ return null;
+ }
+ }
+
+ private native byte[] native_invoke(byte[] request);
/**
* Class for MediaPlayer2 to return each audio/video/subtitle track's metadata.
*
* @see MediaPlayer2#getTrackInfo
*/
- public abstract static class TrackInfo {
+ public static class TrackInfo {
/**
* Gets the track type.
* @return TrackType which indicates if the track is video, audio, timed text.
*/
@UnsupportedAppUsage
- public abstract int getTrackType();
+ public int getTrackType() {
+ return mTrackType;
+ }
/**
* Gets the language code of the track.
@@ -944,13 +1903,22 @@
* ISO-639-2 language code, "und", is returned.
*/
@UnsupportedAppUsage
- public abstract String getLanguage();
+ public String getLanguage() {
+ String language = mFormat.getString(MediaFormat.KEY_LANGUAGE);
+ return language == null ? "und" : language;
+ }
/**
* Gets the {@link MediaFormat} of the track. If the format is
* unknown or could not be determined, null is returned.
*/
- public abstract MediaFormat getFormat();
+ public MediaFormat getFormat() {
+ if (mTrackType == MEDIA_TRACK_TYPE_TIMEDTEXT
+ || mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) {
+ return mFormat;
+ }
+ return null;
+ }
public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0;
public static final int MEDIA_TRACK_TYPE_VIDEO = 1;
@@ -962,8 +1930,56 @@
public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4;
public static final int MEDIA_TRACK_TYPE_METADATA = 5;
+ final int mTrackType;
+ final MediaFormat mFormat;
+
+ TrackInfo(Iterator<Value> in) {
+ mTrackType = in.next().getInt32Value();
+ // TODO: build the full MediaFormat; currently we are using createSubtitleFormat
+ // even for audio/video tracks, meaning we only set the mime and language.
+ String mime = in.next().getStringValue();
+ String language = in.next().getStringValue();
+ mFormat = MediaFormat.createSubtitleFormat(mime, language);
+
+ if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) {
+ mFormat.setInteger(MediaFormat.KEY_IS_AUTOSELECT, in.next().getInt32Value());
+ mFormat.setInteger(MediaFormat.KEY_IS_DEFAULT, in.next().getInt32Value());
+ mFormat.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, in.next().getInt32Value());
+ }
+ }
+
+ /** @hide */
+ TrackInfo(int type, MediaFormat format) {
+ mTrackType = type;
+ mFormat = format;
+ }
+
@Override
- public abstract String toString();
+ public String toString() {
+ StringBuilder out = new StringBuilder(128);
+ out.append(getClass().getName());
+ out.append('{');
+ switch (mTrackType) {
+ case MEDIA_TRACK_TYPE_VIDEO:
+ out.append("VIDEO");
+ break;
+ case MEDIA_TRACK_TYPE_AUDIO:
+ out.append("AUDIO");
+ break;
+ case MEDIA_TRACK_TYPE_TIMEDTEXT:
+ out.append("TIMEDTEXT");
+ break;
+ case MEDIA_TRACK_TYPE_SUBTITLE:
+ out.append("SUBTITLE");
+ break;
+ default:
+ out.append("UNKNOWN");
+ break;
+ }
+ out.append(", " + mFormat.toString());
+ out.append("}");
+ return out.toString();
+ }
};
/**
@@ -972,35 +1988,32 @@
* @return List of track info. The total number of tracks is the array length.
* Must be called again if an external timed text source has been added after
* addTimedTextSource method is called.
+ * @throws IllegalStateException if it is called in an invalid state.
*/
- public abstract List<TrackInfo> getTrackInfo();
+ public List<TrackInfo> getTrackInfo() {
+ TrackInfo[] trackInfo = getInbandTrackInfo();
+ return Arrays.asList(trackInfo);
+ }
- /* Do not change these values without updating their counterparts
- * in include/media/stagefright/MediaDefs.h and media/libstagefright/MediaDefs.cpp!
- */
- /**
- * MIME type for SubRip (SRT) container. Used in addTimedTextSource APIs.
- * @hide
- */
- public static final String MEDIA_MIMETYPE_TEXT_SUBRIP = "application/x-subrip";
-
- /**
- * MIME type for WebVTT subtitle data.
- * @hide
- */
- public static final String MEDIA_MIMETYPE_TEXT_VTT = "text/vtt";
-
- /**
- * MIME type for CEA-608 closed caption data.
- * @hide
- */
- public static final String MEDIA_MIMETYPE_TEXT_CEA_608 = "text/cea-608";
-
- /**
- * MIME type for CEA-708 closed caption data.
- * @hide
- */
- public static final String MEDIA_MIMETYPE_TEXT_CEA_708 = "text/cea-708";
+ private TrackInfo[] getInbandTrackInfo() throws IllegalStateException {
+ PlayerMessage request = PlayerMessage.newBuilder()
+ .addValues(Value.newBuilder().setInt32Value(INVOKE_ID_GET_TRACK_INFO))
+ .build();
+ PlayerMessage response = invoke(request);
+ if (response == null) {
+ return null;
+ }
+ Iterator<Value> in = response.getValuesList().iterator();
+ int size = in.next().getInt32Value();
+ if (size == 0) {
+ return null;
+ }
+ TrackInfo[] trackInfo = new TrackInfo[size];
+ for (int i = 0; i < size; ++i) {
+ trackInfo[i] = new TrackInfo(in);
+ }
+ return trackInfo;
+ }
/**
* Returns the index of the audio, video, or subtitle track currently selected for playback,
@@ -1019,7 +2032,17 @@
* @see #selectTrack(int)
* @see #deselectTrack(int)
*/
- public abstract int getSelectedTrack(int trackType);
+ public int getSelectedTrack(int trackType) {
+ PlayerMessage request = PlayerMessage.newBuilder()
+ .addValues(Value.newBuilder().setInt32Value(INVOKE_ID_GET_SELECTED_TRACK))
+ .addValues(Value.newBuilder().setInt32Value(trackType))
+ .build();
+ PlayerMessage response = invoke(request);
+ if (response == null) {
+ return -1;
+ }
+ return response.getValues(0).getInt32Value();
+ }
/**
* Selects a track.
@@ -1050,7 +2073,14 @@
* @see MediaPlayer2#getTrackInfo
*/
// This is an asynchronous call.
- public abstract Object selectTrack(int index);
+ public Object selectTrack(int index) {
+ return addTask(new Task(CALL_COMPLETED_SELECT_TRACK, false) {
+ @Override
+ void process() {
+ selectOrDeselectTrack(index, true /* select */);
+ }
+ });
+ }
/**
* Deselect a track.
@@ -1067,13 +2097,450 @@
* @see MediaPlayer2#getTrackInfo
*/
// This is an asynchronous call.
- public abstract Object deselectTrack(int index);
+ public Object deselectTrack(int index) {
+ return addTask(new Task(CALL_COMPLETED_DESELECT_TRACK, false) {
+ @Override
+ void process() {
+ selectOrDeselectTrack(index, false /* select */);
+ }
+ });
+ }
+
+ private void selectOrDeselectTrack(int index, boolean select)
+ throws IllegalStateException {
+ PlayerMessage request = PlayerMessage.newBuilder()
+ .addValues(Value.newBuilder().setInt32Value(
+ select ? INVOKE_ID_SELECT_TRACK : INVOKE_ID_DESELECT_TRACK))
+ .addValues(Value.newBuilder().setInt32Value(index))
+ .build();
+ invoke(request);
+ }
+
+ /* Do not change these values without updating their counterparts
+ * in include/media/mediaplayer2.h!
+ */
+ private static final int MEDIA_NOP = 0; // interface test message
+ private static final int MEDIA_PREPARED = 1;
+ private static final int MEDIA_PLAYBACK_COMPLETE = 2;
+ private static final int MEDIA_BUFFERING_UPDATE = 3;
+ private static final int MEDIA_SEEK_COMPLETE = 4;
+ private static final int MEDIA_SET_VIDEO_SIZE = 5;
+ private static final int MEDIA_STARTED = 6;
+ private static final int MEDIA_PAUSED = 7;
+ private static final int MEDIA_STOPPED = 8;
+ private static final int MEDIA_SKIPPED = 9;
+ private static final int MEDIA_NOTIFY_TIME = 98;
+ private static final int MEDIA_TIMED_TEXT = 99;
+ private static final int MEDIA_ERROR = 100;
+ private static final int MEDIA_INFO = 200;
+ private static final int MEDIA_SUBTITLE_DATA = 201;
+ private static final int MEDIA_META_DATA = 202;
+ private static final int MEDIA_DRM_INFO = 210;
+
+ private class TaskHandler extends Handler {
+ private MediaPlayer2 mMediaPlayer;
+
+ TaskHandler(MediaPlayer2 mp, Looper looper) {
+ super(looper);
+ mMediaPlayer = mp;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ handleMessage(msg, 0);
+ }
+
+ public void handleMessage(Message msg, long srcId) {
+ if (mMediaPlayer.mNativeContext == 0) {
+ Log.w(TAG, "mediaplayer2 went away with unhandled events");
+ return;
+ }
+ final int what = msg.arg1;
+ final int extra = msg.arg2;
+
+ final SourceInfo sourceInfo = getSourceInfoById(srcId);
+ if (sourceInfo == null) {
+ return;
+ }
+ final DataSourceDesc dsd = sourceInfo.mDSD;
+
+ switch(msg.what) {
+ case MEDIA_PREPARED:
+ {
+ if (dsd != null) {
+ sendEvent(new EventNotifier() {
+ @Override
+ public void notify(EventCallback callback) {
+ callback.onInfo(
+ mMediaPlayer, dsd, MEDIA_INFO_PREPARED, 0);
+ }
+ });
+ }
+
+ synchronized (mSrcLock) {
+ SourceInfo nextSourceInfo = mNextSourceInfos.peek();
+ Log.i(TAG, "MEDIA_PREPARED: srcId=" + srcId
+ + ", curSrc=" + mCurrentSourceInfo
+ + ", nextSrc=" + nextSourceInfo);
+
+ if (isCurrentSource(srcId)) {
+ prepareNextDataSource();
+ } else if (isNextSource(srcId)) {
+ nextSourceInfo.mStateAsNextSource = NEXT_SOURCE_STATE_PREPARED;
+ if (nextSourceInfo.mPlayPendingAsNextSource) {
+ playNextDataSource();
+ }
+ }
+ }
+
+ synchronized (mTaskLock) {
+ if (mCurrentTask != null
+ && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE
+ && mCurrentTask.mDSD == dsd
+ && mCurrentTask.mNeedToWaitForEventToComplete) {
+ mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR);
+ mCurrentTask = null;
+ processPendingTask_l();
+ }
+ }
+ return;
+ }
+
+ case MEDIA_DRM_INFO:
+ {
+ if (msg.obj == null) {
+ Log.w(TAG, "MEDIA_DRM_INFO msg.obj=NULL");
+ } else if (msg.obj instanceof byte[]) {
+ // The PlayerMessage was parsed already in postEventFromNative
+ final DrmInfo drmInfo;
+
+ synchronized (mDrmLock) {
+ if (mDrmInfo != null) {
+ drmInfo = mDrmInfo.makeCopy();
+ } else {
+ drmInfo = null;
+ }
+ }
+
+ // notifying the client outside the lock
+ if (drmInfo != null) {
+ sendDrmEvent(new DrmEventNotifier() {
+ @Override
+ public void notify(DrmEventCallback callback) {
+ callback.onDrmInfo(
+ mMediaPlayer, dsd, drmInfo);
+ }
+ });
+ }
+ } else {
+ Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + msg.obj);
+ }
+ return;
+ }
+
+ case MEDIA_PLAYBACK_COMPLETE:
+ {
+ if (isCurrentSource(srcId)) {
+ sendEvent(new EventNotifier() {
+ @Override
+ public void notify(EventCallback callback) {
+ callback.onInfo(
+ mMediaPlayer, dsd, MEDIA_INFO_DATA_SOURCE_END, 0);
+ }
+ });
+ stayAwake(false);
+
+ synchronized (mSrcLock) {
+ SourceInfo nextSourceInfo = mNextSourceInfos.peek();
+ if (nextSourceInfo != null) {
+ nextSourceInfo.mPlayPendingAsNextSource = true;
+ }
+ Log.i(TAG, "MEDIA_PLAYBACK_COMPLETE: srcId=" + srcId
+ + ", curSrc=" + mCurrentSourceInfo
+ + ", nextSrc=" + nextSourceInfo);
+ }
+
+ playNextDataSource();
+ }
+
+ return;
+ }
+
+ case MEDIA_STOPPED:
+ case MEDIA_STARTED:
+ case MEDIA_PAUSED:
+ case MEDIA_SKIPPED:
+ case MEDIA_NOTIFY_TIME:
+ {
+ // Do nothing. The client should have enough information with
+ // {@link EventCallback#onMediaTimeDiscontinuity}.
+ break;
+ }
+
+ case MEDIA_BUFFERING_UPDATE:
+ {
+ final int percent = msg.arg1;
+ sendEvent(new EventNotifier() {
+ @Override
+ public void notify(EventCallback callback) {
+ callback.onInfo(
+ mMediaPlayer, dsd, MEDIA_INFO_BUFFERING_UPDATE, percent);
+ }
+ });
+
+ SourceInfo src = getSourceInfoById(srcId);
+ if (src != null) {
+ src.mBufferedPercentage.set(percent);
+ }
+
+ return;
+ }
+
+ case MEDIA_SEEK_COMPLETE:
+ {
+ synchronized (mTaskLock) {
+ if (!mPendingTasks.isEmpty()
+ && mPendingTasks.get(0).mMediaCallType != CALL_COMPLETED_SEEK_TO
+ && getState() == PLAYER_STATE_PLAYING) {
+ mIsPreviousCommandSeekTo = false;
+ }
+
+ if (mCurrentTask != null
+ && mCurrentTask.mMediaCallType == CALL_COMPLETED_SEEK_TO
+ && mCurrentTask.mNeedToWaitForEventToComplete) {
+ mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR);
+ mCurrentTask = null;
+ processPendingTask_l();
+ }
+ }
+ return;
+ }
+
+ case MEDIA_SET_VIDEO_SIZE:
+ {
+ final int width = msg.arg1;
+ final int height = msg.arg2;
+
+ mVideoSize = new VideoSize(width, height);
+ sendEvent(new EventNotifier() {
+ @Override
+ public void notify(EventCallback callback) {
+ callback.onVideoSizeChanged(
+ mMediaPlayer, dsd, mVideoSize);
+ }
+ });
+ return;
+ }
+
+ case MEDIA_ERROR:
+ {
+ Log.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")");
+ sendEvent(new EventNotifier() {
+ @Override
+ public void notify(EventCallback callback) {
+ callback.onError(
+ mMediaPlayer, dsd, what, extra);
+ }
+ });
+ sendEvent(new EventNotifier() {
+ @Override
+ public void notify(EventCallback callback) {
+ callback.onInfo(
+ mMediaPlayer, dsd, MEDIA_INFO_DATA_SOURCE_END, 0);
+ }
+ });
+ stayAwake(false);
+ return;
+ }
+
+ case MEDIA_INFO:
+ {
+ switch (msg.arg1) {
+ case MEDIA_INFO_VIDEO_TRACK_LAGGING:
+ Log.i(TAG, "Info (" + msg.arg1 + "," + msg.arg2 + ")");
+ break;
+ }
+
+ sendEvent(new EventNotifier() {
+ @Override
+ public void notify(EventCallback callback) {
+ callback.onInfo(
+ mMediaPlayer, dsd, what, extra);
+ }
+ });
+
+ if (msg.arg1 == MEDIA_INFO_DATA_SOURCE_START) {
+ if (isCurrentSource(srcId)) {
+ prepareNextDataSource();
+ }
+ }
+
+ // No real default action so far.
+ return;
+ }
+
+ case MEDIA_TIMED_TEXT:
+ {
+ final TimedText text;
+ if (msg.obj instanceof byte[]) {
+ PlayerMessage playerMsg;
+ try {
+ playerMsg = PlayerMessage.parseFrom((byte[]) msg.obj);
+ } catch (InvalidProtocolBufferException e) {
+ Log.w(TAG, "Failed to parse timed text.", e);
+ return;
+ }
+ text = TimedTextUtil.parsePlayerMessage(playerMsg);
+ } else {
+ text = null;
+ }
+
+ sendEvent(new EventNotifier() {
+ @Override
+ public void notify(EventCallback callback) {
+ callback.onTimedText(
+ mMediaPlayer, dsd, text);
+ }
+ });
+ return;
+ }
+
+ case MEDIA_SUBTITLE_DATA:
+ {
+ if (msg.obj instanceof byte[]) {
+ PlayerMessage playerMsg;
+ try {
+ playerMsg = PlayerMessage.parseFrom((byte[]) msg.obj);
+ } catch (InvalidProtocolBufferException e) {
+ Log.w(TAG, "Failed to parse subtitle data.", e);
+ return;
+ }
+ Iterator<Value> in = playerMsg.getValuesList().iterator();
+ SubtitleData data = new SubtitleData(
+ in.next().getInt32Value(), // trackIndex
+ in.next().getInt64Value(), // startTimeUs
+ in.next().getInt64Value(), // durationUs
+ in.next().getBytesValue().toByteArray()); // data
+ sendEvent(new EventNotifier() {
+ @Override
+ public void notify(EventCallback callback) {
+ callback.onSubtitleData(
+ mMediaPlayer, dsd, data);
+ }
+ });
+ }
+ return;
+ }
+
+ case MEDIA_META_DATA:
+ {
+ final TimedMetaData data;
+ if (msg.obj instanceof byte[]) {
+ PlayerMessage playerMsg;
+ try {
+ playerMsg = PlayerMessage.parseFrom((byte[]) msg.obj);
+ } catch (InvalidProtocolBufferException e) {
+ Log.w(TAG, "Failed to parse timed meta data.", e);
+ return;
+ }
+ Iterator<Value> in = playerMsg.getValuesList().iterator();
+ data = new TimedMetaData(
+ in.next().getInt64Value(), // timestampUs
+ in.next().getBytesValue().toByteArray()); // metaData
+ } else {
+ data = null;
+ }
+
+ sendEvent(new EventNotifier() {
+ @Override
+ public void notify(EventCallback callback) {
+ callback.onTimedMetaDataAvailable(
+ mMediaPlayer, dsd, data);
+ }
+ });
+ return;
+ }
+
+ case MEDIA_NOP: // interface test message - ignore
+ {
+ break;
+ }
+
+ default:
+ {
+ Log.e(TAG, "Unknown message type " + msg.what);
+ return;
+ }
+ }
+ }
+ }
+
+ /*
+ * Called from native code when an interesting event happens. This method
+ * just uses the TaskHandler system to post the event back to the main app thread.
+ * We use a weak reference to the original MediaPlayer2 object so that the native
+ * code is safe from the object disappearing from underneath it. (This is
+ * the cookie passed to native_setup().)
+ */
+ private static void postEventFromNative(Object mediaplayer2Ref, long srcId,
+ int what, int arg1, int arg2, byte[] obj) {
+ final MediaPlayer2 mp = (MediaPlayer2) ((WeakReference) mediaplayer2Ref).get();
+ if (mp == null) {
+ return;
+ }
+
+ switch (what) {
+ case MEDIA_DRM_INFO:
+ // We need to derive mDrmInfo before prepare() returns so processing it here
+ // before the notification is sent to TaskHandler below. TaskHandler runs in the
+ // notification looper so its handleMessage might process the event after prepare()
+ // has returned.
+ Log.v(TAG, "postEventFromNative MEDIA_DRM_INFO");
+ if (obj != null) {
+ PlayerMessage playerMsg;
+ try {
+ playerMsg = PlayerMessage.parseFrom(obj);
+ } catch (InvalidProtocolBufferException e) {
+ Log.w(TAG, "MEDIA_DRM_INFO failed to parse msg.obj " + obj);
+ break;
+ }
+ DrmInfo drmInfo = new DrmInfo(playerMsg);
+ synchronized (mp.mDrmLock) {
+ mp.mDrmInfo = drmInfo;
+ }
+ } else {
+ Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + obj);
+ }
+ break;
+
+ case MEDIA_PREPARED:
+ // By this time, we've learned about DrmInfo's presence or absence. This is meant
+ // mainly for prepare() use case. For prepare(), this still can run to a race
+ // condition b/c MediaPlayerNative releases the prepare() lock before calling notify
+ // so we also set mDrmInfoResolved in prepare().
+ synchronized (mp.mDrmLock) {
+ mp.mDrmInfoResolved = true;
+ }
+ break;
+ }
+
+ if (mp.mTaskHandler != null) {
+ Message m = mp.mTaskHandler.obtainMessage(what, arg1, arg2, obj);
+
+ mp.mTaskHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mp.mTaskHandler.handleMessage(m, srcId);
+ }
+ });
+ }
+ }
/**
* Interface definition for callbacks to be invoked when the player has the corresponding
* events.
*/
- public abstract static class EventCallback {
+ public static class EventCallback {
/**
* Called to indicate the video size
*
@@ -1183,6 +2650,10 @@
MediaPlayer2 mp, DataSourceDesc dsd, @NonNull SubtitleData data) { }
}
+ private final Object mEventCbLock = new Object();
+ private ArrayList<Pair<Executor, EventCallback>> mEventCallbackRecords =
+ new ArrayList<Pair<Executor, EventCallback>>();
+
/**
* Registers the callback to be invoked for various events covered by {@link EventCallback}.
*
@@ -1190,8 +2661,19 @@
* @param eventCallback the callback that will be run
*/
// This is a synchronous call.
- public abstract void registerEventCallback(@NonNull @CallbackExecutor Executor executor,
- @NonNull EventCallback eventCallback);
+ public void registerEventCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull EventCallback eventCallback) {
+ if (eventCallback == null) {
+ throw new IllegalArgumentException("Illegal null EventCallback");
+ }
+ if (executor == null) {
+ throw new IllegalArgumentException(
+ "Illegal null Executor for the EventCallback");
+ }
+ synchronized (mEventCbLock) {
+ mEventCallbackRecords.add(new Pair(executor, eventCallback));
+ }
+ }
/**
* Unregisters the {@link EventCallback}.
@@ -1199,10 +2681,58 @@
* @param eventCallback the callback to be unregistered
*/
// This is a synchronous call.
- public abstract void unregisterEventCallback(EventCallback eventCallback);
+ public void unregisterEventCallback(EventCallback eventCallback) {
+ synchronized (mEventCbLock) {
+ for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
+ if (cb.second == eventCallback) {
+ mEventCallbackRecords.remove(cb);
+ }
+ }
+ }
+ }
+
+ private static void checkArgument(boolean expression, String errorMessage) {
+ if (!expression) {
+ throw new IllegalArgumentException(errorMessage);
+ }
+ }
+
+ private void sendEvent(final EventNotifier notifier) {
+ synchronized (mEventCbLock) {
+ try {
+ for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
+ cb.first.execute(() -> notifier.notify(cb.second));
+ }
+ } catch (RejectedExecutionException e) {
+ // The executor has been shut down.
+ Log.w(TAG, "The executor has been shut down. Ignoring event.");
+ }
+ }
+ }
+
+ private void sendDrmEvent(final DrmEventNotifier notifier) {
+ synchronized (mDrmEventCbLock) {
+ try {
+ for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
+ cb.first.execute(() -> notifier.notify(cb.second));
+ }
+ } catch (RejectedExecutionException e) {
+ // The executor has been shut down.
+ Log.w(TAG, "The executor has been shut down. Ignoring drm event.");
+ }
+ }
+ }
+
+ private interface EventNotifier {
+ void notify(EventCallback callback);
+ }
+
+ private interface DrmEventNotifier {
+ void notify(DrmEventCallback callback);
+ }
/* Do not change these values without updating their counterparts
- * in include/media/mediaplayer2.h!
+ * in include/media/MediaPlayer2Types.h!
*/
/** Unspecified media player error.
* @see EventCallback#onError
@@ -1541,7 +3071,7 @@
public static final int CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED =
SEPARATE_CALL_COMPLETED_CALLBACK_START;
- /** The player just completed a call {@link #prepareDrm}.
+ /** The player just completed a call {@link #prepareDrm(DataSourceDesc, UUID)}.
* @see DrmEventCallback#onDrmPrepared
* @hide
*/
@@ -1619,7 +3149,7 @@
public static final int CALL_STATUS_SKIPPED = 5;
/** Status code represents that DRM operation is called before preparing a DRM scheme through
- * {@link #prepareDrm}.
+ * {@link #prepareDrm(DataSourceDesc, UUID)}.
* @see EventCallback#onCallCompleted
*/
public static final int CALL_STATUS_NO_DRM_SCHEME = 6;
@@ -1648,11 +3178,11 @@
* 'securityLevel', which has to be set after DRM scheme creation but
* before the DRM session is opened.
*
- * The only allowed DRM calls in this listener are {@link #getDrmPropertyString}
- * and {@link #setDrmPropertyString}.
+ * The only allowed DRM calls in this listener are
+ * {@link MediaPlayer2#getDrmPropertyString(DataSourceDesc, String)}
+ * and {@link MediaPlayer2#setDrmPropertyString(DataSourceDesc, String, String)}.
*/
- public interface OnDrmConfigHelper
- {
+ public interface OnDrmConfigHelper {
/**
* Called to give the app the opportunity to configure DRM before the session is created
*
@@ -1666,18 +3196,24 @@
* Register a callback to be invoked for configuration of the DRM object before
* the session is created.
* The callback will be invoked synchronously during the execution
- * of {@link #prepareDrm(UUID uuid)}.
+ * of {@link #prepareDrm(DataSourceDesc, UUID)}.
*
* @param listener the callback that will be run
*/
// This is a synchronous call.
- public abstract void setOnDrmConfigHelper(OnDrmConfigHelper listener);
+ public void setOnDrmConfigHelper(OnDrmConfigHelper listener) {
+ synchronized (mDrmLock) {
+ mOnDrmConfigHelper = listener;
+ }
+ }
+
+ private OnDrmConfigHelper mOnDrmConfigHelper;
/**
* Interface definition for callbacks to be invoked when the player has the corresponding
* DRM events.
*/
- public abstract static class DrmEventCallback {
+ public static class DrmEventCallback {
/**
* Called to indicate DRM info is available
*
@@ -1689,8 +3225,8 @@
public void onDrmInfo(MediaPlayer2 mp, DataSourceDesc dsd, DrmInfo drmInfo) { }
/**
- * Called to notify the client that {@link #prepareDrm} is finished and ready for
- * key request/response.
+ * Called to notify the client that {@link MediaPlayer2#prepareDrm(DataSourceDesc, UUID)}
+ * is finished and ready for key request/response.
*
* @param mp the {@code MediaPlayer2} associated with this callback
* @param dsd the DataSourceDesc of this data source
@@ -1700,6 +3236,10 @@
MediaPlayer2 mp, DataSourceDesc dsd, @PrepareDrmStatusCode int status) { }
}
+ private final Object mDrmEventCbLock = new Object();
+ private ArrayList<Pair<Executor, DrmEventCallback>> mDrmEventCallbackRecords =
+ new ArrayList<Pair<Executor, DrmEventCallback>>();
+
/**
* Registers the callback to be invoked for various DRM events.
*
@@ -1707,8 +3247,19 @@
* @param executor the executor through which the callback should be invoked
*/
// This is a synchronous call.
- public abstract void registerDrmEventCallback(@NonNull @CallbackExecutor Executor executor,
- @NonNull DrmEventCallback eventCallback);
+ public void registerDrmEventCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull DrmEventCallback eventCallback) {
+ if (eventCallback == null) {
+ throw new IllegalArgumentException("Illegal null EventCallback");
+ }
+ if (executor == null) {
+ throw new IllegalArgumentException(
+ "Illegal null Executor for the EventCallback");
+ }
+ synchronized (mDrmEventCbLock) {
+ mDrmEventCallbackRecords.add(new Pair(executor, eventCallback));
+ }
+ }
/**
* Unregisters the {@link DrmEventCallback}.
@@ -1716,7 +3267,15 @@
* @param eventCallback the callback to be unregistered
*/
// This is a synchronous call.
- public abstract void unregisterDrmEventCallback(DrmEventCallback eventCallback);
+ public void unregisterDrmEventCallback(DrmEventCallback eventCallback) {
+ synchronized (mDrmEventCbLock) {
+ for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
+ if (cb.second == eventCallback) {
+ mDrmEventCallbackRecords.remove(cb);
+ }
+ }
+ }
+ }
/**
* The status codes for {@link DrmEventCallback#onDrmPrepared} listener.
@@ -1764,19 +3323,42 @@
public @interface PrepareDrmStatusCode {}
/**
- * Retrieves the DRM Info associated with the current source
+ * Retrieves the DRM Info associated with the given source
+ *
+ * @param dsd The DRM protected data source
*
* @throws IllegalStateException if called before being prepared
*/
- public abstract DrmInfo getDrmInfo();
+ public DrmInfo getDrmInfo(@NonNull DataSourceDesc dsd) {
+ // TODO: this implementation only works when dsd is the only data source
+ DrmInfo drmInfo = null;
+
+ // there is not much point if the app calls getDrmInfo within an OnDrmInfoListenet;
+ // regardless below returns drmInfo anyway instead of raising an exception
+ synchronized (mDrmLock) {
+ if (!mDrmInfoResolved && mDrmInfo == null) {
+ final String msg = "The Player has not been prepared yet";
+ Log.v(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ if (mDrmInfo != null) {
+ drmInfo = mDrmInfo.makeCopy();
+ }
+ } // synchronized
+
+ return drmInfo;
+ }
/**
- * Prepares the DRM for the current source
+ * Prepares the DRM for the given data source
* <p>
* If {@link OnDrmConfigHelper} is registered, it will be called during
* preparation to allow configuration of the DRM properties before opening the
- * DRM session. It should be used only for a series of {@link #getDrmPropertyString}
- * and {@link #setDrmPropertyString} calls and refrain from any lengthy operation.
+ * DRM session. It should be used only for a series of
+ * {@link #getDrmPropertyString(DataSourceDesc, String)} and
+ * {@link #setDrmPropertyString(DataSourceDesc, String, String)} calls
+ * and refrain from any lengthy operation.
* <p>
* If the device has not been provisioned before, this call also provisions the device
* which involves accessing the provisioning server and can take a variable time to
@@ -1791,40 +3373,233 @@
* sequence (e.g., before or after prepareDrm returns).
* <p>
*
+ * @param dsd The DRM protected data source
+ *
* @param uuid The UUID of the crypto scheme. If not known beforehand, it can be retrieved
- * from the source through {@code getDrmInfo} or registering a
+ * from the source through {@link #getDrmInfo(DataSourceDesc)} or registering a
* {@link DrmEventCallback#onDrmInfo}.
*
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
*/
// This is an asynchronous call.
- public abstract Object prepareDrm(@NonNull UUID uuid);
+ public Object prepareDrm(@NonNull DataSourceDesc dsd, @NonNull UUID uuid) {
+ // TODO: this implementation only works when dsd is the only data source
+ return addTask(new Task(CALL_COMPLETED_PREPARE_DRM, true) {
+ @Override
+ void process() {
+ int status = PREPARE_DRM_STATUS_SUCCESS;
+ boolean sendEvent = true;
+
+ try {
+ doPrepareDrm(dsd, uuid);
+ } catch (ResourceBusyException e) {
+ status = PREPARE_DRM_STATUS_RESOURCE_BUSY;
+ } catch (UnsupportedSchemeException e) {
+ status = PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME;
+ } catch (NotProvisionedException e) {
+ Log.w(TAG, "prepareDrm: NotProvisionedException");
+
+ // handle provisioning internally; it'll reset mPrepareDrmInProgress
+ status = handleProvisioninig(dsd, uuid);
+
+ if (status == PREPARE_DRM_STATUS_SUCCESS) {
+ // DrmEventCallback will be fired in provisioning
+ sendEvent = false;
+ } else {
+ synchronized (mDrmLock) {
+ cleanDrmObj();
+ }
+
+ switch (status) {
+ case PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR:
+ Log.e(TAG, "prepareDrm: Provisioning was required but failed "
+ + "due to a network error.");
+ break;
+
+ case PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR:
+ Log.e(TAG, "prepareDrm: Provisioning was required but the request "
+ + "was denied by the server.");
+ break;
+
+ case PREPARE_DRM_STATUS_PREPARATION_ERROR:
+ default:
+ Log.e(TAG, "prepareDrm: Post-provisioning preparation failed.");
+ break;
+ }
+ }
+ } catch (Exception e) {
+ status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
+ }
+
+ if (sendEvent) {
+ final int prepareDrmStatus = status;
+ sendDrmEvent(new DrmEventNotifier() {
+ @Override
+ public void notify(DrmEventCallback callback) {
+ callback.onDrmPrepared(
+ MediaPlayer2.this, dsd, prepareDrmStatus);
+ }
+ });
+
+ synchronized (mTaskLock) {
+ mCurrentTask = null;
+ processPendingTask_l();
+ }
+ }
+ }
+ });
+ }
+
+ private void doPrepareDrm(@NonNull DataSourceDesc dsd, @NonNull UUID uuid)
+ throws UnsupportedSchemeException, ResourceBusyException,
+ NotProvisionedException {
+ Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigHelper: " + mOnDrmConfigHelper);
+
+ synchronized (mDrmLock) {
+ // only allowing if tied to a protected source; might relax for releasing offline keys
+ if (mDrmInfo == null) {
+ final String msg = "prepareDrm(): Wrong usage: The player must be prepared and "
+ + "DRM info be retrieved before this call.";
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ if (mActiveDrmScheme) {
+ final String msg = "prepareDrm(): Wrong usage: There is already "
+ + "an active DRM scheme with " + mDrmUUID;
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ if (mPrepareDrmInProgress) {
+ final String msg = "prepareDrm(): Wrong usage: There is already "
+ + "a pending prepareDrm call.";
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ if (mDrmProvisioningInProgress) {
+ final String msg = "prepareDrm(): Unexpectd: Provisioning is already in progress.";
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ // shouldn't need this; just for safeguard
+ cleanDrmObj();
+
+ mPrepareDrmInProgress = true;
+
+ try {
+ // only creating the DRM object to allow pre-openSession configuration
+ prepareDrm_createDrmStep(uuid);
+ } catch (Exception e) {
+ Log.w(TAG, "prepareDrm(): Exception ", e);
+ mPrepareDrmInProgress = false;
+ throw e;
+ }
+
+ mDrmConfigAllowed = true;
+ } // synchronized
+
+ // call the callback outside the lock
+ if (mOnDrmConfigHelper != null) {
+ mOnDrmConfigHelper.onDrmConfig(this, dsd);
+ }
+
+ synchronized (mDrmLock) {
+ mDrmConfigAllowed = false;
+ boolean earlyExit = false;
+
+ try {
+ prepareDrm_openSessionStep(uuid);
+
+ mDrmUUID = uuid;
+ mActiveDrmScheme = true;
+ mPrepareDrmInProgress = false;
+ } catch (IllegalStateException e) {
+ final String msg = "prepareDrm(): Wrong usage: The player must be "
+ + "in the prepared state to call prepareDrm().";
+ Log.e(TAG, msg);
+ earlyExit = true;
+ mPrepareDrmInProgress = false;
+ throw new IllegalStateException(msg);
+ } catch (NotProvisionedException e) {
+ Log.w(TAG, "prepareDrm: NotProvisionedException", e);
+ throw e;
+ } catch (Exception e) {
+ Log.e(TAG, "prepareDrm: Exception " + e);
+ earlyExit = true;
+ mPrepareDrmInProgress = false;
+ throw e;
+ } finally {
+ if (earlyExit) { // clean up object if didn't succeed
+ cleanDrmObj();
+ }
+ } // finally
+ } // synchronized
+ }
/**
- * Releases the DRM session
+ * Releases the DRM session for the given data source
* <p>
* The player has to have an active DRM session and be in stopped, or prepared
* state before this call is made.
- * A {@code reset()} call will release the DRM session implicitly.
+ * A {@link #reset()} call will release the DRM session implicitly.
+ *
+ * @param dsd The DRM protected data source
*
* @throws NoDrmSchemeException if there is no active DRM session to release
*/
// This is a synchronous call.
- public abstract void releaseDrm()
- throws NoDrmSchemeException;
+ public void releaseDrm(@NonNull DataSourceDesc dsd)
+ throws NoDrmSchemeException {
+ // TODO: this implementation only works when dsd is the only data source
+ synchronized (mDrmLock) {
+ Log.v(TAG, "releaseDrm:");
+
+ if (!mActiveDrmScheme) {
+ Log.e(TAG, "releaseDrm(): No active DRM scheme to release.");
+ throw new NoDrmSchemeException(
+ "releaseDrm: No active DRM scheme to release.");
+ }
+
+ try {
+ // we don't have the player's state in this layer. The below call raises
+ // exception if we're in a non-stopped/prepared state.
+
+ // for cleaning native/mediaserver crypto object
+ native_releaseDrm();
+
+ // for cleaning client-side MediaDrm object; only called if above has succeeded
+ cleanDrmObj();
+
+ mActiveDrmScheme = false;
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "releaseDrm: Exception ", e);
+ throw new IllegalStateException(
+ "releaseDrm: The player is not in a valid state.");
+ } catch (Exception e) {
+ Log.e(TAG, "releaseDrm: Exception ", e);
+ }
+ } // synchronized
+ }
+
+ private native void native_releaseDrm();
/**
* A key request/response exchange occurs between the app and a license server
- * to obtain or release keys used to decrypt encrypted content.
+ * to obtain or release keys used to decrypt the given data source.
* <p>
- * getDrmKeyRequest() is used to obtain an opaque key request byte array that is
+ * {@code getDrmKeyRequest()} is used to obtain an opaque key request byte array that is
* delivered to the license server. The opaque key request byte array is returned
* in KeyRequest.data. The recommended URL to deliver the key request to is
- * returned in KeyRequest.defaultUrl.
+ * returned in {@code KeyRequest.defaultUrl}.
* <p>
* After the app has received the key request response from the server,
* it should deliver to the response to the DRM engine plugin using the method
- * {@link #provideDrmKeyResponse}.
+ * {@link #provideDrmKeyResponse(DataSourceDesc, byte[], byte[])}.
+ *
+ * @param dsd the DRM protected data source
*
* @param keySetId is the key-set identifier of the offline keys being released when keyType is
* {@link MediaDrm#KEY_TYPE_RELEASE}. It should be set to null for other key requests, when
@@ -1851,24 +3626,67 @@
* @throws NoDrmSchemeException if there is no active DRM session
*/
@NonNull
- public abstract MediaDrm.KeyRequest getDrmKeyRequest(
+ public MediaDrm.KeyRequest getDrmKeyRequest(
+ @NonNull DataSourceDesc dsd,
@Nullable byte[] keySetId, @Nullable byte[] initData,
@Nullable String mimeType, @MediaDrm.KeyType int keyType,
@Nullable Map<String, String> optionalParameters)
- throws NoDrmSchemeException;
+ throws NoDrmSchemeException {
+ // TODO: this implementation only works when dsd is the only data source
+ Log.v(TAG, "getDrmKeyRequest: "
+ + " keySetId: " + keySetId + " initData:" + initData + " mimeType: " + mimeType
+ + " keyType: " + keyType + " optionalParameters: " + optionalParameters);
+
+ synchronized (mDrmLock) {
+ if (!mActiveDrmScheme) {
+ Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException");
+ throw new NoDrmSchemeException(
+ "getDrmKeyRequest: Has to set a DRM scheme first.");
+ }
+
+ try {
+ byte[] scope = (keyType != MediaDrm.KEY_TYPE_RELEASE)
+ ? mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE
+ keySetId; // keySetId for KEY_TYPE_RELEASE
+
+ HashMap<String, String> hmapOptionalParameters =
+ (optionalParameters != null)
+ ? new HashMap<String, String>(optionalParameters) :
+ null;
+
+ MediaDrm.KeyRequest request = mDrmObj.getKeyRequest(scope, initData, mimeType,
+ keyType, hmapOptionalParameters);
+ Log.v(TAG, "getDrmKeyRequest: --> request: " + request);
+
+ return request;
+
+ } catch (NotProvisionedException e) {
+ Log.w(TAG, "getDrmKeyRequest NotProvisionedException: "
+ + "Unexpected. Shouldn't have reached here.");
+ throw new IllegalStateException("getDrmKeyRequest: Unexpected provisioning error.");
+ } catch (Exception e) {
+ Log.w(TAG, "getDrmKeyRequest Exception " + e);
+ throw e;
+ }
+
+ } // synchronized
+ }
/**
- * A key response is received from the license server by the app, then it is
- * provided to the DRM engine plugin using provideDrmKeyResponse. When the
- * response is for an offline key request, a key-set identifier is returned that
+ * A key response is received from the license server by the app for the given DRM protected
+ * data source, then provided to the DRM engine plugin using {@code provideDrmKeyResponse}.
+ * <p>
+ * When the response is for an offline key request, a key-set identifier is returned that
* can be used to later restore the keys to a new session with the method
- * {@link # restoreDrmKeys}.
+ * {@link #restoreDrmKeys(DataSourceDesc, byte[])}.
* When the response is for a streaming or release request, null is returned.
*
- * @param keySetId When the response is for a release request, keySetId identifies
- * the saved key associated with the release request (i.e., the same keySetId
- * passed to the earlier {@ link # getDrmKeyRequest} call. It MUST be null when the
- * response is for either streaming or offline key requests.
+ * @param dsd the DRM protected data source
+ *
+ * @param keySetId When the response is for a release request, keySetId identifies the saved
+ * key associated with the release request (i.e., the same keySetId passed to the earlier
+ * {@link # getDrmKeyRequest(DataSourceDesc, byte[], byte[], String, int, Map)} call).
+ * It MUST be null when the response is for either streaming or offline key requests.
*
* @param response the byte array response from the server
*
@@ -1877,76 +3695,827 @@
* server rejected the request
*/
// This is a synchronous call.
- public abstract byte[] provideDrmKeyResponse(
+ public byte[] provideDrmKeyResponse(
+ @NonNull DataSourceDesc dsd,
@Nullable byte[] keySetId, @NonNull byte[] response)
- throws NoDrmSchemeException, DeniedByServerException;
+ throws NoDrmSchemeException, DeniedByServerException {
+ // TODO: this implementation only works when dsd is the only data source
+ Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + " response: " + response);
+
+ synchronized (mDrmLock) {
+
+ if (!mActiveDrmScheme) {
+ Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException");
+ throw new NoDrmSchemeException(
+ "getDrmKeyRequest: Has to set a DRM scheme first.");
+ }
+
+ try {
+ byte[] scope = (keySetId == null)
+ ? mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE
+ keySetId; // keySetId for KEY_TYPE_RELEASE
+
+ byte[] keySetResult = mDrmObj.provideKeyResponse(scope, response);
+
+ Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + " response: " + response
+ + " --> " + keySetResult);
+
+
+ return keySetResult;
+
+ } catch (NotProvisionedException e) {
+ Log.w(TAG, "provideDrmKeyResponse NotProvisionedException: "
+ + "Unexpected. Shouldn't have reached here.");
+ throw new IllegalStateException("provideDrmKeyResponse: "
+ + "Unexpected provisioning error.");
+ } catch (Exception e) {
+ Log.w(TAG, "provideDrmKeyResponse Exception " + e);
+ throw e;
+ }
+ } // synchronized
+ }
/**
- * Restore persisted offline keys into a new session. keySetId identifies the
- * keys to load, obtained from a prior call to {@link #provideDrmKeyResponse}.
+ * Restore persisted offline keys into a new session for the given DRM protected data source.
+ * {@code keySetId} identifies the keys to load, obtained from a prior call to
+ * {@link #provideDrmKeyResponse(DataSourceDesc, byte[], byte[])}.
+ *
+ * @param dsd the DRM protected data source
*
* @param keySetId identifies the saved key set to restore
*
* @throws NoDrmSchemeException if there is no active DRM session
*/
// This is a synchronous call.
- public abstract void restoreDrmKeys(@NonNull byte[] keySetId)
- throws NoDrmSchemeException;
+ public void restoreDrmKeys(
+ @NonNull DataSourceDesc dsd,
+ @NonNull byte[] keySetId)
+ throws NoDrmSchemeException {
+ // TODO: this implementation only works when dsd is the only data source
+ Log.v(TAG, "restoreDrmKeys: keySetId: " + keySetId);
+
+ synchronized (mDrmLock) {
+ if (!mActiveDrmScheme) {
+ Log.w(TAG, "restoreDrmKeys NoDrmSchemeException");
+ throw new NoDrmSchemeException(
+ "restoreDrmKeys: Has to set a DRM scheme first.");
+ }
+
+ try {
+ mDrmObj.restoreKeys(mDrmSessionId, keySetId);
+ } catch (Exception e) {
+ Log.w(TAG, "restoreKeys Exception " + e);
+ throw e;
+ }
+ } // synchronized
+ }
/**
- * Read a DRM engine plugin String property value, given the property name string.
- * <p>
+ * Read a DRM engine plugin String property value, given the DRM protected data source
+ * and property name string.
+ *
+ * @param dsd the DRM protected data source
+ *
* @param propertyName the property name
*
* Standard fields names are:
* {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
* {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
+ *
+ * @throws NoDrmSchemeException if there is no active DRM session
*/
@NonNull
- public abstract String getDrmPropertyString(
+ public String getDrmPropertyString(
+ @NonNull DataSourceDesc dsd,
@NonNull @MediaDrm.StringProperty String propertyName)
- throws NoDrmSchemeException;
+ throws NoDrmSchemeException {
+ // TODO: this implementation only works when dsd is the only data source
+ Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName);
+
+ String value;
+ synchronized (mDrmLock) {
+
+ if (!mActiveDrmScheme && !mDrmConfigAllowed) {
+ Log.w(TAG, "getDrmPropertyString NoDrmSchemeException");
+ throw new NoDrmSchemeException(
+ "getDrmPropertyString: Has to prepareDrm() first.");
+ }
+
+ try {
+ value = mDrmObj.getPropertyString(propertyName);
+ } catch (Exception e) {
+ Log.w(TAG, "getDrmPropertyString Exception " + e);
+ throw e;
+ }
+ } // synchronized
+
+ Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName + " --> value: " + value);
+
+ return value;
+ }
/**
- * Set a DRM engine plugin String property value.
- * <p>
+ * Set a DRM engine plugin String property value for the given data source.
+ *
+ * @param dsd the DRM protected data source
* @param propertyName the property name
* @param value the property value
*
* Standard fields names are:
* {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
* {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
+ *
+ * @throws NoDrmSchemeException if there is no active DRM session
*/
// This is a synchronous call.
- public abstract void setDrmPropertyString(
+ public void setDrmPropertyString(
+ @NonNull DataSourceDesc dsd,
@NonNull @MediaDrm.StringProperty String propertyName, @NonNull String value)
- throws NoDrmSchemeException;
+ throws NoDrmSchemeException {
+ // TODO: this implementation only works when dsd is the only data source
+ Log.v(TAG, "setDrmPropertyString: propertyName: " + propertyName + " value: " + value);
+
+ synchronized (mDrmLock) {
+
+ if (!mActiveDrmScheme && !mDrmConfigAllowed) {
+ Log.w(TAG, "setDrmPropertyString NoDrmSchemeException");
+ throw new NoDrmSchemeException(
+ "setDrmPropertyString: Has to prepareDrm() first.");
+ }
+
+ try {
+ mDrmObj.setPropertyString(propertyName, value);
+ } catch (Exception e) {
+ Log.w(TAG, "setDrmPropertyString Exception " + e);
+ throw e;
+ }
+ } // synchronized
+ }
/**
* Encapsulates the DRM properties of the source.
*/
- public abstract static class DrmInfo {
+ public static final class DrmInfo {
+ private Map<UUID, byte[]> mMapPssh;
+ private UUID[] mSupportedSchemes;
+
/**
* Returns the PSSH info of the data source for each supported DRM scheme.
*/
- public abstract Map<UUID, byte[]> getPssh();
+ public Map<UUID, byte[]> getPssh() {
+ return mMapPssh;
+ }
/**
* Returns the intersection of the data source and the device DRM schemes.
* It effectively identifies the subset of the source's DRM schemes which
* are supported by the device too.
*/
- public abstract List<UUID> getSupportedSchemes();
+ public List<UUID> getSupportedSchemes() {
+ return Arrays.asList(mSupportedSchemes);
+ }
+
+ private DrmInfo(Map<UUID, byte[]> pssh, UUID[] supportedSchemes) {
+ mMapPssh = pssh;
+ mSupportedSchemes = supportedSchemes;
+ }
+
+ private DrmInfo(PlayerMessage msg) {
+ Log.v(TAG, "DrmInfo(" + msg + ")");
+
+ Iterator<Value> in = msg.getValuesList().iterator();
+ byte[] pssh = in.next().getBytesValue().toByteArray();
+
+ Log.v(TAG, "DrmInfo() PSSH: " + arrToHex(pssh));
+ mMapPssh = parsePSSH(pssh, pssh.length);
+ Log.v(TAG, "DrmInfo() PSSH: " + mMapPssh);
+
+ int supportedDRMsCount = in.next().getInt32Value();
+ mSupportedSchemes = new UUID[supportedDRMsCount];
+ for (int i = 0; i < supportedDRMsCount; i++) {
+ byte[] uuid = new byte[16];
+ in.next().getBytesValue().copyTo(uuid, 0);
+
+ mSupportedSchemes[i] = bytesToUUID(uuid);
+
+ Log.v(TAG, "DrmInfo() supportedScheme[" + i + "]: " + mSupportedSchemes[i]);
+ }
+
+ Log.v(TAG, "DrmInfo() psshsize: " + pssh.length
+ + " supportedDRMsCount: " + supportedDRMsCount);
+ }
+
+ private DrmInfo makeCopy() {
+ return new DrmInfo(this.mMapPssh, this.mSupportedSchemes);
+ }
+
+ private String arrToHex(byte[] bytes) {
+ String out = "0x";
+ for (int i = 0; i < bytes.length; i++) {
+ out += String.format("%02x", bytes[i]);
+ }
+
+ return out;
+ }
+
+ private UUID bytesToUUID(byte[] uuid) {
+ long msb = 0, lsb = 0;
+ for (int i = 0; i < 8; i++) {
+ msb |= (((long) uuid[i] & 0xff) << (8 * (7 - i)));
+ lsb |= (((long) uuid[i + 8] & 0xff) << (8 * (7 - i)));
+ }
+
+ return new UUID(msb, lsb);
+ }
+
+ private Map<UUID, byte[]> parsePSSH(byte[] pssh, int psshsize) {
+ Map<UUID, byte[]> result = new HashMap<UUID, byte[]>();
+
+ final int uuidSize = 16;
+ final int dataLenSize = 4;
+
+ int len = psshsize;
+ int numentries = 0;
+ int i = 0;
+
+ while (len > 0) {
+ if (len < uuidSize) {
+ Log.w(TAG, String.format("parsePSSH: len is too short to parse "
+ + "UUID: (%d < 16) pssh: %d", len, psshsize));
+ return null;
+ }
+
+ byte[] subset = Arrays.copyOfRange(pssh, i, i + uuidSize);
+ UUID uuid = bytesToUUID(subset);
+ i += uuidSize;
+ len -= uuidSize;
+
+ // get data length
+ if (len < 4) {
+ Log.w(TAG, String.format("parsePSSH: len is too short to parse "
+ + "datalen: (%d < 4) pssh: %d", len, psshsize));
+ return null;
+ }
+
+ subset = Arrays.copyOfRange(pssh, i, i + dataLenSize);
+ int datalen = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN)
+ ? ((subset[3] & 0xff) << 24) | ((subset[2] & 0xff) << 16)
+ | ((subset[1] & 0xff) << 8) | (subset[0] & 0xff) :
+ ((subset[0] & 0xff) << 24) | ((subset[1] & 0xff) << 16)
+ | ((subset[2] & 0xff) << 8) | (subset[3] & 0xff);
+ i += dataLenSize;
+ len -= dataLenSize;
+
+ if (len < datalen) {
+ Log.w(TAG, String.format("parsePSSH: len is too short to parse "
+ + "data: (%d < %d) pssh: %d", len, datalen, psshsize));
+ return null;
+ }
+
+ byte[] data = Arrays.copyOfRange(pssh, i, i + datalen);
+
+ // skip the data
+ i += datalen;
+ len -= datalen;
+
+ Log.v(TAG, String.format("parsePSSH[%d]: <%s, %s> pssh: %d",
+ numentries, uuid, arrToHex(data), psshsize));
+ numentries++;
+ result.put(uuid, data);
+ }
+
+ return result;
+ }
}; // DrmInfo
/**
- * Thrown when a DRM method is called before preparing a DRM scheme through prepareDrm().
+ * Thrown when a DRM method is called before preparing a DRM scheme through
+ * {@link MediaPlayer2#prepareDrm(DataSourceDesc, UUID)}.
* Extends MediaDrm.MediaDrmException
*/
- public abstract static class NoDrmSchemeException extends MediaDrmException {
- protected NoDrmSchemeException(String detailMessage) {
- super(detailMessage);
- }
+ public static final class NoDrmSchemeException extends MediaDrmException {
+ public NoDrmSchemeException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+
+ private native void native_prepareDrm(@NonNull byte[] uuid, @NonNull byte[] drmSessionId);
+
+ // Modular DRM helpers
+
+ private void prepareDrm_createDrmStep(@NonNull UUID uuid)
+ throws UnsupportedSchemeException {
+ Log.v(TAG, "prepareDrm_createDrmStep: UUID: " + uuid);
+
+ try {
+ mDrmObj = new MediaDrm(uuid);
+ Log.v(TAG, "prepareDrm_createDrmStep: Created mDrmObj=" + mDrmObj);
+ } catch (Exception e) { // UnsupportedSchemeException
+ Log.e(TAG, "prepareDrm_createDrmStep: MediaDrm failed with " + e);
+ throw e;
+ }
+ }
+
+ private void prepareDrm_openSessionStep(@NonNull UUID uuid)
+ throws NotProvisionedException, ResourceBusyException {
+ Log.v(TAG, "prepareDrm_openSessionStep: uuid: " + uuid);
+
+ // TODO: don't need an open session for a future specialKeyReleaseDrm mode but we should do
+ // it anyway so it raises provisioning error if needed. We'd rather handle provisioning
+ // at prepareDrm/openSession rather than getDrmKeyRequest/provideDrmKeyResponse
+ try {
+ mDrmSessionId = mDrmObj.openSession();
+ Log.v(TAG, "prepareDrm_openSessionStep: mDrmSessionId=" + mDrmSessionId);
+
+ // Sending it down to native/mediaserver to create the crypto object
+ // This call could simply fail due to bad player state, e.g., after play().
+ native_prepareDrm(getByteArrayFromUUID(uuid), mDrmSessionId);
+ Log.v(TAG, "prepareDrm_openSessionStep: native_prepareDrm/Crypto succeeded");
+
+ } catch (Exception e) { //ResourceBusyException, NotProvisionedException
+ Log.e(TAG, "prepareDrm_openSessionStep: open/crypto failed with " + e);
+ throw e;
+ }
+ }
+
+ // Instantiated from the native side
+ @SuppressWarnings("unused")
+ private static class StreamEventCallback extends AudioTrack.StreamEventCallback {
+ public long mJAudioTrackPtr;
+ public long mNativeCallbackPtr;
+ public long mUserDataPtr;
+
+ StreamEventCallback(long jAudioTrackPtr, long nativeCallbackPtr, long userDataPtr) {
+ super();
+ mJAudioTrackPtr = jAudioTrackPtr;
+ mNativeCallbackPtr = nativeCallbackPtr;
+ mUserDataPtr = userDataPtr;
+ }
+
+ @Override
+ public void onTearDown(AudioTrack track) {
+ native_stream_event_onTearDown(mNativeCallbackPtr, mUserDataPtr);
+ }
+
+ @Override
+ public void onPresentationEnded(AudioTrack track) {
+ native_stream_event_onStreamPresentationEnd(mNativeCallbackPtr, mUserDataPtr);
+ }
+
+ @Override
+ public void onDataRequest(AudioTrack track, int size) {
+ native_stream_event_onStreamDataRequest(
+ mJAudioTrackPtr, mNativeCallbackPtr, mUserDataPtr);
+ }
+ }
+
+ private class ProvisioningThread extends Thread {
+ public static final int TIMEOUT_MS = 60000;
+
+ private final DataSourceDesc mDSD;
+ private UUID mUuid;
+ private String mUrlStr;
+ private Object mDrmLock;
+ private MediaPlayer2 mMediaPlayer;
+ private int mStatus;
+ public int status() {
+ return mStatus;
+ }
+
+ public ProvisioningThread(MediaDrm.ProvisionRequest request,
+ DataSourceDesc dsd,
+ UUID uuid, MediaPlayer2 mediaPlayer) {
+ // lock is held by the caller
+ mDSD = dsd;
+ mDrmLock = mediaPlayer.mDrmLock;
+ mMediaPlayer = mediaPlayer;
+
+ mUrlStr = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData());
+ mUuid = uuid;
+
+ mStatus = PREPARE_DRM_STATUS_PREPARATION_ERROR;
+
+ Log.v(TAG, "handleProvisioninig: Thread is initialised url: " + mUrlStr);
+ }
+
+ public void run() {
+
+ byte[] response = null;
+ boolean provisioningSucceeded = false;
+ try {
+ URL url = new URL(mUrlStr);
+ final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ try {
+ connection.setRequestMethod("POST");
+ connection.setDoOutput(false);
+ connection.setDoInput(true);
+ connection.setConnectTimeout(TIMEOUT_MS);
+ connection.setReadTimeout(TIMEOUT_MS);
+
+ connection.connect();
+ response = readInputStreamFully(connection.getInputStream());
+
+ Log.v(TAG, "handleProvisioninig: Thread run: response "
+ + response.length + " " + response);
+ } catch (Exception e) {
+ mStatus = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
+ Log.w(TAG, "handleProvisioninig: Thread run: connect " + e + " url: " + url);
+ } finally {
+ connection.disconnect();
+ }
+ } catch (Exception e) {
+ mStatus = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
+ Log.w(TAG, "handleProvisioninig: Thread run: openConnection " + e);
+ }
+
+ if (response != null) {
+ try {
+ mDrmObj.provideProvisionResponse(response);
+ Log.v(TAG, "handleProvisioninig: Thread run: "
+ + "provideProvisionResponse SUCCEEDED!");
+
+ provisioningSucceeded = true;
+ } catch (Exception e) {
+ mStatus = PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR;
+ Log.w(TAG, "handleProvisioninig: Thread run: "
+ + "provideProvisionResponse " + e);
+ }
+ }
+
+ boolean succeeded = false;
+
+ synchronized (mDrmLock) {
+ // continuing with prepareDrm
+ if (provisioningSucceeded) {
+ succeeded = mMediaPlayer.resumePrepareDrm(mUuid);
+ mStatus = (succeeded)
+ ? PREPARE_DRM_STATUS_SUCCESS :
+ PREPARE_DRM_STATUS_PREPARATION_ERROR;
+ }
+ mMediaPlayer.mDrmProvisioningInProgress = false;
+ mMediaPlayer.mPrepareDrmInProgress = false;
+ if (!succeeded) {
+ cleanDrmObj(); // cleaning up if it hasn't gone through while in the lock
+ }
+ } // synchronized
+
+ // calling the callback outside the lock
+ sendDrmEvent(new DrmEventNotifier() {
+ @Override
+ public void notify(DrmEventCallback callback) {
+ callback.onDrmPrepared(
+ mMediaPlayer, mDSD, mStatus);
+ }
+ });
+
+ synchronized (mTaskLock) {
+ if (mCurrentTask != null
+ && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE_DRM
+ && mCurrentTask.mNeedToWaitForEventToComplete) {
+ mCurrentTask = null;
+ processPendingTask_l();
+ }
+ }
+ }
+
+ /**
+ * Returns a byte[] containing the remainder of 'in', closing it when done.
+ */
+ private byte[] readInputStreamFully(InputStream in) throws IOException {
+ try {
+ return readInputStreamFullyNoClose(in);
+ } finally {
+ in.close();
+ }
+ }
+
+ /**
+ * Returns a byte[] containing the remainder of 'in'.
+ */
+ private byte[] readInputStreamFullyNoClose(InputStream in) throws IOException {
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ int count;
+ while ((count = in.read(buffer)) != -1) {
+ bytes.write(buffer, 0, count);
+ }
+ return bytes.toByteArray();
+ }
+ } // ProvisioningThread
+
+ private int handleProvisioninig(DataSourceDesc dsd, UUID uuid) {
+ synchronized (mDrmLock) {
+ if (mDrmProvisioningInProgress) {
+ Log.e(TAG, "handleProvisioninig: Unexpected mDrmProvisioningInProgress");
+ return PREPARE_DRM_STATUS_PREPARATION_ERROR;
+ }
+
+ MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest();
+ if (provReq == null) {
+ Log.e(TAG, "handleProvisioninig: getProvisionRequest returned null.");
+ return PREPARE_DRM_STATUS_PREPARATION_ERROR;
+ }
+
+ Log.v(TAG, "handleProvisioninig provReq "
+ + " data: " + provReq.getData() + " url: " + provReq.getDefaultUrl());
+
+ // networking in a background thread
+ mDrmProvisioningInProgress = true;
+
+ mDrmProvisioningThread = new ProvisioningThread(provReq, dsd, uuid, this);
+ mDrmProvisioningThread.start();
+
+ return PREPARE_DRM_STATUS_SUCCESS;
+ }
+ }
+
+ private boolean resumePrepareDrm(UUID uuid) {
+ Log.v(TAG, "resumePrepareDrm: uuid: " + uuid);
+
+ // mDrmLock is guaranteed to be held
+ boolean success = false;
+ try {
+ // resuming
+ prepareDrm_openSessionStep(uuid);
+
+ mDrmUUID = uuid;
+ mActiveDrmScheme = true;
+
+ success = true;
+ } catch (Exception e) {
+ Log.w(TAG, "handleProvisioninig: Thread run native_prepareDrm resume failed with " + e);
+ // mDrmObj clean up is done by the caller
+ }
+
+ return success;
+ }
+
+ private void resetDrmState() {
+ synchronized (mDrmLock) {
+ Log.v(TAG, "resetDrmState:"
+ + " mDrmInfo=" + mDrmInfo
+ + " mDrmProvisioningThread=" + mDrmProvisioningThread
+ + " mPrepareDrmInProgress=" + mPrepareDrmInProgress
+ + " mActiveDrmScheme=" + mActiveDrmScheme);
+
+ mDrmInfoResolved = false;
+ mDrmInfo = null;
+
+ if (mDrmProvisioningThread != null) {
+ // timeout; relying on HttpUrlConnection
+ try {
+ mDrmProvisioningThread.join();
+ } catch (InterruptedException e) {
+ Log.w(TAG, "resetDrmState: ProvThread.join Exception " + e);
+ }
+ mDrmProvisioningThread = null;
+ }
+
+ mPrepareDrmInProgress = false;
+ mActiveDrmScheme = false;
+
+ cleanDrmObj();
+ } // synchronized
+ }
+
+ private void cleanDrmObj() {
+ // the caller holds mDrmLock
+ Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId);
+
+ if (mDrmSessionId != null) {
+ mDrmObj.closeSession(mDrmSessionId);
+ mDrmSessionId = null;
+ }
+ if (mDrmObj != null) {
+ mDrmObj.release();
+ mDrmObj = null;
+ }
+ }
+
+ private static byte[] getByteArrayFromUUID(@NonNull UUID uuid) {
+ long msb = uuid.getMostSignificantBits();
+ long lsb = uuid.getLeastSignificantBits();
+
+ byte[] uuidBytes = new byte[16];
+ for (int i = 0; i < 8; ++i) {
+ uuidBytes[i] = (byte) (msb >>> (8 * (7 - i)));
+ uuidBytes[8 + i] = (byte) (lsb >>> (8 * (7 - i)));
+ }
+
+ return uuidBytes;
+ }
+
+ // Modular DRM end
+
+ private static class TimedTextUtil {
+ // These keys must be in sync with the keys in TextDescription2.h
+ private static final int KEY_START_TIME = 7; // int
+ private static final int KEY_STRUCT_TEXT_POS = 14; // TextPos
+ private static final int KEY_STRUCT_TEXT = 16; // Text
+ private static final int KEY_GLOBAL_SETTING = 101;
+ private static final int KEY_LOCAL_SETTING = 102;
+
+ private static TimedText parsePlayerMessage(PlayerMessage playerMsg) {
+ if (playerMsg.getValuesCount() == 0) {
+ return null;
+ }
+
+ String textChars = null;
+ Rect textBounds = null;
+ Iterator<Value> in = playerMsg.getValuesList().iterator();
+ int type = in.next().getInt32Value();
+ if (type == KEY_LOCAL_SETTING) {
+ type = in.next().getInt32Value();
+ if (type != KEY_START_TIME) {
+ return null;
+ }
+ int startTimeMs = in.next().getInt32Value();
+
+ type = in.next().getInt32Value();
+ if (type != KEY_STRUCT_TEXT) {
+ return null;
+ }
+
+ byte[] text = in.next().getBytesValue().toByteArray();
+ if (text == null || text.length == 0) {
+ textChars = null;
+ } else {
+ textChars = new String(text);
+ }
+
+ } else if (type != KEY_GLOBAL_SETTING) {
+ Log.w(TAG, "Invalid timed text key found: " + type);
+ return null;
+ }
+ if (in.hasNext()) {
+ type = in.next().getInt32Value();
+ if (type == KEY_STRUCT_TEXT_POS) {
+ int top = in.next().getInt32Value();
+ int left = in.next().getInt32Value();
+ int bottom = in.next().getInt32Value();
+ int right = in.next().getInt32Value();
+ textBounds = new Rect(left, top, right, bottom);
+ }
+ }
+ return new TimedText(textChars, textBounds);
+ }
+ }
+
+ private Object addTask(Task task) {
+ synchronized (mTaskLock) {
+ mPendingTasks.add(task);
+ processPendingTask_l();
+ }
+ return task;
+ }
+
+ @GuardedBy("mTaskLock")
+ private void processPendingTask_l() {
+ if (mCurrentTask != null) {
+ return;
+ }
+ if (!mPendingTasks.isEmpty()) {
+ Task task = mPendingTasks.remove(0);
+ mCurrentTask = task;
+ mTaskHandler.post(task);
+ }
+ }
+
+ private abstract class Task implements Runnable {
+ private final int mMediaCallType;
+ private final boolean mNeedToWaitForEventToComplete;
+ private DataSourceDesc mDSD;
+
+ Task(int mediaCallType, boolean needToWaitForEventToComplete) {
+ mMediaCallType = mediaCallType;
+ mNeedToWaitForEventToComplete = needToWaitForEventToComplete;
+ }
+
+ abstract void process() throws IOException, NoDrmSchemeException;
+
+ @Override
+ public void run() {
+ int status = CALL_STATUS_NO_ERROR;
+ try {
+ if (mMediaCallType != CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED
+ && getState() == PLAYER_STATE_ERROR) {
+ status = CALL_STATUS_INVALID_OPERATION;
+ } else {
+ if (mMediaCallType == CALL_COMPLETED_SEEK_TO) {
+ synchronized (mTaskLock) {
+ if (!mPendingTasks.isEmpty()) {
+ Task nextTask = mPendingTasks.get(0);
+ if (nextTask.mMediaCallType == mMediaCallType) {
+ throw new CommandSkippedException(
+ "consecutive seekTo is skipped except last one");
+ }
+ }
+ }
+ }
+ process();
+ }
+ } catch (IllegalStateException e) {
+ status = CALL_STATUS_INVALID_OPERATION;
+ } catch (IllegalArgumentException e) {
+ status = CALL_STATUS_BAD_VALUE;
+ } catch (SecurityException e) {
+ status = CALL_STATUS_PERMISSION_DENIED;
+ } catch (IOException e) {
+ status = CALL_STATUS_ERROR_IO;
+ } catch (NoDrmSchemeException e) {
+ status = CALL_STATUS_NO_DRM_SCHEME;
+ } catch (CommandSkippedException e) {
+ status = CALL_STATUS_SKIPPED;
+ } catch (Exception e) {
+ status = CALL_STATUS_ERROR_UNKNOWN;
+ }
+ mDSD = getCurrentDataSource();
+
+ if (mMediaCallType != CALL_COMPLETED_SEEK_TO) {
+ synchronized (mTaskLock) {
+ mIsPreviousCommandSeekTo = false;
+ }
+ }
+
+ // TODO: Make native implementations asynchronous and let them send notifications.
+ if (!mNeedToWaitForEventToComplete || status != CALL_STATUS_NO_ERROR) {
+
+ sendCompleteNotification(status);
+
+ synchronized (mTaskLock) {
+ mCurrentTask = null;
+ processPendingTask_l();
+ }
+ }
+ }
+
+ private void sendCompleteNotification(int status) {
+ // In {@link #notifyWhenCommandLabelReached} case, a separate callback
+ // {@link #onCommandLabelReached} is already called in {@code process()}.
+ // CALL_COMPLETED_PREPARE_DRM is sent via DrmEventCallback#onDrmPrepared
+ if (mMediaCallType == CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED
+ || mMediaCallType == CALL_COMPLETED_PREPARE_DRM) {
+ return;
+ }
+ sendEvent(new EventNotifier() {
+ @Override
+ public void notify(EventCallback callback) {
+ callback.onCallCompleted(
+ MediaPlayer2.this, mDSD, mMediaCallType, status);
+ }
+ });
+ }
+ };
+
+ private final class CommandSkippedException extends RuntimeException {
+ CommandSkippedException(String detailMessage) {
+ super(detailMessage);
+ }
+ };
+
+ private final class SourceInfo {
+ final DataSourceDesc mDSD;
+ final long mId = mSrcIdGenerator.getAndIncrement();
+ AtomicInteger mBufferedPercentage = new AtomicInteger(0);
+
+ // m*AsNextSource (below) only applies to pending data sources in the playlist;
+ // the meanings of mCurrentSourceInfo.{mStateAsNextSource,mPlayPendingAsNextSource}
+ // are undefined.
+ int mStateAsNextSource = NEXT_SOURCE_STATE_INIT;
+ boolean mPlayPendingAsNextSource = false;
+
+ SourceInfo(DataSourceDesc dsd) {
+ this.mDSD = dsd;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s(%d)", SourceInfo.class.getName(), mId);
+ }
+
+ }
+
+ private SourceInfo getSourceInfoById(long srcId) {
+ synchronized (mSrcLock) {
+ if (isCurrentSource(srcId)) {
+ return mCurrentSourceInfo;
+ }
+ if (isNextSource(srcId)) {
+ return mNextSourceInfos.peek();
+ }
+ }
+ return null;
+ }
+
+ private boolean isCurrentSource(long srcId) {
+ synchronized (mSrcLock) {
+ return mCurrentSourceInfo != null && mCurrentSourceInfo.mId == srcId;
+ }
+ }
+
+ private boolean isNextSource(long srcId) {
+ SourceInfo nextSourceInfo = mNextSourceInfos.peek();
+ return nextSourceInfo != null && nextSourceInfo.mId == srcId;
}
public static final class MetricsConstants {
@@ -2041,4 +4610,19 @@
public static final String ERROR_CODE = "android.media.mediaplayer.errcode";
}
+
+ private void keepAudioSessionIdAlive(int sessionId) {
+ synchronized (mSessionIdLock) {
+ if (mDummyAudioTrack != null) {
+ if (mDummyAudioTrack.getAudioSessionId() == sessionId) {
+ return;
+ }
+ mDummyAudioTrack.release();
+ }
+ // TODO: parameters can be optimized
+ mDummyAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100,
+ AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, 2,
+ AudioTrack.MODE_STATIC, sessionId);
+ }
+ }
}
diff --git a/media/java/android/media/MediaPlayer2Impl.java b/media/java/android/media/MediaPlayer2Impl.java
deleted file mode 100644
index 4ac0188..0000000
--- a/media/java/android/media/MediaPlayer2Impl.java
+++ /dev/null
@@ -1,3153 +0,0 @@
-/*
- * 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.
- */
-
-package android.media;
-
-import android.annotation.CallbackExecutor;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.ActivityManager.RunningAppProcessInfo;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.graphics.Rect;
-import android.media.MediaPlayer2Proto.PlayerMessage;
-import android.media.MediaPlayer2Proto.Value;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PersistableBundle;
-import android.os.PowerManager;
-import android.util.Log;
-import android.util.Pair;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-
-import com.android.framework.protobuf.InvalidProtocolBufferException;
-import com.android.internal.annotations.GuardedBy;
-
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.ref.WeakReference;
-import java.net.HttpCookie;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.nio.ByteOrder;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Queue;
-import java.util.UUID;
-import java.util.WeakHashMap;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.Executor;
-import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-
-/**
- * @hide
- */
-public final class MediaPlayer2Impl extends MediaPlayer2 {
- static {
- System.loadLibrary("media2_jni");
- native_init();
- }
-
- private static final int NEXT_SOURCE_STATE_ERROR = -1;
- private static final int NEXT_SOURCE_STATE_INIT = 0;
- private static final int NEXT_SOURCE_STATE_PREPARING = 1;
- private static final int NEXT_SOURCE_STATE_PREPARED = 2;
-
- private final static String TAG = "MediaPlayer2Impl";
-
- private Context mContext;
-
- private long mNativeContext; // accessed by native methods
- private long mNativeSurfaceTexture; // accessed by native methods
- private int mListenerContext; // accessed by native methods
- private SurfaceHolder mSurfaceHolder;
- private PowerManager.WakeLock mWakeLock = null;
- private boolean mScreenOnWhilePlaying;
- private boolean mStayAwake;
-
- private final Object mSrcLock = new Object();
- //--- guarded by |mSrcLock| start
- private SourceInfo mCurrentSourceInfo;
- private final Queue<SourceInfo> mNextSourceInfos = new ConcurrentLinkedQueue<>();
- //--- guarded by |mSrcLock| end
- private final AtomicLong mSrcIdGenerator = new AtomicLong(0);
-
- private volatile float mVolume = 1.0f;
- private VideoSize mVideoSize = new VideoSize(0, 0);
-
- // Modular DRM
- private final Object mDrmLock = new Object();
- //--- guarded by |mDrmLock| start
- private UUID mDrmUUID;
- private DrmInfoImpl mDrmInfoImpl;
- private MediaDrm mDrmObj;
- private byte[] mDrmSessionId;
- private boolean mDrmInfoResolved;
- private boolean mActiveDrmScheme;
- private boolean mDrmConfigAllowed;
- private boolean mDrmProvisioningInProgress;
- private boolean mPrepareDrmInProgress;
- private ProvisioningThread mDrmProvisioningThread;
- //--- guarded by |mDrmLock| end
-
- private HandlerThread mHandlerThread;
- private final TaskHandler mTaskHandler;
- private final Object mTaskLock = new Object();
- @GuardedBy("mTaskLock")
- private final List<Task> mPendingTasks = new LinkedList<>();
- @GuardedBy("mTaskLock")
- private Task mCurrentTask;
-
- @GuardedBy("mTaskLock")
- boolean mIsPreviousCommandSeekTo = false;
- // |mPreviousSeekPos| and |mPreviousSeekMode| are valid only when |mIsPreviousCommandSeekTo|
- // is true, and they are accessed on |mHandlerThread| only.
- long mPreviousSeekPos = -1;
- int mPreviousSeekMode = SEEK_PREVIOUS_SYNC;
-
- @GuardedBy("this")
- private boolean mReleased;
-
- /**
- * Default constructor.
- * <p>When done with the MediaPlayer2Impl, you should call {@link #close()},
- * to free the resources. If not released, too many MediaPlayer2Impl instances may
- * result in an exception.</p>
- */
- public MediaPlayer2Impl(Context context) {
- mContext = context;
- mHandlerThread = new HandlerThread("MediaPlayer2TaskThread");
- mHandlerThread.start();
- Looper looper = mHandlerThread.getLooper();
- mTaskHandler = new TaskHandler(this, looper);
-
- /* Native setup requires a weak reference to our object.
- * It's easier to create it here than in C++.
- */
- native_setup(new WeakReference<MediaPlayer2Impl>(this));
- }
-
- @Override
- public void close() {
- super.close();
- release();
- }
-
- @Override
- public Object play() {
- return addTask(new Task(CALL_COMPLETED_PLAY, false) {
- @Override
- void process() {
- stayAwake(true);
- _start();
- }
- });
- }
-
- private native void _start() throws IllegalStateException;
-
- @Override
- public Object prepare() {
- return addTask(new Task(CALL_COMPLETED_PREPARE, true) {
- @Override
- void process() {
- _prepare();
- }
- });
- }
-
- public native void _prepare();
-
- @Override
- public Object pause() {
- return addTask(new Task(CALL_COMPLETED_PAUSE, false) {
- @Override
- void process() {
- stayAwake(false);
-
- _pause();
- }
- });
- }
-
- private native void _pause() throws IllegalStateException;
-
- @Override
- public Object skipToNext() {
- return addTask(new Task(CALL_COMPLETED_SKIP_TO_NEXT, false) {
- @Override
- void process() {
- if (getState() == PLAYER_STATE_PLAYING) {
- pause();
- }
- playNextDataSource();
- }
- });
- }
-
- @Override
- public native long getCurrentPosition();
-
- @Override
- public native long getDuration();
-
- @Override
- public long getBufferedPosition() {
- // Use cached buffered percent for now.
- int bufferedPercentage;
- synchronized (mSrcLock) {
- if (mCurrentSourceInfo == null) {
- bufferedPercentage = 0;
- } else {
- bufferedPercentage = mCurrentSourceInfo.mBufferedPercentage.get();
- }
- }
- return getDuration() * bufferedPercentage / 100;
- }
-
- @Override
- public @MediaPlayer2State int getState() {
- return native_getState();
- }
-
- private native int native_getState();
-
- @Override
- public Object setAudioAttributes(@NonNull AudioAttributes attributes) {
- return addTask(new Task(CALL_COMPLETED_SET_AUDIO_ATTRIBUTES, false) {
- @Override
- void process() {
- if (attributes == null) {
- final String msg = "Cannot set AudioAttributes to null";
- throw new IllegalArgumentException(msg);
- }
- native_setAudioAttributes(attributes);
- }
- });
- }
-
- @Override
- public @NonNull AudioAttributes getAudioAttributes() {
- return native_getAudioAttributes();
- }
-
- @Override
- public Object setDataSource(@NonNull DataSourceDesc dsd) {
- return addTask(new Task(CALL_COMPLETED_SET_DATA_SOURCE, false) {
- @Override
- void process() throws IOException {
- checkArgument(dsd != null, "the DataSourceDesc cannot be null");
- int state = getState();
- if (state != PLAYER_STATE_ERROR && state != PLAYER_STATE_IDLE) {
- throw new IllegalStateException("called in wrong state " + state);
- }
-
- synchronized (mSrcLock) {
- mCurrentSourceInfo = new SourceInfo(dsd);
- handleDataSource(true /* isCurrent */, dsd, mCurrentSourceInfo.mId);
- }
- }
- });
- }
-
- @Override
- public Object setNextDataSource(@NonNull DataSourceDesc dsd) {
- return addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCE, false) {
- @Override
- void process() {
- checkArgument(dsd != null, "the DataSourceDesc cannot be null");
- synchronized (mSrcLock) {
- mNextSourceInfos.clear();
- mNextSourceInfos.add(new SourceInfo(dsd));
- }
- prepareNextDataSource();
- }
- });
- }
-
- @Override
- public Object setNextDataSources(@NonNull List<DataSourceDesc> dsds) {
- return addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCES, false) {
- @Override
- void process() {
- if (dsds == null || dsds.size() == 0) {
- throw new IllegalArgumentException("data source list cannot be null or empty.");
- }
- for (DataSourceDesc dsd : dsds) {
- if (dsd == null) {
- throw new IllegalArgumentException(
- "DataSourceDesc in the source list cannot be null.");
- }
- }
-
- synchronized (mSrcLock) {
- mNextSourceInfos.clear();
- for (DataSourceDesc dsd : dsds) {
- mNextSourceInfos.add(new SourceInfo(dsd));
- }
- }
- prepareNextDataSource();
- }
- });
- }
-
- @Override
- public Object clearNextDataSources() {
- return addTask(new Task(CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES, false) {
- @Override
- void process() {
- mNextSourceInfos.clear();
- }
- });
- }
-
- @Override
- public DataSourceDesc getCurrentDataSource() {
- synchronized (mSrcLock) {
- return mCurrentSourceInfo == null ? null : mCurrentSourceInfo.mDSD;
- }
- }
-
- @Override
- public Object loopCurrent(boolean loop) {
- return addTask(new Task(CALL_COMPLETED_LOOP_CURRENT, false) {
- @Override
- void process() {
- setLooping(loop);
- }
- });
- }
-
- private native void setLooping(boolean looping);
-
- @Override
- public Object setPlayerVolume(float volume) {
- return addTask(new Task(CALL_COMPLETED_SET_PLAYER_VOLUME, false) {
- @Override
- void process() {
- mVolume = volume;
- native_setVolume(volume);
- }
- });
- }
-
- private native void native_setVolume(float volume);
-
- @Override
- public float getPlayerVolume() {
- return mVolume;
- }
-
- @Override
- public float getMaxPlayerVolume() {
- return 1.0f;
- }
-
- /* Do not change these values (starting with INVOKE_ID) without updating
- * their counterparts in include/media/mediaplayer2.h!
- */
- private static final int INVOKE_ID_GET_TRACK_INFO = 1;
- private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE = 2;
- private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE_FD = 3;
- private static final int INVOKE_ID_SELECT_TRACK = 4;
- private static final int INVOKE_ID_DESELECT_TRACK = 5;
- private static final int INVOKE_ID_GET_SELECTED_TRACK = 7;
-
- /**
- * Invoke a generic method on the native player using opaque protocol
- * buffer message for the request and reply. Both payloads' format is a
- * convention between the java caller and the native player.
- *
- * @param msg PlayerMessage for the extension.
- *
- * @return PlayerMessage with the data returned by the
- * native player.
- */
- private PlayerMessage invoke(PlayerMessage msg) {
- byte[] ret = native_invoke(msg.toByteArray());
- if (ret == null) {
- return null;
- }
- try {
- return PlayerMessage.parseFrom(ret);
- } catch (InvalidProtocolBufferException e) {
- return null;
- }
- }
-
- private native byte[] native_invoke(byte[] request);
-
- @Override
- public Object notifyWhenCommandLabelReached(Object label) {
- return addTask(new Task(CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED, false) {
- @Override
- void process() {
- sendEvent(new EventNotifier() {
- @Override
- public void notify(EventCallback callback) {
- callback.onCommandLabelReached(
- MediaPlayer2Impl.this, label);
- }
- });
- }
- });
- }
-
- @Override
- public Object setDisplay(SurfaceHolder sh) {
- return addTask(new Task(CALL_COMPLETED_SET_DISPLAY, false) {
- @Override
- void process() {
- mSurfaceHolder = sh;
- Surface surface;
- if (sh != null) {
- surface = sh.getSurface();
- } else {
- surface = null;
- }
- native_setVideoSurface(surface);
- updateSurfaceScreenOn();
- }
- });
- }
-
- @Override
- public Object setSurface(Surface surface) {
- return addTask(new Task(CALL_COMPLETED_SET_SURFACE, false) {
- @Override
- void process() {
- if (mScreenOnWhilePlaying && surface != null) {
- Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective for Surface");
- }
- mSurfaceHolder = null;
- native_setVideoSurface(surface);
- updateSurfaceScreenOn();
- }
- });
- }
-
- private native void native_setVideoSurface(Surface surface);
-
- @Override
- public boolean cancelCommand(Object token) {
- synchronized (mTaskLock) {
- return mPendingTasks.remove(token);
- }
- }
-
- @Override
- public void clearPendingCommands() {
- synchronized (mTaskLock) {
- mPendingTasks.clear();
- }
- }
-
- private void handleDataSource(boolean isCurrent, @NonNull DataSourceDesc dsd, long srcId)
- throws IOException {
- checkArgument(dsd != null, "the DataSourceDesc cannot be null");
-
- switch (dsd.getType()) {
- case DataSourceDesc.TYPE_CALLBACK:
- handleDataSource(isCurrent,
- srcId,
- dsd.getMedia2DataSource(),
- dsd.getStartPosition(),
- dsd.getEndPosition());
- break;
-
- case DataSourceDesc.TYPE_FD:
- handleDataSource(isCurrent,
- srcId,
- dsd.getFileDescriptor(),
- dsd.getFileDescriptorOffset(),
- dsd.getFileDescriptorLength(),
- dsd.getStartPosition(),
- dsd.getEndPosition());
- break;
-
- case DataSourceDesc.TYPE_URI:
- handleDataSource(isCurrent,
- srcId,
- dsd.getUriContext(),
- dsd.getUri(),
- dsd.getUriHeaders(),
- dsd.getUriCookies(),
- dsd.getStartPosition(),
- dsd.getEndPosition());
- break;
-
- default:
- break;
- }
- }
-
- /**
- * To provide cookies for the subsequent HTTP requests, you can install your own default cookie
- * handler and use other variants of setDataSource APIs instead. Alternatively, you can use
- * this API to pass the cookies as a list of HttpCookie. If the app has not installed
- * a CookieHandler already, this API creates a CookieManager and populates its CookieStore with
- * the provided cookies. If the app has installed its own handler already, this API requires the
- * handler to be of CookieManager type such that the API can update the manager’s CookieStore.
- *
- * <p><strong>Note</strong> that the cross domain redirection is allowed by default,
- * but that can be changed with key/value pairs through the headers parameter with
- * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
- * disallow or allow cross domain redirection.
- *
- * @throws IllegalArgumentException if cookies are provided and the installed handler is not
- * a CookieManager
- * @throws IllegalStateException if it is called in an invalid state
- * @throws NullPointerException if context or uri is null
- * @throws IOException if uri has a file scheme and an I/O error occurs
- */
- private void handleDataSource(
- boolean isCurrent, long srcId,
- @NonNull Context context, @NonNull Uri uri,
- @Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies,
- long startPos, long endPos)
- throws IOException {
- // The context and URI usually belong to the calling user. Get a resolver for that user.
- final ContentResolver resolver = context.getContentResolver();
- final String scheme = uri.getScheme();
- if (ContentResolver.SCHEME_FILE.equals(scheme)) {
- handleDataSource(isCurrent, srcId, uri.getPath(), null, null, startPos, endPos);
- return;
- }
-
- final int ringToneType = RingtoneManager.getDefaultType(uri);
- try {
- AssetFileDescriptor afd;
- // Try requested Uri locally first
- if (ContentResolver.SCHEME_CONTENT.equals(scheme) && ringToneType != -1) {
- afd = RingtoneManager.openDefaultRingtoneUri(context, uri);
- if (attemptDataSource(isCurrent, srcId, afd, startPos, endPos)) {
- return;
- }
- final Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(
- context, ringToneType);
- afd = resolver.openAssetFileDescriptor(actualUri, "r");
- } else {
- afd = resolver.openAssetFileDescriptor(uri, "r");
- }
- if (attemptDataSource(isCurrent, srcId, afd, startPos, endPos)) {
- return;
- }
- } catch (NullPointerException | SecurityException | IOException ex) {
- Log.w(TAG, "Couldn't open " + uri + ": " + ex);
- // Fallback to media server
- }
- handleDataSource(isCurrent, srcId, uri.toString(), headers, cookies, startPos, endPos);
- }
-
- private boolean attemptDataSource(boolean isCurrent, long srcId, AssetFileDescriptor afd,
- long startPos, long endPos) throws IOException {
- try {
- if (afd.getDeclaredLength() < 0) {
- handleDataSource(isCurrent,
- srcId,
- afd.getFileDescriptor(),
- 0,
- DataSourceDesc.LONG_MAX,
- startPos,
- endPos);
- } else {
- handleDataSource(isCurrent,
- srcId,
- afd.getFileDescriptor(),
- afd.getStartOffset(),
- afd.getDeclaredLength(),
- startPos,
- endPos);
- }
- return true;
- } catch (NullPointerException | SecurityException | IOException ex) {
- Log.w(TAG, "Couldn't open srcId:" + srcId + ": " + ex);
- return false;
- } finally {
- if (afd != null) {
- afd.close();
- }
- }
- }
-
- private void handleDataSource(
- boolean isCurrent, long srcId,
- String path, Map<String, String> headers, List<HttpCookie> cookies,
- long startPos, long endPos)
- throws IOException {
- String[] keys = null;
- String[] values = null;
-
- if (headers != null) {
- keys = new String[headers.size()];
- values = new String[headers.size()];
-
- int i = 0;
- for (Map.Entry<String, String> entry: headers.entrySet()) {
- keys[i] = entry.getKey();
- values[i] = entry.getValue();
- ++i;
- }
- }
- handleDataSource(isCurrent, srcId, path, keys, values, cookies, startPos, endPos);
- }
-
- private void handleDataSource(boolean isCurrent, long srcId,
- String path, String[] keys, String[] values, List<HttpCookie> cookies,
- long startPos, long endPos)
- throws IOException {
- final Uri uri = Uri.parse(path);
- final String scheme = uri.getScheme();
- if ("file".equals(scheme)) {
- path = uri.getPath();
- } else if (scheme != null) {
- // handle non-file sources
- Media2Utils.storeCookies(cookies);
- nativeHandleDataSourceUrl(
- isCurrent,
- srcId,
- Media2HTTPService.createHTTPService(path),
- path,
- keys,
- values,
- startPos,
- endPos);
- return;
- }
-
- final File file = new File(path);
- if (file.exists()) {
- FileInputStream is = new FileInputStream(file);
- FileDescriptor fd = is.getFD();
- handleDataSource(isCurrent, srcId, fd, 0, DataSourceDesc.LONG_MAX, startPos, endPos);
- is.close();
- } else {
- throw new IOException("handleDataSource failed.");
- }
- }
-
- private native void nativeHandleDataSourceUrl(
- boolean isCurrent, long srcId,
- Media2HTTPService httpService, String path, String[] keys, String[] values,
- long startPos, long endPos)
- throws IOException;
-
- /**
- * Sets the data source (FileDescriptor) to use. The FileDescriptor must be
- * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility
- * to close the file descriptor. It is safe to do so as soon as this call returns.
- *
- * @throws IllegalStateException if it is called in an invalid state
- * @throws IllegalArgumentException if fd is not a valid FileDescriptor
- * @throws IOException if fd can not be read
- */
- private void handleDataSource(
- boolean isCurrent, long srcId,
- FileDescriptor fd, long offset, long length,
- long startPos, long endPos) throws IOException {
- nativeHandleDataSourceFD(isCurrent, srcId, fd, offset, length, startPos, endPos);
- }
-
- private native void nativeHandleDataSourceFD(boolean isCurrent, long srcId,
- FileDescriptor fd, long offset, long length,
- long startPos, long endPos) throws IOException;
-
- /**
- * @throws IllegalStateException if it is called in an invalid state
- * @throws IllegalArgumentException if dataSource is not a valid Media2DataSource
- */
- private void handleDataSource(boolean isCurrent, long srcId, Media2DataSource dataSource,
- long startPos, long endPos) {
- nativeHandleDataSourceCallback(isCurrent, srcId, dataSource, startPos, endPos);
- }
-
- private native void nativeHandleDataSourceCallback(
- boolean isCurrent, long srcId, Media2DataSource dataSource,
- long startPos, long endPos);
-
- // return true if there is a next data source, false otherwise.
- // This function should be always called on |mHandlerThread|.
- private boolean prepareNextDataSource() {
- if (Looper.myLooper() != mHandlerThread.getLooper()) {
- Log.e(TAG, "prepareNextDataSource: called on wrong looper");
- }
-
- boolean hasNextDSD;
- int state = getState();
- synchronized (mSrcLock) {
- hasNextDSD = !mNextSourceInfos.isEmpty();
- if (state == PLAYER_STATE_ERROR || state == PLAYER_STATE_IDLE) {
- // Current source has not been prepared yet.
- return hasNextDSD;
- }
-
- SourceInfo nextSource = mNextSourceInfos.peek();
- if (!hasNextDSD || nextSource.mStateAsNextSource != NEXT_SOURCE_STATE_INIT) {
- // There is no next source or it's in preparing or prepared state.
- return hasNextDSD;
- }
-
- try {
- nextSource.mStateAsNextSource = NEXT_SOURCE_STATE_PREPARING;
- handleDataSource(false /* isCurrent */, nextSource.mDSD, nextSource.mId);
- } catch (Exception e) {
- Message msg = mTaskHandler.obtainMessage(
- MEDIA_ERROR, MEDIA_ERROR_IO, MEDIA_ERROR_UNKNOWN, null);
- mTaskHandler.handleMessage(msg, nextSource.mId);
-
- mNextSourceInfos.poll();
- return prepareNextDataSource();
- }
- }
- return hasNextDSD;
- }
-
- // This function should be always called on |mHandlerThread|.
- private void playNextDataSource() {
- if (Looper.myLooper() != mHandlerThread.getLooper()) {
- Log.e(TAG, "playNextDataSource: called on wrong looper");
- }
-
- boolean hasNextDSD = false;
- synchronized (mSrcLock) {
- if (!mNextSourceInfos.isEmpty()) {
- hasNextDSD = true;
- SourceInfo nextSourceInfo = mNextSourceInfos.peek();
- if (nextSourceInfo.mStateAsNextSource == NEXT_SOURCE_STATE_PREPARED) {
- // Switch to next source only when it has been prepared.
- mCurrentSourceInfo = mNextSourceInfos.poll();
-
- long srcId = mCurrentSourceInfo.mId;
- try {
- nativePlayNextDataSource(srcId);
- } catch (Exception e) {
- Message msg2 = mTaskHandler.obtainMessage(
- MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null);
- mTaskHandler.handleMessage(msg2, srcId);
- // Keep |mNextSourcePlayPending|
- hasNextDSD = prepareNextDataSource();
- }
- if (hasNextDSD) {
- stayAwake(true);
-
- // Now a new current src is playing.
- // Wait for MEDIA_INFO_DATA_SOURCE_START to prepare next source.
- }
- } else if (nextSourceInfo.mStateAsNextSource == NEXT_SOURCE_STATE_INIT) {
- hasNextDSD = prepareNextDataSource();
- }
- }
- }
-
- if (!hasNextDSD) {
- sendEvent(new EventNotifier() {
- @Override
- public void notify(EventCallback callback) {
- callback.onInfo(
- MediaPlayer2Impl.this, null, MEDIA_INFO_DATA_SOURCE_LIST_END, 0);
- }
- });
- }
- }
-
- private native void nativePlayNextDataSource(long srcId);
-
- //--------------------------------------------------------------------------
- // Explicit Routing
- //--------------------
- private AudioDeviceInfo mPreferredDevice = null;
-
- @Override
- public boolean setPreferredDevice(AudioDeviceInfo deviceInfo) {
- boolean status = native_setPreferredDevice(deviceInfo);
- if (status == true) {
- synchronized (this) {
- mPreferredDevice = deviceInfo;
- }
- }
- return status;
- }
-
- @Override
- public AudioDeviceInfo getPreferredDevice() {
- synchronized (this) {
- return mPreferredDevice;
- }
- }
-
- @Override
- public native AudioDeviceInfo getRoutedDevice();
-
- @Override
- public void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener,
- Handler handler) {
- if (listener == null) {
- throw new IllegalArgumentException("addOnRoutingChangedListener: listener is NULL");
- }
- RoutingDelegate routingDelegate = new RoutingDelegate(this, listener, handler);
- native_addDeviceCallback(routingDelegate);
- }
-
- @Override
- public void removeOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener) {
- if (listener == null) {
- throw new IllegalArgumentException("removeOnRoutingChangedListener: listener is NULL");
- }
- native_removeDeviceCallback(listener);
- }
-
- private native boolean native_setPreferredDevice(AudioDeviceInfo device);
- private native void native_addDeviceCallback(RoutingDelegate rd);
- private native void native_removeDeviceCallback(
- AudioRouting.OnRoutingChangedListener listener);
-
- @Override
- public Object setWakeMode(Context context, int mode) {
- return addTask(new Task(CALL_COMPLETED_SET_WAKE_MODE, false) {
- @Override
- void process() {
- boolean washeld = false;
-
- if (mWakeLock != null) {
- if (mWakeLock.isHeld()) {
- washeld = true;
- mWakeLock.release();
- }
- mWakeLock = null;
- }
-
- PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- ActivityManager am =
- (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
- List<RunningAppProcessInfo> runningAppsProcInfo = am.getRunningAppProcesses();
- int pid = android.os.Process.myPid();
- String name = "pid " + String.valueOf(pid);
- if (runningAppsProcInfo != null) {
- for (RunningAppProcessInfo procInfo : runningAppsProcInfo) {
- if (procInfo.pid == pid) {
- name = procInfo.processName;
- break;
- }
- }
- }
- mWakeLock = pm.newWakeLock(mode | PowerManager.ON_AFTER_RELEASE, name);
- mWakeLock.setReferenceCounted(false);
- if (washeld) {
- mWakeLock.acquire();
- }
- }
- });
- }
-
- @Override
- public Object setScreenOnWhilePlaying(boolean screenOn) {
- return addTask(new Task(CALL_COMPLETED_SET_SCREEN_ON_WHILE_PLAYING, false) {
- @Override
- void process() {
- if (mScreenOnWhilePlaying != screenOn) {
- if (screenOn && mSurfaceHolder == null) {
- Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective"
- + " without a SurfaceHolder");
- }
- mScreenOnWhilePlaying = screenOn;
- updateSurfaceScreenOn();
- }
- }
- });
- }
-
- private void stayAwake(boolean awake) {
- if (mWakeLock != null) {
- if (awake && !mWakeLock.isHeld()) {
- mWakeLock.acquire();
- } else if (!awake && mWakeLock.isHeld()) {
- mWakeLock.release();
- }
- }
- mStayAwake = awake;
- updateSurfaceScreenOn();
- }
-
- private void updateSurfaceScreenOn() {
- if (mSurfaceHolder != null) {
- mSurfaceHolder.setKeepScreenOn(mScreenOnWhilePlaying && mStayAwake);
- }
- }
-
- @Override
- public VideoSize getVideoSize() {
- return mVideoSize;
- }
-
- @Override
- public PersistableBundle getMetrics() {
- PersistableBundle bundle = native_getMetrics();
- return bundle;
- }
-
- private native PersistableBundle native_getMetrics();
-
- @Override
- @NonNull
- native BufferingParams getBufferingParams();
-
- @Override
- Object setBufferingParams(@NonNull BufferingParams params) {
- return addTask(new Task(CALL_COMPLETED_SET_BUFFERING_PARAMS, false) {
- @Override
- void process() {
- checkArgument(params != null, "the BufferingParams cannot be null");
- _setBufferingParams(params);
- }
- });
- }
-
- private native void _setBufferingParams(@NonNull BufferingParams params);
-
- @Override
- public Object setPlaybackParams(@NonNull PlaybackParams params) {
- return addTask(new Task(CALL_COMPLETED_SET_PLAYBACK_PARAMS, false) {
- @Override
- void process() {
- checkArgument(params != null, "the PlaybackParams cannot be null");
- _setPlaybackParams(params);
- }
- });
- }
-
- private native void _setPlaybackParams(@NonNull PlaybackParams params);
-
- @Override
- @NonNull
- public native PlaybackParams getPlaybackParams();
-
- @Override
- public Object setSyncParams(@NonNull SyncParams params) {
- return addTask(new Task(CALL_COMPLETED_SET_SYNC_PARAMS, false) {
- @Override
- void process() {
- checkArgument(params != null, "the SyncParams cannot be null");
- _setSyncParams(params);
- }
- });
- }
-
- private native void _setSyncParams(@NonNull SyncParams params);
-
- @Override
- @NonNull
- public native SyncParams getSyncParams();
-
- @Override
- public Object seekTo(final long msec, @SeekMode int mode) {
- return addTask(new Task(CALL_COMPLETED_SEEK_TO, true) {
- @Override
- void process() {
- if (mode < SEEK_PREVIOUS_SYNC || mode > SEEK_CLOSEST) {
- final String msg = "Illegal seek mode: " + mode;
- throw new IllegalArgumentException(msg);
- }
- // TODO: pass long to native, instead of truncating here.
- long posMs = msec;
- if (posMs > Integer.MAX_VALUE) {
- Log.w(TAG, "seekTo offset " + posMs + " is too large, cap to "
- + Integer.MAX_VALUE);
- posMs = Integer.MAX_VALUE;
- } else if (posMs < Integer.MIN_VALUE) {
- Log.w(TAG, "seekTo offset " + posMs + " is too small, cap to "
- + Integer.MIN_VALUE);
- posMs = Integer.MIN_VALUE;
- }
-
- synchronized (mTaskLock) {
- if (mIsPreviousCommandSeekTo
- && mPreviousSeekPos == posMs
- && mPreviousSeekMode == mode) {
- throw new CommandSkippedException(
- "same as previous seekTo");
- }
- }
-
- _seekTo(posMs, mode);
-
- synchronized (mTaskLock) {
- mIsPreviousCommandSeekTo = true;
- mPreviousSeekPos = posMs;
- mPreviousSeekMode = mode;
- }
- }
- });
- }
-
- private native final void _seekTo(long msec, int mode);
-
- /**
- * Get current playback position as a {@link MediaTimestamp}.
- * <p>
- * The MediaTimestamp represents how the media time correlates to the system time in
- * a linear fashion using an anchor and a clock rate. During regular playback, the media
- * time moves fairly constantly (though the anchor frame may be rebased to a current
- * system time, the linear correlation stays steady). Therefore, this method does not
- * need to be called often.
- * <p>
- * To help users get current playback position, this method always anchors the timestamp
- * to the current {@link System#nanoTime system time}, so
- * {@link MediaTimestamp#getAnchorMediaTimeUs} can be used as current playback position.
- *
- * @return a MediaTimestamp object if a timestamp is available, or {@code null} if no timestamp
- * is available, e.g. because the media player has not been initialized.
- *
- * @see MediaTimestamp
- */
- @Override
- @Nullable
- public MediaTimestamp getTimestamp()
- {
- try {
- // TODO: get the timestamp from native side
- return new MediaTimestamp(
- getCurrentPosition() * 1000L,
- System.nanoTime(),
- getState() == PLAYER_STATE_PLAYING ? getPlaybackParams().getSpeed() : 0.f);
- } catch (IllegalStateException e) {
- return null;
- }
- }
-
- /**
- * Resets the MediaPlayer2 to its uninitialized state. After calling
- * this method, you will have to initialize it again by setting the
- * data source and calling prepare().
- */
- @Override
- public void reset() {
- synchronized (mEventCbLock) {
- mEventCallbackRecords.clear();
- }
- synchronized (mDrmEventCbLock) {
- mDrmEventCallbackRecords.clear();
- }
- synchronized (mSrcLock) {
- mCurrentSourceInfo = null;
- mNextSourceInfos.clear();
- }
-
- synchronized (mTaskLock) {
- mPendingTasks.clear();
- mIsPreviousCommandSeekTo = false;
- }
-
- stayAwake(false);
- _reset();
- // make sure none of the listeners get called anymore
- if (mTaskHandler != null) {
- mTaskHandler.removeCallbacksAndMessages(null);
- }
-
- resetDrmState();
- }
-
- private native void _reset();
-
- // Keep KEY_PARAMETER_* in sync with include/media/mediaplayer2.h
- private final static int KEY_PARAMETER_AUDIO_ATTRIBUTES = 1400;
- /**
- * Sets the audio attributes.
- * @param value value of the parameter to be set.
- * @return true if the parameter is set successfully, false otherwise
- */
- private native boolean native_setAudioAttributes(AudioAttributes audioAttributes);
- private native AudioAttributes native_getAudioAttributes();
-
-
- /**
- * Checks whether the MediaPlayer2 is looping or non-looping.
- *
- * @return true if the MediaPlayer2 is currently looping, false otherwise
- * @hide
- */
- @Override
- public native boolean isLooping();
-
- /**
- * Sets the audio session ID.
- *
- * @param sessionId the audio session ID.
- * The audio session ID is a system wide unique identifier for the audio stream played by
- * this MediaPlayer2 instance.
- * The primary use of the audio session ID is to associate audio effects to a particular
- * instance of MediaPlayer2: if an audio session ID is provided when creating an audio effect,
- * this effect will be applied only to the audio content of media players within the same
- * audio session and not to the output mix.
- * When created, a MediaPlayer2 instance automatically generates its own audio session ID.
- * However, it is possible to force this player to be part of an already existing audio session
- * by calling this method.
- * This method must be called before one of the overloaded <code> setDataSource </code> methods.
- * @throws IllegalStateException if it is called in an invalid state
- * @throws IllegalArgumentException if the sessionId is invalid.
- */
- @Override
- public Object setAudioSessionId(int sessionId) {
- return addTask(new Task(CALL_COMPLETED_SET_AUDIO_SESSION_ID, false) {
- @Override
- void process() {
- _setAudioSessionId(sessionId);
- }
- });
- }
-
- private native void _setAudioSessionId(int sessionId);
-
- /**
- * Returns the audio session ID.
- *
- * @return the audio session ID. {@see #setAudioSessionId(int)}
- * Note that the audio session ID is 0 only if a problem occured when the MediaPlayer2 was contructed.
- */
- @Override
- public native int getAudioSessionId();
-
- /**
- * Attaches an auxiliary effect to the player. A typical auxiliary effect is a reverberation
- * effect which can be applied on any sound source that directs a certain amount of its
- * energy to this effect. This amount is defined by setAuxEffectSendLevel().
- * See {@link #setAuxEffectSendLevel(float)}.
- * <p>After creating an auxiliary effect (e.g.
- * {@link android.media.audiofx.EnvironmentalReverb}), retrieve its ID with
- * {@link android.media.audiofx.AudioEffect#getId()} and use it when calling this method
- * to attach the player to the effect.
- * <p>To detach the effect from the player, call this method with a null effect id.
- * <p>This method must be called after one of the overloaded <code> setDataSource </code>
- * methods.
- * @param effectId system wide unique id of the effect to attach
- */
- @Override
- public Object attachAuxEffect(int effectId) {
- return addTask(new Task(CALL_COMPLETED_ATTACH_AUX_EFFECT, false) {
- @Override
- void process() {
- _attachAuxEffect(effectId);
- }
- });
- }
-
- private native void _attachAuxEffect(int effectId);
-
- /**
- * Sets the send level of the player to the attached auxiliary effect.
- * See {@link #attachAuxEffect(int)}. The level value range is 0 to 1.0.
- * <p>By default the send level is 0, so even if an effect is attached to the player
- * this method must be called for the effect to be applied.
- * <p>Note that the passed level value is a raw scalar. UI controls should be scaled
- * logarithmically: the gain applied by audio framework ranges from -72dB to 0dB,
- * so an appropriate conversion from linear UI input x to level is:
- * x == 0 -> level = 0
- * 0 < x <= R -> level = 10^(72*(x-R)/20/R)
- * @param level send level scalar
- */
- @Override
- public Object setAuxEffectSendLevel(float level) {
- return addTask(new Task(CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL, false) {
- @Override
- void process() {
- _setAuxEffectSendLevel(level);
- }
- });
- }
-
- private native void _setAuxEffectSendLevel(float level);
-
- private static native final void native_init();
- private native final void native_setup(Object mediaplayer2_this);
- private native final void native_finalize();
-
- private static native final void native_stream_event_onTearDown(
- long nativeCallbackPtr, long userDataPtr);
- private static native final void native_stream_event_onStreamPresentationEnd(
- long nativeCallbackPtr, long userDataPtr);
- private static native final void native_stream_event_onStreamDataRequest(
- long jAudioTrackPtr, long nativeCallbackPtr, long userDataPtr);
-
- /**
- * Class for MediaPlayer2 to return each audio/video/subtitle track's metadata.
- *
- * @see android.media.MediaPlayer2#getTrackInfo
- */
- public static final class TrackInfoImpl extends TrackInfo {
- /**
- * Gets the track type.
- * @return TrackType which indicates if the track is video, audio, timed text.
- */
- @Override
- public int getTrackType() {
- return mTrackType;
- }
-
- /**
- * Gets the language code of the track.
- * @return a language code in either way of ISO-639-1 or ISO-639-2.
- * When the language is unknown or could not be determined,
- * ISO-639-2 language code, "und", is returned.
- */
- @Override
- public String getLanguage() {
- String language = mFormat.getString(MediaFormat.KEY_LANGUAGE);
- return language == null ? "und" : language;
- }
-
- /**
- * Gets the {@link MediaFormat} of the track. If the format is
- * unknown or could not be determined, null is returned.
- */
- @Override
- public MediaFormat getFormat() {
- if (mTrackType == MEDIA_TRACK_TYPE_TIMEDTEXT
- || mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) {
- return mFormat;
- }
- return null;
- }
-
- final int mTrackType;
- final MediaFormat mFormat;
-
- TrackInfoImpl(Iterator<Value> in) {
- mTrackType = in.next().getInt32Value();
- // TODO: build the full MediaFormat; currently we are using createSubtitleFormat
- // even for audio/video tracks, meaning we only set the mime and language.
- String mime = in.next().getStringValue();
- String language = in.next().getStringValue();
- mFormat = MediaFormat.createSubtitleFormat(mime, language);
-
- if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) {
- mFormat.setInteger(MediaFormat.KEY_IS_AUTOSELECT, in.next().getInt32Value());
- mFormat.setInteger(MediaFormat.KEY_IS_DEFAULT, in.next().getInt32Value());
- mFormat.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, in.next().getInt32Value());
- }
- }
-
- /** @hide */
- TrackInfoImpl(int type, MediaFormat format) {
- mTrackType = type;
- mFormat = format;
- }
-
- @Override
- public String toString() {
- StringBuilder out = new StringBuilder(128);
- out.append(getClass().getName());
- out.append('{');
- switch (mTrackType) {
- case MEDIA_TRACK_TYPE_VIDEO:
- out.append("VIDEO");
- break;
- case MEDIA_TRACK_TYPE_AUDIO:
- out.append("AUDIO");
- break;
- case MEDIA_TRACK_TYPE_TIMEDTEXT:
- out.append("TIMEDTEXT");
- break;
- case MEDIA_TRACK_TYPE_SUBTITLE:
- out.append("SUBTITLE");
- break;
- default:
- out.append("UNKNOWN");
- break;
- }
- out.append(", " + mFormat.toString());
- out.append("}");
- return out.toString();
- }
- };
-
- /**
- * Returns a List of track information.
- *
- * @return List of track info. The total number of tracks is the array length.
- * Must be called again if an external timed text source has been added after
- * addTimedTextSource method is called.
- * @throws IllegalStateException if it is called in an invalid state.
- */
- @Override
- public List<TrackInfo> getTrackInfo() {
- TrackInfoImpl trackInfo[] = getInbandTrackInfoImpl();
- return Arrays.asList(trackInfo);
- }
-
- private TrackInfoImpl[] getInbandTrackInfoImpl() throws IllegalStateException {
- PlayerMessage request = PlayerMessage.newBuilder()
- .addValues(Value.newBuilder().setInt32Value(INVOKE_ID_GET_TRACK_INFO))
- .build();
- PlayerMessage response = invoke(request);
- if (response == null) {
- return null;
- }
- Iterator<Value> in = response.getValuesList().iterator();
- int size = in.next().getInt32Value();
- if (size == 0) {
- return null;
- }
- TrackInfoImpl trackInfo[] = new TrackInfoImpl[size];
- for (int i = 0; i < size; ++i) {
- trackInfo[i] = new TrackInfoImpl(in);
- }
- return trackInfo;
- }
-
- /*
- * A helper function to check if the mime type is supported by media framework.
- */
- private static boolean availableMimeTypeForExternalSource(String mimeType) {
- if (MEDIA_MIMETYPE_TEXT_SUBRIP.equals(mimeType)) {
- return true;
- }
- return false;
- }
-
- /**
- * Returns the index of the audio, video, or subtitle track currently selected for playback,
- * The return value is an index into the array returned by {@link #getTrackInfo()}, and can
- * be used in calls to {@link #selectTrack(int)} or {@link #deselectTrack(int)}.
- *
- * @param trackType should be one of {@link TrackInfo#MEDIA_TRACK_TYPE_VIDEO},
- * {@link TrackInfo#MEDIA_TRACK_TYPE_AUDIO}, or
- * {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE}
- * @return index of the audio, video, or subtitle track currently selected for playback;
- * a negative integer is returned when there is no selected track for {@code trackType} or
- * when {@code trackType} is not one of audio, video, or subtitle.
- * @throws IllegalStateException if called after {@link #close()}
- *
- * @see #getTrackInfo()
- * @see #selectTrack(int)
- * @see #deselectTrack(int)
- */
- @Override
- public int getSelectedTrack(int trackType) {
- PlayerMessage request = PlayerMessage.newBuilder()
- .addValues(Value.newBuilder().setInt32Value(INVOKE_ID_GET_SELECTED_TRACK))
- .addValues(Value.newBuilder().setInt32Value(trackType))
- .build();
- PlayerMessage response = invoke(request);
- if (response == null) {
- return -1;
- }
- return response.getValues(0).getInt32Value();
- }
-
- /**
- * Selects a track.
- * <p>
- * If a MediaPlayer2 is in invalid state, it throws an IllegalStateException exception.
- * If a MediaPlayer2 is in <em>Started</em> state, the selected track is presented immediately.
- * If a MediaPlayer2 is not in Started state, it just marks the track to be played.
- * </p>
- * <p>
- * In any valid state, if it is called multiple times on the same type of track (ie. Video,
- * Audio, Timed Text), the most recent one will be chosen.
- * </p>
- * <p>
- * The first audio and video tracks are selected by default if available, even though
- * this method is not called. However, no timed text track will be selected until
- * this function is called.
- * </p>
- * <p>
- * Currently, only timed text tracks or audio tracks can be selected via this method.
- * In addition, the support for selecting an audio track at runtime is pretty limited
- * in that an audio track can only be selected in the <em>Prepared</em> state.
- * </p>
- * @param index the index of the track to be selected. The valid range of the index
- * is 0..total number of track - 1. The total number of tracks as well as the type of
- * each individual track can be found by calling {@link #getTrackInfo()} method.
- * @throws IllegalStateException if called in an invalid state.
- *
- * @see android.media.MediaPlayer2#getTrackInfo
- */
- @Override
- public Object selectTrack(int index) {
- return addTask(new Task(CALL_COMPLETED_SELECT_TRACK, false) {
- @Override
- void process() {
- selectOrDeselectTrack(index, true /* select */);
- }
- });
- }
-
- /**
- * Deselect a track.
- * <p>
- * Currently, the track must be a timed text track and no audio or video tracks can be
- * deselected. If the timed text track identified by index has not been
- * selected before, it throws an exception.
- * </p>
- * @param index the index of the track to be deselected. The valid range of the index
- * is 0..total number of tracks - 1. The total number of tracks as well as the type of
- * each individual track can be found by calling {@link #getTrackInfo()} method.
- * @throws IllegalStateException if called in an invalid state.
- *
- * @see android.media.MediaPlayer2#getTrackInfo
- */
- @Override
- public Object deselectTrack(int index) {
- return addTask(new Task(CALL_COMPLETED_DESELECT_TRACK, false) {
- @Override
- void process() {
- selectOrDeselectTrack(index, false /* select */);
- }
- });
- }
-
- private void selectOrDeselectTrack(int index, boolean select)
- throws IllegalStateException {
- PlayerMessage request = PlayerMessage.newBuilder()
- .addValues(Value.newBuilder().setInt32Value(
- select? INVOKE_ID_SELECT_TRACK: INVOKE_ID_DESELECT_TRACK))
- .addValues(Value.newBuilder().setInt32Value(index))
- .build();
- invoke(request);
- }
-
- // Have to declare protected for finalize() since it is protected
- // in the base class Object.
- @Override
- protected void finalize() throws Throwable {
- super.finalize();
- native_finalize();
- }
-
- private synchronized void release() {
- if (mReleased) {
- return;
- }
- stayAwake(false);
- updateSurfaceScreenOn();
- synchronized (mEventCbLock) {
- mEventCallbackRecords.clear();
- }
- if (mHandlerThread != null) {
- mHandlerThread.quitSafely();
- mHandlerThread = null;
- }
-
- // Modular DRM clean up
- mOnDrmConfigHelper = null;
- synchronized (mDrmEventCbLock) {
- mDrmEventCallbackRecords.clear();
- }
- resetDrmState();
-
- _release();
- mReleased = true;
- }
-
- private native void _release();
-
- /* Do not change these values without updating their counterparts
- * in include/media/mediaplayer2.h!
- */
- private static final int MEDIA_NOP = 0; // interface test message
- private static final int MEDIA_PREPARED = 1;
- private static final int MEDIA_PLAYBACK_COMPLETE = 2;
- private static final int MEDIA_BUFFERING_UPDATE = 3;
- private static final int MEDIA_SEEK_COMPLETE = 4;
- private static final int MEDIA_SET_VIDEO_SIZE = 5;
- private static final int MEDIA_STARTED = 6;
- private static final int MEDIA_PAUSED = 7;
- private static final int MEDIA_STOPPED = 8;
- private static final int MEDIA_SKIPPED = 9;
- private static final int MEDIA_NOTIFY_TIME = 98;
- private static final int MEDIA_TIMED_TEXT = 99;
- private static final int MEDIA_ERROR = 100;
- private static final int MEDIA_INFO = 200;
- private static final int MEDIA_SUBTITLE_DATA = 201;
- private static final int MEDIA_META_DATA = 202;
- private static final int MEDIA_DRM_INFO = 210;
-
- private class TaskHandler extends Handler {
- private MediaPlayer2Impl mMediaPlayer;
-
- public TaskHandler(MediaPlayer2Impl mp, Looper looper) {
- super(looper);
- mMediaPlayer = mp;
- }
-
- @Override
- public void handleMessage(Message msg) {
- handleMessage(msg, 0);
- }
-
- public void handleMessage(Message msg, long srcId) {
- if (mMediaPlayer.mNativeContext == 0) {
- Log.w(TAG, "mediaplayer2 went away with unhandled events");
- return;
- }
- final int what = msg.arg1;
- final int extra = msg.arg2;
-
- final SourceInfo sourceInfo = getSourceInfoById(srcId);
- if (sourceInfo == null) {
- return;
- }
- final DataSourceDesc dsd = sourceInfo.mDSD;
-
- switch(msg.what) {
- case MEDIA_PREPARED:
- {
- if (dsd != null) {
- sendEvent(new EventNotifier() {
- @Override
- public void notify(EventCallback callback) {
- callback.onInfo(
- mMediaPlayer, dsd, MEDIA_INFO_PREPARED, 0);
- }
- });
- }
-
- synchronized (mSrcLock) {
- SourceInfo nextSourceInfo = mNextSourceInfos.peek();
- Log.i(TAG, "MEDIA_PREPARED: srcId=" + srcId
- + ", curSrc=" + mCurrentSourceInfo
- + ", nextSrc=" + nextSourceInfo);
-
- if (isCurrentSource(srcId)) {
- prepareNextDataSource();
- } else if (isNextSource(srcId)) {
- nextSourceInfo.mStateAsNextSource = NEXT_SOURCE_STATE_PREPARED;
- if (nextSourceInfo.mPlayPendingAsNextSource) {
- playNextDataSource();
- }
- }
- }
-
- synchronized (mTaskLock) {
- if (mCurrentTask != null
- && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE
- && mCurrentTask.mDSD == dsd
- && mCurrentTask.mNeedToWaitForEventToComplete) {
- mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR);
- mCurrentTask = null;
- processPendingTask_l();
- }
- }
- return;
- }
-
- case MEDIA_DRM_INFO:
- {
- if (msg.obj == null) {
- Log.w(TAG, "MEDIA_DRM_INFO msg.obj=NULL");
- } else if (msg.obj instanceof byte[]) {
- // The PlayerMessage was parsed already in postEventFromNative
- final DrmInfoImpl drmInfo;
-
- synchronized (mDrmLock) {
- if (mDrmInfoImpl != null) {
- drmInfo = mDrmInfoImpl.makeCopy();
- } else {
- drmInfo = null;
- }
- }
-
- // notifying the client outside the lock
- if (drmInfo != null) {
- sendDrmEvent(new DrmEventNotifier() {
- @Override
- public void notify(DrmEventCallback callback) {
- callback.onDrmInfo(
- mMediaPlayer, dsd, drmInfo);
- }
- });
- }
- } else {
- Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + msg.obj);
- }
- return;
- }
-
- case MEDIA_PLAYBACK_COMPLETE:
- {
- if (isCurrentSource(srcId)) {
- sendEvent(new EventNotifier() {
- @Override
- public void notify(EventCallback callback) {
- callback.onInfo(
- mMediaPlayer, dsd, MEDIA_INFO_DATA_SOURCE_END, 0);
- }
- });
- stayAwake(false);
-
- synchronized (mSrcLock) {
- SourceInfo nextSourceInfo = mNextSourceInfos.peek();
- if (nextSourceInfo != null) {
- nextSourceInfo.mPlayPendingAsNextSource = true;
- }
- Log.i(TAG, "MEDIA_PLAYBACK_COMPLETE: srcId=" + srcId
- + ", curSrc=" + mCurrentSourceInfo
- + ", nextSrc=" + nextSourceInfo);
- }
-
- playNextDataSource();
- }
-
- return;
- }
-
- case MEDIA_STOPPED:
- case MEDIA_STARTED:
- case MEDIA_PAUSED:
- case MEDIA_SKIPPED:
- case MEDIA_NOTIFY_TIME:
- {
- // Do nothing. The client should have enough information with
- // {@link EventCallback#onMediaTimeDiscontinuity}.
- break;
- }
-
- case MEDIA_BUFFERING_UPDATE:
- {
- final int percent = msg.arg1;
- sendEvent(new EventNotifier() {
- @Override
- public void notify(EventCallback callback) {
- callback.onInfo(
- mMediaPlayer, dsd, MEDIA_INFO_BUFFERING_UPDATE, percent);
- }
- });
-
- SourceInfo src = getSourceInfoById(srcId);
- if (src != null) {
- src.mBufferedPercentage.set(percent);
- }
-
- return;
- }
-
- case MEDIA_SEEK_COMPLETE:
- {
- synchronized (mTaskLock) {
- if (!mPendingTasks.isEmpty()
- && mPendingTasks.get(0).mMediaCallType != CALL_COMPLETED_SEEK_TO
- && getState() == PLAYER_STATE_PLAYING) {
- mIsPreviousCommandSeekTo = false;
- }
-
- if (mCurrentTask != null
- && mCurrentTask.mMediaCallType == CALL_COMPLETED_SEEK_TO
- && mCurrentTask.mNeedToWaitForEventToComplete) {
- mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR);
- mCurrentTask = null;
- processPendingTask_l();
- }
- }
- return;
- }
-
- case MEDIA_SET_VIDEO_SIZE:
- {
- final int width = msg.arg1;
- final int height = msg.arg2;
-
- mVideoSize = new VideoSize(width, height);
- sendEvent(new EventNotifier() {
- @Override
- public void notify(EventCallback callback) {
- callback.onVideoSizeChanged(
- mMediaPlayer, dsd, mVideoSize);
- }
- });
- return;
- }
-
- case MEDIA_ERROR:
- {
- Log.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")");
- sendEvent(new EventNotifier() {
- @Override
- public void notify(EventCallback callback) {
- callback.onError(
- mMediaPlayer, dsd, what, extra);
- }
- });
- sendEvent(new EventNotifier() {
- @Override
- public void notify(EventCallback callback) {
- callback.onInfo(
- mMediaPlayer, dsd, MEDIA_INFO_DATA_SOURCE_END, 0);
- }
- });
- stayAwake(false);
- return;
- }
-
- case MEDIA_INFO:
- {
- switch (msg.arg1) {
- case MEDIA_INFO_VIDEO_TRACK_LAGGING:
- Log.i(TAG, "Info (" + msg.arg1 + "," + msg.arg2 + ")");
- break;
- }
-
- sendEvent(new EventNotifier() {
- @Override
- public void notify(EventCallback callback) {
- callback.onInfo(
- mMediaPlayer, dsd, what, extra);
- }
- });
-
- if (msg.arg1 == MEDIA_INFO_DATA_SOURCE_START) {
- if (isCurrentSource(srcId)) {
- prepareNextDataSource();
- }
- }
-
- // No real default action so far.
- return;
- }
-
- case MEDIA_TIMED_TEXT:
- {
- final TimedText text;
- if (msg.obj instanceof byte[]) {
- PlayerMessage playerMsg;
- try {
- playerMsg = PlayerMessage.parseFrom((byte[]) msg.obj);
- } catch (InvalidProtocolBufferException e) {
- Log.w(TAG, "Failed to parse timed text.", e);
- return;
- }
- text = TimedTextUtil.parsePlayerMessage(playerMsg);
- } else {
- text = null;
- }
-
- sendEvent(new EventNotifier() {
- @Override
- public void notify(EventCallback callback) {
- callback.onTimedText(
- mMediaPlayer, dsd, text);
- }
- });
- return;
- }
-
- case MEDIA_SUBTITLE_DATA:
- {
- if (msg.obj instanceof byte[]) {
- PlayerMessage playerMsg;
- try {
- playerMsg = PlayerMessage.parseFrom((byte[]) msg.obj);
- } catch (InvalidProtocolBufferException e) {
- Log.w(TAG, "Failed to parse subtitle data.", e);
- return;
- }
- Iterator<Value> in = playerMsg.getValuesList().iterator();
- SubtitleData data = new SubtitleData(
- in.next().getInt32Value(), // trackIndex
- in.next().getInt64Value(), // startTimeUs
- in.next().getInt64Value(), // durationUs
- in.next().getBytesValue().toByteArray()); // data
- sendEvent(new EventNotifier() {
- @Override
- public void notify(EventCallback callback) {
- callback.onSubtitleData(
- mMediaPlayer, dsd, data);
- }
- });
- }
- return;
- }
-
- case MEDIA_META_DATA:
- {
- final TimedMetaData data;
- if (msg.obj instanceof byte[]) {
- PlayerMessage playerMsg;
- try {
- playerMsg = PlayerMessage.parseFrom((byte[]) msg.obj);
- } catch (InvalidProtocolBufferException e) {
- Log.w(TAG, "Failed to parse timed meta data.", e);
- return;
- }
- Iterator<Value> in = playerMsg.getValuesList().iterator();
- data = new TimedMetaData(
- in.next().getInt64Value(), // timestampUs
- in.next().getBytesValue().toByteArray()); // metaData
- } else {
- data = null;
- }
-
- sendEvent(new EventNotifier() {
- @Override
- public void notify(EventCallback callback) {
- callback.onTimedMetaDataAvailable(
- mMediaPlayer, dsd, data);
- }
- });
- return;
- }
-
- case MEDIA_NOP: // interface test message - ignore
- {
- break;
- }
-
- default:
- {
- Log.e(TAG, "Unknown message type " + msg.what);
- return;
- }
- }
- }
-
- }
-
- /*
- * Called from native code when an interesting event happens. This method
- * just uses the TaskHandler system to post the event back to the main app thread.
- * We use a weak reference to the original MediaPlayer2 object so that the native
- * code is safe from the object disappearing from underneath it. (This is
- * the cookie passed to native_setup().)
- */
- private static void postEventFromNative(Object mediaplayer2_ref, long srcId,
- int what, int arg1, int arg2, byte[] obj)
- {
- final MediaPlayer2Impl mp = (MediaPlayer2Impl)((WeakReference)mediaplayer2_ref).get();
- if (mp == null) {
- return;
- }
-
- switch (what) {
- case MEDIA_DRM_INFO:
- // We need to derive mDrmInfoImpl before prepare() returns so processing it here
- // before the notification is sent to TaskHandler below. TaskHandler runs in the
- // notification looper so its handleMessage might process the event after prepare()
- // has returned.
- Log.v(TAG, "postEventFromNative MEDIA_DRM_INFO");
- if (obj != null) {
- PlayerMessage playerMsg;
- try {
- playerMsg = PlayerMessage.parseFrom(obj);
- } catch (InvalidProtocolBufferException e) {
- Log.w(TAG, "MEDIA_DRM_INFO failed to parse msg.obj " + obj);
- break;
- }
- DrmInfoImpl drmInfo = new DrmInfoImpl(playerMsg);
- synchronized (mp.mDrmLock) {
- mp.mDrmInfoImpl = drmInfo;
- }
- } else {
- Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + obj);
- }
- break;
-
- case MEDIA_PREPARED:
- // By this time, we've learned about DrmInfo's presence or absence. This is meant
- // mainly for prepare() use case. For prepare(), this still can run to a race
- // condition b/c MediaPlayerNative releases the prepare() lock before calling notify
- // so we also set mDrmInfoResolved in prepare().
- synchronized (mp.mDrmLock) {
- mp.mDrmInfoResolved = true;
- }
- break;
-
- }
-
- if (mp.mTaskHandler != null) {
- Message m = mp.mTaskHandler.obtainMessage(what, arg1, arg2, obj);
-
- mp.mTaskHandler.post(new Runnable() {
- @Override
- public void run() {
- mp.mTaskHandler.handleMessage(m, srcId);
- }
- });
- }
- }
-
- private final Object mEventCbLock = new Object();
- private ArrayList<Pair<Executor, EventCallback> > mEventCallbackRecords
- = new ArrayList<Pair<Executor, EventCallback> >();
-
- /**
- * Register a callback to be invoked when the media source is ready
- * for playback.
- *
- * @param eventCallback the callback that will be run
- * @param executor the executor through which the callback should be invoked
- */
- @Override
- public void registerEventCallback(@NonNull @CallbackExecutor Executor executor,
- @NonNull EventCallback eventCallback) {
- if (eventCallback == null) {
- throw new IllegalArgumentException("Illegal null EventCallback");
- }
- if (executor == null) {
- throw new IllegalArgumentException(
- "Illegal null Executor for the EventCallback");
- }
- synchronized (mEventCbLock) {
- mEventCallbackRecords.add(new Pair(executor, eventCallback));
- }
- }
-
- /**
- * Clears the {@link EventCallback}.
- */
- @Override
- public void unregisterEventCallback(EventCallback eventCallback) {
- synchronized (mEventCbLock) {
- for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
- if (cb.second == eventCallback) {
- mEventCallbackRecords.remove(cb);
- }
- }
- }
- }
-
- private static void checkArgument(boolean expression, String errorMessage) {
- if (!expression) {
- throw new IllegalArgumentException(errorMessage);
- }
- }
-
- private void sendEvent(final EventNotifier notifier) {
- synchronized (mEventCbLock) {
- try {
- for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
- cb.first.execute(() -> notifier.notify(cb.second));
- }
- } catch (RejectedExecutionException e) {
- // The executor has been shut down.
- Log.w(TAG, "The executor has been shut down. Ignoring event.");
- }
- }
- }
-
- private void sendDrmEvent(final DrmEventNotifier notifier) {
- synchronized (mDrmEventCbLock) {
- try {
- for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
- cb.first.execute(() -> notifier.notify(cb.second));
- }
- } catch (RejectedExecutionException e) {
- // The executor has been shut down.
- Log.w(TAG, "The executor has been shut down. Ignoring drm event.");
- }
- }
- }
-
- private interface EventNotifier {
- void notify(EventCallback callback);
- }
-
- private interface DrmEventNotifier {
- void notify(DrmEventCallback callback);
- }
-
- // Modular DRM begin
-
- /**
- * Register a callback to be invoked for configuration of the DRM object before
- * the session is created.
- * The callback will be invoked synchronously during the execution
- * of {@link #prepareDrm(UUID uuid)}.
- *
- * @param listener the callback that will be run
- */
- @Override
- public void setOnDrmConfigHelper(OnDrmConfigHelper listener)
- {
- synchronized (mDrmLock) {
- mOnDrmConfigHelper = listener;
- } // synchronized
- }
-
- private OnDrmConfigHelper mOnDrmConfigHelper;
-
- private final Object mDrmEventCbLock = new Object();
- private ArrayList<Pair<Executor, DrmEventCallback> > mDrmEventCallbackRecords
- = new ArrayList<Pair<Executor, DrmEventCallback> >();
-
- @Override
- public void registerDrmEventCallback(@NonNull @CallbackExecutor Executor executor,
- @NonNull DrmEventCallback eventCallback) {
- if (eventCallback == null) {
- throw new IllegalArgumentException("Illegal null EventCallback");
- }
- if (executor == null) {
- throw new IllegalArgumentException(
- "Illegal null Executor for the EventCallback");
- }
- synchronized (mDrmEventCbLock) {
- mDrmEventCallbackRecords.add(new Pair(executor, eventCallback));
- }
- }
-
- @Override
- public void unregisterDrmEventCallback(DrmEventCallback eventCallback) {
- synchronized (mDrmEventCbLock) {
- for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
- if (cb.second == eventCallback) {
- mDrmEventCallbackRecords.remove(cb);
- }
- }
- }
- }
-
- /**
- * Retrieves the DRM Info associated with the current source
- *
- * @throws IllegalStateException if called before prepare()
- */
- @Override
- public DrmInfo getDrmInfo() {
- DrmInfoImpl drmInfo = null;
-
- // there is not much point if the app calls getDrmInfo within an OnDrmInfoListenet;
- // regardless below returns drmInfo anyway instead of raising an exception
- synchronized (mDrmLock) {
- if (!mDrmInfoResolved && mDrmInfoImpl == null) {
- final String msg = "The Player has not been prepared yet";
- Log.v(TAG, msg);
- throw new IllegalStateException(msg);
- }
-
- if (mDrmInfoImpl != null) {
- drmInfo = mDrmInfoImpl.makeCopy();
- }
- } // synchronized
-
- return drmInfo;
- }
-
- @Override
- public Object prepareDrm(@NonNull UUID uuid) {
- return addTask(new Task(CALL_COMPLETED_PREPARE_DRM, true) {
- @Override
- void process() {
- int status = PREPARE_DRM_STATUS_SUCCESS;
- boolean sendEvent = true;
-
- try {
- doPrepareDrm(uuid);
- } catch (ResourceBusyException e) {
- status = PREPARE_DRM_STATUS_RESOURCE_BUSY;
- } catch (UnsupportedSchemeException e) {
- status = PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME;
- } catch (NotProvisionedException e) {
- Log.w(TAG, "prepareDrm: NotProvisionedException");
-
- // handle provisioning internally; it'll reset mPrepareDrmInProgress
- status = HandleProvisioninig(uuid);
-
- if (status == PREPARE_DRM_STATUS_SUCCESS) {
- // DrmEventCallback will be fired in provisioning
- sendEvent = false;
- } else {
- synchronized (mDrmLock) {
- cleanDrmObj();
- }
-
- switch (status) {
- case PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR:
- Log.e(TAG, "prepareDrm: Provisioning was required but failed " +
- "due to a network error.");
- break;
-
- case PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR:
- Log.e(TAG, "prepareDrm: Provisioning was required but the request " +
- "was denied by the server.");
- break;
-
- case PREPARE_DRM_STATUS_PREPARATION_ERROR:
- default:
- Log.e(TAG, "prepareDrm: Post-provisioning preparation failed.");
- break;
- }
- }
- } catch (Exception e) {
- status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
- }
-
- if (sendEvent) {
- final int prepareDrmStatus = status;
- sendDrmEvent(new DrmEventNotifier() {
- @Override
- public void notify(DrmEventCallback callback) {
- callback.onDrmPrepared(
- MediaPlayer2Impl.this, getCurrentDataSource(), prepareDrmStatus);
- }
- });
-
- synchronized (mTaskLock) {
- mCurrentTask = null;
- processPendingTask_l();
- }
- }
- }
- });
- }
-
- private void doPrepareDrm(@NonNull UUID uuid)
- throws UnsupportedSchemeException, ResourceBusyException,
- NotProvisionedException {
- Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigHelper: " + mOnDrmConfigHelper);
-
- synchronized (mDrmLock) {
- // only allowing if tied to a protected source; might relax for releasing offline keys
- if (mDrmInfoImpl == null) {
- final String msg = "prepareDrm(): Wrong usage: The player must be prepared and " +
- "DRM info be retrieved before this call.";
- Log.e(TAG, msg);
- throw new IllegalStateException(msg);
- }
-
- if (mActiveDrmScheme) {
- final String msg = "prepareDrm(): Wrong usage: There is already " +
- "an active DRM scheme with " + mDrmUUID;
- Log.e(TAG, msg);
- throw new IllegalStateException(msg);
- }
-
- if (mPrepareDrmInProgress) {
- final String msg = "prepareDrm(): Wrong usage: There is already " +
- "a pending prepareDrm call.";
- Log.e(TAG, msg);
- throw new IllegalStateException(msg);
- }
-
- if (mDrmProvisioningInProgress) {
- final String msg = "prepareDrm(): Unexpectd: Provisioning is already in progress.";
- Log.e(TAG, msg);
- throw new IllegalStateException(msg);
- }
-
- // shouldn't need this; just for safeguard
- cleanDrmObj();
-
- mPrepareDrmInProgress = true;
-
- try {
- // only creating the DRM object to allow pre-openSession configuration
- prepareDrm_createDrmStep(uuid);
- } catch (Exception e) {
- Log.w(TAG, "prepareDrm(): Exception ", e);
- mPrepareDrmInProgress = false;
- throw e;
- }
-
- mDrmConfigAllowed = true;
- } // synchronized
-
- // call the callback outside the lock
- if (mOnDrmConfigHelper != null) {
- mOnDrmConfigHelper.onDrmConfig(this, getCurrentDataSource());
- }
-
- synchronized (mDrmLock) {
- mDrmConfigAllowed = false;
- boolean earlyExit = false;
-
- try {
- prepareDrm_openSessionStep(uuid);
-
- mDrmUUID = uuid;
- mActiveDrmScheme = true;
- mPrepareDrmInProgress = false;
- } catch (IllegalStateException e) {
- final String msg = "prepareDrm(): Wrong usage: The player must be " +
- "in the prepared state to call prepareDrm().";
- Log.e(TAG, msg);
- earlyExit = true;
- mPrepareDrmInProgress = false;
- throw new IllegalStateException(msg);
- } catch (NotProvisionedException e) {
- Log.w(TAG, "prepareDrm: NotProvisionedException", e);
- throw e;
- } catch (Exception e) {
- Log.e(TAG, "prepareDrm: Exception " + e);
- earlyExit = true;
- mPrepareDrmInProgress = false;
- throw e;
- } finally {
- if (earlyExit) { // clean up object if didn't succeed
- cleanDrmObj();
- }
- } // finally
- } // synchronized
- }
-
- @Override
- public void releaseDrm()
- throws NoDrmSchemeException {
- synchronized (mDrmLock) {
- Log.v(TAG, "releaseDrm:");
-
- if (!mActiveDrmScheme) {
- Log.e(TAG, "releaseDrm(): No active DRM scheme to release.");
- throw new NoDrmSchemeExceptionImpl(
- "releaseDrm: No active DRM scheme to release.");
- }
-
- try {
- // we don't have the player's state in this layer. The below call raises
- // exception if we're in a non-stopped/prepared state.
-
- // for cleaning native/mediaserver crypto object
- _releaseDrm();
-
- // for cleaning client-side MediaDrm object; only called if above has succeeded
- cleanDrmObj();
-
- mActiveDrmScheme = false;
- } catch (IllegalStateException e) {
- Log.w(TAG, "releaseDrm: Exception ", e);
- throw new IllegalStateException(
- "releaseDrm: The player is not in a valid state.");
- } catch (Exception e) {
- Log.e(TAG, "releaseDrm: Exception ", e);
- }
- } // synchronized
- }
-
- private native void _releaseDrm();
-
- /**
- * A key request/response exchange occurs between the app and a license server
- * to obtain or release keys used to decrypt encrypted content.
- * <p>
- * getDrmKeyRequest() is used to obtain an opaque key request byte array that is
- * delivered to the license server. The opaque key request byte array is returned
- * in KeyRequest.data. The recommended URL to deliver the key request to is
- * returned in KeyRequest.defaultUrl.
- * <p>
- * After the app has received the key request response from the server,
- * it should deliver to the response to the DRM engine plugin using the method
- * {@link #provideDrmKeyResponse}.
- *
- * @param keySetId is the key-set identifier of the offline keys being released when keyType is
- * {@link MediaDrm#KEY_TYPE_RELEASE}. It should be set to null for other key requests, when
- * keyType is {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}.
- *
- * @param initData is the container-specific initialization data when the keyType is
- * {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. Its meaning is
- * interpreted based on the mime type provided in the mimeType parameter. It could
- * contain, for example, the content ID, key ID or other data obtained from the content
- * metadata that is required in generating the key request.
- * When the keyType is {@link MediaDrm#KEY_TYPE_RELEASE}, it should be set to null.
- *
- * @param mimeType identifies the mime type of the content
- *
- * @param keyType specifies the type of the request. The request may be to acquire
- * keys for streaming, {@link MediaDrm#KEY_TYPE_STREAMING}, or for offline content
- * {@link MediaDrm#KEY_TYPE_OFFLINE}, or to release previously acquired
- * keys ({@link MediaDrm#KEY_TYPE_RELEASE}), which are identified by a keySetId.
- *
- * @param optionalParameters are included in the key request message to
- * allow a client application to provide additional message parameters to the server.
- * This may be {@code null} if no additional parameters are to be sent.
- *
- * @throws NoDrmSchemeException if there is no active DRM session
- */
- @Override
- @NonNull
- public MediaDrm.KeyRequest getDrmKeyRequest(@Nullable byte[] keySetId, @Nullable byte[] initData,
- @Nullable String mimeType, @MediaDrm.KeyType int keyType,
- @Nullable Map<String, String> optionalParameters)
- throws NoDrmSchemeException
- {
- Log.v(TAG, "getDrmKeyRequest: " +
- " keySetId: " + keySetId + " initData:" + initData + " mimeType: " + mimeType +
- " keyType: " + keyType + " optionalParameters: " + optionalParameters);
-
- synchronized (mDrmLock) {
- if (!mActiveDrmScheme) {
- Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException");
- throw new NoDrmSchemeExceptionImpl(
- "getDrmKeyRequest: Has to set a DRM scheme first.");
- }
-
- try {
- byte[] scope = (keyType != MediaDrm.KEY_TYPE_RELEASE) ?
- mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE
- keySetId; // keySetId for KEY_TYPE_RELEASE
-
- HashMap<String, String> hmapOptionalParameters =
- (optionalParameters != null) ?
- new HashMap<String, String>(optionalParameters) :
- null;
-
- MediaDrm.KeyRequest request = mDrmObj.getKeyRequest(scope, initData, mimeType,
- keyType, hmapOptionalParameters);
- Log.v(TAG, "getDrmKeyRequest: --> request: " + request);
-
- return request;
-
- } catch (NotProvisionedException e) {
- Log.w(TAG, "getDrmKeyRequest NotProvisionedException: " +
- "Unexpected. Shouldn't have reached here.");
- throw new IllegalStateException("getDrmKeyRequest: Unexpected provisioning error.");
- } catch (Exception e) {
- Log.w(TAG, "getDrmKeyRequest Exception " + e);
- throw e;
- }
-
- } // synchronized
- }
-
-
- /**
- * A key response is received from the license server by the app, then it is
- * provided to the DRM engine plugin using provideDrmKeyResponse. When the
- * response is for an offline key request, a key-set identifier is returned that
- * can be used to later restore the keys to a new session with the method
- * {@link # restoreDrmKeys}.
- * When the response is for a streaming or release request, null is returned.
- *
- * @param keySetId When the response is for a release request, keySetId identifies
- * the saved key associated with the release request (i.e., the same keySetId
- * passed to the earlier {@ link #getDrmKeyRequest} call. It MUST be null when the
- * response is for either streaming or offline key requests.
- *
- * @param response the byte array response from the server
- *
- * @throws NoDrmSchemeException if there is no active DRM session
- * @throws DeniedByServerException if the response indicates that the
- * server rejected the request
- */
- @Override
- public byte[] provideDrmKeyResponse(@Nullable byte[] keySetId, @NonNull byte[] response)
- throws NoDrmSchemeException, DeniedByServerException
- {
- Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + " response: " + response);
-
- synchronized (mDrmLock) {
-
- if (!mActiveDrmScheme) {
- Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException");
- throw new NoDrmSchemeExceptionImpl(
- "getDrmKeyRequest: Has to set a DRM scheme first.");
- }
-
- try {
- byte[] scope = (keySetId == null) ?
- mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE
- keySetId; // keySetId for KEY_TYPE_RELEASE
-
- byte[] keySetResult = mDrmObj.provideKeyResponse(scope, response);
-
- Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + " response: " + response
- + " --> " + keySetResult);
-
-
- return keySetResult;
-
- } catch (NotProvisionedException e) {
- Log.w(TAG, "provideDrmKeyResponse NotProvisionedException: " +
- "Unexpected. Shouldn't have reached here.");
- throw new IllegalStateException("provideDrmKeyResponse: " +
- "Unexpected provisioning error.");
- } catch (Exception e) {
- Log.w(TAG, "provideDrmKeyResponse Exception " + e);
- throw e;
- }
- } // synchronized
- }
-
- @Override
- public void restoreDrmKeys(@NonNull byte[] keySetId)
- throws NoDrmSchemeException {
- Log.v(TAG, "restoreDrmKeys: keySetId: " + keySetId);
-
- synchronized (mDrmLock) {
- if (!mActiveDrmScheme) {
- Log.w(TAG, "restoreDrmKeys NoDrmSchemeException");
- throw new NoDrmSchemeExceptionImpl(
- "restoreDrmKeys: Has to set a DRM scheme first.");
- }
-
- try {
- mDrmObj.restoreKeys(mDrmSessionId, keySetId);
- } catch (Exception e) {
- Log.w(TAG, "restoreKeys Exception " + e);
- throw e;
- }
- } // synchronized
- }
-
- /**
- * Read a DRM engine plugin String property value, given the property name string.
- * <p>
- * @param propertyName the property name
- *
- * Standard fields names are:
- * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
- * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
- */
- @Override
- @NonNull
- public String getDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName)
- throws NoDrmSchemeException
- {
- Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName);
-
- String value;
- synchronized (mDrmLock) {
-
- if (!mActiveDrmScheme && !mDrmConfigAllowed) {
- Log.w(TAG, "getDrmPropertyString NoDrmSchemeException");
- throw new NoDrmSchemeExceptionImpl(
- "getDrmPropertyString: Has to prepareDrm() first.");
- }
-
- try {
- value = mDrmObj.getPropertyString(propertyName);
- } catch (Exception e) {
- Log.w(TAG, "getDrmPropertyString Exception " + e);
- throw e;
- }
- } // synchronized
-
- Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName + " --> value: " + value);
-
- return value;
- }
-
-
- /**
- * Set a DRM engine plugin String property value.
- * <p>
- * @param propertyName the property name
- * @param value the property value
- *
- * Standard fields names are:
- * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
- * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
- */
- @Override
- public void setDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName,
- @NonNull String value)
- throws NoDrmSchemeException
- {
- Log.v(TAG, "setDrmPropertyString: propertyName: " + propertyName + " value: " + value);
-
- synchronized (mDrmLock) {
-
- if ( !mActiveDrmScheme && !mDrmConfigAllowed ) {
- Log.w(TAG, "setDrmPropertyString NoDrmSchemeException");
- throw new NoDrmSchemeExceptionImpl(
- "setDrmPropertyString: Has to prepareDrm() first.");
- }
-
- try {
- mDrmObj.setPropertyString(propertyName, value);
- } catch ( Exception e ) {
- Log.w(TAG, "setDrmPropertyString Exception " + e);
- throw e;
- }
- } // synchronized
- }
-
- /**
- * Encapsulates the DRM properties of the source.
- */
- public static final class DrmInfoImpl extends DrmInfo {
- private Map<UUID, byte[]> mapPssh;
- private UUID[] supportedSchemes;
-
- /**
- * Returns the PSSH info of the data source for each supported DRM scheme.
- */
- @Override
- public Map<UUID, byte[]> getPssh() {
- return mapPssh;
- }
-
- /**
- * Returns the intersection of the data source and the device DRM schemes.
- * It effectively identifies the subset of the source's DRM schemes which
- * are supported by the device too.
- */
- @Override
- public List<UUID> getSupportedSchemes() {
- return Arrays.asList(supportedSchemes);
- }
-
- private DrmInfoImpl(Map<UUID, byte[]> Pssh, UUID[] SupportedSchemes) {
- mapPssh = Pssh;
- supportedSchemes = SupportedSchemes;
- }
-
- private DrmInfoImpl(PlayerMessage msg) {
- Log.v(TAG, "DrmInfoImpl(" + msg + ")");
-
- Iterator<Value> in = msg.getValuesList().iterator();
- byte[] pssh = in.next().getBytesValue().toByteArray();
-
- Log.v(TAG, "DrmInfoImpl() PSSH: " + arrToHex(pssh));
- mapPssh = parsePSSH(pssh, pssh.length);
- Log.v(TAG, "DrmInfoImpl() PSSH: " + mapPssh);
-
- int supportedDRMsCount = in.next().getInt32Value();
- supportedSchemes = new UUID[supportedDRMsCount];
- for (int i = 0; i < supportedDRMsCount; i++) {
- byte[] uuid = new byte[16];
- in.next().getBytesValue().copyTo(uuid, 0);
-
- supportedSchemes[i] = bytesToUUID(uuid);
-
- Log.v(TAG, "DrmInfoImpl() supportedScheme[" + i + "]: " +
- supportedSchemes[i]);
- }
-
- Log.v(TAG, "DrmInfoImpl() psshsize: " + pssh.length +
- " supportedDRMsCount: " + supportedDRMsCount);
- }
-
- private DrmInfoImpl makeCopy() {
- return new DrmInfoImpl(this.mapPssh, this.supportedSchemes);
- }
-
- private String arrToHex(byte[] bytes) {
- String out = "0x";
- for (int i = 0; i < bytes.length; i++) {
- out += String.format("%02x", bytes[i]);
- }
-
- return out;
- }
-
- private UUID bytesToUUID(byte[] uuid) {
- long msb = 0, lsb = 0;
- for (int i = 0; i < 8; i++) {
- msb |= ( ((long)uuid[i] & 0xff) << (8 * (7 - i)) );
- lsb |= ( ((long)uuid[i+8] & 0xff) << (8 * (7 - i)) );
- }
-
- return new UUID(msb, lsb);
- }
-
- private Map<UUID, byte[]> parsePSSH(byte[] pssh, int psshsize) {
- Map<UUID, byte[]> result = new HashMap<UUID, byte[]>();
-
- final int UUID_SIZE = 16;
- final int DATALEN_SIZE = 4;
-
- int len = psshsize;
- int numentries = 0;
- int i = 0;
-
- while (len > 0) {
- if (len < UUID_SIZE) {
- Log.w(TAG, String.format("parsePSSH: len is too short to parse " +
- "UUID: (%d < 16) pssh: %d", len, psshsize));
- return null;
- }
-
- byte[] subset = Arrays.copyOfRange(pssh, i, i + UUID_SIZE);
- UUID uuid = bytesToUUID(subset);
- i += UUID_SIZE;
- len -= UUID_SIZE;
-
- // get data length
- if (len < 4) {
- Log.w(TAG, String.format("parsePSSH: len is too short to parse " +
- "datalen: (%d < 4) pssh: %d", len, psshsize));
- return null;
- }
-
- subset = Arrays.copyOfRange(pssh, i, i+DATALEN_SIZE);
- int datalen = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) ?
- ((subset[3] & 0xff) << 24) | ((subset[2] & 0xff) << 16) |
- ((subset[1] & 0xff) << 8) | (subset[0] & 0xff) :
- ((subset[0] & 0xff) << 24) | ((subset[1] & 0xff) << 16) |
- ((subset[2] & 0xff) << 8) | (subset[3] & 0xff) ;
- i += DATALEN_SIZE;
- len -= DATALEN_SIZE;
-
- if (len < datalen) {
- Log.w(TAG, String.format("parsePSSH: len is too short to parse " +
- "data: (%d < %d) pssh: %d", len, datalen, psshsize));
- return null;
- }
-
- byte[] data = Arrays.copyOfRange(pssh, i, i+datalen);
-
- // skip the data
- i += datalen;
- len -= datalen;
-
- Log.v(TAG, String.format("parsePSSH[%d]: <%s, %s> pssh: %d",
- numentries, uuid, arrToHex(data), psshsize));
- numentries++;
- result.put(uuid, data);
- }
-
- return result;
- }
-
- }; // DrmInfoImpl
-
- /**
- * Thrown when a DRM method is called before preparing a DRM scheme through prepareDrm().
- * Extends MediaDrm.MediaDrmException
- */
- public static final class NoDrmSchemeExceptionImpl extends NoDrmSchemeException {
- public NoDrmSchemeExceptionImpl(String detailMessage) {
- super(detailMessage);
- }
- }
-
- private native void _prepareDrm(@NonNull byte[] uuid, @NonNull byte[] drmSessionId);
-
- // Modular DRM helpers
-
- private void prepareDrm_createDrmStep(@NonNull UUID uuid)
- throws UnsupportedSchemeException {
- Log.v(TAG, "prepareDrm_createDrmStep: UUID: " + uuid);
-
- try {
- mDrmObj = new MediaDrm(uuid);
- Log.v(TAG, "prepareDrm_createDrmStep: Created mDrmObj=" + mDrmObj);
- } catch (Exception e) { // UnsupportedSchemeException
- Log.e(TAG, "prepareDrm_createDrmStep: MediaDrm failed with " + e);
- throw e;
- }
- }
-
- private void prepareDrm_openSessionStep(@NonNull UUID uuid)
- throws NotProvisionedException, ResourceBusyException {
- Log.v(TAG, "prepareDrm_openSessionStep: uuid: " + uuid);
-
- // TODO: don't need an open session for a future specialKeyReleaseDrm mode but we should do
- // it anyway so it raises provisioning error if needed. We'd rather handle provisioning
- // at prepareDrm/openSession rather than getDrmKeyRequest/provideDrmKeyResponse
- try {
- mDrmSessionId = mDrmObj.openSession();
- Log.v(TAG, "prepareDrm_openSessionStep: mDrmSessionId=" + mDrmSessionId);
-
- // Sending it down to native/mediaserver to create the crypto object
- // This call could simply fail due to bad player state, e.g., after play().
- _prepareDrm(getByteArrayFromUUID(uuid), mDrmSessionId);
- Log.v(TAG, "prepareDrm_openSessionStep: _prepareDrm/Crypto succeeded");
-
- } catch (Exception e) { //ResourceBusyException, NotProvisionedException
- Log.e(TAG, "prepareDrm_openSessionStep: open/crypto failed with " + e);
- throw e;
- }
-
- }
-
- // Instantiated from the native side
- @SuppressWarnings("unused")
- private static class StreamEventCallback extends AudioTrack.StreamEventCallback {
- public long mJAudioTrackPtr;
- public long mNativeCallbackPtr;
- public long mUserDataPtr;
-
- public StreamEventCallback(long jAudioTrackPtr, long nativeCallbackPtr, long userDataPtr) {
- super();
- mJAudioTrackPtr = jAudioTrackPtr;
- mNativeCallbackPtr = nativeCallbackPtr;
- mUserDataPtr = userDataPtr;
- }
-
- @Override
- public void onTearDown(AudioTrack track) {
- native_stream_event_onTearDown(mNativeCallbackPtr, mUserDataPtr);
- }
-
- @Override
- public void onPresentationEnded(AudioTrack track) {
- native_stream_event_onStreamPresentationEnd(mNativeCallbackPtr, mUserDataPtr);
- }
-
- @Override
- public void onDataRequest(AudioTrack track, int size) {
- native_stream_event_onStreamDataRequest(
- mJAudioTrackPtr, mNativeCallbackPtr, mUserDataPtr);
- }
- }
-
- private class ProvisioningThread extends Thread {
- public static final int TIMEOUT_MS = 60000;
-
- private UUID uuid;
- private String urlStr;
- private Object drmLock;
- private MediaPlayer2Impl mediaPlayer;
- private int status;
- public int status() {
- return status;
- }
-
- public ProvisioningThread initialize(MediaDrm.ProvisionRequest request,
- UUID uuid, MediaPlayer2Impl mediaPlayer) {
- // lock is held by the caller
- drmLock = mediaPlayer.mDrmLock;
- this.mediaPlayer = mediaPlayer;
-
- urlStr = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData());
- this.uuid = uuid;
-
- status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
-
- Log.v(TAG, "HandleProvisioninig: Thread is initialised url: " + urlStr);
- return this;
- }
-
- public void run() {
-
- byte[] response = null;
- boolean provisioningSucceeded = false;
- try {
- URL url = new URL(urlStr);
- final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
- try {
- connection.setRequestMethod("POST");
- connection.setDoOutput(false);
- connection.setDoInput(true);
- connection.setConnectTimeout(TIMEOUT_MS);
- connection.setReadTimeout(TIMEOUT_MS);
-
- connection.connect();
- response = readInputStreamFully(connection.getInputStream());
-
- Log.v(TAG, "HandleProvisioninig: Thread run: response " +
- response.length + " " + response);
- } catch (Exception e) {
- status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
- Log.w(TAG, "HandleProvisioninig: Thread run: connect " + e + " url: " + url);
- } finally {
- connection.disconnect();
- }
- } catch (Exception e) {
- status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
- Log.w(TAG, "HandleProvisioninig: Thread run: openConnection " + e);
- }
-
- if (response != null) {
- try {
- mDrmObj.provideProvisionResponse(response);
- Log.v(TAG, "HandleProvisioninig: Thread run: " +
- "provideProvisionResponse SUCCEEDED!");
-
- provisioningSucceeded = true;
- } catch (Exception e) {
- status = PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR;
- Log.w(TAG, "HandleProvisioninig: Thread run: " +
- "provideProvisionResponse " + e);
- }
- }
-
- boolean succeeded = false;
-
- synchronized (drmLock) {
- // continuing with prepareDrm
- if (provisioningSucceeded) {
- succeeded = mediaPlayer.resumePrepareDrm(uuid);
- status = (succeeded) ?
- PREPARE_DRM_STATUS_SUCCESS :
- PREPARE_DRM_STATUS_PREPARATION_ERROR;
- }
- mediaPlayer.mDrmProvisioningInProgress = false;
- mediaPlayer.mPrepareDrmInProgress = false;
- if (!succeeded) {
- cleanDrmObj(); // cleaning up if it hasn't gone through while in the lock
- }
- } // synchronized
-
- // calling the callback outside the lock
- sendDrmEvent(new DrmEventNotifier() {
- @Override
- public void notify(DrmEventCallback callback) {
- callback.onDrmPrepared(
- mediaPlayer, getCurrentDataSource(), status);
- }
- });
-
- synchronized (mTaskLock) {
- if (mCurrentTask != null
- && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE_DRM
- && mCurrentTask.mNeedToWaitForEventToComplete) {
- mCurrentTask = null;
- processPendingTask_l();
- }
- }
- }
-
- /**
- * Returns a byte[] containing the remainder of 'in', closing it when done.
- */
- private byte[] readInputStreamFully(InputStream in) throws IOException {
- try {
- return readInputStreamFullyNoClose(in);
- } finally {
- in.close();
- }
- }
-
- /**
- * Returns a byte[] containing the remainder of 'in'.
- */
- private byte[] readInputStreamFullyNoClose(InputStream in) throws IOException {
- ByteArrayOutputStream bytes = new ByteArrayOutputStream();
- byte[] buffer = new byte[1024];
- int count;
- while ((count = in.read(buffer)) != -1) {
- bytes.write(buffer, 0, count);
- }
- return bytes.toByteArray();
- }
- } // ProvisioningThread
-
- private int HandleProvisioninig(UUID uuid) {
- synchronized (mDrmLock) {
- if (mDrmProvisioningInProgress) {
- Log.e(TAG, "HandleProvisioninig: Unexpected mDrmProvisioningInProgress");
- return PREPARE_DRM_STATUS_PREPARATION_ERROR;
- }
-
- MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest();
- if (provReq == null) {
- Log.e(TAG, "HandleProvisioninig: getProvisionRequest returned null.");
- return PREPARE_DRM_STATUS_PREPARATION_ERROR;
- }
-
- Log.v(TAG, "HandleProvisioninig provReq " +
- " data: " + provReq.getData() + " url: " + provReq.getDefaultUrl());
-
- // networking in a background thread
- mDrmProvisioningInProgress = true;
-
- mDrmProvisioningThread = new ProvisioningThread().initialize(provReq, uuid, this);
- mDrmProvisioningThread.start();
-
- return PREPARE_DRM_STATUS_SUCCESS;
- }
- }
-
- private boolean resumePrepareDrm(UUID uuid) {
- Log.v(TAG, "resumePrepareDrm: uuid: " + uuid);
-
- // mDrmLock is guaranteed to be held
- boolean success = false;
- try {
- // resuming
- prepareDrm_openSessionStep(uuid);
-
- mDrmUUID = uuid;
- mActiveDrmScheme = true;
-
- success = true;
- } catch (Exception e) {
- Log.w(TAG, "HandleProvisioninig: Thread run _prepareDrm resume failed with " + e);
- // mDrmObj clean up is done by the caller
- }
-
- return success;
- }
-
- private void resetDrmState() {
- synchronized (mDrmLock) {
- Log.v(TAG, "resetDrmState: " +
- " mDrmInfoImpl=" + mDrmInfoImpl +
- " mDrmProvisioningThread=" + mDrmProvisioningThread +
- " mPrepareDrmInProgress=" + mPrepareDrmInProgress +
- " mActiveDrmScheme=" + mActiveDrmScheme);
-
- mDrmInfoResolved = false;
- mDrmInfoImpl = null;
-
- if (mDrmProvisioningThread != null) {
- // timeout; relying on HttpUrlConnection
- try {
- mDrmProvisioningThread.join();
- }
- catch (InterruptedException e) {
- Log.w(TAG, "resetDrmState: ProvThread.join Exception " + e);
- }
- mDrmProvisioningThread = null;
- }
-
- mPrepareDrmInProgress = false;
- mActiveDrmScheme = false;
-
- cleanDrmObj();
- } // synchronized
- }
-
- private void cleanDrmObj() {
- // the caller holds mDrmLock
- Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId);
-
- if (mDrmSessionId != null) {
- mDrmObj.closeSession(mDrmSessionId);
- mDrmSessionId = null;
- }
- if (mDrmObj != null) {
- mDrmObj.release();
- mDrmObj = null;
- }
- }
-
- private static final byte[] getByteArrayFromUUID(@NonNull UUID uuid) {
- long msb = uuid.getMostSignificantBits();
- long lsb = uuid.getLeastSignificantBits();
-
- byte[] uuidBytes = new byte[16];
- for (int i = 0; i < 8; ++i) {
- uuidBytes[i] = (byte)(msb >>> (8 * (7 - i)));
- uuidBytes[8 + i] = (byte)(lsb >>> (8 * (7 - i)));
- }
-
- return uuidBytes;
- }
-
- // Modular DRM end
-
-
- private static class TimedTextUtil {
- // These keys must be in sync with the keys in TextDescription2.h
- private static final int KEY_START_TIME = 7; // int
- private static final int KEY_STRUCT_TEXT_POS = 14; // TextPos
- private static final int KEY_STRUCT_TEXT = 16; // Text
- private static final int KEY_GLOBAL_SETTING = 101;
- private static final int KEY_LOCAL_SETTING = 102;
-
- private static TimedText parsePlayerMessage(PlayerMessage playerMsg) {
- if (playerMsg.getValuesCount() == 0) {
- return null;
- }
-
- String textChars = null;
- Rect textBounds = null;
- Iterator<Value> in = playerMsg.getValuesList().iterator();
- int type = in.next().getInt32Value();
- if (type == KEY_LOCAL_SETTING) {
- type = in.next().getInt32Value();
- if (type != KEY_START_TIME) {
- return null;
- }
- int startTimeMs = in.next().getInt32Value();
-
- type = in.next().getInt32Value();
- if (type != KEY_STRUCT_TEXT) {
- return null;
- }
-
- byte[] text = in.next().getBytesValue().toByteArray();
- if (text == null || text.length == 0) {
- textChars = null;
- } else {
- textChars = new String(text);
- }
-
- } else if (type != KEY_GLOBAL_SETTING) {
- Log.w(TAG, "Invalid timed text key found: " + type);
- return null;
- }
- if (in.hasNext()) {
- type = in.next().getInt32Value();
- if (type == KEY_STRUCT_TEXT_POS) {
- int top = in.next().getInt32Value();
- int left = in.next().getInt32Value();
- int bottom = in.next().getInt32Value();
- int right = in.next().getInt32Value();
- textBounds = new Rect(left, top, right, bottom);
- }
- }
- return new TimedText(textChars, textBounds);
- }
- }
-
- private Object addTask(Task task) {
- synchronized (mTaskLock) {
- mPendingTasks.add(task);
- processPendingTask_l();
- }
- return task;
- }
-
- @GuardedBy("mTaskLock")
- private void processPendingTask_l() {
- if (mCurrentTask != null) {
- return;
- }
- if (!mPendingTasks.isEmpty()) {
- Task task = mPendingTasks.remove(0);
- mCurrentTask = task;
- mTaskHandler.post(task);
- }
- }
-
- private abstract class Task implements Runnable {
- private final int mMediaCallType;
- private final boolean mNeedToWaitForEventToComplete;
- private DataSourceDesc mDSD;
-
- public Task (int mediaCallType, boolean needToWaitForEventToComplete) {
- mMediaCallType = mediaCallType;
- mNeedToWaitForEventToComplete = needToWaitForEventToComplete;
- }
-
- abstract void process() throws IOException, NoDrmSchemeException;
-
- @Override
- public void run() {
- int status = CALL_STATUS_NO_ERROR;
- try {
- if (mMediaCallType != CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED
- && getState() == PLAYER_STATE_ERROR) {
- status = CALL_STATUS_INVALID_OPERATION;
- } else {
- if (mMediaCallType == CALL_COMPLETED_SEEK_TO) {
- synchronized (mTaskLock) {
- if (!mPendingTasks.isEmpty()) {
- Task nextTask = mPendingTasks.get(0);
- if (nextTask.mMediaCallType == mMediaCallType) {
- throw new CommandSkippedException(
- "consecutive seekTo is skipped except last one");
- }
- }
- }
- }
- process();
- }
- } catch (IllegalStateException e) {
- status = CALL_STATUS_INVALID_OPERATION;
- } catch (IllegalArgumentException e) {
- status = CALL_STATUS_BAD_VALUE;
- } catch (SecurityException e) {
- status = CALL_STATUS_PERMISSION_DENIED;
- } catch (IOException e) {
- status = CALL_STATUS_ERROR_IO;
- } catch (NoDrmSchemeException e) {
- status = CALL_STATUS_NO_DRM_SCHEME;
- } catch (CommandSkippedException e) {
- status = CALL_STATUS_SKIPPED;
- } catch (Exception e) {
- status = CALL_STATUS_ERROR_UNKNOWN;
- }
- mDSD = getCurrentDataSource();
-
- if (mMediaCallType != CALL_COMPLETED_SEEK_TO) {
- synchronized (mTaskLock) {
- mIsPreviousCommandSeekTo = false;
- }
- }
-
- // TODO: Make native implementations asynchronous and let them send notifications.
- if (!mNeedToWaitForEventToComplete || status != CALL_STATUS_NO_ERROR) {
-
- sendCompleteNotification(status);
-
- synchronized (mTaskLock) {
- mCurrentTask = null;
- processPendingTask_l();
- }
- }
- }
-
- private void sendCompleteNotification(int status) {
- // In {@link #notifyWhenCommandLabelReached} case, a separate callback
- // {@link #onCommandLabelReached} is already called in {@code process()}.
- // CALL_COMPLETED_PREPARE_DRM is sent via DrmEventCallback#onDrmPrepared
- if (mMediaCallType == CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED
- || mMediaCallType == CALL_COMPLETED_PREPARE_DRM) {
- return;
- }
- sendEvent(new EventNotifier() {
- @Override
- public void notify(EventCallback callback) {
- callback.onCallCompleted(
- MediaPlayer2Impl.this, mDSD, mMediaCallType, status);
- }
- });
- }
- };
-
- private final class CommandSkippedException extends RuntimeException {
- public CommandSkippedException(String detailMessage) {
- super(detailMessage);
- }
- };
-
- private final class SourceInfo {
- final DataSourceDesc mDSD;
- final long mId = mSrcIdGenerator.getAndIncrement();
- AtomicInteger mBufferedPercentage = new AtomicInteger(0);
-
- // m*AsNextSource (below) only applies to pending data sources in the playlist;
- // the meanings of mCurrentSourceInfo.{mStateAsNextSource,mPlayPendingAsNextSource}
- // are undefined.
- int mStateAsNextSource = NEXT_SOURCE_STATE_INIT;
- boolean mPlayPendingAsNextSource = false;
-
- SourceInfo(DataSourceDesc dsd) {
- this.mDSD = dsd;
- }
-
- @Override
- public String toString() {
- return String.format("%s(%d)", SourceInfo.class.getName(), mId);
- }
-
- }
-
- private SourceInfo getSourceInfoById(long srcId) {
- synchronized (mSrcLock) {
- if (isCurrentSource(srcId)) {
- return mCurrentSourceInfo;
- }
- if (isNextSource(srcId)) {
- return mNextSourceInfos.peek();
- }
- }
- return null;
- }
-
- private boolean isCurrentSource(long srcId) {
- synchronized (mSrcLock) {
- return mCurrentSourceInfo != null && mCurrentSourceInfo.mId == srcId;
- }
- }
-
- private boolean isNextSource(long srcId) {
- SourceInfo nextSourceInfo = mNextSourceInfos.peek();
- return nextSourceInfo != null && nextSourceInfo.mId == srcId;
- }
-
-}
diff --git a/media/java/android/media/MediaPlayer2Utils.java b/media/java/android/media/MediaPlayer2Utils.java
new file mode 100644
index 0000000..c6dee22
--- /dev/null
+++ b/media/java/android/media/MediaPlayer2Utils.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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;
+
+/**
+ * Helper class used by native code to reduce JNI calls from native side.
+ * @hide
+ */
+public class MediaPlayer2Utils {
+ /**
+ * Returns whether audio offloading is supported for the given audio format.
+ *
+ * @param encoding the type of encoding defined in {@link AudioFormat}
+ * @param sampleRate the sampling rate of the stream
+ * @param channelMask the channel mask defined in {@link AudioFormat}
+ */
+ // @CalledByNative
+ public static boolean isOffloadedAudioPlaybackSupported(
+ int encoding, int sampleRate, int channelMask) {
+ final AudioFormat format = new AudioFormat.Builder()
+ .setEncoding(encoding)
+ .setSampleRate(sampleRate)
+ .setChannelMask(channelMask)
+ .build();
+ return AudioManager.isOffloadedPlaybackSupported(format);
+ }
+}
diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java
index 2b3c2b4..9e97125 100644
--- a/media/java/android/media/MediaSession2.java
+++ b/media/java/android/media/MediaSession2.java
@@ -59,10 +59,6 @@
* handle media keys. In general an app only needs one session for all playback, though multiple
* sessions can be created to provide finer grain controls of media.
* <p>
- * If you want to support background playback, {@link MediaSessionService2} is preferred
- * instead. With it, your playback can be revived even after playback is finished. See
- * {@link MediaSessionService2} for details.
- * <p>
* A session can be obtained by {@link Builder}. The owner of the session may pass its session token
* to other processes to allow them to create a {@link MediaController2} to interact with the
* session.
@@ -75,8 +71,6 @@
* and notify any controllers.
* <p>
* {@link MediaSession2} objects should be used on the thread on the looper.
- *
- * @see MediaSessionService2
*/
public class MediaSession2 implements AutoCloseable {
private final MediaSession2Provider mProvider;
diff --git a/media/java/android/media/MediaSessionService2.java b/media/java/android/media/MediaSessionService2.java
deleted file mode 100644
index 6c3a4bf..0000000
--- a/media/java/android/media/MediaSessionService2.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * 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.
- */
-
-package android.media;
-
-import android.annotation.CallSuper;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Notification;
-import android.app.Service;
-import android.content.Intent;
-import android.media.MediaSession2.ControllerInfo;
-import android.media.update.ApiLoader;
-import android.media.update.MediaSessionService2Provider;
-import android.media.update.MediaSessionService2Provider.MediaNotificationProvider;
-import android.os.IBinder;
-
-/**
- * @hide
- * Base class for media session services, which is the service version of the {@link MediaSession2}.
- * <p>
- * It's highly recommended for an app to use this instead of {@link MediaSession2} if it wants
- * to keep media playback in the background.
- * <p>
- * Here's the benefits of using {@link MediaSessionService2} instead of
- * {@link MediaSession2}.
- * <ul>
- * <li>Another app can know that your app supports {@link MediaSession2} even when your app
- * isn't running.
- * <li>Another app can start playback of your app even when your app isn't running.
- * </ul>
- * For example, user's voice command can start playback of your app even when it's not running.
- * <p>
- * To extend this class, adding followings directly to your {@code AndroidManifest.xml}.
- * <pre>
- * <service android:name="component_name_of_your_implementation" >
- * <intent-filter>
- * <action android:name="android.media.MediaSessionService2" />
- * </intent-filter>
- * </service></pre>
- * <p>
- * A {@link MediaSessionService2} is another form of {@link MediaSession2}. IDs shouldn't
- * be shared between the {@link MediaSessionService2} and {@link MediaSession2}. By
- * default, an empty string will be used for ID of the service. If you want to specify an ID,
- * declare metadata in the manifest as follows.
- * <pre>
- * <service android:name="component_name_of_your_implementation" >
- * <intent-filter>
- * <action android:name="android.media.MediaSessionService2" />
- * </intent-filter>
- * <meta-data android:name="android.media.session"
- * android:value="session_id"/>
- * </service></pre>
- * <p>
- * It's recommended for an app to have a single {@link MediaSessionService2} declared in the
- * manifest. Otherwise, your app might be shown twice in the list of the Auto/Wearable, or another
- * app fails to pick the right session service when it wants to start the playback this app.
- * <p>
- * If there's conflicts with the session ID among the services, services wouldn't be available for
- * any controllers.
- * <p>
- * Topic covered here:
- * <ol>
- * <li><a href="#ServiceLifecycle">Service Lifecycle</a>
- * <li><a href="#Permissions">Permissions</a>
- * </ol>
- * <div class="special reference">
- * <a name="ServiceLifecycle"></a>
- * <h3>Service Lifecycle</h3>
- * <p>
- * Session service is bounded service. When a {@link MediaController2} is created for the
- * session service, the controller binds to the session service. {@link #onCreateSession(String)}
- * may be called after the {@link #onCreate} if the service hasn't created yet.
- * <p>
- * After the binding, session's {@link MediaSession2.SessionCallback#onConnect(MediaSession2, ControllerInfo)}
- *
- * will be called to accept or reject connection request from a controller. If the connection is
- * rejected, the controller will unbind. If it's accepted, the controller will be available to use
- * and keep binding.
- * <p>
- * When playback is started for this session service, {@link #onUpdateNotification()}
- * is called and service would become a foreground service. It's needed to keep playback after the
- * controller is destroyed. The session service becomes background service when the playback is
- * stopped.
- * <a name="Permissions"></a>
- * <h3>Permissions</h3>
- * <p>
- * Any app can bind to the session service with controller, but the controller can be used only if
- * the session service accepted the connection request through
- * {@link MediaSession2.SessionCallback#onConnect(MediaSession2, ControllerInfo)}.
- */
-public abstract class MediaSessionService2 extends Service {
- private final MediaSessionService2Provider mProvider;
-
- /**
- * This is the interface name that a service implementing a session service should say that it
- * support -- that is, this is the action it uses for its intent filter.
- */
- public static final String SERVICE_INTERFACE = "android.media.MediaSessionService2";
-
- /**
- * Name under which a MediaSessionService2 component publishes information about itself.
- * This meta-data must provide a string value for the ID.
- */
- public static final String SERVICE_META_DATA = "android.media.session";
-
- public MediaSessionService2() {
- super();
- mProvider = createProvider();
- }
-
- MediaSessionService2Provider createProvider() {
- return ApiLoader.getProvider().createMediaSessionService2(this);
- }
-
- /**
- * Default implementation for {@link MediaSessionService2} to initialize session service.
- * <p>
- * Override this method if you need your own initialization. Derived classes MUST call through
- * to the super class's implementation of this method.
- */
- @CallSuper
- @Override
- public void onCreate() {
- super.onCreate();
- mProvider.onCreate_impl();
- }
-
- /**
- * Called when another app requested to start this service to get {@link MediaSession2}.
- * <p>
- * Session service will accept or reject the connection with the
- * {@link MediaSession2.SessionCallback} in the created session.
- * <p>
- * Service wouldn't run if {@code null} is returned or session's ID doesn't match with the
- * expected ID that you've specified through the AndroidManifest.xml.
- * <p>
- * This method will be called on the main thread.
- *
- * @param sessionId session id written in the AndroidManifest.xml.
- * @return a new session
- * @see MediaSession2.Builder
- * @see #getSession()
- */
- public @NonNull abstract MediaSession2 onCreateSession(String sessionId);
-
- /**
- * Called when the playback state of this session is changed so notification needs update.
- * Override this method to show or cancel your own notification UI.
- * <p>
- * With the notification returned here, the service become foreground service when the playback
- * is started. It becomes background service after the playback is stopped.
- *
- * @return a {@link MediaNotification}. If it's {@code null}, notification wouldn't be shown.
- */
- public @Nullable MediaNotification onUpdateNotification() {
- return mProvider.onUpdateNotification_impl();
- }
-
- /**
- * Get instance of the {@link MediaSession2} that you've previously created with the
- * {@link #onCreateSession} for this service.
- * <p>
- * This may be {@code null} before the {@link #onCreate()} is finished.
- *
- * @return created session
- */
- public final @Nullable MediaSession2 getSession() {
- return mProvider.getSession_impl();
- }
-
- /**
- * Default implementation for {@link MediaSessionService2} to handle incoming binding
- * request. If the request is for getting the session, the intent will have action
- * {@link #SERVICE_INTERFACE}.
- * <p>
- * Override this method if this service also needs to handle binder requests other than
- * {@link #SERVICE_INTERFACE}. Derived classes MUST call through to the super class's
- * implementation of this method.
- *
- * @param intent
- * @return Binder
- */
- @CallSuper
- @Nullable
- @Override
- public IBinder onBind(Intent intent) {
- return mProvider.onBind_impl(intent);
- }
-
- /**
- * Returned by {@link #onUpdateNotification()} for making session service foreground service
- * to keep playback running in the background. It's highly recommended to show media style
- * notification here.
- */
- public static class MediaNotification {
- private final MediaNotificationProvider mProvider;
-
- /**
- * Default constructor
- *
- * @param notificationId notification id to be used for
- * {@link android.app.NotificationManager#notify(int, Notification)}.
- * @param notification a notification to make session service foreground service. Media
- * style notification is recommended here.
- */
- public MediaNotification(int notificationId, @NonNull Notification notification) {
- mProvider = ApiLoader.getProvider().createMediaSessionService2MediaNotification(
- this, notificationId, notification);
- }
-
- public int getNotificationId() {
- return mProvider.getNotificationId_impl();
- }
-
- public @NonNull Notification getNotification() {
- return mProvider.getNotification_impl();
- }
- }
-}
diff --git a/media/java/android/media/SessionToken2.java b/media/java/android/media/SessionToken2.java
index bf2d445..f7d54f2 100644
--- a/media/java/android/media/SessionToken2.java
+++ b/media/java/android/media/SessionToken2.java
@@ -29,7 +29,7 @@
/**
* @hide
- * Represents an ongoing {@link MediaSession2} or a {@link MediaSessionService2}.
+ * Represents an ongoing {@link MediaSession2}.
* If it's representing a session service, it may not be ongoing.
* <p>
* This may be passed to apps by the session owner to allow them to create a
diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java
index 9746842..7480fa0 100644
--- a/media/java/android/media/audiofx/AudioEffect.java
+++ b/media/java/android/media/audiofx/AudioEffect.java
@@ -989,7 +989,7 @@
// --------------------
/**
* The OnEnableStatusChangeListener interface defines a method called by the AudioEffect
- * when a the enabled state of the effect engine was changed by the controlling application.
+ * when the enabled state of the effect engine was changed by the controlling application.
*/
public interface OnEnableStatusChangeListener {
/**
@@ -1003,7 +1003,7 @@
/**
* The OnControlStatusChangeListener interface defines a method called by the AudioEffect
- * when a the control of the effect engine is gained or lost by the application
+ * when control of the effect engine is gained or lost by the application
*/
public interface OnControlStatusChangeListener {
/**
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 900e3bb..b5e2213 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -90,6 +90,8 @@
* int, android.content.Intent)}
* @param resultData The resulting data from {@link android.app.Activity#onActivityResult(int,
* int, android.content.Intent)}
+ * @throws IllegalStateException on pre-Q devices if a previously gotten MediaProjection
+ * from the same {@code resultData} has not yet been stopped
*/
public MediaProjection getMediaProjection(int resultCode, @NonNull Intent resultData) {
if (resultCode != Activity.RESULT_OK || resultData == null) {
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 5ade4dd..98f3fb2 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -16,7 +16,6 @@
package android.media.session;
-import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -29,7 +28,6 @@
import android.media.IRemoteVolumeController;
import android.media.ISessionTokensListener;
import android.media.MediaSession2;
-import android.media.MediaSessionService2;
import android.media.SessionToken2;
import android.media.browse.MediaBrowser;
import android.os.Bundle;
@@ -41,7 +39,6 @@
import android.os.UserHandle;
import android.service.media.MediaBrowserService;
import android.service.notification.NotificationListenerService;
-import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.view.KeyEvent;
@@ -449,8 +446,7 @@
/**
* @hide
* Get {@link List} of {@link SessionToken2} whose sessions are active now. This list represents
- * active sessions regardless of whether they're {@link MediaSession2} or
- * {@link MediaSessionService2}.
+ * active sessions regardless of whether they're {@link MediaSession2}.
* <p>
* This requires the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by the
* calling app. You may also retrieve this list if your app is an enabled notification listener
@@ -470,134 +466,6 @@
}
}
- /**
- * @hide
- * Get {@link List} of {@link SessionToken2} for {@link MediaSessionService2} regardless of their
- * activeness. This list represents media apps that support background playback.
- * <p>
- * This requires the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by the
- * calling app. You may also retrieve this list if your app is an enabled notification listener
- * using the {@link NotificationListenerService} APIs.
- *
- * @return list of tokens
- */
- public List<SessionToken2> getSessionServiceTokens() {
- try {
- List<Bundle> bundles = mService.getSessionTokens(
- /* activeSessionOnly */ false, /* sessionServiceOnly */ true,
- mContext.getOpPackageName());
- return toTokenList(bundles);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Cannot communicate with the service.", e);
- return Collections.emptyList();
- }
- }
-
- /**
- * @hide
- * Get all {@link SessionToken2}s. This is the combined list of {@link #getActiveSessionTokens()}
- * and {@link #getSessionServiceTokens}.
- * <p>
- * This requires the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by the
- * calling app. You may also retrieve this list if your app is an enabled notification listener
- * using the {@link NotificationListenerService} APIs.
- *
- * @return list of tokens
- * @see #getActiveSessionTokens
- * @see #getSessionServiceTokens
- */
- public List<SessionToken2> getAllSessionTokens() {
- try {
- List<Bundle> bundles = mService.getSessionTokens(
- /* activeSessionOnly */ false, /* sessionServiceOnly */ false,
- mContext.getOpPackageName());
- return toTokenList(bundles);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Cannot communicate with the service.", e);
- return Collections.emptyList();
- }
- }
-
- /**
- * @hide
- * Add a listener to be notified when the {@link #getAllSessionTokens()} changes.
- * <p>
- * This requires the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by the
- * calling app. You may also retrieve this list if your app is an enabled notification listener
- * using the {@link NotificationListenerService} APIs.
- *
- * @param executor executor to run this command
- * @param listener The listener to add.
- */
- public void addOnSessionTokensChangedListener(@NonNull @CallbackExecutor Executor executor,
- @NonNull OnSessionTokensChangedListener listener) {
- addOnSessionTokensChangedListener(UserHandle.myUserId(), executor, listener);
- }
-
- /**
- * Add a listener to be notified when the {@link #getAllSessionTokens()} changes.
- * <p>
- * This requires the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by the
- * calling app. You may also retrieve this list if your app is an enabled notification listener
- * using the {@link NotificationListenerService} APIs.
- *
- * @param userId The userId to listen for changes on.
- * @param executor executor to run this command
- * @param listener The listener to add.
- * @hide
- */
- public void addOnSessionTokensChangedListener(int userId,
- @NonNull @CallbackExecutor Executor executor,
- @NonNull OnSessionTokensChangedListener listener) {
- if (executor == null) {
- throw new IllegalArgumentException("executor may not be null");
- }
- if (listener == null) {
- throw new IllegalArgumentException("listener may not be null");
- }
- synchronized (mLock) {
- if (mSessionTokensListener.get(listener) != null) {
- Log.w(TAG, "Attempted to add session listener twice, ignoring.");
- return;
- }
- SessionTokensChangedWrapper wrapper = new SessionTokensChangedWrapper(
- mContext, executor, listener);
- try {
- mService.addSessionTokensListener(wrapper.mStub, userId,
- mContext.getOpPackageName());
- mSessionTokensListener.put(listener, wrapper);
- } catch (RemoteException e) {
- Log.e(TAG, "Error in addSessionTokensListener.", e);
- }
- }
- }
-
- /**
- * @hide
- * Stop receiving session token updates on the specified listener.
- *
- * @param listener The listener to remove.
- */
- public void removeOnSessionTokensChangedListener(
- @NonNull OnSessionTokensChangedListener listener) {
- if (listener == null) {
- throw new IllegalArgumentException("listener may not be null");
- }
- synchronized (mLock) {
- SessionTokensChangedWrapper wrapper = mSessionTokensListener.remove(listener);
- if (wrapper != null) {
- try {
- mService.removeSessionTokensListener(wrapper.mStub,
- mContext.getOpPackageName());
- } catch (RemoteException e) {
- Log.e(TAG, "Error in removeSessionTokensListener.", e);
- } finally {
- wrapper.release();
- }
- }
- }
- }
-
private static List<SessionToken2> toTokenList(List<Bundle> bundles) {
List<SessionToken2> tokens = new ArrayList<>();
if (bundles != null) {
diff --git a/media/java/android/media/update/MediaBrowser2Provider.java b/media/java/android/media/update/MediaBrowser2Provider.java
deleted file mode 100644
index a18701e..0000000
--- a/media/java/android/media/update/MediaBrowser2Provider.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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.
- */
-
-package android.media.update;
-
-import android.os.Bundle;
-
-/**
- * @hide
- */
-public interface MediaBrowser2Provider extends MediaController2Provider {
- void getLibraryRoot_impl(Bundle rootHints);
-
- void subscribe_impl(String parentId, Bundle extras);
- void unsubscribe_impl(String parentId);
-
- void getItem_impl(String mediaId);
- void getChildren_impl(String parentId, int page, int pageSize, Bundle extras);
- void search_impl(String query, Bundle extras);
- void getSearchResult_impl(String query, int page, int pageSize, Bundle extras);
-}
diff --git a/media/java/android/media/update/MediaControlView2Provider.java b/media/java/android/media/update/MediaControlView2Provider.java
deleted file mode 100644
index 8e69653..0000000
--- a/media/java/android/media/update/MediaControlView2Provider.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.update;
-
-import android.media.SessionToken2;
-import android.media.session.MediaController;
-import android.util.AttributeSet;
-import android.widget.MediaControlView2;
-
-/**
- * Interface for connecting the public API to an updatable implementation.
- *
- * Each instance object is connected to one corresponding updatable object which implements the
- * runtime behavior of that class. There should a corresponding provider method for all public
- * methods.
- *
- * All methods behave as per their namesake in the public API.
- *
- * @see android.widget.MediaControlView2
- *
- * @hide
- */
-// TODO: @SystemApi
-public interface MediaControlView2Provider extends ViewGroupProvider {
- void initialize(AttributeSet attrs, int defStyleAttr, int defStyleRes);
-
- void setMediaSessionToken_impl(SessionToken2 token);
- void setOnFullScreenListener_impl(MediaControlView2.OnFullScreenListener l);
- /**
- * @hide TODO: remove
- */
- void setController_impl(MediaController controller);
- /**
- * @hide
- */
- void setButtonVisibility_impl(int button, int visibility);
- void requestPlayButtonFocus_impl();
-}
diff --git a/media/java/android/media/update/MediaLibraryService2Provider.java b/media/java/android/media/update/MediaLibraryService2Provider.java
deleted file mode 100644
index 9a0d693..0000000
--- a/media/java/android/media/update/MediaLibraryService2Provider.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.
- */
-
-package android.media.update;
-
-import android.media.MediaSession2.ControllerInfo;
-import android.os.Bundle;
-
-/**
- * @hide
- */
-public interface MediaLibraryService2Provider extends MediaSessionService2Provider {
- // Nothing new for now
-
- interface MediaLibrarySessionProvider extends MediaSession2Provider {
- void notifyChildrenChanged_impl(ControllerInfo controller, String parentId,
- int itemCount, Bundle extras);
- void notifyChildrenChanged_impl(String parentId, int itemCount, Bundle extras);
- void notifySearchResultChanged_impl(ControllerInfo controller, String query, int itemCount,
- Bundle extras);
- }
-
- interface LibraryRootProvider {
- String getRootId_impl();
- Bundle getExtras_impl();
- }
-}
diff --git a/media/java/android/media/update/MediaSessionService2Provider.java b/media/java/android/media/update/MediaSessionService2Provider.java
deleted file mode 100644
index 5eb6254..0000000
--- a/media/java/android/media/update/MediaSessionService2Provider.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.
- */
-
-package android.media.update;
-
-import android.app.Notification;
-import android.content.Intent;
-import android.media.MediaSession2;
-import android.media.MediaSessionService2.MediaNotification;
-import android.os.IBinder;
-
-/**
- * @hide
- */
-public interface MediaSessionService2Provider {
- MediaSession2 getSession_impl();
- MediaNotification onUpdateNotification_impl();
-
- // Service
- void onCreate_impl();
- IBinder onBind_impl(Intent intent);
-
- interface MediaNotificationProvider {
- int getNotificationId_impl();
- Notification getNotification_impl();
- }
-}
diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java
index 8687b80..ccb8c02 100644
--- a/media/java/android/media/update/StaticProvider.java
+++ b/media/java/android/media/update/StaticProvider.java
@@ -16,41 +16,26 @@
package android.media.update;
-import android.annotation.Nullable;
-import android.app.Notification;
import android.content.Context;
-import android.media.MediaBrowser2;
-import android.media.MediaBrowser2.BrowserCallback;
import android.media.MediaController2;
import android.media.MediaController2.ControllerCallback;
import android.media.MediaItem2;
-import android.media.MediaLibraryService2;
-import android.media.MediaLibraryService2.LibraryRoot;
-import android.media.MediaLibraryService2.MediaLibrarySession;
-import android.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback;
import android.media.MediaMetadata2;
import android.media.MediaPlaylistAgent;
import android.media.MediaSession2;
import android.media.MediaSession2.SessionCallback;
-import android.media.MediaSessionService2;
-import android.media.MediaSessionService2.MediaNotification;
import android.media.Rating2;
import android.media.SessionCommand2;
import android.media.SessionCommandGroup2;
import android.media.SessionToken2;
import android.media.VolumeProvider2;
-import android.media.update.MediaLibraryService2Provider.LibraryRootProvider;
import android.media.update.MediaSession2Provider.BuilderBaseProvider;
import android.media.update.MediaSession2Provider.CommandButtonProvider;
import android.media.update.MediaSession2Provider.CommandGroupProvider;
import android.media.update.MediaSession2Provider.CommandProvider;
import android.media.update.MediaSession2Provider.ControllerInfoProvider;
-import android.media.update.MediaSessionService2Provider.MediaNotificationProvider;
import android.os.Bundle;
import android.os.IInterface;
-import android.util.AttributeSet;
-import android.widget.MediaControlView2;
-import android.widget.VideoView2;
import java.util.concurrent.Executor;
@@ -62,13 +47,6 @@
* @hide
*/
public interface StaticProvider {
- MediaControlView2Provider createMediaControlView2(MediaControlView2 instance,
- ViewGroupProvider superProvider, ViewGroupProvider privateProvider,
- @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes);
- VideoView2Provider createVideoView2(VideoView2 instance,
- ViewGroupProvider superProvider, ViewGroupProvider privateProvider,
- @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes);
-
CommandProvider createMediaSession2Command(SessionCommand2 instance,
int commandCode, String action, Bundle extra);
SessionCommand2 fromBundle_MediaSession2Command(Bundle bundle);
@@ -86,21 +64,6 @@
MediaController2Provider createMediaController2(Context context, MediaController2 instance,
SessionToken2 token, Executor executor, ControllerCallback callback);
- MediaBrowser2Provider createMediaBrowser2(Context context, MediaBrowser2 instance,
- SessionToken2 token, Executor executor, BrowserCallback callback);
-
- MediaSessionService2Provider createMediaSessionService2(MediaSessionService2 instance);
- MediaNotificationProvider createMediaSessionService2MediaNotification(
- MediaNotification mediaNotification, int notificationId, Notification notification);
-
- MediaSessionService2Provider createMediaLibraryService2(MediaLibraryService2 instance);
- BuilderBaseProvider<MediaLibrarySession, MediaLibrarySessionCallback>
- createMediaLibraryService2Builder(
- MediaLibraryService2 service, MediaLibrarySession.Builder instance,
- Executor callbackExecutor, MediaLibrarySessionCallback callback);
- LibraryRootProvider createMediaLibraryService2LibraryRoot(LibraryRoot instance, String rootId,
- Bundle extras);
-
SessionToken2Provider createSessionToken2(Context context, SessionToken2 instance,
String packageName, String serviceName, int uid);
SessionToken2 fromBundle_SessionToken2(Bundle bundle);
diff --git a/media/java/android/media/update/VideoView2Provider.java b/media/java/android/media/update/VideoView2Provider.java
deleted file mode 100644
index 27b436f..0000000
--- a/media/java/android/media/update/VideoView2Provider.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * 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.
- */
-
-package android.media.update;
-
-import android.annotation.SystemApi;
-import android.media.AudioAttributes;
-import android.media.DataSourceDesc;
-import android.media.MediaItem2;
-import android.media.MediaMetadata2;
-import android.media.MediaPlayerBase;
-import android.media.SessionToken2;
-import android.media.session.MediaController;
-import android.media.session.PlaybackState;
-import android.media.session.MediaSession;
-import android.net.Uri;
-import android.util.AttributeSet;
-import android.widget.MediaControlView2;
-import android.widget.VideoView2;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Executor;
-
-/**
- * Interface for connecting the public API to an updatable implementation.
- *
- * Each instance object is connected to one corresponding updatable object which implements the
- * runtime behavior of that class. There should a corresponding provider method for all public
- * methods.
- *
- * All methods behave as per their namesake in the public API.
- *
- * @see android.widget.VideoView2
- *
- * @hide
- */
-// TODO @SystemApi
-public interface VideoView2Provider extends ViewGroupProvider {
- void initialize(AttributeSet attrs, int defStyleAttr, int defStyleRes);
-
- void setMediaControlView2_impl(MediaControlView2 mediaControlView, long intervalMs);
- void setMediaMetadata_impl(MediaMetadata2 metadata);
- /**
- * @hide TODO: remove
- */
- MediaController getMediaController_impl();
- SessionToken2 getMediaSessionToken_impl();
- MediaControlView2 getMediaControlView2_impl();
- MediaMetadata2 getMediaMetadata_impl();
- void setSubtitleEnabled_impl(boolean enable);
- boolean isSubtitleEnabled_impl();
- // TODO: remove setSpeed_impl once MediaController2 is ready.
- void setSpeed_impl(float speed);
- void setAudioFocusRequest_impl(int focusGain);
- void setAudioAttributes_impl(AudioAttributes attributes);
- void setVideoPath_impl(String path);
- /**
- * @hide TODO: remove
- */
- void setVideoUri_impl(Uri uri);
- /**
- * @hide TODO: remove
- */
- void setVideoUri_impl(Uri uri, Map<String, String> headers);
- void setMediaItem_impl(MediaItem2 mediaItem);
- void setDataSource_impl(DataSourceDesc dsd);
- void setViewType_impl(int viewType);
- int getViewType_impl();
- /**
- * @hide TODO: remove
- */
- void setCustomActions_impl(List<PlaybackState.CustomAction> actionList,
- Executor executor, VideoView2.OnCustomActionListener listener);
- /**
- * @hide
- */
- @VisibleForTesting
- void setOnViewTypeChangedListener_impl(VideoView2.OnViewTypeChangedListener l);
- /**
- * @hide TODO: remove
- */
- void setFullScreenRequestListener_impl(VideoView2.OnFullScreenRequestListener l);
-}
diff --git a/media/java/android/media/update/ViewGroupHelper.java b/media/java/android/media/update/ViewGroupHelper.java
deleted file mode 100644
index 6b4f15d..0000000
--- a/media/java/android/media/update/ViewGroupHelper.java
+++ /dev/null
@@ -1,369 +0,0 @@
-/*
- * 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.
- */
-
-package android.media.update;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * Helper class for connecting the public API to an updatable implementation.
- *
- * @see ViewGroupProvider
- *
- * @hide
- */
-public abstract class ViewGroupHelper<T extends ViewGroupProvider> extends ViewGroup {
- /** @hide */
- final public T mProvider;
-
- /** @hide */
- public ViewGroupHelper(ProviderCreator<T> creator,
- Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
-
- mProvider = creator.createProvider(this, new SuperProvider(),
- new PrivateProvider());
- }
-
- /** @hide */
- // TODO @SystemApi
- public T getProvider() {
- return mProvider;
- }
-
- @Override
- protected void onAttachedToWindow() {
- mProvider.onAttachedToWindow_impl();
- }
-
- @Override
- protected void onDetachedFromWindow() {
- mProvider.onDetachedFromWindow_impl();
- }
-
- @Override
- public CharSequence getAccessibilityClassName() {
- return mProvider.getAccessibilityClassName_impl();
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- return mProvider.onTouchEvent_impl(ev);
- }
-
- @Override
- public boolean onTrackballEvent(MotionEvent ev) {
- return mProvider.onTrackballEvent_impl(ev);
- }
-
- @Override
- public void onFinishInflate() {
- mProvider.onFinishInflate_impl();
- }
-
- @Override
- public void setEnabled(boolean enabled) {
- mProvider.setEnabled_impl(enabled);
- }
-
- @Override
- public void onVisibilityAggregated(boolean isVisible) {
- mProvider.onVisibilityAggregated_impl(isVisible);
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- mProvider.onLayout_impl(changed, left, top, right, bottom);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- mProvider.onMeasure_impl(widthMeasureSpec, heightMeasureSpec);
- }
-
- @Override
- protected int getSuggestedMinimumWidth() {
- return mProvider.getSuggestedMinimumWidth_impl();
- }
-
- @Override
- protected int getSuggestedMinimumHeight() {
- return mProvider.getSuggestedMinimumHeight_impl();
- }
-
- // setMeasuredDimension is final
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- return mProvider.dispatchTouchEvent_impl(ev);
- }
-
- @Override
- protected boolean checkLayoutParams(LayoutParams p) {
- return mProvider.checkLayoutParams_impl(p);
- }
-
- @Override
- protected LayoutParams generateDefaultLayoutParams() {
- return mProvider.generateDefaultLayoutParams_impl();
- }
-
- @Override
- public LayoutParams generateLayoutParams(AttributeSet attrs) {
- return mProvider.generateLayoutParams_impl(attrs);
- }
-
- @Override
- protected LayoutParams generateLayoutParams(LayoutParams lp) {
- return mProvider.generateLayoutParams_impl(lp);
- }
-
- @Override
- public boolean shouldDelayChildPressedState() {
- return mProvider.shouldDelayChildPressedState_impl();
- }
-
- @Override
- protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
- int parentHeightMeasureSpec, int heightUsed) {
- mProvider.measureChildWithMargins_impl(child,
- parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
- }
-
- /** @hide */
- public class SuperProvider implements ViewGroupProvider {
- @Override
- public CharSequence getAccessibilityClassName_impl() {
- return ViewGroupHelper.super.getAccessibilityClassName();
- }
-
- @Override
- public boolean onTouchEvent_impl(MotionEvent ev) {
- return ViewGroupHelper.super.onTouchEvent(ev);
- }
-
- @Override
- public boolean onTrackballEvent_impl(MotionEvent ev) {
- return ViewGroupHelper.super.onTrackballEvent(ev);
- }
-
- @Override
- public void onFinishInflate_impl() {
- ViewGroupHelper.super.onFinishInflate();
- }
-
- @Override
- public void setEnabled_impl(boolean enabled) {
- ViewGroupHelper.super.setEnabled(enabled);
- }
-
- @Override
- public void onAttachedToWindow_impl() {
- ViewGroupHelper.super.onAttachedToWindow();
- }
-
- @Override
- public void onDetachedFromWindow_impl() {
- ViewGroupHelper.super.onDetachedFromWindow();
- }
-
- @Override
- public void onVisibilityAggregated_impl(boolean isVisible) {
- ViewGroupHelper.super.onVisibilityAggregated(isVisible);
- }
-
- @Override
- public void onLayout_impl(boolean changed, int left, int top, int right, int bottom) {
- // abstract method; no super
- }
-
- @Override
- public void onMeasure_impl(int widthMeasureSpec, int heightMeasureSpec) {
- ViewGroupHelper.super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- @Override
- public int getSuggestedMinimumWidth_impl() {
- return ViewGroupHelper.super.getSuggestedMinimumWidth();
- }
-
- @Override
- public int getSuggestedMinimumHeight_impl() {
- return ViewGroupHelper.super.getSuggestedMinimumHeight();
- }
-
- @Override
- public void setMeasuredDimension_impl(int measuredWidth, int measuredHeight) {
- ViewGroupHelper.super.setMeasuredDimension(measuredWidth, measuredHeight);
- }
-
- @Override
- public boolean dispatchTouchEvent_impl(MotionEvent ev) {
- return ViewGroupHelper.super.dispatchTouchEvent(ev);
- }
-
- @Override
- public boolean checkLayoutParams_impl(LayoutParams p) {
- return ViewGroupHelper.super.checkLayoutParams(p);
- }
-
- @Override
- public LayoutParams generateDefaultLayoutParams_impl() {
- return ViewGroupHelper.super.generateDefaultLayoutParams();
- }
-
- @Override
- public LayoutParams generateLayoutParams_impl(AttributeSet attrs) {
- return ViewGroupHelper.super.generateLayoutParams(attrs);
- }
-
- @Override
- public LayoutParams generateLayoutParams_impl(LayoutParams lp) {
- return ViewGroupHelper.super.generateLayoutParams(lp);
- }
-
- @Override
- public boolean shouldDelayChildPressedState_impl() {
- return ViewGroupHelper.super.shouldDelayChildPressedState();
- }
-
- @Override
- public void measureChildWithMargins_impl(View child,
- int parentWidthMeasureSpec, int widthUsed,
- int parentHeightMeasureSpec, int heightUsed) {
- ViewGroupHelper.super.measureChildWithMargins(child,
- parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
- }
- }
-
- /** @hide */
- public class PrivateProvider implements ViewGroupProvider {
- @Override
- public CharSequence getAccessibilityClassName_impl() {
- return ViewGroupHelper.this.getAccessibilityClassName();
- }
-
- @Override
- public boolean onTouchEvent_impl(MotionEvent ev) {
- return ViewGroupHelper.this.onTouchEvent(ev);
- }
-
- @Override
- public boolean onTrackballEvent_impl(MotionEvent ev) {
- return ViewGroupHelper.this.onTrackballEvent(ev);
- }
-
- @Override
- public void onFinishInflate_impl() {
- ViewGroupHelper.this.onFinishInflate();
- }
-
- @Override
- public void setEnabled_impl(boolean enabled) {
- ViewGroupHelper.this.setEnabled(enabled);
- }
-
- @Override
- public void onAttachedToWindow_impl() {
- ViewGroupHelper.this.onAttachedToWindow();
- }
-
- @Override
- public void onDetachedFromWindow_impl() {
- ViewGroupHelper.this.onDetachedFromWindow();
- }
-
- @Override
- public void onVisibilityAggregated_impl(boolean isVisible) {
- ViewGroupHelper.this.onVisibilityAggregated(isVisible);
- }
-
- @Override
- public void onLayout_impl(boolean changed, int left, int top, int right, int bottom) {
- ViewGroupHelper.this.onLayout(changed, left, top, right, bottom);
- }
-
- @Override
- public void onMeasure_impl(int widthMeasureSpec, int heightMeasureSpec) {
- ViewGroupHelper.this.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- @Override
- public int getSuggestedMinimumWidth_impl() {
- return ViewGroupHelper.this.getSuggestedMinimumWidth();
- }
-
- @Override
- public int getSuggestedMinimumHeight_impl() {
- return ViewGroupHelper.this.getSuggestedMinimumHeight();
- }
-
- @Override
- public void setMeasuredDimension_impl(int measuredWidth, int measuredHeight) {
- ViewGroupHelper.this.setMeasuredDimension(measuredWidth, measuredHeight);
- }
-
- @Override
- public boolean dispatchTouchEvent_impl(MotionEvent ev) {
- return ViewGroupHelper.this.dispatchTouchEvent(ev);
- }
-
- @Override
- public boolean checkLayoutParams_impl(LayoutParams p) {
- return ViewGroupHelper.this.checkLayoutParams(p);
- }
-
- @Override
- public LayoutParams generateDefaultLayoutParams_impl() {
- return ViewGroupHelper.this.generateDefaultLayoutParams();
- }
-
- @Override
- public LayoutParams generateLayoutParams_impl(AttributeSet attrs) {
- return ViewGroupHelper.this.generateLayoutParams(attrs);
- }
-
- @Override
- public LayoutParams generateLayoutParams_impl(LayoutParams lp) {
- return ViewGroupHelper.this.generateLayoutParams(lp);
- }
-
- @Override
- public boolean shouldDelayChildPressedState_impl() {
- return ViewGroupHelper.this.shouldDelayChildPressedState();
- }
-
- @Override
- public void measureChildWithMargins_impl(View child,
- int parentWidthMeasureSpec, int widthUsed,
- int parentHeightMeasureSpec, int heightUsed) {
- ViewGroupHelper.this.measureChildWithMargins(child,
- parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
- }
- }
-
- /** @hide */
- @FunctionalInterface
- public interface ProviderCreator<T extends ViewGroupProvider> {
- T createProvider(ViewGroupHelper<T> instance, ViewGroupProvider superProvider,
- ViewGroupProvider privateProvider);
- }
-}
diff --git a/media/java/android/media/update/ViewGroupProvider.java b/media/java/android/media/update/ViewGroupProvider.java
deleted file mode 100644
index 67e8cea..0000000
--- a/media/java/android/media/update/ViewGroupProvider.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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.
- */
-
-package android.media.update;
-
-import android.annotation.SystemApi;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup.LayoutParams;
-
-/**
- * Interface for connecting the public API to an updatable implementation.
- *
- * Each instance object is connected to one corresponding updatable object which implements the
- * runtime behavior of that class. There should a corresponding provider method for all public
- * methods.
- *
- * All methods behave as per their namesake in the public API.
- *
- * @see android.view.View
- *
- * @hide
- */
-// TODO @SystemApi
-public interface ViewGroupProvider {
- // View methods
- void onAttachedToWindow_impl();
- void onDetachedFromWindow_impl();
- CharSequence getAccessibilityClassName_impl();
- boolean onTouchEvent_impl(MotionEvent ev);
- boolean onTrackballEvent_impl(MotionEvent ev);
- void onFinishInflate_impl();
- void setEnabled_impl(boolean enabled);
- void onVisibilityAggregated_impl(boolean isVisible);
- void onLayout_impl(boolean changed, int left, int top, int right, int bottom);
- void onMeasure_impl(int widthMeasureSpec, int heightMeasureSpec);
- int getSuggestedMinimumWidth_impl();
- int getSuggestedMinimumHeight_impl();
- void setMeasuredDimension_impl(int measuredWidth, int measuredHeight);
- boolean dispatchTouchEvent_impl(MotionEvent ev);
-
- // ViewGroup methods
- boolean checkLayoutParams_impl(LayoutParams p);
- LayoutParams generateDefaultLayoutParams_impl();
- LayoutParams generateLayoutParams_impl(AttributeSet attrs);
- LayoutParams generateLayoutParams_impl(LayoutParams lp);
- boolean shouldDelayChildPressedState_impl();
- void measureChildWithMargins_impl(View child, int parentWidthMeasureSpec, int widthUsed,
- int parentHeightMeasureSpec, int heightUsed);
-
- // ViewManager methods
- // ViewParent methods
-}
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 0f531c9..faf4301 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -97,7 +97,6 @@
shared_libs: [
"android.hardware.cas@1.0", // for CasManager. VNDK???
"android.hardware.cas.native@1.0", // CasManager. VNDK???
- "libaudioclient", // for use of AudioTrack, AudioSystem. to be removed
"libbinder",
"libgui", // for VideoFrameScheduler
"libhidlallocatorutils",
diff --git a/media/jni/android_media_MediaPlayer2.cpp b/media/jni/android_media_MediaPlayer2.cpp
index 0769e5c..f7de2e7 100644
--- a/media/jni/android_media_MediaPlayer2.cpp
+++ b/media/jni/android_media_MediaPlayer2.cpp
@@ -175,7 +175,7 @@
// that posts events to the application thread.
jclass clazz = env->GetObjectClass(thiz);
if (clazz == NULL) {
- ALOGE("Can't find android/media/MediaPlayer2Impl");
+ ALOGE("Can't find android/media/MediaPlayer2");
jniThrowException(env, "java/lang/Exception", NULL);
return;
}
@@ -488,7 +488,7 @@
env->SetLongField(thiz, fields.surface_texture, (jlong)anw);
// This will fail if the media player has not been initialized yet. This
- // can be the case if setDisplay() on MediaPlayer2Impl.java has been called
+ // can be the case if setDisplay() on MediaPlayer2.java has been called
// before setDataSource(). The redundant call to setVideoSurfaceTexture()
// in prepare/prepare covers for this case.
mp->setVideoSurfaceTexture(new ANativeWindowWrapper(anw));
@@ -950,7 +950,7 @@
{
jclass clazz;
- clazz = env->FindClass("android/media/MediaPlayer2Impl");
+ clazz = env->FindClass("android/media/MediaPlayer2");
if (clazz == NULL) {
return;
}
@@ -1009,10 +1009,11 @@
}
static void
-android_media_MediaPlayer2_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
+android_media_MediaPlayer2_native_setup(JNIEnv *env, jobject thiz,
+ jint sessionId, jobject weak_this)
{
ALOGV("native_setup");
- sp<MediaPlayer2> mp = MediaPlayer2::Create();
+ sp<MediaPlayer2> mp = MediaPlayer2::Create(sessionId);
if (mp == NULL) {
jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
return;
@@ -1050,9 +1051,9 @@
android_media_MediaPlayer2_release(env, thiz);
}
-static void android_media_MediaPlayer2_set_audio_session_id(JNIEnv *env, jobject thiz,
+static void android_media_MediaPlayer2_setAudioSessionId(JNIEnv *env, jobject thiz,
jint sessionId) {
- ALOGV("set_session_id(): %d", sessionId);
+ ALOGV("setAudioSessionId(): %d", sessionId);
sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
if (mp == NULL ) {
jniThrowException(env, "java/lang/IllegalStateException", NULL);
@@ -1062,8 +1063,8 @@
NULL);
}
-static jint android_media_MediaPlayer2_get_audio_session_id(JNIEnv *env, jobject thiz) {
- ALOGV("get_session_id()");
+static jint android_media_MediaPlayer2_getAudioSessionId(JNIEnv *env, jobject thiz) {
+ ALOGV("getAudioSessionId()");
sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
if (mp == NULL ) {
jniThrowException(env, "java/lang/IllegalStateException", NULL);
@@ -1395,21 +1396,21 @@
{"nativePlayNextDataSource", "(J)V", (void *)android_media_MediaPlayer2_playNextDataSource},
{"native_setVideoSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaPlayer2_setVideoSurface},
{"getBufferingParams", "()Landroid/media/BufferingParams;", (void *)android_media_MediaPlayer2_getBufferingParams},
- {"_setBufferingParams", "(Landroid/media/BufferingParams;)V", (void *)android_media_MediaPlayer2_setBufferingParams},
- {"_prepare", "()V", (void *)android_media_MediaPlayer2_prepare},
- {"_start", "()V", (void *)android_media_MediaPlayer2_start},
+ {"native_setBufferingParams", "(Landroid/media/BufferingParams;)V", (void *)android_media_MediaPlayer2_setBufferingParams},
+ {"native_prepare", "()V", (void *)android_media_MediaPlayer2_prepare},
+ {"native_start", "()V", (void *)android_media_MediaPlayer2_start},
{"native_getState", "()I", (void *)android_media_MediaPlayer2_getState},
{"native_getMetrics", "()Landroid/os/PersistableBundle;", (void *)android_media_MediaPlayer2_native_getMetrics},
- {"_setPlaybackParams", "(Landroid/media/PlaybackParams;)V", (void *)android_media_MediaPlayer2_setPlaybackParams},
+ {"native_setPlaybackParams", "(Landroid/media/PlaybackParams;)V", (void *)android_media_MediaPlayer2_setPlaybackParams},
{"getPlaybackParams", "()Landroid/media/PlaybackParams;", (void *)android_media_MediaPlayer2_getPlaybackParams},
- {"_setSyncParams", "(Landroid/media/SyncParams;)V", (void *)android_media_MediaPlayer2_setSyncParams},
+ {"native_setSyncParams", "(Landroid/media/SyncParams;)V", (void *)android_media_MediaPlayer2_setSyncParams},
{"getSyncParams", "()Landroid/media/SyncParams;", (void *)android_media_MediaPlayer2_getSyncParams},
- {"_seekTo", "(JI)V", (void *)android_media_MediaPlayer2_seekTo},
- {"_pause", "()V", (void *)android_media_MediaPlayer2_pause},
+ {"native_seekTo", "(JI)V", (void *)android_media_MediaPlayer2_seekTo},
+ {"native_pause", "()V", (void *)android_media_MediaPlayer2_pause},
{"getCurrentPosition", "()J", (void *)android_media_MediaPlayer2_getCurrentPosition},
{"getDuration", "()J", (void *)android_media_MediaPlayer2_getDuration},
- {"_release", "()V", (void *)android_media_MediaPlayer2_release},
- {"_reset", "()V", (void *)android_media_MediaPlayer2_reset},
+ {"native_release", "()V", (void *)android_media_MediaPlayer2_release},
+ {"native_reset", "()V", (void *)android_media_MediaPlayer2_reset},
{"native_setAudioAttributes", "(Landroid/media/AudioAttributes;)Z", (void *)android_media_MediaPlayer2_setAudioAttributes},
{"native_getAudioAttributes", "()Landroid/media/AudioAttributes;", (void *)android_media_MediaPlayer2_getAudioAttributes},
{"setLooping", "(Z)V", (void *)android_media_MediaPlayer2_setLooping},
@@ -1417,15 +1418,15 @@
{"native_setVolume", "(F)V", (void *)android_media_MediaPlayer2_setVolume},
{"native_invoke", "([B)[B", (void *)android_media_MediaPlayer2_invoke},
{"native_init", "()V", (void *)android_media_MediaPlayer2_native_init},
- {"native_setup", "(Ljava/lang/Object;)V", (void *)android_media_MediaPlayer2_native_setup},
+ {"native_setup", "(ILjava/lang/Object;)V", (void *)android_media_MediaPlayer2_native_setup},
{"native_finalize", "()V", (void *)android_media_MediaPlayer2_native_finalize},
- {"getAudioSessionId", "()I", (void *)android_media_MediaPlayer2_get_audio_session_id},
- {"_setAudioSessionId", "(I)V", (void *)android_media_MediaPlayer2_set_audio_session_id},
- {"_setAuxEffectSendLevel", "(F)V", (void *)android_media_MediaPlayer2_setAuxEffectSendLevel},
- {"_attachAuxEffect", "(I)V", (void *)android_media_MediaPlayer2_attachAuxEffect},
+ {"getAudioSessionId", "()I", (void *)android_media_MediaPlayer2_getAudioSessionId},
+ {"native_setAudioSessionId", "(I)V", (void *)android_media_MediaPlayer2_setAudioSessionId},
+ {"native_setAuxEffectSendLevel", "(F)V", (void *)android_media_MediaPlayer2_setAuxEffectSendLevel},
+ {"native_attachAuxEffect", "(I)V", (void *)android_media_MediaPlayer2_attachAuxEffect},
// Modular DRM
- { "_prepareDrm", "([B[B)V", (void *)android_media_MediaPlayer2_prepareDrm },
- { "_releaseDrm", "()V", (void *)android_media_MediaPlayer2_releaseDrm },
+ { "native_prepareDrm", "([B[B)V", (void *)android_media_MediaPlayer2_prepareDrm },
+ { "native_releaseDrm", "()V", (void *)android_media_MediaPlayer2_releaseDrm },
// AudioRouting
{"native_setPreferredDevice", "(Landroid/media/AudioDeviceInfo;)Z", (void *)android_media_MediaPlayer2_setPreferredDevice},
@@ -1441,9 +1442,9 @@
};
// This function only registers the native methods
-static int register_android_media_MediaPlayer2Impl(JNIEnv *env)
+static int register_android_media_MediaPlayer2(JNIEnv *env)
{
- return jniRegisterNativeMethods(env, "android/media/MediaPlayer2Impl", gMethods, NELEM(gMethods));
+ return jniRegisterNativeMethods(env, "android/media/MediaPlayer2", gMethods, NELEM(gMethods));
}
jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
@@ -1457,7 +1458,7 @@
}
assert(env != NULL);
- if (register_android_media_MediaPlayer2Impl(env) < 0) {
+ if (register_android_media_MediaPlayer2(env) < 0) {
ALOGE("ERROR: MediaPlayer2 native registration failed\n");
goto bail;
}
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 9426148..942eafd 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -37,6 +37,7 @@
ldflags: ["-Wl,--hash-style=both"],
},
},
+ version_script: "libjnigraphics.map.txt",
}
// The headers module is in frameworks/native/Android.bp.
diff --git a/native/webview/plat_support/Android.bp b/native/webview/plat_support/Android.bp
new file mode 100644
index 0000000..d8c5ac9
--- /dev/null
+++ b/native/webview/plat_support/Android.bp
@@ -0,0 +1,43 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+// This package provides the system interfaces allowing WebView to render.
+
+// Native support library (libwebviewchromium_plat_support.so) - does NOT link
+// any native chromium code.
+cc_library_shared {
+ name: "libwebviewchromium_plat_support",
+
+ srcs: [
+ "draw_gl_functor.cpp",
+ "jni_entry_point.cpp",
+ "graphics_utils.cpp",
+ "graphic_buffer_impl.cpp",
+ ],
+
+ shared_libs: [
+ "libandroidfw",
+ "libandroid_runtime",
+ "libcutils",
+ "libhwui",
+ "liblog",
+ "libui",
+ "libutils",
+ ],
+
+ // To remove warnings from skia header files
+ cflags: ["-Wno-unused-parameter"],
+}
diff --git a/native/webview/plat_support/Android.mk b/native/webview/plat_support/Android.mk
deleted file mode 100644
index 6a33fe2..0000000
--- a/native/webview/plat_support/Android.mk
+++ /dev/null
@@ -1,52 +0,0 @@
-#
-# Copyright (C) 2012 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# This package provides the system interfaces allowing WebView to render.
-
-LOCAL_PATH := $(call my-dir)
-
-# Native support library (libwebviewchromium_plat_support.so) - does NOT link
-# any native chromium code.
-include $(CLEAR_VARS)
-
-LOCAL_MODULE:= libwebviewchromium_plat_support
-
-LOCAL_SRC_FILES:= \
- draw_gl_functor.cpp \
- jni_entry_point.cpp \
- graphics_utils.cpp \
- graphic_buffer_impl.cpp \
-
-LOCAL_C_INCLUDES:= \
- external/skia/include/core \
- frameworks/base/core/jni/android/graphics \
- frameworks/native/include/ui \
-
-LOCAL_SHARED_LIBRARIES += \
- libandroid_runtime \
- liblog \
- libcutils \
- libui \
- libutils \
- libhwui \
- libandroidfw
-
-LOCAL_MODULE_TAGS := optional
-
-# To remove warnings from skia header files
-LOCAL_CFLAGS := -Wno-unused-parameter
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/native/webview/plat_support/graphics_utils.cpp b/native/webview/plat_support/graphics_utils.cpp
index 89beb75..56825ce 100644
--- a/native/webview/plat_support/graphics_utils.cpp
+++ b/native/webview/plat_support/graphics_utils.cpp
@@ -25,8 +25,8 @@
#include <cstdlib>
#include <jni.h>
#include <utils/Log.h>
+#include "android/graphics/GraphicsJNI.h"
#include "graphic_buffer_impl.h"
-#include "GraphicsJNI.h"
#include "SkCanvasStateUtils.h"
#include "SkGraphics.h"
#include "SkPicture.h"
diff --git a/packages/CaptivePortalLogin/AndroidManifest.xml b/packages/CaptivePortalLogin/AndroidManifest.xml
index 9ecaa03..5ab6632 100644
--- a/packages/CaptivePortalLogin/AndroidManifest.xml
+++ b/packages/CaptivePortalLogin/AndroidManifest.xml
@@ -26,7 +26,8 @@
<uses-permission android:name="android.permission.NETWORK_SETTINGS" />
<application android:label="@string/app_name"
- android:usesCleartextTraffic="true">
+ android:usesCleartextTraffic="true"
+ android:supportsRtl="true" >
<activity
android:name="com.android.captiveportallogin.CaptivePortalLoginActivity"
android:label="@string/action_bar_label"
diff --git a/packages/CaptivePortalLogin/res/layout/ssl_error_msg.xml b/packages/CaptivePortalLogin/res/layout/ssl_error_msg.xml
new file mode 100644
index 0000000..d460041
--- /dev/null
+++ b/packages/CaptivePortalLogin/res/layout/ssl_error_msg.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/ssl_error_msg"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_marginStart="20dip"
+ android:layout_marginEnd="20dip"
+ android:gravity="center_vertical"
+ android:layout_marginBottom="4dip"
+ android:layout_marginTop="4dip" />
+
diff --git a/packages/CaptivePortalLogin/res/layout/ssl_warning.xml b/packages/CaptivePortalLogin/res/layout/ssl_warning.xml
new file mode 100644
index 0000000..ffd57a4
--- /dev/null
+++ b/packages/CaptivePortalLogin/res/layout/ssl_warning.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <!-- ssl error type -->
+ <TextView
+ android:id="@+id/ssl_error_type"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start"
+ android:text="SSL_UNKNOWN"
+ android:layout_marginStart="24dip"
+ android:layout_marginEnd="24dip"
+ android:layout_marginBottom="0dip"
+ android:layout_marginTop="24dip" />
+
+ <!-- Page info: -->
+ <TextView
+ android:id="@+id/page_info"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/page_info"
+ android:textStyle="bold"
+ android:layout_marginStart="24dip"
+ android:layout_marginEnd="24dip" />
+
+ <!-- Title: -->
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textStyle="bold"
+ android:layout_marginStart="24dip"
+ android:layout_marginEnd="24dip" />
+
+ <!-- Address: -->
+ <TextView
+ android:id="@+id/address_header"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/page_info_address"
+ android:layout_marginStart="24dip"
+ android:layout_marginEnd="24dip" />
+
+ <TextView
+ android:id="@+id/address"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="24dip"
+ android:layout_marginEnd="24dip" />
+
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="4dip"
+ android:paddingEnd="4dip" >
+
+ <!-- certificate view: -->
+ <LinearLayout
+ android:id="@+id/certificate_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="16dip" >
+ </LinearLayout>
+
+ </ScrollView>
+
+</LinearLayout>
diff --git a/packages/CaptivePortalLogin/res/values-af/strings.xml b/packages/CaptivePortalLogin/res/values-af/strings.xml
index fa6f3fa..cf4dc82 100644
--- a/packages/CaptivePortalLogin/res/values-af/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-af/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Die netwerk waarby jy probeer aansluit, het sekuriteitkwessies."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Byvoorbeeld, die aanmeldbladsy behoort dalk nie aan die organisasie wat gewys word nie."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Gaan in elk geval deur blaaier voort"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Bladsy-inligting"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Adres:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Sekuriteitswaarskuwing"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Bekyk sertifikaat"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Hierdie sertifikaat is nie van \'n betroubare owerheid nie."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Die naam van die werf kom nie ooreen met die naam op die sertifikaat nie."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Hierdie sertifikaat het verval."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Hierdie sertifikaat is nog nie geldig nie."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Hierdie sertifikaat het \'n ongeldige datum."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Hierdie sertifikaat is ongeldig."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Onbekende sertifikaatfout."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-am/strings.xml b/packages/CaptivePortalLogin/res/values-am/strings.xml
index 36d5e19..cdcb5a5 100644
--- a/packages/CaptivePortalLogin/res/values-am/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-am/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"ለመቀላቀል እየሞከሩ ያሉት አውታረ መረብ የደህንነት ችግሮች አሉበት።"</string>
<string name="ssl_error_example" msgid="647898534624078900">"ለምሳሌ፣ የመግቢያ ገጹ የሚታየው ድርጅት ላይሆን ይችላል።"</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"ለማንኛውም በአሳሽ በኩል ይቀጥሉ"</string>
+ <string name="ok" msgid="1509280796718850364">"እሺ"</string>
+ <string name="page_info" msgid="4048529256302257195">"የገፅ መረጃ"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"አድራሻ:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"የደህንነት ቅንብሮች"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"ምስክሮች ይመልከቱ"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"ይህ ምስክር ከታማኝ ቦታ አይደለም።"</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"የጣቢያው ስም ከምስክር ወረቀቱ ስም ጋር አይዛመድም።"</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"ይህ ምስክር ጊዜው አልፏል"</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"ይህ ምስክር ገና ትክክል አይደለም።"</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"ይህ ምስክር ትክክለኛ ቀን አለው።"</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"ይህ ምስክር ትክክል ያልሆነ ነው።"</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"ያልታወቀ የምስክር ስህተት።"</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-ar/strings.xml b/packages/CaptivePortalLogin/res/values-ar/strings.xml
index 8eb259b..7773eeb 100644
--- a/packages/CaptivePortalLogin/res/values-ar/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-ar/strings.xml
@@ -11,4 +11,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"الشبكة التي تحاول الانضمام إليها بها مشاكل أمنية."</string>
<string name="ssl_error_example" msgid="647898534624078900">"على سبيل المثال، قد لا تنتمي صفحة تسجيل الدخول إلى المنظمة المعروضة."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"المتابعة على أي حال عبر المتصفح"</string>
+ <string name="ok" msgid="1509280796718850364">"موافق"</string>
+ <string name="page_info" msgid="4048529256302257195">"معلومات الصفحة"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"العنوان:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"تحذير أمان"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"عرض الشهادة"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"هذه الشهادة ليست من جهة موثوق بها."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"لا يتطابق اسم الموقع مع الاسم على الشهادة."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"انتهت صلاحية هذه الشهادة."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"هذه الشهادة ليست صالحة بعد."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"تشتمل هذه الشهادة على تاريخ غير صالح."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"هذه الشهادة غير صالحة."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"حدث خطأ غير معروف بالشهادة."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-bg/strings.xml b/packages/CaptivePortalLogin/res/values-bg/strings.xml
index 8ce9deb..4dd8aa0 100644
--- a/packages/CaptivePortalLogin/res/values-bg/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-bg/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Мрежата, към която опитвате да се присъедините, има проблеми със сигурността."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Например страницата за вход може да не принадлежи на показаната организация."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Продължаване през браузър въпреки това"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Данни за страницата"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Адрес:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Предупреждение относно защитата"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Преглед на сертификата"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Сертификатът не е от надежден орган."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Името на сайта не съответства на името в сертификата."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Сертификатът е изтекъл."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Сертификатът още не е валиден."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Този сертификат е с невалидна дата."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Този сертификат е невалиден."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Неизвестна грешка в сертификата."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-bn/strings.xml b/packages/CaptivePortalLogin/res/values-bn/strings.xml
index b75d76e..fb703cf 100644
--- a/packages/CaptivePortalLogin/res/values-bn/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-bn/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"আপনি যে নেটওয়ার্কে যোগ দেওয়ার চেষ্টা করছেন তাতে নিরাপত্তার সমস্যা আছে।"</string>
<string name="ssl_error_example" msgid="647898534624078900">"উদাহরণস্বরূপ, লগ-ইন পৃষ্ঠাটি প্রদর্শিত প্রতিষ্ঠানের অন্তর্গত নাও হতে পারে৷"</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"যাই হোক না কেন ব্রাউজারের মাধ্যমে অবিরত রাখুন"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Sideinfo"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Adresse:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Sikkerhetsadvarsel"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Vis sertifikat"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Sertifikatet er ikke fra en pålitelig myndighet."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Navnet på nettstedet samsvarer ikke med navnet på sertifikatet."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Sertifikatet er utløpt."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Sertifikatet er ikke gyldig ennå."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Dette sertifikatet har en ugyldig dato."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Dette sertifikatet er ugyldig."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Ukjent sertifikatfeil."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-ca/strings.xml b/packages/CaptivePortalLogin/res/values-ca/strings.xml
index fe189ed..a2c9ed8 100644
--- a/packages/CaptivePortalLogin/res/values-ca/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-ca/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"La xarxa a què et vols connectar té problemes de seguretat."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Per exemple, la pàgina d\'inici de sessió podria no pertànyer a l\'organització que es mostra."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Continua igualment mitjançant el navegador"</string>
+ <string name="ok" msgid="1509280796718850364">"D\'acord"</string>
+ <string name="page_info" msgid="4048529256302257195">"Informació de la pàgina"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Adreça:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Advertiment de seguretat"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Visualitza el certificat"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Aquest certificat no és d\'una autoritat de confiança."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"El nom del lloc no coincideix amb el del certificat."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Aquest certificat ha caducat."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Aquest certificat encara no és vàlid."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Aquest certificat té una data no vàlida."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Aquest certificat no és vàlid."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Error de certificat desconegut."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-cs/strings.xml b/packages/CaptivePortalLogin/res/values-cs/strings.xml
index 09dcc5f..be649a5 100644
--- a/packages/CaptivePortalLogin/res/values-cs/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-cs/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Síť, ke které se pokoušíte připojit, má bezpečnostní problémy."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Například přihlašovací stránka nemusí patřit do zobrazované organizace."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Přesto pokračovat prostřednictvím prohlížeče"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Informace o stránce"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Adresa:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Upozornění zabezpečení"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Zobrazit certifikát"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Tento certifikát nepochází od důvěryhodné autority."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Název webu se neshoduje s názvem uvedeným v certifikátu."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Platnost certifikátu vypršela."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Tento certifikát ještě není platný."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Datum tohoto certifikátu není platné."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Tento certifikát je neplatný."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Neznámá chyba certifikátu."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-da/strings.xml b/packages/CaptivePortalLogin/res/values-da/strings.xml
index dc0dd17..8183105 100644
--- a/packages/CaptivePortalLogin/res/values-da/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-da/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Der er sikkerhedsproblemer på det netværk, du forsøger at logge ind på."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Det er f.eks. ikke sikkert, at loginsiden tilhører den anførte organisation."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Fortsæt alligevel via browseren"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Sideoplysninger"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Adresse:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Sikkerhedsadvarsel"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Vis certifikat"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Dette certifikat stammer ikke fra en troværdig autoritet."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Navnet på websitet stemmer ikke overens med navnet på certifikatet."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Dette certifikat er udløbet."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Dette certifikat er endnu ikke gyldigt."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Dette certifikat har en ugyldig dato."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Dette certifikat er ugyldigt."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Ukendt fejl i certifikatet."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-de/strings.xml b/packages/CaptivePortalLogin/res/values-de/strings.xml
index d8f7be9..a9b7415 100644
--- a/packages/CaptivePortalLogin/res/values-de/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-de/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Im Netzwerk, zu dem du eine Verbindung herstellen möchtest, liegen Sicherheitsprobleme vor."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Beispiel: Die Log-in-Seite gehört eventuell nicht zur angezeigten Organisation."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Trotzdem in einem Browser fortfahren"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Seiteninfo"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Adresse:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Sicherheitswarnung"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Zertifikat ansehen"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Dieses Zertifikat wurde nicht von einer vertrauenswürdigen Stelle ausgegeben."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Name der Website stimmt nicht mit dem Namen auf dem Zertifikat überein."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Dieses Zertifikat ist abgelaufen."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Dieses Zertifikat ist noch nicht gültig."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Dieses Zertifikat weist ein ungültiges Datum auf."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Dieses Zertifikat ist ungültig."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Unbekannter Zertifikatfehler"</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-el/strings.xml b/packages/CaptivePortalLogin/res/values-el/strings.xml
index cb61710..16bf6e2 100644
--- a/packages/CaptivePortalLogin/res/values-el/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-el/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Παρουσιάζονται προβλήματα ασφάλειας στο δίκτυο στο οποίο προσπαθείτε να συνδεθείτε."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Για παράδειγμα, η σελίδα σύνδεσης ενδέχεται να μην ανήκει στον οργανισμό που εμφανίζεται."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Συνέχεια ούτως ή άλλως μέσω του προγράμματος περιήγησης"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Πληροφορίες σελίδας"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Διεύθυνση:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Προειδοποίηση ασφαλείας"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Προβολή πιστοποιητικού"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Αυτό το πιστοποιητικό δεν προέρχεται από αξιόπιστη αρχή."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Το όνομα του ιστότοπου δεν αντιστοιχεί με το όνομα στο πιστοποιητικό."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Αυτό το πιστοποιητικό έχει λήξει."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Αυτό το πιστοποιητικό δεν είναι έγκυρο ακόμα."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Αυτό το πιστοποιητικό δεν έχει έγκυρη ημερομηνία."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Αυτό το πιστοποιητικό δεν είναι έγκυρο."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Άγνωστο σφάλμα πιστοποιητικού."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-en-rGB/strings.xml b/packages/CaptivePortalLogin/res/values-en-rGB/strings.xml
index 2e8d1f0..f940299 100644
--- a/packages/CaptivePortalLogin/res/values-en-rGB/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-en-rGB/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"The network that you’re trying to join has security issues."</string>
<string name="ssl_error_example" msgid="647898534624078900">"For example, the login page might not belong to the organisation shown."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Continue anyway via browser"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Page info"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Address:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Security warning"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"View certificate"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"This certificate isn\'t from a trusted authority."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"The name of the site doesn\'t match the name on the certificate."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"This certificate has expired."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"This certificate isn\'t valid yet."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"This certificate has an invalid date."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"This certificate is invalid."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Unknown certificate error."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-en-rIN/strings.xml b/packages/CaptivePortalLogin/res/values-en-rIN/strings.xml
index 2e8d1f0..f940299 100644
--- a/packages/CaptivePortalLogin/res/values-en-rIN/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-en-rIN/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"The network that you’re trying to join has security issues."</string>
<string name="ssl_error_example" msgid="647898534624078900">"For example, the login page might not belong to the organisation shown."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Continue anyway via browser"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Page info"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Address:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Security warning"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"View certificate"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"This certificate isn\'t from a trusted authority."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"The name of the site doesn\'t match the name on the certificate."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"This certificate has expired."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"This certificate isn\'t valid yet."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"This certificate has an invalid date."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"This certificate is invalid."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Unknown certificate error."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-es-rUS/strings.xml b/packages/CaptivePortalLogin/res/values-es-rUS/strings.xml
index 5d7ba91..c011664 100644
--- a/packages/CaptivePortalLogin/res/values-es-rUS/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-es-rUS/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"La red a la que intentas conectarte tiene problemas de seguridad."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Por ejemplo, es posible que la página de acceso no pertenezca a la organización que aparece."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Continuar de todos modos desde el navegador"</string>
+ <string name="ok" msgid="1509280796718850364">"Aceptar"</string>
+ <string name="page_info" msgid="4048529256302257195">"Información de la página"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Dirección:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Advertencia de seguridad"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Ver certificado"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Este certificado no proviene de una autoridad confiable."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"El nombre del sitio no coincide con el nombre del certificado."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Este certificado ha expirado."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Este certificado aún no es válido."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"La fecha de este certificado no es válida."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Este certificado no es válido."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Error de certificado desconocido"</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-es/strings.xml b/packages/CaptivePortalLogin/res/values-es/strings.xml
index da2eae9..65244e7 100644
--- a/packages/CaptivePortalLogin/res/values-es/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-es/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"La red a la que intentas unirte tiene problemas de seguridad."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Por ejemplo, es posible que la página de inicio de sesión no pertenezca a la organización mostrada."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Continuar de todos modos a través del navegador"</string>
+ <string name="ok" msgid="1509280796718850364">"Aceptar"</string>
+ <string name="page_info" msgid="4048529256302257195">"Información de la página"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Dirección:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Advertencia de seguridad"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Ver certificado"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Este certificado no procede de una entidad de certificación de confianza."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"El nombre del sitio no coincide con el del certificado."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Este certificado ha caducado."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Este certificado aún no es válido."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"La fecha de este certificado no es válida."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Este certificado no es válido."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Error de certificado desconocido"</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-et/strings.xml b/packages/CaptivePortalLogin/res/values-et/strings.xml
index 41fcb9a..e4c4c98 100644
--- a/packages/CaptivePortalLogin/res/values-et/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-et/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Võrgul, millega üritate ühenduse luua, on turvaprobleeme."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Näiteks ei pruugi sisselogimisleht kuuluda kuvatavale organisatsioonile."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Jätka siiski brauseris"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Lehe teave"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Aadress:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Turvahoiatus"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Kuva sertifikaat"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"See sertifikaat ei pärine usaldusväärselt asutuselt."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Saidi nimi ei vasta sertifikaadil olevale nimele."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"See sertifikaat on aegunud."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"See sertifikaat pole veel kehtiv."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Sellel sertifikaadil on kehtetu kuupäev."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"See sertifikaat on kehtetu."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Tundmatu sertifikaadiviga."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-fa/strings.xml b/packages/CaptivePortalLogin/res/values-fa/strings.xml
index 2e4cc51..27b9b7f 100644
--- a/packages/CaptivePortalLogin/res/values-fa/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-fa/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"شبکهای که میخواهید به آن بپیوندید مشکلات امنیتی دارد."</string>
<string name="ssl_error_example" msgid="647898534624078900">"به عنوان مثال، صفحه ورود به سیستم ممکن است متعلق به سازمان نشان داده شده نباشد."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"در هر صورت از طریق مرورگر ادامه یابد"</string>
+ <string name="ok" msgid="1509280796718850364">"تأیید"</string>
+ <string name="page_info" msgid="4048529256302257195">"اطلاعات صفحه"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"آدرس:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"اخطار امنیتی"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"مشاهده گواهی"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"این گواهی از یک منبع مورد اطمینان صادر نشده است."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"نام سایت با نام موجود در گواهی مطابقت ندارد."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"این گواهی منقضی شده است."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"این گواهی هنوز معتبر نیست."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"تاریخ این گواهی نامعتبر است."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"این گواهی نامعتبر است."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"خطای ناشناخته در گواهی."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-fi/strings.xml b/packages/CaptivePortalLogin/res/values-fi/strings.xml
index 1976f7d..8086fbf 100644
--- a/packages/CaptivePortalLogin/res/values-fi/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-fi/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Verkossa, johon yrität muodostaa yhteyttä, on turvallisuusongelmia."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Kirjautumissivu ei välttämättä kuulu näytetylle organisaatiolle."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Jatka silti selaimen kautta."</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Sivun tiedot"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Osoite:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Suojausvaroitus"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Näytä varmenne"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Varmenteen myöntäjä ei ole luotettava taho."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Sivuston nimi ei vastaa varmenteessa olevaa nimeä."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Varmenne ei ole enää voimassa."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Varmenne ei ole vielä voimassa."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Varmenteen päiväys ei kelpaa."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Varmenne on virheellinen."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Tuntematon varmennevirhe."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-fr/strings.xml b/packages/CaptivePortalLogin/res/values-fr/strings.xml
index 8f98bb5..39fc569 100644
--- a/packages/CaptivePortalLogin/res/values-fr/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-fr/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Le réseau que vous essayez de rejoindre présente des problèmes de sécurité."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Par exemple, la page de connexion peut ne pas appartenir à l\'organisation représentée."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Continuer quand même dans le navigateur"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Infos sur la page"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Adresse :"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Avertissement de sécurité"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Afficher le certificat"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Ce certificat provient d\'une autorité non approuvée."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Le nom du site ne correspond pas au nom indiqué dans le certificat."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Le certificat a expiré."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Ce certificat n\'est pas encore valide."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"La date de ce certificat n\'est pas valide."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Ce certificat n\'est pas valide."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Erreur : Certificat inconnu."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-hi/strings.xml b/packages/CaptivePortalLogin/res/values-hi/strings.xml
index 1bacc46..d924fff 100644
--- a/packages/CaptivePortalLogin/res/values-hi/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-hi/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"आप जिस नेटवर्क में शामिल होने का प्रयास कर रहे हैं उसमें सुरक्षा समस्याएं हैं."</string>
<string name="ssl_error_example" msgid="647898534624078900">"उदाहरण के लिए, हो सकता है कि लॉगिन पृष्ठ दिखाए गए संगठन से संबद्ध ना हो."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"ब्राउज़र के द्वारा फिर जारी रखें"</string>
+ <string name="ok" msgid="1509280796718850364">"ठीक"</string>
+ <string name="page_info" msgid="4048529256302257195">"पृष्ठ जानकारी"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"पता:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"सुरक्षा चेतावनी"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"प्रमाणपत्र देखें"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"यह प्रमाणपत्र किसी विश्वस्त प्राधिकारी का नहीं है."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"साइट का नाम, प्रमाणपत्र के नाम से मिलान नहीं करता."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"इस प्रमाणपत्र की समय सीमा समाप्त हो गई है."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"यह प्रमाणपत्र अभी तक मान्य नहीं है."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"इस प्रमाणपत्र में एक अमान्य दिनांक है."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"यह प्रमाणपत्र अमान्य है."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"अज्ञात प्रमाणपत्र त्रुटि."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-hr/strings.xml b/packages/CaptivePortalLogin/res/values-hr/strings.xml
index e44cd3b..11b1dd3 100644
--- a/packages/CaptivePortalLogin/res/values-hr/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-hr/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Mreža kojoj se pokušavate pridružiti ima sigurnosne poteškoće."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Na primjer, stranica za prijavu možda ne pripada prikazanoj organizaciji."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Ipak nastavi putem preglednika"</string>
+ <string name="ok" msgid="1509280796718850364">"U redu"</string>
+ <string name="page_info" msgid="4048529256302257195">"Informacije o stranici"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Adresa:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Upozorenje o sigurnosti"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Prikaži certifikat"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Ovaj certifikat ne potječe iz pouzdanog izvora."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Naziv web-lokacije ne podudara se s nazivom na certifikatu."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Ovaj je certifikat istekao."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Ovaj certifikat još nije važeći."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Ovaj certifikat ima nevažeći datum."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Ovaj certifikat nije valjan."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Nepoznata pogreška certifikata."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-hu/strings.xml b/packages/CaptivePortalLogin/res/values-hu/strings.xml
index f15fb49..145e2ab 100644
--- a/packages/CaptivePortalLogin/res/values-hu/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-hu/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Biztonsági problémák vannak azzal a hálózattal, amelyhez csatlakozni szeretne."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Például lehet, hogy a bejelentkezési oldal nem a megjelenített szervezethez tartozik."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Folytatás ennek ellenére böngészőn keresztül"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Oldaladatok"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Cím:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Biztonsági figyelmeztetés"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Tanúsítvány megtekintése"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Ez a tanúsítvány nem hiteles tanúsítványkibocsátótól származik."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"A webhely neve nem egyezik a tanúsítványon lévő névvel."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"A tanúsítvány lejárt."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"A tanúsítvány még nem érvényes."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"A tanúsítvány dátuma érvénytelen."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Ez a tanúsítvány érvénytelen."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Ismeretlen tanúsítványhiba."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-in/strings.xml b/packages/CaptivePortalLogin/res/values-in/strings.xml
index 10e3de6..4a335dd 100644
--- a/packages/CaptivePortalLogin/res/values-in/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-in/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Jaringan yang ingin Anda masuki mengalami masalah keamanan."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Misalnya, halaman masuk mungkin bukan milik organisasi yang ditampilkan."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Tetap lanjutkan melalui browser"</string>
+ <string name="ok" msgid="1509280796718850364">"Oke"</string>
+ <string name="page_info" msgid="4048529256302257195">"Info laman"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Alamat:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Peringatan sertifikat"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Lihat sertifikat"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Sertifikat ini tidak berasal dari otoritas tepercaya."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Nama situs tidak cocok dengan nama pada sertifikat."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Sertifikat ini telah kedaluwarsa."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Sertifikat ini belum valid."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Tanggal sertifikat ini tidak valid."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Sertifikat ini tidak valid."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Kesalahan sertifikat tak dikenal."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-it/strings.xml b/packages/CaptivePortalLogin/res/values-it/strings.xml
index a01a553..2cc4038 100644
--- a/packages/CaptivePortalLogin/res/values-it/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-it/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"La rete a cui stai tentando di accedere presenta problemi di sicurezza."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Ad esempio, la pagina di accesso potrebbe non appartenere all\'organizzazione indicata."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Continua comunque dal browser"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Info pagina"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Indirizzo:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Avviso di sicurezza"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Visualizza certificato"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Questo certificato non proviene da un\'autorità attendibile."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Il nome del sito non corrisponde al nome nel certificato."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Il certificato è scaduto."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Questo certificato non è ancora valido."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Questo certificato presenta una data non valida."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Questo certificato non è valido."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Errore certificato sconosciuto."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-iw/strings.xml b/packages/CaptivePortalLogin/res/values-iw/strings.xml
index 8e7915d..527e692 100644
--- a/packages/CaptivePortalLogin/res/values-iw/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-iw/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"יש בעיות אבטחה ברשת שאליה אתה מנסה להתחבר."</string>
<string name="ssl_error_example" msgid="647898534624078900">"לדוגמה, ייתכן שדף ההתחברות אינו שייך לארגון המוצג."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"המשך בכל זאת באמצעות דפדפן"</string>
+ <string name="ok" msgid="1509280796718850364">"אישור"</string>
+ <string name="page_info" msgid="4048529256302257195">"פרטי דף"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"כתובת:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"אזהרת אבטחה"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"הצג אישור"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"אישור זה אינו מגיע מרשות אמינה."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"שם האתר לא תואם לשם באישור."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"פג תוקפו של אישור זה."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"אישור זה אינו חוקי עדיין."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"לאישור זה יש תאריך בלתי חוקי."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"אישור זה אינו חוקי."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"שגיאת אישור לא ידועה."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-ja/strings.xml b/packages/CaptivePortalLogin/res/values-ja/strings.xml
index e275b95..bcc8686 100644
--- a/packages/CaptivePortalLogin/res/values-ja/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-ja/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"接続しようとしているネットワークにセキュリティの問題があります。"</string>
<string name="ssl_error_example" msgid="647898534624078900">"たとえば、ログインページが表示されている組織に属していない可能性があります。"</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"ブラウザから続行"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"ページ情報"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"アドレス:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"セキュリティ警告"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"証明書を表示"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"この証明書は信頼できる認証機関のものではありません。"</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"サイト名と証明書上の名前が一致しません。"</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"この証明書は有効期限切れです。"</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"この証明書はまだ有効ではありません。"</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"この証明書の日付は無効です。"</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"この証明書は無効です。"</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"不明な証明書エラーです。"</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-ko/strings.xml b/packages/CaptivePortalLogin/res/values-ko/strings.xml
index 75f2b48..7a7f7e0 100644
--- a/packages/CaptivePortalLogin/res/values-ko/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-ko/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"가입하려는 네트워크에 보안 문제가 있습니다."</string>
<string name="ssl_error_example" msgid="647898534624078900">"예를 들어 로그인 페이지가 표시된 조직에 속하지 않을 수 있습니다."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"브라우저를 통해 계속하기"</string>
+ <string name="ok" msgid="1509280796718850364">"확인"</string>
+ <string name="page_info" msgid="4048529256302257195">"페이지 정보"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"주소:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"보안 경고"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"인증서 보기"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"신뢰할 수 있는 인증 기관에서 발급한 인증서가 아닙니다."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"사이트 이름이 인증서에 있는 것과 일치하지 않습니다."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"인증서가 만료되었습니다."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"인증서가 아직 유효하지 않습니다."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"인증서 날짜가 유효하지 않습니다."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"인증서가 잘못되었습니다."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"알 수 없는 인증서 오류입니다."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-lt/strings.xml b/packages/CaptivePortalLogin/res/values-lt/strings.xml
index 17da83f..158f7ce 100644
--- a/packages/CaptivePortalLogin/res/values-lt/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-lt/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Kilo tinklo, prie kurio bandote prisijungti, problemų."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Pavyzdžiui, prisijungimo puslapis gali nepriklausyti rodomai organizacijai."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Vis tiek tęsti naudojant naršyklę"</string>
+ <string name="ok" msgid="1509280796718850364">"Gerai"</string>
+ <string name="page_info" msgid="4048529256302257195">"Puslapio informacija"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Adresas:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Saugos įspėjimas"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Žiūrėti sertifikatą"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Šį sertifikatą išdavė nepatikima įstaiga."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Svetainės pavadinimas neatitinka sertifikate nurodyto pavadinimo."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Šio sertifikato galiojimo laikas baigėsi."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Šis sertifikatas dar negalioja."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Šio sertifikato data netinkama."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Šis sertifikatas netinkamas."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Nežinoma sertifikato klaida."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-lv/strings.xml b/packages/CaptivePortalLogin/res/values-lv/strings.xml
index 95b8558..a42cb22 100644
--- a/packages/CaptivePortalLogin/res/values-lv/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-lv/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Tīklam, kuram mēģināt pievienoties, ir drošības problēmas."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Piemēram, pieteikšanās lapa, iespējams, nepieder norādītajai organizācijai."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Tik un tā turpināt, izmantojot pārlūkprogrammu"</string>
+ <string name="ok" msgid="1509280796718850364">"Labi"</string>
+ <string name="page_info" msgid="4048529256302257195">"Lapas informācija"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Adrese:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Drošības brīdinājums"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Skatīt sertifikātu"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Šo sertifikātu nav izsniegusi uzticama iestāde."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Vietnes nosaukums neatbilst nosaukumam sertifikātā."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Šī sertifikāta derīguma termiņš ir beidzies."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Šis sertifikāts vēl nav derīgs."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Šī sertifikāta datums nav derīgs."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Šis sertifikāts nav derīgs."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Nezināma sertifikāta kļūda."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-ms/strings.xml b/packages/CaptivePortalLogin/res/values-ms/strings.xml
index 933721a..aaa51c8 100644
--- a/packages/CaptivePortalLogin/res/values-ms/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-ms/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Rangkaian yang anda cuba sertai mempunyai isu keselamatan."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Contohnya, halaman log masuk mungkin bukan milik organisasi yang ditunjukkan."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Teruskan juga melalui penyemak imbas"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Maklumat halaman"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Alamat:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Amaran keselamatan"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Lihat sijil"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Sijil ini bukan daripada pihak berkuasa yang dipercayai."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Nama tapak tidak sepadan dengan nama pada sijil."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Sijil ini telah tamat tempoh."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Sijil ini belum lagi sah."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Sijil ini mempunyai tarikh yang tidak sah."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Sijil ini tidak sah."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Ralat sijil tidak diketahui."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-nb/strings.xml b/packages/CaptivePortalLogin/res/values-nb/strings.xml
index 0dd5b6c..29c23ed 100644
--- a/packages/CaptivePortalLogin/res/values-nb/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-nb/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Nettverket du prøver å logge på, har sikkerhetsproblemer."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Det er for eksempel mulig at påloggingssiden kanskje ikke tilhører organisasjonen som vises."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Fortsett likevel via nettleseren"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Sideinfo"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Adresse:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Sikkerhetsadvarsel"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Vis sertifikat"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Sertifikatet er ikke fra en pålitelig myndighet."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Navnet på nettstedet samsvarer ikke med navnet på sertifikatet."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Sertifikatet er utløpt."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Sertifikatet er ikke gyldig ennå."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Dette sertifikatet har en ugyldig dato."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Dette sertifikatet er ugyldig."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Ukjent sertifikatfeil."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-nl/strings.xml b/packages/CaptivePortalLogin/res/values-nl/strings.xml
index 1c59601..2cbca06 100644
--- a/packages/CaptivePortalLogin/res/values-nl/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-nl/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Het netwerk waarmee u verbinding probeert te maken, heeft beveiligingsproblemen."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Zo hoort de weergegeven inlogpagina misschien niet bij de weergegeven organisatie."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Toch doorgaan via browser"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Pagina-informatie"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Adres:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Beveiligingsmelding"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Certificaat weergeven"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Dit is geen certificaat van een vertrouwde autoriteit."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"De naam van deze site komt niet overeen met de naam op het certificaat."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Dit certificaat is verlopen."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Dit certificaat is nog niet geldig."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Dit certificaat heeft een ongeldige datum."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Dit certificaat is ongeldig."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Onbekende certificaatfout."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-pl/strings.xml b/packages/CaptivePortalLogin/res/values-pl/strings.xml
index 17f20df..9ba066e 100644
--- a/packages/CaptivePortalLogin/res/values-pl/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-pl/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"W sieci, z którą próbujesz się połączyć, występują problemy z zabezpieczeniami."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Na przykład strona logowania może nie należeć do wyświetlanej organizacji."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Kontynuuj mimo to w przeglądarce"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Informacje o stronie"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Adres:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Ostrzeżenie zabezpieczeń"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Wyświetl certyfikat"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Certyfikat nie pochodzi od zaufanego urzędu."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Nazwa witryny nie pasuje do nazwy na certyfikacie."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Ten certyfikat wygasł."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Certyfikat nie jest jeszcze ważny."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Certyfikat ma nieprawidłową datę."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Certyfikat jest nieprawidłowy."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Nieznany błąd certyfikatu"</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-pt-rPT/strings.xml b/packages/CaptivePortalLogin/res/values-pt-rPT/strings.xml
index 94b9d60..5bef235 100644
--- a/packages/CaptivePortalLogin/res/values-pt-rPT/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-pt-rPT/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"A rede à qual está a tentar aceder tem problemas de segurança."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Por exemplo, a página de início de sessão pode não pertencer à entidade apresentada."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Continuar mesmo assim através do navegador"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Informações da página"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Endereço:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Aviso de segurança"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Ver certificado"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Este certificado não pertence a uma autoridade fidedigna."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"O nome do Web site não corresponde ao nome constante no certificado."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Este certificado expirou."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Este certificado ainda não é válido."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Este certificado tem uma data inválida."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Este certificado é inválido."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Erro: certificado desconhecido."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-pt/strings.xml b/packages/CaptivePortalLogin/res/values-pt/strings.xml
index 3d1064c..ebe4148 100644
--- a/packages/CaptivePortalLogin/res/values-pt/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-pt/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"A rede à qual você está tentando se conectar tem problemas de segurança."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Por exemplo, a página de login pode não pertencer à organização mostrada."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Continuar mesmo assim pelo navegador"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Informações da página"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Endereço:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Aviso de segurança"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Visualizar certificado"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Este certificado não é de uma autoridade confiável."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"O nome do site não corresponde ao nome no certificado."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Este certificado expirou."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Este certificado ainda não é válido."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Este certificado tem uma data inválida."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Este certificado é inválido."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Erro de certificado desconhecido."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-ro/strings.xml b/packages/CaptivePortalLogin/res/values-ro/strings.xml
index cf1b6b5..e2e4eac 100644
--- a/packages/CaptivePortalLogin/res/values-ro/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-ro/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Rețeaua la care încercați să vă conectați are probleme de securitate."</string>
<string name="ssl_error_example" msgid="647898534624078900">"De exemplu, este posibil ca pagina de conectare să nu aparțină organizației afișate."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Continuați oricum prin browser"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Informaţii pagină"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Adresă:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Avertisment de securitate"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Vizualizaţi certificatul"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Acest certificat nu provine de la o autoritate de încredere."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Numele acestui site nu se potriveşte cu numele de pe certificat."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Acest certificat a expirat."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Acest certificat nu este încă valid."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Acest certificat are o dată nevalidă."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Acest certificat este nevalid."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Eroare de certificat necunoscută."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-ru/strings.xml b/packages/CaptivePortalLogin/res/values-ru/strings.xml
index 6966bcd..c0153e6 100644
--- a/packages/CaptivePortalLogin/res/values-ru/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-ru/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Сеть, к которой вы хотите подключиться, небезопасна."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Например, страница входа в аккаунт может быть фиктивной."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Игнорировать и открыть браузер"</string>
+ <string name="ok" msgid="1509280796718850364">"ОК"</string>
+ <string name="page_info" msgid="4048529256302257195">"Информация о странице"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Адрес:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Угроза безопасности"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Просмотреть сертификат"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Этот сертификат получен из ненадежных источников."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Название сайта не соответствует названию в сертификате."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Срок действия сертификата истек."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Сертификат еще не действителен."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Дата этого сертификата недействительна."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Этот сертификат недействителен."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Неизвестная ошибка сертификата."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-sk/strings.xml b/packages/CaptivePortalLogin/res/values-sk/strings.xml
index 54763be..8ba24b1 100644
--- a/packages/CaptivePortalLogin/res/values-sk/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-sk/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Sieť, ku ktorej sa pokúšate pripojiť, má problémy so zabezpečením"</string>
<string name="ssl_error_example" msgid="647898534624078900">"Napríklad prihlasovacia stránka nemusí patriť uvedenej organizácii."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Pokračovať pomocou prehliadača"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Informácie o stránke"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Adresa:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Upozornenie zabezpečenia"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Zobraziť certifikát"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Tento certifikát nepochádza od dôveryhodnej autority."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Názov stránky sa nezhoduje s názvom uvedeným v certifikáte."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Platnosť certifikátu skončila."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Tento certifikát zatiaľ nie je platný."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Tento certifikát má neplatný dátum."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Tento certifikát je neplatný."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Neznáma chyba certifikátu."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-sl/strings.xml b/packages/CaptivePortalLogin/res/values-sl/strings.xml
index 7dd0b37..b7d9a8a 100644
--- a/packages/CaptivePortalLogin/res/values-sl/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-sl/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Omrežje, ki se mu poskušate pridružiti, ima varnostne težave."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Stran za prijavo na primer morda ne pripada prikazani organizaciji."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Vseeno nadaljuj v brskalniku"</string>
+ <string name="ok" msgid="1509280796718850364">"V redu"</string>
+ <string name="page_info" msgid="4048529256302257195">"Podatki o strani"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Naslov:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Varnostno opozorilo"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Prikaži potrdilo"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Potrdila ni izdal zaupanja vreden overitelj."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Ime spletnega mesta se ne ujema z imenom na potrdilu."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Potrdilo je poteklo."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"To potrdilo še ni veljavno."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Potrdilo ima neveljaven datum."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"To potrdilo ni veljavno."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Neznana napaka potrdila."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-sr/strings.xml b/packages/CaptivePortalLogin/res/values-sr/strings.xml
index f604289..967c8ba 100644
--- a/packages/CaptivePortalLogin/res/values-sr/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-sr/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Мрежа којој покушавате да се придружите има безбедносних проблема."</string>
<string name="ssl_error_example" msgid="647898534624078900">"На пример, страница за пријављивање можда не припада приказаној организацији."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Ипак настави преко прегледача"</string>
+ <string name="ok" msgid="1509280796718850364">"Потврди"</string>
+ <string name="page_info" msgid="4048529256302257195">"Информације о страници"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Адреса:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Безбедносно упозорење"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Прикажи сертификат"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Овај сертификат не потиче од поузданог ауторитета."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Назив сајта се не подудара са називом на сертификату."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Овај сертификат је истекао."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Овај сертификат још увек није важећи."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Датум овог сертификата је неважећи."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Овај сертификат је неважећи."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Непозната грешка сертификата."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-sv/strings.xml b/packages/CaptivePortalLogin/res/values-sv/strings.xml
index 8cf7041..75356f0 100644
--- a/packages/CaptivePortalLogin/res/values-sv/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-sv/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Nätverket du försöker ansluta till har säkerhetsproblem."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Det kan t.ex. hända att inloggningssidan inte tillhör den organisation som visas."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Fortsätt ändå via webbläsaren"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Sidinformation"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Adress:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Säkerhetsvarning"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Visa certifikat"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Certifikatet kommer inte från en betrodd utfärdare."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Webbplatsens namn stämmer inte med namnet på certifikatet."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Certifikatet har upphört att gälla."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Certifikatet är inte giltigt än."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Det här certifikatet har ett ogiltigt datum."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Certifikatet är ogiltigt."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Okänt certifikatfel."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-sw/strings.xml b/packages/CaptivePortalLogin/res/values-sw/strings.xml
index 1c8b6e1..feb2dde 100644
--- a/packages/CaptivePortalLogin/res/values-sw/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-sw/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Mtandao unaojaribu kujiunga nao una matatizo ya usalama."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Kwa mfano, ukurasa wa kuingia katika akaunti unaweza usiwe unamilikiwa na shirika lililoonyeshwa."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Endelea hata hivyo kupitia kivinjari"</string>
+ <string name="ok" msgid="1509280796718850364">"Sawa"</string>
+ <string name="page_info" msgid="4048529256302257195">"Maelezo ya ukurasa"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Anwani:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Ilani ya usalama"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Tazama cheti"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Cheti hiki hakijatoka kwa mamlaka inayoaminika."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Jina la tovuti halilingani na jina lililo katika cheti."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Cheti hiki kimepitwa na muda"</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Cheti bado si halali."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Cheti hiki kina tarehe batili."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Hati hii ni batili."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Hitilafu isiyojulikana ya cheti."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-th/strings.xml b/packages/CaptivePortalLogin/res/values-th/strings.xml
index 9a3a626..11a2131 100644
--- a/packages/CaptivePortalLogin/res/values-th/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-th/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"เครือข่ายที่คุณพยายามเข้าร่วมมีปัญหาด้านความปลอดภัย"</string>
<string name="ssl_error_example" msgid="647898534624078900">"ตัวอย่างเช่น หน้าเข้าสู่ระบบอาจไม่ใช่ขององค์กรที่แสดงไว้"</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"ดำเนินการต่อผ่านเบราว์เซอร์"</string>
+ <string name="ok" msgid="1509280796718850364">"ตกลง"</string>
+ <string name="page_info" msgid="4048529256302257195">"ข้อมูลหน้าเว็บ"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"ที่อยู่:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"คำเตือนเกี่ยวกับความปลอดภัย"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"ดูใบรับรอง"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"ใบรับรองนี้ไม่ได้มาจากผู้ออกที่เชื่อถือได้"</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"ชื่อไซต์ไม่ตรงกับในใบรับรอง"</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"ใบรับรองนี้หมดอายุแล้ว"</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"ใบรับรองนี้ยังใช้งานไม่ได้"</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"ใบรับรองนี้มีวันที่ไม่ถูกต้อง"</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"ใบรับรองนี้ไม่ถูกต้อง"</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"ข้อผิดพลาดใบรับรองที่ไม่รู้จัก"</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-tl/strings.xml b/packages/CaptivePortalLogin/res/values-tl/strings.xml
index 565ef8f..07a2479 100644
--- a/packages/CaptivePortalLogin/res/values-tl/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-tl/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"May mga isyu sa seguridad ang network kung saan mo sinusubukang sumali."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Halimbawa, maaaring hindi sa organisasyong ipinapakita ang page sa pag-log in."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Magpatuloy pa rin sa pamamagitan ng browser"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Impormasyon ng pahina"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Address:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Babala sa seguridad"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Tingnan ang certificate"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Ang certificate ay hindi mula sa isang pinagkakatiwalaang kinauukulan."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Ang pangalan ng site ay hindi tumutugma sa pangalan sa certificate."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Nag-expire na ang certificate na ito."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Wala pang bisa ang certificate na ito."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Ang certificate ay mayroong di-wastong petsa."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Di-wasto ang certificate na ito."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Hindi kilalang error ng certificate."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-tr/strings.xml b/packages/CaptivePortalLogin/res/values-tr/strings.xml
index 73d2455..cdedd33 100644
--- a/packages/CaptivePortalLogin/res/values-tr/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-tr/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Katılmaya çalıştığınız ağda güvenlik sorunları var."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Örneğin, giriş sayfası, gösterilen kuruluşa ait olmayabilir."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Yine de tarayıcıyla devam et"</string>
+ <string name="ok" msgid="1509280796718850364">"Tamam"</string>
+ <string name="page_info" msgid="4048529256302257195">"Sayfa bilgileri"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Adres:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Güvenlik uyarısı"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Sertifikayı görüntüle"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Bu sertifika güvenilir bir yetkiliden değil."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Sitenin adı sertifika üzerindeki adla eşleşmiyor."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Bu sertifikanın süresi dolmuş."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Bu sertifika henüz geçerli değil."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Bu sertifikanın tarihi geçersiz."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Bu sertifika geçersiz."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Bilinmeyen sertifika hatası."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-uk/strings.xml b/packages/CaptivePortalLogin/res/values-uk/strings.xml
index 0e818d3..0f4cd16 100644
--- a/packages/CaptivePortalLogin/res/values-uk/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-uk/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"У мережі, до якої ви намагаєтеся під’єднатись, є проблеми з безпекою."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Наприклад, сторінка входу може не належати вказаній організації."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Усе одно продовжити у веб-переглядачі"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Інфо про стор."</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Адреса:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Застереж. про небезп."</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Переглянути сертиф."</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Сертифікат видано ненадійним центром сертифікації."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Назва сайту не збігається з назвою в сертифікаті."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Термін дії сертиф. завершився."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Цей сертифікат ще не дійсний."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Цей сертифікат має недійсну дату."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Цей сертифікат недійсний."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Помилка невідомого сертифіката."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-vi/strings.xml b/packages/CaptivePortalLogin/res/values-vi/strings.xml
index e51d2aa..9c702b9 100644
--- a/packages/CaptivePortalLogin/res/values-vi/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-vi/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Mạng mà bạn đang cố gắng tham gia có vấn đề về bảo mật."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Ví dụ, trang đăng nhập có thể không thuộc về tổ chức được hiển thị."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Vẫn tiếp tục qua trình duyệt"</string>
+ <string name="ok" msgid="1509280796718850364">"OK"</string>
+ <string name="page_info" msgid="4048529256302257195">"Thông tin trang"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Địa chỉ:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Cảnh báo bảo mật"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Xem chứng chỉ"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Chứng chỉ này không xuất phát từ tổ chức phát hành đáng tin cậy."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Tên của trang web không khớp với tên trên chứng chỉ."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Chứng chỉ này đã hết hạn."</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Chứng chỉ này chưa hợp lệ."</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Chứng chỉ này có ngày không hợp lệ."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Chứng chỉ này không hợp lệ."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Lỗi chứng chỉ không xác định."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-zh-rCN/strings.xml b/packages/CaptivePortalLogin/res/values-zh-rCN/strings.xml
index ce822e7..70c2a08 100644
--- a/packages/CaptivePortalLogin/res/values-zh-rCN/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-zh-rCN/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"您尝试加入的网络存在安全问题。"</string>
<string name="ssl_error_example" msgid="647898534624078900">"例如,登录页面可能并不属于页面上显示的单位。"</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"仍然通过浏览器继续操作"</string>
+ <string name="ok" msgid="1509280796718850364">"确定"</string>
+ <string name="page_info" msgid="4048529256302257195">"网页信息"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"网址:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"安全警告"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"查看证书"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"该证书并非来自可信的授权中心。"</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"网站的名称与证书上的名称不一致。"</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"该证书已过期。"</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"该证书尚未生效。"</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"该证书的日期无效。"</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"该证书无效。"</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"未知证书错误。"</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-zh-rHK/strings.xml b/packages/CaptivePortalLogin/res/values-zh-rHK/strings.xml
index 9010e1e..df1c700 100644
--- a/packages/CaptivePortalLogin/res/values-zh-rHK/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-zh-rHK/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"您正在嘗試加入的網絡有安全性問題。"</string>
<string name="ssl_error_example" msgid="647898534624078900">"例如,登入頁面並不屬於所顯示的機構。"</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"透過瀏覽器繼續"</string>
+ <string name="ok" msgid="1509280796718850364">"確定"</string>
+ <string name="page_info" msgid="4048529256302257195">"網頁資訊"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"地址:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"安全性警告"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"查看憑證"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"這個憑證並非由受信任的權威機構發出。"</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"網站名稱與憑證上的名稱不相符。"</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"這個憑證已過期。"</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"這個憑證尚未生效。"</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"此憑證的日期無效。"</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"此憑證是無效的。"</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"不明的憑證錯誤。"</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-zh-rTW/strings.xml b/packages/CaptivePortalLogin/res/values-zh-rTW/strings.xml
index 5b535e2..2a2e397 100644
--- a/packages/CaptivePortalLogin/res/values-zh-rTW/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-zh-rTW/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"你嘗試加入的網路有安全問題。"</string>
<string name="ssl_error_example" msgid="647898534624078900">"例如,登入網頁中顯示的機構可能並非該網頁實際隸屬的機構。"</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"透過瀏覽器繼續"</string>
+ <string name="ok" msgid="1509280796718850364">"確定"</string>
+ <string name="page_info" msgid="4048529256302257195">"頁面資訊"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"位址:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"安全性警告"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"檢視憑證"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"這個憑證並非來自信任的授權單位。"</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"網站名稱與憑證上的名稱不相符。"</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"此憑證已過期"</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"這個憑證尚未生效。"</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"這個憑證的日期無效。"</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"這個憑證無效。"</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"不明的憑證錯誤。"</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values-zu/strings.xml b/packages/CaptivePortalLogin/res/values-zu/strings.xml
index 866ba18..7943645 100644
--- a/packages/CaptivePortalLogin/res/values-zu/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-zu/strings.xml
@@ -9,4 +9,16 @@
<string name="ssl_error_warning" msgid="6653188881418638872">"Inethiwekhi ozama ukuyijoyina inezinkinga zokuvikela."</string>
<string name="ssl_error_example" msgid="647898534624078900">"Isibonelo, ikhasi lokungena ngemvume kungenzeka lingelenhlangano ebonisiwe."</string>
<string name="ssl_error_continue" msgid="6492718244923937110">"Qhubeka noma kunjalo ngesiphequluli"</string>
+ <string name="ok" msgid="1509280796718850364">"KULUNGILE"</string>
+ <string name="page_info" msgid="4048529256302257195">"Ulwazi lekhasi"</string>
+ <string name="page_info_address" msgid="2222306609532903254">"Ikheli:"</string>
+ <string name="ssl_security_warning_title" msgid="6607795404322797541">"Isexwayiso sokuvikeleka"</string>
+ <string name="ssl_error_view_certificate" msgid="1472768887529093862">"Buka isitifiketi"</string>
+ <string name="ssl_error_untrusted" msgid="7754507359360636447">"Lesi sitifiketi asiphumi embusweni othembekile."</string>
+ <string name="ssl_error_mismatch" msgid="3809794439740523641">"Igama lale ngosi alifani negama elikusitifiketi."</string>
+ <string name="ssl_error_expired" msgid="5739349389499575559">"Lesi sitifiketi siphelelwe yisikhathi"</string>
+ <string name="ssl_error_not_yet_valid" msgid="8193083327719048247">"Lesi sitifiketi asilungile okwamanje"</string>
+ <string name="ssl_error_date_invalid" msgid="3705563379257285534">"Lesi sitifiketi sinosuku olungalungile."</string>
+ <string name="ssl_error_invalid" msgid="9041704741505449967">"Lesi sitifiketi asilungile."</string>
+ <string name="ssl_error_unknown" msgid="5679243486524754571">"Iphutha lesitifiketi elingaziwa."</string>
</resources>
diff --git a/packages/CaptivePortalLogin/res/values/strings.xml b/packages/CaptivePortalLogin/res/values/strings.xml
index f486fe4..e9698db 100644
--- a/packages/CaptivePortalLogin/res/values/strings.xml
+++ b/packages/CaptivePortalLogin/res/values/strings.xml
@@ -9,5 +9,17 @@
<string name="ssl_error_warning">The network you’re trying to join has security issues.</string>
<string name="ssl_error_example">For example, the login page may not belong to the organization shown.</string>
<string name="ssl_error_continue">Continue anyway via browser</string>
+ <string name="ssl_error_untrusted">This certificate isn\'t from a trusted authority.</string>
+ <string name="ssl_error_mismatch">The name of the site doesn\'t match the name on the certificate.</string>
+ <string name="ssl_error_expired">This certificate has expired.</string>
+ <string name="ssl_error_not_yet_valid">This certificate isn\'t valid yet.</string>
+ <string name="ssl_error_date_invalid">This certificate has an invalid date.</string>
+ <string name="ssl_error_invalid">This certificate is invalid.</string>
+ <string name="ssl_error_unknown">Unknown certificate error.</string>
+ <string name="ssl_security_warning_title">Security warning</string>
+ <string name="ssl_error_view_certificate">View certificate</string>
+ <string name="ok">OK</string>
+ <string name="page_info_address">Address:</string>
+ <string name="page_info">Page info</string>
</resources>
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
index 0ba37ae..83084c5 100644
--- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
+++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
@@ -20,8 +20,10 @@
import static android.net.captiveportal.CaptivePortalProbeSpec.HTTP_LOCATION_HEADER_NAME;
import android.app.Activity;
+import android.app.AlertDialog;
import android.app.LoadedApk;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.CaptivePortal;
@@ -33,6 +35,7 @@
import android.net.Proxy;
import android.net.Uri;
import android.net.captiveportal.CaptivePortalProbeSpec;
+import android.net.http.SslCertificate;
import android.net.http.SslError;
import android.net.wifi.WifiInfo;
import android.os.Build;
@@ -42,8 +45,9 @@
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
-import android.util.TypedValue;
import android.util.SparseArray;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -52,8 +56,8 @@
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
-import android.webkit.WebView;
import android.webkit.WebViewClient;
+import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
@@ -276,6 +280,13 @@
@Override
public void onDestroy() {
super.onDestroy();
+ final WebView webview = (WebView) findViewById(R.id.webview);
+ if (webview != null) {
+ webview.stopLoading();
+ webview.setWebViewClient(null);
+ webview.setWebChromeClient(null);
+ webview.destroy();
+ }
if (mNetworkCallback != null) {
// mNetworkCallback is not null if mUrl is not null.
mCm.unregisterNetworkCallback(mNetworkCallback);
@@ -382,6 +393,7 @@
private static final String INTERNAL_ASSETS = "file:///android_asset/";
private final String mBrowserBailOutToken = Long.toString(new Random().nextLong());
+ private final String mCertificateOutToken = Long.toString(new Random().nextLong());
// How many Android device-independent-pixels per scaled-pixel
// dp/sp = (px/sp) / (px/dp) = (1/sp) / (1/dp)
private final float mDpPerSp = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 1,
@@ -397,6 +409,10 @@
return mPagesLoaded > 1;
}
+ private String mSslErrorTitle = null;
+ private SslErrorHandler mSslErrorHandler = null;
+ private SslError mSslError = null;
+
@Override
public void onPageStarted(WebView view, String urlString, Bitmap favicon) {
if (urlString.contains(mBrowserBailOutToken)) {
@@ -473,12 +489,16 @@
logMetricsEvent(MetricsEvent.CAPTIVE_PORTAL_LOGIN_ACTIVITY_SSL_ERROR);
final String sslErrorPage = makeSslErrorPage();
view.loadDataWithBaseURL(INTERNAL_ASSETS, sslErrorPage, "text/HTML", "UTF-8", null);
+ mSslErrorTitle = view.getTitle() == null ? "" : view.getTitle();
+ mSslErrorHandler = handler;
+ mSslError = error;
}
private String makeSslErrorPage() {
final String warningMsg = getString(R.string.ssl_error_warning);
final String exampleMsg = getString(R.string.ssl_error_example);
final String continueMsg = getString(R.string.ssl_error_continue);
+ final String certificateMsg = getString(R.string.ssl_error_view_certificate);
return String.join("\n",
"<html>",
"<head>",
@@ -516,13 +536,18 @@
" text-decoration:none;",
" text-transform:uppercase;",
" }",
+ " a.certificate {",
+ " margin-top:0px;",
+ " }",
" </style>",
"</head>",
"<body>",
" <p><img src=quantum_ic_warning_amber_96.png><br>",
" <div class=warn>" + warningMsg + "</div>",
" <div class=example>" + exampleMsg + "</div>",
- " <a href=" + mBrowserBailOutToken + ">" + continueMsg + "</a>",
+ " <a href=" + mBrowserBailOutToken + ">" + continueMsg + "</a><br>",
+ " <a class=certificate href=" + mCertificateOutToken + ">" + certificateMsg +
+ "</a>",
"</body>",
"</html>");
}
@@ -533,8 +558,50 @@
startActivity(new Intent(Intent.ACTION_DIAL, Uri.parse(url)));
return true;
}
+ if (url.contains(mCertificateOutToken) && mSslError != null) {
+ showSslAlertDialog(mSslErrorHandler, mSslError, mSslErrorTitle);
+ return true;
+ }
return false;
}
+ private void showSslAlertDialog(SslErrorHandler handler, SslError error, String title) {
+ final LayoutInflater factory = LayoutInflater.from(CaptivePortalLoginActivity.this);
+ final View sslWarningView = factory.inflate(R.layout.ssl_warning, null);
+
+ // Set Security certificate
+ setViewSecurityCertificate(sslWarningView.findViewById(R.id.certificate_layout), error);
+ ((TextView) sslWarningView.findViewById(R.id.ssl_error_type))
+ .setText(sslErrorName(error));
+ ((TextView) sslWarningView.findViewById(R.id.title)).setText(mSslErrorTitle);
+ ((TextView) sslWarningView.findViewById(R.id.address)).setText(error.getUrl());
+
+ AlertDialog sslAlertDialog = new AlertDialog.Builder(CaptivePortalLoginActivity.this)
+ .setTitle(R.string.ssl_security_warning_title)
+ .setView(sslWarningView)
+ .setPositiveButton(R.string.ok, (DialogInterface dialog, int whichButton) -> {
+ // handler.cancel is called via OnCancelListener.
+ dialog.cancel();
+ })
+ .setOnCancelListener((DialogInterface dialogInterface) -> handler.cancel())
+ .create();
+ sslAlertDialog.show();
+ }
+
+ private void setViewSecurityCertificate(LinearLayout certificateLayout, SslError error) {
+ SslCertificate cert = error.getCertificate();
+
+ View certificateView = cert.inflateCertificateView(CaptivePortalLoginActivity.this);
+ final LinearLayout placeholder = (LinearLayout) certificateView
+ .findViewById(com.android.internal.R.id.placeholder);
+ LayoutInflater factory = LayoutInflater.from(CaptivePortalLoginActivity.this);
+
+ TextView textView = (TextView) factory.inflate(
+ R.layout.ssl_error_msg, placeholder, false);
+ textView.setText(sslErrorMessage(error));
+ placeholder.addView(textView);
+
+ certificateLayout.addView(certificateView);
+ }
}
private class MyWebChromeClient extends WebChromeClient {
@@ -587,4 +654,18 @@
private static String sslErrorName(SslError error) {
return SSL_ERRORS.get(error.getPrimaryError(), "UNKNOWN");
}
+
+ private static final SparseArray<Integer> SSL_ERROR_MSGS = new SparseArray<>();
+ static {
+ SSL_ERROR_MSGS.put(SslError.SSL_NOTYETVALID, R.string.ssl_error_not_yet_valid);
+ SSL_ERROR_MSGS.put(SslError.SSL_EXPIRED, R.string.ssl_error_expired);
+ SSL_ERROR_MSGS.put(SslError.SSL_IDMISMATCH, R.string.ssl_error_mismatch);
+ SSL_ERROR_MSGS.put(SslError.SSL_UNTRUSTED, R.string.ssl_error_untrusted);
+ SSL_ERROR_MSGS.put(SslError.SSL_DATE_INVALID, R.string.ssl_error_date_invalid);
+ SSL_ERROR_MSGS.put(SslError.SSL_INVALID, R.string.ssl_error_invalid);
+ }
+
+ private static Integer sslErrorMessage(SslError error) {
+ return SSL_ERROR_MSGS.get(error.getPrimaryError(), R.string.ssl_error_unknown);
+ }
}
diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
index 1136684..60153fc 100644
--- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java
+++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
@@ -78,8 +78,6 @@
public class Assistant extends NotificationAssistantService {
private static final String TAG = "ExtAssistant";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- public static final boolean AUTO_DEMOTE_NOTIFICATIONS = SystemProperties.getBoolean(
- "debug.demote_notifs", false);
public static final boolean AGE_NOTIFICATIONS = SystemProperties.getBoolean(
"debug.age_notifs", false);
@@ -242,7 +240,8 @@
if (!smartReplies.isEmpty()) {
signals.putCharSequenceArrayList(Adjustment.KEY_SMART_REPLIES, smartReplies);
}
- if (AUTO_DEMOTE_NOTIFICATIONS) {
+ if (Settings.Secure.getInt(getContentResolver(),
+ Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL, 0) == 1) {
if (mNotificationCategorizer.shouldSilence(entry)) {
final int importance = entry.getImportance() < IMPORTANCE_LOW
? entry.getImportance() : IMPORTANCE_LOW;
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 4abcf73..c9ee5c8 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -541,14 +541,14 @@
}
@Override
- public Cursor querySearchDocuments(String rootId, String query, String[] projection)
+ public Cursor querySearchDocuments(String rootId, String[] projection, Bundle queryArgs)
throws FileNotFoundException {
final File parent;
synchronized (mRootsLock) {
parent = mRoots.get(rootId).path;
}
- return querySearchDocuments(parent, query, projection, Collections.emptySet());
+ return querySearchDocuments(parent, projection, Collections.emptySet(), queryArgs);
}
@Override
diff --git a/packages/SettingsLib/SettingsSpinner/res/values/styles.xml b/packages/SettingsLib/SettingsSpinner/res/values/styles.xml
index 8447b08..8af20e2 100644
--- a/packages/SettingsLib/SettingsSpinner/res/values/styles.xml
+++ b/packages/SettingsLib/SettingsSpinner/res/values/styles.xml
@@ -17,7 +17,7 @@
<resources>
<style name="SettingsSpinnerTitleBar">
- <item name="android:textAppearance">?android:attr/textAppearance</item>
+ <item name="android:textAppearance">?android:attr/textAppearanceButton</item>
<item name="android:paddingStart">16dp</item>
<item name="android:paddingEnd">36dp</item>
<item name="android:paddingTop">8dp</item>
diff --git a/packages/SettingsLib/res/drawable/list_divider_dark.xml b/packages/SettingsLib/res/drawable/list_divider_dark.xml
deleted file mode 100644
index 5773d9e..0000000
--- a/packages/SettingsLib/res/drawable/list_divider_dark.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<shape
- xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="#64000000" />
- <size
- android:height="1dp"
- android:width="1dp" />
-</shape>
diff --git a/packages/SettingsLib/res/layout/preference_two_target_divider.xml b/packages/SettingsLib/res/layout/preference_two_target_divider.xml
index 60efed4..b81dd83 100644
--- a/packages/SettingsLib/res/layout/preference_two_target_divider.xml
+++ b/packages/SettingsLib/res/layout/preference_two_target_divider.xml
@@ -27,5 +27,5 @@
<View
android:layout_width="1dp"
android:layout_height="match_parent"
- android:background="@drawable/list_divider_dark" />
+ android:background="?android:attr/listDivider" />
</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values/colors.xml b/packages/SettingsLib/res/values/colors.xml
index 02b7ea6..66bbb3a 100644
--- a/packages/SettingsLib/res/values/colors.xml
+++ b/packages/SettingsLib/res/values/colors.xml
@@ -18,4 +18,5 @@
<color name="disabled_text_color">#66000000</color> <!-- 38% black -->
<color name="usage_graph_dots">@*android:color/tertiary_device_default_settings</color>
+ <color name="list_divider_color">#64000000</color>
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 0dbc037..2f082b9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -152,7 +152,11 @@
private boolean mNetworkScoringUiEnabled;
private long mMaxSpeedLabelScoreCacheAge;
-
+ private static final String WIFI_SECURITY_PSK = "PSK";
+ private static final String WIFI_SECURITY_EAP = "EAP";
+ private static final String WIFI_SECURITY_SAE = "SAE";
+ private static final String WIFI_SECURITY_OWE = "OWE";
+ private static final String WIFI_SECURITY_SUITE_B_192 = "SUITE_B_192";
@VisibleForTesting
Scanner mScanner;
@@ -505,13 +509,18 @@
* {@link #updateAccessPoints(List, List)}.
*/
private void fetchScansAndConfigsAndUpdateAccessPoints() {
- final List<ScanResult> newScanResults = mWifiManager.getScanResults();
+ List<ScanResult> newScanResults = mWifiManager.getScanResults();
+
+ // Filter all unsupported networks from the scan result list
+ final List<ScanResult> filteredScanResults =
+ filterScanResultsByCapabilities(newScanResults);
+
if (isVerboseLoggingEnabled()) {
- Log.i(TAG, "Fetched scan results: " + newScanResults);
+ Log.i(TAG, "Fetched scan results: " + filteredScanResults);
}
List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
- updateAccessPoints(newScanResults, configs);
+ updateAccessPoints(filteredScanResults, configs);
}
/** Update the internal list of access points. */
@@ -937,4 +946,49 @@
mListener.onAccessPointsChanged();
}
+
+ /**
+ * Filters unsupported networks from scan results. New WPA3 networks and OWE networks
+ * may not be compatible with the device HW/SW.
+ * @param scanResults List of scan results
+ * @return List of filtered scan results based on local device capabilities
+ */
+ private List<ScanResult> filterScanResultsByCapabilities(List<ScanResult> scanResults) {
+ if (scanResults == null) {
+ return null;
+ }
+
+ // Get and cache advanced capabilities
+ final boolean isOweSupported = mWifiManager.isOweSupported();
+ final boolean isSaeSupported = mWifiManager.isWpa3SaeSupported();
+ final boolean isSuiteBSupported = mWifiManager.isWpa3SuiteBSupported();
+
+ List<ScanResult> filteredScanResultList = new ArrayList<>();
+
+ // Iterate through the list of scan results and filter out APs which are not
+ // compatible with our device.
+ for (ScanResult scanResult : scanResults) {
+ if (scanResult.capabilities.contains(WIFI_SECURITY_PSK)) {
+ // All devices (today) support RSN-PSK or WPA-PSK
+ // Add this here because some APs may support both PSK and SAE and the check
+ // below will filter it out.
+ filteredScanResultList.add(scanResult);
+ continue;
+ }
+
+ if ((scanResult.capabilities.contains(WIFI_SECURITY_SUITE_B_192) && !isSuiteBSupported)
+ || (scanResult.capabilities.contains(WIFI_SECURITY_SAE) && !isSaeSupported)
+ || (scanResult.capabilities.contains(WIFI_SECURITY_OWE) && !isOweSupported)) {
+ if (isVerboseLoggingEnabled()) {
+ Log.v(TAG, "filterScanResultsByCapabilities: Filtering SSID "
+ + scanResult.SSID + " with capabilities: " + scanResult.capabilities);
+ }
+ } else {
+ // Safe to add
+ filteredScanResultList.add(scanResult);
+ }
+ }
+
+ return filteredScanResultList;
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java
index b6ac467..50044f2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java
@@ -80,7 +80,8 @@
}
@Test
- public void getHistoricalUsageLevel_noNetworkSession_shouldReturnNegative1() {
+ public void getHistoricalUsageLevel_v1_noNetworkSession_shouldReturnNegative1() {
+ FeatureFlagUtils.setEnabled(mContext, DataUsageController.DATA_USAGE_V2, false);
doReturn(null).when(mController).getSession();
assertThat(mController.getHistoricalUsageLevel(null /* template */)).isEqualTo(-1L);
@@ -88,7 +89,8 @@
}
@Test
- public void getHistoriclUsageLevel_noUsageData_shouldReturn0() {
+ public void getHistoriclUsageLevel_v1_noUsageData_shouldReturn0() {
+ FeatureFlagUtils.setEnabled(mContext, DataUsageController.DATA_USAGE_V2, false);
doReturn(mSession).when(mController).getSession();
assertThat(mController.getHistoricalUsageLevel(NetworkTemplate.buildTemplateWifiWildcard()))
@@ -97,7 +99,8 @@
}
@Test
- public void getHistoricalUsageLevel_hasUsageData_shouldReturnTotalUsage() {
+ public void getHistoricalUsageLevel_v1_hasUsageData_shouldReturnTotalUsage() {
+ FeatureFlagUtils.setEnabled(mContext, DataUsageController.DATA_USAGE_V2, false);
doReturn(mSession).when(mController).getSession();
final long receivedBytes = 743823454L;
final long transmittedBytes = 16574289L;
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 8aced61..0b9b27f 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -125,6 +125,7 @@
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.RESET_FINGERPRINT_LOCKOUT" />
<uses-permission android:name="android.permission.MANAGE_SLICE_PERMISSIONS" />
+ <uses-permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS" />
<!-- Needed for WallpaperManager.clear in ImageWallpaper.updateWallpaperLocked -->
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
index b51ad1c..51f6a4b 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
@@ -20,8 +20,8 @@
<!-- This is a view that shows general status information in Keyguard. -->
<com.android.keyguard.KeyguardSliceView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_marginStart="16dp"
- android:layout_marginEnd="16dp"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
diff --git a/packages/SystemUI/res/drawable/ic_home_button_outline.xml b/packages/SystemUI/res/drawable/ic_home_button_outline.xml
new file mode 100644
index 0000000..5bf345d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_home_button_outline.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright (C) 2018 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="28dp"
+ android:height="10dp"
+ android:viewportWidth="28"
+ android:viewportHeight="10">
+ <path
+ android:pathData="M23,1H5C2.7909,1 1,2.7909 1,5C1,7.2091 2.7909,9 5,9H23C25.2091,9 27,7.2091 27,5C27,2.7909 25.2091,1 23,1ZM5,0C2.2386,0 0,2.2386 0,5C0,7.7614 2.2386,10 5,10H23C25.7614,10 28,7.7614 28,5C28,2.2386 25.7614,0 23,0H5Z"
+ android:fillColor="?attr/wallpaperTextColor"
+ android:fillType="evenOdd"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_notification_block.xml b/packages/SystemUI/res/drawable/ic_notification_block.xml
new file mode 100644
index 0000000..572e97b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_notification_block.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M12.0,2.0C6.48,2.0 2.0,6.48 2.0,12.0s4.48,10.0 10.0,10.0 10.0,-4.48 10.0,-10.0S17.52,2.0 12.0,2.0zM4.0,12.0c0.0,-4.42 3.58,-8.0 8.0,-8.0 1.85,0.0 3.5,0.63 4.9,1.69L5.69,16.9C4.63,15.55 4.0,13.85 4.0,12.0zm8.0,8.0c-1.85,0.0 -3.55,-0.63 -4.9,-1.69L18.31,7.1C19.37,8.45 20.0,10.15 20.0,12.0c0.0,4.42 -3.58,8.0 -8.0,8.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_notifications_alert.xml b/packages/SystemUI/res/drawable/ic_notifications_alert.xml
new file mode 100644
index 0000000..eb7b8ee
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_notifications_alert.xml
@@ -0,0 +1,24 @@
+<!--
+Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M7.58 4.08L6.15 2.65C3.75 4.48 2.17 7.3 2.03 10.5h2c.15-2.65 1.51-4.97 3.55-6.42zm12.39 6.42h2c-.15-3.2-1.73-6.02-4.12-7.85l-1.42 1.43c2.02 1.45 3.39 3.77 3.54 6.42zM18 11c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2v-5zm-6 11c.14 0 .27-.01.4-.04.65-.14 1.18-.58 1.44-1.18.1-.24.15-.5.15-.78h-4c.01 1.1.9 2 2.01 2z"
+ android:fillColor="#FF000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_notifications_silence.xml b/packages/SystemUI/res/drawable/ic_notifications_silence.xml
new file mode 100644
index 0000000..ff136eb
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_notifications_silence.xml
@@ -0,0 +1,28 @@
+<!--
+Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M0 0h24v24H0z"
+ />
+ <path
+ android:pathData="M20 18.69L7.84 6.14 5.27 3.49 4 4.76l2.8 2.8v.01c-.52.99-.8 2.16-.8 3.42v5l-2 2v1h13.73l2 2L21 19.72l-1-1.03zM12 22c1.11 0 2-.89 2-2h-4c0 1.11.89 2 2 2zm6-7.32V11c0-3.08-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68c-.15.03-.29.08-.42.12-.1.03-.2.07-.3.11h-.01c-.01 0-.01 0-.02.01-.23.09-.46.2-.68.31 0 0-.01 0-.01.01L18 14.68z"
+ android:fillColor="#FF000000"
+ />
+</vector>
diff --git a/packages/SystemUI/res/drawable/privacy_chip_bg.xml b/packages/SystemUI/res/drawable/privacy_chip_bg.xml
index 8247c27..36d0659 100644
--- a/packages/SystemUI/res/drawable/privacy_chip_bg.xml
+++ b/packages/SystemUI/res/drawable/privacy_chip_bg.xml
@@ -16,7 +16,7 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="#bbbbbb" />
+ <solid android:color="#4a4a4a" />
<padding android:padding="@dimen/ongoing_appops_chip_bg_padding" />
<corners android:radius="@dimen/ongoing_appops_chip_bg_corner_radius" />
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/stat_sys_camera.xml b/packages/SystemUI/res/drawable/stat_sys_camera.xml
new file mode 100644
index 0000000..eb3e963
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_camera.xml
@@ -0,0 +1,31 @@
+<?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.
+*/
+-->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetLeft="3dp"
+ android:insetRight="3dp">
+ <vector
+ android:width="17dp"
+ android:height="17dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FFF"
+ android:pathData="M20,5h-3.17L15,3H9L7.17,5H4C2.9,5 2,5.9 2,7v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V7C22,5.9 21.1,5 20,5zM20,19H4V7h16V19zM12,9c-2.21,0 -4,1.79 -4,4c0,2.21 1.79,4 4,4s4,-1.79 4,-4C16,10.79 14.21,9 12,9z"/>
+ </vector>
+</inset>
diff --git a/packages/SystemUI/res/drawable/stat_sys_mic_none.xml b/packages/SystemUI/res/drawable/stat_sys_mic_none.xml
new file mode 100644
index 0000000..d6bdf9f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_mic_none.xml
@@ -0,0 +1,30 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="18dp"
+ android:height="18dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FFF"
+ android:pathData="M12,14c1.66,0 3,-1.34 3,-3V5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6C9,12.66 10.34,14 12,14zM11,5c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v6c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1V5z"/>
+ <path
+ android:fillColor="#FFF"
+ android:pathData="M17,11c0,2.76 -2.24,5 -5,5s-5,-2.24 -5,-5H5c0,3.53 2.61,6.43 6,6.92V21h2v-3.08c3.39,-0.49 6,-3.39 6,-6.92H17z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
index ddefb6a..cbdd51b 100644
--- a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
@@ -18,11 +18,14 @@
<com.android.systemui.privacy.OngoingPrivacyChip
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/privacy_chip"
- android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:layout_margin="@dimen/ongoing_appops_chip_margin"
+ android:layout_width="wrap_content"
+ android:layout_marginLeft="@dimen/ongoing_appops_chip_margin"
+ android:layout_marginRight="@dimen/ongoing_appops_chip_margin"
+ android:layout_marginTop="@dimen/ongoing_appops_top_chip_margin"
+ android:layout_marginBottom="@dimen/ongoing_appops_top_chip_margin"
android:gravity="center_vertical|center_horizontal"
- android:layout_gravity="center_vertical|end"
+ android:layout_gravity="center_vertical|start"
android:orientation="horizontal"
android:paddingStart="@dimen/ongoing_appops_chip_side_padding"
android:paddingEnd="@dimen/ongoing_appops_chip_side_padding"
@@ -38,12 +41,17 @@
/>
<TextView
- android:id="@+id/app_name"
+ android:id="@+id/text_container"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:singleLine="true"
android:ellipsize="end"
+ android:lines="1"
android:layout_gravity="center_vertical|end"
android:gravity="center_vertical"
+ android:textAppearance="@style/TextAppearance.StatusBar.Clock"
+ android:textColor="@color/status_bar_clock_color"
+ android:layout_marginStart="@dimen/ongoing_appops_chip_icon_margin"
+ android:layout_marginEnd="@dimen/ongoing_appops_chip_icon_margin"
/>
</com.android.systemui.privacy.OngoingPrivacyChip>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_dialog_content.xml b/packages/SystemUI/res/layout/ongoing_privacy_dialog_content.xml
index b5e24a0..2f7d486 100644
--- a/packages/SystemUI/res/layout/ongoing_privacy_dialog_content.xml
+++ b/packages/SystemUI/res/layout/ongoing_privacy_dialog_content.xml
@@ -29,22 +29,30 @@
android:orientation="vertical"
android:padding="@dimen/ongoing_appops_dialog_content_padding">
- <LinearLayout
- android:id="@+id/icons_container"
+ <TextView
+ android:id="@+id/title"
android:layout_width="match_parent"
- android:layout_height="@dimen/ongoing_appops_dialog_icon_height"
- android:orientation="horizontal"
+ android:layout_height="wrap_content"
android:gravity="center"
- android:importantForAccessibility="no"
+ android:textDirection="locale"
+ android:textAppearance="@style/TextAppearance.AppOpsDialog.Title"
+ android:textColor="@*android:color/text_color_primary"
+ android:paddingStart="@dimen/ongoing_appops_dialog_title_padding"
+ android:paddingEnd="@dimen/ongoing_appops_dialog_title_padding"
+ android:paddingBottom="@dimen/ongoing_appops_dialog_sep"
/>
<LinearLayout
- android:id="@+id/text_container"
+ android:id="@+id/items_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="start"
/>
+
+ <include android:id="@+id/overflow" layout="@layout/ongoing_privacy_dialog_item"
+ android:visibility="gone" />
+
</LinearLayout>
</ScrollView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_dialog_item.xml b/packages/SystemUI/res/layout/ongoing_privacy_dialog_item.xml
new file mode 100644
index 0000000..f05f7ba
--- /dev/null
+++ b/packages/SystemUI/res/layout/ongoing_privacy_dialog_item.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fillViewport="true"
+ android:orientation="horizontal"
+ android:layout_marginTop="@dimen/ongoing_appops_dialog_text_margin"
+ android:focusable="true" >
+
+ <ImageView
+ android:id="@+id/app_icon"
+ android:layout_height="@dimen/ongoing_appops_dialog_icon_height"
+ android:layout_width="@dimen/ongoing_appops_dialog_icon_height"
+ />
+
+ <TextView
+ android:id="@+id/app_name"
+ android:layout_height="@dimen/ongoing_appops_dialog_icon_height"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:gravity="bottom|start"
+ android:textDirection="locale"
+ android:textAppearance="@style/TextAppearance.AppOpsDialog.Item"
+ android:textColor="@*android:color/text_color_primary"
+ android:paddingStart="@dimen/ongoing_appops_dialog_text_padding"
+ android:paddingEnd="@dimen/ongoing_appops_dialog_text_padding"
+
+ />
+
+ <LinearLayout
+ android:id="@+id/icons"
+ android:layout_height="@dimen/ongoing_appops_dialog_icon_height"
+ android:layout_width="wrap_content"
+ android:gravity="end"
+ android:visibility="gone"
+ />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index f554150..890bf5d 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -62,7 +62,7 @@
android:layout_weight="1"
android:layout_marginEnd="32dp"
android:ellipsize="marquee"
- android:textAppearance="@style/TextAppearance.QS.TileLabel"
+ android:textAppearance="@style/TextAppearance.QS.CarrierInfo"
android:textColor="?android:attr/textColorPrimary"
android:textDirection="locale"
android:singleLine="true" />
diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml
index 980442c..f34161e 100644
--- a/packages/SystemUI/res/layout/qs_tile_label.xml
+++ b/packages/SystemUI/res/layout/qs_tile_label.xml
@@ -79,7 +79,7 @@
android:padding="0dp"
android:visibility="gone"
android:gravity="center"
- android:textAppearance="@style/TextAppearance.QS.TileLabel"
+ android:textAppearance="@style/TextAppearance.QS.TileLabel.Secondary"
android:textColor="?android:attr/textColorSecondary"/>
<View
diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
index e7f2c51..22b8d2f 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
@@ -59,7 +59,7 @@
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="horizontal"
- android:gravity="center_vertical|end">
+ android:gravity="center_vertical|end" >
<include layout="@layout/ongoing_privacy_chip" />
@@ -67,6 +67,7 @@
android:id="@+id/battery"
android:layout_height="match_parent"
android:layout_width="wrap_content"
- android:gravity="center_vertical|end" />
+ android:gravity="center_vertical|end"
+ android:layout_gravity="center_vertical|end" />
</LinearLayout>
</LinearLayout>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index bb0c6f6..df858f0 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -34,4 +34,5 @@
<bool name="quick_settings_wide">true</bool>
<dimen name="qs_detail_margin_top">0dp</dimen>
<dimen name="qs_paged_tile_layout_padding_bottom">0dp</dimen>
+ <dimen name="ongoing_appops_top_chip_margin">2dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 0e41a7f..07628c6 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -369,6 +369,7 @@
<dimen name="qs_page_indicator_height">8dp</dimen>
<dimen name="qs_tile_icon_size">24dp</dimen>
<dimen name="qs_tile_text_size">12sp</dimen>
+ <dimen name="qs_carrier_info_text_size">14sp</dimen>
<dimen name="qs_tile_divider_height">1dp</dimen>
<dimen name="qs_panel_padding">16dp</dimen>
<dimen name="qs_dual_tile_height">112dp</dimen>
@@ -939,18 +940,47 @@
that just start below the notch. -->
<dimen name="display_cutout_touchable_region_size">12dp</dimen>
+ <!-- Padding below Ongoing App Ops dialog title -->
+ <dimen name="ongoing_appops_dialog_sep">16dp</dimen>
+ <!--Padding around text items in Ongoing App Ops dialog -->
+ <dimen name="ongoing_appops_dialog_text_padding">16dp</dimen>
<!-- Height of icons in Ongoing App Ops dialog. Both App Op icon and application icon -->
- <dimen name="ongoing_appops_dialog_icon_height">48dp</dimen>
+ <dimen name="ongoing_appops_dialog_icon_height">28dp</dimen>
<!-- Margin between text lines in Ongoing App Ops dialog -->
<dimen name="ongoing_appops_dialog_text_margin">15dp</dimen>
+ <!-- Side padding of title in Ongoing App Ops dialog -->
+ <dimen name="ongoing_appops_dialog_title_padding">10dp</dimen>
<!-- Padding around Ongoing App Ops dialog content -->
<dimen name="ongoing_appops_dialog_content_padding">24dp</dimen>
- <!-- Margins around the Ongoing App Ops chip. In landscape, the side margins are 0 -->
+ <!-- Side margins around the Ongoing App Ops chip-->
<dimen name="ongoing_appops_chip_margin">12dp</dimen>
+ <!-- Top and bottom margins around the Ongoing App Ops chip -->
+ <dimen name="ongoing_appops_top_chip_margin">12dp</dimen>
<!-- Start and End padding for Ongoing App Ops chip -->
<dimen name="ongoing_appops_chip_side_padding">6dp</dimen>
<!-- Padding between background of Ongoing App Ops chip and content -->
- <dimen name="ongoing_appops_chip_bg_padding">4dp</dimen>
+ <dimen name="ongoing_appops_chip_bg_padding">0dp</dimen>
+ <!-- Margin between icons of Ongoing App Ops chip -->
+ <dimen name="ongoing_appops_chip_icon_margin">4dp</dimen>
+ <!-- Icon size of Ongoing App Ops chip -->
+ <dimen name="ongoing_appops_chip_icon_size">18dp</dimen>
<!-- Radius of Ongoing App Ops chip corners -->
<dimen name="ongoing_appops_chip_bg_corner_radius">12dp</dimen>
+ <!-- Text size for Ongoing App Ops dialog title -->
+ <dimen name="ongoing_appops_dialog_title_size">24sp</dimen>
+ <!-- Text size for Ongoing App Ops dialog items -->
+ <dimen name="ongoing_appops_dialog_item_size">20sp</dimen>
+
+ <!-- How much a bubble is elevated -->
+ <dimen name="bubble_elevation">8dp</dimen>
+ <!-- Padding between bubbles when displayed in expanded state -->
+ <dimen name="bubble_padding">8dp</dimen>
+ <!-- Padding around the view displayed when the bubble is expanded -->
+ <dimen name="bubble_expanded_view_padding">8dp</dimen>
+ <!-- Size of the collapsed bubble -->
+ <dimen name="bubble_size">56dp</dimen>
+ <!-- Size of an icon displayed within the bubble -->
+ <dimen name="bubble_icon_size">24dp</dimen>
+ <!-- Default height of the expanded view shown when the bubble is expanded -->
+ <dimen name="bubble_expanded_default_height">400dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 50454fc..4a0bc9b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2250,39 +2250,48 @@
app for debugging. Will not be seen by users. [CHAR LIMIT=20] -->
<string name="heap_dump_tile_name">Dump SysUI Heap</string>
+ <!-- Text on chip for multiple apps using a single app op [CHAR LIMIT=10] -->
+ <string name="ongoing_privacy_chip_multiple_apps"><xliff:g id="num_apps" example="3">%d</xliff:g> apps</string>
+
<!-- Content description for ongoing privacy chip. Use with a single app [CHAR LIMIT=NONE]-->
<string name="ongoing_privacy_chip_content_single_app"><xliff:g id="app" example="Example App">%1$s</xliff:g> is using your <xliff:g id="types_list" example="camera, location">%2$s</xliff:g>.</string>
<!-- Content description for ongoing privacy chip. Use with multiple apps [CHAR LIMIT=NONE]-->
<string name="ongoing_privacy_chip_content_multiple_apps">Applications are using your <xliff:g id="types_list" example="camera, location">%s</xliff:g>.</string>
- <!-- Action on Ongoing Privacy Dialog to open application [CHAR LIMIT=10]-->
- <string name="ongoing_privacy_dialog_open_app">Open app</string>
+ <!-- Content description for ongoing privacy chip. Use with multiple apps using same app op[CHAR LIMIT=NONE]-->
+ <string name="ongoing_privacy_chip_content_multiple_apps_single_op"><xliff:g id="num_apps" example="3">%1$d</xliff:g> applications are using your <xliff:g id="type" example="camera">%2$s</xliff:g>.</string>
<!-- Action on Ongoing Privacy Dialog to dismiss [CHAR LIMIT=10]-->
<string name="ongoing_privacy_dialog_cancel">Cancel</string>
- <!-- Action on Ongoing Privacy Dialog to dismiss [CHAR LIMIT=10]-->
- <string name="ongoing_privacy_dialog_okay">Okay</string>
+ <!-- Action on Ongoing Privacy Dialog to open privacy hub [CHAR LIMIT=15]-->
+ <string name="ongoing_privacy_dialog_open_settings">View details</string>
- <!-- Action on Ongoing Privacy Dialog to open privacy hub [CHAR LIMIT=10]-->
- <string name="ongoing_privacy_dialog_open_settings">Settings</string>
+ <!-- Text for item in Ongoing Privacy Dialog title when only one app is using app ops [CHAR LIMIT=NONE] -->
+ <string name="ongoing_privacy_dialog_single_app_title">App using your <xliff:g id="types_list" example="camera( and location)">%s</xliff:g></string>
- <!-- Text for item in Ongoing Privacy Dialog when only one app is using a particular type of app op [CHAR LIMIT=NONE] -->
- <string name="ongoing_privacy_dialog_app_item"><xliff:g id="app" example="Example App">%1$s</xliff:g> is using your <xliff:g id="type" example="camera">%2$s</xliff:g> for the last <xliff:g id="time" example="3">%3$d</xliff:g> min</string>
+ <!-- Text for item in Ongoing Privacy Dialog title when multiple apps is using app ops [CHAR LIMIT=NONE] -->
+ <string name="ongoing_privacy_dialog_multiple_apps_title">Apps using your <xliff:g id="types_list" example="camera( and location)">%s</xliff:g></string>
- <!-- Text for item in Ongoing Privacy Dialog when only multiple apps are using a particular type of app op [CHAR LIMIT=NONE] -->
- <string name="ongoing_privacy_dialog_apps_item"><xliff:g id="apps" example="Camera, Phone">%1$s</xliff:g> are using your <xliff:g id="type" example="camera">%2$s</xliff:g></string>
+ <!-- Separator for types. Include spaces before and after if needed [CHAR LIMIT=10] -->
+ <string name="ongoing_privacy_dialog_separator">,\u0020</string>
- <!-- Text for Ongoing Privacy Dialog when a single app is using app ops [CHAR LIMIT=NONE] -->
- <string name="ongoing_privacy_dialog_single_app"><xliff:g id="app" example="Example App">%1$s</xliff:g> is using your <xliff:g id="types_list" example="camera, location">%2$s</xliff:g></string>
+ <!-- Separator for types, before last type. Include spaces before and after if needed [CHAR LIMIT=10] -->
+ <string name="ongoing_privacy_dialog_last_separator">\u0020and\u0020</string>
- <!-- Text for camera app op [CHAR LIMIT=12]-->
+ <!-- Text for camera app op [CHAR LIMIT=20]-->
<string name="privacy_type_camera">camera</string>
- <!-- Text for location app op [CHAR LIMIT=12]-->
+ <!-- Text for location app op [CHAR LIMIT=20]-->
<string name="privacy_type_location">location</string>
- <!-- Text for microphone app op [CHAR LIMIT=12]-->
+ <!-- Text for microphone app op [CHAR LIMIT=20]-->
<string name="privacy_type_microphone">microphone</string>
+
+ <!-- Text for indicating extra apps using app ops [CHAR LIMIT=NONE] -->
+ <plurals name="ongoing_privacy_dialog_overflow_text">
+ <item quantity="one"><xliff:g id="num_apps" example="1">%d</xliff:g> other app</item>
+ <item quantity="other"><xliff:g id="num_apps" example="3">%d</xliff:g> other app</item>
+ </plurals>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 6244e1c..e9aa1b6 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -125,7 +125,7 @@
<style name="TextAppearance.StatusBar.Clock" parent="@*android:style/TextAppearance.StatusBar.Icon">
<item name="android:textSize">@dimen/status_bar_clock_size</item>
- <item name="android:fontFamily">sans-serif-medium</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
<item name="android:textColor">@color/status_bar_clock_color</item>
</style>
@@ -135,7 +135,7 @@
<style name="TextAppearance.StatusBar.Expanded.Clock">
<item name="android:textSize">@dimen/qs_time_expanded_size</item>
- <item name="android:fontFamily">sans-serif-medium</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textStyle">normal</item>
</style>
@@ -240,9 +240,31 @@
<style name="TextAppearance.QS.TileLabel">
<item name="android:textSize">@dimen/qs_tile_text_size</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ </style>
+
+ <style name="TextAppearance.QS.TileLabel.Secondary">
+ <item name="android:textSize">@dimen/qs_tile_text_size</item>
<item name="android:fontFamily">sans-serif</item>
</style>
+ <style name="TextAppearance.QS.CarrierInfo">
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ <item name="android:textSize">@dimen/qs_carrier_info_text_size</item>
+ </style>
+
+ <style name="TextAppearance.AppOpsDialog" />
+
+ <style name="TextAppearance.AppOpsDialog.Title">
+ <item name="android:textSize">@dimen/ongoing_appops_dialog_title_size</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+ </style>
+
+ <style name="TextAppearance.AppOpsDialog.Item">
+ <item name="android:textSize">@dimen/ongoing_appops_dialog_item_size</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ </style>
+
<style name="BaseBrightnessDialogContainer" parent="@style/Theme.SystemUI">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplier.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplier.java
index dc4eb3b..65c5220 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplier.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplier.java
@@ -16,12 +16,12 @@
package com.android.systemui.shared.system;
+import android.graphics.HardwareRenderer;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
-import android.view.ThreadedRenderer;
import android.view.View;
import android.view.ViewRootImpl;
@@ -52,7 +52,7 @@
if (mTargetViewRootImpl == null) {
return;
}
- mTargetViewRootImpl.registerRtFrameCallback(new ThreadedRenderer.FrameDrawingCallback() {
+ mTargetViewRootImpl.registerRtFrameCallback(new HardwareRenderer.FrameDrawingCallback() {
@Override
public void onFrameDraw(long frame) {
if (mTargetSurface == null || !mTargetSurface.isValid()) {
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierText.java b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
index a0a3687..b7d5197 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierText.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
@@ -16,10 +16,6 @@
package com.android.keyguard;
-import java.util.List;
-import java.util.Locale;
-import java.util.Objects;
-
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -28,6 +24,7 @@
import android.net.wifi.WifiManager;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.text.method.SingleLineTransformationMethod;
import android.util.AttributeSet;
@@ -40,7 +37,9 @@
import com.android.internal.telephony.TelephonyIntents;
import com.android.settingslib.WirelessUtils;
-import android.telephony.TelephonyManager;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
public class CarrierText extends TextView {
private static final boolean DEBUG = KeyguardConstants.DEBUG;
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
index ac8f024..210b82d 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
@@ -16,7 +16,6 @@
package com.android.keyguard;
-import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.content.Context;
@@ -37,8 +36,8 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.telephony.IccCardConstants.State;
-import com.android.internal.widget.LockPatternUtils;
import com.android.internal.util.EmergencyAffordanceManager;
+import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.util.EmergencyDialerConstants;
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
index 63b7ae2..c2bbfbf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
@@ -22,19 +22,15 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.os.Handler;
-import android.os.HandlerThread;
import android.os.UserHandle;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.euicc.EuiccManager;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
-import android.telephony.SubscriptionManager;
-import android.telephony.SubscriptionInfo;
-import android.telephony.euicc.EuiccManager;
-import android.util.Log;
-
-import java.lang.ref.WeakReference;
/***
* This button is used by the device with embedded SIM card to disable current carrier to unlock
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
index 34df15f..cf22286 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
@@ -19,8 +19,8 @@
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
-import android.content.res.Resources;
import android.content.res.ColorStateList;
+import android.content.res.Resources;
import android.graphics.Canvas;
import android.media.AudioManager;
import android.os.SystemClock;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index f400f60..3cc18dd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -23,8 +23,6 @@
import android.view.MotionEvent;
import android.view.View;
-import com.android.internal.annotations.VisibleForTesting;
-
/**
* A Pin based Keyguard input view
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index b8df3c06..7af27f2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -21,7 +21,6 @@
import android.content.Context;
import android.content.res.ColorStateList;
import android.os.UserHandle;
-import androidx.annotation.VisibleForTesting;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Slog;
@@ -31,6 +30,8 @@
import android.view.WindowManager;
import android.widget.FrameLayout;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
index 1a09364..272b3bd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -15,9 +15,10 @@
*/
package com.android.keyguard;
-import com.android.internal.widget.LockPatternUtils;
import android.content.res.ColorStateList;
+import com.android.internal.widget.LockPatternUtils;
+
public interface KeyguardSecurityView {
static public final int SCREEN_ON = 1;
static public final int VIEW_REVEALED = 2;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
index 74e2a68..e6a0250 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
@@ -18,8 +18,8 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.content.res.TypedArray;
import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
@@ -28,18 +28,15 @@
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewHierarchyEncoder;
-import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ViewFlipper;
import com.android.internal.widget.LockPatternUtils;
-import java.lang.Override;
-
/**
* Subclass of the current view flipper that allows us to overload dispatchTouchEvent() so
- * we can emulate {@link WindowManager.LayoutParams#FLAG_SLIPPERY} within a view hierarchy.
- *
+ * we can emulate {@link android.view.WindowManager.LayoutParams#FLAG_SLIPPERY} within a view
+ * hierarchy.
*/
public class KeyguardSecurityViewFlipper extends ViewFlipper implements KeyguardSecurityView {
private static final String TAG = "KeyguardSecurityViewFlipper";
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
index 42c7a56..6528d8c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
@@ -16,32 +16,31 @@
package com.android.keyguard;
-import com.android.internal.telephony.ITelephony;
-import com.android.internal.telephony.IccCardConstants;
-import com.android.internal.telephony.IccCardConstants.State;
-import com.android.internal.telephony.PhoneConstants;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.Configuration;
-import android.content.res.Resources;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.Dialog;
import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Color;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
-import android.telephony.euicc.EuiccManager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.IccCardConstants.State;
+import com.android.internal.telephony.PhoneConstants;
+
/**
* Displays a PIN pad for unlocking.
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
index 1b61568..1157f86 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
@@ -16,20 +16,19 @@
package com.android.keyguard;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
import android.graphics.Color;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
-import android.telephony.euicc.EuiccManager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
@@ -38,8 +37,8 @@
import com.android.internal.telephony.ITelephony;
import com.android.internal.telephony.IccCardConstants;
-import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.IccCardConstants.State;
+import com.android.internal.telephony.PhoneConstants;
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index 79966f7..c41ef0e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -243,46 +243,6 @@
}
}
- /**
- * Breaks a string in 2 lines where both have similar character count
- * but first line is always longer.
- *
- * @param charSequence Original text.
- * @return Optimal string.
- */
- private static CharSequence findBestLineBreak(CharSequence charSequence) {
- if (TextUtils.isEmpty(charSequence)) {
- return charSequence;
- }
-
- String source = charSequence.toString();
- // Ignore if there is only 1 word,
- // or if line breaks were manually set.
- if (source.contains("\n") || !source.contains(" ")) {
- return source;
- }
-
- final String[] words = source.split(" ");
- final StringBuilder optimalString = new StringBuilder(source.length());
- int current = 0;
- while (optimalString.length() < source.length() - optimalString.length()) {
- optimalString.append(words[current]);
- if (current < words.length - 1) {
- optimalString.append(" ");
- }
- current++;
- }
- optimalString.append("\n");
- for (int i = current; i < words.length; i++) {
- optimalString.append(words[i]);
- if (current < words.length - 1) {
- optimalString.append(" ");
- }
- }
-
- return optimalString.toString();
- }
-
public void setDarkAmount(float darkAmount) {
mDarkAmount = darkAmount;
mRow.setDarkAmount(darkAmount);
diff --git a/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java b/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java
index bbc8ecd..5ed9eaa 100644
--- a/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java
@@ -18,7 +18,6 @@
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
-import android.os.LocaleList;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
diff --git a/packages/SystemUI/src/com/android/systemui/DejankUtils.java b/packages/SystemUI/src/com/android/systemui/DejankUtils.java
index 4ee3bd3..bec8820 100644
--- a/packages/SystemUI/src/com/android/systemui/DejankUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/DejankUtils.java
@@ -16,12 +16,12 @@
package com.android.systemui;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.util.Assert;
-
import android.os.Handler;
import android.view.Choreographer;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.util.Assert;
+
import java.util.ArrayList;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/DockedStackExistsListener.java b/packages/SystemUI/src/com/android/systemui/DockedStackExistsListener.java
index 81e4db3..5c0df17 100644
--- a/packages/SystemUI/src/com/android/systemui/DockedStackExistsListener.java
+++ b/packages/SystemUI/src/com/android/systemui/DockedStackExistsListener.java
@@ -14,7 +14,6 @@
package com.android.systemui;
-import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.IDockedStackListener;
diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index f5ad747..ecf4c0a 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -32,9 +32,9 @@
import android.view.ViewConfiguration;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
-import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.policy.ScrollAdapter;
public class ExpandHelper implements Gefingerpoken {
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceControllerImpl.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceControllerImpl.java
index 1fa925e..bab472c 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceControllerImpl.java
@@ -23,7 +23,6 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
-import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.messages.nano.SystemMessageProto;
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServicesDialog.java b/packages/SystemUI/src/com/android/systemui/ForegroundServicesDialog.java
index 9d286cf..cb9523f 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServicesDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServicesDialog.java
@@ -42,8 +42,6 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
-import com.android.systemui.R;
-
import java.util.ArrayList;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
index b1463a3..16e869e 100644
--- a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
@@ -14,6 +14,10 @@
package com.android.systemui;
+import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE;
+import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE;
+import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -33,10 +37,6 @@
import com.android.systemui.tuner.TunerService.Tunable;
import com.android.systemui.util.leak.RotationUtils;
-import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE;
-import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE;
-import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE;
-
public class HardwareUiLayout extends LinearLayout implements Tunable {
private static final String EDGE_BLEED = "sysui_hwui_edge_bleed";
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index d8eb965..1d2d7fa 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -20,6 +20,7 @@
import android.content.ComponentCallbacks2;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region.Op;
@@ -30,7 +31,6 @@
import android.util.Log;
import android.view.Display;
import android.view.DisplayInfo;
-import android.graphics.RecordingCanvas;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.WindowManager;
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index 1e458fa..50f1b44 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -16,17 +16,17 @@
package com.android.systemui;
-import android.hardware.biometrics.BiometricSourceType;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.hardware.biometrics.BiometricSourceType;
import android.os.Build;
import android.os.PowerManager;
import android.os.SystemClock;
-import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.internal.util.LatencyTracker;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.StatusBar;
diff --git a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
index f6ad626..f9617ca 100644
--- a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
@@ -21,8 +21,8 @@
import android.view.View;
import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.plugins.ViewProvider;
+import com.android.systemui.shared.plugins.PluginManager;
/**
* Define an interface or abstract class as follows that includes the
diff --git a/packages/SystemUI/src/com/android/systemui/RegionInterceptingFrameLayout.java b/packages/SystemUI/src/com/android/systemui/RegionInterceptingFrameLayout.java
index 6dc2d67..8351bbf 100644
--- a/packages/SystemUI/src/com/android/systemui/RegionInterceptingFrameLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/RegionInterceptingFrameLayout.java
@@ -21,7 +21,6 @@
import android.graphics.Region.Op;
import android.util.AttributeSet;
import android.view.View;
-import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.InternalInsetsInfo;
import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
import android.widget.FrameLayout;
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index c844496..1dd231c 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -63,6 +63,8 @@
import android.widget.FrameLayout;
import android.widget.ImageView;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.util.Preconditions;
import com.android.systemui.RegionInterceptingFrameLayout.RegionInterceptableView;
import com.android.systemui.fragments.FragmentHostManager;
@@ -79,8 +81,6 @@
import java.util.ArrayList;
import java.util.List;
-import androidx.annotation.VisibleForTesting;
-
/**
* An overlay that draws screen decorations in software (e.g for rounded corners or display cutout)
* for antialiasing and emulation purposes.
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index e3584cf..3666400 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -88,8 +88,6 @@
private Runnable mWatchLongPress;
private final long mLongPressTimeout;
- protected boolean mSwipingInProgress;
-
final private int[] mTmpPos = new int[2];
private final int mFalsingThreshold;
private boolean mTouchAboveFalsingThreshold;
@@ -130,10 +128,6 @@
mDisableHwLayers = disableHwLayers;
}
- public boolean isSwipingInProgress() {
- return mSwipingInProgress;
- }
-
private float getPos(MotionEvent ev) {
return mSwipeDirection == X ? ev.getX() : ev.getY();
}
@@ -325,7 +319,6 @@
if (Math.abs(delta) > mPagingTouchSlop
&& Math.abs(delta) > Math.abs(deltaPerpendicular)) {
if (mCallback.canChildBeDragged(mCurrView)) {
- mSwipingInProgress = true;
mCallback.onBeginDrag(mCurrView);
mDragging = true;
mInitialTouchPos = getPos(ev);
@@ -445,7 +438,6 @@
wasRemoved = row.isRemoved();
}
if (!mCancelled || wasRemoved) {
- mSwipingInProgress = false;
mCallback.onChildDismissed(animView);
}
if (endAction != null) {
@@ -637,7 +629,6 @@
!swipedFastEnough() /* useAccelerateInterpolator */);
} else {
// snappity
- mSwipingInProgress = false;
mCallback.onDragCancelled(mCurrView);
snapChild(mCurrView, 0 /* leftTarget */, velocity);
}
diff --git a/packages/SystemUI/src/com/android/systemui/SysUIToast.java b/packages/SystemUI/src/com/android/systemui/SysUIToast.java
index 43b918d..8bcf057 100644
--- a/packages/SystemUI/src/com/android/systemui/SysUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/SysUIToast.java
@@ -15,11 +15,12 @@
*/
package com.android.systemui;
+import static android.widget.Toast.Duration;
+
import android.annotation.StringRes;
import android.content.Context;
import android.view.WindowManager;
import android.widget.Toast;
-import static android.widget.Toast.Duration;
public class SysUIToast {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemBars.java b/packages/SystemUI/src/com/android/systemui/SystemBars.java
index b5093b3..6edc23b 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemBars.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemBars.java
@@ -14,13 +14,8 @@
package com.android.systemui;
-import android.content.res.Configuration;
-import android.provider.Settings;
import android.util.Log;
-import com.android.systemui.R;
-import com.android.systemui.SystemUI;
-
import java.io.FileDescriptor;
import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index c4bf27b..314a3c3 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -22,30 +22,31 @@
import android.util.Log;
import android.view.ViewGroup;
+import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.internal.util.function.TriConsumer;
import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.Dependency.DependencyProvider;
+import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.statusbar.AmbientPulseManager;
import com.android.systemui.statusbar.KeyguardIndicationController;
-import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
-import com.android.systemui.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.SmartReplyController;
+import com.android.systemui.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
@@ -155,5 +156,6 @@
providers.put(SmartReplyController.class, () -> new SmartReplyController());
providers.put(RemoteInputQuickSettingsDisabler.class,
() -> new RemoteInputQuickSettingsDisabler(context));
+ providers.put(BubbleController.class, () -> new BubbleController(context));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index 8e29841..ac108be 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -24,13 +24,13 @@
import android.os.SystemProperties;
import android.util.Slog;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
import com.android.internal.os.BinderInternal;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginManagerImpl;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
public class SystemUIService extends Service {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
index 4bd095d..79d4f8d 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
@@ -31,7 +31,6 @@
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
-import android.view.animation.AnimationUtils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
new file mode 100644
index 0000000..e868f96
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bubbles;
+
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import static com.android.systemui.bubbles.BubbleMovementHelper.EDGE_OVERLAP;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.service.notification.StatusBarNotification;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.phone.StatusBarWindowController;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Bubbles are a special type of content that can "float" on top of other apps or System UI.
+ * Bubbles can be expanded to show more content.
+ *
+ * The controller manages addition, removal, and visible state of bubbles on screen.
+ */
+public class BubbleController {
+ private static final int MAX_BUBBLES = 5; // TODO: actually enforce this
+
+ private static final String TAG = "BubbleController";
+
+ // Enables some subset of notifs to automatically become bubbles
+ public static final boolean DEBUG_ENABLE_AUTO_BUBBLE = false;
+ // When a bubble is dismissed, recreate it as a notification
+ public static final boolean DEBUG_DEMOTE_TO_NOTIF = false;
+
+ private Context mContext;
+ private BubbleDismissListener mDismissListener;
+ private BubbleStateChangeListener mStateChangeListener;
+ private BubbleExpandListener mExpandListener;
+
+ private Map<String, BubbleView> mBubbles = new HashMap<>();
+ private BubbleStackView mStackView;
+ private Point mDisplaySize;
+
+ // Bubbles get added to the status bar view
+ private StatusBarWindowController mStatusBarWindowController;
+
+ // Used for determining view rect for touch interaction
+ private Rect mTempRect = new Rect();
+
+ /**
+ * Listener to find out about bubble / bubble stack dismissal events.
+ */
+ public interface BubbleDismissListener {
+ /**
+ * Called when the entire stack of bubbles is dismissed by the user.
+ */
+ void onStackDismissed();
+
+ /**
+ * Called when a specific bubble is dismissed by the user.
+ */
+ void onBubbleDismissed(String key);
+ }
+
+ /**
+ * Listener to be notified when some states of the bubbles change.
+ */
+ public interface BubbleStateChangeListener {
+ /**
+ * Called when the stack has bubbles or no longer has bubbles.
+ */
+ void onHasBubblesChanged(boolean hasBubbles);
+ }
+
+ /**
+ * Listener to find out about stack expansion / collapse events.
+ */
+ public interface BubbleExpandListener {
+ /**
+ * Called when the expansion state of the bubble stack changes.
+ *
+ * @param isExpanding whether it's expanding or collapsing
+ * @param amount fraction of how expanded or collapsed it is, 1 being fully, 0 at the start
+ */
+ void onBubbleExpandChanged(boolean isExpanding, float amount);
+ }
+
+ public BubbleController(Context context) {
+ mContext = context;
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ mDisplaySize = new Point();
+ wm.getDefaultDisplay().getSize(mDisplaySize);
+ mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
+ }
+
+ /**
+ * Set a listener to be notified of bubble dismissal events.
+ */
+ public void setDismissListener(BubbleDismissListener listener) {
+ mDismissListener = listener;
+ }
+
+ /**
+ * Set a listener to be notified when some states of the bubbles change.
+ */
+ public void setBubbleStateChangeListener(BubbleStateChangeListener listener) {
+ mStateChangeListener = listener;
+ }
+
+ /**
+ * Set a listener to be notified of bubble expand events.
+ */
+ public void setExpandListener(BubbleExpandListener listener) {
+ mExpandListener = listener;
+ if (mStackView != null) {
+ mStackView.setExpandListener(mExpandListener);
+ }
+ }
+
+ /**
+ * Whether or not there are bubbles present, regardless of them being visible on the
+ * screen (e.g. if on AOD).
+ */
+ public boolean hasBubbles() {
+ return mBubbles.size() > 0;
+ }
+
+ /**
+ * Whether the stack of bubbles is expanded or not.
+ */
+ public boolean isStackExpanded() {
+ return mStackView != null && mStackView.isExpanded();
+ }
+
+ /**
+ * Tell the stack of bubbles to collapse.
+ */
+ public void collapseStack() {
+ if (mStackView != null) {
+ mStackView.animateExpansion(false);
+ }
+ }
+
+ /**
+ * Tell the stack of bubbles to be dismissed, this will remove all of the bubbles in the stack.
+ */
+ public void dismissStack() {
+ if (mStackView == null) {
+ return;
+ }
+ Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize);
+ // Reset the position of the stack (TODO - or should we save / respect last user position?)
+ mStackView.setPosition(startPoint.x, startPoint.y);
+ for (String key: mBubbles.keySet()) {
+ removeBubble(key);
+ }
+ if (mDismissListener != null) {
+ mDismissListener.onStackDismissed();
+ }
+ updateBubblesShowing();
+ }
+
+ /**
+ * Adds a bubble associated with the provided notification entry or updates it if it exists.
+ */
+ public void addBubble(NotificationData.Entry notif) {
+ if (mBubbles.containsKey(notif.key)) {
+ // It's an update
+ BubbleView bubble = mBubbles.get(notif.key);
+ mStackView.updateBubble(bubble, notif);
+ } else {
+ // It's new
+ BubbleView bubble = new BubbleView(mContext);
+ bubble.setNotif(notif);
+ mBubbles.put(bubble.getKey(), bubble);
+
+ boolean setPosition = mStackView != null && mStackView.getVisibility() != VISIBLE;
+ if (mStackView == null) {
+ setPosition = true;
+ mStackView = new BubbleStackView(mContext);
+ ViewGroup sbv = mStatusBarWindowController.getStatusBarView();
+ // XXX: Bug when you expand the shade on top of expanded bubble, there is no scrim
+ // between bubble and the shade
+ int bubblePosition = sbv.indexOfChild(sbv.findViewById(R.id.scrim_behind)) + 1;
+ sbv.addView(mStackView, bubblePosition,
+ new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+ if (mExpandListener != null) {
+ mStackView.setExpandListener(mExpandListener);
+ }
+ }
+ mStackView.addBubble(bubble);
+ if (setPosition) {
+ // Need to add the bubble to the stack before we can know the width
+ Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize);
+ mStackView.setPosition(startPoint.x, startPoint.y);
+ mStackView.setVisibility(VISIBLE);
+ }
+ updateBubblesShowing();
+ }
+ }
+
+ /**
+ * Removes the bubble associated with the {@param uri}.
+ */
+ public void removeBubble(String key) {
+ BubbleView bv = mBubbles.get(key);
+ if (mStackView != null && bv != null) {
+ mStackView.removeBubble(bv);
+ bv.getEntry().setBubbleDismissed(true);
+ }
+ if (mDismissListener != null) {
+ mDismissListener.onBubbleDismissed(key);
+ }
+ updateBubblesShowing();
+ }
+
+ private void updateBubblesShowing() {
+ boolean hasBubblesShowing = false;
+ for (BubbleView bv : mBubbles.values()) {
+ if (!bv.getEntry().isBubbleDismissed()) {
+ hasBubblesShowing = true;
+ break;
+ }
+ }
+ boolean hadBubbles = mStatusBarWindowController.getBubblesShowing();
+ mStatusBarWindowController.setBubblesShowing(hasBubblesShowing);
+ if (mStackView != null && !hasBubblesShowing) {
+ mStackView.setVisibility(INVISIBLE);
+ }
+ if (mStateChangeListener != null && hadBubbles != hasBubblesShowing) {
+ mStateChangeListener.onHasBubblesChanged(hasBubblesShowing);
+ }
+ }
+
+ /**
+ * Sets the visibility of the bubbles, doesn't un-bubble them, just changes visibility.
+ */
+ public void updateVisibility(boolean visible) {
+ if (mStackView == null) {
+ return;
+ }
+ ArrayList<BubbleView> viewsToRemove = new ArrayList<>();
+ for (BubbleView bv : mBubbles.values()) {
+ NotificationData.Entry entry = bv.getEntry();
+ if (entry != null) {
+ if (entry.row.isRemoved() || entry.isBubbleDismissed() || entry.row.isDismissed()) {
+ viewsToRemove.add(bv);
+ }
+ }
+ }
+ for (BubbleView view : viewsToRemove) {
+ mBubbles.remove(view.getKey());
+ mStackView.removeBubble(view);
+ }
+ if (mStackView != null) {
+ mStackView.setVisibility(visible ? VISIBLE : INVISIBLE);
+ if (!visible) {
+ collapseStack();
+ }
+ }
+ updateBubblesShowing();
+ }
+
+ /**
+ * Rect indicating the touchable region for the bubble stack / expanded stack.
+ */
+ public Rect getTouchableRegion() {
+ if (mStackView == null || mStackView.getVisibility() != VISIBLE) {
+ return null;
+ }
+ mStackView.getBoundsOnScreen(mTempRect);
+ return mTempRect;
+ }
+
+ // TODO: factor in PIP location / maybe last place user had it
+ /**
+ * Gets an appropriate starting point to position the bubble stack.
+ */
+ public static Point getStartPoint(int size, Point displaySize) {
+ final int x = displaySize.x - size + EDGE_OVERLAP;
+ final int y = displaySize.y / 4;
+ return new Point(x, y);
+ }
+
+ /**
+ * Gets an appropriate position for the bubble when the stack is expanded.
+ */
+ public static Point getExpandPoint(BubbleStackView view, int size, Point displaySize) {
+ // Same place for now..
+ return new Point(EDGE_OVERLAP, size);
+ }
+
+ /**
+ * Whether the notification should bubble or not.
+ */
+ public static boolean shouldAutoBubble(NotificationData.Entry entry, int priority,
+ boolean canAppOverlay) {
+ if (!DEBUG_ENABLE_AUTO_BUBBLE || entry.isBubbleDismissed()) {
+ return false;
+ }
+ StatusBarNotification n = entry.notification;
+ boolean hasRemoteInput = false;
+ if (n.getNotification().actions != null) {
+ for (Notification.Action action : n.getNotification().actions) {
+ if (action.getRemoteInputs() != null) {
+ hasRemoteInput = true;
+ break;
+ }
+ }
+ }
+ Class<? extends Notification.Style> style = n.getNotification().getNotificationStyle();
+ boolean shouldBubble = priority >= NotificationManager.IMPORTANCE_HIGH
+ || Notification.MessagingStyle.class.equals(style)
+ || Notification.CATEGORY_MESSAGE.equals(n.getNotification().category)
+ || hasRemoteInput
+ || canAppOverlay;
+ return shouldBubble && !entry.isBubbleDismissed();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java
new file mode 100644
index 0000000..c1063fa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bubbles;
+
+import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
+
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.systemui.bubbles.BubbleTouchHandler.FloatingView;
+
+import java.util.Arrays;
+
+/**
+ * Math and animators to move bubbles around the screen.
+ *
+ * TODO: straight up copy paste from old prototype -- consider physics, see if bubble & pip
+ * movements can be unified maybe?
+ */
+public class BubbleMovementHelper {
+
+ private static final int MAGNET_ANIM_TIME = 150;
+ public static final int EDGE_OVERLAP = 0;
+
+ private Context mContext;
+ private Point mDisplaySize;
+
+ public BubbleMovementHelper(Context context) {
+ mContext = context;
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ mDisplaySize = new Point();
+ wm.getDefaultDisplay().getSize(mDisplaySize);
+ }
+
+ /**
+ * @return the distance between the two provided points.
+ */
+ static double distance(Point p1, Point p2) {
+ return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
+ }
+
+ /**
+ * @return the y value of a line defined by y = mx+b
+ */
+ static float findY(float m, float b, float x) {
+ return (m * x) + b;
+ }
+
+ /**
+ * @return the x value of a line defined by y = mx+b
+ */
+ static float findX(float m, float b, float y) {
+ return (y - b) / m;
+ }
+
+ /**
+ * Determines a point on the edge of the screen based on the velocity and position.
+ */
+ public Point getPointOnEdge(View bv, Point p, float velX, float velY) {
+ // Find the slope and the y-intercept
+ velX = velX == 0 ? 1 : velX;
+ final float m = velY / velX;
+ final float b = p.y - m * p.x;
+
+ // There are two lines it can intersect, find the two points
+ Point pointHoriz = new Point();
+ Point pointVert = new Point();
+
+ if (velX > 0) {
+ // right
+ pointHoriz.x = mDisplaySize.x;
+ pointHoriz.y = (int) findY(m, b, mDisplaySize.x);
+ } else {
+ // left
+ pointHoriz.x = EDGE_OVERLAP;
+ pointHoriz.y = (int) findY(m, b, 0);
+ }
+ if (velY > 0) {
+ // bottom
+ pointVert.x = (int) findX(m, b, mDisplaySize.y);
+ pointVert.y = mDisplaySize.y - getNavBarHeight();
+ } else {
+ // top
+ pointVert.x = (int) findX(m, b, 0);
+ pointVert.y = EDGE_OVERLAP;
+ }
+
+ // Use the point that's closest to the start position
+ final double distanceToVertPoint = distance(p, pointVert);
+ final double distanceToHorizPoint = distance(p, pointHoriz);
+ boolean useVert = distanceToVertPoint < distanceToHorizPoint;
+ // Check if we're being flung along the current edge, use opposite point in this case
+ // XXX: on*Edge methods should actually use 'down' position of view and compare 'up' but
+ // this works well enough for now
+ if (onSideEdge(bv, p) && Math.abs(velY) > Math.abs(velX)) {
+ // Flinging along left or right edge, favor vert edge
+ useVert = true;
+
+ } else if (onTopBotEdge(bv, p) && Math.abs(velX) > Math.abs(velY)) {
+ // Flinging along top or bottom edge
+ useVert = false;
+ }
+
+ if (useVert) {
+ pointVert.x = capX(pointVert.x, bv);
+ pointVert.y = capY(pointVert.y, bv);
+ return pointVert;
+
+ }
+ pointHoriz.x = capX(pointHoriz.x, bv);
+ pointHoriz.y = capY(pointHoriz.y, bv);
+ return pointHoriz;
+ }
+
+ /**
+ * @return whether the view is on a side edge of the screen (i.e. left or right).
+ */
+ public boolean onSideEdge(View fv, Point p) {
+ return p.x + fv.getWidth() + EDGE_OVERLAP <= mDisplaySize.x
+ - EDGE_OVERLAP
+ || p.x >= EDGE_OVERLAP;
+ }
+
+ /**
+ * @return whether the view is on a top or bottom edge of the screen.
+ */
+ public boolean onTopBotEdge(View bv, Point p) {
+ return p.y >= getStatusBarHeight() + EDGE_OVERLAP
+ || p.y + bv.getHeight() + EDGE_OVERLAP <= mDisplaySize.y
+ - EDGE_OVERLAP;
+ }
+
+ /**
+ * @return constrained x value based on screen size and how much a view can overlap with a side
+ * edge.
+ */
+ public int capX(float x, View bv) {
+ // Floating things can't stick to top or bottom edges, so figure out if it's closer to
+ // left or right and just use that side + the overlap.
+ final float centerX = x + bv.getWidth() / 2;
+ if (centerX > mDisplaySize.x / 2) {
+ // Right side
+ return mDisplaySize.x - bv.getWidth() - EDGE_OVERLAP;
+ } else {
+ // Left side
+ return EDGE_OVERLAP;
+ }
+ }
+
+ /**
+ * @return constrained y value based on screen size and how much a view can overlap with a top
+ * or bottom edge.
+ */
+ public int capY(float y, View bv) {
+ final int height = bv.getHeight();
+ if (y < getStatusBarHeight() + EDGE_OVERLAP) {
+ return getStatusBarHeight() + EDGE_OVERLAP;
+ }
+ if (y + height + EDGE_OVERLAP > mDisplaySize.y - EDGE_OVERLAP) {
+ return mDisplaySize.y - height - EDGE_OVERLAP;
+ }
+ return (int) y;
+ }
+
+ /**
+ * Animation to translate the provided view.
+ */
+ public AnimatorSet animateMagnetTo(final BubbleStackView bv) {
+ Point pos = bv.getPosition();
+
+ // Find the distance to each edge
+ final int leftDistance = pos.x;
+ final int rightDistance = mDisplaySize.x - leftDistance;
+ final int topDistance = pos.y;
+ final int botDistance = mDisplaySize.y - topDistance;
+
+ int smallest;
+ // Find the closest one
+ int[] distances = {
+ leftDistance, rightDistance, topDistance, botDistance
+ };
+ Arrays.sort(distances);
+ smallest = distances[0];
+
+ // Animate to the closest edge
+ Point p = new Point();
+ if (smallest == leftDistance) {
+ p.x = capX(EDGE_OVERLAP, bv);
+ p.y = capY(topDistance, bv);
+ }
+ if (smallest == rightDistance) {
+ p.x = capX(mDisplaySize.x, bv);
+ p.y = capY(topDistance, bv);
+ }
+ if (smallest == topDistance) {
+ p.x = capX(leftDistance, bv);
+ p.y = capY(0, bv);
+ }
+ if (smallest == botDistance) {
+ p.x = capX(leftDistance, bv);
+ p.y = capY(mDisplaySize.y, bv);
+ }
+ return getTranslateAnim(bv, p, MAGNET_ANIM_TIME);
+ }
+
+ /**
+ * Animation to fling the provided view.
+ */
+ public AnimatorSet animateFlingTo(final BubbleStackView bv, float velX, float velY) {
+ Point pos = bv.getPosition();
+ Point endPos = getPointOnEdge(bv, pos, velX, velY);
+ endPos = new Point(capX(endPos.x, bv), capY(endPos.y, bv));
+ final double distance = Math.sqrt(Math.pow(endPos.x - pos.x, 2)
+ + Math.pow(endPos.y - pos.y, 2));
+ final float sumVel = Math.abs(velX) + Math.abs(velY);
+ final int duration = Math.max(Math.min(200, (int) (distance * 1000f / (sumVel / 2))), 50);
+ return getTranslateAnim(bv, endPos, duration);
+ }
+
+ /**
+ * Animation to translate the provided view.
+ */
+ public AnimatorSet getTranslateAnim(final FloatingView v, Point p, int duration) {
+ return getTranslateAnim(v, p, duration, 0);
+ }
+
+ /**
+ * Animation to translate the provided view.
+ */
+ public AnimatorSet getTranslateAnim(final FloatingView v, Point p,
+ int duration, int startDelay) {
+ return getTranslateAnim(v, p, duration, startDelay, null);
+ }
+
+ /**
+ * Animation to translate the provided view.
+ *
+ * @param v the view to translate.
+ * @param p the point to translate to.
+ * @param duration the duration of the animation.
+ * @param startDelay the start delay of the animation.
+ * @param listener the listener to add to the animation.
+ *
+ * @return the animation.
+ */
+ public static AnimatorSet getTranslateAnim(final FloatingView v, Point p, int duration,
+ int startDelay, AnimatorListener listener) {
+ Point curPos = v.getPosition();
+ final ValueAnimator animX = ValueAnimator.ofFloat(curPos.x, p.x);
+ animX.setDuration(duration);
+ animX.setStartDelay(startDelay);
+ animX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float value = (float) animation.getAnimatedValue();
+ v.setPositionX((int) value);
+ }
+ });
+
+ final ValueAnimator animY = ValueAnimator.ofFloat(curPos.y, p.y);
+ animY.setDuration(duration);
+ animY.setStartDelay(startDelay);
+ animY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float value = (float) animation.getAnimatedValue();
+ v.setPositionY((int) value);
+ }
+ });
+ if (listener != null) {
+ animY.addListener(listener);
+ }
+
+ AnimatorSet set = new AnimatorSet();
+ set.playTogether(animX, animY);
+ set.setInterpolator(FAST_OUT_SLOW_IN);
+ return set;
+ }
+
+
+ // TODO -- now that this is in system we should be able to get these better, but ultimately
+ // makes more sense to move to movement bounds style a la PIP
+ /**
+ * Returns the status bar height.
+ */
+ public int getStatusBarHeight() {
+ Resources res = mContext.getResources();
+ int resourceId = res.getIdentifier("status_bar_height", "dimen", "android");
+ if (resourceId > 0) {
+ return res.getDimensionPixelSize(resourceId);
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the status bar height.
+ */
+ public int getNavBarHeight() {
+ Resources res = mContext.getResources();
+ int resourceId = res.getIdentifier("navigation_bar_height", "dimen", "android");
+ if (resourceId > 0) {
+ return res.getDimensionPixelSize(resourceId);
+ }
+ return 0;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
new file mode 100644
index 0000000..365dbda
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -0,0 +1,495 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bubbles;
+
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.RectF;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewPropertyAnimator;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.OvershootInterpolator;
+import android.widget.FrameLayout;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.widget.ViewClippingUtil;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
+import com.android.systemui.statusbar.notification.stack.ViewState;
+
+/**
+ * Renders bubbles in a stack and handles animating expanded and collapsed states.
+ */
+public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.FloatingView {
+
+ private Point mDisplaySize;
+
+ private FrameLayout mBubbleContainer;
+ private FrameLayout mExpandedViewContainer;
+
+ private int mBubbleSize;
+ private int mBubblePadding;
+
+ private boolean mIsExpanded;
+ private BubbleView mExpandedBubble;
+ private Point mCollapsedPosition;
+ private BubbleTouchHandler mTouchHandler;
+ private BubbleController.BubbleExpandListener mExpandListener;
+
+ private boolean mViewUpdatedRequested = false;
+ private boolean mIsAnimating = false;
+
+ // Used for determining view / touch intersection
+ int[] mTempLoc = new int[2];
+ RectF mTempRect = new RectF();
+
+ private ViewTreeObserver.OnPreDrawListener mViewUpdater =
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ getViewTreeObserver().removeOnPreDrawListener(mViewUpdater);
+ applyCurrentState();
+ mViewUpdatedRequested = false;
+ return true;
+ }
+ };
+
+ private ViewClippingUtil.ClippingParameters mClippingParameters =
+ new ViewClippingUtil.ClippingParameters() {
+
+ @Override
+ public boolean shouldFinish(View view) {
+ return false;
+ }
+
+ @Override
+ public boolean isClippingEnablingAllowed(View view) {
+ return !mIsExpanded;
+ }
+ };
+
+ public BubbleStackView(Context context) {
+ super(context);
+
+ mTouchHandler = new BubbleTouchHandler(context);
+ setOnTouchListener(mTouchHandler);
+
+ Resources res = getResources();
+ mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
+ mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+
+ mDisplaySize = new Point();
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ wm.getDefaultDisplay().getSize(mDisplaySize);
+
+ int padding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
+ int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
+ mExpandedViewContainer = new FrameLayout(context);
+ mExpandedViewContainer.setElevation(elevation);
+ mExpandedViewContainer.setPadding(padding, padding, padding, padding);
+ mExpandedViewContainer.setClipChildren(false);
+ addView(mExpandedViewContainer);
+
+ mBubbleContainer = new FrameLayout(context);
+ mBubbleContainer.setElevation(elevation);
+ mBubbleContainer.setPadding(padding, 0, padding, 0);
+ mBubbleContainer.setClipChildren(false);
+ addView(mBubbleContainer);
+
+ setClipChildren(false);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ getViewTreeObserver().removeOnPreDrawListener(mViewUpdater);
+ }
+
+ @Override
+ public void onMeasure(int widthSpec, int heightSpec) {
+ super.onMeasure(widthSpec, heightSpec);
+
+ int bubbleHeightSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightSpec),
+ MeasureSpec.UNSPECIFIED);
+ if (mIsExpanded) {
+ ViewGroup parent = (ViewGroup) getParent();
+ int parentWidth = MeasureSpec.makeMeasureSpec(
+ MeasureSpec.getSize(parent.getWidth()), MeasureSpec.EXACTLY);
+ int parentHeight = MeasureSpec.makeMeasureSpec(
+ MeasureSpec.getSize(parent.getHeight()), MeasureSpec.EXACTLY);
+ measureChild(mBubbleContainer, parentWidth, bubbleHeightSpec);
+
+ int expandedViewHeight = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightSpec),
+ MeasureSpec.UNSPECIFIED);
+ measureChild(mExpandedViewContainer, parentWidth, expandedViewHeight);
+ setMeasuredDimension(widthSpec, parentHeight);
+ } else {
+ // Not expanded
+ measureChild(mExpandedViewContainer, 0, 0);
+
+ // Bubbles are translated a little to stack on top of each other
+ widthSpec = MeasureSpec.makeMeasureSpec(getStackWidth(), MeasureSpec.EXACTLY);
+ measureChild(mBubbleContainer, widthSpec, bubbleHeightSpec);
+
+ heightSpec = MeasureSpec.makeMeasureSpec(mBubbleContainer.getMeasuredHeight(),
+ MeasureSpec.EXACTLY);
+ setMeasuredDimension(widthSpec, heightSpec);
+ }
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ float x = ev.getRawX();
+ float y = ev.getRawY();
+ // If we're expanded only intercept if the tap is outside of the widget container
+ if (mIsExpanded && isIntersecting(mExpandedViewContainer, x, y)) {
+ return false;
+ } else {
+ return isIntersecting(mBubbleContainer, x, y);
+ }
+ }
+
+ /**
+ * Sets the listener to notify when the bubble stack is expanded.
+ */
+ public void setExpandListener(BubbleController.BubbleExpandListener listener) {
+ mExpandListener = listener;
+ }
+
+ /**
+ * Whether the stack of bubbles is expanded or not.
+ */
+ public boolean isExpanded() {
+ return mIsExpanded;
+ }
+
+ /**
+ * The {@link BubbleView} that is expanded, null if one does not exist.
+ */
+ public BubbleView getExpandedBubble() {
+ return mExpandedBubble;
+ }
+
+ /**
+ * Sets the bubble that should be expanded and expands if needed.
+ */
+ public void setExpandedBubble(BubbleView bubbleToExpand) {
+ mExpandedBubble = bubbleToExpand;
+ mIsExpanded = true;
+ updateExpandedBubble();
+ requestUpdate();
+ }
+
+ /**
+ * Adds a bubble to the stack.
+ */
+ public void addBubble(BubbleView bubbleView) {
+ mBubbleContainer.addView(bubbleView, 0,
+ new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+ ViewClippingUtil.setClippingDeactivated(bubbleView, true, mClippingParameters);
+ requestUpdate();
+ }
+
+ /**
+ * Remove a bubble from the stack.
+ */
+ public void removeBubble(BubbleView bubbleView) {
+ int removedIndex = mBubbleContainer.indexOfChild(bubbleView);
+ mBubbleContainer.removeView(bubbleView);
+ boolean wasExpanded = mIsExpanded;
+ int bubbleCount = mBubbleContainer.getChildCount();
+ if (bubbleView.equals(mExpandedBubble) && bubbleCount > 0) {
+ // If we have other bubbles and are expanded go to the next one or previous
+ // if the bubble removed was last
+ int nextIndex = bubbleCount > removedIndex ? removedIndex : bubbleCount - 1;
+ mExpandedBubble = (BubbleView) mBubbleContainer.getChildAt(nextIndex);
+ }
+ mIsExpanded = wasExpanded && mBubbleContainer.getChildCount() > 0;
+ requestUpdate();
+ if (wasExpanded && !mIsExpanded && mExpandListener != null) {
+ mExpandListener.onBubbleExpandChanged(mIsExpanded, 1 /* amount */);
+ }
+ }
+
+ /**
+ * Updates a bubble in the stack.
+ *
+ * @param bubbleView the view to update in the stack.
+ * @param entry the entry to update it with.
+ */
+ public void updateBubble(BubbleView bubbleView, NotificationData.Entry entry) {
+ // TODO - move to top of bubble stack, make it show its update if it makes sense
+ bubbleView.update(entry);
+ if (bubbleView.equals(mExpandedBubble)) {
+ requestUpdate();
+ }
+ }
+
+ /**
+ * @return the view the touch event is on
+ */
+ @Nullable
+ public View getTargetView(MotionEvent event) {
+ float x = event.getRawX();
+ float y = event.getRawY();
+ if (mIsExpanded) {
+ if (isIntersecting(mBubbleContainer, x, y)) {
+ for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
+ BubbleView view = (BubbleView) mBubbleContainer.getChildAt(i);
+ if (isIntersecting(view, x, y)) {
+ return view;
+ }
+ }
+ } else if (isIntersecting(mExpandedViewContainer, x, y)) {
+ return mExpandedViewContainer;
+ }
+ // Outside parts of view we care about.
+ return null;
+ }
+ // If we're collapsed, the stack is always the target.
+ return this;
+ }
+
+ /**
+ * Tell the stack to animate to collapsed or expanded state.
+ */
+ public void animateExpansion(boolean shouldExpand) {
+ if (mIsExpanded != shouldExpand) {
+ mIsExpanded = shouldExpand;
+ mExpandedBubble = shouldExpand ? getTopBubble() : null;
+ updateExpandedBubble();
+
+ if (mExpandListener != null) {
+ mExpandListener.onBubbleExpandChanged(mIsExpanded, 1 /* amount */);
+ }
+ if (shouldExpand) {
+ // Save current position so that we might return there
+ savePosition();
+ }
+
+ // Determine the translation for the stack
+ Point position = shouldExpand
+ ? BubbleController.getExpandPoint(this, mBubbleSize, mDisplaySize)
+ : mCollapsedPosition;
+ int delay = shouldExpand ? 0 : 100;
+ AnimatorSet translationAnim = BubbleMovementHelper.getTranslateAnim(this, position,
+ 200, delay, null);
+ if (!shouldExpand) {
+ // First collapse the stack, then translate, maybe should expand at same time?
+ animateStackExpansion(() -> translationAnim.start());
+ } else {
+ // First translate, then expand
+ translationAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mIsAnimating = true;
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animateStackExpansion(() -> mIsAnimating = false);
+ }
+ });
+ translationAnim.start();
+ }
+ }
+ }
+
+ /**
+ * The width of the collapsed stack of bubbles.
+ */
+ public int getStackWidth() {
+ return mBubblePadding * (mBubbleContainer.getChildCount() - 1)
+ + mBubbleSize + mBubbleContainer.getPaddingEnd()
+ + mBubbleContainer.getPaddingStart();
+ }
+
+ /**
+ * Saves the current position of the stack, used to save user placement of the stack to
+ * return to after an animation.
+ */
+ private void savePosition() {
+ mCollapsedPosition = getPosition();
+ }
+
+ private BubbleView getTopBubble() {
+ return getBubbleAt(0);
+ }
+
+ private BubbleView getBubbleAt(int i) {
+ return mBubbleContainer.getChildCount() > i
+ ? (BubbleView) mBubbleContainer.getChildAt(i)
+ : null;
+ }
+
+ @Override
+ public void setPosition(int x, int y) {
+ setPositionX(x);
+ setPositionY(y);
+ }
+
+ @Override
+ public void setPositionX(int x) {
+ setTranslationX(x);
+ }
+
+ @Override
+ public void setPositionY(int y) {
+ setTranslationY(y);
+ }
+
+ @Override
+ public Point getPosition() {
+ return new Point((int) getTranslationX(), (int) getTranslationY());
+ }
+
+ private boolean isIntersecting(View view, float x, float y) {
+ mTempLoc = view.getLocationOnScreen();
+ mTempRect.set(mTempLoc[0], mTempLoc[1], mTempLoc[0] + view.getWidth(),
+ mTempLoc[1] + view.getHeight());
+ return mTempRect.contains(x, y);
+ }
+
+ private void requestUpdate() {
+ if (mViewUpdatedRequested || mIsAnimating) {
+ return;
+ }
+ mViewUpdatedRequested = true;
+ getViewTreeObserver().addOnPreDrawListener(mViewUpdater);
+ invalidate();
+ }
+
+ private void updateExpandedBubble() {
+ if (mExpandedBubble != null) {
+ ExpandableNotificationRow row = mExpandedBubble.getRowView();
+ if (!row.equals(mExpandedViewContainer.getChildAt(0))) {
+ // Different expanded view than what we have
+ mExpandedViewContainer.removeAllViews();
+ }
+ mExpandedViewContainer.addView(row);
+ }
+ }
+
+ private void applyCurrentState() {
+ mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
+ if (!mIsExpanded) {
+ mExpandedViewContainer.removeAllViews();
+ } else {
+ mExpandedViewContainer.setTranslationY(mBubbleContainer.getHeight());
+ ExpandableNotificationRow row = mExpandedBubble.getRowView();
+ applyRowState(row);
+ }
+ int bubbsCount = mBubbleContainer.getChildCount();
+ for (int i = 0; i < bubbsCount; i++) {
+ BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
+ bv.setZ(bubbsCount - 1);
+
+ int transX = mIsExpanded ? (bv.getWidth() + mBubblePadding) * i : mBubblePadding * i;
+ ViewState viewState = new ViewState();
+ viewState.initFrom(bv);
+ viewState.xTranslation = transX;
+ viewState.applyToView(bv);
+
+ if (mIsExpanded) {
+ // Save the position so we can magnet back, tag is retrieved in BubbleTouchHandler
+ bv.setTag(new Point(transX, 0));
+ }
+ }
+ }
+
+ private void animateStackExpansion(Runnable endRunnable) {
+ int childCount = mBubbleContainer.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ BubbleView child = (BubbleView) mBubbleContainer.getChildAt(i);
+ int transX = mIsExpanded ? (mBubbleSize + mBubblePadding) * i : mBubblePadding * i;
+ int duration = childCount > 1 ? 200 : 0;
+ if (mIsExpanded) {
+ // Save the position so we can magnet back, tag is retrieved in BubbleTouchHandler
+ child.setTag(new Point(transX, 0));
+ }
+ ViewPropertyAnimator anim = child
+ .animate()
+ .setStartDelay(15 * i)
+ .setDuration(duration)
+ .setInterpolator(mIsExpanded
+ ? new OvershootInterpolator()
+ : new AccelerateInterpolator())
+ .translationY(0)
+ .translationX(transX);
+ final int fi = i;
+ // Probably want this choreographed with translation somehow / make it snappier
+ anim.withStartAction(() -> mIsAnimating = true);
+ anim.withEndAction(() -> {
+ if (endRunnable != null) {
+ endRunnable.run();
+ }
+ if (fi == mBubbleContainer.getChildCount() - 1) {
+ applyCurrentState();
+ mIsAnimating = false;
+ requestUpdate();
+ }
+ });
+ anim.start();
+ }
+ }
+
+ private void applyRowState(ExpandableNotificationRow view) {
+ view.reset();
+ view.setHeadsUp(false);
+ view.setOnKeyguard(false);
+ view.setOnAmbient(false);
+ view.setClipBottomAmount(0);
+ view.setClipTopAmount(0);
+ view.setContentTransformationAmount(0, false);
+ view.setIconsVisible(true);
+
+ // TODO - Need to reset this (and others) when view goes back in shade, leave for now
+ // view.setTopRoundness(1, false);
+ // view.setBottomRoundness(1, false);
+
+ ExpandableViewState viewState = view.getViewState();
+ viewState = viewState == null ? new ExpandableViewState() : viewState;
+ viewState.height = view.getIntrinsicHeight();
+ viewState.gone = false;
+ viewState.hidden = false;
+ viewState.dimmed = false;
+ viewState.dark = false;
+ viewState.alpha = 1f;
+ viewState.shadowAlpha = 1f;
+ viewState.notGoneIndex = -1;
+ viewState.xTranslation = 0;
+ viewState.yTranslation = 0;
+ viewState.zTranslation = 0;
+ viewState.scaleX = 1;
+ viewState.scaleY = 1;
+ viewState.inShelf = true;
+ viewState.headsUpIsVisible = false;
+ viewState.applyToView(view);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
new file mode 100644
index 0000000..88030ee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bubbles;
+
+import static com.android.systemui.pip.phone.PipDismissViewController.SHOW_TARGET_DELAY;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.os.Handler;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.pip.phone.PipDismissViewController;
+
+/**
+ * Handles interpreting touches on a {@link BubbleStackView}. This includes expanding, collapsing,
+ * dismissing, and flings.
+ */
+public class BubbleTouchHandler implements View.OnTouchListener {
+
+ private BubbleController mController = Dependency.get(BubbleController.class);
+ private PipDismissViewController mDismissViewController;
+ private BubbleMovementHelper mMovementHelper;
+
+ // The position of the bubble on down event
+ private int mBubbleDownPosX;
+ private int mBubbleDownPosY;
+ // The touch position on down event
+ private int mDownX = -1;
+ private int mDownY = -1;
+
+ private boolean mMovedEnough;
+ private int mTouchSlopSquared;
+ private float mMinFlingVelocity;
+ private VelocityTracker mVelocityTracker;
+
+ private boolean mInDismissTarget;
+ private Handler mHandler = new Handler();
+ private Runnable mShowDismissAffordance = new Runnable() {
+ @Override
+ public void run() {
+ mDismissViewController.showDismissTarget();
+ }
+ };
+
+ // Bubble being dragged from the row of bubbles when the stack is expanded
+ private BubbleView mBubbleDraggingOut;
+
+ /**
+ * Views movable by this touch handler should implement this interface.
+ */
+ public interface FloatingView {
+
+ /**
+ * Sets the position of the view.
+ */
+ void setPosition(int x, int y);
+
+ /**
+ * Sets the x position of the view.
+ */
+ void setPositionX(int x);
+
+ /**
+ * Sets the y position of the view.
+ */
+ void setPositionY(int y);
+
+ /**
+ * @return the position of the view.
+ */
+ Point getPosition();
+ }
+
+ public BubbleTouchHandler(Context context) {
+ final int touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ mTouchSlopSquared = touchSlop * touchSlop;
+
+ // Multiply by 3 for better fling
+ mMinFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity() * 3;
+
+ mMovementHelper = new BubbleMovementHelper(context);
+ mDismissViewController = new PipDismissViewController(context);
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ int action = event.getActionMasked();
+
+ BubbleStackView stack = (BubbleStackView) v;
+ View targetView = mBubbleDraggingOut != null
+ ? mBubbleDraggingOut
+ : stack.getTargetView(event);
+ boolean isFloating = targetView instanceof FloatingView;
+ if (!isFloating || targetView == null || action == MotionEvent.ACTION_OUTSIDE) {
+ stack.animateExpansion(false /* shouldExpand */);
+ cleanUpDismissTarget();
+ resetTouches();
+ return false;
+ }
+
+ FloatingView floatingView = (FloatingView) targetView;
+ boolean isBubbleStack = floatingView instanceof BubbleStackView;
+
+ Point startPos = floatingView.getPosition();
+ int rawX = (int) event.getRawX();
+ int rawY = (int) event.getRawY();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ trackMovement(event);
+
+ mDismissViewController.createDismissTarget();
+ mHandler.postDelayed(mShowDismissAffordance, SHOW_TARGET_DELAY);
+
+ mBubbleDownPosX = startPos.x;
+ mBubbleDownPosY = startPos.y;
+ mDownX = rawX;
+ mDownY = rawY;
+ mMovedEnough = false;
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ trackMovement(event);
+
+ if (mBubbleDownPosX == -1 || mDownX == -1) {
+ mBubbleDownPosX = startPos.x;
+ mBubbleDownPosY = startPos.y;
+ mDownX = rawX;
+ mDownY = rawY;
+ }
+ final int deltaX = rawX - mDownX;
+ final int deltaY = rawY - mDownY;
+ if ((deltaX * deltaX) + (deltaY * deltaY) > mTouchSlopSquared && !mMovedEnough) {
+ mMovedEnough = true;
+ }
+ int x = mBubbleDownPosX + rawX - mDownX;
+ int y = mBubbleDownPosY + rawY - mDownY;
+
+ if (mMovedEnough) {
+ if (floatingView instanceof BubbleView && mBubbleDraggingOut == null) {
+ mBubbleDraggingOut = ((BubbleView) floatingView);
+ }
+ floatingView.setPosition(x, y);
+ }
+ // TODO - when we're in the target stick to it / animate in some way?
+ mInDismissTarget = mDismissViewController.updateTarget((View) floatingView);
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ resetTouches();
+ cleanUpDismissTarget();
+ break;
+
+ case MotionEvent.ACTION_UP:
+ trackMovement(event);
+ if (mInDismissTarget) {
+ if (isBubbleStack) {
+ mController.dismissStack();
+ } else {
+ mController.removeBubble(((BubbleView) floatingView).getKey());
+ }
+ } else if (mMovedEnough) {
+ mVelocityTracker.computeCurrentVelocity(1000);
+ final float velX = mVelocityTracker.getXVelocity();
+ final float velY = mVelocityTracker.getYVelocity();
+ if (isBubbleStack) {
+ if ((Math.abs(velY) > mMinFlingVelocity)
+ || (Math.abs(velX) > mMinFlingVelocity)) {
+ // It's being flung somewhere
+ mMovementHelper.animateFlingTo(stack, velX, velY).start();
+ } else {
+ // Magnet back to nearest edge
+ mMovementHelper.animateMagnetTo(stack).start();
+ }
+ } else {
+ // Individual bubble got dragged but not dismissed.. lets animate it back
+ // into position
+ Point toGoTo = (Point) ((View) floatingView).getTag();
+ mMovementHelper.getTranslateAnim(floatingView, toGoTo, 100, 0).start();
+ }
+ } else if (floatingView.equals(stack.getExpandedBubble())) {
+ stack.animateExpansion(false /* shouldExpand */);
+ } else if (isBubbleStack) {
+ stack.animateExpansion(!stack.isExpanded() /* shouldExpand */);
+ } else {
+ stack.setExpandedBubble((BubbleView) floatingView);
+ }
+ cleanUpDismissTarget();
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ resetTouches();
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * Removes the dismiss target and cancels any pending callbacks to show it.
+ */
+ private void cleanUpDismissTarget() {
+ mHandler.removeCallbacks(mShowDismissAffordance);
+ mDismissViewController.destroyDismissTarget();
+ }
+
+ /**
+ * Resets anything we care about after a gesture is complete.
+ */
+ private void resetTouches() {
+ mDownX = -1;
+ mDownY = -1;
+ mBubbleDownPosX = -1;
+ mBubbleDownPosY = -1;
+ mBubbleDraggingOut = null;
+ }
+
+ private void trackMovement(MotionEvent event) {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(event);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
new file mode 100644
index 0000000..a79e047
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bubbles;
+
+import android.app.Notification;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.OvalShape;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import com.android.internal.util.ContrastColorUtil;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+
+/**
+ * A floating object on the screen that has a collapsed and expanded state.
+ */
+public class BubbleView extends LinearLayout implements BubbleTouchHandler.FloatingView {
+ private static final String TAG = "BubbleView";
+
+ private Context mContext;
+ private View mIconView;
+
+ private NotificationData.Entry mEntry;
+ private int mBubbleSize;
+ private int mIconSize;
+
+ public BubbleView(Context context) {
+ this(context, null);
+ }
+
+ public BubbleView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public BubbleView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public BubbleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ setOrientation(LinearLayout.VERTICAL);
+ mContext = context;
+ mBubbleSize = getResources().getDimensionPixelSize(R.dimen.bubble_size);
+ mIconSize = getResources().getDimensionPixelSize(R.dimen.bubble_icon_size);
+ }
+
+ /**
+ * Populates this view with a notification.
+ *
+ * @param entry the notification to display as a bubble.
+ */
+ public void setNotif(NotificationData.Entry entry) {
+ removeAllViews();
+ // TODO: migrate to inflater
+ mIconView = new ImageView(mContext);
+ addView(mIconView);
+
+ LinearLayout.LayoutParams iconLp = (LinearLayout.LayoutParams) mIconView.getLayoutParams();
+ iconLp.width = mBubbleSize;
+ iconLp.height = mBubbleSize;
+ mIconView.setLayoutParams(iconLp);
+
+ update(entry);
+ }
+
+ /**
+ * Updates the UI based on the entry.
+ */
+ public void update(NotificationData.Entry entry) {
+ mEntry = entry;
+ Notification n = entry.notification.getNotification();
+ Icon ic = n.getLargeIcon() != null ? n.getLargeIcon() : n.getSmallIcon();
+
+ if (n.getLargeIcon() == null) {
+ createCircledIcon(n.color, ic, ((ImageView) mIconView));
+ } else {
+ ((ImageView) mIconView).setImageIcon(ic);
+ }
+ }
+
+ /**
+ * @return the key identifying this bubble / notification entry associated with this
+ * bubble, if it exists.
+ */
+ public String getKey() {
+ return mEntry == null ? null : mEntry.key;
+ }
+
+ /**
+ * @return the notification entry associated with this bubble.
+ */
+ public NotificationData.Entry getEntry() {
+ return mEntry;
+ }
+
+ /**
+ * @return the view to display when the bubble is expanded.
+ */
+ public ExpandableNotificationRow getRowView() {
+ return mEntry.row;
+ }
+
+ @Override
+ public void setPosition(int x, int y) {
+ setTranslationX(x);
+ setTranslationY(y);
+ }
+
+ @Override
+ public void setPositionX(int x) {
+ setTranslationX(x);
+ }
+
+ @Override
+ public void setPositionY(int y) {
+ setTranslationY(y);
+ }
+
+ @Override
+ public Point getPosition() {
+ return new Point((int) getTranslationX(), (int) getTranslationY());
+ }
+
+ // Seems sub optimal
+ private void createCircledIcon(int tint, Icon icon, ImageView v) {
+ // TODO: dark mode
+ icon.setTint(Color.WHITE);
+ icon.scaleDownIfNecessary(mIconSize, mIconSize);
+ v.setImageDrawable(icon.loadDrawable(mContext));
+ v.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+ LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) v.getLayoutParams();
+ int color = ContrastColorUtil.ensureContrast(tint, Color.WHITE,
+ false /* isBgDarker */, 3);
+ Drawable d = new ShapeDrawable(new OvalShape());
+ d.setTint(color);
+ v.setBackgroundDrawable(d);
+
+ lp.width = mBubbleSize;
+ lp.height = mBubbleSize;
+ v.setLayoutParams(lp);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
index 96af08b..50fefe9 100644
--- a/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
@@ -17,9 +17,9 @@
import android.content.Context;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
public class CarNotificationEntryManager extends NotificationEntryManager {
public CarNotificationEntryManager(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
index e4b2e07..09c000b 100644
--- a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
@@ -23,10 +23,10 @@
import com.android.systemui.Dependency.DependencyProvider;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.car.CarFacetButtonController;
import com.android.systemui.statusbar.car.CarStatusBarKeyguardViewManager;
import com.android.systemui.statusbar.car.hvac.HvacController;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/chooser/ChooserActivity.java b/packages/SystemUI/src/com/android/systemui/chooser/ChooserActivity.java
index 158deb4..28a3808 100644
--- a/packages/SystemUI/src/com/android/systemui/chooser/ChooserActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/chooser/ChooserActivity.java
@@ -17,16 +17,7 @@
package com.android.systemui.chooser;
import android.app.Activity;
-import android.app.ActivityManager;
-import android.content.Intent;
import android.os.Bundle;
-import android.os.IBinder;
-import android.util.Log;
-
-import com.android.systemui.R;
-
-import java.lang.Thread;
-import java.util.ArrayList;
/**
* Activity for selecting which application ought to handle an ACTION_SEND intent.
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/AnglesClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/AnglesClassifier.java
index cdf4ba7..6d13973 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/AnglesClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/AnglesClassifier.java
@@ -18,7 +18,6 @@
import android.os.Build;
import android.os.SystemProperties;
-import android.util.Log;
import android.view.MotionEvent;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingLog.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingLog.java
index 71ddba5..cb7c998 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingLog.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingLog.java
@@ -23,8 +23,6 @@
import android.util.Log;
import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java
index 592a275..577d57a 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java
@@ -24,13 +24,11 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.util.DisplayMetrics;
-import android.util.Log;
import android.view.MotionEvent;
import com.android.systemui.R;
import java.util.ArrayDeque;
-import java.util.ArrayList;
/**
* An classifier trying to determine whether it is a human interacting with the phone or not.
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index e2047bf..d93ed178 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -22,7 +22,6 @@
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.os.Handler;
-import android.os.PowerManager;
import com.android.internal.hardware.AmbientDisplayConfiguration;
import com.android.keyguard.KeyguardUpdateMonitor;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index ce84b84..01a2345 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -33,8 +33,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dependency;
-import java.util.concurrent.atomic.AtomicBoolean;
-
/**
* Controls the screen brightness when dozing.
*/
@@ -66,7 +64,6 @@
* --ei brightness_bucket 1}
*/
private int mDebugBrightnessBucket = -1;
- private AtomicBoolean mIsDestroyed = new AtomicBoolean();
@VisibleForTesting
public DozeScreenBrightness(Context context, DozeMachine.Service service,
@@ -89,9 +86,7 @@
Dependency.get(Dependency.BG_HANDLER).post(()-> {
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_AOD_BRIGHTNESS);
- if (!mIsDestroyed.get()) {
- mContext.registerReceiverAsUser(this, UserHandle.ALL, filter, null, handler);
- }
+ mContext.registerReceiverAsUser(this, UserHandle.ALL, filter, null, handler);
});
}
}
@@ -129,10 +124,11 @@
}
private void onDestroy() {
- mIsDestroyed.set(true);
setLightSensorEnabled(false);
if (mDebuggable) {
- mContext.unregisterReceiver(this);
+ Dependency.get(Dependency.BG_HANDLER).post(()-> {
+ mContext.unregisterReceiver(this);
+ });
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
index 4fc2d9b..25c2c39 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
@@ -16,9 +16,10 @@
package com.android.systemui.doze;
-import androidx.annotation.VisibleForTesting;
import android.view.Display;
+import androidx.annotation.VisibleForTesting;
+
import com.android.systemui.statusbar.phone.DozeParameters;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index c61e10a..4557b4d 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -38,6 +38,7 @@
private DozeMachine mDozeMachine;
private DozeServicePlugin mDozePlugin;
+ private PluginManager mPluginManager;
public DozeService() {
setDebug(DEBUG);
@@ -53,14 +54,14 @@
finish();
return;
}
- Dependency.get(PluginManager.class).addPluginListener(this,
- DozeServicePlugin.class, false /* Allow multiple */);
+ mPluginManager = Dependency.get(PluginManager.class);
+ mPluginManager.addPluginListener(this, DozeServicePlugin.class, false /* allowMultiple */);
mDozeMachine = new DozeFactory().assembleMachine(this);
}
@Override
public void onDestroy() {
- Dependency.get(PluginManager.class).removePluginListener(this);
+ mPluginManager.removePluginListener(this);
super.onDestroy();
mDozeMachine = null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java
index 3013b96..a0c490951 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java
@@ -16,9 +16,10 @@
package com.android.systemui.doze;
-import androidx.annotation.VisibleForTesting;
import android.view.Display;
+import androidx.annotation.VisibleForTesting;
+
import com.android.systemui.statusbar.phone.DozeParameters;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
index 47f86fe..9a5a5b8 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
@@ -23,8 +23,6 @@
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.statusbar.phone.DozeParameters;
import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/egg/MLandActivity.java b/packages/SystemUI/src/com/android/systemui/egg/MLandActivity.java
index f06ea45..84b91bc 100644
--- a/packages/SystemUI/src/com/android/systemui/egg/MLandActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/egg/MLandActivity.java
@@ -19,7 +19,6 @@
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
-import android.view.ViewGroup;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
index 779a86c..60e39b2 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
@@ -28,11 +28,12 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Parcelable;
-import androidx.annotation.NonNull;
import android.util.ArrayMap;
import android.view.LayoutInflater;
import android.view.View;
+import androidx.annotation.NonNull;
+
import com.android.settingslib.applications.InterestingConfigChanges;
import com.android.systemui.Dependency;
import com.android.systemui.plugins.Plugin;
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
index e0657c9..7d52a9a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -43,12 +43,12 @@
import android.widget.Toast;
import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
-import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java b/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java
index d833c16..7bec5c0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java
@@ -19,6 +19,7 @@
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.systemui.Dependency;
import com.android.systemui.UiOffloadThread;
+
import java.util.ArrayList;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
index 4c98c08..b3481c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
@@ -19,8 +19,6 @@
import android.os.Handler;
import android.os.Message;
-import com.android.internal.policy.IKeyguardDrawnCallback;
-
/**
* Dispatches the lifecycles keyguard gets from WindowManager on the main thread.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 22b41a4..81247cd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -16,6 +16,8 @@
package com.android.systemui.keyguard;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
@@ -34,8 +36,6 @@
import com.android.systemui.Dependency;
import com.android.systemui.SystemUIApplication;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
public class KeyguardService extends Service {
static final String TAG = "KeyguardService";
static final String PERMISSION = android.Manifest.permission.CONTROL_KEYGUARD;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index fe1b356..3b9110d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1940,11 +1940,6 @@
mContext.getSystemService(Context.STATUS_BAR_SERVICE);
}
- // TODO(b/113914868): investigation log for disappearing home button
- Log.d(TAG, "adjustStatusBarLocked (b/113914868): mShowing=" + mShowing
- + " mStatusBarManager=" + mStatusBarManager + " mOccluded="
- + mOccluded + " isSecure=" + isSecure() + " force=" + forceHideHomeRecentsButtons);
-
if (mStatusBarManager == null) {
Log.w(TAG, "Could not get status bar manager");
} else {
@@ -1961,6 +1956,12 @@
+ " --> flags=0x" + Integer.toHexString(flags));
}
+ // TODO(b/113914868): investigation log for disappearing home button
+ Log.d(TAG, "adjustStatusBarLocked (b/113914868): flags=" + flags
+ + "mShowing=" + mShowing + " mStatusBarManager=" + mStatusBarManager
+ + " mOccluded=" + mOccluded + " isSecure=" + isSecure()
+ + " force=" + forceHideHomeRecentsButtons);
+
mStatusBarManager.disable(flags);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
index 74f7706..6498b91 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
@@ -28,6 +28,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index ab612dd..ddd9cbf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
@@ -33,10 +33,8 @@
import android.os.ServiceManager;
import android.os.UserHandle;
import android.provider.MediaStore;
-import android.provider.MediaStore.Audio.AudioColumns;
import android.util.Log;
-import com.android.internal.util.Preconditions;
import com.android.systemui.SystemUI;
import java.io.FileDescriptor;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipUI.java b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
index 70b581a..7792e17 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
@@ -23,8 +23,10 @@
import android.content.res.Configuration;
import android.os.UserHandle;
import android.os.UserManager;
+
import com.android.systemui.SystemUI;
import com.android.systemui.statusbar.CommandQueue;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
index 9ce2606..8262b54 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
@@ -21,21 +21,27 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.widget.FrameLayout;
+
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.shared.system.WindowManagerWrapper;
+/**
+ * Displays the dismiss UI and target for floating objects.
+ */
public class PipDismissViewController {
// This delay controls how long to wait before we show the target when the user first moves
// the PIP, to prevent the target from animating if the user just wants to fling the PIP
- private static final int SHOW_TARGET_DELAY = 100;
+ public static final int SHOW_TARGET_DELAY = 100;
private static final int SHOW_TARGET_DURATION = 350;
private static final int HIDE_TARGET_DURATION = 225;
@@ -43,9 +49,16 @@
private WindowManager mWindowManager;
private View mDismissView;
+ // Used for dismissing a bubble -- bubble should be in the target to be considered a dismiss
+ private View mTargetView;
+ private int[] mLoc = new int[2];
+ private boolean mIntersecting;
+ private Vibrator mVibe;
+
public PipDismissViewController(Context context) {
mContext = context;
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ mVibe = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
}
/**
@@ -75,9 +88,10 @@
mDismissView.setBackground(gradient);
// Adjust bottom margins of the text
- View text = mDismissView.findViewById(R.id.pip_dismiss_text);
- FrameLayout.LayoutParams tlp = (FrameLayout.LayoutParams) text.getLayoutParams();
+ mTargetView = mDismissView.findViewById(R.id.pip_dismiss_text);
+ FrameLayout.LayoutParams tlp = (FrameLayout.LayoutParams) mTargetView.getLayoutParams();
tlp.bottomMargin = stableInsets.bottom + bottomMargin;
+ mTargetView.setLayoutParams(tlp);
// Add the target to the window
LayoutParams lp = new LayoutParams(
@@ -96,6 +110,36 @@
mDismissView.animate().cancel();
}
+
+ /**
+ * Updates the dismiss target based on location of the view.
+ *
+ * @return whether the view is within the dismiss target.
+ */
+ public boolean updateTarget(View view) {
+ if (mDismissView == null) {
+ return false;
+ }
+ if (mDismissView.getAlpha() > 0) {
+ view.getLocationOnScreen(mLoc);
+ Rect viewRect = new Rect(mLoc[0], mLoc[1], mLoc[0] + view.getWidth(),
+ mLoc[1] + view.getHeight());
+ mTargetView.getLocationOnScreen(mLoc);
+ Rect targetRect = new Rect(mLoc[0], mLoc[1], mLoc[0] + mTargetView.getWidth(),
+ mLoc[1] + mTargetView.getHeight());
+ boolean intersecting = targetRect.intersect(viewRect);
+ if (intersecting && !mIntersecting) {
+ // TODO: is this the right effect?
+ mVibe.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
+ mIntersecting = true;
+ }
+ mIntersecting = intersecting;
+ return intersecting;
+ }
+ return false;
+ }
+
+
/**
* Shows the dismiss target.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 08208e5..3346ad2 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -35,6 +35,7 @@
import android.view.IPinnedStackListener;
import android.view.IWindowManager;
import android.view.WindowManagerGlobal;
+
import com.android.systemui.Dependency;
import com.android.systemui.UiOffloadThread;
import com.android.systemui.pip.BasePipManager;
@@ -42,6 +43,7 @@
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.WindowManagerWrapper;
+
import java.io.PrintWriter;
/**
@@ -87,7 +89,6 @@
final Pair<ComponentName, Integer> topPipActivityInfo = PipUtils.getTopPinnedActivity(
mContext, mActivityManager);
final ComponentName topActivity = topPipActivityInfo.first;
- final int userId = topActivity != null ? topPipActivityInfo.second : 0;
mMenuController.onActivityUnpinned();
mTouchHandler.onActivityUnpinned(topActivity);
mAppOpsListener.onActivityUnpinned();
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index b746c19..e447def 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -23,15 +23,14 @@
import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ACTIONS;
import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ALLOW_TIMEOUT;
import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER;
-import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_WILL_RESIZE_MENU;
import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_DISMISS_FRACTION;
-import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_MOVEMENT_BOUNDS;
import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_MENU_STATE;
+import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_MOVEMENT_BOUNDS;
import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_STACK_BOUNDS;
-
-import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE;
+import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_WILL_RESIZE_MENU;
import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE;
import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL;
+import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
index 56b8324..46d53e4 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -37,8 +37,10 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Log;
+
import com.android.systemui.pip.phone.PipMediaController.ActionListener;
import com.android.systemui.shared.system.InputConsumerController;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index ce7da79..3858356 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -18,6 +18,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
import static com.android.systemui.Interpolators.FAST_OUT_LINEAR_IN;
import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.systemui.Interpolators.LINEAR_OUT_SLOW_IN;
@@ -42,11 +43,13 @@
import android.os.RemoteException;
import android.util.Log;
import android.view.animation.Interpolator;
+
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.internal.os.SomeArgs;
import com.android.internal.policy.PipSnapAlgorithm;
import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.systemui.statusbar.FlingAnimationUtils;
+
import java.io.PrintWriter;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
index b9369d3..9aa21f8 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
@@ -18,7 +18,6 @@
import android.graphics.PointF;
import android.os.Handler;
-import android.os.SystemClock;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
index 10206d4..a40b72b 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
@@ -16,7 +16,6 @@
package com.android.systemui.pip.tv;
-import android.app.ActivityManager;
import android.app.PendingIntent.CanceledException;
import android.app.RemoteAction;
import android.content.Context;
@@ -24,20 +23,15 @@
import android.media.session.MediaController;
import android.media.session.PlaybackState;
import android.os.Handler;
-import android.os.RemoteException;
+import android.util.AttributeSet;
import android.util.Log;
-import android.view.View;
import android.view.Gravity;
import android.view.LayoutInflater;
-import android.widget.ImageView;
+import android.view.View;
import android.widget.LinearLayout;
-import android.util.AttributeSet;
import com.android.systemui.R;
-import static android.media.session.PlaybackState.ACTION_PAUSE;
-import static android.media.session.PlaybackState.ACTION_PLAY;
-
import java.util.ArrayList;
import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index e17e0bc..ca3cdf4 100755
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -49,6 +49,7 @@
import android.view.IPinnedStackListener;
import android.view.IWindowManager;
import android.view.WindowManagerGlobal;
+
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.UiOffloadThread;
@@ -56,7 +57,7 @@
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.WindowManagerWrapper;
-import java.io.PrintWriter;
+
import java.util.ArrayList;
import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
index e437eff..3a5fa22 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
@@ -22,7 +22,6 @@
import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.os.Bundle;
-import android.view.View;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
index ac41b75..89ecc6a 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
@@ -26,17 +26,15 @@
import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.drawable.Icon;
import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.PlaybackState;
import android.text.TextUtils;
import android.util.Log;
-import android.view.View;
-import com.android.systemui.util.NotificationChannels;
-import com.android.systemui.R;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.systemui.R;
+import com.android.systemui.util.NotificationChannels;
/**
* A notification that informs users that PIP is running and also provides PIP controls.
diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
index 5686d80..3f24176 100644
--- a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
@@ -1,7 +1,5 @@
package com.android.systemui.power;
-import android.util.Log;
-
public class EnhancedEstimatesImpl implements EnhancedEstimates {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 21eab59..b722f9f 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -41,6 +41,7 @@
import android.util.Log;
import android.util.Slog;
import android.view.View;
+
import androidx.annotation.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
index fc1baef..d3715d0 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
@@ -15,7 +15,6 @@
package com.android.systemui.privacy
import android.content.Context
-import android.graphics.Color
import android.util.AttributeSet
import android.view.ViewGroup
import android.widget.ImageView
@@ -30,7 +29,13 @@
defStyleRes: Int = 0
) : LinearLayout(context, attrs, defStyleAttrs, defStyleRes) {
- private lateinit var appName: TextView
+ private val iconMargin =
+ context.resources.getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_margin)
+ private val iconSize =
+ context.resources.getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_size)
+ val iconColor = context.resources.getColor(
+ R.color.status_bar_clock_color, context.theme)
+ private lateinit var text: TextView
private lateinit var iconsContainer: LinearLayout
var builder = PrivacyDialogBuilder(context, emptyList<PrivacyItem>())
var privacyList = emptyList<PrivacyItem>()
@@ -43,7 +48,7 @@
override fun onFinishInflate() {
super.onFinishInflate()
- appName = findViewById(R.id.app_name)
+ text = findViewById(R.id.text_container)
iconsContainer = findViewById(R.id.icons_container)
}
@@ -53,39 +58,52 @@
iconsContainer.removeAllViews()
dialogBuilder.generateIcons().forEach {
it.mutate()
- it.setTint(Color.WHITE)
- iconsContainer.addView(ImageView(context).apply {
+ it.setTint(iconColor)
+ val image = ImageView(context).apply {
setImageDrawable(it)
- maxHeight = this@OngoingPrivacyChip.height
- })
+ scaleType = ImageView.ScaleType.CENTER_INSIDE
+ }
+ iconsContainer.addView(image, iconSize, iconSize)
+ val lp = image.layoutParams as MarginLayoutParams
+ lp.marginStart = iconMargin
+ image.layoutParams = lp
}
}
- if (privacyList.isEmpty()) {
- return
- } else {
+ if (!privacyList.isEmpty()) {
generateContentDescription()
setIcons(builder, iconsContainer)
- appName.visibility = GONE
- builder.app?.let {
- appName.apply {
- setText(it.applicationName)
- setTextColor(Color.WHITE)
- visibility = VISIBLE
+ text.visibility = if (builder.types.size == 1) VISIBLE else GONE
+ if (builder.types.size == 1) {
+ if (builder.app != null) {
+ text.setText(builder.app?.applicationName)
+ } else {
+ text.text = context.getString(R.string.ongoing_privacy_chip_multiple_apps,
+ builder.appsAndTypes.size)
}
}
+ } else {
+ text.visibility = GONE
+ iconsContainer.removeAllViews()
}
requestLayout()
}
private fun generateContentDescription() {
- val typesText = builder.generateTypesText()
- if (builder.app != null) {
- contentDescription = context.getString(R.string.ongoing_privacy_chip_content_single_app,
- builder.app?.applicationName, typesText)
- } else {
+ val typesText = builder.joinTypes()
+ if (builder.types.size > 1) {
contentDescription = context.getString(
R.string.ongoing_privacy_chip_content_multiple_apps, typesText)
+ } else {
+ if (builder.app != null) {
+ contentDescription =
+ context.getString(R.string.ongoing_privacy_chip_content_single_app,
+ builder.app?.applicationName, typesText)
+ } else {
+ contentDescription = context.getString(
+ R.string.ongoing_privacy_chip_content_multiple_apps_single_op,
+ builder.appsAndTypes.size, typesText)
+ }
}
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
index 1d0e16e..f6a95af 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
@@ -18,10 +18,10 @@
import android.app.Dialog
import android.content.Context
import android.content.DialogInterface
-import android.graphics.drawable.Drawable
+import android.content.Intent
+import android.content.res.ColorStateList
import android.view.LayoutInflater
import android.view.View
-import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
@@ -34,29 +34,25 @@
val dialogBuilder: PrivacyDialogBuilder
) {
- val iconHeight = context.resources.getDimensionPixelSize(
+ val iconSize = context.resources.getDimensionPixelSize(
R.dimen.ongoing_appops_dialog_icon_height)
- val textMargin = context.resources.getDimensionPixelSize(
- R.dimen.ongoing_appops_dialog_text_margin)
val iconColor = context.resources.getColor(
com.android.internal.R.color.text_color_primary, context.theme)
+ companion object {
+ private const val MAX_ITEMS = 10
+ }
fun createDialog(): Dialog {
- val builder = AlertDialog.Builder(context)
- .setNeutralButton(R.string.ongoing_privacy_dialog_open_settings, null)
- if (dialogBuilder.app != null) {
- builder.setPositiveButton(R.string.ongoing_privacy_dialog_open_app,
+ val builder = AlertDialog.Builder(context).apply {
+ setNegativeButton(R.string.ongoing_privacy_dialog_cancel, null)
+ setPositiveButton(R.string.ongoing_privacy_dialog_open_settings,
object : DialogInterface.OnClickListener {
- val intent = context.packageManager
- .getLaunchIntentForPackage(dialogBuilder.app.packageName)
+ val intent = Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE)
override fun onClick(dialog: DialogInterface?, which: Int) {
Dependency.get(ActivityStarter::class.java).startActivity(intent, false)
}
})
- builder.setNegativeButton(R.string.ongoing_privacy_dialog_cancel, null)
- } else {
- builder.setPositiveButton(R.string.ongoing_privacy_dialog_okay, null)
}
builder.setView(getContentView())
return builder.create()
@@ -66,44 +62,67 @@
val layoutInflater = LayoutInflater.from(context)
val contentView = layoutInflater.inflate(R.layout.ongoing_privacy_dialog_content, null)
- val iconsContainer = contentView.findViewById(R.id.icons_container) as LinearLayout
- val textContainer = contentView.findViewById(R.id.text_container) as LinearLayout
+ val title = contentView.findViewById(R.id.title) as TextView
+ val appsList = contentView.findViewById(R.id.items_container) as LinearLayout
- addIcons(dialogBuilder, iconsContainer)
- val lm = ViewGroup.MarginLayoutParams(
- ViewGroup.MarginLayoutParams.WRAP_CONTENT,
- ViewGroup.MarginLayoutParams.WRAP_CONTENT)
- lm.topMargin = textMargin
- val now = System.currentTimeMillis()
- dialogBuilder.generateText(now).forEach {
- val text = layoutInflater.inflate(R.layout.ongoing_privacy_text_item, null) as TextView
- text.setText(it)
- textContainer.addView(text, lm)
+ title.setText(dialogBuilder.getDialogTitle())
+
+ val numItems = dialogBuilder.appsAndTypes.size
+ for (i in 0..(numItems - 1)) {
+ if (i >= MAX_ITEMS) break
+ val item = dialogBuilder.appsAndTypes[i]
+ addAppItem(appsList, item.first, item.second, dialogBuilder.types.size > 1)
}
+
+ if (numItems > MAX_ITEMS) {
+ val overflow = contentView.findViewById(R.id.overflow) as LinearLayout
+ overflow.visibility = View.VISIBLE
+ val overflowText = overflow.findViewById(R.id.app_name) as TextView
+ overflowText.text = context.resources.getQuantityString(
+ R.plurals.ongoing_privacy_dialog_overflow_text,
+ numItems - MAX_ITEMS,
+ numItems - MAX_ITEMS
+ )
+ val overflowPlus = overflow.findViewById(R.id.app_icon) as ImageView
+ overflowPlus.apply {
+ imageTintList = ColorStateList.valueOf(iconColor)
+ setImageDrawable(context.getDrawable(R.drawable.plus))
+ }
+ }
+
return contentView
}
- private fun addIcons(dialogBuilder: PrivacyDialogBuilder, iconsContainer: LinearLayout) {
+ private fun addAppItem(
+ itemList: LinearLayout,
+ app: PrivacyApplication,
+ types: List<PrivacyType>,
+ showIcons: Boolean = true
+ ) {
+ val layoutInflater = LayoutInflater.from(context)
+ val item = layoutInflater.inflate(R.layout.ongoing_privacy_dialog_item, itemList, false)
+ val appIcon = item.findViewById(R.id.app_icon) as ImageView
+ val appName = item.findViewById(R.id.app_name) as TextView
+ val icons = item.findViewById(R.id.icons) as LinearLayout
- fun LinearLayout.addIcon(icon: Drawable) {
- val image = ImageView(context).apply {
- setImageDrawable(icon.apply {
- setBounds(0, 0, iconHeight, iconHeight)
- maxHeight = this@addIcon.height
- })
- adjustViewBounds = true
+ app.icon?.let {
+ appIcon.setImageDrawable(it)
+ }
+
+ appName.text = app.applicationName
+ if (showIcons) {
+ dialogBuilder.generateIconsForApp(types).forEach {
+ it.setBounds(0, 0, iconSize, iconSize)
+ val image = ImageView(context).apply {
+ imageTintList = ColorStateList.valueOf(iconColor)
+ setImageDrawable(it)
+ }
+ icons.addView(image, iconSize, LinearLayout.LayoutParams.WRAP_CONTENT)
}
- addView(image, LinearLayout.LayoutParams.WRAP_CONTENT,
- LinearLayout.LayoutParams.MATCH_PARENT)
+ icons.visibility = View.VISIBLE
+ } else {
+ icons.visibility = View.GONE
}
-
- dialogBuilder.generateIcons().forEach {
- it.mutate()
- it.setTint(iconColor)
- iconsContainer.addIcon(it)
- }
- dialogBuilder.app.let {
- it?.icon?.let { iconsContainer.addIcon(it) }
- }
+ itemList.addView(item)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt
index 2f86f78..519df19 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt
@@ -15,60 +15,53 @@
package com.android.systemui.privacy
import android.content.Context
+import android.graphics.drawable.Drawable
import com.android.systemui.R
-import java.lang.IllegalStateException
-import java.lang.Math.max
class PrivacyDialogBuilder(val context: Context, itemsList: List<PrivacyItem>) {
- companion object {
- val MILLIS_IN_MINUTE: Long = 1000 * 60
- }
- private val itemsByType: Map<PrivacyType, List<PrivacyItem>>
+ val appsAndTypes: List<Pair<PrivacyApplication, List<PrivacyType>>>
+ val types: List<PrivacyType>
val app: PrivacyApplication?
+ private val separator = context.getString(R.string.ongoing_privacy_dialog_separator)
+ private val lastSeparator = context.getString(R.string.ongoing_privacy_dialog_last_separator)
init {
- itemsByType = itemsList.groupBy { it.privacyType }
- val apps = itemsList.map { it.application }.distinct()
- val singleApp = apps.size == 1
- app = if (singleApp) apps.get(0) else null
+ appsAndTypes = itemsList.groupBy({ it.application }, { it.privacyType })
+ .toList()
+ .sortedWith(compareBy({ -it.second.size }, { it.first }))
+ types = itemsList.map { it.privacyType }.distinct().sorted()
+ val singleApp = appsAndTypes.size == 1
+ app = if (singleApp) appsAndTypes[0].first else null
}
- private fun buildTextForItem(type: PrivacyType, now: Long): String {
- val items = itemsByType.getOrDefault(type, emptyList<PrivacyItem>())
- return when (items.size) {
- 0 -> throw IllegalStateException("List cannot be empty")
- 1 -> {
- val item = items.get(0)
- val minutesUsed = max(((now - item.timeStarted) / MILLIS_IN_MINUTE).toInt(), 1)
- context.getString(R.string.ongoing_privacy_dialog_app_item,
- item.application.applicationName, type.getName(context), minutesUsed)
- }
- else -> {
- val apps = items.map { it.application.applicationName }.joinToString()
- context.getString(R.string.ongoing_privacy_dialog_apps_item,
- apps, type.getName(context))
- }
+ fun generateIconsForApp(types: List<PrivacyType>): List<Drawable> {
+ return types.sorted().map { it.getIcon(context) }
+ }
+
+ fun generateIcons() = types.map { it.getIcon(context) }
+
+ private fun <T> List<T>.joinWithAnd(): StringBuilder {
+ return subList(0, size - 1).joinTo(StringBuilder(), separator = separator).apply {
+ append(lastSeparator)
+ append(this@joinWithAnd.last())
}
}
- private fun buildTextForApp(types: Set<PrivacyType>): List<String> {
- app?.let {
- val typesText = types.map { it.getName(context) }.sorted().joinToString()
- return listOf(context.getString(R.string.ongoing_privacy_dialog_single_app,
- it.applicationName, typesText))
- } ?: throw IllegalStateException("There has to be a single app")
+ fun joinTypes(): String {
+ return when (types.size) {
+ 0 -> ""
+ 1 -> types[0].getName(context)
+ else -> types.map { it.getName(context) }.joinWithAnd().toString()
+ }
}
- fun generateText(now: Long): List<String> {
- if (app == null || itemsByType.keys.size == 1) {
- return itemsByType.keys.map { buildTextForItem(it, now) }
+ fun getDialogTitle(): String {
+ if (app != null) {
+ return context.getString(R.string.ongoing_privacy_dialog_single_app_title, joinTypes())
} else {
- return buildTextForApp(itemsByType.keys)
+ return context.getString(R.string.ongoing_privacy_dialog_multiple_apps_title,
+ joinTypes())
}
}
-
- fun generateTypesText() = itemsByType.keys.map { it.getName(context) }.sorted().joinToString()
-
- fun generateIcons() = itemsByType.keys.map { it.getIcon(context) }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
index f409902..85e99f0 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
@@ -23,22 +23,27 @@
typealias Privacy = PrivacyType
enum class PrivacyType(val nameId: Int, val iconId: Int) {
- TYPE_CAMERA(R.string.privacy_type_camera, com.android.internal.R.drawable.ic_camera),
+ TYPE_CAMERA(R.string.privacy_type_camera, R.drawable.stat_sys_camera),
TYPE_LOCATION(R.string.privacy_type_location, R.drawable.stat_sys_location),
- TYPE_MICROPHONE(R.string.privacy_type_microphone, R.drawable.ic_mic_26dp);
+ TYPE_MICROPHONE(R.string.privacy_type_microphone, R.drawable.stat_sys_mic_none);
fun getName(context: Context) = context.resources.getString(nameId)
- fun getIcon(context: Context) = context.resources.getDrawable(iconId, null)
+ fun getIcon(context: Context) = context.resources.getDrawable(iconId, context.theme)
}
data class PrivacyItem(
val privacyType: PrivacyType,
- val application: PrivacyApplication,
- val timeStarted: Long
+ val application: PrivacyApplication
)
-data class PrivacyApplication(val packageName: String, val context: Context) {
+data class PrivacyApplication(val packageName: String, val context: Context)
+ : Comparable<PrivacyApplication> {
+
+ override fun compareTo(other: PrivacyApplication): Int {
+ return applicationName.compareTo(other.applicationName)
+ }
+
var icon: Drawable? = null
var applicationName: String
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index 5141e50..3fa3e8e 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -95,7 +95,7 @@
else -> return null
}
val app = PrivacyApplication(appOpItem.packageName, context)
- return PrivacyItem(type, app, appOpItem.timeStarted)
+ return PrivacyItem(type, app)
}
// Used by containing class to get notified of changes
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AlphaControlledSignalTileView.java b/packages/SystemUI/src/com/android/systemui/qs/AlphaControlledSignalTileView.java
index 2c7ec70..6a6f572 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/AlphaControlledSignalTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/AlphaControlledSignalTileView.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
+
import com.android.systemui.qs.tileimpl.SlashImageView;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoSizingList.java b/packages/SystemUI/src/com/android/systemui/qs/AutoSizingList.java
index 767fd9e..1195184 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/AutoSizingList.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/AutoSizingList.java
@@ -23,6 +23,7 @@
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
+
import com.android.systemui.R;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java b/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java
index 376e6ae..c1aa706 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java
@@ -20,7 +20,6 @@
import android.widget.ImageView;
import com.android.settingslib.graph.SignalDrawable;
-import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.plugins.qs.QSTile.Icon;
import com.android.systemui.plugins.qs.QSTile.State;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java b/packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java
index 6aad479..afce69e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java
@@ -18,7 +18,6 @@
import android.content.Context;
import android.content.res.Resources;
-import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
index aa2f8d1..d5c5ba4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
@@ -6,10 +6,10 @@
import android.graphics.drawable.AnimatedVectorDrawable;
import android.util.AttributeSet;
import android.util.Log;
-import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
+
import com.android.systemui.R;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 1451e71..dbd3042 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -23,14 +23,11 @@
import android.content.res.Configuration;
import android.graphics.Point;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.View;
import android.widget.FrameLayout;
import com.android.systemui.R;
-import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.qs.customize.QSCustomizer;
-import com.android.systemui.statusbar.CommandQueue;
/**
* Wrapper view with background which contains {@link QSPanel} and {@link BaseStatusBarHeader}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
index ddd9910..dab0efe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
@@ -32,6 +32,7 @@
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
+
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
import com.android.systemui.plugins.qs.QSTile;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index 84eb3da5..b597a72 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -15,9 +15,10 @@
*/
package com.android.systemui.qs;
-import androidx.annotation.Nullable;
import android.view.View;
+import androidx.annotation.Nullable;
+
/**
* The bottom footer of the quick settings panel.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index f147fb3..8903a38 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -28,8 +28,6 @@
import android.graphics.drawable.RippleDrawable;
import android.os.Bundle;
import android.os.UserManager;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
@@ -40,6 +38,9 @@
import android.widget.LinearLayout;
import android.widget.Toast;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.keyguard.CarrierText;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index f9971d8..953eb70 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -22,8 +22,6 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
@@ -34,6 +32,9 @@
import android.view.ViewTreeObserver;
import android.widget.FrameLayout.LayoutParams;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -42,9 +43,9 @@
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.qs.customize.QSCustomizer;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
-import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
public class QSFragment extends Fragment implements QS, CommandQueue.Callbacks {
private static final String TAG = "QS";
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index cf63e47..f1f0f69 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -17,6 +17,7 @@
package com.android.systemui.qs;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
import static com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState;
import android.annotation.Nullable;
@@ -30,7 +31,6 @@
import android.os.Message;
import android.service.quicksettings.Tile;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java b/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
index 7ebab0b..001cbba 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
@@ -17,7 +17,6 @@
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
-import androidx.core.widget.NestedScrollView;
import android.util.Property;
import android.view.MotionEvent;
import android.view.View;
@@ -25,6 +24,8 @@
import android.view.ViewParent;
import android.widget.LinearLayout;
+import androidx.core.widget.NestedScrollView;
+
import com.android.systemui.R;
import com.android.systemui.qs.touch.OverScroll;
import com.android.systemui.qs.touch.SwipeDetector;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index 8f3a7b3..e2e943a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -33,8 +33,8 @@
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
import android.view.View.OnClickListener;
+import android.view.ViewGroup;
import android.view.Window;
import android.widget.ImageView;
import android.widget.TextView;
@@ -46,8 +46,6 @@
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.SecurityController;
-import static android.provider.Settings.ACTION_VPN_SETTINGS;
-
public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListener {
protected static final String TAG = "QSSecurityFooter";
protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index cefeeb5..d1c2df5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -31,14 +31,14 @@
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.plugins.qs.QSFactory;
-import com.android.systemui.plugins.qs.QSTileView;
import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.plugins.qs.QSTileView;
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.external.TileLifecycleManager;
import com.android.systemui.qs.external.TileServices;
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
+import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarIconController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index e3f85d9..427f638 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -325,15 +325,10 @@
newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE;
mBatteryMeterView.useWallpaperTextColor(shouldUseWallpaperTextColor);
mClockView.useWallpaperTextColor(shouldUseWallpaperTextColor);
-
- MarginLayoutParams lm = (MarginLayoutParams) mPrivacyChip.getLayoutParams();
- int sideMargins = lm.leftMargin;
- int topBottomMargins = (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
- ? 0 : sideMargins;
- lm.setMargins(sideMargins, topBottomMargins, sideMargins, topBottomMargins);
- mPrivacyChip.setLayoutParams(lm);
}
+
+
@Override
public void onRtlPropertiesChanged(int layoutDirection) {
super.onRtlPropertiesChanged(layoutDirection);
@@ -378,6 +373,15 @@
setLayoutParams(lp);
+ if (mPrivacyChip != null) {
+ MarginLayoutParams lm = (MarginLayoutParams) mPrivacyChip.getLayoutParams();
+ int sideMargins = lm.leftMargin;
+ int topBottomMargins = resources.getDimensionPixelSize(
+ R.dimen.ongoing_appops_top_chip_margin);
+ lm.setMargins(sideMargins, topBottomMargins, sideMargins, topBottomMargins);
+ mPrivacyChip.setLayoutParams(lm);
+ }
+
updateStatusIconAlphaAnimator();
updateHeaderTextContainerAlphaAnimator();
}
@@ -729,7 +733,8 @@
public void setMargins(int sideMargins) {
for (int i = 0; i < getChildCount(); i++) {
View v = getChildAt(i);
- if (v == mSystemIconsView || v == mQuickQsStatusIcons || v == mHeaderQsPanel) {
+ if (v == mSystemIconsView || v == mQuickQsStatusIcons || v == mHeaderQsPanel
+ || v == mPrivacyChip) {
continue;
}
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) v.getLayoutParams();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java
index f6b08b0..0389030 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java
@@ -16,14 +16,14 @@
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
-import androidx.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
-import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
+import androidx.annotation.Nullable;
+
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFragment.java
index 2d9e4d7..3e82c54 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFragment.java
@@ -22,14 +22,15 @@
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-import androidx.recyclerview.widget.GridLayoutManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.recyclerview.widget.GridLayoutManager;
+
import com.android.systemui.R;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.qs.QSFooter;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/car/CarStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/car/CarStatusBarHeader.java
index 9b225bb..083a747 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/car/CarStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/car/CarStatusBarHeader.java
@@ -16,11 +16,12 @@
import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
-import androidx.annotation.IdRes;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
+import androidx.annotation.IdRes;
+
import com.android.settingslib.Utils;
import com.android.systemui.BatteryMeterView;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index dc17dd8..64ad95c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -20,13 +20,8 @@
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.content.res.Configuration;
-import android.graphics.Point;
import android.os.Bundle;
-import androidx.recyclerview.widget.DefaultItemAnimator;
-import androidx.recyclerview.widget.GridLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
import android.util.AttributeSet;
-import android.util.Log;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
@@ -37,6 +32,10 @@
import android.widget.Toolbar;
import android.widget.Toolbar.OnMenuItemClickListener;
+import androidx.recyclerview.widget.DefaultItemAnimator;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settingslib.Utils;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index 15d2e66..a29e93a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -23,13 +23,6 @@
import android.graphics.Canvas;
import android.graphics.drawable.ColorDrawable;
import android.os.Handler;
-import androidx.core.view.ViewCompat;
-import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerView.ItemDecoration;
-import androidx.recyclerview.widget.RecyclerView.State;
-import androidx.recyclerview.widget.RecyclerView.ViewHolder;
-import androidx.recyclerview.widget.ItemTouchHelper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
@@ -39,6 +32,14 @@
import android.widget.FrameLayout;
import android.widget.TextView;
+import androidx.core.view.ViewCompat;
+import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup;
+import androidx.recyclerview.widget.ItemTouchHelper;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.ItemDecoration;
+import androidx.recyclerview.widget.RecyclerView.State;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index bb65bed..8906665 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -15,6 +15,9 @@
*/
package com.android.systemui.qs.external;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
+
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Intent;
@@ -41,13 +44,11 @@
import com.android.systemui.Dependency;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile.State;
-import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener;
import com.android.systemui.qs.QSTileHost;
-import java.util.Objects;
+import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
+import java.util.Objects;
public class CustomTile extends QSTileImpl<State> implements TileChangeListener {
public static final String PREFIX = "custom(";
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java b/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java
index 451e1f6..2345667 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java
@@ -16,9 +16,7 @@
package com.android.systemui.qs.external;
import android.os.IBinder;
-import android.service.quicksettings.IQSService;
import android.service.quicksettings.IQSTileService;
-import android.service.quicksettings.Tile;
import android.util.Log;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileColorPicker.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileColorPicker.java
index 75dd0d9..1caab5a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileColorPicker.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileColorPicker.java
@@ -18,7 +18,9 @@
import android.content.Context;
import android.content.res.ColorStateList;
import android.service.quicksettings.Tile;
+
import androidx.annotation.VisibleForTesting;
+
import com.android.systemui.R;
public class TileColorPicker {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index 4e0f38f..305fbf2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -15,15 +15,14 @@
*/
package com.android.systemui.qs.external;
-import android.app.AppGlobals;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
-import android.content.ServiceConnection;
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
@@ -34,10 +33,10 @@
import android.service.quicksettings.IQSTileService;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
-import androidx.annotation.VisibleForTesting;
import android.util.ArraySet;
import android.util.Log;
-import com.android.systemui.qs.external.PackageManagerAdapter;
+
+import androidx.annotation.VisibleForTesting;
import java.util.Objects;
import java.util.Set;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index 9f9fe39..416c2da 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -30,9 +30,10 @@
import android.service.quicksettings.IQSTileService;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
-import androidx.annotation.VisibleForTesting;
import android.util.Log;
+import androidx.annotation.VisibleForTesting;
+
import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener;
import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index ac7ef5d..b2f6043 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -20,8 +20,11 @@
import android.view.ContextThemeWrapper;
import com.android.systemui.R;
-import com.android.systemui.plugins.qs.*;
+import com.android.systemui.plugins.qs.QSFactory;
+import com.android.systemui.plugins.qs.QSIconView;
+import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTileView;
+import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.tiles.AirplaneModeTile;
import com.android.systemui.qs.tiles.BatterySaverTile;
@@ -41,7 +44,6 @@
import com.android.systemui.qs.tiles.UserTile;
import com.android.systemui.qs.tiles.WifiTile;
import com.android.systemui.qs.tiles.WorkModeTile;
-import com.android.systemui.qs.QSTileHost;
import com.android.systemui.util.leak.GarbageMonitor;
public class QSFactoryImpl implements QSFactory {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index 9dd5d8f..e245312 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -34,8 +34,8 @@
import com.android.systemui.plugins.qs.QSIconView;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.State;
-
import com.android.systemui.qs.AlphaControlledSignalTileView.AlphaControlledSlashImageView;
+
import java.util.Objects;
public class QSIconViewImpl extends QSIconView {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SlashImageView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SlashImageView.java
index a3e9afd..72c68ce 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SlashImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SlashImageView.java
@@ -17,9 +17,10 @@
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.drawable.Drawable;
-import androidx.annotation.NonNull;
import android.widget.ImageView;
+import androidx.annotation.NonNull;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.plugins.qs.QSTile.SlashState;
import com.android.systemui.qs.SlashDrawable;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index cd00311..c62a592 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -16,8 +16,6 @@
package com.android.systemui.qs.tiles;
-import static com.android.settingslib.graph.BluetoothDeviceLayerDrawable.createLayerDrawable;
-
import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index b93f1c2..c13a07f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -22,14 +22,13 @@
import android.service.quicksettings.Tile;
import android.widget.Switch;
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.R;
import com.android.systemui.R.drawable;
-import com.android.systemui.qs.QSHost;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
-import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.SecureSetting;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
/** Quick settings tile: Invert colors **/
public class ColorInversionTile extends QSTileImpl<BooleanState> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index ace361b..fd8b9c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -18,6 +18,7 @@
import android.content.Intent;
import android.service.quicksettings.Tile;
import android.widget.Switch;
+
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.Prefs;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java
index 7bcc6d7..5578558 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java
@@ -16,11 +16,10 @@
package com.android.systemui.qs.tiles;
-import android.annotation.ColorInt;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.content.res.ColorStateList;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
index 3c565ef..a639a95 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
@@ -29,13 +29,11 @@
import android.text.TextUtils;
import android.util.Log;
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.qs.QSHost;
-import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.State;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import java.util.Arrays;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index d7f2a26..b5f2d00 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -27,8 +27,8 @@
import com.android.systemui.R;
import com.android.systemui.R.drawable;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.qs.QSHost;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.LocationController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
index 9edd65e..a365e4c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
@@ -22,17 +22,15 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
-import android.graphics.drawable.Drawable;
import android.nfc.NfcAdapter;
import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.widget.Switch;
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.R;
-import com.android.systemui.qs.QSHost;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.tileimpl.QSTileImpl;
/** Quick settings tile: Enable/Disable NFC **/
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index c41f087..90890c0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -21,19 +21,21 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Intent;
+import android.hardware.display.ColorDisplayManager;
import android.metrics.LogMaker;
import android.provider.Settings;
import android.service.quicksettings.Tile;
-import androidx.annotation.StringRes;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Switch;
+import androidx.annotation.StringRes;
+
import com.android.internal.app.ColorDisplayController;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.R;
-import com.android.systemui.qs.QSHost;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import java.text.DateFormat;
@@ -53,7 +55,6 @@
private static final String PATTERN_HOUR_MINUTE = "h:mm a";
private static final String PATTERN_HOUR_NINUTE_24 = "HH:mm";
-
private ColorDisplayController mController;
private boolean mIsListening;
@@ -64,7 +65,7 @@
@Override
public boolean isAvailable() {
- return ColorDisplayController.isAvailable(mContext);
+ return ColorDisplayManager.isNightDisplayAvailable(mContext);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 28b047b..6345816 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -19,17 +19,15 @@
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
-
import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.widget.Switch;
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.qs.QSHost;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
index ad7d1b6..64fe54a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
@@ -30,7 +30,6 @@
import android.widget.TextView;
import com.android.internal.util.ArrayUtils;
-import com.android.settingslib.drawable.UserIconDrawable;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.UserAvatarView;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
index bde1c98..e5c51a6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
@@ -24,9 +24,9 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.plugins.qs.DetailAdapter;
-import com.android.systemui.qs.QSHost;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.State;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/touch/SwipeDetector.java b/packages/SystemUI/src/com/android/systemui/qs/touch/SwipeDetector.java
index 911bea6..e7161e1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/touch/SwipeDetector.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/touch/SwipeDetector.java
@@ -19,12 +19,13 @@
import android.content.Context;
import android.graphics.PointF;
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
/**
* One dimensional scroll/drag/swipe gesture detector.
*
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
index 661b958..958695d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
@@ -31,6 +31,7 @@
import android.util.Log;
import android.view.Display;
import android.widget.Toast;
+
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SysUiServiceProvider;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 19f7675..1b89324 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -17,9 +17,9 @@
package com.android.systemui.recents;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
-import static android.view.MotionEvent.ACTION_CANCEL;
import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_SWIPE_UP;
import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON;
@@ -42,6 +42,7 @@
import android.provider.Settings;
import android.util.Log;
import android.view.MotionEvent;
+
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.Prefs;
@@ -55,6 +56,7 @@
import com.android.systemui.statusbar.policy.CallbackController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index de22d21..0702d74 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -20,9 +20,11 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.provider.Settings;
+
import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.statusbar.CommandQueue;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java
index 8a04c11..3efed3f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java
@@ -18,7 +18,9 @@
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
+
import com.android.systemui.SysUiServiceProvider;
+
import java.io.PrintWriter;
interface RecentsImplementation {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
index af0ebdc..34f3c60 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
@@ -19,16 +19,17 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static com.android.systemui.Prefs.Key.HAS_DISMISSED_RECENTS_QUICK_SCRUB_ONBOARDING_ONCE;
import static com.android.systemui.Prefs.Key.DISMISSED_RECENTS_SWIPE_UP_ONBOARDING_COUNT;
+import static com.android.systemui.Prefs.Key.HAS_DISMISSED_RECENTS_QUICK_SCRUB_ONBOARDING_ONCE;
import static com.android.systemui.Prefs.Key.HAS_SEEN_RECENTS_QUICK_SCRUB_ONBOARDING;
import static com.android.systemui.Prefs.Key.HAS_SEEN_RECENTS_SWIPE_UP_ONBOARDING;
import static com.android.systemui.Prefs.Key.OVERVIEW_OPENED_COUNT;
import static com.android.systemui.Prefs.Key.OVERVIEW_OPENED_FROM_HOME_COUNT;
-import static com.android.systemui.shared.system.LauncherEventUtil.VISIBLE;
import static com.android.systemui.shared.system.LauncherEventUtil.DISMISS;
-import static com.android.systemui.shared.system.LauncherEventUtil.RECENTS_QUICK_SCRUB_ONBOARDING_TIP;
+import static com.android.systemui.shared.system.LauncherEventUtil
+ .RECENTS_QUICK_SCRUB_ONBOARDING_TIP;
import static com.android.systemui.shared.system.LauncherEventUtil.RECENTS_SWIPE_UP_ONBOARDING_TIP;
+import static com.android.systemui.shared.system.LauncherEventUtil.VISIBLE;
import android.annotation.StringRes;
import android.annotation.TargetApi;
@@ -45,9 +46,9 @@
import android.graphics.PixelFormat;
import android.graphics.drawable.ShapeDrawable;
import android.os.Build;
+import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserManager;
-import android.os.RemoteException;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
@@ -64,6 +65,7 @@
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
+
import java.io.PrintWriter;
import java.util.Collections;
import java.util.HashSet;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index f92c50a..216b940 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -16,6 +16,9 @@
package com.android.systemui.recents;
+import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE;
+import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE;
+
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.app.ActivityManager;
@@ -50,9 +53,6 @@
import java.util.ArrayList;
-import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE;
-import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE;
-
public class ScreenPinningRequest implements View.OnClickListener {
private final Context mContext;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/TriangleShape.java b/packages/SystemUI/src/com/android/systemui/recents/TriangleShape.java
index af8c2d0..ef4e195 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/TriangleShape.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/TriangleShape.java
@@ -19,6 +19,7 @@
import android.graphics.Outline;
import android.graphics.Path;
import android.graphics.drawable.shapes.PathShape;
+
import androidx.annotation.NonNull;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 97534ed..bed0c45 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -40,7 +40,6 @@
import android.content.ClipDescription;
import android.content.ComponentName;
import android.content.ContentResolver;
-import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
@@ -64,7 +63,6 @@
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.DisplayMetrics;
-import android.util.Log;
import android.util.Slog;
import android.view.Display;
import android.view.LayoutInflater;
@@ -85,8 +83,9 @@
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.util.NotificationChannels;
-import java.io.File;
-import java.io.FileOutputStream;
+import libcore.io.IoUtils;
+
+import java.io.IOException;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
@@ -121,16 +120,13 @@
class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
private static final String TAG = "SaveImageInBackgroundTask";
- private static final String SCREENSHOTS_DIR_NAME = "Screenshots";
private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png";
private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
private final SaveImageInBackgroundData mParams;
private final NotificationManager mNotificationManager;
private final Notification.Builder mNotificationBuilder, mPublicNotificationBuilder;
- private final File mScreenshotDir;
private final String mImageFileName;
- private final String mImageFilePath;
private final long mImageTime;
private final BigPictureStyle mNotificationStyle;
private final int mImageWidth;
@@ -146,10 +142,6 @@
String imageDate = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(mImageTime));
mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);
- mScreenshotDir = new File(Environment.getExternalStoragePublicDirectory(
- Environment.DIRECTORY_PICTURES), SCREENSHOTS_DIR_NAME);
- mImageFilePath = new File(mScreenshotDir, mImageFileName).getAbsolutePath();
-
// Create the large notification icon
mImageWidth = data.image.getWidth();
mImageHeight = data.image.getHeight();
@@ -238,7 +230,7 @@
}
@Override
- protected Void doInBackground(Void... params) {
+ protected Void doInBackground(Void... paramsUnused) {
if (isCancelled()) {
return null;
}
@@ -252,36 +244,27 @@
Resources r = context.getResources();
try {
- // Create screenshot directory if it doesn't exist
- boolean madeDirs = mScreenshotDir.mkdirs();
- if (madeDirs == false) {
- Log.e(TAG, "Couldn't create screenshot directory: " + mScreenshotDir);
- }
-
- // media provider uses seconds for DATE_MODIFIED and DATE_ADDED, but milliseconds
- // for DATE_TAKEN
- long dateSeconds = mImageTime / 1000;
-
- // Save
- OutputStream out = new FileOutputStream(mImageFilePath);
- image.compress(Bitmap.CompressFormat.PNG, 100, out);
- out.flush();
- out.close();
-
// Save the screenshot to the MediaStore
- ContentValues values = new ContentValues();
- ContentResolver resolver = context.getContentResolver();
- values.put(MediaStore.Images.ImageColumns.DATA, mImageFilePath);
- values.put(MediaStore.Images.ImageColumns.TITLE, mImageFileName);
- values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, mImageFileName);
- values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, mImageTime);
- values.put(MediaStore.Images.ImageColumns.DATE_ADDED, dateSeconds);
- values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, dateSeconds);
- values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/png");
- values.put(MediaStore.Images.ImageColumns.WIDTH, mImageWidth);
- values.put(MediaStore.Images.ImageColumns.HEIGHT, mImageHeight);
- values.put(MediaStore.Images.ImageColumns.SIZE, new File(mImageFilePath).length());
- Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+ final MediaStore.PendingParams params = new MediaStore.PendingParams(
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mImageFileName, "image/png");
+ params.setPrimaryDirectory(Environment.DIRECTORY_PICTURES);
+ params.setSecondaryDirectory(Environment.DIRECTORY_SCREENSHOTS);
+
+ final Uri uri = MediaStore.createPending(context, params);
+ final MediaStore.PendingSession session = MediaStore.openPending(context, uri);
+ try {
+ try (OutputStream out = session.openOutputStream()) {
+ if (!image.compress(Bitmap.CompressFormat.PNG, 100, out)) {
+ throw new IOException("Failed to compress");
+ }
+ }
+ session.publish();
+ } catch (Exception e) {
+ session.abandon();
+ throw e;
+ } finally {
+ IoUtils.closeQuietly(session);
+ }
// Note: Both the share and edit actions are proxied through ActionProxyReceiver in
// order to do some common work like dismissing the keyguard and sending
diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
index 3ed5f70..79228b9 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
@@ -43,7 +43,6 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.systemui.Dependency;
diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java
index 2ae53b5..0374a01 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java
@@ -18,7 +18,6 @@
import android.app.Activity;
import android.os.Bundle;
-import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
diff --git a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
index 64fa8f8..07675e2 100644
--- a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
@@ -26,6 +26,7 @@
import android.view.KeyEvent;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
+
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.systemui.SystemUI;
import com.android.systemui.recents.Recents;
diff --git a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyServiceProxy.java b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyServiceProxy.java
index 8ec862e..156964a 100644
--- a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyServiceProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyServiceProxy.java
@@ -19,6 +19,7 @@
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
+
import com.android.internal.policy.IShortcutService;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index ea194a7..cd2074f 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -26,9 +26,11 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManagerGlobal;
+
import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.recents.Recents;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index fa01af6..7a7d1f6 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -55,6 +55,7 @@
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.policy.DividerSnapAlgorithm;
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java
index b7a5d31..2486d653 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java
@@ -16,12 +16,6 @@
package com.android.systemui.stackdivider;
-import android.content.Context;
-import android.graphics.PixelFormat;
-import android.os.Binder;
-import android.view.View;
-import android.view.WindowManager;
-
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
@@ -32,6 +26,12 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.os.Binder;
+import android.view.View;
+import android.view.WindowManager;
+
/**
* Manages the window parameters of the docked stack divider.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
index f66db48..c6ac309 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
@@ -16,7 +16,8 @@
package com.android.systemui.stackdivider;
-import static com.android.systemui.stackdivider.ForcedResizableInfoActivity.EXTRA_FORCED_RESIZEABLE_REASON;
+import static com.android.systemui.stackdivider.ForcedResizableInfoActivity
+ .EXTRA_FORCED_RESIZEABLE_REASON;
import android.app.ActivityOptions;
import android.content.Context;
@@ -25,6 +26,7 @@
import android.os.UserHandle;
import android.util.ArraySet;
import android.widget.Toast;
+
import com.android.systemui.R;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
index 600964e..228aab5 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
@@ -18,7 +18,6 @@
import static android.view.WindowManager.DOCKED_INVALID;
-import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.graphics.Rect;
import android.os.RemoteException;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index 00e0b95..7f39e47 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -62,11 +62,13 @@
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
+
import com.android.internal.app.AssistUtils;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settingslib.Utils;
import com.android.systemui.R;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
index 1f57634..a188c5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
@@ -40,7 +40,6 @@
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.KeyguardAffordanceHelper;
/**
* An ImageView which does not have overlapping renderings commands and therefore does not need a
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NeutralGoodDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/NeutralGoodDrawable.java
index cdb0514..8642ca4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NeutralGoodDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NeutralGoodDrawable.java
@@ -17,14 +17,11 @@
package com.android.systemui.statusbar;
import android.content.Context;
-import android.content.res.Resources.Theme;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
+
import com.android.settingslib.Utils;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java
index 62c21dc..ecd9814 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java
@@ -1,9 +1,9 @@
package com.android.systemui.statusbar;
-import com.android.systemui.statusbar.notification.NotificationData;
-
import androidx.annotation.NonNull;
+import com.android.systemui.statusbar.notification.NotificationData;
+
/**
* Interface for anything that may need to keep notifications managed even after
* {@link NotificationListener} removes it. The lifetime extender is in charge of performing the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index f7cc9cb..b0724b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -397,7 +397,8 @@
Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle);
final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
- final boolean allowed = allowedByUser && allowedByDpm;
+ final boolean allowedBySystem = mKeyguardManager.getPrivateNotificationsAllowed();
+ final boolean allowed = allowedByUser && allowedByDpm && allowedBySystem;
mUsersAllowingNotifications.append(userHandle, allowed);
return allowed;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
index 5c8f4cb..0e9f950 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
@@ -16,11 +16,8 @@
package com.android.systemui.statusbar;
import android.content.Intent;
-import android.os.Handler;
-import android.view.View;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
index 47b7fe9..f23ae3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
@@ -22,10 +22,10 @@
import android.graphics.drawable.Icon;
import android.text.TextUtils;
-import com.android.systemui.statusbar.notification.NotificationData;
-
import androidx.annotation.VisibleForTesting;
+import com.android.systemui.statusbar.notification.NotificationData;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -53,7 +53,8 @@
public static NotificationUiAdjustment extractFromNotificationEntry(
NotificationData.Entry entry) {
- return new NotificationUiAdjustment(entry.key, entry.smartActions, entry.smartReplies);
+ return new NotificationUiAdjustment(
+ entry.key, entry.systemGeneratedSmartActions, entry.smartReplies);
}
public static boolean needReinflate(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 7581d8c..ea67736 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar;
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
+
import android.content.Context;
import android.content.res.Resources;
import android.os.Trace;
@@ -25,6 +27,7 @@
import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
@@ -62,6 +65,7 @@
Dependency.get(StatusBarStateController.class);
private final NotificationEntryManager mEntryManager =
Dependency.get(NotificationEntryManager.class);
+ private final BubbleController mBubbleController = Dependency.get(BubbleController.class);
// Lazy
private ShadeController mShadeController;
@@ -75,6 +79,41 @@
private NotificationPresenter mPresenter;
private NotificationListContainer mListContainer;
+ private StatusBarStateListener mStatusBarStateListener;
+
+ /**
+ * Listens for the current state of the status bar and updates the visibility state
+ * of bubbles as needed.
+ */
+ public class StatusBarStateListener implements StatusBarStateController.StateListener {
+ private int mState;
+ private BubbleController mController;
+
+ public StatusBarStateListener(BubbleController controller) {
+ mController = controller;
+ }
+
+ /**
+ * Returns the current status bar state.
+ */
+ public int getCurrentState() {
+ return mState;
+ }
+
+ @Override
+ public void onStateChanged(int newState) {
+ mState = newState;
+ // Order here matters because we need to remove the expandable notification row
+ // from it's current parent (NSSL or bubble) before it can be added to the new parent
+ if (mState == SHADE) {
+ updateNotificationViews();
+ mController.updateVisibility(true);
+ } else {
+ mController.updateVisibility(false);
+ updateNotificationViews();
+ }
+ }
+ }
private ShadeController getShadeController() {
if (mShadeController == null) {
@@ -87,6 +126,9 @@
Resources res = context.getResources();
mAlwaysExpandNonGroupedNotification =
res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
+ mStatusBarStateListener = new StatusBarStateListener(mBubbleController);
+ mEntryManager.setStatusBarStateListener(mStatusBarStateListener);
+ Dependency.get(StatusBarStateController.class).addListener(mStatusBarStateListener);
}
public void setUpWithPresenter(NotificationPresenter presenter,
@@ -102,6 +144,7 @@
ArrayList<NotificationData.Entry> activeNotifications = mEntryManager.getNotificationData()
.getActiveNotifications();
ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
+ ArrayList<NotificationData.Entry> toBubble = new ArrayList<>();
final int N = activeNotifications.size();
for (int i = 0; i < N; i++) {
NotificationData.Entry ent = activeNotifications.get(i);
@@ -110,6 +153,14 @@
// temporarily become children if they were isolated before.
continue;
}
+ ent.row.setStatusBarState(mStatusBarStateListener.getCurrentState());
+ boolean showAsBubble = ent.isBubble() && !ent.isBubbleDismissed()
+ && mStatusBarStateListener.getCurrentState() == SHADE;
+ if (showAsBubble) {
+ toBubble.add(ent);
+ continue;
+ }
+
int userId = ent.notification.getUserId();
// Display public version of the notification if we need to redact.
@@ -210,6 +261,12 @@
}
+ for (int i = 0; i < toBubble.size(); i++) {
+ // TODO: might make sense to leave them in the shade and just reposition them
+ NotificationData.Entry ent = toBubble.get(i);
+ mBubbleController.addBubble(ent);
+ }
+
mVisualStabilityManager.onReorderingFinished();
// clear the map again for the next usage
mTmpChildOrderMap.clear();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
index 5090f74..f1a891b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
@@ -15,10 +15,9 @@
package com.android.systemui.statusbar;
import android.content.Context;
-import android.net.ConnectivityManager;
import android.graphics.Rect;
+import android.net.ConnectivityManager;
import android.os.Bundle;
-import android.provider.Settings;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.text.TextUtils;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index 7f63191..929f43e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -16,10 +16,6 @@
package com.android.systemui.statusbar;
-import com.android.internal.util.Preconditions;
-import com.android.systemui.statusbar.notification.NotificationData;
-import com.android.systemui.statusbar.policy.RemoteInputView;
-
import android.app.Notification;
import android.app.RemoteInput;
import android.content.Context;
@@ -27,6 +23,10 @@
import android.util.ArrayMap;
import android.util.Pair;
+import com.android.internal.util.Preconditions;
+import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.policy.RemoteInputView;
+
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
index 3bc4342..cb9060b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
@@ -28,16 +28,14 @@
import android.graphics.PorterDuff;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffColorFilter;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import androidx.core.graphics.ColorUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
-import android.view.animation.Interpolator;
+
+import androidx.core.graphics.ColorUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
index fb888dd..758c33a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
@@ -16,13 +16,11 @@
package com.android.systemui.statusbar;
import android.os.RemoteException;
-import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.notification.NotificationData;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
import java.util.Set;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java
index 56f78f4..0652227 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar;
import com.android.internal.statusbar.StatusBarIcon;
+
import java.util.ArrayList;
import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
index 3c52e8c..bc89889 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -27,16 +27,14 @@
import android.graphics.Color;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
-
import android.widget.LinearLayout;
+
import com.android.internal.annotations.VisibleForTesting;
-import com.android.keyguard.AlphaOptimizedLinearLayout;
import com.android.settingslib.graph.SignalDrawable;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
index f3fc99e..045221f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
@@ -27,18 +27,14 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
import android.widget.FrameLayout;
import android.widget.ImageView;
-
import android.widget.LinearLayout;
-import com.android.keyguard.AlphaOptimizedLinearLayout;
+
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java
index f9afc7c..c4fadff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java
@@ -16,12 +16,12 @@
package com.android.systemui.statusbar;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
import android.content.Context;
import android.content.DialogInterface;
import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
public class UserUtil {
public static void deleteUserWithPrompt(Context context, int userId,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java
index 46f8863..bd32856 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java
@@ -3,10 +3,8 @@
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
index 9ed0929..81f7846 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
@@ -16,13 +16,10 @@
package com.android.systemui.statusbar.car;
-import android.app.UiModeManager;
import android.content.Context;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
-import android.widget.TextView;
import com.android.keyguard.AlphaOptimizedImageButton;
import com.android.systemui.Dependency;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
index 0304086..f2923f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
@@ -1,5 +1,7 @@
package com.android.systemui.statusbar.car;
+import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
+
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadsetClient;
@@ -9,21 +11,19 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.telephony.SignalStrength;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.widget.ImageView;
+
import com.android.settingslib.graph.SignalDrawable;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.ScalingDrawableWrapper;
import com.android.systemui.statusbar.policy.BluetoothController;
-import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
-
/**
* Controller that monitors signal strength for a device that is connected via bluetooth.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/SwitchToGuestTimer.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/SwitchToGuestTimer.java
index f9fa44b..0c91cba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/SwitchToGuestTimer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/SwitchToGuestTimer.java
@@ -21,10 +21,10 @@
import android.os.CountDownTimer;
import android.util.Log;
-import com.android.systemui.R;
-
import androidx.annotation.GuardedBy;
+import com.android.systemui.R;
+
/**
* Wrapper for a countdown timer that switches to Guest if the user has been driving with
* the keyguard up for configurable number of seconds.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
index 53a7afe..d802ed8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
@@ -29,7 +29,6 @@
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.os.AsyncTask;
-import android.os.UserHandle;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -44,8 +43,8 @@
import com.android.internal.util.UserIcons;
import com.android.systemui.R;
-
import com.android.systemui.statusbar.phone.SystemUIDialog;
+
import java.util.ArrayList;
import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActionListTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActionListTransformState.java
index 8c72544..6044a7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActionListTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActionListTransformState.java
@@ -16,11 +16,7 @@
package com.android.systemui.statusbar.notification;
-import android.text.Layout;
-import android.text.TextUtils;
import android.util.Pools;
-import android.view.View;
-import android.widget.TextView;
/**
* A transform state of the action list
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/CustomInterpolatorTransformation.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/CustomInterpolatorTransformation.java
index de4c312..dea1a07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/CustomInterpolatorTransformation.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/CustomInterpolatorTransformation.java
@@ -17,16 +17,11 @@
package com.android.systemui.statusbar.notification;
import android.view.View;
-import android.view.animation.Interpolator;
-import com.android.systemui.Interpolators;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.ViewTransformationHelper;
-import static com.android.systemui.statusbar.TransformableView.TRANSFORMING_VIEW_TITLE;
-import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y;
-
/**
* A custom transformation that modifies the interpolator
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageGradientColorizer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageGradientColorizer.java
index 454edbb..f5a76f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageGradientColorizer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageGradientColorizer.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.notification;
import android.graphics.Bitmap;
-import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorMatrix;
@@ -27,7 +26,6 @@
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Shader;
-import android.graphics.Xfermode;
import android.graphics.drawable.Drawable;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java
index 8c0d700..ab94008 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java
@@ -23,9 +23,10 @@
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.util.LayoutDirection;
+
import androidx.annotation.VisibleForTesting;
import androidx.palette.graphics.Palette;
-import android.util.LayoutDirection;
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java
index b97995d..a3fb225 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java
@@ -20,7 +20,6 @@
import android.view.View;
import com.android.internal.widget.MessagingImageMessage;
-import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.ViewTransformationHelper;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
index 4e712a5..da6d977 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
@@ -114,8 +114,9 @@
public CharSequence remoteInputText;
public List<SnoozeCriterion> snoozeCriteria;
public int userSentiment = Ranking.USER_SENTIMENT_NEUTRAL;
+ /** Smart Actions provided by the NotificationAssistantService. */
@NonNull
- public List<Notification.Action> smartActions = Collections.emptyList();
+ public List<Notification.Action> systemGeneratedSmartActions = Collections.emptyList();
public CharSequence[] smartReplies = new CharSequence[0];
private int mCachedContrastColor = COLOR_INVALID;
@@ -171,7 +172,7 @@
importance = ranking.getImportance();
snoozeCriteria = ranking.getSnoozeCriteria();
userSentiment = ranking.getUserSentiment();
- smartActions = ranking.getSmartActions() == null
+ systemGeneratedSmartActions = ranking.getSmartActions() == null
? Collections.emptyList() : ranking.getSmartActions();
smartReplies = ranking.getSmartReplies() == null
? new CharSequence[0]
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 3bea7db..ac0fe6e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -15,13 +15,16 @@
*/
package com.android.systemui.statusbar.notification;
+import static com.android.systemui.bubbles.BubbleController.DEBUG_DEMOTE_TO_NOTIF;
import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY;
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_AMBIENT;
import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_HEADS_UP;
import android.annotation.Nullable;
import android.app.Notification;
+import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
@@ -62,6 +65,7 @@
import com.android.systemui.InitController;
import com.android.systemui.R;
import com.android.systemui.UiOffloadThread;
+import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.AmbientPulseManager;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
@@ -72,6 +76,7 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationUiAdjustment;
import com.android.systemui.statusbar.NotificationUpdateHandler;
+import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -99,7 +104,7 @@
*/
public class NotificationEntryManager implements Dumpable, NotificationInflater.InflationCallback,
ExpandableNotificationRow.ExpansionLogger, NotificationUpdateHandler,
- VisualStabilityManager.Callback {
+ VisualStabilityManager.Callback, BubbleController.BubbleDismissListener {
private static final String TAG = "NotificationEntryMgr";
protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
protected static final boolean ENABLE_HEADS_UP = true;
@@ -124,6 +129,7 @@
Dependency.get(ForegroundServiceController.class);
private final AmbientPulseManager mAmbientPulseManager =
Dependency.get(AmbientPulseManager.class);
+ private final BubbleController mBubbleController = Dependency.get(BubbleController.class);
// Lazily retrieved dependencies
private NotificationRemoteInputManager mRemoteInputManager;
@@ -146,6 +152,7 @@
protected final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders
= new ArrayList<>();
private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener;
+ private NotificationViewHierarchyManager.StatusBarStateListener mStatusBarStateListener;
private final class NotificationClicker implements View.OnClickListener {
@@ -175,6 +182,11 @@
row.setJustClicked(true);
DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));
+ // If it was a bubble we should close it
+ if (row.getEntry().isBubble()) {
+ mBubbleController.collapseStack();
+ }
+
mCallback.onNotificationClicked(sbn, row);
}
@@ -229,6 +241,7 @@
mDreamManager = IDreamManager.Stub.asInterface(
ServiceManager.checkService(DreamService.DREAM_SERVICE));
mMessagingUtil = new NotificationMessagingUtil(context);
+ mBubbleController.setDismissListener(this /* bubbleEventListener */);
mNotificationData = new NotificationData();
Dependency.get(InitController.class).addPostInitTask(this::onPostInit);
}
@@ -451,6 +464,23 @@
mCallback.onPerformRemoveNotification(n);
}
+ @Override
+ public void onStackDismissed() {
+ updateNotifications();
+ }
+
+ @Override
+ public void onBubbleDismissed(String key) {
+ NotificationData.Entry entry = mNotificationData.get(key);
+ if (entry != null) {
+ entry.setBubbleDismissed(true);
+ if (!DEBUG_DEMOTE_TO_NOTIF) {
+ performRemoveNotification(entry.notification);
+ }
+ }
+ updateNotifications();
+ }
+
/**
* Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
* about the failure.
@@ -702,7 +732,6 @@
&& !mPresenter.isPresenterFullyCollapsed();
row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
- row.setSmartActions(entry.smartActions);
row.setEntry(entry);
row.updateInflationFlag(FLAG_CONTENT_VIEW_HEADS_UP, shouldHeadsUp(entry));
@@ -728,7 +757,12 @@
if (DEBUG) {
Log.d(TAG, "createNotificationViews(notification=" + sbn + " " + ranking);
}
+
NotificationData.Entry entry = new NotificationData.Entry(sbn, ranking);
+ if (shouldAutoBubble(entry)) {
+ entry.setIsBubble(true);
+ }
+
Dependency.get(LeakDetector.class).trackInstance(entry);
entry.createIcons(mContext, sbn);
// Construct the expanded view.
@@ -949,6 +983,11 @@
}
}
+ public void setStatusBarStateListener(
+ NotificationViewHierarchyManager.StatusBarStateListener listener) {
+ mStatusBarStateListener = listener;
+ }
+
/**
* Whether the notification should peek in from the top and alert the user.
*
@@ -966,6 +1005,14 @@
return false;
}
+ // TODO: need to changes this, e.g. should still heads up in expanded shade, might want
+ // message bubble from the bubble to go through heads up path
+ boolean inShade = mStatusBarStateListener != null
+ && mStatusBarStateListener.getCurrentState() == SHADE;
+ if (entry.isBubble() && !entry.isBubbleDismissed() && inShade) {
+ return false;
+ }
+
if (!canAlertCommon(entry)) {
if (DEBUG) {
Log.d(TAG, "No heads up: notification shouldn't alert: " + sbn.getKey());
@@ -1153,6 +1200,17 @@
}
}
+
+ /**
+ * Whether a bubble is appropriate to auto-bubble or not.
+ */
+ private boolean shouldAutoBubble(NotificationData.Entry entry) {
+ int priority = mNotificationData.getImportance(entry.key);
+ NotificationChannel channel = mNotificationData.getChannel(entry.key);
+ boolean canAppOverlay = channel != null && channel.canOverlayApps();
+ return BubbleController.shouldAutoBubble(entry, priority, canAppOverlay);
+ }
+
/**
* Callback for NotificationEntryManager.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
index 07b8c35..337f312 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -29,9 +29,9 @@
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.ViewTransformationHelper;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
/**
* A transform state of a view.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
index da8954a..75613a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
@@ -16,9 +16,10 @@
package com.android.systemui.statusbar.notification;
-import androidx.collection.ArraySet;
import android.view.View;
+import androidx.collection.ArraySet;
+
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 5166e06..b7bdc2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row;
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator
.ExpandAnimationParameters;
import static com.android.systemui.statusbar.notification.row.NotificationContentView
@@ -39,7 +40,6 @@
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.Notification;
import android.app.NotificationChannel;
import android.content.Context;
import android.content.pm.PackageInfo;
@@ -340,6 +340,7 @@
private SystemNotificationAsyncTask mSystemNotificationAsyncTask =
new SystemNotificationAsyncTask();
+ private int mStatusBarState = -1;
/**
* Returns whether the given {@code statusBarNotification} is a system notification.
@@ -1567,10 +1568,6 @@
mNotificationInflater.setUsesIncreasedHeight(use);
}
- public void setSmartActions(List<Notification.Action> smartActions) {
- mNotificationInflater.setSmartActions(smartActions);
- }
-
public void setUseIncreasedHeadsUpHeight(boolean use) {
mUseIncreasedHeadsUpHeight = use;
mNotificationInflater.setUsesIncreasedHeadsUpHeight(use);
@@ -2261,6 +2258,9 @@
@Override
public int getIntrinsicHeight() {
+ if (isShownAsBubble()) {
+ return getMaxExpandHeight();
+ }
if (isUserLocked()) {
return getActualHeight();
}
@@ -2291,6 +2291,20 @@
return !mOnKeyguard && !mOnAmbient;
}
+ private boolean isShownAsBubble() {
+ return mEntry.isBubble() && (mStatusBarState == SHADE || mStatusBarState == -1);
+ }
+
+ /**
+ * Set the current status bar state.
+ * @param state should be from {@link com.android.systemui.statusbar.StatusBarState}.
+ */
+ public void setStatusBarState(int state) {
+ if (mStatusBarState != state) {
+ mStatusBarState = state;
+ }
+ }
+
@Override
public boolean isGroupExpanded() {
return mGroupManager.isGroupExpanded(mStatusBarNotification);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
index 1a4ef09..16796dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
@@ -16,10 +16,13 @@
package com.android.systemui.statusbar.notification.row;
+import static android.service.notification.NotificationListenerService.Ranking
+ .USER_SENTIMENT_NEGATIVE;
+
import android.content.Context;
+import android.util.Log;
import androidx.annotation.VisibleForTesting;
-import android.util.Log;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.Dependency;
@@ -31,8 +34,6 @@
import java.util.HashSet;
import java.util.Set;
-import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
-
/**
* Manager for the notification blocking helper - tracks and helps create the blocking helper
* affordance.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index b838c9b..37bf06e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -21,6 +21,8 @@
import static android.service.notification.NotificationListenerService.Ranking
.USER_SENTIMENT_NEGATIVE;
+import static com.android.systemui.statusbar.notification.row.NotificationInfo.ACTION_NONE;
+
import android.app.INotificationManager;
import android.app.NotificationChannel;
import android.content.Context;
@@ -189,7 +191,13 @@
} else if (gutsView instanceof AppOpsInfo) {
initializeAppOpsInfo(row, (AppOpsInfo) gutsView);
} else if (gutsView instanceof NotificationInfo) {
- initializeNotificationInfo(row, (NotificationInfo) gutsView);
+ int action;
+ if (item instanceof NotificationMenuRow.NotificationInfoMenuItem) {
+ action = ((NotificationMenuRow.NotificationInfoMenuItem) item).mAction;
+ } else {
+ action = ACTION_NONE;
+ }
+ initializeNotificationInfo(row, (NotificationInfo) gutsView, action);
}
return true;
} catch (Exception e) {
@@ -246,14 +254,15 @@
/**
* Sets up the {@link NotificationInfo} inside the notification row's guts.
- *
* @param row view to set up the guts for
* @param notificationInfoView view to set up/bind within {@code row}
+ * @param action The action to take immediately upon binding, if any.
*/
@VisibleForTesting
void initializeNotificationInfo(
final ExpandableNotificationRow row,
- NotificationInfo notificationInfoView) throws Exception {
+ NotificationInfo notificationInfoView,
+ @NotificationInfo.NotificationInfoAction int action) throws Exception {
NotificationGuts guts = row.getGuts();
StatusBarNotification sbn = row.getStatusBarNotification();
String packageName = sbn.getPackageName();
@@ -297,7 +306,8 @@
isForBlockingHelper,
row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE,
row.getEntry().noisy,
- row.getEntry().importance);
+ row.getEntry().importance,
+ action);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java
index 38d6b35..e1c2f73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java
@@ -43,10 +43,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
-import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
@@ -131,7 +128,6 @@
private boolean mIsChildInGroup;
private InflationCallback mCallback;
private boolean mRedactAmbient;
- private List<Notification.Action> mSmartActions;
private final ArrayMap<Integer, RemoteViews> mCachedContentViews = new ArrayMap<>();
public NotificationInflater(ExpandableNotificationRow row) {
@@ -161,10 +157,6 @@
mUsesIncreasedHeight = usesIncreasedHeight;
}
- public void setSmartActions(List<Notification.Action> smartActions) {
- mSmartActions = smartActions;
- }
-
public void setUsesIncreasedHeadsUpHeight(boolean usesIncreasedHeight) {
mUsesIncreasedHeadsUpHeight = usesIncreasedHeight;
}
@@ -258,8 +250,7 @@
StatusBarNotification sbn = mRow.getEntry().notification;
AsyncInflationTask task = new AsyncInflationTask(sbn, reInflateFlags, mCachedContentViews,
mRow, mIsLowPriority, mIsChildInGroup, mUsesIncreasedHeight,
- mUsesIncreasedHeadsUpHeight, mRedactAmbient, mCallback, mRemoteViewClickHandler,
- mSmartActions);
+ mUsesIncreasedHeadsUpHeight, mRedactAmbient, mCallback, mRemoteViewClickHandler);
if (mCallback != null && mCallback.doInflateSynchronous()) {
task.onPostExecute(task.doInBackground());
} else {
@@ -765,15 +756,13 @@
private Exception mError;
private RemoteViews.OnClickHandler mRemoteViewClickHandler;
private CancellationSignal mCancellationSignal;
- private List<Notification.Action> mSmartActions;
private AsyncInflationTask(StatusBarNotification notification,
@InflationFlag int reInflateFlags,
ArrayMap<Integer, RemoteViews> cachedContentViews, ExpandableNotificationRow row,
boolean isLowPriority, boolean isChildInGroup, boolean usesIncreasedHeight,
boolean usesIncreasedHeadsUpHeight, boolean redactAmbient,
- InflationCallback callback, RemoteViews.OnClickHandler remoteViewClickHandler,
- List<Notification.Action> smartActions) {
+ InflationCallback callback, RemoteViews.OnClickHandler remoteViewClickHandler) {
mRow = row;
mSbn = notification;
mReInflateFlags = reInflateFlags;
@@ -786,9 +775,6 @@
mRedactAmbient = redactAmbient;
mRemoteViewClickHandler = remoteViewClickHandler;
mCallback = callback;
- mSmartActions = smartActions == null
- ? Collections.emptyList()
- : new ArrayList<>(smartActions);
NotificationData.Entry entry = row.getEntry();
entry.setInflationTask(this);
}
@@ -806,8 +792,6 @@
= Notification.Builder.recoverBuilder(mContext,
mSbn.getNotification());
- applyChanges(recoveredBuilder);
-
Context packageContext = mSbn.getPackageContext(mContext);
Notification notification = mSbn.getNotification();
if (notification.isMediaNotification()) {
@@ -834,18 +818,6 @@
}
}
- /**
- * Apply changes to the given notification builder, like adding smart actions suggested by
- * a {@link android.service.notification.NotificationAssistantService}.
- */
- private void applyChanges(Notification.Builder builder) {
- if (mSmartActions != null) {
- for (Notification.Action smartAction : mSmartActions) {
- builder.addAction(smartAction);
- }
- }
- }
-
private void handleError(Exception e) {
mRow.getEntry().onInflationTaskFinished();
StatusBarNotification sbn = mRow.getStatusBarNotification();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index 522da4d..3a7091b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -71,16 +71,19 @@
public class NotificationInfo extends LinearLayout implements NotificationGuts.GutsContent {
private static final String TAG = "InfoGuts";
- @IntDef(prefix = { "SWAP_CONTENT_" }, value = {
- SWAP_CONTENT_UNDO,
- SWAP_CONTENT_TOGGLE_SILENT,
- SWAP_CONTENT_BLOCK,
+ @IntDef(prefix = { "ACTION_" }, value = {
+ ACTION_NONE,
+ ACTION_UNDO,
+ ACTION_TOGGLE_SILENT,
+ ACTION_BLOCK,
})
- @interface SwapContentAction {}
+ public @interface NotificationInfoAction {
+ }
- private static final int SWAP_CONTENT_UNDO = 0;
- private static final int SWAP_CONTENT_TOGGLE_SILENT = 1;
- private static final int SWAP_CONTENT_BLOCK = 2;
+ public static final int ACTION_NONE = 0;
+ public static final int ACTION_UNDO = 1;
+ public static final int ACTION_TOGGLE_SILENT = 2;
+ public static final int ACTION_BLOCK = 3;
private INotificationManager mINotificationManager;
private PackageManager mPm;
@@ -123,8 +126,7 @@
private OnClickListener mOnToggleSilent = v -> {
Runnable saveImportance = () -> {
- mExitReason = NotificationCounters.BLOCKING_HELPER_TOGGLE_SILENT;
- swapContent(SWAP_CONTENT_TOGGLE_SILENT);
+ swapContent(ACTION_TOGGLE_SILENT, true /* animate */);
};
if (mCheckSaveListener != null) {
mCheckSaveListener.checkSave(saveImportance, mSbn);
@@ -135,8 +137,7 @@
private OnClickListener mOnStopOrMinimizeNotifications = v -> {
Runnable saveImportance = () -> {
- mExitReason = NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS;
- swapContent(SWAP_CONTENT_BLOCK);
+ swapContent(ACTION_BLOCK, true /* animate */);
};
if (mCheckSaveListener != null) {
mCheckSaveListener.checkSave(saveImportance, mSbn);
@@ -149,7 +150,7 @@
// Reset exit counter that we'll log and record an undo event separately (not an exit event)
mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
logBlockingHelperCounter(NotificationCounters.BLOCKING_HELPER_UNDO);
- swapContent(SWAP_CONTENT_UNDO);
+ swapContent(ACTION_UNDO, true /* animate */);
};
public NotificationInfo(Context context, AttributeSet attrs) {
@@ -185,13 +186,14 @@
boolean isDeviceProvisioned,
boolean isNonblockable,
boolean isNoisy,
- int importance)
+ int importance,
+ @NotificationInfoAction int action)
throws RemoteException {
bindNotification(pm, iNotificationManager, pkg, notificationChannel,
numUniqueChannelsInRow, sbn, checkSaveListener, onSettingsClick,
onAppSettingsClick, isDeviceProvisioned, isNonblockable,
false /* isBlockingHelper */, false /* isUserSentimentNegative */, isNoisy,
- importance);
+ importance, action);
}
public void bindNotification(
@@ -209,7 +211,8 @@
boolean isForBlockingHelper,
boolean isUserSentimentNegative,
boolean isNoisy,
- int importance)
+ int importance,
+ @NotificationInfoAction int action)
throws RemoteException {
mINotificationManager = iNotificationManager;
mMetricsLogger = Dependency.get(MetricsLogger.class);
@@ -250,6 +253,10 @@
bindHeader();
bindPrompt();
bindButtons();
+
+ if (action != ACTION_NONE) {
+ swapContent(action, false /* don't animate */);
+ }
}
private void bindHeader() throws RemoteException {
@@ -351,7 +358,8 @@
}
private void saveImportance() {
- if (!mIsNonblockable) {
+ if (!mIsNonblockable
+ || mExitReason != NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS) {
updateImportance();
}
}
@@ -421,7 +429,7 @@
}
}
- private void swapContent(@SwapContentAction int action) {
+ private void swapContent(@NotificationInfoAction int action, boolean animate) {
if (mExpandAnimation != null) {
mExpandAnimation.cancel();
}
@@ -432,10 +440,11 @@
View header = findViewById(R.id.header);
switch (action) {
- case SWAP_CONTENT_UNDO:
+ case ACTION_UNDO:
mChosenImportance = mStartingChannelImportance;
break;
- case SWAP_CONTENT_TOGGLE_SILENT:
+ case ACTION_TOGGLE_SILENT:
+ mExitReason = NotificationCounters.BLOCKING_HELPER_TOGGLE_SILENT;
if (mStartingChannelOrNotificationImportance >= IMPORTANCE_DEFAULT) {
mChosenImportance = IMPORTANCE_LOW;
confirmationText.setText(R.string.notification_channel_silenced);
@@ -444,7 +453,8 @@
confirmationText.setText(R.string.notification_channel_unsilenced);
}
break;
- case SWAP_CONTENT_BLOCK:
+ case ACTION_BLOCK:
+ mExitReason = NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS;
if (mIsForeground) {
mChosenImportance = IMPORTANCE_MIN;
confirmationText.setText(R.string.notification_channel_minimized);
@@ -457,38 +467,41 @@
throw new IllegalArgumentException();
}
- boolean isUndo = action == SWAP_CONTENT_UNDO;
- ObjectAnimator promptAnim = ObjectAnimator.ofFloat(prompt, View.ALPHA,
- prompt.getAlpha(), isUndo ? 1f : 0f);
- promptAnim.setInterpolator(isUndo ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT);
- ObjectAnimator confirmAnim = ObjectAnimator.ofFloat(confirmation, View.ALPHA,
- confirmation.getAlpha(), isUndo ? 0f : 1f);
- confirmAnim.setInterpolator(isUndo ? Interpolators.ALPHA_OUT : Interpolators.ALPHA_IN);
+ boolean isUndo = action == ACTION_UNDO;
prompt.setVisibility(isUndo ? VISIBLE : GONE);
confirmation.setVisibility(isUndo ? GONE : VISIBLE);
header.setVisibility(isUndo ? VISIBLE : GONE);
- mExpandAnimation = new AnimatorSet();
- mExpandAnimation.playTogether(promptAnim, confirmAnim);
- mExpandAnimation.setDuration(150);
- mExpandAnimation.addListener(new AnimatorListenerAdapter() {
- boolean cancelled = false;
+ if (animate) {
+ ObjectAnimator promptAnim = ObjectAnimator.ofFloat(prompt, View.ALPHA,
+ prompt.getAlpha(), isUndo ? 1f : 0f);
+ promptAnim.setInterpolator(isUndo ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT);
+ ObjectAnimator confirmAnim = ObjectAnimator.ofFloat(confirmation, View.ALPHA,
+ confirmation.getAlpha(), isUndo ? 0f : 1f);
+ confirmAnim.setInterpolator(isUndo ? Interpolators.ALPHA_OUT : Interpolators.ALPHA_IN);
- @Override
- public void onAnimationCancel(Animator animation) {
- cancelled = true;
- }
+ mExpandAnimation = new AnimatorSet();
+ mExpandAnimation.playTogether(promptAnim, confirmAnim);
+ mExpandAnimation.setDuration(150);
+ mExpandAnimation.addListener(new AnimatorListenerAdapter() {
+ boolean mCancelled = false;
- @Override
- public void onAnimationEnd(Animator animation) {
- if (!cancelled) {
- prompt.setVisibility(isUndo ? VISIBLE : GONE);
- confirmation.setVisibility(isUndo ? GONE : VISIBLE);
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
}
- }
- });
- mExpandAnimation.start();
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!mCancelled) {
+ prompt.setVisibility(isUndo ? VISIBLE : GONE);
+ confirmation.setVisibility(isUndo ? GONE : VISIBLE);
+ }
+ }
+ });
+ mExpandAnimation.start();
+ }
// Since we're swapping/update the content, reset the timeout so the UI can't close
// immediately after the update.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index 674c8ee..c16b28f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -17,12 +17,16 @@
package com.android.systemui.statusbar.notification.row;
import static com.android.systemui.SwipeHelper.SWIPED_FAR_ENOUGH_SIZE_FRACTION;
+import static com.android.systemui.statusbar.notification.row.NotificationInfo.ACTION_BLOCK;
+import static com.android.systemui.statusbar.notification.row.NotificationInfo.ACTION_NONE;
+import static com.android.systemui.statusbar.notification.row.NotificationInfo.ACTION_TOGGLE_SILENT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
import android.app.Notification;
+import android.app.NotificationManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
@@ -40,7 +44,9 @@
import com.android.systemui.R;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.AlphaOptimizedImageView;
+import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.row.NotificationGuts.GutsContent;
+import com.android.systemui.statusbar.notification.row.NotificationInfo.NotificationInfoAction;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import java.util.ArrayList;
@@ -67,7 +73,7 @@
private Context mContext;
private FrameLayout mMenuContainer;
- private MenuItem mInfoItem;
+ private NotificationInfoMenuItem mInfoItem;
private MenuItem mAppOpsItem;
private MenuItem mSnoozeItem;
private ArrayList<MenuItem> mLeftMenuItems;
@@ -170,7 +176,9 @@
@Override
public void createMenu(ViewGroup parent, StatusBarNotification sbn) {
mParent = (ExpandableNotificationRow) parent;
- createMenuViews(true /* resetState */);
+ createMenuViews(true /* resetState */,
+ sbn != null && (sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE)
+ != 0);
}
@Override
@@ -214,7 +222,8 @@
// Menu hasn't been created yet, no need to do anything.
return;
}
- createMenuViews(!isMenuVisible() /* resetState */);
+ createMenuViews(!isMenuVisible() /* resetState */,
+ (sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0);
}
@Override
@@ -229,30 +238,47 @@
mParent.removeListener();
}
- private void createMenuViews(boolean resetState) {
+ private void createMenuViews(boolean resetState, final boolean isForeground) {
final Resources res = mContext.getResources();
mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);
mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
mLeftMenuItems.clear();
mRightMenuItems.clear();
// Construct the menu items based on the notification
- if (mParent != null && mParent.getStatusBarNotification() != null) {
- int flags = mParent.getStatusBarNotification().getNotification().flags;
- boolean isForeground = (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
- if (!isForeground) {
- // Only show snooze for non-foreground notifications
- mSnoozeItem = createSnoozeItem(mContext);
- mLeftMenuItems.add(mSnoozeItem);
- mRightMenuItems.add(mSnoozeItem);
- }
+ if (!isForeground) {
+ // Only show snooze for non-foreground notifications
+ mSnoozeItem = createSnoozeItem(mContext);
+ mLeftMenuItems.add(mSnoozeItem);
}
mInfoItem = createInfoItem(mContext);
- mLeftMenuItems.add(mInfoItem);
- mRightMenuItems.add(mInfoItem);
+ if (!NotificationUtils.useNewInterruptionModel(mContext)) {
+ mLeftMenuItems.add(mInfoItem);
+ }
mAppOpsItem = createAppOpsItem(mContext);
mLeftMenuItems.add(mAppOpsItem);
- mRightMenuItems.add(mAppOpsItem);
+
+ if (NotificationUtils.useNewInterruptionModel(mContext)) {
+ if (!mParent.getIsNonblockable()) {
+ mRightMenuItems.add(createBlockItem(mContext, mInfoItem.getGutsView()));
+ }
+ // TODO(kprevas): this is duplicated logic
+ // but it's currently spread across NotificationGutsManager and NotificationInfo.
+ // Try to consolidate and reuse here.
+ boolean canToggleSilent = !mParent.getIsNonblockable()
+ && !isForeground
+ && mParent.getEntry().noisy;
+ if (canToggleSilent) {
+ int channelImportance = mParent.getEntry().channel.getImportance();
+ int effectiveImportance =
+ channelImportance == NotificationManager.IMPORTANCE_UNSPECIFIED
+ ? mParent.getEntry().importance : channelImportance;
+ mRightMenuItems.add(createToggleSilentItem(mContext, mInfoItem.getGutsView(),
+ effectiveImportance < NotificationManager.IMPORTANCE_DEFAULT));
+ }
+ } else {
+ mRightMenuItems.addAll(mLeftMenuItems);
+ }
populateMenuViews();
if (resetState) {
@@ -595,7 +621,7 @@
// TODO -- handle / allow custom menu items!
}
- public static MenuItem createSnoozeItem(Context context) {
+ static MenuItem createSnoozeItem(Context context) {
Resources res = context.getResources();
NotificationSnooze content = (NotificationSnooze) LayoutInflater.from(context)
.inflate(R.layout.notification_snooze, null, false);
@@ -605,17 +631,16 @@
return snooze;
}
- public static MenuItem createInfoItem(Context context) {
+ static NotificationInfoMenuItem createInfoItem(Context context) {
Resources res = context.getResources();
String infoDescription = res.getString(R.string.notification_menu_gear_description);
NotificationInfo infoContent = (NotificationInfo) LayoutInflater.from(context).inflate(
R.layout.notification_info, null, false);
- MenuItem info = new NotificationMenuItem(context, infoDescription, infoContent,
- R.drawable.ic_settings);
- return info;
+ return new NotificationInfoMenuItem(context, infoDescription, infoContent,
+ R.drawable.ic_settings, ACTION_NONE);
}
- public static MenuItem createAppOpsItem(Context context) {
+ static MenuItem createAppOpsItem(Context context) {
AppOpsInfo appOpsContent = (AppOpsInfo) LayoutInflater.from(context).inflate(
R.layout.app_ops_info, null, false);
MenuItem info = new NotificationMenuItem(context, null, appOpsContent,
@@ -623,6 +648,29 @@
return info;
}
+ private static MenuItem createBlockItem(Context context, NotificationInfo gutsView) {
+ return new NotificationInfoMenuItem(
+ context,
+ context.getResources().getString(R.string.inline_stop_button),
+ gutsView,
+ R.drawable.ic_notification_block,
+ ACTION_BLOCK);
+ }
+
+ private static MenuItem createToggleSilentItem(Context context, NotificationInfo gutsView,
+ boolean isCurrentlySilent) {
+ return new NotificationInfoMenuItem(
+ context,
+ isCurrentlySilent
+ ? context.getResources().getString(R.string.inline_silent_button_alert)
+ : context.getResources().getString(R.string.inline_silent_button_silent),
+ gutsView,
+ isCurrentlySilent
+ ? R.drawable.ic_notifications_alert
+ : R.drawable.ic_notifications_silence,
+ ACTION_TOGGLE_SILENT);
+ }
+
private void addMenuView(MenuItem item, ViewGroup parent) {
View menuView = item.getMenuView();
if (menuView != null) {
@@ -704,7 +752,8 @@
* Add a new 'guts' panel. If iconResId < 0 it will not appear in the slow swipe menu
* but can still be exposed via other affordances.
*/
- public NotificationMenuItem(Context context, String s, GutsContent content, int iconResId) {
+ public NotificationMenuItem(Context context, String contentDescription, GutsContent content,
+ int iconResId) {
Resources res = context.getResources();
int padding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);
int tint = res.getColor(R.color.notification_gear_color);
@@ -717,7 +766,7 @@
iv.setAlpha(1f);
mMenuView = iv;
}
- mContentDescription = s;
+ mContentDescription = contentDescription;
mGutsContent = content;
}
@@ -737,4 +786,23 @@
return mContentDescription;
}
}
+
+ /** A {@link NotificationMenuItem} with an associated {@link NotificationInfoAction}. */
+ public static class NotificationInfoMenuItem extends NotificationMenuItem {
+
+ @NotificationInfoAction
+ int mAction;
+
+ public NotificationInfoMenuItem(Context context, String contentDescription,
+ NotificationInfo content, int iconResId,
+ @NotificationInfoAction int action) {
+ super(context, contentDescription, content, iconResId);
+ this.mAction = action;
+ }
+
+ @Override
+ public NotificationInfo getGutsView() {
+ return (NotificationInfo) super.getGutsView();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
index 75b05c2..d65f2c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
@@ -16,16 +16,6 @@
package com.android.systemui.statusbar.notification.row;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
-import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -53,8 +43,17 @@
import android.widget.LinearLayout;
import android.widget.TextView;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
public class NotificationSnooze extends LinearLayout
implements NotificationGuts.GutsContent, View.OnClickListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
index a21794b..1741a0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
@@ -17,11 +17,12 @@
package com.android.systemui.statusbar.notification.row;
import android.content.Context;
-import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
+import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
+
import com.android.systemui.R;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.notification.NotificationData;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index 8a061a6..1b40c06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -24,7 +24,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Interpolators;
-import com.android.systemui.statusbar.notification.row.ExpandableView;
/**
* A common base class for all views in the notification stack scroller which don't have a
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigTextTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigTextTemplateViewWrapper.java
index 133df3c..4261df3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigTextTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigTextTemplateViewWrapper.java
@@ -21,8 +21,8 @@
import android.view.View;
import com.android.internal.widget.ImageFloatingTextView;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.TransformableView;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
/**
* Wraps a notification containing a big text template
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index 6ca07ed..1be2afe7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.notification.row.wrapper;
+import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y;
import static com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
.DEFAULT_HEADER_VISIBLE_AMOUNT;
-import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y;
import android.app.Notification;
import android.content.Context;
@@ -34,12 +34,13 @@
import com.android.internal.widget.NotificationExpandButton;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.CustomInterpolatorTransformation;
-import com.android.systemui.statusbar.notification.ImageTransformState;
-import com.android.systemui.statusbar.notification.TransformState;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.ViewTransformationHelper;
+import com.android.systemui.statusbar.notification.CustomInterpolatorTransformation;
+import com.android.systemui.statusbar.notification.ImageTransformState;
+import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.TransformState;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import java.util.Stack;
@@ -69,7 +70,8 @@
protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
super(ctx, view, row);
mShowExpandButtonAtEnd = ctx.getResources().getBoolean(
- R.bool.config_showNotificationExpandButtonAtEnd);
+ R.bool.config_showNotificationExpandButtonAtEnd)
+ || NotificationUtils.useNewInterruptionModel(ctx);
mTransformationHelper = new ViewTransformationHelper();
// we want to avoid that the header clashes with the other text when transforming
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
index 37d2f6b..5a9a568 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
@@ -19,8 +19,8 @@
import android.content.Context;
import android.view.View;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.TransformableView;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
/**
* Wraps a notification containing a media template
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapper.java
index 13c5960..c9a2742 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapper.java
@@ -16,15 +16,15 @@
package com.android.systemui.statusbar.notification.row.wrapper;
+import android.content.Context;
+import android.view.View;
+
import com.android.internal.widget.MessagingLayout;
import com.android.internal.widget.MessagingLinearLayout;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import android.content.Context;
-import android.view.View;
-
/**
* Wraps a notification containing a messaging template
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
index d934902..ff5e15b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
@@ -37,11 +37,11 @@
import com.android.systemui.R;
import com.android.systemui.UiOffloadThread;
import com.android.systemui.statusbar.CrossFadeHelper;
+import com.android.systemui.statusbar.TransformableView;
+import com.android.systemui.statusbar.ViewTransformationHelper;
import com.android.systemui.statusbar.notification.ImageTransformState;
import com.android.systemui.statusbar.notification.TransformState;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.TransformableView;
-import com.android.systemui.statusbar.ViewTransformationHelper;
import com.android.systemui.statusbar.notification.row.HybridNotificationView;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index f76284d..1efdc56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -24,9 +24,9 @@
import android.view.View;
import com.android.systemui.statusbar.CrossFadeHelper;
+import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.notification.TransformState;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.TransformableView;
/**
* Wraps the actual notification content view; used to implement behaviors which are different for
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
index c6f953c..ba56a94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
@@ -16,10 +16,11 @@
package com.android.systemui.statusbar.notification.stack;
-import androidx.collection.ArraySet;
import android.util.Property;
import android.view.View;
+import androidx.collection.ArraySet;
+
import java.util.ArrayList;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index da089b3..74b4aa2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -33,13 +33,13 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.NotificationHeaderUtil;
+import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.HybridGroupManager;
import com.android.systemui.statusbar.notification.row.HybridNotificationView;
-import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
import java.util.ArrayList;
import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
index cfb6d99..4d100a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
@@ -16,17 +16,18 @@
package com.android.systemui.statusbar.notification.stack;
-import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
+import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator
+ .ExpandAnimationParameters;
import android.view.View;
import android.view.ViewGroup;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
-import com.android.systemui.statusbar.notification.NotificationData;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
/**
* Interface representing the entity that contains notifications. It can have
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 7bd58203..ff31b26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -401,6 +401,8 @@
*/
private float mBackgroundXFactor = 1f;
+ private boolean mSwipingInProgress;
+
private boolean mUsingLightTheme;
private boolean mQsExpanded;
private boolean mForwardScrollable;
@@ -3286,7 +3288,7 @@
|| ev.getActionMasked() == MotionEvent.ACTION_UP;
handleEmptySpaceClick(ev);
boolean expandWantsIt = false;
- boolean swipingInProgress = mSwipeHelper.isSwipingInProgress();
+ boolean swipingInProgress = mSwipingInProgress;
if (mIsExpanded && !swipingInProgress && !mOnlyScrollingInThisMotion) {
if (isCancelOrUp) {
mExpandHelper.onlyObserveMovements(false);
@@ -3341,7 +3343,7 @@
@Override
@ShadeViewRefactor(RefactorComponent.INPUT)
public boolean onGenericMotionEvent(MotionEvent event) {
- if (!isScrollingEnabled() || !mIsExpanded || mSwipeHelper.isSwipingInProgress() || mExpandingNotification
+ if (!isScrollingEnabled() || !mIsExpanded || mSwipingInProgress || mExpandingNotification
|| mDisallowScrollingInThisMotion) {
return false;
}
@@ -3568,7 +3570,7 @@
initDownStates(ev);
handleEmptySpaceClick(ev);
boolean expandWantsIt = false;
- boolean swipingInProgress = mSwipeHelper.isSwipingInProgress();
+ boolean swipingInProgress = mSwipingInProgress;
if (!swipingInProgress && !mOnlyScrollingInThisMotion) {
expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
}
@@ -3847,6 +3849,14 @@
}
}
+ @ShadeViewRefactor(RefactorComponent.INPUT)
+ private void setSwipingInProgress(boolean swiping) {
+ mSwipingInProgress = swiping;
+ if (swiping) {
+ requestDisallowInterceptTouchEvent(true);
+ }
+ }
+
@Override
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void onWindowFocusChanged(boolean hasWindowFocus) {
@@ -5642,6 +5652,7 @@
@Override
public void onDragCancelled(View v) {
+ setSwipingInProgress(false);
mFalsingManager.onNotificatonStopDismissing();
}
@@ -5669,6 +5680,7 @@
*/
public void handleChildViewDismissed(View view) {
+ setSwipingInProgress(false);
if (mDismissAllInProgress) {
return;
}
@@ -5737,6 +5749,7 @@
@Override
public void onBeginDrag(View v) {
mFalsingManager.onNotificatonStartDismissing();
+ setSwipingInProgress(true);
mAmbientState.onBeginDrag(v);
updateContinuousShadowDrawing();
if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) {
@@ -5786,7 +5799,13 @@
@Override
public boolean canChildBeDismissedInDirection(View v, boolean isRightOrDown) {
- return (isLayoutRtl() ? !isRightOrDown : isRightOrDown) && canChildBeDismissed(v);
+ boolean isValidDirection;
+ if (NotificationUtils.useNewInterruptionModel(mContext)) {
+ isValidDirection = isLayoutRtl() ? !isRightOrDown : isRightOrDown;
+ } else {
+ isValidDirection = true;
+ }
+ return isValidDirection && canChildBeDismissed(v);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index 599da3b..f1d9549 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -165,15 +165,15 @@
if (menuRow.isSnappedAndOnSameSide()) {
// Menu was snapped to previously and we're on the same side
- handleSwipeFromSnap(ev, animView, velocity, menuRow);
+ handleSwipeFromOpenState(ev, animView, velocity, menuRow);
} else {
// Menu has not been snapped, or was snapped previously but is now on
// the opposite side.
- handleSwipeFromNonSnap(ev, animView, velocity, menuRow);
+ handleSwipeFromClosedState(ev, animView, velocity, menuRow);
}
}
- private void handleSwipeFromNonSnap(MotionEvent ev, View animView, float velocity,
+ private void handleSwipeFromClosedState(MotionEvent ev, View animView, float velocity,
NotificationMenuRowPlugin menuRow) {
boolean isDismissGesture = isDismissGesture(ev);
final boolean gestureTowardsMenu = menuRow.isTowardsMenu(velocity);
@@ -183,10 +183,14 @@
final boolean showMenuForSlowOnGoing = !menuRow.canBeDismissed()
&& timeForGesture >= SWIPE_MENU_TIMING;
- if (!isFalseGesture(ev)
- && (swipedEnoughToShowMenu(menuRow)
- && (!gestureFastEnough || showMenuForSlowOnGoing))
- || (gestureTowardsMenu && !isDismissGesture)) {
+ boolean isNonDismissGestureTowardsMenu = gestureTowardsMenu && !isDismissGesture;
+ boolean isSlowSwipe = !gestureFastEnough || showMenuForSlowOnGoing;
+ boolean slowSwipedFarEnough = swipedEnoughToShowMenu(menuRow) && isSlowSwipe;
+ boolean isFastNonDismissGesture =
+ gestureFastEnough && !gestureTowardsMenu && !isDismissGesture;
+ boolean isMenuRevealingGestureAwayFromMenu = slowSwipedFarEnough || isFastNonDismissGesture;
+ if (isNonDismissGestureTowardsMenu
+ || (!isFalseGesture(ev) && isMenuRevealingGestureAwayFromMenu)) {
// Menu has not been snapped to previously and this is menu revealing gesture
snapOpen(animView, menuRow.getMenuSnapTarget(), velocity);
menuRow.onSnapOpen();
@@ -199,7 +203,7 @@
}
}
- private void handleSwipeFromSnap(MotionEvent ev, View animView, float velocity,
+ private void handleSwipeFromOpenState(MotionEvent ev, View animView, float velocity,
NotificationMenuRowPlugin menuRow) {
boolean isDismissGesture = isDismissGesture(ev);
@@ -227,7 +231,6 @@
if (mCallback.isExpanded()) {
// We don't want to quick-dismiss when it's a heads up as this might lead to closing
// of the panel early.
- mSwipingInProgress = false;
mCallback.handleChildViewDismissed(view);
}
mCallback.onDismiss();
@@ -247,7 +250,6 @@
@Override
public void snapChild(final View animView, final float targetLeft, float velocity) {
superSnapChild(animView, targetLeft, velocity);
- mSwipingInProgress = false;
mCallback.onDragCancelled(animView);
if (targetLeft == 0) {
handleMenuCoveredOrDismissed();
@@ -354,7 +356,6 @@
public void onMenuShown(View animView) {
setExposedMenuView(getTranslatingParentView());
- mSwipingInProgress = false;
mCallback.onDragCancelled(animView);
Handler handler = getHandler();
@@ -422,4 +423,4 @@
void onDismiss();
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index da3fb66..a94401b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -26,10 +26,10 @@
import com.android.keyguard.KeyguardSliceView;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.ExpandableView;
import java.util.ArrayList;
import java.util.HashSet;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
index a15fd70..b00068c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
@@ -28,9 +28,9 @@
import com.android.systemui.Dumpable;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.policy.HeadsUpUtil;
import java.io.FileDescriptor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index ab58660..1d7e899 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -15,8 +15,10 @@
package com.android.systemui.statusbar.phone;
import android.content.Context;
+import android.hardware.display.ColorDisplayManager;
import android.os.Handler;
import android.provider.Settings.Secure;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ColorDisplayController;
import com.android.systemui.Dependency;
@@ -80,7 +82,7 @@
Dependency.get(ManagedProfileController.class).addCallback(mProfileCallback);
}
if (!mAutoTracker.isAdded(NIGHT)
- && ColorDisplayController.isAvailable(mContext)) {
+ && ColorDisplayManager.isNightDisplayAvailable(mContext)) {
Dependency.get(ColorDisplayController.class).setListener(mColorDisplayCallback);
}
}
@@ -93,7 +95,9 @@
Dependency.get(HotspotController.class).removeCallback(mHotspotCallback);
Dependency.get(DataSaverController.class).removeCallback(mDataSaverListener);
Dependency.get(ManagedProfileController.class).removeCallback(mProfileCallback);
- Dependency.get(ColorDisplayController.class).setListener(null);
+ if (ColorDisplayManager.isNightDisplayAvailable(mContext)) {
+ Dependency.get(ColorDisplayController.class).setListener(null);
+ }
}
public void unmarkTileAsAutoAdded(String tabSpec) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
index c32dcea..3d81473 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.phone;
-import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
@@ -25,9 +24,9 @@
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
+import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
-import android.graphics.PorterDuff.Mode;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
import android.util.Log;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 8325bf8..302d630 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -16,18 +16,18 @@
package com.android.systemui.statusbar.phone;
-import android.hardware.biometrics.BiometricSourceType;
import android.content.Context;
+import android.hardware.biometrics.BiometricSourceType;
import android.os.Handler;
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.Trace;
import android.util.Log;
+import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardConstants;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.internal.util.LatencyTracker;
import com.android.systemui.Dependency;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
index 119f01a..4ced702 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
@@ -21,8 +21,8 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.view.View;
-
import android.view.View.AccessibilityDelegate;
+
import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider.ButtonInterface;
import com.android.systemui.statusbar.policy.KeyButtonDrawable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButtonGroup.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButtonGroup.java
index 9703043..cc8adde 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButtonGroup.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButtonGroup.java
@@ -19,6 +19,7 @@
import android.annotation.IdRes;
import android.annotation.NonNull;
import android.view.View;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index e052e53..3425dd2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -24,20 +24,20 @@
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.DemoMode;
import com.android.systemui.R;
-import com.android.systemui.statusbar.StatusIconDisplayable;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
+import com.android.systemui.statusbar.StatusIconDisplayable;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.policy.DarkIconDispatcher;
import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
+
import java.util.ArrayList;
public class DemoStatusIcons extends StatusIconContainer implements DemoMode, DarkReceiver {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ExpandableIndicator.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ExpandableIndicator.java
index 8f49c85..efc2891 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ExpandableIndicator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ExpandableIndicator.java
@@ -18,6 +18,7 @@
import android.graphics.drawable.AnimatedVectorDrawable;
import android.util.AttributeSet;
import android.widget.ImageView;
+
import com.android.systemui.R;
public class ExpandableIndicator extends ImageView {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index c66bbb1..40f9f45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -26,12 +26,12 @@
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.HeadsUpStatusBarView;
import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.policy.DarkIconDispatcher;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 976327a..00d6b14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -21,8 +21,6 @@
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
-import androidx.collection.ArraySet;
-
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.Region.Op;
@@ -33,16 +31,19 @@
import android.view.View;
import android.view.ViewTreeObserver;
+import androidx.collection.ArraySet;
+
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.ScreenDecorations;
+import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
@@ -56,8 +57,8 @@
* A implementation of HeadsUpManager for phone and car.
*/
public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
- ViewTreeObserver.OnComputeInternalInsetsListener, VisualStabilityManager.Callback,
- OnHeadsUpChangedListener, ConfigurationController.ConfigurationListener {
+ ViewTreeObserver.OnComputeInternalInsetsListener, VisualStabilityManager.Callback,
+ OnHeadsUpChangedListener, ConfigurationController.ConfigurationListener {
private static final String TAG = "HeadsUpManagerPhone";
private final View mStatusBarWindowView;
@@ -78,11 +79,13 @@
private int[] mTmpTwoArray = new int[2];
private boolean mHeadsUpGoingAway;
private boolean mWaitingOnCollapseWhenGoingAway;
+ private boolean mBubbleGoingAway;
private boolean mIsObserving;
private int mStatusBarState;
private final StateListener mStateListener = this::setStatusBarState;
private AnimationStateHandler mAnimationStateHandler;
+ private BubbleController mBubbleController = Dependency.get(BubbleController.class);
private final Pools.Pool<HeadsUpEntryPhone> mEntryPool = new Pools.Pool<HeadsUpEntryPhone>() {
private Stack<HeadsUpEntryPhone> mPoolObjects = new Stack<>();
@@ -127,6 +130,12 @@
}
});
Dependency.get(StatusBarStateController.class).addListener(mStateListener);
+ mBubbleController.setBubbleStateChangeListener((hasBubbles) -> {
+ if (!hasBubbles) {
+ mBubbleGoingAway = true;
+ }
+ updateTouchableRegionListener();
+ });
}
public void setAnimationStateHandler(AnimationStateHandler handler) {
@@ -210,6 +219,9 @@
mHeadsUpGoingAway = false;
updateTouchableRegionListener();
}
+ if (mBubbleController.hasBubbles() || !mIsExpanded) {
+ updateTouchableRegionListener();
+ }
}
}
@@ -310,6 +322,11 @@
} else {
setCollapsedTouchableInsets(info);
}
+ Rect r = mBubbleController.getTouchableRegion();
+ if (r != null) {
+ info.touchableRegion.union(r);
+ }
+ mBubbleGoingAway = false;
}
private void setCollapsedTouchableInsets(ViewTreeObserver.InternalInsetsInfo info) {
@@ -428,8 +445,11 @@
});
}
+ // TODO: some kind of TouchableRegionManager to deal with this, HeadsUpManager is not really
+ // the right place
private void updateTouchableRegionListener() {
boolean shouldObserve = hasPinnedHeadsUp() || mHeadsUpGoingAway
+ || mBubbleController.hasBubbles() || mBubbleGoingAway
|| mWaitingOnCollapseWhenGoingAway
|| mStatusBarWindowView.getRootWindowInsets().getDisplayCutout() != null;
if (shouldObserve == mIsObserving) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index e4a5caa..be4df45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -21,10 +21,9 @@
import android.view.ViewConfiguration;
import com.android.systemui.Gefingerpoken;
+import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
-import com.android.systemui.statusbar.notification.NotificationData;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
/**
* A helper class to handle touches on the heads-up views.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 5439497..21c506b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -54,6 +54,7 @@
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.MathUtils;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
@@ -568,6 +569,7 @@
mDarkAmount = darkAmount;
mIndicationController.setDarkAmount(darkAmount);
mLockIcon.setDarkAmount(darkAmount);
+ dozeTimeTick();
}
private static boolean isSuccessfulLaunch(int result) {
@@ -840,12 +842,10 @@
}
public void dozeTimeTick() {
- if (mDarkAmount == 1) {
- // Move views every minute to avoid burn-in
- int burnInYOffset = getBurnInOffset(mBurnInYOffset * 2, false /* xAxis */)
- - mBurnInYOffset;
- mLockIcon.setTranslationY(burnInYOffset);
- }
+ // Move views every minute to avoid burn-in
+ int burnInYOffset = -getBurnInOffset(mBurnInYOffset, false /* xAxis */);
+ burnInYOffset = (int) MathUtils.lerp(0, burnInYOffset, mDarkAmount);
+ mLockIcon.setTranslationY(burnInYOffset);
}
public void setBurnInXOffset(int burnInXOffset) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 235629b..c0d1818 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -16,8 +16,8 @@
package com.android.systemui.statusbar.phone;
-import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import static com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -268,6 +268,8 @@
}
public void hide(boolean destroyView) {
+ // TODO(b/113914868): investigation log for disappearing home button
+ Log.i(TAG, "KeyguardBouncer.hide (b/113914868): destroyView=" + destroyView);
if (isShowing()) {
StatsLog.write(StatsLog.KEYGUARD_BOUNCER_STATE_CHANGED,
StatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index 8cace72..e0c5516 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar.phone;
-import android.app.WallpaperColors;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
+
import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
@@ -32,9 +34,6 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
-
/**
* Controls how light status bar flag applies to the icons.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index d5067b5..1be3975 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -22,6 +22,7 @@
import android.graphics.PorterDuff;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
+import android.os.SystemProperties;
import android.util.AttributeSet;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -44,6 +45,8 @@
private static final int STATE_FACE_UNLOCK = 2;
private static final int STATE_FINGERPRINT = 3;
private static final int STATE_FINGERPRINT_ERROR = 4;
+ private static final boolean HOLLOW_PILL = SystemProperties
+ .getBoolean("persist.sysui.hollow_pill", false);
private int mLastState = 0;
private boolean mLastDeviceInteractive;
@@ -221,6 +224,16 @@
throw new IllegalArgumentException();
}
+ if (HOLLOW_PILL && deviceInteractive) {
+ switch (state) {
+ case STATE_FINGERPRINT:
+ case STATE_LOCK_OPEN:
+ case STATE_LOCKED:
+ case STATE_FACE_UNLOCK:
+ iconRes = R.drawable.ic_home_button_outline;
+ }
+ }
+
return mContext.getDrawable(iconRes);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
index 2779820..7621887 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
@@ -19,7 +19,6 @@
import android.metrics.LogMaker;
import android.util.ArrayMap;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 673cdb7..d2023ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -20,6 +20,7 @@
import android.app.ActivityManager;
import android.app.IWallpaperManager;
import android.app.IWallpaperManagerCallback;
+import android.app.WallpaperColors;
import android.app.WallpaperManager;
import android.content.Context;
import android.content.res.Resources;
@@ -36,7 +37,6 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
-import android.app.WallpaperColors;
import android.util.Log;
import com.android.keyguard.KeyguardUpdateMonitor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index 22b6ba6..7c31dae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -14,6 +14,8 @@
package com.android.systemui.statusbar.phone;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
@@ -34,11 +36,11 @@
import android.widget.Space;
import com.android.systemui.Dependency;
-import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.R;
import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider;
+import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.phone.ReverseLinearLayout.ReverseRelativeLayout;
import com.android.systemui.statusbar.policy.KeyButtonView;
import com.android.systemui.tuner.TunerService;
@@ -48,8 +50,6 @@
import java.util.List;
import java.util.Objects;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-
public class NavigationBarInflaterView extends FrameLayout
implements Tunable, PluginListener<NavBarButtonProvider> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
index d58b554..12a0cc8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -26,7 +26,6 @@
import android.view.IWindowManager;
import android.view.MotionEvent;
import android.view.View;
-import android.view.View.OnLayoutChangeListener;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.Dependency;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index b43bbdc..2f58ca1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -17,10 +17,15 @@
package com.android.systemui.statusbar.phone;
import static android.view.MotionEvent.ACTION_DOWN;
+
+import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_QUICK_SCRUB;
+import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON;
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK;
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_DEAD_ZONE;
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_HOME;
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
+import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_OVERVIEW;
+import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_ROTATION;
import android.animation.LayoutTransition;
import android.animation.LayoutTransition.TransitionListener;
@@ -60,15 +65,15 @@
import com.android.systemui.Dependency;
import com.android.systemui.DockedStackExistsListener;
import com.android.systemui.Interpolators;
-import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.R;
import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.plugins.statusbar.phone.NavGesture;
import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
+import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsOnboarding;
+import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.NavigationBarCompat;
import com.android.systemui.shared.system.WindowManagerWrapper;
@@ -79,11 +84,6 @@
import java.io.PrintWriter;
import java.util.function.Consumer;
-import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_QUICK_SCRUB;
-import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON;
-import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_OVERVIEW;
-import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_ROTATION;
-
public class NavigationBarView extends FrameLayout implements PluginListener<NavGesture> {
final static boolean DEBUG = false;
final static String TAG = "StatusBar/NavBarView";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java
index 593bfae..83067f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java
@@ -26,7 +26,6 @@
import android.graphics.Canvas;
import android.view.MotionEvent;
-import android.view.WindowManagerPolicyConstants;
import com.android.systemui.recents.OverviewProxyService;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
index 1524f80..2a11c26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
@@ -16,17 +16,14 @@
import android.content.Context;
import android.content.res.Configuration;
-import android.graphics.Rect;
-import androidx.annotation.VisibleForTesting;
import android.util.AttributeSet;
-import android.util.Log;
import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
-import com.android.systemui.R;
+import androidx.annotation.VisibleForTesting;
import java.util.ArrayList;
import java.util.Comparator;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index fa71df2..851e6d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -2787,7 +2787,9 @@
}
final float darkAmount = dozing && !mSemiAwake ? 1 : 0;
- mStatusBarStateController.setDozeAmount(darkAmount, animate);
+ if (!mSemiAwake) {
+ mStatusBarStateController.setDozeAmount(darkAmount, animate);
+ }
if (animate) {
mNotificationStackScroller.notifyDarkAnimationStart(mDozing);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index e7ede6f..ca762cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -20,7 +20,6 @@
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
-import androidx.annotation.DimenRes;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewStub;
@@ -28,6 +27,8 @@
import android.view.WindowInsets;
import android.widget.FrameLayout;
+import androidx.annotation.DimenRes;
+
import com.android.systemui.R;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index deac669e..65b0ecc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -22,7 +22,6 @@
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
-import android.view.View;
import android.widget.FrameLayout;
public abstract class PanelBar extends FrameLayout {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 0e6efc8..c84f3db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -66,6 +66,8 @@
import com.android.systemui.R;
import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.UiOffloadThread;
+import com.android.systemui.privacy.PrivacyItem;
+import com.android.systemui.privacy.PrivacyItemController;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -101,7 +103,8 @@
*/
public class PhoneStatusBarPolicy implements Callback, Callbacks,
RotationLockControllerCallback, Listener, LocationChangeCallback,
- ZenModeController.Callback, DeviceProvisionedListener, KeyguardMonitor.Callback {
+ ZenModeController.Callback, DeviceProvisionedListener, KeyguardMonitor.Callback,
+ PrivacyItemController.Callback {
private static final String TAG = "PhoneStatusBarPolicy";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -120,6 +123,8 @@
private final String mSlotHeadset;
private final String mSlotDataSaver;
private final String mSlotLocation;
+ private final String mSlotMicrophone;
+ private final String mSlotCamera;
private final Context mContext;
private final Handler mHandler = new Handler();
@@ -136,6 +141,7 @@
private final DeviceProvisionedController mProvisionedController;
private final KeyguardMonitor mKeyguardMonitor;
private final LocationController mLocationController;
+ private final PrivacyItemController mPrivacyItemController;
private final ArraySet<Pair<String, Integer>> mCurrentNotifs = new ArraySet<>();
private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
@@ -169,6 +175,7 @@
mProvisionedController = Dependency.get(DeviceProvisionedController.class);
mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
mLocationController = Dependency.get(LocationController.class);
+ mPrivacyItemController = new PrivacyItemController(mContext, this);
mSlotCast = context.getString(com.android.internal.R.string.status_bar_cast);
mSlotHotspot = context.getString(com.android.internal.R.string.status_bar_hotspot);
@@ -183,6 +190,8 @@
mSlotHeadset = context.getString(com.android.internal.R.string.status_bar_headset);
mSlotDataSaver = context.getString(com.android.internal.R.string.status_bar_data_saver);
mSlotLocation = context.getString(com.android.internal.R.string.status_bar_location);
+ mSlotMicrophone = context.getString(com.android.internal.R.string.status_bar_microphone);
+ mSlotCamera = context.getString(com.android.internal.R.string.status_bar_camera);
// listen for broadcasts
IntentFilter filter = new IntentFilter();
@@ -241,6 +250,12 @@
context.getString(R.string.accessibility_data_saver_on));
mIconController.setIconVisibility(mSlotDataSaver, false);
+ // privacy items
+ mIconController.setIcon(mSlotMicrophone, R.drawable.stat_sys_mic_none, null);
+ mIconController.setIconVisibility(mSlotMicrophone, false);
+ mIconController.setIcon(mSlotCamera, R.drawable.stat_sys_camera, null);
+ mIconController.setIconVisibility(mSlotCamera, false);
+
mRotationLockController.addCallback(this);
mBluetooth.addCallback(this);
mProvisionedController.addCallback(this);
@@ -251,6 +266,7 @@
mDataSaver.addCallback(this);
mKeyguardMonitor.addCallback(this);
mLocationController.addCallback(this);
+ mPrivacyItemController.setListening(true);
SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallbacks(this);
ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener);
@@ -279,6 +295,7 @@
mDataSaver.removeCallback(this);
mKeyguardMonitor.removeCallback(this);
mLocationController.removeCallback(this);
+ mPrivacyItemController.setListening(false);
SysUiServiceProvider.getComponent(mContext, CommandQueue.class).removeCallbacks(this);
mContext.unregisterReceiver(mIntentReceiver);
@@ -798,6 +815,34 @@
mIconController.setIconVisibility(mSlotDataSaver, isDataSaving);
}
+ @Override // PrivacyItemController.Callback
+ public void privacyChanged(List<PrivacyItem> privacyItems) {
+ updatePrivacyItems(privacyItems);
+ }
+
+ private void updatePrivacyItems(List<PrivacyItem> items) {
+ boolean showCamera = false;
+ boolean showMicrophone = false;
+ boolean showLocation = false;
+ for (PrivacyItem item : items) {
+ switch (item.getPrivacyType()) {
+ case TYPE_CAMERA:
+ showCamera = true;
+ break;
+ case TYPE_LOCATION:
+ showLocation = true;
+ break;
+ case TYPE_MICROPHONE:
+ showMicrophone = true;
+ break;
+ }
+ }
+
+ mIconController.setIconVisibility(mSlotCamera, showCamera);
+ mIconController.setIconVisibility(mSlotMicrophone, showMicrophone);
+ mIconController.setIconVisibility(mSlotLocation, showLocation);
+ }
+
private final TaskStackChangeListener mTaskListener = new TaskStackChangeListener() {
@Override
public void onTaskStackChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubAction.java
index c64e124..74744f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubAction.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubAction.java
@@ -34,7 +34,6 @@
import android.graphics.Rect;
import android.graphics.Shader;
import android.os.RemoteException;
-
import android.util.FloatProperty;
import android.util.Log;
import android.view.MotionEvent;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
index 37c4c58a..0eff4d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
@@ -42,10 +42,10 @@
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
-import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
import com.android.systemui.R;
-import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
+import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.system.NavigationBarCompat;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java
index c6e98e0..a7b8eff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java
@@ -39,10 +39,10 @@
import android.view.View;
import android.view.WindowManagerGlobal;
-import com.android.systemui.Dependency;
-import com.android.systemui.Interpolators;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenPinningNotify.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenPinningNotify.java
index 2a5028b..f8731b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenPinningNotify.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenPinningNotify.java
@@ -19,7 +19,6 @@
import android.content.Context;
import android.os.SystemClock;
import android.util.Slog;
-import android.view.WindowManager;
import android.widget.Toast;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 1bed26d..9f34cbb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -23,7 +23,6 @@
import android.app.WallpaperManager;
import android.content.Context;
import android.graphics.Color;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Trace;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index ade063d..75f81c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone;
import android.graphics.Color;
+import android.os.SystemProperties;
import android.os.Trace;
import android.util.MathUtils;
@@ -75,7 +76,12 @@
public void prepare(ScrimState previousState) {
mBlankScreen = mDisplayRequiresBlanking && previousState != ScrimState.AOD;
mAnimationDuration = StackStateAnimator.ANIMATION_DURATION_WAKEUP;
- mCurrentBehindAlpha = ScrimController.GRADIENT_SCRIM_DARK_KEYGUARD;
+ String opacity = SystemProperties.get("persist.sysui.aod2_scrim_opacity", "0.8");
+ try {
+ mCurrentBehindAlpha = Float.parseFloat(opacity);
+ } catch (RuntimeException e) {
+ mCurrentBehindAlpha = ScrimController.GRADIENT_SCRIM_DARK_KEYGUARD;
+ }
mCurrentInFrontAlpha = 0;
mCurrentInFrontTint = Color.BLACK;
mCurrentBehindTint = Color.BLACK;
@@ -186,6 +192,20 @@
mBlankScreen = false;
}
}
+ },
+
+ /**
+ * Unlocked with a bubble expanded.
+ */
+ BUBBLE_EXPANDED(7) {
+ @Override
+ public void prepare(ScrimState previousState) {
+ mCurrentInFrontTint = Color.TRANSPARENT;
+ mCurrentBehindTint = Color.TRANSPARENT;
+ mAnimationDuration = ScrimController.ANIMATION_DURATION;
+ mCurrentBehindAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
+ mBlankScreen = false;
+ }
};
boolean mBlankScreen = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java
index e546119..f926218 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java
@@ -15,6 +15,7 @@
package com.android.systemui.statusbar.phone;
import android.view.View;
+
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 12fbf2d..8e61366 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -141,6 +141,7 @@
import com.android.systemui.UiOffloadThread;
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.charging.WirelessChargingAnimation;
import com.android.systemui.classifier.FalsingLog;
import com.android.systemui.classifier.FalsingManager;
@@ -342,7 +343,8 @@
private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
protected StatusBarWindowController mStatusBarWindowController;
protected UnlockMethodCache mUnlockMethodCache;
- private DozeServiceHost mDozeServiceHost = new DozeServiceHost();
+ @VisibleForTesting
+ DozeServiceHost mDozeServiceHost = new DozeServiceHost();
private boolean mWakeUpComingFromTouch;
private PointF mWakeUpTouchLocation;
@@ -455,6 +457,13 @@
private NotificationMediaManager mMediaManager;
protected NotificationLockscreenUserManager mLockscreenUserManager;
protected NotificationRemoteInputManager mRemoteInputManager;
+ protected BubbleController mBubbleController;
+ private final BubbleController.BubbleExpandListener mBubbleExpandListener =
+ (isExpanding, amount) -> {
+ if (amount == 1) {
+ updateScrimController();
+ }
+ };
private final BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() {
@Override
@@ -479,7 +488,7 @@
private boolean mLaunchCameraOnScreenTurningOn;
private boolean mLaunchCameraOnFinishedGoingToSleep;
private int mLastCameraLaunchSource;
- private PowerManager.WakeLock mGestureWakeLock;
+ protected PowerManager.WakeLock mGestureWakeLock;
private Vibrator mVibrator;
private long[] mCameraLaunchGestureVibePattern;
@@ -612,6 +621,8 @@
mColorExtractor = Dependency.get(SysuiColorExtractor.class);
mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
mNavigationBarController = Dependency.get(DisplayNavigationBarController.class);
+ mBubbleController = Dependency.get(BubbleController.class);
+ mBubbleController.setExpandListener(mBubbleExpandListener);
mColorExtractor.addOnColorsChangedListener(this);
mStatusBarStateController.addListener(this, StatusBarStateController.RANK_STATUS_BAR);
@@ -3263,6 +3274,10 @@
mNotificationPanel.setDozing(mDozing, animate, mWakeUpTouchLocation,
mDozeServiceHost.wasPassivelyInterrupted());
+ if (mNotificationPanel.isSemiAwake()
+ && SystemProperties.getBoolean("persist.systemui.show_swipe_up", false)) {
+ mKeyguardIndicationController.showTransientIndication(R.string.keyguard_unlock);
+ }
updateQsExpansionEnabled();
Trace.endSection();
}
@@ -3609,6 +3624,7 @@
}
}
+ @VisibleForTesting
final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
@Override
public void onFinishedGoingToSleep() {
@@ -3650,6 +3666,7 @@
mNotificationPanel.setTouchAndAnimationDisabled(false);
updateVisibleToUser();
updateIsKeyguard();
+ mDozeServiceHost.stopDozing();
}
};
@@ -3842,6 +3859,8 @@
} else if (mIsKeyguard && !wakeAndUnlocking) {
mScrimController.transitionTo(mNotificationPanel.isSemiAwake()
? ScrimState.DARK_KEYGUARD : ScrimState.KEYGUARD);
+ } else if (mBubbleController.isStackExpanded()) {
+ mScrimController.transitionTo(ScrimState.BUBBLE_EXPANDED);
} else {
mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
}
@@ -3856,7 +3875,8 @@
return mStatusBarKeyguardViewManager.isShowing();
}
- private final class DozeServiceHost implements DozeHost {
+ @VisibleForTesting
+ final class DozeServiceHost implements DozeHost {
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
private boolean mAnimateWakeup;
private boolean mAnimateScreenOff;
@@ -3944,7 +3964,6 @@
mDozingRequested = false;
DozeLog.traceDozing(mContext, mDozing);
updateDozing();
- mWakefulnessLifecycle.dispatchStartedWakingUp();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 0d6cb5c..26c9d28 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -16,16 +16,15 @@
import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
import static android.app.StatusBarManager.DISABLE_NONE;
+
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
import android.content.Context;
import android.os.Bundle;
-import androidx.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.ArraySet;
-import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
@@ -33,19 +32,22 @@
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.DemoMode;
import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.statusbar.StatusIconDisplayable;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
+import com.android.systemui.statusbar.StatusIconDisplayable;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.policy.DarkIconDispatcher;
import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.util.Utils.DisableStateTracker;
+
import java.util.List;
public interface StatusBarIconController {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 24a5896..7c17c01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -45,8 +45,6 @@
import java.util.ArrayList;
import java.util.List;
-import static com.android.systemui.statusbar.phone.StatusBarIconController.TAG_PRIMARY;
-
/**
* Receives the callbacks from CommandQueue related to icons and tracks the state of
* all the icons. Dispatches this state to any IconManagers that are currently
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index c4ff85f..88d0035 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.graphics.drawable.Icon;
import android.os.UserHandle;
+
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
index b7e1cfb..2e41617 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
@@ -16,16 +16,17 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.statusbar.phone.StatusBarIconController.TAG_PRIMARY;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.statusbar.StatusBarIcon;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
-import static com.android.systemui.statusbar.phone.StatusBarIconController.TAG_PRIMARY;
-
public class StatusBarIconList {
private ArrayList<Slot> mSlots = new ArrayList<>();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index f81ffe9..8286d26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -17,29 +17,20 @@
package com.android.systemui.statusbar.phone;
import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Rect;
import android.os.Handler;
-import android.os.Looper;
import android.telephony.SubscriptionInfo;
import android.util.ArraySet;
import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.ImageView;
-import com.android.settingslib.graph.SignalDrawable;
+
import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
-import com.android.systemui.statusbar.policy.DarkIconDispatcher;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkControllerImpl;
import com.android.systemui.statusbar.policy.SecurityController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
index 11de941..62b6d91 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
@@ -65,7 +65,7 @@
private final WindowManager mWindowManager;
private final IActivityManager mActivityManager;
private final DozeParameters mDozeParameters;
- private View mStatusBarView;
+ private ViewGroup mStatusBarView;
private WindowManager.LayoutParams mLp;
private WindowManager.LayoutParams mLpChanged;
private boolean mHasTopUi;
@@ -109,7 +109,7 @@
* @param statusBarView The view to add.
* @param barHeight The height of the status bar in collapsed state.
*/
- public void add(View statusBarView, int barHeight) {
+ public void add(ViewGroup statusBarView, int barHeight) {
// Now that the status bar window encompasses the sliding panel and its
// translucent backdrop, the entire thing is made TRANSLUCENT and is
@@ -138,6 +138,10 @@
onThemeChanged();
}
+ public ViewGroup getStatusBarView() {
+ return mStatusBarView;
+ }
+
public void setDozeScreenBrightness(int value) {
mScreenBrightnessDoze = value / 255f;
}
@@ -232,7 +236,7 @@
private boolean isExpanded(State state) {
return !state.forceCollapsed && (state.isKeyguardShowingAndNotOccluded()
|| state.panelVisible || state.keyguardFadingAway || state.bouncerShowing
- || state.headsUpShowing
+ || state.headsUpShowing || state.bubblesShowing
|| state.scrimsVisibility != ScrimController.VISIBILITY_FULLY_TRANSPARENT);
}
@@ -469,6 +473,21 @@
apply(mCurrentState);
}
+ /**
+ * Sets whether there are bubbles showing on the screen.
+ */
+ public void setBubblesShowing(boolean bubblesShowing) {
+ mCurrentState.bubblesShowing = bubblesShowing;
+ apply(mCurrentState);
+ }
+
+ /**
+ * The bubbles showing state for the status bar.
+ */
+ public boolean getBubblesShowing() {
+ return mCurrentState.bubblesShowing;
+ }
+
public void setStateListener(OtherwisedCollapsedListener listener) {
mListener = listener;
}
@@ -521,6 +540,7 @@
boolean backdropShowing;
boolean wallpaperSupportsAmbientMode;
boolean notTouchable;
+ boolean bubblesShowing;
/**
* The {@link StatusBar} state from the status bar.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index ad9b9b3..978a72d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -33,7 +33,6 @@
import android.media.session.MediaSessionLegacyHelper;
import android.net.Uri;
import android.os.Bundle;
-import android.os.IBinder;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.ActionMode;
@@ -50,8 +49,6 @@
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.Window;
-import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
import android.widget.FrameLayout;
import com.android.internal.annotations.VisibleForTesting;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
index 56a177e..6495910 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.phone;
-import static com.android.systemui.statusbar.StatusBarIconView.STATE_ICON;
import static com.android.systemui.statusbar.StatusBarIconView.STATE_DOT;
import static com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN;
+import static com.android.systemui.statusbar.StatusBarIconView.STATE_ICON;
import android.annotation.Nullable;
import android.content.Context;
@@ -28,14 +28,15 @@
import android.graphics.Paint.Style;
import android.util.AttributeSet;
import android.util.Log;
-
import android.view.View;
+
import com.android.keyguard.AlphaOptimizedLinearLayout;
import com.android.systemui.R;
import com.android.systemui.statusbar.StatusIconDisplayable;
import com.android.systemui.statusbar.notification.stack.AnimationFilter;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.ViewState;
+
import java.util.ArrayList;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
index e5925f0..bdd76c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
@@ -16,8 +16,8 @@
package com.android.systemui.statusbar.phone;
-import android.hardware.biometrics.BiometricSourceType;
import android.content.Context;
+import android.hardware.biometrics.BiometricSourceType;
import android.os.Trace;
import com.android.internal.widget.LockPatternUtils;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java
index 2ed2edb..ba55f2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java
@@ -20,7 +20,6 @@
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
-import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
index d85e18c..67da8a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
@@ -14,14 +14,11 @@
package com.android.systemui.statusbar.policy;
-import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.Context;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
-import java.util.List;
-
/**
* For mocking because AccessibilityManager is final for some reason...
*/
@@ -62,8 +59,8 @@
mAccessibilityManager.sendAccessibilityEvent(event);
}
- public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
- int feedbackTypeFlags) {
- return mAccessibilityManager.getEnabledAccessibilityServiceList(feedbackTypeFlags);
+ /** Returns a recommended ui timeout value in milliseconds. */
+ public int getRecommendedTimeoutMillis(int originalTimeout, int uiContentFlags) {
+ return mAccessibilityManager.getRecommendedTimeoutMillis(originalTimeout, uiContentFlags);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
index b76d536..e1bb19a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
@@ -19,6 +19,7 @@
import android.os.Looper;
import android.os.Message;
import android.telephony.SubscriptionInfo;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
index 533bd86..ccfe073 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.policy;
+import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
+
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -30,6 +32,8 @@
import android.util.ArraySet;
import android.util.Log;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.annotations.GuardedBy;
import com.android.systemui.R;
@@ -40,10 +44,6 @@
import java.util.Set;
import java.util.UUID;
-import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
-
-import androidx.annotation.VisibleForTesting;
-
/** Platform implementation of the cast controller. **/
public class CastControllerImpl implements CastController {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
index 2ede327..911715f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
@@ -21,8 +21,6 @@
import android.os.Looper;
import android.os.RemoteException;
-import com.android.systemui.statusbar.policy.DataSaverController.Listener;
-
import java.util.ArrayList;
public class DataSaverControllerImpl implements DataSaverController {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
index dcce77c..74a30fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
@@ -27,7 +27,6 @@
import android.util.AttributeSet;
import android.widget.TextView;
-import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java
index cae76b4..7b4c35a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java
@@ -14,8 +14,6 @@
package com.android.systemui.statusbar.policy;
-import android.content.Context;
-
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
public interface DeviceProvisionedController extends CallbackController<DeviceProvisionedListener> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
index c726189..0d6178b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
@@ -23,13 +23,9 @@
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.provider.Settings;
-import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.text.TextUtils;
import android.util.AttributeSet;
-import android.util.Log;
-import android.view.ViewGroup;
-import android.view.ViewParent;
import android.widget.TextView;
import com.android.internal.telephony.IccCardConstants;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
index 11fc408..e23ce2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
@@ -30,8 +30,6 @@
import android.text.TextUtils;
import android.util.Log;
-import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener;
-
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index b4d24d16..6b83b70b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -16,22 +16,23 @@
package com.android.systemui.statusbar.policy;
-import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_HEADS_UP;
+import static com.android.systemui.statusbar.notification.row.NotificationInflater
+ .FLAG_CONTENT_VIEW_HEADS_UP;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.database.ContentObserver;
-import android.util.ArrayMap;
import android.provider.Settings;
+import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.statusbar.AlertingNotificationManager;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import java.io.FileDescriptor;
import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLoggerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLoggerImpl.java
index 5fd79a4..aee021c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLoggerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLoggerImpl.java
@@ -23,9 +23,10 @@
import android.metrics.LogMaker;
import android.os.Handler;
import android.os.Looper;
-import androidx.annotation.VisibleForTesting;
import android.util.ArraySet;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.logging.MetricsLogger;
import java.util.Arrays;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
index 2340786..03c89c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
@@ -37,6 +37,7 @@
import android.graphics.drawable.Drawable;
import android.util.FloatProperty;
import android.view.ContextThemeWrapper;
+
import com.android.settingslib.Utils;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
index 59bd85e..8b86996 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
@@ -25,9 +25,9 @@
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
+import android.graphics.RecordingCanvas;
import android.graphics.drawable.Drawable;
import android.os.Handler;
-import android.graphics.RecordingCanvas;
import android.view.RenderNodeAnimator;
import android.view.View;
import android.view.ViewConfiguration;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 6fa73ef..2c756ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -45,12 +45,13 @@
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ImageView;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
-import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.R;
import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider.ButtonInterface;
+import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.shared.system.NavigationBarCompat;
public class KeyButtonView extends ImageView implements ButtonInterface {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index fcd7e09..840e77e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -29,12 +29,14 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+
import androidx.annotation.VisibleForTesting;
+
import com.android.systemui.util.Utils;
+
import java.util.ArrayList;
import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 8ca1415..e943261 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -35,8 +35,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.cdma.EriInfo;
-import com.android.settingslib.graph.SignalDrawable;
import com.android.settingslib.Utils;
+import com.android.settingslib.graph.SignalDrawable;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
index dfdeae1..dac878c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
@@ -23,8 +23,6 @@
import android.content.IntentFilter;
import android.os.UserHandle;
-import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
-
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java
index 5028fd1..d434768 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java
@@ -16,8 +16,8 @@
package com.android.systemui.statusbar.policy;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
/**
* A listener to heads up changes
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 7dd0d0f..a485fa8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -55,8 +55,8 @@
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.RemoteInputController;
+import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
index 91c208d..9ec30d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
@@ -15,16 +15,17 @@
*/
package com.android.systemui.statusbar.policy;
+import static com.android.systemui.statusbar.policy.NetworkControllerImpl.TAG;
+
import android.content.Context;
import android.text.format.DateFormat;
import android.util.Log;
+
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
import java.io.PrintWriter;
import java.util.BitSet;
-import static com.android.systemui.statusbar.policy.NetworkControllerImpl.TAG;
-
/**
* Common base class for handling signal for both wifi and mobile data.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
index 7b0b8004..71d6e54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.policy;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.database.ContentObserver;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index aa4782f..42f1378 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -28,11 +28,11 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ContrastColorUtil;
-import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.statusbar.SmartReplyController;
+import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
index f5ae88b..fed8032 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -37,7 +37,6 @@
import com.android.internal.util.UserIcons;
import com.android.settingslib.drawable.UserIconDrawable;
import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index b5d92a5..cd379c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -25,7 +25,6 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.SystemUI;
-import com.android.systemui.pip.tv.PipManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/BatteryPreference.java b/packages/SystemUI/src/com/android/systemui/tuner/BatteryPreference.java
index 499be42..905b9a3 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/BatteryPreference.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/BatteryPreference.java
@@ -14,19 +14,21 @@
package com.android.systemui.tuner;
+import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
+
import android.content.Context;
import android.provider.Settings;
-import androidx.preference.DropDownPreference;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.AttributeSet;
+
+import androidx.preference.DropDownPreference;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.phone.StatusBarIconController;
-import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
-
public class BatteryPreference extends DropDownPreference implements TunerService.Tunable {
private static final String PERCENT = "percent";
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/BetterListPreference.java b/packages/SystemUI/src/com/android/systemui/tuner/BetterListPreference.java
index 702abea..265823a 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/BetterListPreference.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/BetterListPreference.java
@@ -15,9 +15,10 @@
package com.android.systemui.tuner;
import android.content.Context;
-import androidx.preference.ListPreference;
import android.util.AttributeSet;
+import androidx.preference.ListPreference;
+
public class BetterListPreference extends ListPreference {
private CharSequence mSummary;
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/ClockPreference.java b/packages/SystemUI/src/com/android/systemui/tuner/ClockPreference.java
index 16ab65c..a526603 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/ClockPreference.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/ClockPreference.java
@@ -14,11 +14,12 @@
package com.android.systemui.tuner;
import android.content.Context;
-import androidx.preference.DropDownPreference;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.AttributeSet;
+import androidx.preference.DropDownPreference;
+
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.Clock;
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/CustomListPreference.java b/packages/SystemUI/src/com/android/systemui/tuner/CustomListPreference.java
index 8da0043..ade1f82 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/CustomListPreference.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/CustomListPreference.java
@@ -14,7 +14,6 @@
package com.android.systemui.tuner;
-import android.annotation.Nullable;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
@@ -22,12 +21,10 @@
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
-import androidx.preference.ListPreferenceDialogFragment;
-import androidx.preference.ListPreference;
import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
+
+import androidx.preference.ListPreference;
+import androidx.preference.ListPreferenceDialogFragment;
public class CustomListPreference extends ListPreference {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
index a0f278b..a60ca62 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
@@ -23,12 +23,13 @@
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
-import androidx.preference.PreferenceFragment;
-import androidx.preference.SwitchPreference;
+import android.view.MenuItem;
+
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceChangeListener;
+import androidx.preference.PreferenceFragment;
import androidx.preference.PreferenceScreen;
-import android.view.MenuItem;
+import androidx.preference.SwitchPreference;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/LockscreenFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/LockscreenFragment.java
index d63235c..4dbceac 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/LockscreenFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/LockscreenFragment.java
@@ -21,32 +21,25 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.LauncherActivityInfo;
-import android.content.pm.LauncherApps;
-import android.content.pm.LauncherApps.ShortcutQuery;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ShortcutInfo;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.ScaleDrawable;
import android.os.Bundle;
import android.os.Handler;
-import android.os.Process;
-import androidx.preference.PreferenceFragment;
-import androidx.preference.SwitchPreference;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceGroup;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import android.text.TextUtils;
-import android.util.Log;
import android.util.TypedValue;
-import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragment;
+import androidx.preference.SwitchPreference;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.IntentButtonProvider.IntentButton;
@@ -57,7 +50,6 @@
import com.android.systemui.tuner.TunerService.Tunable;
import java.util.ArrayList;
-import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
index e3a452a..fa531b5 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
@@ -34,9 +34,6 @@
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Handler;
-import androidx.preference.ListPreference;
-import androidx.preference.Preference;
-import androidx.preference.Preference.OnPreferenceChangeListener;
import android.text.SpannableStringBuilder;
import android.text.style.ImageSpan;
import android.util.Log;
@@ -44,6 +41,10 @@
import android.view.KeyEvent;
import android.widget.EditText;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.Preference.OnPreferenceChangeListener;
+
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.tuner.TunerService.Tunable;
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/OtherPrefs.java b/packages/SystemUI/src/com/android/systemui/tuner/OtherPrefs.java
index d766145..7239c8a 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/OtherPrefs.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/OtherPrefs.java
@@ -15,7 +15,9 @@
package com.android.systemui.tuner;
import android.os.Bundle;
+
import androidx.preference.PreferenceFragment;
+
import com.android.systemui.R;
public class OtherPrefs extends PreferenceFragment {
@@ -23,4 +25,4 @@
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.other_settings);
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
index ecb830c..dae1472 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
@@ -29,6 +29,11 @@
import android.util.ArraySet;
import android.view.View;
+import androidx.preference.PreferenceFragment;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.PreferenceViewHolder;
+import androidx.preference.SwitchPreference;
+
import com.android.internal.util.ArrayUtils;
import com.android.systemui.Dependency;
import com.android.systemui.R;
@@ -41,11 +46,6 @@
import java.util.List;
import java.util.Set;
-import androidx.preference.PreferenceFragment;
-import androidx.preference.PreferenceScreen;
-import androidx.preference.PreferenceViewHolder;
-import androidx.preference.SwitchPreference;
-
public class PluginFragment extends PreferenceFragment {
public static final String ACTION_PLUGIN_SETTINGS
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/PowerNotificationControlsFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/PowerNotificationControlsFragment.java
index 8740a3c..80f9de6 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/PowerNotificationControlsFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/PowerNotificationControlsFragment.java
@@ -15,10 +15,6 @@
*/
package com.android.systemui.tuner;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.R;
-
import android.annotation.Nullable;
import android.app.Fragment;
import android.os.Bundle;
@@ -29,6 +25,10 @@
import android.widget.Switch;
import android.widget.TextView;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.R;
+
public class PowerNotificationControlsFragment extends Fragment {
private static final String KEY_SHOW_PNC = "show_importance_slider";
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java b/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java
index d80c649..79811c5 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java
@@ -17,21 +17,19 @@
import android.app.AlertDialog.Builder;
import android.app.Dialog;
import android.app.DialogFragment;
-import android.app.Fragment;
import android.content.Context;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
-import androidx.preference.PreferenceFragment;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.View;
import android.widget.Toolbar;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
import com.android.settingslib.Utils;
-import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.R;
+import com.android.systemui.fragments.FragmentHostManager;
import java.util.Objects;
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/SelectablePreference.java b/packages/SystemUI/src/com/android/systemui/tuner/SelectablePreference.java
index dded464..0be793e 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/SelectablePreference.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/SelectablePreference.java
@@ -16,9 +16,10 @@
import android.content.Context;
import android.graphics.drawable.Drawable;
-import androidx.preference.CheckBoxPreference;
import android.util.TypedValue;
+import androidx.preference.CheckBoxPreference;
+
import com.android.systemui.statusbar.ScalingDrawableWrapper;
public class SelectablePreference extends CheckBoxPreference {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/ShortcutPicker.java b/packages/SystemUI/src/com/android/systemui/tuner/ShortcutPicker.java
index 3c9d25d..11e1f27 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/ShortcutPicker.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/ShortcutPicker.java
@@ -20,12 +20,12 @@
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.Process;
-import androidx.preference.PreferenceFragment;
+
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceFragment;
import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder;
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/StatusBarSwitch.java b/packages/SystemUI/src/com/android/systemui/tuner/StatusBarSwitch.java
index 670fcc2..6f23e20 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/StatusBarSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/StatusBarSwitch.java
@@ -19,10 +19,11 @@
import android.content.ContentResolver;
import android.content.Context;
import android.provider.Settings;
-import androidx.preference.SwitchPreference;
import android.text.TextUtils;
import android.util.AttributeSet;
+import androidx.preference.SwitchPreference;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
index 5aa3035..ecf1784 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
@@ -22,12 +22,13 @@
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
-import androidx.preference.PreferenceFragment;
-import androidx.preference.Preference;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragment;
+
import com.android.internal.hardware.AmbientDisplayConfiguration;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerPreferenceFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerPreferenceFragment.java
index a22277e..9cc8943 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerPreferenceFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerPreferenceFragment.java
@@ -15,9 +15,9 @@
package com.android.systemui.tuner;
import android.app.DialogFragment;
-import android.os.Bundle;
-import androidx.preference.PreferenceFragment;
+
import androidx.preference.Preference;
+import androidx.preference.PreferenceFragment;
public abstract class TunerPreferenceFragment extends PreferenceFragment {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index 3a9d1c7..3bccdab 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -26,8 +26,6 @@
import android.os.UserHandle;
import android.provider.Settings;
-import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
-import com.android.systemui.DemoMode;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java
index f53d516..2df9000 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java
@@ -3,9 +3,10 @@
import android.content.Context;
import android.content.res.TypedArray;
import android.provider.Settings;
-import androidx.preference.SwitchPreference;
import android.util.AttributeSet;
+import androidx.preference.SwitchPreference;
+
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.Dependency;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index 2f8dfdc..329d029 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -19,7 +19,6 @@
import android.annotation.NonNull;
import android.app.Notification;
import android.app.Notification.Action;
-import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
index ec5030b..fa3ff64 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
@@ -16,6 +16,8 @@
package com.android.systemui.usb;
+import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
+
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.pm.ResolveInfo;
@@ -38,8 +40,6 @@
import java.util.ArrayList;
import java.util.Iterator;
-import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
-
/* Activity for choosing an application for a USB device or accessory */
public class UsbResolverActivity extends ResolverActivity {
public static final String TAG = "UsbResolverActivity";
diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
index c468fef..f35af90 100644
--- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
+++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
@@ -16,7 +16,6 @@
import android.app.NotificationChannel;
import android.app.NotificationManager;
-
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/DumpTruck.java b/packages/SystemUI/src/com/android/systemui/util/leak/DumpTruck.java
index 9b15b00..efd6e03 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/DumpTruck.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/DumpTruck.java
@@ -20,9 +20,10 @@
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
-import androidx.core.content.FileProvider;
import android.util.Log;
+import androidx.core.content.FileProvider;
+
import com.android.systemui.Dependency;
import java.io.BufferedInputStream;
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
index 3c88d29..b2cc269 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
@@ -37,7 +37,6 @@
import android.os.Process;
import android.os.SystemProperties;
import android.provider.Settings;
-import androidx.annotation.VisibleForTesting;
import android.service.quicksettings.Tile;
import android.text.format.DateUtils;
import android.util.Log;
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/LeakDetector.java b/packages/SystemUI/src/com/android/systemui/util/leak/LeakDetector.java
index 574fdb98..c50e8f8 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/LeakDetector.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/LeakDetector.java
@@ -24,7 +24,6 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.io.Writer;
import java.util.Collection;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java b/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java
index 8ea5fd4..a47e99d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java
@@ -27,9 +27,10 @@
import android.os.Debug;
import android.os.SystemProperties;
import android.os.UserHandle;
-import androidx.core.content.FileProvider;
import android.util.Log;
+import androidx.core.content.FileProvider;
+
import com.google.android.collect.Lists;
import java.io.File;
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/SettableWakeLock.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/SettableWakeLock.java
index f2ed55f3..13729df 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/SettableWakeLock.java
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/SettableWakeLock.java
@@ -16,9 +16,6 @@
package com.android.systemui.util.wakelock;
-import android.os.Handler;
-import android.os.PowerManager;
-
import com.android.internal.util.Preconditions;
public class SettableWakeLock {
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
index 16ce35c..b0f1b54 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.os.PowerManager;
+
import androidx.annotation.VisibleForTesting;
/** WakeLock wrapper for testability */
@@ -71,4 +72,4 @@
}
};
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
index 9077b6b..9b616e0 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
@@ -34,8 +34,8 @@
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
import android.graphics.PixelFormat;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.media.AudioAttributes;
import android.media.AudioManager;
@@ -65,17 +65,17 @@
import androidx.car.widget.PagedListView;
import androidx.car.widget.SeekbarListItem;
-import java.util.Iterator;
+import com.android.systemui.R;
+import com.android.systemui.plugins.VolumeDialog;
+
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
-import com.android.systemui.R;
-import com.android.systemui.plugins.VolumeDialog;
-
/**
* Car version of the volume dialog.
*
diff --git a/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java b/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java
index c97095e..361604c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java
@@ -27,7 +27,6 @@
import android.view.KeyEvent;
import android.view.WindowManager;
-
import com.android.systemui.statusbar.phone.SystemUIDialog;
abstract public class SafetyWarningDialog extends SystemUIDialog
@@ -116,4 +115,4 @@
}
}
};
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
index 36c673c..4239337 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
@@ -19,7 +19,6 @@
import android.content.res.Configuration;
import com.android.systemui.DemoMode;
-import com.android.systemui.statusbar.policy.ZenModeController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 798f8bc..b588305 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -16,8 +16,6 @@
package com.android.systemui.volume;
-import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
-import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.media.AudioManager.RINGER_MODE_NORMAL;
import static android.media.AudioManager.RINGER_MODE_SILENT;
@@ -32,7 +30,6 @@
import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
-import android.accessibilityservice.AccessibilityServiceInfo;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
@@ -68,13 +65,12 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.View.AccessibilityDelegate;
-import android.view.View.OnAttachStateChangeListener;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
+import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
@@ -113,6 +109,10 @@
private static final long USER_ATTEMPT_GRACE_PERIOD = 1000;
private static final int UPDATE_ANIMATION_DURATION = 80;
+ static final int DIALOG_TIMEOUT_MILLIS = 3000;
+ static final int DIALOG_SAFETYWARNING_TIMEOUT_MILLIS = 5000;
+ static final int DIALOG_HOVERING_TIMEOUT_MILLIS = 16000;
+
private final Context mContext;
private final H mHandler = new H();
private final VolumeDialogController mController;
@@ -170,7 +170,6 @@
@Override
public void destroy() {
- mAccessibility.destroy();
mController.removeCallback(mControllerCallbackH);
mHandler.removeCallbacksAndMessages(null);
}
@@ -356,8 +355,6 @@
writer.print(" mDynamic: "); writer.println(mDynamic);
writer.print(" mAutomute: "); writer.println(mAutomute);
writer.print(" mSilentMode: "); writer.println(mSilentMode);
- writer.print(" mAccessibility.mFeedbackEnabled: ");
- writer.println(mAccessibility.mFeedbackEnabled);
}
private static int getImpliedLevel(SeekBar seekBar, int progress) {
@@ -571,10 +568,18 @@
}
private int computeTimeoutH() {
- if (mAccessibility.mFeedbackEnabled) return 20000;
- if (mHovering) return 16000;
- if (mSafetyWarning != null) return 5000;
- return 3000;
+ if (mHovering) {
+ return mAccessibilityMgr.getRecommendedTimeoutMillis(DIALOG_HOVERING_TIMEOUT_MILLIS,
+ AccessibilityManager.FLAG_CONTENT_CONTROLS);
+ }
+ if (mSafetyWarning != null) {
+ return mAccessibilityMgr.getRecommendedTimeoutMillis(
+ DIALOG_SAFETYWARNING_TIMEOUT_MILLIS,
+ AccessibilityManager.FLAG_CONTENT_TEXT
+ | AccessibilityManager.FLAG_CONTENT_CONTROLS);
+ }
+ return mAccessibilityMgr.getRecommendedTimeoutMillis(DIALOG_TIMEOUT_MILLIS,
+ AccessibilityManager.FLAG_CONTENT_CONTROLS);
}
protected void dismissH(int reason) {
@@ -1261,28 +1266,8 @@
}
private final class Accessibility extends AccessibilityDelegate {
- private boolean mFeedbackEnabled;
-
public void init() {
- mDialogView.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
- @Override
- public void onViewDetachedFromWindow(View v) {
- if (D.BUG) Log.d(TAG, "onViewDetachedFromWindow");
- }
-
- @Override
- public void onViewAttachedToWindow(View v) {
- if (D.BUG) Log.d(TAG, "onViewAttachedToWindow");
- updateFeedbackEnabled();
- }
- });
mDialogView.setAccessibilityDelegate(this);
- mAccessibilityMgr.addCallback(mListener);
- updateFeedbackEnabled();
- }
-
- public void destroy() {
- mAccessibilityMgr.removeCallback(mListener);
}
@Override
@@ -1298,25 +1283,6 @@
rescheduleTimeoutH();
return super.onRequestSendAccessibilityEvent(host, child, event);
}
-
- private void updateFeedbackEnabled() {
- mFeedbackEnabled = computeFeedbackEnabled();
- }
-
- private boolean computeFeedbackEnabled() {
- // are there any enabled non-generic a11y services?
- final List<AccessibilityServiceInfo> services =
- mAccessibilityMgr.getEnabledAccessibilityServiceList(FEEDBACK_ALL_MASK);
- for (AccessibilityServiceInfo asi : services) {
- if (asi.feedbackType != 0 && asi.feedbackType != FEEDBACK_GENERIC) {
- return true;
- }
- }
- return false;
- }
-
- private final AccessibilityServicesStateChangeListener mListener =
- enabled -> updateFeedbackEnabled();
}
private static class VolumeRow {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 6f65b08..e4f37de 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -23,8 +23,6 @@
import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.qs.tiles.DndTile;
-import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
import java.io.FileDescriptor;
import java.io.PrintWriter;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt
index 7204d31..b23f667 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt
@@ -27,55 +27,28 @@
@SmallTest
class PrivacyDialogBuilderTest : SysuiTestCase() {
- companion object {
- val MILLIS_IN_MINUTE: Long = 1000 * 60
- val NOW = 4 * MILLIS_IN_MINUTE
- }
-
@Test
- fun testGenerateText_multipleApps() {
+ fun testGenerateAppsList() {
val bar2 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication(
- "Bar", context), 2 * MILLIS_IN_MINUTE)
+ "Bar", context))
val bar3 = PrivacyItem(Privacy.TYPE_LOCATION, PrivacyApplication(
- "Bar", context), 3 * MILLIS_IN_MINUTE)
+ "Bar", context))
val foo0 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication(
- "Foo", context), 0)
+ "Foo", context))
val baz1 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication(
- "Baz", context), 1 * MILLIS_IN_MINUTE)
+ "Baz", context))
val items = listOf(bar2, foo0, baz1, bar3)
val textBuilder = PrivacyDialogBuilder(context, items)
- val textList = textBuilder.generateText(NOW)
- assertEquals(2, textList.size)
- assertEquals("Bar, Foo, Baz are using your camera", textList[0])
- assertEquals("Bar is using your location for the last 1 min", textList[1])
- }
-
- @Test
- fun testGenerateText_singleApp() {
- val bar2 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication(
- "Bar", context), 0)
- val bar1 = PrivacyItem(Privacy.TYPE_LOCATION, PrivacyApplication(
- "Bar", context), 0)
-
- val items = listOf(bar2, bar1)
-
- val textBuilder = PrivacyDialogBuilder(context, items)
- val textList = textBuilder.generateText(NOW)
- assertEquals(1, textList.size)
- assertEquals("Bar is using your camera, location", textList[0])
- }
-
- @Test
- fun testGenerateText_singleApp_singleType() {
- val bar2 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication(
- "Bar", context), 2 * MILLIS_IN_MINUTE)
- val items = listOf(bar2)
- val textBuilder = PrivacyDialogBuilder(context, items)
- val textList = textBuilder.generateText(NOW)
- assertEquals(1, textList.size)
- assertEquals("Bar is using your camera for the last 2 min", textList[0])
+ val list = textBuilder.appsAndTypes
+ assertEquals(3, list.size)
+ val appsList = list.map { it.first }
+ val typesList = list.map { it.second }
+ assertEquals(listOf("Bar", "Baz", "Foo"), appsList.map { it.packageName })
+ assertEquals(listOf(Privacy.TYPE_CAMERA, Privacy.TYPE_LOCATION), typesList[0])
+ assertEquals(listOf(Privacy.TYPE_CAMERA), typesList[1])
+ assertEquals(listOf(Privacy.TYPE_CAMERA), typesList[2])
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java
index 8e6bfe3..f59bfae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java
@@ -72,6 +72,8 @@
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -390,10 +392,16 @@
@Test
public void testCreateNotificationDataEntry_RankingUpdate() {
Ranking ranking = mock(Ranking.class);
+ initStatusBarNotification(false);
- ArrayList<Notification.Action> smartActions = new ArrayList<>();
- smartActions.add(createAction());
- when(ranking.getSmartActions()).thenReturn(smartActions);
+ List<Notification.Action> appGeneratedSmartActions =
+ Collections.singletonList(createContextualAction("appGeneratedAction"));
+ mMockStatusBarNotification.getNotification().actions =
+ appGeneratedSmartActions.toArray(new Notification.Action[0]);
+
+ List<Notification.Action> systemGeneratedSmartActions =
+ Collections.singletonList(createAction("systemGeneratedAction"));
+ when(ranking.getSmartActions()).thenReturn(systemGeneratedSmartActions);
when(ranking.getChannel()).thenReturn(NOTIFICATION_CHANNEL);
@@ -407,7 +415,7 @@
NotificationData.Entry entry =
new NotificationData.Entry(mMockStatusBarNotification, ranking);
- assertEquals(smartActions, entry.smartActions);
+ assertEquals(systemGeneratedSmartActions, entry.systemGeneratedSmartActions);
assertEquals(NOTIFICATION_CHANNEL, entry.channel);
assertEquals(Ranking.USER_SENTIMENT_NEGATIVE, entry.userSentiment);
assertEquals(snoozeCriterions, entry.snoozeCriteria);
@@ -459,10 +467,20 @@
}
}
- private Notification.Action createAction() {
+ private Notification.Action createContextualAction(String title) {
return new Notification.Action.Builder(
Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
- "action",
+ title,
+ PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0))
+ .setSemanticAction(
+ Notification.Action.SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION)
+ .build();
+ }
+
+ private Notification.Action createAction(String title) {
+ return new Notification.Action.Builder(
+ Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
+ title,
PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0)).build();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 9f8a5cc..d1fe5af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -438,8 +438,8 @@
mEntryManager.updateNotificationRanking(mRankingMap);
verify(mRow).setEntry(eq(mEntry));
- assertEquals(1, mEntry.smartActions.size());
- assertEquals("action", mEntry.smartActions.get(0).title);
+ assertEquals(1, mEntry.systemGeneratedSmartActions.size());
+ assertEquals("action", mEntry.systemGeneratedSmartActions.get(0).title);
}
@Test
@@ -453,7 +453,7 @@
mEntryManager.updateNotificationRanking(mRankingMap);
verify(mRow, never()).setEntry(eq(mEntry));
- assertEquals(0, mEntry.smartActions.size());
+ assertEquals(0, mEntry.systemGeneratedSmartActions.size());
}
@Test
@@ -467,8 +467,8 @@
mEntryManager.updateNotificationRanking(mRankingMap);
verify(mRow, never()).setEntry(eq(mEntry));
- assertEquals(1, mEntry.smartActions.size());
- assertEquals("action", mEntry.smartActions.get(0).title);
+ assertEquals(1, mEntry.systemGeneratedSmartActions.size());
+ assertEquals("action", mEntry.systemGeneratedSmartActions.get(0).title);
}
@Test
@@ -482,8 +482,8 @@
mEntryManager.updateNotificationRanking(mRankingMap);
verify(mRow, never()).setEntry(eq(mEntry));
- assertEquals(1, mEntry.smartActions.size());
- assertEquals("action", mEntry.smartActions.get(0).title);
+ assertEquals(1, mEntry.systemGeneratedSmartActions.size());
+ assertEquals("action", mEntry.systemGeneratedSmartActions.get(0).title);
}
private Notification.Action createAction() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 626726d..3d2ea70 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -284,7 +284,8 @@
when(row.getIsNonblockable()).thenReturn(false);
StatusBarNotification statusBarNotification = row.getStatusBarNotification();
- mGutsManager.initializeNotificationInfo(row, notificationInfoView);
+ mGutsManager.initializeNotificationInfo(row, notificationInfoView,
+ NotificationInfo.ACTION_NONE);
verify(notificationInfoView).bindNotification(
any(PackageManager.class),
@@ -301,7 +302,8 @@
eq(true) /* isForBlockingHelper */,
eq(true) /* isUserSentimentNegative */,
eq(false) /*isNoisy */,
- eq(0));
+ eq(0),
+ eq(NotificationInfo.ACTION_NONE));
}
@Test
@@ -313,7 +315,8 @@
when(row.getIsNonblockable()).thenReturn(false);
StatusBarNotification statusBarNotification = row.getStatusBarNotification();
- mGutsManager.initializeNotificationInfo(row, notificationInfoView);
+ mGutsManager.initializeNotificationInfo(row, notificationInfoView,
+ NotificationInfo.ACTION_NONE);
verify(notificationInfoView).bindNotification(
any(PackageManager.class),
@@ -330,7 +333,8 @@
eq(false) /* isForBlockingHelper */,
eq(true) /* isUserSentimentNegative */,
eq(false) /*isNoisy */,
- eq(0));
+ eq(0),
+ eq(NotificationInfo.ACTION_NONE));
}
@Test
@@ -343,7 +347,8 @@
when(row.getIsNonblockable()).thenReturn(false);
StatusBarNotification statusBarNotification = row.getStatusBarNotification();
- mGutsManager.initializeNotificationInfo(row, notificationInfoView);
+ mGutsManager.initializeNotificationInfo(row, notificationInfoView,
+ NotificationInfo.ACTION_NONE);
verify(notificationInfoView).bindNotification(
any(PackageManager.class),
@@ -360,7 +365,8 @@
eq(true) /* isForBlockingHelper */,
eq(true) /* isUserSentimentNegative */,
eq(true) /*isNoisy */,
- eq(0));
+ eq(0),
+ eq(NotificationInfo.ACTION_NONE));
}
@Test
@@ -373,7 +379,8 @@
when(row.getIsNonblockable()).thenReturn(false);
StatusBarNotification statusBarNotification = row.getStatusBarNotification();
- mGutsManager.initializeNotificationInfo(row, notificationInfoView);
+ mGutsManager.initializeNotificationInfo(row, notificationInfoView,
+ NotificationInfo.ACTION_NONE);
verify(notificationInfoView).bindNotification(
any(PackageManager.class),
@@ -390,7 +397,8 @@
eq(true) /* isForBlockingHelper */,
eq(true) /* isUserSentimentNegative */,
eq(false) /*isNoisy */,
- eq(IMPORTANCE_DEFAULT));
+ eq(IMPORTANCE_DEFAULT),
+ eq(NotificationInfo.ACTION_NONE));
}
@Test
@@ -403,7 +411,8 @@
StatusBarNotification statusBarNotification = row.getStatusBarNotification();
when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
- mGutsManager.initializeNotificationInfo(row, notificationInfoView);
+ mGutsManager.initializeNotificationInfo(row, notificationInfoView,
+ NotificationInfo.ACTION_NONE);
verify(notificationInfoView).bindNotification(
any(PackageManager.class),
@@ -420,7 +429,39 @@
eq(false) /* isForBlockingHelper */,
eq(true) /* isUserSentimentNegative */,
eq(false) /*isNoisy */,
- eq(0));
+ eq(0),
+ eq(NotificationInfo.ACTION_NONE));
+ }
+
+ @Test
+ public void testInitializeNotificationInfoView_withInitialAction() throws Exception {
+ NotificationInfo notificationInfoView = mock(NotificationInfo.class);
+ ExpandableNotificationRow row = spy(mHelper.createRow());
+ row.setBlockingHelperShowing(true);
+ row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+ when(row.getIsNonblockable()).thenReturn(false);
+ StatusBarNotification statusBarNotification = row.getStatusBarNotification();
+
+ mGutsManager.initializeNotificationInfo(row, notificationInfoView,
+ NotificationInfo.ACTION_BLOCK);
+
+ verify(notificationInfoView).bindNotification(
+ any(PackageManager.class),
+ any(INotificationManager.class),
+ eq(statusBarNotification.getPackageName()),
+ any(NotificationChannel.class),
+ anyInt(),
+ eq(statusBarNotification),
+ any(NotificationInfo.CheckSaveListener.class),
+ any(NotificationInfo.OnSettingsClickListener.class),
+ any(NotificationInfo.OnAppSettingsClickListener.class),
+ eq(false),
+ eq(false),
+ eq(true) /* isForBlockingHelper */,
+ eq(true) /* isUserSentimentNegative */,
+ eq(false) /*isNoisy */,
+ eq(0),
+ eq(NotificationInfo.ACTION_BLOCK));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index 3744196..1cc1c63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -187,7 +187,7 @@
when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final TextView textView = mNotificationInfo.findViewById(R.id.pkgname);
assertTrue(textView.getText().toString().contains("App Name"));
assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
@@ -200,7 +200,7 @@
.thenReturn(iconDrawable);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final ImageView iconView = mNotificationInfo.findViewById(R.id.pkgicon);
assertEquals(iconDrawable, iconView.getDrawable());
}
@@ -209,7 +209,7 @@
public void testBindNotification_GroupNameHiddenIfNoGroup() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name);
assertEquals(GONE, groupNameView.getVisibility());
final TextView groupDividerView = mNotificationInfo.findViewById(R.id.pkg_group_divider);
@@ -226,7 +226,7 @@
.thenReturn(notificationChannelGroup);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name);
assertEquals(View.VISIBLE, groupNameView.getVisibility());
assertEquals("Test Group Name", groupNameView.getText());
@@ -238,7 +238,7 @@
public void testBindNotification_SetsTextChannelName() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
assertEquals(TEST_CHANNEL_NAME, textView.getText());
}
@@ -247,7 +247,7 @@
public void testBindNotification_DefaultChannelDoesNotUseChannelName() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mDefaultNotificationChannel, 1, mSbn, null, null, null, true,
- false, false, IMPORTANCE_DEFAULT);
+ false, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
assertEquals(GONE, textView.getVisibility());
}
@@ -260,7 +260,7 @@
eq(TEST_PACKAGE_NAME), eq(TEST_UID), anyBoolean())).thenReturn(10);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mDefaultNotificationChannel, 1, mSbn, null, null, null, true,
- false, false, IMPORTANCE_DEFAULT);
+ false, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
assertEquals(VISIBLE, textView.getVisibility());
}
@@ -269,7 +269,7 @@
public void testBindNotification_UnblockablePackageUsesChannelName() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
assertEquals(VISIBLE, textView.getVisibility());
}
@@ -278,7 +278,7 @@
public void testBindNotification_BlockButton() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final View block = mNotificationInfo.findViewById(R.id.block);
final View toggleSilent = mNotificationInfo.findViewById(R.id.toggle_silent);
final View minimize = mNotificationInfo.findViewById(R.id.minimize);
@@ -292,7 +292,7 @@
mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- true, IMPORTANCE_DEFAULT);
+ true, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final TextView toggleSilent = mNotificationInfo.findViewById(R.id.toggle_silent);
assertEquals(VISIBLE, toggleSilent.getVisibility());
assertEquals(
@@ -304,7 +304,7 @@
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- true, IMPORTANCE_LOW);
+ true, IMPORTANCE_LOW, NotificationInfo.ACTION_NONE);
final TextView toggleSilent = mNotificationInfo.findViewById(R.id.toggle_silent);
assertEquals(VISIBLE, toggleSilent.getVisibility());
assertEquals(
@@ -316,7 +316,7 @@
mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- true, IMPORTANCE_DEFAULT);
+ true, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final TextView toggleSilent = mNotificationInfo.findViewById(R.id.toggle_silent);
assertEquals(VISIBLE, toggleSilent.getVisibility());
assertEquals(
@@ -329,7 +329,7 @@
mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- true, IMPORTANCE_LOW);
+ true, IMPORTANCE_LOW, NotificationInfo.ACTION_NONE);
final TextView toggleSilent = mNotificationInfo.findViewById(R.id.toggle_silent);
assertEquals(VISIBLE, toggleSilent.getVisibility());
assertEquals(
@@ -341,7 +341,7 @@
mSbn.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final View block = mNotificationInfo.findViewById(R.id.block);
final View minimize = mNotificationInfo.findViewById(R.id.minimize);
assertEquals(GONE, block.getVisibility());
@@ -356,7 +356,7 @@
(View v, NotificationChannel c, int appUid) -> {
assertEquals(mNotificationChannel, c);
latch.countDown();
- }, null, true, false, false, IMPORTANCE_DEFAULT);
+ }, null, true, false, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
settingsButton.performClick();
@@ -368,7 +368,7 @@
public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
assertTrue(settingsButton.getVisibility() != View.VISIBLE);
}
@@ -380,7 +380,7 @@
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null,
(View v, NotificationChannel c, int appUid) -> {
assertEquals(mNotificationChannel, c);
- }, null, false, false, false, IMPORTANCE_DEFAULT);
+ }, null, false, false, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
assertTrue(settingsButton.getVisibility() != View.VISIBLE);
}
@@ -389,11 +389,11 @@
public void testBindNotification_SettingsButtonReappearsAfterSecondBind() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null,
(View v, NotificationChannel c, int appUid) -> {
- }, null, true, false, false, IMPORTANCE_DEFAULT);
+ }, null, true, false, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
assertEquals(View.VISIBLE, settingsButton.getVisibility());
}
@@ -402,7 +402,7 @@
public void testLogBlockingHelperCounter_doesntLogForNormalGutsView() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.logBlockingHelperCounter("HowCanNotifsBeRealIfAppsArent");
verify(mMetricsLogger, times(0)).count(anyString(), anyInt());
}
@@ -411,7 +411,7 @@
public void testLogBlockingHelperCounter_logsForBlockingHelper() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false, true,
- true, true, false, IMPORTANCE_DEFAULT);
+ true, true, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.logBlockingHelperCounter("HowCanNotifsBeRealIfAppsArent");
verify(mMetricsLogger, times(1)).count(anyString(), anyInt());
}
@@ -424,7 +424,7 @@
(View v, NotificationChannel c, int appUid) -> {
assertEquals(null, c);
latch.countDown();
- }, null, true, true, false, IMPORTANCE_DEFAULT);
+ }, null, true, true, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.info).performClick();
// Verify that listener was triggered.
@@ -437,7 +437,7 @@
throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, mSbn, null, null,
- null, true, true, false, IMPORTANCE_DEFAULT);
+ null, true, true, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final TextView channelNameView =
mNotificationInfo.findViewById(R.id.channel_name);
assertEquals(GONE, channelNameView.getVisibility());
@@ -448,7 +448,7 @@
public void testStopInvisibleIfBundleFromDifferentChannels() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, mSbn, null, null,
- null, true, true, false, IMPORTANCE_DEFAULT);
+ null, true, true, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final TextView blockView = mNotificationInfo.findViewById(R.id.block);
assertEquals(GONE, blockView.getVisibility());
}
@@ -457,7 +457,7 @@
public void testbindNotification_BlockingHelper() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false, false,
- true, true, false, IMPORTANCE_DEFAULT);
+ true, true, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final TextView view = mNotificationInfo.findViewById(R.id.block_prompt);
assertEquals(View.VISIBLE, view.getVisibility());
assertEquals(mContext.getString(R.string.inline_blocking_helper), view.getText());
@@ -467,7 +467,7 @@
public void testbindNotification_UnblockableTextVisibleWhenAppUnblockable() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final TextView view = mNotificationInfo.findViewById(R.id.block_prompt);
assertEquals(View.VISIBLE, view.getVisibility());
assertEquals(mContext.getString(R.string.notification_unblockable_desc),
@@ -478,7 +478,7 @@
public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mTestableLooper.processAllMessages();
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), eq(TEST_UID), any());
@@ -489,7 +489,7 @@
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.block).performClick();
mTestableLooper.processAllMessages();
@@ -503,7 +503,7 @@
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.minimize).performClick();
mTestableLooper.processAllMessages();
@@ -517,7 +517,7 @@
mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- true, IMPORTANCE_DEFAULT);
+ true, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.toggle_silent).performClick();
mTestableLooper.processAllMessages();
@@ -531,7 +531,7 @@
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- true, IMPORTANCE_DEFAULT);
+ true, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.toggle_silent).performClick();
mTestableLooper.processAllMessages();
@@ -545,7 +545,7 @@
int originalImportance = mNotificationChannel.getImportance();
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.handleCloseControls(true, false);
mTestableLooper.processAllMessages();
@@ -560,7 +560,7 @@
mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.handleCloseControls(true, false);
@@ -578,7 +578,8 @@
TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */,
10 /* numUniqueChannelsInRow */, mSbn, null /* checkSaveListener */,
null /* onSettingsClick */, null /* onAppSettingsClick */ ,
- true, false /* isNonblockable */, false, /* isNoisy */IMPORTANCE_DEFAULT);
+ true, false /* isNonblockable */, false /* isNoisy */, IMPORTANCE_DEFAULT,
+ NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.block).performClick();
waitForUndoButton();
@@ -598,8 +599,9 @@
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */,
10 /* numUniqueChannelsInRow */, mSbn, null /* checkSaveListener */,
- null /* onSettingsClick */, null /* onAppSettingsClick */ ,
- true, false /* isNonblockable */, false, /* isNoisy */IMPORTANCE_DEFAULT);
+ null /* onSettingsClick */, null /* onAppSettingsClick */,
+ true, false /* isNonblockable */, false /* isNoisy */, IMPORTANCE_DEFAULT,
+ NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.block).performClick();
waitForUndoButton();
@@ -620,7 +622,8 @@
null /* onSettingsClick */, null /* onAppSettingsClick */ ,
true /* provisioned */,
false /* isNonblockable */, true /* isForBlockingHelper */,
- true /* isUserSentimentNegative */, false, /* isNoisy */IMPORTANCE_DEFAULT);
+ true /* isUserSentimentNegative */, false /* isNoisy */, IMPORTANCE_DEFAULT,
+ NotificationInfo.ACTION_NONE);
NotificationGuts guts = spy(new NotificationGuts(mContext, null));
when(guts.getWindowToken()).thenReturn(mock(IBinder.class));
@@ -648,7 +651,8 @@
10 /* numUniqueChannelsInRow */, mSbn, listener /* checkSaveListener */,
null /* onSettingsClick */, null /* onAppSettingsClick */ , true /* provisioned */,
false /* isNonblockable */, true /* isForBlockingHelper */,
- true /* isUserSentimentNegative */, false, /* isNoisy */IMPORTANCE_DEFAULT);
+ true /* isUserSentimentNegative */, false /* isNoisy */, IMPORTANCE_DEFAULT,
+ NotificationInfo.ACTION_NONE);
NotificationGuts guts = spy(new NotificationGuts(mContext, null));
when(guts.getWindowToken()).thenReturn(mock(IBinder.class));
@@ -676,8 +680,8 @@
10 /* numUniqueChannelsInRow */, mSbn, listener /* checkSaveListener */,
null /* onSettingsClick */, null /* onAppSettingsClick */ ,
false /* isNonblockable */, true /* isForBlockingHelper */,
- true, true /* isUserSentimentNegative */, false, /* isNoisy */
- IMPORTANCE_DEFAULT);
+ true, true /* isUserSentimentNegative */, false /* isNoisy */,
+ IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.handleCloseControls(true /* save */, false /* force */);
@@ -696,7 +700,8 @@
null /* onSettingsClick */, null /* onAppSettingsClick */,
true /* provisioned */,
false /* isNonblockable */, true /* isForBlockingHelper */,
- true /* isUserSentimentNegative */, false, /* isNoisy */IMPORTANCE_DEFAULT);
+ true /* isUserSentimentNegative */, false /* isNoisy */, IMPORTANCE_DEFAULT,
+ NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.block).performClick();
mTestableLooper.processAllMessages();
@@ -719,7 +724,7 @@
true /* isForBlockingHelper */,
true,
false /* isUserSentimentNegative */,
- false, /* isNoisy */IMPORTANCE_DEFAULT);
+ false /* isNoisy */, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
NotificationGuts guts = mock(NotificationGuts.class);
doCallRealMethod().when(guts).closeControls(anyInt(), anyInt(), anyBoolean(), anyBoolean());
mNotificationInfo.setGutsParent(guts);
@@ -734,7 +739,7 @@
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.block).performClick();
waitForUndoButton();
@@ -748,7 +753,7 @@
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.block).performClick();
waitForUndoButton();
@@ -781,7 +786,7 @@
false /* isNonblockable */,
true /* isForBlockingHelper */,
true /* isUserSentimentNegative */,
- false, /* isNoisy */IMPORTANCE_DEFAULT);
+ false/* isNoisy */, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.block).performClick();
waitForUndoButton();
@@ -803,7 +808,7 @@
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.minimize).performClick();
waitForUndoButton();
@@ -818,7 +823,7 @@
mSbn.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.minimize).performClick();
waitForUndoButton();
@@ -839,7 +844,7 @@
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.handleCloseControls(true, false);
@@ -857,7 +862,7 @@
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.block).performClick();
waitForUndoButton();
@@ -879,7 +884,7 @@
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.minimize).performClick();
waitForUndoButton();
@@ -901,7 +906,7 @@
mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- true, IMPORTANCE_DEFAULT);
+ true, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.toggle_silent).performClick();
waitForUndoButton();
@@ -922,7 +927,7 @@
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.toggle_silent).performClick();
waitForUndoButton();
@@ -944,7 +949,7 @@
mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- true, IMPORTANCE_DEFAULT);
+ true, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.toggle_silent).performClick();
waitForUndoButton();
@@ -966,7 +971,7 @@
mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- false, IMPORTANCE_LOW);
+ false, IMPORTANCE_LOW, NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.toggle_silent).performClick();
waitForUndoButton();
@@ -987,7 +992,7 @@
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.minimize).performClick();
waitForUndoButton();
@@ -1003,7 +1008,7 @@
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.block).performClick();
waitForUndoButton();
@@ -1020,7 +1025,7 @@
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn,
(Runnable saveImportance, StatusBarNotification sbn) -> {
- }, null, null, true, true, false, IMPORTANCE_DEFAULT);
+ }, null, null, true, true, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.block).performClick();
mTestableLooper.processAllMessages();
@@ -1038,7 +1043,8 @@
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn,
(Runnable saveImportance, StatusBarNotification sbn) -> {
saveImportance.run();
- }, null, null, true, false, false, IMPORTANCE_DEFAULT);
+ }, null, null, true, false, false, IMPORTANCE_DEFAULT,
+ NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.block).performClick();
mTestableLooper.processAllMessages();
@@ -1074,7 +1080,7 @@
TEST_PACKAGE_NAME, mNotificationChannel, 1, sbn, null, null,
(View v, Intent intent) -> {
latch.countDown();
- }, true, false, false, IMPORTANCE_DEFAULT);
+ }, true, false, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings);
assertEquals(View.VISIBLE, settingsLink.getVisibility());
settingsLink.performClick();
@@ -1102,7 +1108,7 @@
TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, sbn, null, null,
(View v, Intent intent) -> {
latch.countDown();
- }, true, false, false, IMPORTANCE_DEFAULT);
+ }, true, false, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings);
assertEquals(View.VISIBLE, settingsLink.getVisibility());
settingsLink.performClick();
@@ -1121,7 +1127,7 @@
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, sbn, null, null,
- null, true, false, false, IMPORTANCE_DEFAULT);
+ null, true, false, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings);
assertEquals(GONE, settingsLink.getVisibility());
}
@@ -1142,7 +1148,7 @@
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, sbn, null, null, null, true, false,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings);
assertEquals(GONE, settingsLink.getVisibility());
}
@@ -1165,7 +1171,7 @@
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, sbn, null, null, null, false, true,
- true, true, false, IMPORTANCE_DEFAULT);
+ true, true, false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings);
assertEquals(GONE, settingsLink.getVisibility());
}
@@ -1182,7 +1188,7 @@
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.minimize).performClick();
waitForUndoButton();
@@ -1195,7 +1201,7 @@
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.block).performClick();
waitForUndoButton();
@@ -1208,7 +1214,7 @@
mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
- true, IMPORTANCE_DEFAULT);
+ true, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.toggle_silent).performClick();
waitForUndoButton();
@@ -1222,7 +1228,7 @@
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
- true, IMPORTANCE_DEFAULT);
+ true, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.toggle_silent).performClick();
waitForUndoButton();
@@ -1236,7 +1242,7 @@
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.block).performClick();
waitForUndoButton();
@@ -1248,7 +1254,7 @@
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
mNotificationInfo.findViewById(R.id.block).performClick();
waitForUndoButton();
@@ -1256,4 +1262,60 @@
waitForStopButton();
assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
}
+
+ @Test
+ public void testBindNotificationWithInitialBlockAction() throws Exception {
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
+ false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_BLOCK);
+ waitForUndoButton();
+ mNotificationInfo.handleCloseControls(true, false);
+
+ mTestableLooper.processAllMessages();
+ ArgumentCaptor<NotificationChannel> updated =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), eq(TEST_UID), updated.capture());
+ assertTrue((updated.getValue().getUserLockedFields()
+ & USER_LOCKED_IMPORTANCE) != 0);
+ assertEquals(IMPORTANCE_NONE, updated.getValue().getImportance());
+ }
+
+ @Test
+ public void testBindNotificationWithInitialSilenceAction() throws Exception {
+ mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
+ true, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_TOGGLE_SILENT);
+ waitForUndoButton();
+ mNotificationInfo.handleCloseControls(true, false);
+
+ mTestableLooper.processAllMessages();
+ ArgumentCaptor<NotificationChannel> updated =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), eq(TEST_UID), updated.capture());
+ assertTrue((updated.getValue().getUserLockedFields()
+ & USER_LOCKED_IMPORTANCE) != 0);
+ assertEquals(IMPORTANCE_LOW, updated.getValue().getImportance());
+ }
+
+ @Test
+ public void testBindNotificationWithInitialUnSilenceAction() throws Exception {
+ mNotificationChannel.setImportance(IMPORTANCE_LOW);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
+ true, IMPORTANCE_LOW, NotificationInfo.ACTION_TOGGLE_SILENT);
+ waitForUndoButton();
+ mNotificationInfo.handleCloseControls(true, false);
+
+ mTestableLooper.processAllMessages();
+ ArgumentCaptor<NotificationChannel> updated =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), eq(TEST_UID), updated.capture());
+ assertTrue((updated.getValue().getUserLockedFields()
+ & USER_LOCKED_IMPORTANCE) != 0);
+ assertEquals(IMPORTANCE_HIGH, updated.getValue().getImportance());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index dcd5946..db39373 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -32,8 +32,8 @@
import android.os.Handler;
import android.service.notification.StatusBarNotification;
import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.testing.UiThreadTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.view.MotionEvent;
import android.view.View;
@@ -44,7 +44,6 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -56,9 +55,8 @@
* Tests for {@link NotificationSwipeHelper}.
*/
@SmallTest
-@Ignore
-@RunWith(AndroidJUnit4.class)
-@UiThreadTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper()
public class NotificationSwipeHelperTest extends SysuiTestCase {
private NotificationSwipeHelper mSwipeHelper;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 23365a4..1481aef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -19,22 +19,22 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.hardware.display.ColorDisplayManager;
import android.os.Handler;
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
+
import com.android.internal.app.ColorDisplayController;
-import com.android.systemui.Dependency;
-import com.android.systemui.Prefs;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.qs.AutoAddTracker;
import com.android.systemui.qs.QSTileHost;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@@ -56,7 +56,7 @@
@Test
public void nightTileAdded_whenActivated() {
- if (!ColorDisplayController.isAvailable(mContext)) {
+ if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
return;
}
mAutoTileManager.mColorDisplayCallback.onActivated(true);
@@ -65,7 +65,7 @@
@Test
public void nightTileNotAdded_whenDeactivated() {
- if (!ColorDisplayController.isAvailable(mContext)) {
+ if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
return;
}
mAutoTileManager.mColorDisplayCallback.onActivated(false);
@@ -74,7 +74,7 @@
@Test
public void nightTileAdded_whenNightModeTwilight() {
- if (!ColorDisplayController.isAvailable(mContext)) {
+ if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
return;
}
mAutoTileManager.mColorDisplayCallback.onAutoModeChanged(
@@ -84,7 +84,7 @@
@Test
public void nightTileAdded_whenNightModeCustom() {
- if (!ColorDisplayController.isAvailable(mContext)) {
+ if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
return;
}
mAutoTileManager.mColorDisplayCallback.onAutoModeChanged(
@@ -94,7 +94,7 @@
@Test
public void nightTileNotAdded_whenNightModeDisabled() {
- if (!ColorDisplayController.isAvailable(mContext)) {
+ if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
return;
}
mAutoTileManager.mColorDisplayCallback.onAutoModeChanged(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 9c55874..0bc304e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -263,6 +263,19 @@
}
@Test
+ public void transitionToBubbleExpanded() {
+ mScrimController.transitionTo(ScrimState.BUBBLE_EXPANDED);
+ mScrimController.finishAnimationsImmediately();
+
+ // Front scrim should be transparent
+ Assert.assertEquals(ScrimController.VISIBILITY_FULLY_TRANSPARENT,
+ mScrimInFront.getViewAlpha(), 0.0f);
+ // Back scrim should be visible
+ Assert.assertEquals(ScrimController.GRADIENT_SCRIM_ALPHA_BUSY,
+ mScrimBehind.getViewAlpha(), 0.0f);
+ }
+
+ @Test
public void scrimStateCallback() {
mScrimController.transitionTo(ScrimState.UNLOCKED);
mScrimController.finishAnimationsImmediately();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 882f261..7dc858f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -70,6 +70,7 @@
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.appops.AppOpsControllerImpl;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -138,6 +139,7 @@
@Mock private DeviceProvisionedController mDeviceProvisionedController;
@Mock private NotificationPresenter mNotificationPresenter;
@Mock private NotificationEntryManager.Callback mCallback;
+ @Mock private BubbleController mBubbleController;
private TestableStatusBar mStatusBar;
private FakeMetricsLogger mMetricsLogger;
@@ -162,6 +164,7 @@
mDependency.injectTestDependency(StatusBarStateController.class, mStatusBarStateController);
mDependency.injectTestDependency(DeviceProvisionedController.class,
mDeviceProvisionedController);
+ mDependency.injectMockDependency(BubbleController.class);
mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class));
@@ -210,7 +213,7 @@
mock(NotificationIconAreaController.class), mock(DozeScrimController.class),
mock(NotificationShelf.class), mLockscreenUserManager,
mCommandQueue,
- mNotificationPresenter);
+ mNotificationPresenter, mock(BubbleController.class));
mStatusBar.mContext = mContext;
mStatusBar.mComponents = mContext.getComponents();
mStatusBar.putComponent(StatusBar.class, mStatusBar);
@@ -593,6 +596,30 @@
verify(mStatusBarStateController).setState(eq(StatusBarState.FULLSCREEN_USER_SWITCHER));
}
+ @Test
+ public void testStartStopDozing() {
+ mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
+ when(mStatusBarStateController.isKeyguardRequested()).thenReturn(true);
+
+ mStatusBar.mDozeServiceHost.startDozing();
+ verify(mStatusBarStateController).setIsDozing(eq(true));
+
+ mStatusBar.mDozeServiceHost.stopDozing();
+ verify(mStatusBarStateController).setIsDozing(eq(false));
+ }
+
+ @Test
+ public void testOnStartedWakingUp_isNotDozing() {
+ mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
+ when(mStatusBarStateController.isKeyguardRequested()).thenReturn(true);
+
+ mStatusBar.mDozeServiceHost.startDozing();
+ verify(mStatusBarStateController).setIsDozing(eq(true));
+
+ mStatusBar.mWakefulnessObserver.onStartedWakingUp();
+ verify(mStatusBarStateController).setIsDozing(eq(false));
+ }
+
static class TestableStatusBar extends StatusBar {
public TestableStatusBar(StatusBarKeyguardViewManager man,
UnlockMethodCache unlock, KeyguardIndicationController key,
@@ -614,7 +641,8 @@
NotificationShelf notificationShelf,
NotificationLockscreenUserManager notificationLockscreenUserManager,
CommandQueue commandQueue,
- NotificationPresenter notificationPresenter) {
+ NotificationPresenter notificationPresenter,
+ BubbleController bubbleController) {
mStatusBarKeyguardViewManager = man;
mUnlockMethodCache = unlock;
mKeyguardIndicationController = key;
@@ -642,6 +670,8 @@
mLockscreenUserManager = notificationLockscreenUserManager;
mCommandQueue = commandQueue;
mPresenter = notificationPresenter;
+ mGestureWakeLock = mock(PowerManager.WakeLock.class);
+ mBubbleController = bubbleController;
}
private WakefulnessLifecycle createAwakeWakefulnessLifecycle() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
index de26c70..98d0c6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
@@ -28,7 +28,7 @@
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
-import android.view.View;
+import android.view.ViewGroup;
import android.view.WindowManager;
import com.android.systemui.SysuiTestCase;
@@ -37,7 +37,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -51,7 +50,7 @@
@Mock
private DozeParameters mDozeParameters;
@Mock
- private View mStatusBarView;
+ private ViewGroup mStatusBarView;
@Mock
private IActivityManager mActivityManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index c536dca..5f02fad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -27,18 +27,23 @@
import static junit.framework.Assert.assertTrue;
+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.KeyguardManager;
import android.media.AudioManager;
+import android.os.SystemClock;
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.text.TextUtils;
+import android.view.InputDevice;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
import android.widget.ImageView;
import com.android.systemui.R;
@@ -48,10 +53,11 @@
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.function.Predicate;
@@ -59,7 +65,6 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-@Ignore
public class VolumeDialogImplTest extends SysuiTestCase {
VolumeDialogImpl mDialog;
@@ -113,6 +118,45 @@
+ " failed test", condition.test(view));
}
}
+
+ @Test
+ public void testComputeTimeout() {
+ Mockito.reset(mAccessibilityMgr);
+ mDialog.rescheduleTimeoutH();
+ verify(mAccessibilityMgr).getRecommendedTimeoutMillis(
+ VolumeDialogImpl.DIALOG_TIMEOUT_MILLIS,
+ AccessibilityManager.FLAG_CONTENT_CONTROLS);
+ }
+
+ @Test
+ public void testComputeTimeout_withHovering() {
+ Mockito.reset(mAccessibilityMgr);
+ View dialog = mDialog.getDialogView();
+ long uptimeMillis = SystemClock.uptimeMillis();
+ MotionEvent event = MotionEvent.obtain(uptimeMillis, uptimeMillis,
+ MotionEvent.ACTION_HOVER_ENTER, 0, 0, 0);
+ event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+ dialog.dispatchGenericMotionEvent(event);
+ event.recycle();
+ verify(mAccessibilityMgr).getRecommendedTimeoutMillis(
+ VolumeDialogImpl.DIALOG_HOVERING_TIMEOUT_MILLIS,
+ AccessibilityManager.FLAG_CONTENT_CONTROLS);
+ }
+
+ @Test
+ public void testComputeTimeout_withSafetyWarningOn() {
+ Mockito.reset(mAccessibilityMgr);
+ ArgumentCaptor<VolumeDialogController.Callbacks> controllerCallbackCapture =
+ ArgumentCaptor.forClass(VolumeDialogController.Callbacks.class);
+ verify(mController).addCallback(controllerCallbackCapture.capture(), any());
+ VolumeDialogController.Callbacks callbacks = controllerCallbackCapture.getValue();
+ callbacks.onShowSafetyWarning(AudioManager.FLAG_SHOW_UI);
+ verify(mAccessibilityMgr).getRecommendedTimeoutMillis(
+ VolumeDialogImpl.DIALOG_SAFETYWARNING_TIMEOUT_MILLIS,
+ AccessibilityManager.FLAG_CONTENT_TEXT
+ | AccessibilityManager.FLAG_CONTENT_CONTROLS);
+ }
+
/*
@Test
public void testContentDescriptions() {
diff --git a/packages/services/PacProcessor/Android.mk b/packages/services/PacProcessor/Android.mk
index 75d0363..295b3d8 100644
--- a/packages/services/PacProcessor/Android.mk
+++ b/packages/services/PacProcessor/Android.mk
@@ -29,5 +29,3 @@
LOCAL_JNI_SHARED_LIBRARIES := libjni_pacprocessor libpac
include $(BUILD_PACKAGE)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/packages/services/PacProcessor/jni/Android.bp b/packages/services/PacProcessor/jni/Android.bp
new file mode 100644
index 0000000..2a94237
--- /dev/null
+++ b/packages/services/PacProcessor/jni/Android.bp
@@ -0,0 +1,40 @@
+//
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_library_shared {
+ name: "libjni_pacprocessor",
+
+ srcs: [
+ "jni_init.cpp",
+ "com_android_pacprocessor_PacNative.cpp",
+ ],
+
+ shared_libs: [
+ "libandroidfw",
+ "libandroid_runtime",
+ "liblog",
+ "libutils",
+ "libnativehelper",
+ "libpac",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wunused",
+ "-Wunreachable-code",
+ ],
+}
diff --git a/packages/services/PacProcessor/jni/Android.mk b/packages/services/PacProcessor/jni/Android.mk
deleted file mode 100644
index 254cbc2..0000000
--- a/packages/services/PacProcessor/jni/Android.mk
+++ /dev/null
@@ -1,41 +0,0 @@
-#
-# Copyright (C) 2013 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
- jni_init.cpp \
- com_android_pacprocessor_PacNative.cpp
-
-LOCAL_C_INCLUDES += \
- external/chromium-libpac/src
-
-LOCAL_SHARED_LIBRARIES := \
- libandroidfw \
- libandroid_runtime \
- liblog \
- libutils \
- libnativehelper \
- libpac
-
-LOCAL_MODULE := libjni_pacprocessor
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index 8ae5872..3e07d12 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -844,6 +844,8 @@
// PACKAGE: App that posted the notification
// DETAIL: Notification is expanded by user.
// PACKAGE: App that posted the notification
+ // COLLAPSE: Notification is collapsed by user.
+ // PACKAGE: App that posted the notification
// DISMISS: Notification is dismissed.
// PACKAGE: App that posted the notification
// SUBTYPE: Dismiss reason from NotificationManagerService.java
@@ -6596,6 +6598,12 @@
// OS: Q
NOTIFICATION_ZEN_MODE_OVERRIDING_APP = 1589;
+ // ACTION: User sent a direct reply
+ // PACKAGE: App that posted the notification
+ // CATEGORY: NOTIFICATION
+ // OS: Q
+ NOTIFICATION_DIRECT_REPLY_ACTION = 1590;
+
// ---- End Q Constants, all Q constants go above this line ----
// Add new aosp constants above this line.
diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto
index 033e996..11963d2 100644
--- a/proto/src/wifi.proto
+++ b/proto/src/wifi.proto
@@ -1674,4 +1674,20 @@
// Total time the wifi radio is scanning in ms over the logging duration.
optional int64 radio_scan_time_ms = 5;
+
+ // Total time the wifi radio spent doing nan scans in ms over the logging duration.
+ optional int64 radio_nan_scan_time_ms = 6;
+
+ // Total time the wifi radio spent doing background scans in ms over the logging duration.
+ optional int64 radio_background_scan_time_ms = 7;
+
+ // Total time the wifi radio spent doing roam scans in ms over the logging duration.
+ optional int64 radio_roam_scan_time_ms = 8;
+
+ // Total time the wifi radio spent doing pno scans in ms over the logging duration.
+ optional int64 radio_pno_scan_time_ms = 9;
+
+ // Total time the wifi radio spent doing hotspot 2.0 scans and GAS exchange
+ // in ms over the logging duration.
+ optional int64 radio_hs20_scan_time_ms = 10;
}
\ No newline at end of file
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 4205ac7..17d8ea7 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -89,7 +89,7 @@
* {@link AutofillManagerServiceImpl} itself.
*/
public final class AutofillManagerService
- extends AbstractMasterSystemService<AutofillManagerServiceImpl> {
+ extends AbstractMasterSystemService<AutofillManagerService, AutofillManagerServiceImpl> {
private static final String TAG = "AutofillManagerService";
@@ -166,12 +166,12 @@
context.registerReceiver(mBroadcastReceiver, filter, null, FgThread.getHandler());
}
- @Override // from MasterSystemService
+ @Override // from AbstractMasterSystemService
protected String getServiceSettingsProperty() {
return Settings.Secure.AUTOFILL_SERVICE;
}
- @Override // from MasterSystemService
+ @Override // from AbstractMasterSystemService
protected void registerForExtraSettingsChanges(@NonNull ContentResolver resolver,
@NonNull ContentObserver observer) {
resolver.registerContentObserver(Settings.Global.getUriFor(
@@ -188,7 +188,7 @@
UserHandle.USER_ALL);
}
- @Override // from MasterSystemService
+ @Override // from AbstractMasterSystemService
protected void onSettingsChanged(int userId, @NonNull String property) {
switch (property) {
case Settings.Global.AUTOFILL_LOGGING_LEVEL:
@@ -210,25 +210,24 @@
}
}
- @Override // from MasterSystemService
- protected AutofillManagerServiceImpl newServiceLocked(int resolvedUserId, boolean disabled) {
+ @Override // from AbstractMasterSystemService
+ protected AutofillManagerServiceImpl newServiceLocked(@UserIdInt int resolvedUserId,
+ boolean disabled) {
return new AutofillManagerServiceImpl(this, mLock, mRequestsHistory,
mUiLatencyHistory, mWtfHistory, resolvedUserId, mUi, mAutofillCompatState,
disabled);
}
- @Override // MasterSystemService
- protected AutofillManagerServiceImpl removeCachedServiceLocked(int userId) {
- final AutofillManagerServiceImpl service = super.removeCachedServiceLocked(userId);
- if (service != null) {
- service.destroyLocked();
- mAutofillCompatState.removeCompatibilityModeRequests(userId);
- }
- return service;
+ @Override // AbstractMasterSystemService
+ protected void onServiceRemoved(@NonNull AutofillManagerServiceImpl service,
+ @UserIdInt int userId) {
+ service.destroyLocked();
+ mAutofillCompatState.removeCompatibilityModeRequests(userId);
}
- @Override // from MasterSystemService
- protected void onServiceEnabledLocked(@NonNull AutofillManagerServiceImpl service, int userId) {
+ @Override // from AbstractMasterSystemService
+ protected void onServiceEnabledLocked(@NonNull AutofillManagerServiceImpl service,
+ @UserIdInt int userId) {
addCompatibilityModeRequestsLocked(service, userId);
}
@@ -245,7 +244,7 @@
}
// Called by Shell command.
- void destroySessions(int userId, IResultReceiver receiver) {
+ void destroySessions(@UserIdInt int userId, IResultReceiver receiver) {
Slog.i(TAG, "destroySessions() for userId " + userId);
getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 4810355..67ccc9b 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -86,7 +86,7 @@
*
*/
final class AutofillManagerServiceImpl
- extends AbstractPerUserSystemService<AutofillManagerServiceImpl> {
+ extends AbstractPerUserSystemService<AutofillManagerServiceImpl, AutofillManagerService> {
private static final String TAG = "AutofillManagerServiceImpl";
private static final int MAX_SESSION_ID_CREATE_TRIES = 2048;
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 1ad83ec..7621ada 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -11,7 +11,7 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
package com.android.server.backup;
@@ -104,7 +104,6 @@
import com.android.server.backup.fullbackup.FullBackupEntry;
import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
import com.android.server.backup.internal.BackupHandler;
-import com.android.server.backup.keyvalue.BackupRequest;
import com.android.server.backup.internal.ClearDataObserver;
import com.android.server.backup.internal.OnTaskFinishedListener;
import com.android.server.backup.internal.Operation;
@@ -112,6 +111,7 @@
import com.android.server.backup.internal.ProvisionedObserver;
import com.android.server.backup.internal.RunBackupReceiver;
import com.android.server.backup.internal.RunInitializeReceiver;
+import com.android.server.backup.keyvalue.BackupRequest;
import com.android.server.backup.params.AdbBackupParams;
import com.android.server.backup.params.AdbParams;
import com.android.server.backup.params.AdbRestoreParams;
@@ -159,17 +159,20 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
+/** System service that performs backup/restore operations. */
public class BackupManagerService {
-
public static final String TAG = "BackupManagerService";
public static final boolean DEBUG = true;
public static final boolean MORE_DEBUG = false;
- public static final boolean DEBUG_SCHEDULING = MORE_DEBUG || true;
+ public static final boolean DEBUG_SCHEDULING = true;
// File containing backup-enabled state. Contains a single byte;
// nonzero == enabled. File missing or contains a zero byte == disabled.
private static final String BACKUP_ENABLE_FILE = "backup_enabled";
+ // Persistently track the need to do a full init.
+ private static final String INIT_SENTINEL_FILE_NAME = "_need_init_";
+
// System-private key used for backing up an app's widget state. Must
// begin with U+FFxx by convention (we reserve all keys starting
// with U+FF00 or higher for system use).
@@ -196,11 +199,16 @@
public static final int BACKUP_METADATA_VERSION = 1;
public static final int BACKUP_WIDGET_METADATA_TOKEN = 0x01FFED01;
- private static final boolean COMPRESS_FULL_BACKUPS = true; // should be true in production
+ private static final int CURRENT_ANCESTRAL_RECORD_VERSION = 1;
+
+ // Round-robin queue for scheduling full backup passes.
+ private static final int SCHEDULE_FILE_VERSION = 1;
public static final String SETTINGS_PACKAGE = "com.android.providers.settings";
public static final String SHARED_BACKUP_AGENT_PACKAGE = "com.android.sharedstoragebackup";
- private static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
+
+ // Pseudoname that we use for the Package Manager metadata "package".
+ public static final String PACKAGE_MANAGER_SENTINEL = "@pm@";
// Retry interval for clear/init when the transport is unavailable
private static final long TRANSPORT_RETRY_INTERVAL = 1 * AlarmManager.INTERVAL_HOUR;
@@ -210,8 +218,23 @@
public static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED";
public static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName";
- // Time delay for initialization operations that can be delayed so as not to consume too much CPU
- // on bring-up and increase time-to-UI.
+ // Bookkeeping of in-flight operations. The operation token is the index of the entry in the
+ // pending operations list.
+ public static final int OP_PENDING = 0;
+ private static final int OP_ACKNOWLEDGED = 1;
+ private static final int OP_TIMEOUT = -1;
+
+ // Waiting for backup agent to respond during backup operation.
+ public static final int OP_TYPE_BACKUP_WAIT = 0;
+
+ // Waiting for backup agent to respond during restore operation.
+ public static final int OP_TYPE_RESTORE_WAIT = 1;
+
+ // An entire backup operation spanning multiple packages.
+ public static final int OP_TYPE_BACKUP = 2;
+
+ // Time delay for initialization operations that can be delayed so as not to consume too much
+ // CPU on bring-up and increase time-to-UI.
private static final long INITIALIZATION_DELAY_MILLIS = 3000;
// Timeout interval for deciding that a bind or clear-data has taken too long
@@ -226,8 +249,62 @@
private static final long BUSY_BACKOFF_MIN_MILLIS = 1000 * 60 * 60; // one hour
private static final int BUSY_BACKOFF_FUZZ = 1000 * 60 * 60 * 2; // two hours
- private BackupManagerConstants mConstants;
+ // The published binder is a singleton Trampoline object that calls through to the proper code.
+ // This indirection lets us turn down the heavy implementation object on the fly without
+ // disturbing binders that have been cached elsewhere in the system.
+ private static Trampoline sInstance;
+
+ static Trampoline getInstance() {
+ // Always constructed during system bring up, so no need to lazy-init.
+ return sInstance;
+ }
+
+ /** Helper to create the {@link BackupManagerService} instance. */
+ public static BackupManagerService create(
+ Context context,
+ Trampoline parent,
+ HandlerThread backupThread) {
+ // Set up our transport options and initialize the default transport
+ SystemConfig systemConfig = SystemConfig.getInstance();
+ Set<ComponentName> transportWhitelist = systemConfig.getBackupTransportWhitelist();
+ if (transportWhitelist == null) {
+ transportWhitelist = Collections.emptySet();
+ }
+
+ String transport =
+ Settings.Secure.getString(
+ context.getContentResolver(), Settings.Secure.BACKUP_TRANSPORT);
+ if (TextUtils.isEmpty(transport)) {
+ transport = null;
+ }
+ if (DEBUG) {
+ Slog.v(TAG, "Starting with transport " + transport);
+ }
+ TransportManager transportManager =
+ new TransportManager(
+ context,
+ transportWhitelist,
+ transport);
+
+ // If encrypted file systems is enabled or disabled, this call will return the
+ // correct directory.
+ File baseStateDir = new File(Environment.getDataDirectory(), "backup");
+
+ // This dir on /cache is managed directly in init.rc
+ File dataDir = new File(Environment.getDownloadCacheDirectory(), "backup_stage");
+
+ return new BackupManagerService(
+ context,
+ parent,
+ backupThread,
+ baseStateDir,
+ dataDir,
+ transportManager);
+ }
+
private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
+ private final TransportManager mTransportManager;
+
private Context mContext;
private PackageManager mPackageManager;
private IPackageManager mPackageManagerBinder;
@@ -235,30 +312,26 @@
private PowerManager mPowerManager;
private AlarmManager mAlarmManager;
private IStorageManager mStorageManager;
+ private BackupManagerConstants mConstants;
+ private PowerManager.WakeLock mWakelock;
+ private BackupHandler mBackupHandler;
private IBackupManager mBackupManagerBinder;
- private final TransportManager mTransportManager;
-
private boolean mEnabled; // access to this is synchronized on 'this'
private boolean mProvisioned;
private boolean mAutoRestore;
- private PowerManager.WakeLock mWakelock;
- private BackupHandler mBackupHandler;
+
private PendingIntent mRunBackupIntent;
private PendingIntent mRunInitIntent;
- private BroadcastReceiver mRunBackupReceiver;
- private BroadcastReceiver mRunInitReceiver;
+
+ private final ArraySet<String> mPendingInits = new ArraySet<>(); // transport names
+
// map UIDs to the set of participating packages under that UID
- private final SparseArray<HashSet<String>> mBackupParticipants
- = new SparseArray<>();
+ private final SparseArray<HashSet<String>> mBackupParticipants = new SparseArray<>();
// Backups that we haven't started yet. Keys are package names.
- private HashMap<String, BackupRequest> mPendingBackups
- = new HashMap<>();
-
- // Pseudoname that we use for the Package Manager metadata "package"
- public static final String PACKAGE_MANAGER_SENTINEL = "@pm@";
+ private HashMap<String, BackupRequest> mPendingBackups = new HashMap<>();
// locking around the pending-backup management
private final Object mQueueLock = new Object();
@@ -269,25 +342,32 @@
// completed.
private final Object mAgentConnectLock = new Object();
private IBackupAgent mConnectedAgent;
- private volatile boolean mBackupRunning;
private volatile boolean mConnecting;
- private volatile long mLastBackupPass;
- // For debugging, we maintain a progress trace of operations during backup
- public static final boolean DEBUG_BACKUP_TRACE = true;
- private final List<String> mBackupTrace = new ArrayList<>();
+ private volatile boolean mBackupRunning;
+ private volatile long mLastBackupPass;
// A similar synchronization mechanism around clearing apps' data for restore
private final Object mClearDataLock = new Object();
private volatile boolean mClearingData;
+ // Used by ADB.
private final BackupPasswordManager mBackupPasswordManager;
+ private final SparseArray<AdbParams> mAdbBackupRestoreConfirmations = new SparseArray<>();
+ private final SecureRandom mRng = new SecureRandom();
// Time when we post the transport registration operation
private final long mRegisterTransportsRequestedTime;
+ @GuardedBy("mQueueLock")
+ private PerformFullTransportBackupTask mRunningFullBackupTask;
+
+ @GuardedBy("mQueueLock")
+ private ArrayList<FullBackupEntry> mFullBackupQueue;
+
@GuardedBy("mPendingRestores")
private boolean mIsRestoreInProgress;
+
@GuardedBy("mPendingRestores")
private final Queue<PerformUnifiedRestoreTask> mPendingRestores = new ArrayDeque<>();
@@ -296,17 +376,155 @@
// Watch the device provisioning operation during setup
private ContentObserver mProvisionedObserver;
- // The published binder is actually to a singleton trampoline object that calls
- // through to the proper code. This indirection lets us turn down the heavy
- // implementation object on the fly without disturbing binders that have been
- // cached elsewhere in the system.
- static Trampoline sInstance;
+ /**
+ * mCurrentOperations contains the list of currently active operations.
+ *
+ * If type of operation is OP_TYPE_WAIT, it are waiting for an ack or timeout.
+ * An operation wraps a BackupRestoreTask within it.
+ * It's the responsibility of this task to remove the operation from this array.
+ *
+ * A BackupRestore task gets notified of ack/timeout for the operation via
+ * BackupRestoreTask#handleCancel, BackupRestoreTask#operationComplete and notifyAll called
+ * on the mCurrentOpLock.
+ * {@link BackupManagerService#waitUntilOperationComplete(int)} is
+ * used in various places to 'wait' for notifyAll and detect change of pending state of an
+ * operation. So typically, an operation will be removed from this array by:
+ * - BackupRestoreTask#handleCancel and
+ * - BackupRestoreTask#operationComplete OR waitUntilOperationComplete. Do not remove at both
+ * these places because waitUntilOperationComplete relies on the operation being present to
+ * determine its completion status.
+ *
+ * If type of operation is OP_BACKUP, it is a task running backups. It provides a handle to
+ * cancel backup tasks.
+ */
+ @GuardedBy("mCurrentOpLock")
+ private final SparseArray<Operation> mCurrentOperations = new SparseArray<>();
+ private final Object mCurrentOpLock = new Object();
+ private final Random mTokenGenerator = new Random();
+ final AtomicInteger mNextToken = new AtomicInteger();
- static Trampoline getInstance() {
- // Always constructed during system bringup, so no need to lazy-init
- return sInstance;
+ // Where we keep our journal files and other bookkeeping.
+ private File mBaseStateDir;
+ private File mDataDir;
+ private File mJournalDir;
+ @Nullable
+ private DataChangedJournal mJournal;
+ private File mFullBackupScheduleFile;
+
+ // Keep a log of all the apps we've ever backed up.
+ private ProcessedPackagesJournal mProcessedPackagesJournal;
+
+ private File mTokenFile;
+ private Set<String> mAncestralPackages = null;
+ private long mAncestralToken = 0;
+ private long mCurrentToken = 0;
+
+ @VisibleForTesting
+ public BackupManagerService(
+ Context context,
+ Trampoline parent,
+ HandlerThread backupThread,
+ File baseStateDir,
+ File dataDir,
+ TransportManager transportManager) {
+ mContext = context;
+ mPackageManager = context.getPackageManager();
+ mPackageManagerBinder = AppGlobals.getPackageManager();
+ mActivityManager = ActivityManager.getService();
+
+ mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ mStorageManager = IStorageManager.Stub.asInterface(ServiceManager.getService("mount"));
+
+ mBackupManagerBinder = Trampoline.asInterface(parent.asBinder());
+
+ mAgentTimeoutParameters = new
+ BackupAgentTimeoutParameters(Handler.getMain(), mContext.getContentResolver());
+ mAgentTimeoutParameters.start();
+
+ // spin up the backup/restore handler thread
+ mBackupHandler = new BackupHandler(this, backupThread.getLooper());
+
+ // Set up our bookkeeping
+ final ContentResolver resolver = context.getContentResolver();
+ mProvisioned = Settings.Global.getInt(resolver,
+ Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+ mAutoRestore = Settings.Secure.getInt(resolver,
+ Settings.Secure.BACKUP_AUTO_RESTORE, 1) != 0;
+
+ mProvisionedObserver = new ProvisionedObserver(this, mBackupHandler);
+ resolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+ false, mProvisionedObserver);
+
+ mBaseStateDir = baseStateDir;
+ mBaseStateDir.mkdirs();
+ if (!SELinux.restorecon(mBaseStateDir)) {
+ Slog.e(TAG, "SELinux restorecon failed on " + mBaseStateDir);
+ }
+
+ mDataDir = dataDir;
+
+ mBackupPasswordManager = new BackupPasswordManager(mContext, mBaseStateDir, mRng);
+
+ // Alarm receivers for scheduled backups & initialization operations
+ BroadcastReceiver mRunBackupReceiver = new RunBackupReceiver(this);
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(RUN_BACKUP_ACTION);
+ context.registerReceiver(mRunBackupReceiver, filter,
+ android.Manifest.permission.BACKUP, null);
+
+ BroadcastReceiver mRunInitReceiver = new RunInitializeReceiver(this);
+ filter = new IntentFilter();
+ filter.addAction(RUN_INITIALIZE_ACTION);
+ context.registerReceiver(mRunInitReceiver, filter,
+ android.Manifest.permission.BACKUP, null);
+
+ Intent backupIntent = new Intent(RUN_BACKUP_ACTION);
+ backupIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mRunBackupIntent = PendingIntent.getBroadcast(context, 0, backupIntent, 0);
+
+ Intent initIntent = new Intent(RUN_INITIALIZE_ACTION);
+ initIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mRunInitIntent = PendingIntent.getBroadcast(context, 0, initIntent, 0);
+
+ // Set up the backup-request journaling
+ mJournalDir = new File(mBaseStateDir, "pending");
+ mJournalDir.mkdirs(); // creates mBaseStateDir along the way
+ mJournal = null; // will be created on first use
+
+ mConstants = new BackupManagerConstants(mBackupHandler, mContext.getContentResolver());
+ // We are observing changes to the constants throughout the lifecycle of BMS. This is
+ // because we reference the constants in multiple areas of BMS, which otherwise would
+ // require frequent starting and stopping.
+ mConstants.start();
+
+ // Set up the various sorts of package tracking we do
+ mFullBackupScheduleFile = new File(mBaseStateDir, "fb-schedule");
+ initPackageTracking();
+
+ // Build our mapping of uid to backup client services. This implicitly
+ // schedules a backup pass on the Package Manager metadata the first
+ // time anything needs to be backed up.
+ synchronized (mBackupParticipants) {
+ addPackageParticipantsLocked(null);
+ }
+
+ mTransportManager = transportManager;
+ mTransportManager.setOnTransportRegisteredListener(this::onTransportRegistered);
+ mRegisterTransportsRequestedTime = SystemClock.elapsedRealtime();
+ mBackupHandler.postDelayed(
+ mTransportManager::registerTransports, INITIALIZATION_DELAY_MILLIS);
+
+ // Now that we know about valid backup participants, parse any leftover journal files into
+ // the pending backup set
+ mBackupHandler.postDelayed(this::parseLeftoverJournals, INITIALIZATION_DELAY_MILLIS);
+
+ // Power management
+ mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*");
}
+
public BackupManagerConstants getConstants() {
return mConstants;
}
@@ -549,6 +767,7 @@
return mPendingInits;
}
+ /** Clear all pending transport initializations. */
public void clearPendingInits() {
mPendingInits.clear();
}
@@ -562,28 +781,10 @@
mRunningFullBackupTask = runningFullBackupTask;
}
- public static final class Lifecycle extends SystemService {
-
- public Lifecycle(Context context) {
- super(context);
- sInstance = new Trampoline(context);
- }
-
- @Override
- public void onStart() {
- publishBinderService(Context.BACKUP_SERVICE, sInstance);
- }
-
- @Override
- public void onUnlockUser(int userId) {
- if (userId == UserHandle.USER_SYSTEM) {
- sInstance.unlockSystemUser();
- }
- }
- }
-
- // Called through the trampoline from onUnlockUser(), then we buck the work
- // off to the background thread to keep the unlock time down.
+ /**
+ * Called through Trampoline from {@link Lifecycle#onUnlockUser(int)}. We run the heavy work on
+ * a background thread to keep the unlock time down.
+ */
public void unlockSystemUser() {
// Migrate legacy setting
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup migrate");
@@ -618,89 +819,10 @@
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
- // Bookkeeping of in-flight operations for timeout etc. purposes. The operation
- // token is the index of the entry in the pending-operations list.
- public static final int OP_PENDING = 0;
- private static final int OP_ACKNOWLEDGED = 1;
- private static final int OP_TIMEOUT = -1;
-
- // Waiting for backup agent to respond during backup operation.
- public static final int OP_TYPE_BACKUP_WAIT = 0;
-
- // Waiting for backup agent to respond during restore operation.
- public static final int OP_TYPE_RESTORE_WAIT = 1;
-
- // An entire backup operation spanning multiple packages.
- public static final int OP_TYPE_BACKUP = 2;
-
/**
- * mCurrentOperations contains the list of currently active operations.
- *
- * If type of operation is OP_TYPE_WAIT, it are waiting for an ack or timeout.
- * An operation wraps a BackupRestoreTask within it.
- * It's the responsibility of this task to remove the operation from this array.
- *
- * A BackupRestore task gets notified of ack/timeout for the operation via
- * BackupRestoreTask#handleCancel, BackupRestoreTask#operationComplete and notifyAll called
- * on the mCurrentOpLock.
- * {@link BackupManagerService#waitUntilOperationComplete(int)} is
- * used in various places to 'wait' for notifyAll and detect change of pending state of an
- * operation. So typically, an operation will be removed from this array by:
- * - BackupRestoreTask#handleCancel and
- * - BackupRestoreTask#operationComplete OR waitUntilOperationComplete. Do not remove at both
- * these places because waitUntilOperationComplete relies on the operation being present to
- * determine its completion status.
- *
- * If type of operation is OP_BACKUP, it is a task running backups. It provides a handle to
- * cancel backup tasks.
+ * Utility: build a new random integer token. The low bits are the ordinal of the operation for
+ * near-time uniqueness, and the upper bits are random for app-side unpredictability.
*/
- @GuardedBy("mCurrentOpLock")
- private final SparseArray<Operation> mCurrentOperations = new SparseArray<>();
- private final Object mCurrentOpLock = new Object();
- private final Random mTokenGenerator = new Random();
- final AtomicInteger mNextToken = new AtomicInteger();
-
- private final SparseArray<AdbParams> mAdbBackupRestoreConfirmations = new SparseArray<>();
-
- // Where we keep our journal files and other bookkeeping
- private File mBaseStateDir;
- private File mDataDir;
- private File mJournalDir;
- @Nullable
- private DataChangedJournal mJournal;
-
- private final SecureRandom mRng = new SecureRandom();
-
- // Keep a log of all the apps we've ever backed up, and what the dataset tokens are for both
- // the current backup dataset and the ancestral dataset.
- private ProcessedPackagesJournal mProcessedPackagesJournal;
-
- private static final int CURRENT_ANCESTRAL_RECORD_VERSION = 1;
- // increment when the schema changes
- private File mTokenFile;
- private Set<String> mAncestralPackages = null;
- private long mAncestralToken = 0;
- private long mCurrentToken = 0;
-
- // Persistently track the need to do a full init
- private static final String INIT_SENTINEL_FILE_NAME = "_need_init_";
- private final ArraySet<String> mPendingInits = new ArraySet<>(); // transport names
-
- // Round-robin queue for scheduling full backup passes
- private static final int SCHEDULE_FILE_VERSION = 1; // current version of the schedule file
-
- private File mFullBackupScheduleFile;
- // If we're running a schedule-driven full backup, this is the task instance doing it
-
- @GuardedBy("mQueueLock")
- private PerformFullTransportBackupTask mRunningFullBackupTask;
-
- @GuardedBy("mQueueLock")
- private ArrayList<FullBackupEntry> mFullBackupQueue;
-
- // Utility: build a new random integer token. The low bits are the ordinal of the
- // operation for near-time uniqueness, and the upper bits are random for app-
- // side unpredictability.
public int generateRandomIntegerToken() {
int token = mTokenGenerator.nextInt();
if (token < 0) token = -token;
@@ -709,10 +831,9 @@
return token;
}
- /*
- * Construct a backup agent instance for the metadata pseudopackage. This is a
- * process-local non-lifecycle agent instance, so we manually set up the context
- * topology for it.
+ /**
+ * Construct a backup agent instance for the metadata pseudopackage. This is a process-local
+ * non-lifecycle agent instance, so we manually set up the context topology for it.
*/
public BackupAgent makeMetadataAgent() {
PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(mPackageManager);
@@ -721,8 +842,8 @@
return pmAgent;
}
- /*
- * Same as above but with the explicit package-set configuration.
+ /**
+ * Same as {@link #makeMetadataAgent()} but with explicit package-set configuration.
*/
public PackageManagerBackupAgent makeMetadataAgent(List<PackageInfo> packages) {
PackageManagerBackupAgent pmAgent =
@@ -732,172 +853,6 @@
return pmAgent;
}
- // ----- Debug-only backup operation trace -----
- public void addBackupTrace(String s) {
- if (DEBUG_BACKUP_TRACE) {
- synchronized (mBackupTrace) {
- mBackupTrace.add(s);
- }
- }
- }
-
- public void clearBackupTrace() {
- if (DEBUG_BACKUP_TRACE) {
- synchronized (mBackupTrace) {
- mBackupTrace.clear();
- }
- }
- }
-
- // ----- Main service implementation -----
-
- public static BackupManagerService create(
- Context context,
- Trampoline parent,
- HandlerThread backupThread) {
- // Set up our transport options and initialize the default transport
- SystemConfig systemConfig = SystemConfig.getInstance();
- Set<ComponentName> transportWhitelist = systemConfig.getBackupTransportWhitelist();
- if (transportWhitelist == null) {
- transportWhitelist = Collections.emptySet();
- }
-
- String transport =
- Settings.Secure.getString(
- context.getContentResolver(), Settings.Secure.BACKUP_TRANSPORT);
- if (TextUtils.isEmpty(transport)) {
- transport = null;
- }
- if (DEBUG) {
- Slog.v(TAG, "Starting with transport " + transport);
- }
- TransportManager transportManager =
- new TransportManager(
- context,
- transportWhitelist,
- transport);
-
- // If encrypted file systems is enabled or disabled, this call will return the
- // correct directory.
- File baseStateDir = new File(Environment.getDataDirectory(), "backup");
-
- // This dir on /cache is managed directly in init.rc
- File dataDir = new File(Environment.getDownloadCacheDirectory(), "backup_stage");
-
- return new BackupManagerService(
- context,
- parent,
- backupThread,
- baseStateDir,
- dataDir,
- transportManager);
- }
-
- @VisibleForTesting
- public BackupManagerService(
- Context context,
- Trampoline parent,
- HandlerThread backupThread,
- File baseStateDir,
- File dataDir,
- TransportManager transportManager) {
- mContext = context;
- mPackageManager = context.getPackageManager();
- mPackageManagerBinder = AppGlobals.getPackageManager();
- mActivityManager = ActivityManager.getService();
-
- mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- mStorageManager = IStorageManager.Stub.asInterface(ServiceManager.getService("mount"));
-
- mBackupManagerBinder = Trampoline.asInterface(parent.asBinder());
-
- mAgentTimeoutParameters = new
- BackupAgentTimeoutParameters(Handler.getMain(), mContext.getContentResolver());
- mAgentTimeoutParameters.start();
-
- // spin up the backup/restore handler thread
- mBackupHandler = new BackupHandler(this, backupThread.getLooper());
-
- // Set up our bookkeeping
- final ContentResolver resolver = context.getContentResolver();
- mProvisioned = Settings.Global.getInt(resolver,
- Settings.Global.DEVICE_PROVISIONED, 0) != 0;
- mAutoRestore = Settings.Secure.getInt(resolver,
- Settings.Secure.BACKUP_AUTO_RESTORE, 1) != 0;
-
- mProvisionedObserver = new ProvisionedObserver(this, mBackupHandler);
- resolver.registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
- false, mProvisionedObserver);
-
- mBaseStateDir = baseStateDir;
- mBaseStateDir.mkdirs();
- if (!SELinux.restorecon(mBaseStateDir)) {
- Slog.e(TAG, "SELinux restorecon failed on " + mBaseStateDir);
- }
-
- mDataDir = dataDir;
-
- mBackupPasswordManager = new BackupPasswordManager(mContext, mBaseStateDir, mRng);
-
- // Alarm receivers for scheduled backups & initialization operations
- mRunBackupReceiver = new RunBackupReceiver(this);
- IntentFilter filter = new IntentFilter();
- filter.addAction(RUN_BACKUP_ACTION);
- context.registerReceiver(mRunBackupReceiver, filter,
- android.Manifest.permission.BACKUP, null);
-
- mRunInitReceiver = new RunInitializeReceiver(this);
- filter = new IntentFilter();
- filter.addAction(RUN_INITIALIZE_ACTION);
- context.registerReceiver(mRunInitReceiver, filter,
- android.Manifest.permission.BACKUP, null);
-
- Intent backupIntent = new Intent(RUN_BACKUP_ACTION);
- backupIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mRunBackupIntent = PendingIntent.getBroadcast(context, 0, backupIntent, 0);
-
- Intent initIntent = new Intent(RUN_INITIALIZE_ACTION);
- initIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mRunInitIntent = PendingIntent.getBroadcast(context, 0, initIntent, 0);
-
- // Set up the backup-request journaling
- mJournalDir = new File(mBaseStateDir, "pending");
- mJournalDir.mkdirs(); // creates mBaseStateDir along the way
- mJournal = null; // will be created on first use
-
- mConstants = new BackupManagerConstants(mBackupHandler, mContext.getContentResolver());
- // We are observing changes to the constants throughout the lifecycle of BMS. This is
- // because we reference the constants in multiple areas of BMS, which otherwise would
- // require frequent starting and stopping.
- mConstants.start();
-
- // Set up the various sorts of package tracking we do
- mFullBackupScheduleFile = new File(mBaseStateDir, "fb-schedule");
- initPackageTracking();
-
- // Build our mapping of uid to backup client services. This implicitly
- // schedules a backup pass on the Package Manager metadata the first
- // time anything needs to be backed up.
- synchronized (mBackupParticipants) {
- addPackageParticipantsLocked(null);
- }
-
- mTransportManager = transportManager;
- mTransportManager.setOnTransportRegisteredListener(this::onTransportRegistered);
- mRegisterTransportsRequestedTime = SystemClock.elapsedRealtime();
- mBackupHandler.postDelayed(
- mTransportManager::registerTransports, INITIALIZATION_DELAY_MILLIS);
-
- // Now that we know about valid backup participants, parse any leftover journal files into
- // the pending backup set
- mBackupHandler.postDelayed(this::parseLeftoverJournals, INITIALIZATION_DELAY_MILLIS);
-
- // Power management
- mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*");
- }
-
private void initPackageTracking() {
if (MORE_DEBUG) Slog.v(TAG, "` tracking");
@@ -965,16 +920,16 @@
return null;
}
- final int N = in.readInt();
- schedule = new ArrayList<>(N);
+ final int numPackages = in.readInt();
+ schedule = new ArrayList<>(numPackages);
// HashSet instead of ArraySet specifically because we want the eventual
// lookups against O(hundreds) of entries to be as fast as possible, and
// we discard the set immediately after the scan so the extra memory
// overhead is transient.
- HashSet<String> foundApps = new HashSet<>(N);
+ HashSet<String> foundApps = new HashSet<>(numPackages);
- for (int i = 0; i < N; i++) {
+ for (int i = 0; i < numPackages; i++) {
String pkgName = in.readUTF();
long lastBackup = in.readLong();
foundApps.add(pkgName); // all apps that we've addressed already
@@ -1057,10 +1012,10 @@
// [utf8] package name
// [long] last backup time for this package
// }
- int N = mFullBackupQueue.size();
- bufOut.writeInt(N);
+ int numPackages = mFullBackupQueue.size();
+ bufOut.writeInt(numPackages);
- for (int i = 0; i < N; i++) {
+ for (int i = 0; i < numPackages; i++) {
FullBackupEntry entry = mFullBackupQueue.get(i);
bufOut.writeUTF(entry.packageName);
bufOut.writeLong(entry.lastBackup);
@@ -1100,21 +1055,24 @@
}
}
- // Used for generating random salts or passwords
+ /** Used for generating random salts or passwords. */
public byte[] randomBytes(int bits) {
byte[] array = new byte[bits / 8];
mRng.nextBytes(array);
return array;
}
+ /** For adb backup/restore. */
public boolean setBackupPassword(String currentPw, String newPw) {
return mBackupPasswordManager.setBackupPassword(currentPw, newPw);
}
+ /** For adb backup/restore. */
public boolean hasBackupPassword() {
return mBackupPasswordManager.hasBackupPassword();
}
+ /** For adb backup/restore. */
public boolean backupPasswordMatches(String currentPw) {
return mBackupPasswordManager.backupPasswordMatches(currentPw);
}
@@ -1151,9 +1109,10 @@
}
}
- // Reset all of our bookkeeping, in response to having been told that
- // the backend data has been wiped [due to idle expiry, for example],
- // so we must re-upload all saved settings.
+ /**
+ * Reset all of our bookkeeping because the backend data has been wiped (for example due to idle
+ * expiry), so we must re-upload all saved settings.
+ */
public void resetBackupState(File stateFileDir) {
synchronized (mQueueLock) {
mProcessedPackagesJournal.reset();
@@ -1172,8 +1131,8 @@
// Enqueue a new backup of every participant
synchronized (mBackupParticipants) {
- final int N = mBackupParticipants.size();
- for (int i = 0; i < N; i++) {
+ final int numParticipants = mBackupParticipants.size();
+ for (int i = 0; i < numParticipants; i++) {
HashSet<String> participants = mBackupParticipants.valueAt(i);
if (participants != null) {
for (String packageName : participants) {
@@ -1217,10 +1176,10 @@
boolean added = false;
boolean changed = false;
Bundle extras = intent.getExtras();
- String pkgList[] = null;
- if (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
- Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
- Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+ String[] pkgList = null;
+ if (Intent.ACTION_PACKAGE_ADDED.equals(action)
+ || Intent.ACTION_PACKAGE_REMOVED.equals(action)
+ || Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
Uri uri = intent.getData();
if (uri == null) {
return;
@@ -1413,8 +1372,8 @@
// !!! TODO: cache this and regenerate only when necessary
int flags = PackageManager.GET_SIGNING_CERTIFICATES;
List<PackageInfo> packages = mPackageManager.getInstalledPackages(flags);
- int N = packages.size();
- for (int a = N - 1; a >= 0; a--) {
+ int numPackages = packages.size();
+ for (int a = numPackages - 1; a >= 0; a--) {
PackageInfo pkg = packages.get(a);
try {
ApplicationInfo app = pkg.applicationInfo;
@@ -1437,9 +1396,10 @@
return packages;
}
- // Called from the backup tasks: record that the given app has been successfully
- // backed up at least once. This includes both key/value and full-data backups
- // through the transport.
+ /**
+ * Called from the backup tasks: record that the given app has been successfully backed up at
+ * least once. This includes both key/value and full-data backups through the transport.
+ */
public void logBackupComplete(String packageName) {
if (packageName.equals(PACKAGE_MANAGER_SENTINEL)) return;
@@ -1447,8 +1407,8 @@
final Intent notification = new Intent();
notification.setAction(BACKUP_FINISHED_ACTION);
notification.setPackage(receiver);
- notification.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES |
- Intent.FLAG_RECEIVER_FOREGROUND);
+ notification.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES
+ | Intent.FLAG_RECEIVER_FOREGROUND);
notification.putExtra(BACKUP_FINISHED_PACKAGE_EXTRA, packageName);
mContext.sendBroadcastAsUser(notification, UserHandle.OWNER);
}
@@ -1456,9 +1416,10 @@
mProcessedPackagesJournal.addPackage(packageName);
}
- // Persistently record the current and ancestral backup tokens as well
- // as the set of packages with data [supposedly] available in the
- // ancestral dataset.
+ /**
+ * Persistently record the current and ancestral backup tokens, as well as the set of packages
+ * with data available in the ancestral dataset.
+ */
public void writeRestoreTokens() {
try (RandomAccessFile af = new RandomAccessFile(mTokenFile, "rwd")) {
// First, the version number of this record, for futureproofing
@@ -1512,7 +1473,7 @@
}
// if we timed out with no connect, abort and move on
- if (mConnecting == true) {
+ if (mConnecting) {
Slog.w(TAG, "Timeout waiting for agent " + app);
mConnectedAgent = null;
}
@@ -1533,6 +1494,7 @@
return agent;
}
+ /** Unbind from a backup agent. */
public void unbindAgent(ApplicationInfo app) {
try {
mActivityManager.unbindBackupAgent(app);
@@ -1541,11 +1503,13 @@
}
}
- // clear an application's data, blocking until the operation completes or times out
- // if keepSystemState is true, we intentionally do not also clear system state that
- // would ordinarily also be cleared, because we aren't actually wiping the app back
- // to empty; we're bringing it into the actual expected state related to the already-
- // restored notification state etc.
+ /**
+ * Clear an application's data, blocking until the operation completes or times out. If {@code
+ * keepSystemState} is {@code true}, we intentionally do not clear system state that would
+ * ordinarily also be cleared, because we aren't actually wiping the app back to empty; we're
+ * bringing it into the actual expected state related to the already-restored notification state
+ * etc.
+ */
public void clearApplicationDataSynchronous(String packageName, boolean keepSystemState) {
// Don't wipe packages marked allowClearUserData=false
try {
@@ -1567,7 +1531,8 @@
synchronized (mClearDataLock) {
mClearingData = true;
try {
- mActivityManager.clearApplicationUserData(packageName, keepSystemState, observer, 0);
+ mActivityManager.clearApplicationUserData(
+ packageName, keepSystemState, observer, 0);
} catch (RemoteException e) {
// can't happen because the activity manager is in this process
}
@@ -1585,8 +1550,10 @@
}
}
- // Get the restore-set token for the best-available restore set for this package:
- // the active set if possible, else the ancestral one. Returns zero if none available.
+ /**
+ * Get the restore-set token for the best-available restore set for this {@code packageName}:
+ * the active set if possible, else the ancestral one. Returns zero if none available.
+ */
public long getAvailableRestoreToken(String packageName) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"getAvailableRestoreToken");
@@ -1604,10 +1571,19 @@
return token;
}
+ /**
+ * Requests a backup for the inputted {@code packages}.
+ *
+ * @see #requestBackup(String[], IBackupObserver, IBackupManagerMonitor, int).
+ */
public int requestBackup(String[] packages, IBackupObserver observer, int flags) {
return requestBackup(packages, observer, null, flags);
}
+ /**
+ * Requests a backup for the inputted {@code packages} with a specified {@link
+ * IBackupManagerMonitor}.
+ */
public int requestBackup(String[] packages, IBackupObserver observer,
IBackupManagerMonitor monitor, int flags) {
mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "requestBackup");
@@ -1681,8 +1657,8 @@
EventLog.writeEvent(EventLogTags.BACKUP_REQUESTED, packages.length, kvBackupList.size(),
fullBackupList.size());
if (MORE_DEBUG) {
- Slog.i(TAG, "Backup requested for " + packages.length + " packages, of them: " +
- fullBackupList.size() + " full backups, " + kvBackupList.size()
+ Slog.i(TAG, "Backup requested for " + packages.length + " packages, of them: "
+ + fullBackupList.size() + " full backups, " + kvBackupList.size()
+ " k/v backups");
}
@@ -1695,7 +1671,7 @@
return BackupManager.SUCCESS;
}
- // Cancel all running backups.
+ /** Cancel all running backups. */
public void cancelBackups() {
mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "cancelBackups");
if (MORE_DEBUG) {
@@ -1725,11 +1701,12 @@
}
}
+ /** Schedule a timeout message for the operation identified by {@code token}. */
public void prepareOperationTimeout(int token, long interval, BackupRestoreTask callback,
int operationType) {
if (operationType != OP_TYPE_BACKUP_WAIT && operationType != OP_TYPE_RESTORE_WAIT) {
- Slog.wtf(TAG, "prepareOperationTimeout() doesn't support operation " +
- Integer.toHexString(token) + " of type " + operationType);
+ Slog.wtf(TAG, "prepareOperationTimeout() doesn't support operation "
+ + Integer.toHexString(token) + " of type " + operationType);
return;
}
if (MORE_DEBUG) {
@@ -1752,12 +1729,16 @@
case OP_TYPE_RESTORE_WAIT:
return MSG_RESTORE_OPERATION_TIMEOUT;
default:
- Slog.wtf(TAG, "getMessageIdForOperationType called on invalid operation type: " +
- operationType);
+ Slog.wtf(TAG, "getMessageIdForOperationType called on invalid operation type: "
+ + operationType);
return -1;
}
}
+ /**
+ * Add an operation to the list of currently running operations. Used for cancellation,
+ * completion and timeout callbacks that act on the operation via the {@code token}.
+ */
public void putOperation(int token, Operation operation) {
if (MORE_DEBUG) {
Slog.d(TAG, "Adding operation token=" + Integer.toHexString(token) + ", operation type="
@@ -1768,20 +1749,24 @@
}
}
+ /**
+ * Remove an operation from the list of currently running operations. An operation is removed
+ * when it is completed, cancelled, or timed out, and thus no longer running.
+ */
public void removeOperation(int token) {
if (MORE_DEBUG) {
Slog.d(TAG, "Removing operation token=" + Integer.toHexString(token));
}
synchronized (mCurrentOpLock) {
if (mCurrentOperations.get(token) == null) {
- Slog.w(TAG, "Duplicate remove for operation. token=" +
- Integer.toHexString(token));
+ Slog.w(TAG, "Duplicate remove for operation. token="
+ + Integer.toHexString(token));
}
mCurrentOperations.remove(token);
}
}
- // synchronous waiter case
+ /** Block until we received an operation complete message (from the agent or cancellation). */
public boolean waitUntilOperationComplete(int token) {
if (MORE_DEBUG) {
Slog.i(TAG, "Blocking until operation complete for "
@@ -1804,8 +1789,8 @@
// When the wait is notified we loop around and recheck the current state
} else {
if (MORE_DEBUG) {
- Slog.d(TAG, "Unblocked waiting for operation token=" +
- Integer.toHexString(token));
+ Slog.d(TAG, "Unblocked waiting for operation token="
+ + Integer.toHexString(token));
}
// No longer pending; we're done
finalState = op.state;
@@ -1826,6 +1811,7 @@
return finalState == OP_ACKNOWLEDGED;
}
+ /** Cancel the operation associated with {@code token}. */
public void handleCancel(int token, boolean cancelAll) {
// Notify any synchronous waiters
Operation op = null;
@@ -1841,8 +1827,8 @@
if (state == OP_ACKNOWLEDGED) {
// The operation finished cleanly, so we have nothing more to do.
if (DEBUG) {
- Slog.w(TAG, "Operation already got an ack." +
- "Should have been removed from mCurrentOperations.");
+ Slog.w(TAG, "Operation already got an ack."
+ + "Should have been removed from mCurrentOperations.");
}
op = null;
mCurrentOperations.delete(token);
@@ -1871,8 +1857,7 @@
}
}
- // ----- Back up a set of applications via a worker thread -----
-
+ /** Returns {@code true} if a backup is currently running, else returns {@code false}. */
public boolean isBackupOperationInProgress() {
synchronized (mCurrentOpLock) {
for (int i = 0; i < mCurrentOperations.size(); i++) {
@@ -1885,7 +1870,7 @@
return false;
}
-
+ /** Unbind the backup agent and kill the app if it's a non-system app. */
public void tearDownAgentAndKill(ApplicationInfo app) {
if (app == null) {
// Null means the system package, so just quietly move on. :)
@@ -1911,6 +1896,7 @@
}
}
+ /** For adb backup/restore. */
public boolean deviceIsEncrypted() {
try {
return mStorageManager.getEncryptionState()
@@ -1961,8 +1947,8 @@
*/
@GuardedBy("mQueueLock")
private void dequeueFullBackupLocked(String packageName) {
- final int N = mFullBackupQueue.size();
- for (int i = N - 1; i >= 0; i--) {
+ final int numPackages = mFullBackupQueue.size();
+ for (int i = numPackages - 1; i >= 0; i--) {
final FullBackupEntry e = mFullBackupQueue.get(i);
if (packageName.equals(e.packageName)) {
mFullBackupQueue.remove(i);
@@ -2211,8 +2197,10 @@
return true;
}
- // The job scheduler says our constraints don't hold any more,
- // so tear down any ongoing backup task right away.
+ /**
+ * The job scheduler says our constraints don't hold anymore, so tear down any ongoing backup
+ * task right away.
+ */
public void endFullBackup() {
// offload the mRunningFullBackupTask.handleCancel() call to another thread,
// as we might have to wait for mCancelLock
@@ -2236,7 +2224,7 @@
new Thread(endFullBackupRunnable, "end-full-backup").start();
}
- // Used by both incremental and full restore
+ /** Used by both incremental and full restore to restore widget data. */
public void restoreWidgetData(String packageName, byte[] widgetData) {
// Apply the restored widget state and generate the ID update for the app
// TODO: http://b/22388012
@@ -2250,6 +2238,7 @@
// NEW UNIFIED RESTORE IMPLEMENTATION
// *****************************
+ /** Schedule a backup pass for {@code packageName}. */
public void dataChangedImpl(String packageName) {
HashSet<String> targets = dataChangedTargets(packageName);
dataChangedImpl(packageName, targets);
@@ -2319,6 +2308,7 @@
// ----- IBackupManager binder interface -----
+ /** Sent from an app's backup agent to let the service know that there's new data to backup. */
public void dataChanged(final String packageName) {
final int callingUserHandle = UserHandle.getCallingUserId();
if (callingUserHandle != UserHandle.USER_SYSTEM) {
@@ -2348,13 +2338,11 @@
});
}
- // Run an initialize operation for the given transport
+ /** Run an initialize operation for the given transport. */
public void initializeTransports(String[] transportNames, IBackupObserver observer) {
mContext.enforceCallingPermission(android.Manifest.permission.BACKUP,
"initializeTransport");
- if (MORE_DEBUG || true) {
- Slog.v(TAG, "initializeTransport(): " + Arrays.asList(transportNames));
- }
+ Slog.v(TAG, "initializeTransport(): " + Arrays.asList(transportNames));
final long oldId = Binder.clearCallingIdentity();
try {
@@ -2367,7 +2355,7 @@
}
}
- // Clear the given package's backup data from the current transport
+ /** Clear the given package's backup data from the current transport. */
public void clearBackupData(String transportName, String packageName) {
if (DEBUG) Slog.v(TAG, "clearBackupData() of " + packageName + " on " + transportName);
PackageInfo info;
@@ -2421,8 +2409,10 @@
}
}
- // Run a backup pass immediately for any applications that have declared
- // that they have pending updates.
+ /**
+ * Run a backup pass immediately for any applications that have declared that they have pending
+ * updates.
+ */
public void backupNow() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "backupNow");
@@ -2453,17 +2443,18 @@
}
}
+ /** Returns {@code true} if the system user has gone through SUW. */
public boolean deviceIsProvisioned() {
final ContentResolver resolver = mContext.getContentResolver();
return (Settings.Global.getInt(resolver, Settings.Global.DEVICE_PROVISIONED, 0) != 0);
}
- // Run a backup pass for the given packages, writing the resulting data stream
- // to the supplied file descriptor. This method is synchronous and does not return
- // to the caller until the backup has been completed.
- //
- // This is the variant used by 'adb backup'; it requires on-screen confirmation
- // by the user because it can be used to offload data over untrusted USB.
+ /**
+ * Used by 'adb backup' to run a backup pass for packages supplied via the command line, writing
+ * the resulting data stream to the supplied {@code fd}. This method is synchronous and does not
+ * return to the caller until the backup has been completed. It requires on-screen confirmation
+ * by the user.
+ */
public void adbBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
boolean includeShared, boolean doWidgets, boolean doAllApps, boolean includeSystem,
boolean compress, boolean doKeyValue, String[] pkgList) {
@@ -2541,6 +2532,7 @@
}
}
+ /** Run a full backup pass for the given packages. Used by 'adb shell bmgr'. */
public void fullTransportBackup(String[] pkgNames) {
mContext.enforceCallingPermission(android.Manifest.permission.BACKUP,
"fullTransportBackup");
@@ -2600,6 +2592,10 @@
}
}
+ /**
+ * Used by 'adb restore' to run a restore pass, blocking until completion. Requires user
+ * confirmation.
+ */
public void adbRestore(ParcelFileDescriptor fd) {
mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbRestore");
@@ -2683,7 +2679,7 @@
private void waitForCompletion(AdbParams params) {
synchronized (params.latch) {
- while (params.latch.get() == false) {
+ while (!params.latch.get()) {
try {
params.latch.wait();
} catch (InterruptedException e) { /* never interrupted */ }
@@ -2691,6 +2687,7 @@
}
}
+ /** Called when adb backup/restore has completed. */
public void signalAdbBackupRestoreCompletion(AdbParams params) {
synchronized (params.latch) {
params.latch.set(true);
@@ -2698,8 +2695,10 @@
}
}
- // Confirm that the previously-requested full backup/restore operation can proceed. This
- // is used to require a user-facing disclosure about the operation.
+ /**
+ * Confirm that the previously-requested full backup/restore operation can proceed. This is used
+ * to require a user-facing disclosure about the operation.
+ */
public void acknowledgeAdbBackupOrRestore(int token, boolean allow,
String curPassword, String encPpassword, IFullBackupRestoreObserver observer) {
if (DEBUG) {
@@ -2750,55 +2749,7 @@
}
}
- private static boolean backupSettingMigrated(int userId) {
- File base = new File(Environment.getDataDirectory(), "backup");
- File enableFile = new File(base, BACKUP_ENABLE_FILE);
- return enableFile.exists();
- }
-
- private static boolean readBackupEnableState(int userId) {
- File base = new File(Environment.getDataDirectory(), "backup");
- File enableFile = new File(base, BACKUP_ENABLE_FILE);
- if (enableFile.exists()) {
- try (FileInputStream fin = new FileInputStream(enableFile)) {
- int state = fin.read();
- return state != 0;
- } catch (IOException e) {
- // can't read the file; fall through to assume disabled
- Slog.e(TAG, "Cannot read enable state; assuming disabled");
- }
- } else {
- if (DEBUG) {
- Slog.i(TAG, "isBackupEnabled() => false due to absent settings file");
- }
- }
- return false;
- }
-
- private static void writeBackupEnableState(boolean enable, int userId) {
- File base = new File(Environment.getDataDirectory(), "backup");
- File enableFile = new File(base, BACKUP_ENABLE_FILE);
- File stage = new File(base, BACKUP_ENABLE_FILE + "-stage");
- try (FileOutputStream fout = new FileOutputStream(stage)) {
- fout.write(enable ? 1 : 0);
- fout.close();
- stage.renameTo(enableFile);
- // will be synced immediately by the try-with-resources call to close()
- } catch (IOException | RuntimeException e) {
- // Whoops; looks like we're doomed. Roll everything out, disabled,
- // including the legacy state.
- Slog.e(TAG, "Unable to record backup enable state; reverting to disabled: "
- + e.getMessage());
-
- ContentResolver resolver = sInstance.getContext().getContentResolver();
- Settings.Secure.putStringForUser(resolver,
- Settings.Secure.BACKUP_ENABLED, null, userId);
- enableFile.delete();
- stage.delete();
- }
- }
-
- // Enable/disable backups
+ /** User-configurable enabling/disabling of backups. */
public void setBackupEnabled(boolean enable) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"setBackupEnabled");
@@ -2865,7 +2816,7 @@
}
}
- // Enable/disable automatic restore of app data at install time
+ /** Enable/disable automatic restore of app data at install time. */
public void setAutoRestore(boolean doAutoRestore) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"setAutoRestore");
@@ -2884,7 +2835,7 @@
}
}
- // Mark the backup service as having been provisioned
+ /** Mark the backup service as having been provisioned. */
public void setBackupProvisioned(boolean available) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"setBackupProvisioned");
@@ -2893,14 +2844,14 @@
*/
}
- // Report whether the backup mechanism is currently enabled
+ /** Report whether the backup mechanism is currently enabled. */
public boolean isBackupEnabled() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"isBackupEnabled");
return mEnabled; // no need to synchronize just to read it
}
- // Report the name of the currently active transport
+ /** Report the name of the currently active transport. */
public String getCurrentTransport() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"getCurrentTransport");
@@ -2927,7 +2878,7 @@
}
}
- // Report all known, available backup transports
+ /** Report all known, available backup transports by name. */
public String[] listAllTransports() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"listAllTransports");
@@ -2935,12 +2886,14 @@
return mTransportManager.getRegisteredTransportNames();
}
+ /** Report all known, available backup transports by component. */
public ComponentName[] listAllTransportComponents() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"listAllTransportComponents");
return mTransportManager.getRegisteredTransportComponents();
}
+ /** Report all system whitelisted transports. */
public String[] getTransportWhitelist() {
// No permission check, intentionally.
Set<ComponentName> whitelistedComponents = mTransportManager.getTransportWhitelist();
@@ -3039,7 +2992,12 @@
}
}
- /** Selects transport {@code transportName} and returns previous selected transport. */
+ /**
+ * Selects transport {@code transportName} and returns previously selected transport.
+ *
+ * @deprecated Use {@link #selectBackupTransportAsync(ComponentName,
+ * ISelectBackupTransportCallback)} instead.
+ */
@Deprecated
@Nullable
public String selectBackupTransport(String transportName) {
@@ -3058,6 +3016,10 @@
}
}
+ /**
+ * Selects transport {@code transportComponent} asynchronously and notifies {@code listener}
+ * with the result upon completion.
+ */
public void selectBackupTransportAsync(
ComponentName transportComponent, ISelectBackupTransportCallback listener) {
mContext.enforceCallingOrSelfPermission(
@@ -3126,9 +3088,11 @@
}
}
- // Supply the configuration Intent for the given transport. If the name is not one
- // of the available transports, or if the transport does not supply any configuration
- // UI, the method returns null.
+ /**
+ * Supply the configuration intent for the given transport. If the name is not one of the
+ * available transports, or if the transport does not supply any configuration UI, the method
+ * returns {@code null}.
+ */
public Intent getConfigurationIntent(String transportName) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"getConfigurationIntent");
@@ -3169,7 +3133,7 @@
}
}
- // Supply the manage-data intent for the given transport.
+ /** Supply the manage-data intent for the given transport. */
public Intent getDataManagementIntent(String transportName) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"getDataManagementIntent");
@@ -3186,8 +3150,10 @@
}
}
- // Supply the menu label for affordances that fire the manage-data intent
- // for the given transport.
+ /**
+ * Supply the menu label for affordances that fire the manage-data intent for the given
+ * transport.
+ */
public String getDataManagementLabel(String transportName) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"getDataManagementLabel");
@@ -3204,8 +3170,10 @@
}
}
- // Callback: a requested backup agent has been instantiated. This should only
- // be called from the Activity Manager.
+ /**
+ * Callback: a requested backup agent has been instantiated. This should only be called from the
+ * {@link ActivityManager}.
+ */
public void agentConnected(String packageName, IBinder agentBinder) {
synchronized (mAgentConnectLock) {
if (Binder.getCallingUid() == Process.SYSTEM_UID) {
@@ -3221,9 +3189,11 @@
}
}
- // Callback: a backup agent has failed to come up, or has unexpectedly quit.
- // If the agent failed to come up in the first place, the agentBinder argument
- // will be null. This should only be called from the Activity Manager.
+ /**
+ * Callback: a backup agent has failed to come up, or has unexpectedly quit. If the agent failed
+ * to come up in the first place, the agentBinder argument will be {@code null}. This should
+ * only be called from the {@link ActivityManager}.
+ */
public void agentDisconnected(String packageName) {
// TODO: handle backup being interrupted
synchronized (mAgentConnectLock) {
@@ -3238,8 +3208,10 @@
}
}
- // An application being installed will need a restore pass, then the Package Manager
- // will need to be told when the restore is finished.
+ /**
+ * An application being installed will need a restore pass, then the {@link PackageManager} will
+ * need to be told when the restore is finished.
+ */
public void restoreAtInstall(String packageName, int token) {
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
Slog.w(TAG, "Non-system process uid=" + Binder.getCallingUid()
@@ -3283,8 +3255,8 @@
mWakelock.acquire();
OnTaskFinishedListener listener = caller -> {
- mTransportManager.disposeOfTransportClient(transportClient, caller);
- mWakelock.release();
+ mTransportManager.disposeOfTransportClient(transportClient, caller);
+ mWakelock.release();
};
if (MORE_DEBUG) {
@@ -3324,7 +3296,7 @@
}
}
- // Hand off a restore session
+ /** Hand off a restore session. */
public IRestoreSession beginRestoreSession(String packageName, String transport) {
if (DEBUG) {
Slog.v(TAG, "beginRestoreSession: pkg=" + packageName
@@ -3376,6 +3348,7 @@
return mActiveRestoreSession;
}
+ /** Clear the specified restore session. */
public void clearRestoreSession(ActiveRestoreSession currentSession) {
synchronized (this) {
if (currentSession != mActiveRestoreSession) {
@@ -3388,8 +3361,10 @@
}
}
- // Note that a currently-active backup agent has notified us that it has
- // completed the given outstanding asynchronous backup/restore operation.
+ /**
+ * Note that a currently-active backup agent has notified us that it has completed the given
+ * outstanding asynchronous backup/restore operation.
+ */
public void opComplete(int token, long result) {
if (MORE_DEBUG) {
Slog.v(TAG, "opComplete: " + Integer.toHexString(token) + " result=" + result);
@@ -3405,8 +3380,8 @@
mCurrentOperations.delete(token);
} else if (op.state == OP_ACKNOWLEDGED) {
if (DEBUG) {
- Slog.w(TAG, "Received duplicate ack for token=" +
- Integer.toHexString(token));
+ Slog.w(TAG, "Received duplicate ack for token="
+ + Integer.toHexString(token));
}
op = null;
mCurrentOperations.remove(token);
@@ -3427,6 +3402,7 @@
}
}
+ /** Checks if the package is eligible for backup. */
public boolean isAppEligibleForBackup(String packageName) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BACKUP, "isAppEligibleForBackup");
@@ -3448,6 +3424,7 @@
}
}
+ /** Returns the inputted packages that are eligible for backup. */
public String[] filterAppsEligibleForBackup(String[] packages) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BACKUP, "filterAppsEligibleForBackup");
@@ -3474,6 +3451,7 @@
}
}
+ /** Prints service state for 'dumpsys backup'. */
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
@@ -3565,25 +3543,14 @@
pw.println(" " + s);
}
- if (DEBUG_BACKUP_TRACE) {
- synchronized (mBackupTrace) {
- if (!mBackupTrace.isEmpty()) {
- pw.println("Most recent backup trace:");
- for (String s : mBackupTrace) {
- pw.println(" " + s);
- }
- }
- }
- }
-
pw.print("Ancestral: ");
pw.println(Long.toHexString(mAncestralToken));
pw.print("Current: ");
pw.println(Long.toHexString(mCurrentToken));
- int N = mBackupParticipants.size();
+ int numPackages = mBackupParticipants.size();
pw.println("Participants:");
- for (int i = 0; i < N; i++) {
+ for (int i = 0; i < numPackages; i++) {
int uid = mBackupParticipants.keyAt(i);
pw.print(" uid: ");
pw.println(uid);
@@ -3627,4 +3594,71 @@
return mBackupManagerBinder;
}
+ private static boolean backupSettingMigrated(int userId) {
+ File base = new File(Environment.getDataDirectory(), "backup");
+ File enableFile = new File(base, BACKUP_ENABLE_FILE);
+ return enableFile.exists();
+ }
+
+ private static boolean readBackupEnableState(int userId) {
+ File base = new File(Environment.getDataDirectory(), "backup");
+ File enableFile = new File(base, BACKUP_ENABLE_FILE);
+ if (enableFile.exists()) {
+ try (FileInputStream fin = new FileInputStream(enableFile)) {
+ int state = fin.read();
+ return state != 0;
+ } catch (IOException e) {
+ // can't read the file; fall through to assume disabled
+ Slog.e(TAG, "Cannot read enable state; assuming disabled");
+ }
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "isBackupEnabled() => false due to absent settings file");
+ }
+ }
+ return false;
+ }
+
+ private static void writeBackupEnableState(boolean enable, int userId) {
+ File base = new File(Environment.getDataDirectory(), "backup");
+ File enableFile = new File(base, BACKUP_ENABLE_FILE);
+ File stage = new File(base, BACKUP_ENABLE_FILE + "-stage");
+ try (FileOutputStream fout = new FileOutputStream(stage)) {
+ fout.write(enable ? 1 : 0);
+ fout.close();
+ stage.renameTo(enableFile);
+ // will be synced immediately by the try-with-resources call to close()
+ } catch (IOException | RuntimeException e) {
+ // Whoops; looks like we're doomed. Roll everything out, disabled,
+ // including the legacy state.
+ Slog.e(TAG, "Unable to record backup enable state; reverting to disabled: "
+ + e.getMessage());
+
+ ContentResolver resolver = sInstance.getContext().getContentResolver();
+ Settings.Secure.putStringForUser(resolver,
+ Settings.Secure.BACKUP_ENABLED, null, userId);
+ enableFile.delete();
+ stage.delete();
+ }
+ }
+
+ /** Implementation to receive lifecycle event callbacks for system services. */
+ public static final class Lifecycle extends SystemService {
+ public Lifecycle(Context context) {
+ super(context);
+ sInstance = new Trampoline(context);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.BACKUP_SERVICE, sInstance);
+ }
+
+ @Override
+ public void onUnlockUser(int userId) {
+ if (userId == UserHandle.USER_SYSTEM) {
+ sInstance.unlockSystemUser();
+ }
+ }
+ }
}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunker.java b/services/backup/java/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunker.java
new file mode 100644
index 0000000..18011f6
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunker.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking.cdc;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import com.android.server.backup.encryption.chunking.Chunker;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+
+/** Splits a stream of bytes into variable-sized chunks, using content-defined chunking. */
+public class ContentDefinedChunker implements Chunker {
+ private static final int WINDOW_SIZE = 31;
+ private static final byte DEFAULT_OUT_BYTE = (byte) 0;
+
+ private final byte[] mChunkBuffer;
+ private final RabinFingerprint64 mRabinFingerprint64;
+ private final FingerprintMixer mFingerprintMixer;
+ private final BreakpointPredicate mBreakpointPredicate;
+ private final int mMinChunkSize;
+ private final int mMaxChunkSize;
+
+ /**
+ * Constructor.
+ *
+ * @param minChunkSize The minimum size of a chunk. No chunk will be produced of a size smaller
+ * than this except possibly at the very end of the stream.
+ * @param maxChunkSize The maximum size of a chunk. No chunk will be produced of a larger size.
+ * @param rabinFingerprint64 Calculates fingerprints, with which to determine breakpoints.
+ * @param breakpointPredicate Given a Rabin fingerprint, returns whether this ought to be a
+ * breakpoint.
+ */
+ public ContentDefinedChunker(
+ int minChunkSize,
+ int maxChunkSize,
+ RabinFingerprint64 rabinFingerprint64,
+ FingerprintMixer fingerprintMixer,
+ BreakpointPredicate breakpointPredicate) {
+ checkArgument(
+ minChunkSize >= WINDOW_SIZE,
+ "Minimum chunk size must be greater than window size.");
+ checkArgument(
+ maxChunkSize >= minChunkSize,
+ "Maximum chunk size cannot be smaller than minimum chunk size.");
+ mChunkBuffer = new byte[maxChunkSize];
+ mRabinFingerprint64 = rabinFingerprint64;
+ mBreakpointPredicate = breakpointPredicate;
+ mFingerprintMixer = fingerprintMixer;
+ mMinChunkSize = minChunkSize;
+ mMaxChunkSize = maxChunkSize;
+ }
+
+ /**
+ * Breaks the input stream into variable-sized chunks.
+ *
+ * @param inputStream The input bytes to break into chunks.
+ * @param chunkConsumer A function to process each chunk as it's generated.
+ * @throws IOException Thrown if there is an issue reading from the input stream.
+ * @throws GeneralSecurityException Thrown if the {@link ChunkConsumer} throws it.
+ */
+ @Override
+ public void chunkify(InputStream inputStream, ChunkConsumer chunkConsumer)
+ throws IOException, GeneralSecurityException {
+ int chunkLength;
+ int initialReadLength = mMinChunkSize - WINDOW_SIZE;
+
+ // Performance optimization - there is no reason to calculate fingerprints for windows
+ // ending before the minimum chunk size.
+ while ((chunkLength =
+ inputStream.read(mChunkBuffer, /*off=*/ 0, /*len=*/ initialReadLength))
+ != -1) {
+ int b;
+ long fingerprint = 0L;
+
+ while ((b = inputStream.read()) != -1) {
+ byte inByte = (byte) b;
+ byte outByte = getCurrentWindowStartByte(chunkLength);
+ mChunkBuffer[chunkLength++] = inByte;
+
+ fingerprint =
+ mRabinFingerprint64.computeFingerprint64(inByte, outByte, fingerprint);
+
+ if (chunkLength >= mMaxChunkSize
+ || (chunkLength >= mMinChunkSize
+ && mBreakpointPredicate.isBreakpoint(
+ mFingerprintMixer.mix(fingerprint)))) {
+ chunkConsumer.accept(Arrays.copyOf(mChunkBuffer, chunkLength));
+ chunkLength = 0;
+ break;
+ }
+ }
+
+ if (chunkLength > 0) {
+ chunkConsumer.accept(Arrays.copyOf(mChunkBuffer, chunkLength));
+ }
+ }
+ }
+
+ private byte getCurrentWindowStartByte(int chunkLength) {
+ if (chunkLength < mMinChunkSize) {
+ return DEFAULT_OUT_BYTE;
+ } else {
+ return mChunkBuffer[chunkLength - WINDOW_SIZE];
+ }
+ }
+
+ /** Whether the current fingerprint indicates the end of a chunk. */
+ public interface BreakpointPredicate {
+
+ /**
+ * Returns {@code true} if the fingerprint of the last {@code WINDOW_SIZE} bytes indicates
+ * the chunk ought to end at this position.
+ *
+ * @param fingerprint Fingerprint of the last {@code WINDOW_SIZE} bytes.
+ * @return Whether this ought to be a chunk breakpoint.
+ */
+ boolean isBreakpoint(long fingerprint);
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/FingerprintMixer.java b/services/backup/java/com/android/server/backup/encryption/chunking/cdc/FingerprintMixer.java
new file mode 100644
index 0000000..e9f3050
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/encryption/chunking/cdc/FingerprintMixer.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking.cdc;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+
+import javax.crypto.SecretKey;
+
+/**
+ * Helper for mixing fingerprint with key material.
+ *
+ * <p>We do this as otherwise the Rabin fingerprint leaks information about the plaintext. i.e., if
+ * two users have the same file, it will be partitioned by Rabin in the same way, allowing us to
+ * infer that it is the same as another user's file.
+ *
+ * <p>By mixing the fingerprint with the user's secret key, the chunking method is different on a
+ * per key basis. Each application has its own {@link SecretKey}, so we cannot infer that a file is
+ * the same even across multiple applications owned by the same user, never mind across multiple
+ * users.
+ *
+ * <p>Instead of directly mixing the fingerprint with the user's secret, we first securely and
+ * deterministically derive a secondary chunking key. As Rabin is not a cryptographically secure
+ * hash, it might otherwise leak information about the user's secret. This prevents that from
+ * happening.
+ */
+public class FingerprintMixer {
+ public static final int SALT_LENGTH_BYTES = 256 / Byte.SIZE;
+ private static final String DERIVED_KEY_NAME = "RabinFingerprint64Mixer";
+
+ private final long mAddend;
+ private final long mMultiplicand;
+
+ /**
+ * A new instance from a given secret key and salt. Salt must be the same across incremental
+ * backups, or a different chunking strategy will be used each time, defeating the dedup.
+ *
+ * @param secretKey The application-specific secret.
+ * @param salt The salt.
+ * @throws InvalidKeyException If the encoded form of {@code secretKey} is inaccessible.
+ */
+ public FingerprintMixer(SecretKey secretKey, byte[] salt) throws InvalidKeyException {
+ checkArgument(salt.length == SALT_LENGTH_BYTES, "Requires a 256-bit salt.");
+ byte[] keyBytes = secretKey.getEncoded();
+ if (keyBytes == null) {
+ throw new InvalidKeyException("SecretKey must support encoding for FingerprintMixer.");
+ }
+ byte[] derivedKey =
+ Hkdf.hkdf(keyBytes, salt, DERIVED_KEY_NAME.getBytes(StandardCharsets.UTF_8));
+ ByteBuffer buffer = ByteBuffer.wrap(derivedKey);
+ mAddend = buffer.getLong();
+ // Multiplicand must be odd - otherwise we lose some bits of the Rabin fingerprint when
+ // mixing
+ mMultiplicand = buffer.getLong() | 1;
+ }
+
+ /**
+ * Mixes the fingerprint with the derived key material. This is performed by adding part of the
+ * derived key and multiplying by another part of the derived key (which is forced to be odd, so
+ * that the operation is reversible).
+ *
+ * @param fingerprint A 64-bit Rabin fingerprint.
+ * @return The mixed fingerprint.
+ */
+ long mix(long fingerprint) {
+ return ((fingerprint + mAddend) * mMultiplicand);
+ }
+
+ /** The addend part of the derived key. */
+ long getAddend() {
+ return mAddend;
+ }
+
+ /** The multiplicand part of the derived key. */
+ long getMultiplicand() {
+ return mMultiplicand;
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/Hkdf.java b/services/backup/java/com/android/server/backup/encryption/chunking/cdc/Hkdf.java
new file mode 100644
index 0000000..6f4f549
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/encryption/chunking/cdc/Hkdf.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking.cdc;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Secure HKDF utils. Allows client to deterministically derive additional key material from a base
+ * secret. If the derived key material is compromised, this does not in of itself compromise the
+ * root secret.
+ *
+ * <p>TODO(b/116575321): After all code is ported, rename this class to HkdfUtils.
+ */
+public final class Hkdf {
+ private static final byte[] CONSTANT_01 = {0x01};
+ private static final String HmacSHA256 = "HmacSHA256";
+ private static final String AES = "AES";
+
+ /**
+ * Implements HKDF (RFC 5869) with the SHA-256 hash and a 256-bit output key length.
+ *
+ * <p>IMPORTANT: The use or edit of this method requires a security review.
+ *
+ * @param masterKey Master key from which to derive sub-keys.
+ * @param salt A randomly generated 256-bit byte string.
+ * @param data Arbitrary information that is bound to the derived key (i.e., used in its
+ * creation).
+ * @return Raw derived key bytes = HKDF-SHA256(masterKey, salt, data).
+ * @throws InvalidKeyException If the salt can not be used as a valid key.
+ */
+ static byte[] hkdf(byte[] masterKey, byte[] salt, byte[] data) throws InvalidKeyException {
+ checkNotNull(masterKey, "HKDF requires master key to be set.");
+ checkNotNull(salt, "HKDF requires a salt.");
+ checkNotNull(data, "No data provided to HKDF.");
+ return hkdfSha256Expand(hkdfSha256Extract(masterKey, salt), data);
+ }
+
+ private Hkdf() {}
+
+ /**
+ * The HKDF (RFC 5869) extraction function, using the SHA-256 hash function. This function is
+ * used to pre-process the {@code inputKeyMaterial} and mix it with the {@code salt}, producing
+ * output suitable for use with HKDF expansion function (which produces the actual derived key).
+ *
+ * <p>IMPORTANT: The use or edit of this method requires a security review.
+ *
+ * @see #hkdfSha256Expand(byte[], byte[])
+ * @return HMAC-SHA256(salt, inputKeyMaterial) (salt is the "key" for the HMAC)
+ * @throws InvalidKeyException If the salt can not be used as a valid key.
+ */
+ private static byte[] hkdfSha256Extract(byte[] inputKeyMaterial, byte[] salt)
+ throws InvalidKeyException {
+ // Note that the SecretKey encoding format is defined to be RAW, so the encoded form should
+ // be consistent across implementations.
+ Mac sha256;
+ try {
+ sha256 = Mac.getInstance(HmacSHA256);
+ } catch (NoSuchAlgorithmException e) {
+ // This can not happen - HmacSHA256 is supported by the platform.
+ throw new AssertionError(e);
+ }
+ sha256.init(new SecretKeySpec(salt, AES));
+
+ return sha256.doFinal(inputKeyMaterial);
+ }
+
+ /**
+ * Special case of HKDF (RFC 5869) expansion function, using the SHA-256 hash function and
+ * allowing for a maximum output length of 256 bits.
+ *
+ * <p>IMPORTANT: The use or edit of this method requires a security review.
+ *
+ * @param pseudoRandomKey Generated by {@link #hkdfSha256Extract(byte[], byte[])}.
+ * @param info Arbitrary information the derived key should be bound to.
+ * @return Raw derived key bytes = HMAC-SHA256(pseudoRandomKey, info | 0x01).
+ * @throws InvalidKeyException If the salt can not be used as a valid key.
+ */
+ private static byte[] hkdfSha256Expand(byte[] pseudoRandomKey, byte[] info)
+ throws InvalidKeyException {
+ // Note that RFC 5869 computes number of blocks N = ceil(hash length / output length), but
+ // here we only deal with a 256 bit hash up to a 256 bit output, yielding N=1.
+ Mac sha256;
+ try {
+ sha256 = Mac.getInstance(HmacSHA256);
+ } catch (NoSuchAlgorithmException e) {
+ // This can not happen - HmacSHA256 is supported by the platform.
+ throw new AssertionError(e);
+ }
+ sha256.init(new SecretKeySpec(pseudoRandomKey, AES));
+
+ sha256.update(info);
+ sha256.update(CONSTANT_01);
+ return sha256.doFinal();
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpoint.java b/services/backup/java/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpoint.java
new file mode 100644
index 0000000..e867e7c
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpoint.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking.cdc;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import com.android.server.backup.encryption.chunking.cdc.ContentDefinedChunker.BreakpointPredicate;
+
+/**
+ * Function to determine whether a 64-bit fingerprint ought to be a chunk breakpoint.
+ *
+ * <p>This works by checking whether there are at least n leading zeros in the fingerprint. n is
+ * calculated to on average cause a breakpoint after a given number of trials (provided in the
+ * constructor). This allows us to choose a number of trials that gives a desired average chunk
+ * size. This works because the fingerprint is pseudo-randomly distributed.
+ */
+public class IsChunkBreakpoint implements BreakpointPredicate {
+ private final int mLeadingZeros;
+ private final long mBitmask;
+
+ /**
+ * A new instance that causes a breakpoint after a given number of trials on average.
+ *
+ * @param averageNumberOfTrialsUntilBreakpoint The number of trials after which on average to
+ * create a new chunk. If this is not a power of 2, some precision is sacrificed (i.e., on
+ * average, breaks will actually happen after the nearest power of 2 to the average number
+ * of trials passed in).
+ */
+ public IsChunkBreakpoint(long averageNumberOfTrialsUntilBreakpoint) {
+ checkArgument(
+ averageNumberOfTrialsUntilBreakpoint >= 0,
+ "Average number of trials must be non-negative");
+
+ // Want n leading zeros after t trials.
+ // P(leading zeros = n) = 1/2^n
+ // Expected num trials to get n leading zeros = 1/2^-n
+ // t = 1/2^-n
+ // n = log2(t)
+ mLeadingZeros = (int) Math.round(log2(averageNumberOfTrialsUntilBreakpoint));
+ mBitmask = ~(~0L >>> mLeadingZeros);
+ }
+
+ /**
+ * Returns {@code true} if {@code fingerprint} indicates that there should be a chunk
+ * breakpoint.
+ */
+ @Override
+ public boolean isBreakpoint(long fingerprint) {
+ return (fingerprint & mBitmask) == 0;
+ }
+
+ /** Returns the number of leading zeros in the fingerprint that causes a breakpoint. */
+ public int getLeadingZeros() {
+ return mLeadingZeros;
+ }
+
+ /**
+ * Calculates log base 2 of x. Not the most efficient possible implementation, but it's simple,
+ * obviously correct, and is only invoked on object construction.
+ */
+ private static double log2(double x) {
+ return Math.log(x) / Math.log(2);
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64.java b/services/backup/java/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64.java
new file mode 100644
index 0000000..1e14ffa
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking.cdc;
+
+/** Helper to calculate a 64-bit Rabin fingerprint over a 31-byte window. */
+public class RabinFingerprint64 {
+ private static final long DEFAULT_IRREDUCIBLE_POLYNOMIAL_64 = 0x000000000000001BL;
+ private static final int POLYNOMIAL_DEGREE = 64;
+ private static final int SLIDING_WINDOW_SIZE_BYTES = 31;
+
+ private final long mPoly64;
+ // Auxiliary tables to speed up the computation of Rabin fingerprints.
+ private final long[] mTableFP64 = new long[256];
+ private final long[] mTableOutByte = new long[256];
+
+ /**
+ * Constructs a new instance over the given irreducible 64-degree polynomial. It is up to the
+ * caller to determine that the polynomial is irreducible. If it is not the fingerprinting will
+ * not behave as expected.
+ *
+ * @param poly64 The polynomial.
+ */
+ public RabinFingerprint64(long poly64) {
+ mPoly64 = poly64;
+ }
+
+ /** Constructs a new instance using {@code x^64 + x^4 + x + 1} as the irreducible polynomial. */
+ public RabinFingerprint64() {
+ this(DEFAULT_IRREDUCIBLE_POLYNOMIAL_64);
+ computeFingerprintTables64();
+ computeFingerprintTables64Windowed();
+ }
+
+ /**
+ * Computes the fingerprint for the new sliding window given the fingerprint of the previous
+ * sliding window, the byte sliding in, and the byte sliding out.
+ *
+ * @param inChar The new char coming into the sliding window.
+ * @param outChar The left most char sliding out of the window.
+ * @param fingerPrint Fingerprint for previous window.
+ * @return New fingerprint for the new sliding window.
+ */
+ public long computeFingerprint64(byte inChar, byte outChar, long fingerPrint) {
+ return (fingerPrint << 8)
+ ^ (inChar & 0xFF)
+ ^ mTableFP64[(int) (fingerPrint >>> 56)]
+ ^ mTableOutByte[outChar & 0xFF];
+ }
+
+ /** Compute auxiliary tables to speed up the fingerprint computation. */
+ private void computeFingerprintTables64() {
+ long[] degreesRes64 = new long[POLYNOMIAL_DEGREE];
+ degreesRes64[0] = mPoly64;
+ for (int i = 1; i < POLYNOMIAL_DEGREE; i++) {
+ if ((degreesRes64[i - 1] & (1L << 63)) == 0) {
+ degreesRes64[i] = degreesRes64[i - 1] << 1;
+ } else {
+ degreesRes64[i] = (degreesRes64[i - 1] << 1) ^ mPoly64;
+ }
+ }
+ for (int i = 0; i < 256; i++) {
+ int currIndex = i;
+ for (int j = 0; (currIndex > 0) && (j < 8); j++) {
+ if ((currIndex & 0x1) == 1) {
+ mTableFP64[i] ^= degreesRes64[j];
+ }
+ currIndex >>>= 1;
+ }
+ }
+ }
+
+ /**
+ * Compute auxiliary table {@code mTableOutByte} to facilitate the computing of fingerprints for
+ * sliding windows. This table is to take care of the effect on the fingerprint when the
+ * leftmost byte in the window slides out.
+ */
+ private void computeFingerprintTables64Windowed() {
+ // Auxiliary array degsRes64[8] defined by: <code>degsRes64[i] = x^(8 *
+ // SLIDING_WINDOW_SIZE_BYTES + i) mod this.mPoly64.</code>
+ long[] degsRes64 = new long[8];
+ degsRes64[0] = mPoly64;
+ for (int i = 65; i < 8 * (SLIDING_WINDOW_SIZE_BYTES + 1); i++) {
+ if ((degsRes64[(i - 1) % 8] & (1L << 63)) == 0) {
+ degsRes64[i % 8] = degsRes64[(i - 1) % 8] << 1;
+ } else {
+ degsRes64[i % 8] = (degsRes64[(i - 1) % 8] << 1) ^ mPoly64;
+ }
+ }
+ for (int i = 0; i < 256; i++) {
+ int currIndex = i;
+ for (int j = 0; (currIndex > 0) && (j < 8); j++) {
+ if ((currIndex & 0x1) == 1) {
+ mTableOutByte[i] ^= degsRes64[j];
+ }
+ currIndex >>>= 1;
+ }
+ }
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index e108026..755095e 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -45,9 +45,9 @@
import com.android.internal.util.Preconditions;
import com.android.server.EventLogTags;
import com.android.server.backup.BackupAgentTimeoutParameters;
+import com.android.server.backup.BackupManagerService;
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.FullBackupJob;
-import com.android.server.backup.BackupManagerService;
import com.android.server.backup.TransportManager;
import com.android.server.backup.internal.OnTaskFinishedListener;
import com.android.server.backup.internal.Operation;
@@ -599,7 +599,6 @@
cleanUpPipes(enginePipes);
if (currentPackage.applicationInfo != null) {
Slog.i(TAG, "Unbinding agent in " + packageName);
- backupManagerService.addBackupTrace("unbinding " + packageName);
try {
backupManagerService.getActivityManager().unbindBackupAgent(
currentPackage.applicationInfo);
@@ -709,7 +708,6 @@
try {
backupManagerService.prepareOperationTimeout(
mCurrentOpToken, fullBackupAgentTimeoutMillis, this, OP_TYPE_BACKUP_WAIT);
- backupManagerService.addBackupTrace("preflighting");
if (MORE_DEBUG) {
Slog.d(TAG, "Preflighting full payload of " + pkg.packageName);
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 888ad1d..6174300 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -3,6 +3,7 @@
aidl: {
include_dirs: [
+ "frameworks/base/cmds/idmap2/idmap2d/aidl",
"frameworks/native/aidl/binder",
"frameworks/native/cmds/dumpstate/binder",
"system/core/storaged/binder",
@@ -13,6 +14,7 @@
srcs: [
"java/**/*.java",
":dumpstate_aidl",
+ ":idmap2_aidl",
":netd_aidl",
":netd_metrics_aidl",
":installd_aidl",
diff --git a/services/core/java/com/android/server/AbstractMasterSystemService.java b/services/core/java/com/android/server/AbstractMasterSystemService.java
index c955daf..9c1e3cd 100644
--- a/services/core/java/com/android/server/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/AbstractMasterSystemService.java
@@ -62,13 +62,14 @@
* <p>See {@code com.android.server.autofill.AutofillManagerService} for a concrete
* (no pun intended) example of how to use it.
*
+ * @param <M> "master" service class.
* @param <S> "real" service class.
*
* @hide
*/
// TODO(b/117779333): improve javadoc above instead of using Autofill as an example
-public abstract class AbstractMasterSystemService<S extends AbstractPerUserSystemService<S>>
- extends SystemService {
+public abstract class AbstractMasterSystemService<M extends AbstractMasterSystemService<M, S>,
+ S extends AbstractPerUserSystemService<S, M>> extends SystemService {
/**
* Log tag
@@ -244,7 +245,7 @@
*/
@GuardedBy("mLock")
@Nullable
- protected S peekServiceForUserLocked(int userId) {
+ protected S peekServiceForUserLocked(@UserIdInt int userId) {
final int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, false, false, null, null);
return mServicesCache.get(resolvedUserId);
@@ -254,7 +255,7 @@
* Updates a cached service for a given user.
*/
@GuardedBy("mLock")
- protected void updateCachedServiceLocked(int userId) {
+ protected void updateCachedServiceLocked(@UserIdInt int userId) {
updateCachedServiceLocked(userId, isDisabledLocked(userId));
}
@@ -262,7 +263,7 @@
* Checks whether the service is disabled (through {@link UserManager} restrictions) for the
* given user.
*/
- protected boolean isDisabledLocked(int userId) {
+ protected boolean isDisabledLocked(@UserIdInt int userId) {
return mDisabledUsers == null ? false : mDisabledUsers.get(userId);
}
@@ -274,7 +275,7 @@
* @return service for the user.
*/
@GuardedBy("mLock")
- protected S updateCachedServiceLocked(int userId, boolean disabled) {
+ protected S updateCachedServiceLocked(@UserIdInt int userId, boolean disabled) {
final S service = getServiceForUserLocked(userId);
if (service != null) {
service.updateLocked(disabled);
@@ -304,7 +305,7 @@
* <p>By default doesn't do anything, but can be overridden by subclasses.
*/
@SuppressWarnings("unused")
- protected void onServiceEnabledLocked(S service, @UserIdInt int userId) {
+ protected void onServiceEnabledLocked(@NonNull S service, @UserIdInt int userId) {
}
/**
@@ -314,15 +315,23 @@
*/
@GuardedBy("mLock")
@NonNull
- protected S removeCachedServiceLocked(@UserIdInt int userId) {
+ private S removeCachedServiceLocked(@UserIdInt int userId) {
final S service = peekServiceForUserLocked(userId);
if (service != null) {
mServicesCache.delete(userId);
+ onServiceRemoved(service, userId);
}
return service;
}
/**
+ * Called after the service is removed from the cache.
+ */
+ @SuppressWarnings("unused")
+ protected void onServiceRemoved(@NonNull S service, @UserIdInt int userId) {
+ }
+
+ /**
* Visits all services in the cache.
*/
@GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/AbstractPerUserSystemService.java b/services/core/java/com/android/server/AbstractPerUserSystemService.java
index 201abe6..b37888f 100644
--- a/services/core/java/com/android/server/AbstractPerUserSystemService.java
+++ b/services/core/java/com/android/server/AbstractPerUserSystemService.java
@@ -41,17 +41,19 @@
* Companion for {@link AbstractMasterSystemService}, it's the base class for the "real" service
* implementation.
*
- * @param <S> itself
+ * @param <M> "master" service class.
+ * @param <S> "real" service class.
*
* @hide
*/
-public abstract class AbstractPerUserSystemService<S extends AbstractPerUserSystemService<S>> {
+public abstract class AbstractPerUserSystemService<S extends AbstractPerUserSystemService<S, M>,
+ M extends AbstractMasterSystemService<M, S>> {
protected final @UserIdInt int mUserId;
protected final Object mLock;
protected final String mTag = getClass().getSimpleName();
- protected final AbstractMasterSystemService<S> mMaster;
+ protected final M mMaster;
/**
* Whether service was disabled for user due to {@link UserManager} restrictions.
@@ -68,8 +70,8 @@
@GuardedBy("mLock")
private ServiceInfo mServiceInfo;
- protected AbstractPerUserSystemService(@NonNull AbstractMasterSystemService<S> master,
- @NonNull Object lock, @UserIdInt int userId) {
+ protected AbstractPerUserSystemService(@NonNull M master, @NonNull Object lock,
+ @UserIdInt int userId) {
mMaster = master;
mLock = lock;
mUserId = userId;
@@ -167,7 +169,7 @@
@GuardedBy("mLock")
protected final int getServiceUidLocked() {
if (mServiceInfo == null) {
- Slog.w(mTag, "getServiceUidLocked(): no mServiceInfo");
+ if (mMaster.verbose) Slog.v(mTag, "getServiceUidLocked(): no mServiceInfo");
return Process.INVALID_UID;
}
return mServiceInfo.applicationInfo.uid;
@@ -267,8 +269,18 @@
@GuardedBy("mLock")
protected void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) {
pw.print(prefix); pw.print("User: "); pw.println(mUserId);
- pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled);
+ pw.print(prefix); pw.print("Disabled by UserManager: "); pw.println(mDisabled);
pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete);
- pw.print(prefix); pw.print("Service name: "); pw.println(getComponentNameFromSettings());
+ if (mServiceInfo != null) {
+ pw.print(prefix); pw.print("Service UID: ");
+ pw.println(mServiceInfo.applicationInfo.uid);
+ }
+ final String componentName = getComponentNameFromSettings();
+ if (componentName != null) {
+ pw.print(prefix); pw.print("Service name: ");
+ pw.println(componentName);
+ } else {
+ pw.println("No service package set");
+ }
}
}
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index 5814064..356a4da 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -25,6 +25,8 @@
import static android.app.AppOpsManager.UID_STATE_PERSISTENT;
import static android.app.AppOpsManager.UID_STATE_TOP;
import static android.app.AppOpsManager._NUM_UID_STATE;
+import static android.app.AppOpsManager.modeToName;
+import static android.app.AppOpsManager.opToName;
import android.Manifest;
import android.annotation.NonNull;
@@ -80,6 +82,7 @@
import android.util.TimeUtils;
import android.util.Xml;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IAppOpsActiveCallback;
import com.android.internal.app.IAppOpsCallback;
@@ -219,6 +222,7 @@
SparseIntArray mProfileOwners;
+ @GuardedBy("this")
private CheckOpsDelegate mCheckOpsDelegate;
/**
@@ -877,6 +881,9 @@
}
private ArrayList<AppOpsManager.OpEntry> collectOps(SparseIntArray uidOps, int[] ops) {
+ if (uidOps == null) {
+ return null;
+ }
ArrayList<AppOpsManager.OpEntry> resOps = null;
if (ops == null) {
resOps = new ArrayList<>();
@@ -1131,6 +1138,11 @@
@Override
public void setUidMode(int code, int uid, int mode) {
+ if (DEBUG) {
+ Slog.i(TAG, "uid " + uid + " OP_" + opToName(code) + " := " + modeToName(mode)
+ + " by uid " + Binder.getCallingUid());
+ }
+
enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
verifyIncomingOp(code);
code = AppOpsManager.opToSwitch(code);
@@ -1589,24 +1601,28 @@
public int checkOperation(int code, int uid, String packageName) {
final CheckOpsDelegate delegate;
synchronized (this) {
- if (mCheckOpsDelegate == null) {
- return checkOperationImpl(code, uid, packageName);
- }
delegate = mCheckOpsDelegate;
}
+ if (delegate == null) {
+ return checkOperationImpl(code, uid, packageName);
+ }
return delegate.checkOperation(code, uid, packageName,
AppOpsService.this::checkOperationImpl);
}
private int checkOperationImpl(int code, int uid, String packageName) {
+ verifyIncomingUid(uid);
+ verifyIncomingOp(code);
+ String resolvedPackageName = resolvePackageName(uid, packageName);
+ if (resolvedPackageName == null) {
+ return AppOpsManager.MODE_IGNORED;
+ }
+ return checkOperationUnchecked(code, uid, resolvedPackageName);
+ }
+
+ private int checkOperationUnchecked(int code, int uid, String packageName) {
synchronized (this) {
- verifyIncomingUid(uid);
- verifyIncomingOp(code);
- String resolvedPackageName = resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return AppOpsManager.MODE_IGNORED;
- }
- if (isOpRestrictedLocked(uid, code, resolvedPackageName)) {
+ if (isOpRestrictedLocked(uid, code, packageName)) {
return AppOpsManager.MODE_IGNORED;
}
code = AppOpsManager.opToSwitch(code);
@@ -1615,7 +1631,7 @@
&& uidState.opModes.indexOfKey(code) >= 0) {
return uidState.opModes.get(code);
}
- Op op = getOpLocked(code, uid, resolvedPackageName, false, true, false);
+ Op op = getOpLocked(code, uid, packageName, false, true, false);
if (op == null) {
return AppOpsManager.opToDefaultMode(code);
}
@@ -1627,31 +1643,31 @@
public int checkAudioOperation(int code, int usage, int uid, String packageName) {
final CheckOpsDelegate delegate;
synchronized (this) {
- if (mCheckOpsDelegate == null) {
- return checkAudioOperationImpl(code, usage, uid, packageName);
- }
delegate = mCheckOpsDelegate;
}
+ if (delegate == null) {
+ return checkAudioOperationImpl(code, usage, uid, packageName);
+ }
return delegate.checkAudioOperation(code, usage, uid, packageName,
AppOpsService.this::checkAudioOperationImpl);
}
private int checkAudioOperationImpl(int code, int usage, int uid, String packageName) {
+ boolean suspended;
+ try {
+ suspended = isPackageSuspendedForUser(packageName, uid);
+ } catch (IllegalArgumentException ex) {
+ // Package not found.
+ suspended = false;
+ }
+
+ if (suspended) {
+ Slog.i(TAG, "Audio disabled for suspended package=" + packageName
+ + " for uid=" + uid);
+ return AppOpsManager.MODE_IGNORED;
+ }
+
synchronized (this) {
- boolean suspended;
- try {
- suspended = isPackageSuspendedForUser(packageName, uid);
- } catch (IllegalArgumentException ex) {
- // Package not found.
- suspended = false;
- }
-
- if (suspended) {
- Slog.i(TAG, "Audio disabled for suspended package=" + packageName
- + " for uid=" + uid);
- return AppOpsManager.MODE_IGNORED;
- }
-
final int mode = checkRestrictionLocked(code, usage, uid, packageName);
if (mode != AppOpsManager.MODE_ALLOWED) {
return mode;
@@ -1754,11 +1770,11 @@
public int noteOperation(int code, int uid, String packageName) {
final CheckOpsDelegate delegate;
synchronized (this) {
- if (mCheckOpsDelegate == null) {
- return noteOperationImpl(code, uid, packageName);
- }
delegate = mCheckOpsDelegate;
}
+ if (delegate == null) {
+ return noteOperationImpl(code, uid, packageName);
+ }
return delegate.noteOperation(code, uid, packageName,
AppOpsService.this::noteOperationImpl);
}
@@ -3024,11 +3040,21 @@
return res;
}
- List<AppOpsManager.PackageOps> ops;
+ List<AppOpsManager.PackageOps> ops = new ArrayList<>();
if (shell.packageName != null) {
- ops = shell.mInterface.getOpsForPackage(
+ // Uid mode overrides package mode, so make sure it's also reported
+ List<AppOpsManager.PackageOps> r = shell.mInterface.getUidOps(
+ shell.packageUid,
+ shell.op != AppOpsManager.OP_NONE ? new int[]{shell.op} : null);
+ if (r != null) {
+ ops.addAll(r);
+ }
+ r = shell.mInterface.getOpsForPackage(
shell.packageUid, shell.packageName,
shell.op != AppOpsManager.OP_NONE ? new int[]{shell.op} : null);
+ if (r != null) {
+ ops.addAll(r);
+ }
} else {
ops = shell.mInterface.getUidOps(
shell.nonpackageUid,
@@ -3044,7 +3070,11 @@
}
final long now = System.currentTimeMillis();
for (int i=0; i<ops.size(); i++) {
- List<AppOpsManager.OpEntry> entries = ops.get(i).getOps();
+ AppOpsManager.PackageOps packageOps = ops.get(i);
+ if (packageOps.getPackageName() == null) {
+ pw.print("Uid mode: ");
+ }
+ List<AppOpsManager.OpEntry> entries = packageOps.getOps();
for (int j=0; j<entries.size(); j++) {
AppOpsManager.OpEntry ent = entries.get(j);
pw.print(AppOpsManager.opToName(ent.getOp()));
diff --git a/services/core/java/com/android/server/ExtconStateObserver.java b/services/core/java/com/android/server/ExtconStateObserver.java
new file mode 100644
index 0000000..6b561c7
--- /dev/null
+++ b/services/core/java/com/android/server/ExtconStateObserver.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.FileUtils;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * A specialized ExtconUEventObserver that on receiving a {@link UEvent} calls {@link
+ * #updateState(ExtconInfo, String, S)} with the value of{@link #parseState(ExtconInfo, String)}.
+ *
+ * @param <S> the type of state to parse and update
+ * @hide
+ */
+public abstract class ExtconStateObserver<S> extends ExtconUEventObserver {
+ private static final String TAG = "ExtconStateObserver";
+ private static final boolean LOG = false;
+
+ /**
+ * Parses the current state from the state file for {@code extconInfo} and calls {@link
+ * #updateState(ExtconInfo, String, Object)}
+ *
+ * @param extconInfo the extconInfo to update state for
+ * @see #parseState(ExtconInfo, String)
+ * @see ExtconInfo#getStatePath()
+ */
+ public void updateStateFromFile(ExtconInfo extconInfo) {
+ String statePath = extconInfo.getStatePath();
+ try {
+ S state =
+ parseState(
+ extconInfo,
+ FileUtils.readTextFile(new File(statePath), 0, null).trim());
+ if (state != null) {
+ updateState(extconInfo, extconInfo.getName(), state);
+ }
+ } catch (FileNotFoundException e) {
+ Slog.w(TAG, statePath + " not found while attempting to determine initial state", e);
+ } catch (IOException e) {
+ Slog.e(
+ TAG,
+ "Error reading " + statePath + " while attempting to determine initial state ",
+ e);
+ }
+ }
+
+ @Override
+ public void onUEvent(ExtconInfo extconInfo, UEvent event) {
+ if (LOG) Slog.d(TAG, extconInfo.getName() + " UEVENT: " + event);
+ String name = event.get("NAME");
+ S state = parseState(extconInfo, event.get("STATE"));
+ if (state != null) {
+ updateState(extconInfo, name, state);
+ }
+ }
+
+ /**
+ * Subclasses of ExtconStateObserver should override this method update state for {@code
+ * exconInfo} from an {@code UEvent}.
+ *
+ * @param extconInfo the external connection
+ * @param eventName the {@code NAME} of the {@code UEvent}
+ * @param state the{@code STATE} as parsed by {@link #parseState(ExtconInfo, String)}.
+ */
+ public abstract void updateState(ExtconInfo extconInfo, String eventName, @NonNull S state);
+
+ /**
+ * Subclasses of ExtconStateObserver should override this method to parse the {@code STATE} from
+ * an UEvent.
+ *
+ * @param extconInfo that matches the {@code DEVPATH} of {@code event}
+ * @param state the {@code STATE} from a {@code UEvent}.
+ * @return the parsed state. Return null if the state can not be parsed.
+ */
+ @Nullable
+ public abstract S parseState(ExtconInfo extconInfo, String state);
+}
diff --git a/services/core/java/com/android/server/ExtconUEventObserver.java b/services/core/java/com/android/server/ExtconUEventObserver.java
new file mode 100644
index 0000000..b3084f5
--- /dev/null
+++ b/services/core/java/com/android/server/ExtconUEventObserver.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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;
+
+import android.annotation.Nullable;
+import android.os.UEventObserver;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * A specialized UEventObserver that receives UEvents from the kernel for devices in the {@code
+ * /sys/class/extcon}. directory
+ *
+ * <p>Subclass ExtconUEventObserver, implementing {@link #onUEvent(ExtconInfo, UEvent)}, then call
+ * startObserving() with a ExtconInfo to observe. The UEvent thread will then call your onUEvent()
+ * method when a UEvent occurs that matches the path of your ExtconInfos.
+ *
+ * <p>Call stopObserving() to stop receiving UEvents.
+ *
+ * <p>There is only one UEvent thread per process, even if that process has multiple UEventObserver
+ * subclass instances. The UEvent thread starts when the startObserving() is called for the first
+ * time in that process. Once started the UEvent thread will not stop (although it can stop
+ * notifying UEventObserver's via stopObserving()).
+ *
+ * <p>
+ *
+ * @hide
+ */
+public abstract class ExtconUEventObserver extends UEventObserver {
+ private static final String TAG = "ExtconUEventObserver";
+ private static final boolean LOG = false;
+ private final Map<String, ExtconInfo> mExtconInfos = new ArrayMap<>();
+
+ @Override
+ public final void onUEvent(UEvent event) {
+ String devPath = event.get("DEVPATH");
+ ExtconInfo info = mExtconInfos.get(devPath);
+ if (info != null) {
+ onUEvent(info, event);
+ } else {
+ Slog.w(TAG, "No match found for DEVPATH of " + event + " in " + mExtconInfos);
+ }
+ }
+
+ /**
+ * Subclasses of ExtconUEventObserver should override this method to handle UEvents.
+ *
+ * @param extconInfo that matches the {@code DEVPATH} of {@code event}
+ * @param event the event
+ */
+ protected abstract void onUEvent(ExtconInfo extconInfo, UEvent event);
+
+ /** Starts observing {@link ExtconInfo#getDevicePath()}. */
+ public void startObserving(ExtconInfo extconInfo) {
+ mExtconInfos.put(extconInfo.getDevicePath(), extconInfo);
+ if (LOG) Slog.v(TAG, "Observing " + extconInfo.getDevicePath());
+ startObserving("DEVPATH=" + extconInfo.getDevicePath());
+ }
+
+ /** An External Connection to watch. */
+ public static final class ExtconInfo {
+ private static final String TAG = "ExtconInfo";
+
+ private final String mName;
+
+ public ExtconInfo(String name) {
+ mName = name;
+ }
+
+ /** The name of the external connection */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * The path to the device for this external connection.
+ *
+ * <p><b>NOTE</b> getting this path involves resolving a symlink.
+ *
+ * @return the device path, or null if it not found.
+ */
+ @Nullable
+ public String getDevicePath() {
+ try {
+ String extconPath = String.format(Locale.US, "/sys/class/extcon/%s", mName);
+ File devPath = new File(extconPath);
+ if (devPath.exists()) {
+ String canonicalPath = devPath.getCanonicalPath();
+ int start = canonicalPath.indexOf("/devices");
+ return canonicalPath.substring(start);
+ }
+ return null;
+ } catch (IOException e) {
+ Slog.e(TAG, "Could not get the extcon device path for " + mName, e);
+ return null;
+ }
+ }
+
+ /** The path to the state file */
+ public String getStatePath() {
+ return String.format(Locale.US, "/sys/class/extcon/%s/state", mName);
+ }
+ }
+
+ /** Does the {@link /sys/class/extcon} directory exist */
+ public static boolean extconExists() {
+ File extconDir = new File("/sys/class/extcon");
+ return extconDir.exists() && extconDir.isDirectory();
+ }
+}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 78e82b6..923ac00 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -186,8 +186,6 @@
private static final boolean ENABLE_ISOLATED_STORAGE = SystemProperties
.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false);
- private static final String SHARED_SANDBOX_ID_PREFIX = "shared:";
-
public static class Lifecycle extends SystemService {
private StorageManagerService mStorageManagerService;
@@ -1501,7 +1499,8 @@
}
private static String getSandboxId(String packageName, String sharedUserId) {
- return sharedUserId == null ? packageName : SHARED_SANDBOX_ID_PREFIX + sharedUserId;
+ return sharedUserId == null
+ ? packageName : StorageManager.SHARED_SANDBOX_PREFIX + sharedUserId;
}
private void connect() {
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index cb9d4c6..1b1e6ad 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -230,10 +230,7 @@
|| pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
mWatch = pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
- final int defaultNightMode = res.getInteger(
- com.android.internal.R.integer.config_defaultNightMode);
- mNightMode = Settings.Secure.getInt(context.getContentResolver(),
- Settings.Secure.UI_NIGHT_MODE, defaultNightMode);
+ updateNightModeFromSettings(context, res, UserHandle.getCallingUserId());
// Update the initial, static configurations.
SystemServerInitThreadPool.get().submit(() -> {
@@ -245,6 +242,29 @@
}, TAG + ".onStart");
publishBinderService(Context.UI_MODE_SERVICE, mService);
publishLocalService(UiModeManagerInternal.class, mLocalService);
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_USER_SWITCHED);
+ context.registerReceiver(new UserSwitchedReceiver(), filter, null, mHandler);
+ }
+
+ /**
+ * Updates the night mode setting in Settings.Global and returns if the value was successfully
+ * changed.
+ * @param context A valid context
+ * @param res A valid resource object
+ * @param userId The user to update the setting for
+ * @return True if the new value is different from the old value. False otherwise.
+ */
+ private boolean updateNightModeFromSettings(Context context, Resources res, int userId) {
+ final int defaultNightMode = res.getInteger(
+ com.android.internal.R.integer.config_defaultNightMode);
+ int oldNightMode = mNightMode;
+ mNightMode = Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.UI_NIGHT_MODE, defaultNightMode, userId);
+
+ // false if night mode stayed the same, true otherwise.
+ return !(oldNightMode == mNightMode);
}
private final IUiModeManager.Stub mService = new IUiModeManager.Stub() {
@@ -315,12 +335,13 @@
throw new IllegalArgumentException("Unknown mode: " + mode);
}
+ final int user = UserHandle.getCallingUserId();
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
if (mNightMode != mode) {
- Settings.Secure.putInt(getContext().getContentResolver(),
- Settings.Secure.UI_NIGHT_MODE, mode);
+ Settings.Secure.putIntForUser(getContext().getContentResolver(),
+ Settings.Secure.UI_NIGHT_MODE, mode, user);
mNightMode = mode;
updateLocked(0, 0);
}
@@ -860,4 +881,18 @@
}
}
}
+
+ private final class UserSwitchedReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ synchronized (mLock) {
+ final int currentId = intent.getIntExtra(
+ Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM);
+ // only update if the value is actually changed
+ if (updateNightModeFromSettings(context, context.getResources(), currentId)) {
+ updateLocked(0, 0);
+ }
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index a33ac70..c660cc6 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -192,10 +192,10 @@
public void stopForegroundServicesForUidPackage(final int uid, final String packageName) {
synchronized (mAm) {
final ServiceMap smap = getServiceMapLocked(UserHandle.getUserId(uid));
- final int N = smap.mServicesByName.size();
+ final int N = smap.mServicesByInstanceName.size();
final ArrayList<ServiceRecord> toStop = new ArrayList<>(N);
for (int i = 0; i < N; i++) {
- final ServiceRecord r = smap.mServicesByName.valueAt(i);
+ final ServiceRecord r = smap.mServicesByInstanceName.valueAt(i);
if (uid == r.serviceInfo.applicationInfo.uid
|| packageName.equals(r.serviceInfo.packageName)) {
if (r.isForeground) {
@@ -246,7 +246,7 @@
*/
final class ServiceMap extends Handler {
final int mUserId;
- final ArrayMap<ComponentName, ServiceRecord> mServicesByName = new ArrayMap<>();
+ final ArrayMap<ComponentName, ServiceRecord> mServicesByInstanceName = new ArrayMap<>();
final ArrayMap<Intent.FilterComparison, ServiceRecord> mServicesByIntent = new ArrayMap<>();
final ArrayList<ServiceRecord> mDelayedStartList = new ArrayList<>();
@@ -368,7 +368,7 @@
// TODO: Deal with global services
if (DEBUG_MU)
Slog.v(TAG_MU, "getServiceByNameLocked(" + name + "), callingUser = " + callingUser);
- return getServiceMapLocked(callingUser).mServicesByName.get(name);
+ return getServiceMapLocked(callingUser).mServicesByInstanceName.get(name);
}
boolean hasBackgroundServicesLocked(int callingUser) {
@@ -386,7 +386,7 @@
}
ArrayMap<ComponentName, ServiceRecord> getServicesLocked(int callingUser) {
- return getServiceMapLocked(callingUser).mServicesByName;
+ return getServiceMapLocked(callingUser).mServicesByInstanceName;
}
private boolean appRestrictedAnyInBackground(final int uid, final String packageName) {
@@ -416,7 +416,7 @@
}
ServiceLookupResult res =
- retrieveServiceLocked(service, resolvedType, callingPackage,
+ retrieveServiceLocked(service, null, resolvedType, callingPackage,
callingPid, callingUid, userId, true, callerFg, false, false);
if (res == null) {
return null;
@@ -444,7 +444,7 @@
boolean forcedStandby = false;
if (bgLaunch && appRestrictedAnyInBackground(r.appInfo.uid, r.packageName)) {
if (DEBUG_FOREGROUND_SERVICE) {
- Slog.d(TAG, "Forcing bg-only service start only for " + r.shortName
+ Slog.d(TAG, "Forcing bg-only service start only for " + r.shortInstanceName
+ " : bgLaunch=" + bgLaunch + " callerFg=" + callerFg);
}
forcedStandby = true;
@@ -464,7 +464,7 @@
// Not allowed, fall back to normal start service, failing siliently
// if background check restricts that.
Slog.w(TAG, "startForegroundService not allowed due to app op: service "
- + service + " to " + r.name.flattenToShortString()
+ + service + " to " + r.shortInstanceName
+ " from pid=" + callingPid + " uid=" + callingUid
+ " pkg=" + callingPackage);
fgRequired = false;
@@ -484,7 +484,7 @@
r.appInfo.targetSdkVersion, callingPid, false, false, forcedStandby);
if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
Slog.w(TAG, "Background start not allowed: service "
- + service + " to " + r.name.flattenToShortString()
+ + service + " to " + r.shortInstanceName
+ " from pid=" + callingPid + " uid=" + callingUid
+ " pkg=" + callingPackage + " startFg?=" + fgRequired);
if (allowed == ActivityManager.APP_START_MODE_DELAYED || forceSilentAbort) {
@@ -677,6 +677,8 @@
stracker.setStarted(true, mAm.mProcessStats.getMemFactorLocked(), r.lastActivity);
}
r.callStart = false;
+ StatsLog.write(StatsLog.SERVICE_STATE_CHANGED, r.appInfo.uid, r.name.getPackageName(),
+ r.name.getClassName(), StatsLog.SERVICE_STATE_CHANGED__STATE__START);
synchronized (r.stats.getBatteryStats()) {
r.stats.startRunningLocked();
}
@@ -715,6 +717,9 @@
service.delayedStop = true;
return;
}
+ StatsLog.write(StatsLog.SERVICE_STATE_CHANGED, service.appInfo.uid,
+ service.name.getPackageName(), service.name.getClassName(),
+ StatsLog.SERVICE_STATE_CHANGED__STATE__STOP);
synchronized (service.stats.getBatteryStats()) {
service.stats.stopRunningLocked();
}
@@ -741,7 +746,7 @@
}
// If this service is active, make sure it is stopped.
- ServiceLookupResult r = retrieveServiceLocked(service, resolvedType, null,
+ ServiceLookupResult r = retrieveServiceLocked(service, null, resolvedType, null,
Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, false, false);
if (r != null) {
if (r.record != null) {
@@ -765,8 +770,8 @@
ServiceMap services = mServiceMap.get(UserHandle.getUserId(uid));
ArrayList<ServiceRecord> stopping = null;
if (services != null) {
- for (int i=services.mServicesByName.size()-1; i>=0; i--) {
- ServiceRecord service = services.mServicesByName.valueAt(i);
+ for (int i = services.mServicesByInstanceName.size() - 1; i >= 0; i--) {
+ ServiceRecord service = services.mServicesByInstanceName.valueAt(i);
if (service.appInfo.uid == uid && service.startRequested) {
if (mAm.getAppStartModeLocked(service.appInfo.uid, service.packageName,
service.appInfo.targetSdkVersion, -1, false, false, false)
@@ -774,7 +779,7 @@
if (stopping == null) {
stopping = new ArrayList<>();
}
- String compName = service.name.flattenToShortString();
+ String compName = service.shortInstanceName;
EventLogTags.writeAmStopIdleService(service.appInfo.uid, compName);
StringBuilder sb = new StringBuilder(64);
sb.append("Stopping service due to app idle: ");
@@ -801,7 +806,7 @@
}
IBinder peekServiceLocked(Intent service, String resolvedType, String callingPackage) {
- ServiceLookupResult r = retrieveServiceLocked(service, resolvedType, callingPackage,
+ ServiceLookupResult r = retrieveServiceLocked(service, null, resolvedType, callingPackage,
Binder.getCallingPid(), Binder.getCallingUid(),
UserHandle.getCallingUserId(), false, false, false, false);
@@ -856,6 +861,8 @@
}
}
+ StatsLog.write(StatsLog.SERVICE_STATE_CHANGED, r.appInfo.uid, r.name.getPackageName(),
+ r.name.getClassName(), StatsLog.SERVICE_STATE_CHANGED__STATE__STOP);
synchronized (r.stats.getBatteryStats()) {
r.stats.stopRunningLocked();
}
@@ -1228,7 +1235,7 @@
case AppOpsManager.MODE_IGNORED:
// Whoops, silently ignore this.
Slog.w(TAG, "Service.startForeground() not allowed due to app op: service "
- + r.shortName);
+ + r.shortInstanceName);
ignoreForeground = true;
break;
default:
@@ -1239,7 +1246,7 @@
appRestrictedAnyInBackground(r.appInfo.uid, r.packageName)) {
Slog.w(TAG,
"Service.startForeground() not allowed due to bg restriction: service "
- + r.shortName);
+ + r.shortInstanceName);
// Back off of any foreground expectations around this service, since we've
// just turned down its fg request.
updateServiceForegroundLocked(r.app, false);
@@ -1293,7 +1300,7 @@
AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName,
true);
StatsLog.write(StatsLog.FOREGROUND_SERVICE_STATE_CHANGED,
- r.appInfo.uid, r.shortName,
+ r.appInfo.uid, r.shortInstanceName,
StatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER);
mAm.updateForegroundServiceUsageStats(r.name, r.userId, true);
}
@@ -1343,7 +1350,7 @@
AppOpsManager.getToken(mAm.mAppOpsService),
AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName);
StatsLog.write(StatsLog.FOREGROUND_SERVICE_STATE_CHANGED,
- r.appInfo.uid, r.shortName,
+ r.appInfo.uid, r.shortInstanceName,
StatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT);
mAm.updateForegroundServiceUsageStats(r.name, r.userId, false);
if (r.app != null) {
@@ -1373,8 +1380,8 @@
// due the other service.
ServiceMap sm = getServiceMapLocked(r.userId);
if (sm != null) {
- for (int i = sm.mServicesByName.size()-1; i >= 0; i--) {
- ServiceRecord other = sm.mServicesByName.valueAt(i);
+ for (int i = sm.mServicesByInstanceName.size() - 1; i >= 0; i--) {
+ ServiceRecord other = sm.mServicesByInstanceName.valueAt(i);
if (other != r && other.foregroundId == r.foregroundId
&& other.packageName.equals(r.packageName)) {
// Found one! Abort the cancel.
@@ -1466,7 +1473,8 @@
int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
String resolvedType, final IServiceConnection connection, int flags,
- String callingPackage, final int userId) throws TransactionTooLargeException {
+ String instanceName, String callingPackage, final int userId)
+ throws TransactionTooLargeException {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service
+ " type=" + resolvedType + " conn=" + connection.asBinder()
+ " flags=0x" + Integer.toHexString(flags));
@@ -1530,8 +1538,9 @@
final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0;
ServiceLookupResult res =
- retrieveServiceLocked(service, resolvedType, callingPackage, Binder.getCallingPid(),
- Binder.getCallingUid(), userId, true, callerFg, isBindExternal, allowInstant);
+ retrieveServiceLocked(service, instanceName, resolvedType, callingPackage,
+ Binder.getCallingPid(), Binder.getCallingUid(), userId, true,
+ callerFg, isBindExternal, allowInstant);
if (res == null) {
return 0;
}
@@ -1637,7 +1646,7 @@
mAm.startAssociationLocked(callerApp.uid, callerApp.processName,
callerApp.getCurProcState(), s.appInfo.uid, s.appInfo.longVersionCode,
- s.name, s.processName);
+ s.instanceName, s.processName);
// Once the apps have become associated, if one of them is caller is ephemeral
// the target app should now be able to see the calling app
mAm.grantEphemeralAccessLocked(callerApp.userId, service,
@@ -1709,7 +1718,7 @@
try {
c.conn.connected(s.name, b.intent.binder, false);
} catch (Exception e) {
- Slog.w(TAG, "Failure sending service " + s.shortName
+ Slog.w(TAG, "Failure sending service " + s.shortInstanceName
+ " to connection " + c.conn.asBinder()
+ " (in " + c.binding.client.processName + ")", e);
}
@@ -1763,9 +1772,9 @@
try {
c.conn.connected(r.name, service, false);
} catch (Exception e) {
- Slog.w(TAG, "Failure sending service " + r.name +
- " to connection " + c.conn.asBinder() +
- " (in " + c.binding.client.processName + ")", e);
+ Slog.w(TAG, "Failure sending service " + r.shortInstanceName
+ + " to connection " + c.conn.asBinder()
+ + " (in " + c.binding.client.processName + ")", e);
}
}
}
@@ -1898,7 +1907,8 @@
}
private ServiceLookupResult retrieveServiceLocked(Intent service,
- String resolvedType, String callingPackage, int callingPid, int callingUid, int userId,
+ String instanceName, String resolvedType, String callingPackage,
+ int callingPid, int callingUid, int userId,
boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
boolean allowInstant) {
ServiceRecord r = null;
@@ -1909,12 +1919,23 @@
ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE, "service", null);
ServiceMap smap = getServiceMapLocked(userId);
- final ComponentName comp = service.getComponent();
+ final ComponentName comp;
+ if (instanceName == null) {
+ comp = service.getComponent();
+ } else {
+ final ComponentName realComp = service.getComponent();
+ if (realComp == null) {
+ throw new IllegalArgumentException("Can't use custom instance name '" + instanceName
+ + "' without expicit component in Intent");
+ }
+ comp = new ComponentName(realComp.getPackageName(),
+ realComp.getClassName() + ":" + instanceName);
+ }
if (comp != null) {
- r = smap.mServicesByName.get(comp);
+ r = smap.mServicesByInstanceName.get(comp);
if (DEBUG_SERVICE && r != null) Slog.v(TAG_SERVICE, "Retrieved by component: " + r);
}
- if (r == null && !isBindExternal) {
+ if (r == null && !isBindExternal && instanceName == null) {
Intent.FilterComparison filter = new Intent.FilterComparison(service);
r = smap.mServicesByIntent.get(filter);
if (DEBUG_SERVICE && r != null) Slog.v(TAG_SERVICE, "Retrieved by intent: " + r);
@@ -1936,24 +1957,29 @@
// TODO: come back and remove this assumption to triage all services
ResolveInfo rInfo = mAm.getPackageManagerInternalLocked().resolveService(service,
resolvedType, flags, userId, callingUid);
- ServiceInfo sInfo =
- rInfo != null ? rInfo.serviceInfo : null;
+ ServiceInfo sInfo = rInfo != null ? rInfo.serviceInfo : null;
if (sInfo == null) {
Slog.w(TAG_SERVICE, "Unable to start service " + service + " U=" + userId +
": not found");
return null;
}
- ComponentName name = new ComponentName(
+ if (instanceName != null
+ && (sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0) {
+ throw new IllegalArgumentException("Can't use instance name '" + instanceName
+ + "' with non-isolated service '" + sInfo.name + "'");
+ }
+ ComponentName className = new ComponentName(
sInfo.applicationInfo.packageName, sInfo.name);
+ ComponentName name = comp != null ? comp : className;
if ((sInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) != 0) {
if (isBindExternal) {
if (!sInfo.exported) {
- throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " + name +
- " is not exported");
+ throw new SecurityException("BIND_EXTERNAL_SERVICE failed, "
+ + className + " is not exported");
}
if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0) {
- throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " + name +
- " is not an isolatedProcess");
+ throw new SecurityException("BIND_EXTERNAL_SERVICE failed, "
+ + className + " is not an isolatedProcess");
}
// Run the service under the calling package's application.
ApplicationInfo aInfo = AppGlobals.getPackageManager().getApplicationInfo(
@@ -1967,6 +1993,9 @@
sInfo.applicationInfo.packageName = aInfo.packageName;
sInfo.applicationInfo.uid = aInfo.uid;
name = new ComponentName(aInfo.packageName, name.getClassName());
+ className = new ComponentName(aInfo.packageName,
+ instanceName == null ? className.getClassName()
+ : (className.getClassName() + ":" + instanceName));
service.setComponent(name);
} else {
throw new SecurityException("BIND_EXTERNAL_SERVICE required for " +
@@ -1986,7 +2015,7 @@
sInfo = new ServiceInfo(sInfo);
sInfo.applicationInfo = mAm.getAppInfoForUser(sInfo.applicationInfo, userId);
}
- r = smap.mServicesByName.get(name);
+ r = smap.mServicesByInstanceName.get(name);
if (DEBUG_SERVICE && r != null) Slog.v(TAG_SERVICE,
"Retrieved via pm by intent: " + r);
if (r == null && createIfNeeded) {
@@ -1997,19 +2026,20 @@
final BatteryStatsImpl stats = mAm.mBatteryStatsService.getActiveStatistics();
synchronized (stats) {
ss = stats.getServiceStatsLocked(
- sInfo.applicationInfo.uid, sInfo.packageName,
- sInfo.name);
+ sInfo.applicationInfo.uid, name.getPackageName(),
+ name.getClassName());
}
- r = new ServiceRecord(mAm, ss, name, filter, sInfo, callingFromFg, res);
+ r = new ServiceRecord(mAm, ss, className, name, filter, sInfo, callingFromFg,
+ res);
res.setService(r);
- smap.mServicesByName.put(name, r);
+ smap.mServicesByInstanceName.put(name, r);
smap.mServicesByIntent.put(filter, r);
// Make sure this component isn't in the pending list.
for (int i=mPendingServices.size()-1; i>=0; i--) {
final ServiceRecord pr = mPendingServices.get(i);
if (pr.serviceInfo.applicationInfo.uid == sInfo.applicationInfo.uid
- && pr.name.equals(name)) {
+ && pr.instanceName.equals(name)) {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Remove pending: " + pr);
mPendingServices.remove(i);
}
@@ -2024,14 +2054,14 @@
if (mAm.checkComponentPermission(r.permission,
callingPid, callingUid, r.appInfo.uid, r.exported) != PERMISSION_GRANTED) {
if (!r.exported) {
- Slog.w(TAG, "Permission Denial: Accessing service " + r.name
+ Slog.w(TAG, "Permission Denial: Accessing service " + r.shortInstanceName
+ " from pid=" + callingPid
+ ", uid=" + callingUid
+ " that is not exported from uid " + r.appInfo.uid);
return new ServiceLookupResult(null, "not exported from uid "
+ r.appInfo.uid);
}
- Slog.w(TAG, "Permission Denial: Accessing service " + r.name
+ Slog.w(TAG, "Permission Denial: Accessing service " + r.shortInstanceName
+ " from pid=" + callingPid
+ ", uid=" + callingUid
+ " requires " + r.permission);
@@ -2040,7 +2070,7 @@
final int opCode = AppOpsManager.permissionToOpCode(r.permission);
if (opCode != AppOpsManager.OP_NONE && mAm.mAppOpsService.noteOperation(
opCode, callingUid, callingPackage) != AppOpsManager.MODE_ALLOWED) {
- Slog.w(TAG, "Appop Denial: Accessing service " + r.name
+ Slog.w(TAG, "Appop Denial: Accessing service " + r.shortInstanceName
+ " from pid=" + callingPid
+ ", uid=" + callingUid
+ " requires appop " + AppOpsManager.opToName(opCode));
@@ -2061,7 +2091,7 @@
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, ">>> EXECUTING "
+ why + " of " + r + " in app " + r.app);
else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING, ">>> EXECUTING "
- + why + " of " + r.shortName);
+ + why + " of " + r.shortInstanceName);
// For b/34123235: Services within the system server won't start until SystemServer
// does Looper.loop(), so we shouldn't try to start/bind to them too early in the boot
@@ -2146,14 +2176,14 @@
boolean canceled = false;
if (mAm.mAtmInternal.isShuttingDown()) {
- Slog.w(TAG, "Not scheduling restart of crashed service " + r.shortName
+ Slog.w(TAG, "Not scheduling restart of crashed service " + r.shortInstanceName
+ " - system is shutting down");
return false;
}
ServiceMap smap = getServiceMapLocked(r.userId);
- if (smap.mServicesByName.get(r.name) != r) {
- ServiceRecord cur = smap.mServicesByName.get(r.name);
+ if (smap.mServicesByInstanceName.get(r.instanceName) != r) {
+ ServiceRecord cur = smap.mServicesByInstanceName.get(r.instanceName);
Slog.wtf(TAG, "Attempting to schedule restart of " + r
+ " when found in map: " + cur);
return false;
@@ -2184,7 +2214,7 @@
if (resetTime < dur) resetTime = dur;
} else {
Slog.w(TAG, "Canceling start item " + si.intent + " in service "
- + r.name);
+ + r.shortInstanceName);
canceled = true;
}
}
@@ -2256,9 +2286,9 @@
mAm.mHandler.postAtTime(r.restarter, r.nextRestartTime);
r.nextRestartTime = SystemClock.uptimeMillis() + r.restartDelay;
Slog.w(TAG, "Scheduling restart of crashed service "
- + r.shortName + " in " + r.restartDelay + "ms");
+ + r.shortInstanceName + " in " + r.restartDelay + "ms");
EventLog.writeEvent(EventLogTags.AM_SCHEDULE_SERVICE_RESTART,
- r.userId, r.shortName, r.restartDelay);
+ r.userId, r.shortInstanceName, r.restartDelay);
return canceled;
}
@@ -2393,7 +2423,7 @@
} catch (TransactionTooLargeException e) {
throw e;
} catch (RemoteException e) {
- Slog.w(TAG, "Exception when starting service " + r.shortName, e);
+ Slog.w(TAG, "Exception when starting service " + r.shortInstanceName, e);
}
// If a dead object exception was thrown -- fall through to
@@ -2417,7 +2447,7 @@
// to be executed when the app comes up.
if (app == null && !permissionsReviewRequired) {
if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
- hostingType, r.name, false, isolated, false)) == null) {
+ hostingType, r.instanceName, false, isolated, false)) == null) {
String msg = "Unable to launch app "
+ r.appInfo.packageName + "/"
+ r.appInfo.uid + " for service "
@@ -2488,11 +2518,14 @@
try {
if (LOG_SERVICE_START_STOP) {
String nameTerm;
- int lastPeriod = r.shortName.lastIndexOf('.');
- nameTerm = lastPeriod >= 0 ? r.shortName.substring(lastPeriod) : r.shortName;
+ int lastPeriod = r.shortInstanceName.lastIndexOf('.');
+ nameTerm = lastPeriod >= 0 ? r.shortInstanceName.substring(lastPeriod)
+ : r.shortInstanceName;
EventLogTags.writeAmCreateService(
r.userId, System.identityHashCode(r), nameTerm, r.app.uid, r.app.pid);
}
+ StatsLog.write(StatsLog.SERVICE_LAUNCH_REPORTED, r.appInfo.uid, r.name.getPackageName(),
+ r.name.getClassName());
synchronized (r.stats.getBatteryStats()) {
r.stats.startLaunchedLocked();
}
@@ -2705,9 +2738,9 @@
try {
cr.conn.connected(r.name, null, true);
} catch (Exception e) {
- Slog.w(TAG, "Failure disconnecting service " + r.name +
- " to connection " + c.get(i).conn.asBinder() +
- " (in " + c.get(i).binding.client.processName + ")", e);
+ Slog.w(TAG, "Failure disconnecting service " + r.shortInstanceName
+ + " to connection " + c.get(i).conn.asBinder()
+ + " (in " + c.get(i).binding.client.processName + ")", e);
}
}
}
@@ -2728,7 +2761,7 @@
ibr.intent.getIntent());
} catch (Exception e) {
Slog.w(TAG, "Exception when unbinding service "
- + r.shortName, e);
+ + r.shortInstanceName, e);
serviceProcessGoneLocked(r);
}
}
@@ -2773,14 +2806,14 @@
}
final ServiceMap smap = getServiceMapLocked(r.userId);
- ServiceRecord found = smap.mServicesByName.remove(r.name);
+ ServiceRecord found = smap.mServicesByInstanceName.remove(r.instanceName);
// Note when this method is called by bringUpServiceLocked(), the service is not found
- // in mServicesByName and found will be null.
+ // in mServicesByInstanceName and found will be null.
if (found != null && found != r) {
// This is not actually the service we think is running... this should not happen,
// but if it does, fail hard.
- smap.mServicesByName.put(r.name, found);
+ smap.mServicesByInstanceName.put(r.instanceName, found);
throw new IllegalStateException("Bringing down " + r + " but actually running "
+ found);
}
@@ -2807,8 +2840,8 @@
mAm.mAppOpsService.finishOperation(
AppOpsManager.getToken(mAm.mAppOpsService),
AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName);
- StatsLog.write(StatsLog.FOREGROUND_SERVICE_STATE_CHANGED, r.appInfo.uid, r.shortName,
- StatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT);
+ StatsLog.write(StatsLog.FOREGROUND_SERVICE_STATE_CHANGED, r.appInfo.uid,
+ r.shortInstanceName, StatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT);
mAm.updateForegroundServiceUsageStats(r.name, r.userId, false);
}
@@ -2838,7 +2871,7 @@
r.app.thread.scheduleStopService(r);
} catch (Exception e) {
Slog.w(TAG, "Exception when destroying service "
- + r.shortName, e);
+ + r.shortInstanceName, e);
serviceProcessGoneLocked(r);
}
} else {
@@ -2915,7 +2948,7 @@
}
mAm.stopAssociationLocked(b.client.uid, b.client.processName, s.appInfo.uid,
- s.appInfo.longVersionCode, s.name, s.processName);
+ s.appInfo.longVersionCode, s.instanceName, s.processName);
if (b.connections.size() == 0) {
b.intent.apps.remove(b.client);
@@ -2942,7 +2975,7 @@
b.intent.doRebind = false;
s.app.thread.scheduleUnbindService(s, b.intent.intent.getIntent());
} catch (Exception e) {
- Slog.w(TAG, "Exception when unbinding service " + s.shortName, e);
+ Slog.w(TAG, "Exception when unbinding service " + s.shortInstanceName, e);
serviceProcessGoneLocked(s);
}
}
@@ -3061,17 +3094,17 @@
+ ": nesting=" + r.executeNesting
+ ", inDestroying=" + inDestroying + ", app=" + r.app);
else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING,
- "<<< DONE EXECUTING " + r.shortName);
+ "<<< DONE EXECUTING " + r.shortInstanceName);
r.executeNesting--;
if (r.executeNesting <= 0) {
if (r.app != null) {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
- "Nesting at 0 of " + r.shortName);
+ "Nesting at 0 of " + r.shortInstanceName);
r.app.execServicesFg = false;
r.app.executingServices.remove(r);
if (r.app.executingServices.size() == 0) {
if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING,
- "No more executingServices of " + r.shortName);
+ "No more executingServices of " + r.shortInstanceName);
mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
} else if (r.executeFg) {
// Need to re-evaluate whether the app still needs to be in the foreground.
@@ -3141,7 +3174,7 @@
}
} catch (RemoteException e) {
Slog.w(TAG, "Exception in new application when starting service "
- + sr.shortName, e);
+ + sr.shortInstanceName, e);
throw e;
}
}
@@ -3227,7 +3260,8 @@
if (userId == UserHandle.USER_ALL) {
for (int i = mServiceMap.size() - 1; i >= 0; i--) {
didSomething |= collectPackageServicesLocked(packageName, filterByClasses,
- evenPersistent, doit, killProcess, mServiceMap.valueAt(i).mServicesByName);
+ evenPersistent, doit, killProcess,
+ mServiceMap.valueAt(i).mServicesByInstanceName);
if (!doit && didSomething) {
return true;
}
@@ -3238,7 +3272,7 @@
} else {
ServiceMap smap = mServiceMap.get(userId);
if (smap != null) {
- ArrayMap<ComponentName, ServiceRecord> items = smap.mServicesByName;
+ ArrayMap<ComponentName, ServiceRecord> items = smap.mServicesByInstanceName;
didSomething = collectPackageServicesLocked(packageName, filterByClasses,
evenPersistent, doit, killProcess, items);
}
@@ -3288,7 +3322,7 @@
ServiceRecord sr = services.get(i);
if (sr.startRequested) {
if ((sr.serviceInfo.flags&ServiceInfo.FLAG_STOP_WITH_TASK) != 0) {
- Slog.i(TAG, "Stopping service " + sr.shortName + ": remove task");
+ Slog.i(TAG, "Stopping service " + sr.shortInstanceName + ": remove task");
stopServiceLocked(sr);
} else {
sr.pendingStarts.add(new ServiceRecord.StartItem(sr, true,
@@ -3326,7 +3360,7 @@
} catch (Exception e) {
// todo: this should be asynchronous!
Slog.w(TAG, "Exception thrown disconnected servce "
- + r.shortName
+ + r.shortInstanceName
+ " from app " + app.processName, e);
}
}
@@ -3400,7 +3434,7 @@
if (false && proc != null && !proc.isPersistent() && proc.thread != null
&& proc.pid != 0 && proc.pid != ActivityManagerService.MY_PID
&& proc.setProcState >= ActivityManager.PROCESS_STATE_LAST_ACTIVITY) {
- proc.kill("bound to service " + sr.name.flattenToShortString()
+ proc.kill("bound to service " + sr.shortInstanceName
+ " in dying proc " + (app != null ? app.processName : "??"), true);
}
}
@@ -3421,7 +3455,7 @@
// Sanity check: if the service listed for the app is not one
// we actually are maintaining, just let it drop.
- final ServiceRecord curRec = smap.mServicesByName.get(sr.name);
+ final ServiceRecord curRec = smap.mServicesByInstanceName.get(sr.instanceName);
if (curRec != sr) {
if (curRec != null) {
Slog.wtf(TAG, "Service " + sr + " in process " + app
@@ -3438,7 +3472,7 @@
Slog.w(TAG, "Service crashed " + sr.crashCount
+ " times, stopping: " + sr);
EventLog.writeEvent(EventLogTags.AM_SERVICE_CRASHED_TOO_MUCH,
- sr.userId, sr.crashCount, sr.shortName, app.pid);
+ sr.userId, sr.crashCount, sr.shortInstanceName, app.pid);
bringDownServiceLocked(sr);
} else if (!allowRestart
|| !mAm.mUserController.isUserRunning(sr.userId, 0)) {
@@ -3644,7 +3678,7 @@
mLastAnrDump = sw.toString();
mAm.mHandler.removeCallbacks(mLastAnrDumpClearer);
mAm.mHandler.postDelayed(mLastAnrDumpClearer, LAST_ANR_LIFETIME_DURATION_MSECS);
- anrMessage = "executing service " + timeout.shortName;
+ anrMessage = "executing service " + timeout.shortInstanceName;
} else {
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_TIMEOUT_MSG);
@@ -3690,7 +3724,8 @@
final int userId = UserHandle.getUserId(applicationInfo.uid);
ServiceMap serviceMap = mServiceMap.get(userId);
if (serviceMap != null) {
- ArrayMap<ComponentName, ServiceRecord> servicesByName = serviceMap.mServicesByName;
+ ArrayMap<ComponentName, ServiceRecord> servicesByName
+ = serviceMap.mServicesByInstanceName;
for (int j = servicesByName.size() - 1; j >= 0; j--) {
ServiceRecord serviceRecord = servicesByName.valueAt(j);
if (applicationInfo.packageName.equals(serviceRecord.appInfo.packageName)) {
@@ -3761,9 +3796,9 @@
final int[] users = mAm.mUserController.getUsers();
for (int user : users) {
ServiceMap smap = getServiceMapLocked(user);
- if (smap.mServicesByName.size() > 0) {
- for (int si=0; si<smap.mServicesByName.size(); si++) {
- ServiceRecord r = smap.mServicesByName.valueAt(si);
+ if (smap.mServicesByInstanceName.size() > 0) {
+ for (int si=0; si<smap.mServicesByInstanceName.size(); si++) {
+ ServiceRecord r = smap.mServicesByInstanceName.valueAt(si);
if (!matcher.match(r, r.name)) {
continue;
}
@@ -4164,7 +4199,7 @@
}
long token = proto.start(ActiveServicesProto.SERVICES_BY_USERS);
proto.write(ActiveServicesProto.ServicesByUser.USER_ID, user);
- ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByName;
+ ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByInstanceName;
for (int i=0; i<alls.size(); i++) {
alls.valueAt(i).writeToProto(proto,
ActiveServicesProto.ServicesByUser.SERVICE_RECORDS);
@@ -4197,7 +4232,7 @@
if (smap == null) {
continue;
}
- ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByName;
+ ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByInstanceName;
for (int i=0; i<alls.size(); i++) {
ServiceRecord r1 = alls.valueAt(i);
@@ -4235,7 +4270,7 @@
String innerPrefix = prefix + " ";
synchronized (mAm) {
pw.print(prefix); pw.print("SERVICE ");
- pw.print(r.shortName); pw.print(" ");
+ pw.print(r.shortInstanceName); pw.print(" ");
pw.print(Integer.toHexString(System.identityHashCode(r)));
pw.print(" pid=");
if (r.app != null) pw.println(r.app.pid);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d1392d0..4e417ba 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -127,6 +127,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.MemoryStatUtil.hasMemcg;
import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
+import static com.android.server.am.MemoryStatUtil.readRssHighWaterMarkFromProcfs;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONFIGURATION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
@@ -182,6 +183,7 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.ProcessMemoryHighWaterMark;
import android.app.ProcessMemoryState;
import android.app.ProfilerInfo;
import android.app.WaitResult;
@@ -4820,6 +4822,10 @@
String packageName, IBinder token, String resultWho,
int requestCode, Intent[] intents, String[] resolvedTypes,
int flags, Bundle bOptions, int userId) {
+
+ // NOTE: The service lock isn't held in this method because nothing in the method requires
+ // the service lock to be held.
+
enforceNotIsolatedCaller("getIntentSender");
// Refuse possible leaked file descriptors
if (intents != null) {
@@ -4851,43 +4857,41 @@
}
}
- synchronized(this) {
- int callingUid = Binder.getCallingUid();
- int origUserId = userId;
- userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
- type == ActivityManager.INTENT_SENDER_BROADCAST,
- ALLOW_NON_FULL, "getIntentSender", null);
- if (origUserId == UserHandle.USER_CURRENT) {
- // We don't want to evaluate this until the pending intent is
- // actually executed. However, we do want to always do the
- // security checking for it above.
- userId = UserHandle.USER_CURRENT;
- }
- try {
- if (callingUid != 0 && callingUid != SYSTEM_UID) {
- final int uid = AppGlobals.getPackageManager().getPackageUid(packageName,
- MATCH_DEBUG_TRIAGED_MISSING, UserHandle.getUserId(callingUid));
- if (!UserHandle.isSameApp(callingUid, uid)) {
- String msg = "Permission Denial: getIntentSender() from pid="
- + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid()
- + ", (need uid=" + uid + ")"
- + " is not allowed to send as package " + packageName;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
+ int callingUid = Binder.getCallingUid();
+ int origUserId = userId;
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
+ type == ActivityManager.INTENT_SENDER_BROADCAST,
+ ALLOW_NON_FULL, "getIntentSender", null);
+ if (origUserId == UserHandle.USER_CURRENT) {
+ // We don't want to evaluate this until the pending intent is
+ // actually executed. However, we do want to always do the
+ // security checking for it above.
+ userId = UserHandle.USER_CURRENT;
+ }
+ try {
+ if (callingUid != 0 && callingUid != SYSTEM_UID) {
+ final int uid = AppGlobals.getPackageManager().getPackageUid(packageName,
+ MATCH_DEBUG_TRIAGED_MISSING, UserHandle.getUserId(callingUid));
+ if (!UserHandle.isSameApp(callingUid, uid)) {
+ String msg = "Permission Denial: getIntentSender() from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + ", (need uid=" + uid + ")"
+ + " is not allowed to send as package " + packageName;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
}
+ }
- if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) {
- return mAtmInternal.getIntentSender(type, packageName, callingUid, userId,
- token, resultWho, requestCode, intents, resolvedTypes, flags, bOptions);
- }
- return mPendingIntentController.getIntentSender(type, packageName, callingUid,
- userId, token, resultWho, requestCode, intents, resolvedTypes, flags,
- bOptions);
- } catch (RemoteException e) {
- throw new SecurityException(e);
+ if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) {
+ return mAtmInternal.getIntentSender(type, packageName, callingUid, userId,
+ token, resultWho, requestCode, intents, resolvedTypes, flags, bOptions);
}
+ return mPendingIntentController.getIntentSender(type, packageName, callingUid,
+ userId, token, resultWho, requestCode, intents, resolvedTypes, flags,
+ bOptions);
+ } catch (RemoteException e) {
+ throw new SecurityException(e);
}
}
@@ -7002,7 +7006,7 @@
mCoreSettingsObserver = new CoreSettingsObserver(this);
mActivityTaskManager.installSystemProviders();
mDevelopmentSettingsObserver = new DevelopmentSettingsObserver();
- GlobalSettingsToPropertiesMapper.start(mContext.getContentResolver());
+ SettingsToPropertiesMapper.start(mContext.getContentResolver());
// Now that the settings provider is published we can consider sending
// in a rescue party.
@@ -8566,7 +8570,8 @@
r != null ? (r.isInterestingToUserLocked()
? StatsLog.APP_CRASH_OCCURRED__FOREGROUND_STATE__FOREGROUND
: StatsLog.APP_CRASH_OCCURRED__FOREGROUND_STATE__BACKGROUND)
- : StatsLog.APP_CRASH_OCCURRED__FOREGROUND_STATE__UNKNOWN
+ : StatsLog.APP_CRASH_OCCURRED__FOREGROUND_STATE__UNKNOWN,
+ (r != null) ? r.getProcessClassEnum() : 0
);
final int relaunchReason = r == null ? RELAUNCH_REASON_NONE
@@ -8749,7 +8754,7 @@
processName, r == null ? -1 : r.info.flags, tag, crashInfo.exceptionMessage);
StatsLog.write(StatsLog.WTF_OCCURRED, callingUid, tag, processName,
- callingPid);
+ callingPid, (r != null) ? r.getProcessClassEnum() : 0);
addErrorToDropBox("wtf", r, processName, null, null, null, tag, null, null, crashInfo);
@@ -13183,8 +13188,15 @@
}
public int bindService(IApplicationThread caller, IBinder token, Intent service,
- String resolvedType, IServiceConnection connection, int flags, String callingPackage,
- int userId) throws TransactionTooLargeException {
+ String resolvedType, IServiceConnection connection, int flags,
+ String callingPackage, int userId) throws TransactionTooLargeException {
+ return bindIsolatedService(caller, token, service, resolvedType, connection, flags,
+ null, callingPackage, userId);
+ }
+
+ public int bindIsolatedService(IApplicationThread caller, IBinder token, Intent service,
+ String resolvedType, IServiceConnection connection, int flags, String instanceName,
+ String callingPackage, int userId) throws TransactionTooLargeException {
enforceNotIsolatedCaller("bindService");
// Refuse possible leaked file descriptors
@@ -13198,7 +13210,7 @@
synchronized(this) {
return mServices.bindServiceLocked(caller, token, service,
- resolvedType, connection, flags, callingPackage, userId);
+ resolvedType, connection, flags, instanceName, callingPackage, userId);
}
}
@@ -15831,7 +15843,7 @@
mayBeTop = true;
mayBeTopType = "service";
mayBeTopSource = cr.binding.client;
- mayBeTopTarget = s.name;
+ mayBeTopTarget = s.instanceName;
clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
} else {
// Special handling for above-top states (persistent
@@ -15885,7 +15897,7 @@
.REASON_SERVICE_IN_USE;
app.adjSource = cr.binding.client;
app.adjSourceProcState = clientProcState;
- app.adjTarget = s.name;
+ app.adjTarget = s.instanceName;
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
+ ": " + app + ", due to " + cr.binding.client
@@ -15915,7 +15927,7 @@
.REASON_SERVICE_IN_USE;
app.adjSource = a;
app.adjSourceProcState = procState;
- app.adjTarget = s.name;
+ app.adjTarget = s.instanceName;
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise to service w/activity: " + app);
@@ -18750,6 +18762,7 @@
if (memoryStat == null) {
continue;
}
+ // TODO(rslawik): Delete RSS high-water mark field.
ProcessMemoryState processMemoryState =
new ProcessMemoryState(uid,
r.processName,
@@ -18768,6 +18781,20 @@
}
@Override
+ public List<ProcessMemoryHighWaterMark> getMemoryHighWaterMarkForProcesses() {
+ List<ProcessMemoryHighWaterMark> results = new ArrayList<>();
+ synchronized (mPidsSelfLocked) {
+ for (int i = 0, size = mPidsSelfLocked.size(); i < size; i++) {
+ final ProcessRecord r = mPidsSelfLocked.valueAt(i);
+ final long rssHighWaterMarkInBytes = readRssHighWaterMarkFromProcfs(r.pid);
+ results.add(new ProcessMemoryHighWaterMark(r.uid, r.processName,
+ rssHighWaterMarkInBytes));
+ }
+ }
+ return results;
+ }
+
+ @Override
public int handleIncomingUser(int callingPid, int callingUid, int userId,
boolean allowAll, int allowMode, String name, String callerPackage) {
return mUserController.handleIncomingUser(callingPid, callingUid, userId, allowAll,
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 9018006..8f8d5ab 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -58,7 +58,6 @@
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.opengl.GLES10;
@@ -735,7 +734,6 @@
return -1;
}
- ;
if (!mInterface.stopBinderTrackingAndDump(fd)) {
err.println("STOP TRACE FAILED.");
return -1;
@@ -2024,19 +2022,20 @@
}
private void writeDeviceConfig(ProtoOutputStream protoOutputStream, long fieldId,
- PrintWriter pw, Configuration config, DisplayManager dm) {
- Point stableSize = dm.getStableDisplaySize();
+ PrintWriter pw, Configuration config, DisplayMetrics displayMetrics) {
long token = -1;
if (protoOutputStream != null) {
token = protoOutputStream.start(fieldId);
- protoOutputStream.write(DeviceConfigurationProto.STABLE_SCREEN_WIDTH_PX, stableSize.x);
- protoOutputStream.write(DeviceConfigurationProto.STABLE_SCREEN_HEIGHT_PX, stableSize.y);
+ protoOutputStream.write(DeviceConfigurationProto.STABLE_SCREEN_WIDTH_PX,
+ displayMetrics.widthPixels);
+ protoOutputStream.write(DeviceConfigurationProto.STABLE_SCREEN_HEIGHT_PX,
+ displayMetrics.heightPixels);
protoOutputStream.write(DeviceConfigurationProto.STABLE_DENSITY_DPI,
DisplayMetrics.DENSITY_DEVICE_STABLE);
}
if (pw != null) {
- pw.print("stable-width-px: "); pw.println(stableSize.x);
- pw.print("stable-height-px: "); pw.println(stableSize.y);
+ pw.print("stable-width-px: "); pw.println(displayMetrics.widthPixels);
+ pw.print("stable-height-px: "); pw.println(displayMetrics.heightPixels);
pw.print("stable-density-dpi: "); pw.println(DisplayMetrics.DENSITY_DEVICE_STABLE);
}
@@ -2130,11 +2129,12 @@
int runGetConfig(PrintWriter pw) throws RemoteException {
int days = -1;
+ int displayId = Display.DEFAULT_DISPLAY;
boolean asProto = false;
boolean inclDevice = false;
String opt;
- while ((opt=getNextOption()) != null) {
+ while ((opt = getNextOption()) != null) {
if (opt.equals("--days")) {
days = Integer.parseInt(getNextArgRequired());
if (days <= 0) {
@@ -2144,6 +2144,11 @@
asProto = true;
} else if (opt.equals("--device")) {
inclDevice = true;
+ } else if (opt.equals("--display")) {
+ displayId = Integer.parseInt(getNextArgRequired());
+ if (displayId < 0) {
+ throw new IllegalArgumentException("--display must be a non-negative integer");
+ }
} else {
getErrPrintWriter().println("Error: Unknown option: " + opt);
return -1;
@@ -2157,7 +2162,13 @@
}
DisplayManager dm = mInternal.mContext.getSystemService(DisplayManager.class);
- Display display = dm.getDisplay(Display.DEFAULT_DISPLAY);
+ Display display = dm.getDisplay(displayId);
+
+ if (display == null) {
+ getErrPrintWriter().println("Error: Display does not exist: " + displayId);
+ return -1;
+ }
+
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
@@ -2165,15 +2176,14 @@
final ProtoOutputStream proto = new ProtoOutputStream(getOutFileDescriptor());
config.writeResConfigToProto(proto, GlobalConfigurationProto.RESOURCES, metrics);
if (inclDevice) {
- writeDeviceConfig(proto, GlobalConfigurationProto.DEVICE, null, config, dm);
+ writeDeviceConfig(proto, GlobalConfigurationProto.DEVICE, null, config, metrics);
}
proto.flush();
-
} else {
pw.println("config: " + Configuration.resourceQualifierString(config, metrics));
pw.println("abi: " + TextUtils.join(",", Build.SUPPORTED_ABIS));
if (inclDevice) {
- writeDeviceConfig(null, -1, pw, config, dm);
+ writeDeviceConfig(null, -1, pw, config, metrics);
}
if (days >= 0) {
@@ -3028,11 +3038,13 @@
pw.println(" Gets the process state of an app given its <UID>.");
pw.println(" attach-agent <PROCESS> <FILE>");
pw.println(" Attach an agent to the specified <PROCESS>, which may be either a process name or a PID.");
- pw.println(" get-config [--days N] [--device] [--proto]");
+ pw.println(" get-config [--days N] [--device] [--proto] [--display <DISPLAY_ID>]");
pw.println(" Retrieve the configuration and any recent configurations of the device.");
pw.println(" --days: also return last N days of configurations that have been seen.");
pw.println(" --device: also output global device configuration info.");
pw.println(" --proto: return result as a proto; does not include --days info.");
+ pw.println(" --display: Specify for which display to run the command; if not ");
+ pw.println(" specified then run for the default display.");
pw.println(" supports-multiwindow");
pw.println(" Returns true if the device supports multiwindow.");
pw.println(" supports-split-screen-multi-window");
diff --git a/services/core/java/com/android/server/am/AppBindRecord.java b/services/core/java/com/android/server/am/AppBindRecord.java
index 4eaebd0..9870420 100644
--- a/services/core/java/com/android/server/am/AppBindRecord.java
+++ b/services/core/java/com/android/server/am/AppBindRecord.java
@@ -59,12 +59,12 @@
public String toString() {
return "AppBindRecord{"
+ Integer.toHexString(System.identityHashCode(this))
- + " " + service.shortName + ":" + client.processName + "}";
+ + " " + service.shortInstanceName + ":" + client.processName + "}";
}
void writeToProto(ProtoOutputStream proto, long fieldId) {
long token = proto.start(fieldId);
- proto.write(AppBindRecordProto.SERVICE_NAME, service.shortName);
+ proto.write(AppBindRecordProto.SERVICE_NAME, service.shortInstanceName);
proto.write(AppBindRecordProto.CLIENT_PROC_NAME, client.processName);
final int N = connections.size();
for (int i=0; i<N; i++) {
diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java
index 37d07bb..bfa3f66 100644
--- a/services/core/java/com/android/server/am/ConnectionRecord.java
+++ b/services/core/java/com/android/server/am/ConnectionRecord.java
@@ -115,16 +115,16 @@
&& (binding.service.appInfo.uid != clientUid
|| !binding.service.processName.equals(clientProcessName))) {
ProcessStats.ProcessStateHolder holder = binding.service.app.pkgList.get(
- binding.service.name.getPackageName());
+ binding.service.instanceName.getPackageName());
if (holder == null) {
Slog.wtf(TAG_AM, "No package in referenced service "
- + binding.service.name.toShortString() + ": proc=" + binding.service.app);
+ + binding.service.shortInstanceName + ": proc=" + binding.service.app);
} else if (holder.pkg == null) {
Slog.wtf(TAG_AM, "Inactive holder in referenced service "
- + binding.service.name.toShortString() + ": proc=" + binding.service.app);
+ + binding.service.shortInstanceName + ": proc=" + binding.service.app);
} else {
association = holder.pkg.getAssociationStateLocked(holder.state,
- binding.service.name.getClassName()).startSource(clientUid,
+ binding.service.instanceName.getClassName()).startSource(clientUid,
clientProcessName);
}
@@ -202,7 +202,7 @@
if (serviceDead) {
sb.append("DEAD ");
}
- sb.append(binding.service.shortName);
+ sb.append(binding.service.shortInstanceName);
sb.append(":@");
sb.append(Integer.toHexString(System.identityHashCode(conn.asBinder())));
sb.append('}');
@@ -223,7 +223,7 @@
proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.DEAD);
}
if (binding.service != null) {
- proto.write(ConnectionRecordProto.SERVICE_NAME, binding.service.shortName);
+ proto.write(ConnectionRecordProto.SERVICE_NAME, binding.service.shortInstanceName);
}
proto.end(token);
}
diff --git a/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java
deleted file mode 100644
index 1366c21..0000000
--- a/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.am;
-
-import android.content.ContentResolver;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Build;
-import android.os.SystemProperties;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.Slog;
-import android.view.ThreadedRenderer;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
-
-/**
- * Maps global system settings to system properties.
- * <p>The properties are dynamically updated when settings change.
- */
-class GlobalSettingsToPropertiesMapper {
-
- private static final String TAG = "GlobalSettingsToPropertiesMapper";
-
- // List mapping entries in the following format:
- // {Settings.Global.SETTING_NAME, "system_property_name"}
- // Important: Property being added should be whitelisted by SELinux policy or have one of the
- // already whitelisted prefixes in system_server.te, e.g. sys.
- private static final String[][] sGlobalSettingsMapping = new String[][] {
- {Settings.Global.SYS_VDSO, "sys.vdso"},
- {Settings.Global.FPS_DEVISOR, ThreadedRenderer.DEBUG_FPS_DIVISOR},
- {Settings.Global.DISPLAY_PANEL_LPM, "sys.display_panel_lpm"},
- {Settings.Global.SYS_UIDCPUPOWER, "sys.uidcpupower"},
- {Settings.Global.SYS_TRACED, "sys.traced.enable_override"},
- };
-
-
- private final ContentResolver mContentResolver;
- private final String[][] mGlobalSettingsMapping;
-
- @VisibleForTesting
- GlobalSettingsToPropertiesMapper(ContentResolver contentResolver,
- String[][] globalSettingsMapping) {
- mContentResolver = contentResolver;
- mGlobalSettingsMapping = globalSettingsMapping;
- }
-
- void updatePropertiesFromGlobalSettings() {
- for (String[] entry : mGlobalSettingsMapping) {
- final String settingName = entry[0];
- final String propName = entry[1];
- Uri settingUri = Settings.Global.getUriFor(settingName);
- Preconditions.checkNotNull(settingUri, "Setting " + settingName + " not found");
- ContentObserver co = new ContentObserver(null) {
- @Override
- public void onChange(boolean selfChange) {
- updatePropertyFromSetting(settingName, propName);
- }
- };
- updatePropertyFromSetting(settingName, propName);
- mContentResolver.registerContentObserver(settingUri, false, co);
- }
- }
-
- public static void start(ContentResolver contentResolver) {
- new GlobalSettingsToPropertiesMapper(contentResolver, sGlobalSettingsMapping)
- .updatePropertiesFromGlobalSettings();
- }
-
- private String getGlobalSetting(String name) {
- return Settings.Global.getString(mContentResolver, name);
- }
-
- private void setProperty(String key, String value) {
- // Check if need to clear the property
- if (value == null) {
- // It's impossible to remove system property, therefore we check previous value to
- // avoid setting an empty string if the property wasn't set.
- if (TextUtils.isEmpty(systemPropertiesGet(key))) {
- return;
- }
- value = "";
- }
- try {
- systemPropertiesSet(key, value);
- } catch (Exception e) {
- // Failure to set a property can be caused by SELinux denial. This usually indicates
- // that the property wasn't whitelisted in sepolicy.
- // No need to report it on all user devices, only on debug builds.
- if (Build.IS_DEBUGGABLE) {
- Slog.wtf(TAG, "Unable to set property " + key + " value '" + value + "'", e);
- } else {
- Slog.e(TAG, "Unable to set property " + key + " value '" + value + "'", e);
- }
- }
- }
-
- @VisibleForTesting
- protected String systemPropertiesGet(String key) {
- return SystemProperties.get(key);
- }
-
- @VisibleForTesting
- protected void systemPropertiesSet(String key, String value) {
- SystemProperties.set(key, value);
- }
-
- @VisibleForTesting
- void updatePropertyFromSetting(String settingName, String propName) {
- String settingValue = getGlobalSetting(settingName);
- setProperty(propName, settingValue);
- }
-}
diff --git a/services/core/java/com/android/server/am/IntentBindRecord.java b/services/core/java/com/android/server/am/IntentBindRecord.java
index 839b6e1..90aef3e 100644
--- a/services/core/java/com/android/server/am/IntentBindRecord.java
+++ b/services/core/java/com/android/server/am/IntentBindRecord.java
@@ -99,7 +99,7 @@
if ((collectFlags()&Context.BIND_AUTO_CREATE) != 0) {
sb.append("CR ");
}
- sb.append(service.shortName);
+ sb.append(service.shortInstanceName);
sb.append(':');
if (intent != null) {
intent.getIntent().toShortString(sb, false, false, false, false);
diff --git a/services/core/java/com/android/server/am/MemoryStatUtil.java b/services/core/java/com/android/server/am/MemoryStatUtil.java
index 80b4f77..cc3da1c 100644
--- a/services/core/java/com/android/server/am/MemoryStatUtil.java
+++ b/services/core/java/com/android/server/am/MemoryStatUtil.java
@@ -39,38 +39,6 @@
* Static utility methods related to {@link MemoryStat}.
*/
public final class MemoryStatUtil {
- /**
- * Which native processes to create {@link MemoryStat} for.
- *
- * <p>Processes are matched by their cmdline in procfs. Example: cat /proc/pid/cmdline returns
- * /system/bin/statsd for the stats daemon.
- */
- public static final String[] MEMORY_STAT_INTERESTING_NATIVE_PROCESSES = new String[]{
- "/system/bin/statsd", // Stats daemon.
- "/system/bin/surfaceflinger",
- "/system/bin/apexd", // APEX daemon.
- "/system/bin/audioserver",
- "/system/bin/cameraserver",
- "/system/bin/drmserver",
- "/system/bin/healthd",
- "/system/bin/incidentd",
- "/system/bin/installd",
- "/system/bin/lmkd", // Low memory killer daemon.
- "/system/bin/logd",
- "media.codec",
- "media.extractor",
- "media.metrics",
- "/system/bin/mediadrmserver",
- "/system/bin/mediaserver",
- "/system/bin/performanced",
- "/system/bin/tombstoned",
- "/system/bin/traced", // Perfetto.
- "/system/bin/traced_probes", // Perfetto.
- "webview_zygote",
- "zygote",
- "zygote64",
- };
-
static final int BYTES_IN_KILOBYTE = 1024;
static final int PAGE_SIZE = 4096;
static final long JIFFY_NANOS = 1_000_000_000 / Os.sysconf(OsConstants._SC_CLK_TCK);
@@ -152,12 +120,20 @@
if (stat == null) {
return null;
}
- final String statusPath = String.format(Locale.US, PROC_STATUS_FILE_FMT, pid);
- stat.rssHighWatermarkInBytes = parseVmHWMFromProcfs(readFileContents(statusPath));
+ stat.rssHighWatermarkInBytes = readRssHighWaterMarkFromProcfs(pid);
return stat;
}
/**
+ * Reads RSS high-water mark of a process from procfs. Returns value of the VmHWM field in
+ * /proc/PID/status in bytes or 0 if not available.
+ */
+ public static long readRssHighWaterMarkFromProcfs(int pid) {
+ final String statusPath = String.format(Locale.US, PROC_STATUS_FILE_FMT, pid);
+ return parseVmHWMFromProcfs(readFileContents(statusPath));
+ }
+
+ /**
* Reads cmdline of a process from procfs.
*
* Returns content of /proc/pid/cmdline (e.g. /system/bin/statsd) or an empty string
diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS
index 79c98e5..5208ca5 100644
--- a/services/core/java/com/android/server/am/OWNERS
+++ b/services/core/java/com/android/server/am/OWNERS
@@ -27,4 +27,4 @@
michaelwr@google.com
narayan@google.com
-per-file GlobalSettingsToPropertiesMapper.java = omakoto@google.com, svetoslavganov@google.com, yamasani@google.com
+per-file SettingsToPropertiesMapper.java = omakoto@google.com, svetoslavganov@google.com, yamasani@google.com
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 745c1263..faf8561 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -42,6 +42,7 @@
import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
+import android.server.ServerProtoEnums;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DebugUtils;
@@ -1224,6 +1225,17 @@
return mWindowProcessController.getInputDispatchingTimeout();
}
+ public int getProcessClassEnum() {
+ if (pid == MY_PID) {
+ return ServerProtoEnums.SYSTEM_SERVER;
+ }
+ if (info == null) {
+ return ServerProtoEnums.ERROR_SOURCE_UNKNOWN;
+ }
+ return (info.flags & ApplicationInfo.FLAG_SYSTEM) != 0 ? ServerProtoEnums.SYSTEM_APP :
+ ServerProtoEnums.DATA_APP;
+ }
+
void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
String parentShortComponentName, WindowProcessController parentProcess,
boolean aboveSystem, String annotation) {
@@ -1380,7 +1392,9 @@
: StatsLog.ANROCCURRED__IS_INSTANT_APP__UNAVAILABLE,
isInterestingToUserLocked()
? StatsLog.ANROCCURRED__FOREGROUND_STATE__FOREGROUND
- : StatsLog.ANROCCURRED__FOREGROUND_STATE__BACKGROUND);
+ : StatsLog.ANROCCURRED__FOREGROUND_STATE__BACKGROUND,
+ getProcessClassEnum(),
+ (this.info != null) ? this.info.packageName : "");
final ProcessRecord parentPr = parentProcess != null
? (ProcessRecord) parentProcess.mOwner : null;
mService.addErrorToDropBox("anr", this, processName, activityShortComponentName,
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index d8f94c9..09f8c3e 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -70,7 +70,8 @@
final ActivityManagerService ams;
final BatteryStatsImpl.Uid.Pkg.Serv stats;
final ComponentName name; // service component.
- final String shortName; // name.flattenToShortString().
+ final ComponentName instanceName; // service component's per-instance name.
+ final String shortInstanceName; // instanceName.flattenToShortString().
final Intent.FilterComparison intent;
// original intent used to find service.
final ServiceInfo serviceInfo;
@@ -190,7 +191,7 @@
StringBuilder sb = new StringBuilder(128);
sb.append("ServiceRecord{")
.append(Integer.toHexString(System.identityHashCode(sr)))
- .append(' ').append(sr.shortName)
+ .append(' ').append(sr.shortInstanceName)
.append(" StartItem ")
.append(Integer.toHexString(System.identityHashCode(this)))
.append(" id=").append(id).append('}');
@@ -235,7 +236,7 @@
void writeToProto(ProtoOutputStream proto, long fieldId) {
long token = proto.start(fieldId);
- proto.write(ServiceRecordProto.SHORT_NAME, this.shortName);
+ proto.write(ServiceRecordProto.SHORT_NAME, this.shortInstanceName);
proto.write(ServiceRecordProto.IS_RUNNING, app != null);
if (app != null) {
proto.write(ServiceRecordProto.PID, app.pid);
@@ -448,12 +449,14 @@
ServiceRecord(ActivityManagerService ams,
BatteryStatsImpl.Uid.Pkg.Serv servStats, ComponentName name,
+ ComponentName instanceName,
Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg,
Runnable restarter) {
this.ams = ams;
this.stats = servStats;
this.name = name;
- shortName = name.flattenToShortString();
+ this.instanceName = instanceName;
+ shortInstanceName = instanceName.flattenToShortString();
this.intent = intent;
serviceInfo = sInfo;
appInfo = sInfo.applicationInfo;
@@ -618,7 +621,7 @@
// those dirty apps we will create a notification clearly
// blaming the app.
Slog.v(TAG, "Attempted to start a foreground service ("
- + name
+ + shortInstanceName
+ ") with a broken notification (no icon: "
+ localForegroundNoti
+ ")");
@@ -701,7 +704,7 @@
Slog.w(TAG, "Error showing notification for service", e);
// If it gave us a garbage notification, it doesn't
// get to be foreground.
- ams.setServiceForeground(name, ServiceRecord.this,
+ ams.setServiceForeground(instanceName, ServiceRecord.this,
0, null, 0);
ams.crashApplication(appUid, appPid, localPackageName, -1,
"Bad notification for startForeground: " + e);
@@ -773,7 +776,7 @@
sb.append("ServiceRecord{")
.append(Integer.toHexString(System.identityHashCode(this)))
.append(" u").append(userId)
- .append(' ').append(shortName).append('}');
+ .append(' ').append(shortInstanceName).append('}');
return stringName = sb.toString();
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
new file mode 100644
index 0000000..a5848ca
--- /dev/null
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashSet;
+
+/**
+ * Maps system settings to system properties.
+ * <p>The properties are dynamically updated when settings change.
+ */
+class SettingsToPropertiesMapper {
+
+ private static final String TAG = "SettingsToPropertiesMapper";
+
+ private static final String SYSTEM_PROPERTY_PREFIX = "persist.device_config.";
+
+ private static final String RESET_PERFORMED_PROPERTY = "device_config.reset_performed";
+
+ private static final String RESET_RECORD_FILE_PATH =
+ "/data/server_configurable_flags/reset_flags";
+
+ private static final String SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX = "^[\\w\\.\\-@:]*$";
+
+ private static final String SYSTEM_PROPERTY_INVALID_SUBSTRING = "..";
+
+ private static final int SYSTEM_PROPERTY_MAX_LENGTH = 92;
+
+ // experiment flags added to Global.Settings(before new "Config" provider table is available)
+ // will be added under this category.
+ private static final String GLOBAL_SETTINGS_CATEGORY = "global_settings";
+
+ // Add the global setting you want to push to native level as experiment flag into this list.
+ //
+ // NOTE: please grant write permission system property prefix
+ // with format persist.experiment.[experiment_category_name]. in system_server.te and grant read
+ // permission in the corresponding .te file your feature belongs to.
+ @VisibleForTesting
+ static final String[] sGlobalSettings = new String[] {
+ };
+
+ @VisibleForTesting
+ static final String[] sDeviceConfigScopes = new String[] {
+ };
+
+ private final String[] mGlobalSettings;
+
+ private final String[] mDeviceConfigScopes;
+
+ private final ContentResolver mContentResolver;
+
+ @VisibleForTesting
+ protected SettingsToPropertiesMapper(ContentResolver contentResolver,
+ String[] globalSettings,
+ String[] deviceConfigScopes) {
+ mContentResolver = contentResolver;
+ mGlobalSettings = globalSettings;
+ mDeviceConfigScopes = deviceConfigScopes;
+ }
+
+ @VisibleForTesting
+ void updatePropertiesFromSettings() {
+ for (String globalSetting : mGlobalSettings) {
+ Uri settingUri = Settings.Global.getUriFor(globalSetting);
+ String propName = makePropertyName(GLOBAL_SETTINGS_CATEGORY, globalSetting);
+ if (settingUri == null) {
+ log("setting uri is null for globalSetting " + globalSetting);
+ continue;
+ }
+ if (propName == null) {
+ log("invalid prop name for globalSetting " + globalSetting);
+ continue;
+ }
+
+ ContentObserver co = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updatePropertyFromSetting(globalSetting, propName, true);
+ }
+ };
+
+ // only updating on starting up when no native flags reset is performed during current
+ // booting.
+ if (!isNativeFlagsResetPerformed()) {
+ updatePropertyFromSetting(globalSetting, propName, true);
+ }
+ mContentResolver.registerContentObserver(settingUri, false, co);
+ }
+
+ // TODO: address sDeviceConfigScopes after DeviceConfig APIs are available.
+ }
+
+ public static SettingsToPropertiesMapper start(ContentResolver contentResolver) {
+ SettingsToPropertiesMapper mapper = new SettingsToPropertiesMapper(
+ contentResolver, sGlobalSettings, sDeviceConfigScopes);
+ mapper.updatePropertiesFromSettings();
+ return mapper;
+ }
+
+ /**
+ * If native level flags reset has been performed as an attempt to recover from a crash loop
+ * during current device booting.
+ * @return
+ */
+ public boolean isNativeFlagsResetPerformed() {
+ String value = systemPropertiesGet(RESET_PERFORMED_PROPERTY);
+ return "true".equals(value);
+ }
+
+ /**
+ * return an array of native flag categories under which flags got reset during current device
+ * booting.
+ * @return
+ */
+ public String[] getResetNativeCategories() {
+ if (!isNativeFlagsResetPerformed()) {
+ return new String[0];
+ }
+
+ String content = getResetFlagsFileContent();
+ if (TextUtils.isEmpty(content)) {
+ return new String[0];
+ }
+
+ String[] property_names = content.split(";");
+ HashSet<String> categories = new HashSet<>();
+ for (String property_name : property_names) {
+ String[] segments = property_name.split("\\.");
+ if (segments.length < 3) {
+ log("failed to extract category name from property " + property_name);
+ continue;
+ }
+ categories.add(segments[2]);
+ }
+ return categories.toArray(new String[0]);
+ }
+
+ /**
+ * system property name constructing rule: "persist.device_config.[category_name].[flag_name]".
+ * If the name contains invalid characters or substrings for system property name,
+ * will return null.
+ * @param categoryName
+ * @param flagName
+ * @return
+ */
+ @VisibleForTesting
+ static String makePropertyName(String categoryName, String flagName) {
+ String propertyName = SYSTEM_PROPERTY_PREFIX + categoryName + "." + flagName;
+
+ if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX)
+ || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) {
+ return null;
+ }
+
+ return propertyName;
+ }
+
+ private String getSetting(String name, boolean isGlobalSetting) {
+ if (isGlobalSetting) {
+ return Settings.Global.getString(mContentResolver, name);
+ } else {
+ // TODO: complete the code after DeviceConfig APIs implemented.
+ return null;
+ }
+ }
+
+ private void setProperty(String key, String value) {
+ // Check if need to clear the property
+ if (value == null) {
+ // It's impossible to remove system property, therefore we check previous value to
+ // avoid setting an empty string if the property wasn't set.
+ if (TextUtils.isEmpty(systemPropertiesGet(key))) {
+ return;
+ }
+ value = "";
+ } else if (value.length() > SYSTEM_PROPERTY_MAX_LENGTH) {
+ log(value + " exceeds system property max length.");
+ return;
+ }
+
+ try {
+ systemPropertiesSet(key, value);
+ } catch (Exception e) {
+ // Failure to set a property can be caused by SELinux denial. This usually indicates
+ // that the property wasn't whitelisted in sepolicy.
+ // No need to report it on all user devices, only on debug builds.
+ log("Unable to set property " + key + " value '" + value + "'", e);
+ }
+ }
+
+ private static void log(String msg, Exception e) {
+ if (Build.IS_DEBUGGABLE) {
+ Slog.wtf(TAG, msg, e);
+ } else {
+ Slog.e(TAG, msg, e);
+ }
+ }
+
+ private static void log(String msg) {
+ if (Build.IS_DEBUGGABLE) {
+ Slog.wtf(TAG, msg);
+ } else {
+ Slog.e(TAG, msg);
+ }
+ }
+
+ @VisibleForTesting
+ protected String systemPropertiesGet(String key) {
+ return SystemProperties.get(key);
+ }
+
+ @VisibleForTesting
+ protected void systemPropertiesSet(String key, String value) {
+ SystemProperties.set(key, value);
+ }
+
+ @VisibleForTesting
+ protected String getResetFlagsFileContent() {
+ String content = null;
+ try {
+ File reset_flag_file = new File(RESET_RECORD_FILE_PATH);
+ BufferedReader br = new BufferedReader(new FileReader(reset_flag_file));
+ content = br.readLine();
+
+ br.close();
+ } catch (IOException ioe) {
+ log("failed to read file " + RESET_RECORD_FILE_PATH, ioe);
+ }
+ return content;
+ }
+
+ @VisibleForTesting
+ void updatePropertyFromSetting(String settingName, String propName, boolean isGlobalSetting) {
+ String settingValue = getSetting(settingName, isGlobalSetting);
+ setProperty(propName, settingValue);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 278c55f..5f09189 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -246,9 +246,20 @@
public void authenticate(IBinder token, long sessionId, int userId,
IBiometricServiceReceiver receiver, int flags, String opPackageName,
Bundle bundle, IBiometricPromptReceiver dialogReceiver) throws RemoteException {
- // Check the USE_BIOMETRIC permission here. In the BiometricServiceBase, check do the
- // AppOps and foreground check.
- checkPermission();
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int callingUserId = UserHandle.getCallingUserId();
+
+ // In the BiometricServiceBase, check do the AppOps and foreground check.
+ if (userId == callingUserId) {
+ // Check the USE_BIOMETRIC permission here.
+ checkPermission();
+ } else {
+ // Only allow internal clients to authenticate with a different userId
+ Slog.w(TAG, "User " + callingUserId + " is requesting authentication of userid: "
+ + userId);
+ checkInternalPermission();
+ }
if (token == null || receiver == null || opPackageName == null || bundle == null
|| dialogReceiver == null) {
@@ -262,10 +273,6 @@
checkInternalPermission();
}
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
- final int callingUserId = UserHandle.getCallingUserId();
-
mHandler.post(() -> {
final Pair<Integer, Integer> result = checkAndGetBiometricModality(callingUserId);
final int modality = result.first;
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index a769590..65537ad 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -55,6 +55,7 @@
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.intelligence.IntelligenceManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -157,6 +158,7 @@
private final IUserManager mUm;
private final PackageManager mPm;
private final AppOpsManager mAppOps;
+ private final IntelligenceManagerInternal mIm;
private final IBinder mPermissionOwner;
private HostClipboardMonitor mHostClipboardMonitor = null;
private Thread mHostMonitorThread = null;
@@ -176,6 +178,7 @@
mPm = getContext().getPackageManager();
mUm = (IUserManager) ServiceManager.getService(Context.USER_SERVICE);
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
+ mIm = LocalServices.getService(IntelligenceManagerInternal.class);
final IBinder permOwner = mUgmInternal.newUriPermissionOwner("clipboard");
mPermissionOwner = permOwner;
if (IS_EMULATOR) {
@@ -635,8 +638,9 @@
return true;
}
// The default IME is always allowed to access the clipboard.
+ int userId = UserHandle.getUserId(callingUid);
String defaultIme = Settings.Secure.getStringForUser(getContext().getContentResolver(),
- Settings.Secure.DEFAULT_INPUT_METHOD, UserHandle.getUserId(callingUid));
+ Settings.Secure.DEFAULT_INPUT_METHOD, userId);
if (!TextUtils.isEmpty(defaultIme)) {
final String imePkg = ComponentName.unflattenFromString(defaultIme).getPackageName();
if (imePkg.equals(callingPackage)) {
@@ -646,13 +650,18 @@
switch (op) {
case AppOpsManager.OP_READ_CLIPBOARD:
- // Clipboard can only be read by applications with focus.
- boolean uidFocused = mWm.isUidFocused(callingUid);
- if (!uidFocused) {
- Slog.e(TAG, "Denying clipboard access to " + callingPackage
- + ", application is not in focus.");
+ // Clipboard can only be read by applications with focus..
+ boolean allowed = mWm.isUidFocused(callingUid);
+ if (!allowed && mIm != null) {
+ // ...or the Intelligence Service
+ allowed = mIm.isIntelligenceServiceForUser(callingUid, userId);
}
- return uidFocused;
+ if (!allowed) {
+ Slog.e(TAG, "Denying clipboard access to " + callingPackage
+ + ", application is not in focus neither is the IntelligeService for "
+ + "user " + userId);
+ }
+ return allowed;
case AppOpsManager.OP_WRITE_CLIPBOARD:
// Writing is allowed without focus.
return true;
diff --git a/services/core/java/com/android/server/connectivity/LingerMonitor.java b/services/core/java/com/android/server/connectivity/LingerMonitor.java
index 0e727c5..929dfc4 100644
--- a/services/core/java/com/android/server/connectivity/LingerMonitor.java
+++ b/services/core/java/com/android/server/connectivity/LingerMonitor.java
@@ -90,8 +90,8 @@
mNotifier = notifier;
mDailyLimit = dailyLimit;
mRateLimitMillis = rateLimitMillis;
- // Ensure that (now - mFirstNotificationMillis) >= rateLimitMillis at first
- mFirstNotificationMillis = -rateLimitMillis;
+ // Ensure that (now - mLastNotificationMillis) >= rateLimitMillis at first
+ mLastNotificationMillis = -rateLimitMillis;
}
private static HashMap<String, Integer> makeTransportToNameMap() {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index c20079e..3a31c9c 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -72,6 +72,8 @@
import android.view.IInputFilterHost;
import android.view.IWindow;
import android.view.InputChannel;
+import android.view.InputApplicationHandle;
+import android.view.InputWindowHandle;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.KeyEvent;
@@ -197,7 +199,7 @@
private static native boolean nativeHasKeys(long ptr,
int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists);
private static native void nativeRegisterInputChannel(long ptr, InputChannel inputChannel,
- InputWindowHandle inputWindowHandle, int displayId);
+ int displayId);
private static native void nativeUnregisterInputChannel(long ptr, InputChannel inputChannel);
private static native void nativeSetInputFilterEnabled(long ptr, boolean enable);
private static native int nativeInjectInputEvent(long ptr, InputEvent event,
@@ -486,8 +488,7 @@
}
InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName);
- // Register channel for monitor.
- nativeRegisterInputChannel(mPtr, inputChannels[0], null, displayId);
+ nativeRegisterInputChannel(mPtr, inputChannels[0], displayId);
inputChannels[0].dispose(); // don't need to retain the Java object reference
return inputChannels[1];
}
@@ -498,14 +499,17 @@
* @param inputWindowHandle The handle of the input window associated with the
* input channel, or null if none.
*/
- public void registerInputChannel(InputChannel inputChannel,
- InputWindowHandle inputWindowHandle) {
+ public void registerInputChannel(InputChannel inputChannel, IBinder token) {
if (inputChannel == null) {
throw new IllegalArgumentException("inputChannel must not be null.");
}
- // Register channel for normal.
- nativeRegisterInputChannel(mPtr, inputChannel, inputWindowHandle, Display.INVALID_DISPLAY);
+ if (token == null) {
+ token = new Binder();
+ }
+ inputChannel.setToken(token);
+
+ nativeRegisterInputChannel(mPtr, inputChannel, Display.INVALID_DISPLAY);
}
/**
@@ -1791,15 +1795,15 @@
}
// Native callback.
- private void notifyInputChannelBroken(InputWindowHandle inputWindowHandle) {
- mWindowManagerCallbacks.notifyInputChannelBroken(inputWindowHandle);
+ private void notifyInputChannelBroken(IBinder token) {
+ mWindowManagerCallbacks.notifyInputChannelBroken(token);
}
// Native callback.
private long notifyANR(InputApplicationHandle inputApplicationHandle,
- InputWindowHandle inputWindowHandle, String reason) {
+ IBinder token, String reason) {
return mWindowManagerCallbacks.notifyANR(
- inputApplicationHandle, inputWindowHandle, reason);
+ inputApplicationHandle, token, reason);
}
// Native callback.
@@ -1830,13 +1834,13 @@
}
// Native callback.
- private long interceptKeyBeforeDispatching(InputWindowHandle focus,
+ private long interceptKeyBeforeDispatching(IBinder focus,
KeyEvent event, int policyFlags) {
return mWindowManagerCallbacks.interceptKeyBeforeDispatching(focus, event, policyFlags);
}
// Native callback.
- private KeyEvent dispatchUnhandledKey(InputWindowHandle focus,
+ private KeyEvent dispatchUnhandledKey(IBinder focus,
KeyEvent event, int policyFlags) {
return mWindowManagerCallbacks.dispatchUnhandledKey(focus, event, policyFlags);
}
@@ -1987,19 +1991,19 @@
public void notifyCameraLensCoverSwitchChanged(long whenNanos, boolean lensCovered);
- public void notifyInputChannelBroken(InputWindowHandle inputWindowHandle);
+ public void notifyInputChannelBroken(IBinder token);
public long notifyANR(InputApplicationHandle inputApplicationHandle,
- InputWindowHandle inputWindowHandle, String reason);
+ IBinder token, String reason);
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags);
public int interceptMotionBeforeQueueingNonInteractive(long whenNanos, int policyFlags);
- public long interceptKeyBeforeDispatching(InputWindowHandle focus,
+ public long interceptKeyBeforeDispatching(IBinder token,
KeyEvent event, int policyFlags);
- public KeyEvent dispatchUnhandledKey(InputWindowHandle focus,
+ public KeyEvent dispatchUnhandledKey(IBinder token,
KeyEvent event, int policyFlags);
public int getPointerLayer();
diff --git a/services/core/java/com/android/server/intelligence/IntelligenceManagerInternal.java b/services/core/java/com/android/server/intelligence/IntelligenceManagerInternal.java
new file mode 100644
index 0000000..0ed56ff
--- /dev/null
+++ b/services/core/java/com/android/server/intelligence/IntelligenceManagerInternal.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.intelligence;
+
+import android.annotation.UserIdInt;
+
+/**
+ * Intelligence Manager local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class IntelligenceManagerInternal {
+
+ /**
+ * Checks whether the given {@code uid} owns the
+ * {@link android.service.intelligence.IntelligenceService} implementation associated with the
+ * given {@code userId}.
+ */
+ public abstract boolean isIntelligenceServiceForUser(int uid, @UserIdInt int userId);
+}
diff --git a/services/core/java/com/android/server/location/ContextHubClientBroker.java b/services/core/java/com/android/server/location/ContextHubClientBroker.java
index 002d4e1..6612d02 100644
--- a/services/core/java/com/android/server/location/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/ContextHubClientBroker.java
@@ -40,6 +40,8 @@
* notification callbacks. This class implements the IContextHubClient object, and the implemented
* APIs must be thread-safe.
*
+ * TODO: Consider refactoring this class via inheritance
+ *
* @hide
*/
public class ContextHubClientBroker extends IContextHubClient.Stub
@@ -92,7 +94,7 @@
/*
* The PendingIntent registered with this client.
*/
- private final PendingIntentRequest mPendingIntentRequest = new PendingIntentRequest();
+ private final PendingIntentRequest mPendingIntentRequest;
/*
* Helper class to manage registered PendingIntent requests from the client.
@@ -130,41 +132,31 @@
public void clear() {
mPendingIntent = null;
}
-
- public boolean register(PendingIntent pendingIntent, long nanoAppId) {
- boolean success = false;
- if (hasPendingIntent()) {
- Log.e(TAG, "Failed to register PendingIntent: registered PendingIntent exists");
- } else {
- mNanoAppId = nanoAppId;
- mPendingIntent = pendingIntent;
- success = true;
- }
-
- return success;
- }
-
- public boolean unregister(PendingIntent pendingIntent) {
- boolean success = false;
- if (!hasPendingIntent() || !mPendingIntent.equals(pendingIntent)) {
- Log.e(TAG, "Failed to unregister PendingIntent: PendingIntent is not registered");
- } else {
- mPendingIntent = null;
- success = true;
- }
-
- return success;
- }
}
/* package */ ContextHubClientBroker(
Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager,
- ContextHubInfo contextHubInfo, short hostEndPointId) {
+ ContextHubInfo contextHubInfo, short hostEndPointId,
+ IContextHubClientCallback callback) {
mContext = context;
mContextHubProxy = contextHubProxy;
mClientManager = clientManager;
mAttachedContextHubInfo = contextHubInfo;
mHostEndPointId = hostEndPointId;
+ mCallbackInterface = callback;
+ mPendingIntentRequest = new PendingIntentRequest();
+ }
+
+ /* package */ ContextHubClientBroker(
+ Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager,
+ ContextHubInfo contextHubInfo, short hostEndPointId, PendingIntent pendingIntent,
+ long nanoAppId) {
+ mContext = context;
+ mContextHubProxy = contextHubProxy;
+ mClientManager = clientManager;
+ mAttachedContextHubInfo = contextHubInfo;
+ mHostEndPointId = hostEndPointId;
+ mPendingIntentRequest = new PendingIntentRequest(pendingIntent, nanoAppId);
}
/**
@@ -179,11 +171,7 @@
ContextHubServiceUtil.checkPermissions(mContext);
int result;
- IContextHubClientCallback callback = null;
- synchronized (this) {
- callback = mCallbackInterface;
- }
- if (callback != null) {
+ if (isRegistered()) {
ContextHubMsg messageToNanoApp =
ContextHubServiceUtil.createHidlContextHubMessage(mHostEndPointId, message);
@@ -204,64 +192,16 @@
}
/**
- * @param pendingIntent the intent to register
- * @param nanoAppId the ID of the nanoapp to send events for
- * @return true on success, false otherwise
- */
- @Override
- public boolean registerIntent(PendingIntent pendingIntent, long nanoAppId) {
- ContextHubServiceUtil.checkPermissions(mContext);
- if (mClientManager.isPendingIntentRegistered(pendingIntent)) {
- Log.e(TAG, "Failed to register PendingIntent: already registered");
- return false;
- }
-
- boolean success = false;
- synchronized (this) {
- if (mCallbackInterface == null) {
- Log.e(TAG, "Failed to register PendingIntent: client connection is closed");
- } else {
- success = mPendingIntentRequest.register(pendingIntent, nanoAppId);
- }
- }
-
- return success;
- }
-
- /**
- * @param pendingIntent the intent to unregister
- * @return true on success, false otherwise
- */
- @Override
- public boolean unregisterIntent(PendingIntent pendingIntent) {
- ContextHubServiceUtil.checkPermissions(mContext);
-
- boolean success = false;
- synchronized (this) {
- success = mPendingIntentRequest.unregister(pendingIntent);
- if (mCallbackInterface == null) {
- close();
- }
- }
-
- return success;
- }
-
- /**
* Closes the connection for this client with the service.
+ *
+ * If the client has a PendingIntent registered, this method also unregisters it.
*/
@Override
public void close() {
synchronized (this) {
- if (mCallbackInterface != null) {
- mCallbackInterface.asBinder().unlinkToDeath(this, 0 /* flags */);
- mCallbackInterface = null;
- }
- if (!mPendingIntentRequest.hasPendingIntent() && mRegistered) {
- mClientManager.unregisterClient(mHostEndPointId);
- mRegistered = false;
- }
+ mPendingIntentRequest.clear();
}
+ onClientExit();
}
/**
@@ -269,38 +209,7 @@
*/
@Override
public void binderDied() {
- close();
- }
-
- /**
- * Sets the callback interface for this client, only if the callback is currently unregistered.
- *
- * Also attaches a death recipient to a ContextHubClientBroker object. If unsuccessful, the
- * connection is closed.
- *
- * @param callback the callback interface
- * @return true if the callback was successfully set, false otherwise
- *
- * @throws IllegalStateException if the client has already been registered to a callback
- */
- /* package */
- synchronized boolean setCallback(IContextHubClientCallback callback) {
- boolean success = false;
- if (mCallbackInterface != null) {
- throw new IllegalStateException("Client is already registered with a callback");
- } else {
- mCallbackInterface = callback;
- try {
- mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */);
- success = true;
- } catch (RemoteException e) {
- // The client process has died, so we close the connection.
- Log.e(TAG, "Failed to attach death recipient to client");
- close();
- }
- }
-
- return success;
+ onClientExit();
}
/**
@@ -375,15 +284,30 @@
}
/**
- * @param intent the PendingIntent to compare to
+ * @param intent the PendingIntent to compare to
+ * @param nanoAppId the ID of the nanoapp of the PendingIntent to compare to
* @return true if the given PendingIntent is currently registered, false otherwise
*/
- /* package */ boolean hasPendingIntent(PendingIntent intent) {
+ /* package */ boolean hasPendingIntent(PendingIntent intent, long nanoAppId) {
PendingIntent pendingIntent = null;
+ long intentNanoAppId;
synchronized (this) {
pendingIntent = mPendingIntentRequest.getPendingIntent();
+ intentNanoAppId = mPendingIntentRequest.getNanoAppId();
}
- return (pendingIntent != null) && pendingIntent.equals(intent);
+ return (pendingIntent != null) && pendingIntent.equals(intent)
+ && intentNanoAppId == nanoAppId;
+ }
+
+ /**
+ * Attaches the death recipient to the callback interface object, if any.
+ *
+ * @throws RemoteException if the client process already died
+ */
+ /* package */ void attachDeathRecipient() throws RemoteException {
+ if (mCallbackInterface != null) {
+ mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */);
+ }
}
/**
@@ -446,11 +370,29 @@
// The PendingIntent is no longer valid
Log.w(TAG, "PendingIntent has been canceled, unregistering from client"
+ " (host endpoint ID " + mHostEndPointId + ")");
- mPendingIntentRequest.clear();
- if (mCallbackInterface == null) {
- close();
- }
+ close();
}
}
}
+
+ /**
+ * @return true if the client is still registered with the service, false otherwise
+ */
+ private synchronized boolean isRegistered() {
+ return mRegistered;
+ }
+
+ /**
+ * Invoked when a client exits either explicitly or by binder death.
+ */
+ private synchronized void onClientExit() {
+ if (mCallbackInterface != null) {
+ mCallbackInterface.asBinder().unlinkToDeath(this, 0 /* flags */);
+ mCallbackInterface = null;
+ }
+ if (!mPendingIntentRequest.hasPendingIntent() && mRegistered) {
+ mClientManager.unregisterClient(mHostEndPointId);
+ mRegistered = false;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/location/ContextHubClientManager.java b/services/core/java/com/android/server/location/ContextHubClientManager.java
index fe93a1a..00b7d62 100644
--- a/services/core/java/com/android/server/location/ContextHubClientManager.java
+++ b/services/core/java/com/android/server/location/ContextHubClientManager.java
@@ -24,6 +24,7 @@
import android.hardware.location.IContextHubClient;
import android.hardware.location.IContextHubClientCallback;
import android.hardware.location.NanoAppMessage;
+import android.os.RemoteException;
import android.util.Log;
import java.util.concurrent.ConcurrentHashMap;
@@ -68,7 +69,7 @@
/*
* The next host endpoint ID to start iterating for the next available host endpoint ID.
*/
- private int mNextHostEndpointId = 0;
+ private int mNextHostEndPointId = 0;
/* package */ ContextHubClientManager(
Context context, IContexthub contextHubProxy) {
@@ -79,18 +80,31 @@
/**
* Registers a new client with the service.
*
- * @param clientCallback the callback interface of the client to register
* @param contextHubInfo the object describing the hub this client is attached to
+ * @param clientCallback the callback interface of the client to register
*
* @return the client interface
*
* @throws IllegalStateException if max number of clients have already registered
*/
/* package */ IContextHubClient registerClient(
- IContextHubClientCallback clientCallback, ContextHubInfo contextHubInfo) {
- ContextHubClientBroker broker = createNewClientBroker(contextHubInfo);
- if (!broker.setCallback(clientCallback)) {
- return null; // Client process has died, so we return null
+ ContextHubInfo contextHubInfo, IContextHubClientCallback clientCallback) {
+ ContextHubClientBroker broker;
+ synchronized (this) {
+ short hostEndPointId = getHostEndPointId();
+ broker = new ContextHubClientBroker(
+ mContext, mContextHubProxy, this /* clientManager */, contextHubInfo,
+ hostEndPointId, clientCallback);
+ mHostEndPointIdToClientMap.put(hostEndPointId, broker);
+ }
+
+ try {
+ broker.attachDeathRecipient();
+ } catch (RemoteException e) {
+ // The client process has died, so we close the connection and return null
+ Log.e(TAG, "Failed to attach death recipient to client");
+ broker.close();
+ return null;
}
Log.d(TAG, "Registered client with host endpoint ID " + broker.getHostEndPointId());
@@ -98,32 +112,34 @@
}
/**
- * Binds a existing and registered client with a new callback interface, provided a previously
- * registered PendingIntent.
+ * Registers a new client with the service.
*
- * @param pendingIntent a previously registered PendingIntent for a registered client
- * @param clientCallback the callback interface of the client to bind to
- * @param contextHubId the ID of the hub this client is attached to
+ * @param pendingIntent the callback interface of the client to register
+ * @param contextHubInfo the object describing the hub this client is attached to
+ * @param nanoAppId the ID of the nanoapp to receive Intent events for
*
* @return the client interface
*
- * @throws IllegalArgumentException if no matching client is found
- * @throws IllegalStateException if the client has already been registered to a callback
+ * @throws IllegalStateException if there were too many registered clients at the service
*/
- /* package */ IContextHubClient bindClient(
- PendingIntent pendingIntent, IContextHubClientCallback clientCallback,
- int contextHubId) {
- ContextHubClientBroker broker = getClientBroker(pendingIntent, contextHubId);
- if (broker == null) {
- throw new IllegalArgumentException("Could not find client of Context Hub (ID = "
- + contextHubId + ") with PendingIntent");
+ /* package */ IContextHubClient registerClient(
+ ContextHubInfo contextHubInfo, PendingIntent pendingIntent, long nanoAppId) {
+ ContextHubClientBroker broker;
+ String registerString = "Regenerated";
+ synchronized (this) {
+ broker = getClientBroker(contextHubInfo.getId(), pendingIntent, nanoAppId);
+
+ if (broker == null) {
+ short hostEndPointId = getHostEndPointId();
+ broker = new ContextHubClientBroker(
+ mContext, mContextHubProxy, this /* clientManager */, contextHubInfo,
+ hostEndPointId, pendingIntent, nanoAppId);
+ mHostEndPointIdToClientMap.put(hostEndPointId, broker);
+ registerString = "Registered";
+ }
}
- if (!broker.setCallback(clientCallback)) {
- return null; // Client process has died, so we return null
- }
-
- Log.d(TAG, "Re-registered client with host endpoint ID " + broker.getHostEndPointId());
+ Log.d(TAG, registerString + " client with host endpoint ID " + broker.getHostEndPointId());
return IContextHubClient.Stub.asInterface(broker);
}
@@ -203,50 +219,28 @@
}
/**
- * @param pendingIntent the PendingIntent to check
- * @return true if the given PendingIntent is registered by a client, false otherwise
- */
- /* package */ boolean isPendingIntentRegistered(PendingIntent pendingIntent) {
- for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) {
- if (broker.hasPendingIntent(pendingIntent)) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Creates a new ContextHubClientBroker object for a client and registers it with the client
- * manager.
+ * Returns an available host endpoint ID.
*
- * @param contextHubInfo the object describing the hub this client is attached to
- *
- * @return the ContextHubClientBroker object
+ * @returns an available host endpoint ID
*
* @throws IllegalStateException if max number of clients have already registered
*/
- private synchronized ContextHubClientBroker createNewClientBroker(
- ContextHubInfo contextHubInfo) {
+ private short getHostEndPointId() {
if (mHostEndPointIdToClientMap.size() == MAX_CLIENT_ID + 1) {
throw new IllegalStateException("Could not register client - max limit exceeded");
}
- ContextHubClientBroker broker = null;
- int id = mNextHostEndpointId;
+ int id = mNextHostEndPointId;
for (int i = 0; i <= MAX_CLIENT_ID; i++) {
if (!mHostEndPointIdToClientMap.containsKey((short) id)) {
- broker = new ContextHubClientBroker(
- mContext, mContextHubProxy, this, contextHubInfo, (short) id);
- mHostEndPointIdToClientMap.put((short) id, broker);
- mNextHostEndpointId = (id == MAX_CLIENT_ID) ? 0 : id + 1;
+ mNextHostEndPointId = (id == MAX_CLIENT_ID) ? 0 : id + 1;
break;
}
id = (id == MAX_CLIENT_ID) ? 0 : id + 1;
}
- return broker;
+ return (short) id;
}
/**
@@ -280,9 +274,10 @@
* @param contextHubId the ID of the Context Hub the client is attached to
* @return the matching ContextHubClientBroker, null if not found
*/
- private ContextHubClientBroker getClientBroker(PendingIntent pendingIntent, int contextHubId) {
+ private ContextHubClientBroker getClientBroker(
+ int contextHubId, PendingIntent pendingIntent, long nanoAppId) {
for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) {
- if (broker.hasPendingIntent(pendingIntent)
+ if (broker.hasPendingIntent(pendingIntent, nanoAppId)
&& broker.getAttachedContextHubId() == contextHubId) {
return broker;
}
diff --git a/services/core/java/com/android/server/location/ContextHubService.java b/services/core/java/com/android/server/location/ContextHubService.java
index 215e67c..36b0342 100644
--- a/services/core/java/com/android/server/location/ContextHubService.java
+++ b/services/core/java/com/android/server/location/ContextHubService.java
@@ -173,7 +173,7 @@
for (int contextHubId : mContextHubIdToInfoMap.keySet()) {
ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
IContextHubClient client = mClientManager.registerClient(
- createDefaultClientCallback(contextHubId), contextHubInfo);
+ contextHubInfo, createDefaultClientCallback(contextHubId));
defaultClientMap.put(contextHubId, client);
try {
@@ -608,8 +608,8 @@
/**
* Creates and registers a client at the service for the specified Context Hub.
*
- * @param clientCallback the client interface to register with the service
* @param contextHubId the ID of the hub this client is attached to
+ * @param clientCallback the client interface to register with the service
* @return the generated client interface, null if registration was unsuccessful
*
* @throws IllegalArgumentException if contextHubId is not a valid ID
@@ -618,7 +618,7 @@
*/
@Override
public IContextHubClient createClient(
- IContextHubClientCallback clientCallback, int contextHubId) throws RemoteException {
+ int contextHubId, IContextHubClientCallback clientCallback) throws RemoteException {
checkPermissions();
if (!isValidContextHubId(contextHubId)) {
throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
@@ -628,38 +628,30 @@
}
ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
- return mClientManager.registerClient(clientCallback, contextHubInfo);
+ return mClientManager.registerClient(contextHubInfo, clientCallback);
}
/**
- * Recreates and binds a IContextHubClientCallback interface to an existing and registered
- * client at the service for the specified Context Hub, provided a previously registered
- * PendingIntent.
+ * Creates and registers a PendingIntent client at the service for the specified Context Hub.
*
- * @param pendingIntent the PendingIntent previously registered for the client
- * @param clientCallback the client interface to register with the service
- * @param contextHubId the ID of the hub this client is attached to
- * @return the generated client interface, null if registration was unsuccessful
+ * @param contextHubId the ID of the hub this client is attached to
+ * @param pendingIntent the PendingIntent associated with this client
+ * @param nanoAppId the ID of the nanoapp PendingIntent events will be sent for
+ * @return the generated client interface
*
- * @throws IllegalArgumentException if contextHubId is not a valid ID
- * @throws NullPointerException if clientCallback or pendingIntent is null
+ * @throws IllegalArgumentException if hubInfo does not represent a valid hub
+ * @throws IllegalStateException if there were too many registered clients at the service
*/
@Override
- public IContextHubClient bindClient(
- PendingIntent pendingIntent, IContextHubClientCallback clientCallback,
- int contextHubId) throws RemoteException {
+ public IContextHubClient createPendingIntentClient(
+ int contextHubId, PendingIntent pendingIntent, long nanoAppId) throws RemoteException {
checkPermissions();
if (!isValidContextHubId(contextHubId)) {
throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
}
- if (pendingIntent == null) {
- throw new NullPointerException("Cannot create client with null pending intent");
- }
- if (clientCallback == null) {
- throw new NullPointerException("Cannot create client with null callback");
- }
- return mClientManager.bindClient(pendingIntent, clientCallback, contextHubId);
+ ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
+ return mClientManager.registerClient(contextHubInfo, pendingIntent, nanoAppId);
}
/**
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index dc4405f..9d402b3 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -30,12 +30,9 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.media.AudioManager;
@@ -45,8 +42,6 @@
import android.media.IRemoteVolumeController;
import android.media.ISessionTokensListener;
import android.media.MediaController2;
-import android.media.MediaLibraryService2;
-import android.media.MediaSessionService2;
import android.media.SessionToken2;
import android.media.session.IActiveSessionsListener;
import android.media.session.ICallback;
@@ -81,7 +76,6 @@
import android.view.KeyEvent;
import android.view.ViewConfiguration;
-import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.server.SystemService;
import com.android.server.Watchdog;
@@ -90,10 +84,8 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.NoSuchElementException;
/**
@@ -187,10 +179,6 @@
PackageManager.FEATURE_LEANBACK);
updateUser();
-
- registerPackageBroadcastReceivers();
- // TODO(jaewan): Query per users (b/73597722)
- buildMediaSessionService2List();
}
private IAudioService getAudioService() {
@@ -444,148 +432,6 @@
mHandler.postSessionsChanged(session.getUserId());
}
- private void registerPackageBroadcastReceivers() {
- // TODO(jaewan): Only consider changed packages when building session service list
- // when we make this multi-user aware. At that time,
- // use PackageMonitor.getChangingUserId() to know which user has changed.
- // (b/73597722)
- IntentFilter filter = new IntentFilter();
- filter.addDataScheme("package");
- filter.addAction(Intent.ACTION_PACKAGE_ADDED);
- filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- filter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
- filter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
- filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
- filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
- filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
-
- getContext().registerReceiverAsUser(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final int changeUserId = intent.getIntExtra(
- Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
- if (changeUserId == UserHandle.USER_NULL) {
- Log.w(TAG, "Intent broadcast does not contain user handle: "+ intent);
- return;
- }
- // Check if the package is replacing (i.e. reinstalling)
- final boolean isReplacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
- // TODO(jaewan): Add multi-user support with this. (b/73597722)
- // final int uid = intent.getIntExtra(Intent.EXTRA_UID, 0);
-
- if (DEBUG) {
- Log.d(TAG, "Received change in packages, intent=" + intent);
- }
- switch (intent.getAction()) {
- case Intent.ACTION_PACKAGE_ADDED:
- case Intent.ACTION_PACKAGE_REMOVED:
- case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
- case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
- if (isReplacing) {
- // Ignore if the package(s) are replacing. In that case, followings will
- // happen in order.
- // 1. ACTION_PACKAGE_REMOVED with isReplacing=true
- // 2. ACTION_PACKAGE_ADDED with isReplacing=true
- // 3. ACTION_PACKAGE_REPLACED
- // (Note that ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE and
- // ACTION_EXTERNAL_APPLICATIONS_AVAILABLE will be also called with
- // isReplacing=true for both ASEC hosted packages and packages in
- // external storage)
- // Since we only want to update session service list once, ignore
- // actions above when replacing.
- // Replacing will be handled only once with the ACTION_PACKAGE_REPLACED.
- break;
- }
- // pass-through
- case Intent.ACTION_PACKAGE_CHANGED:
- case Intent.ACTION_PACKAGES_SUSPENDED:
- case Intent.ACTION_PACKAGES_UNSUSPENDED:
- case Intent.ACTION_PACKAGE_REPLACED:
- buildMediaSessionService2List();
- }
- }
- }, UserHandle.ALL, filter, null, BackgroundThread.getHandler());
- }
-
- private void buildMediaSessionService2List() {
- if (!USE_MEDIA2_APIS) {
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "buildMediaSessionService2List");
- }
- // TODO(jaewan): Also query for managed profile users. (b/73597722)
- PackageManager manager = getContext().getPackageManager();
- List<ResolveInfo> services = new ArrayList<>();
- // If multiple actions are declared for a service, browser gets higher priority.
- List<ResolveInfo> libraryServices = manager.queryIntentServices(
- new Intent(MediaLibraryService2.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
- if (libraryServices != null) {
- services.addAll(libraryServices);
- }
- List<ResolveInfo> sessionServices = manager.queryIntentServices(
- new Intent(MediaSessionService2.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
- if (sessionServices != null) {
- services.addAll(sessionServices);
- }
- synchronized (mLock) {
- // List to keep the session services that need be removed because they don't exist
- // in the 'services' above.
- boolean notifySessionTokensUpdated = false;
- Set<SessionToken2> sessionTokensToRemove = new HashSet<>();
- for (SessionToken2 token : mSessionRecords.keySet()) {
- if (token.getType() != TYPE_SESSION) {
- sessionTokensToRemove.add(token);
- }
- }
-
- for (int i = 0; i < services.size(); i++) {
- if (services.get(i) == null || services.get(i).serviceInfo == null) {
- continue;
- }
- ServiceInfo serviceInfo = services.get(i).serviceInfo;
- int uid;
- try {
- // TODO(jaewan): Do this per user. (b/73597722)
- uid = manager.getPackageUid(serviceInfo.packageName,
- PackageManager.GET_META_DATA);
- } catch (NameNotFoundException e) {
- continue;
- }
- SessionToken2 token;
- try {
- token = new SessionToken2(getContext(),
- serviceInfo.packageName, serviceInfo.name, uid);
- } catch (IllegalArgumentException e) {
- Log.w(TAG, "Invalid session service", e);
- continue;
- }
- // If the token already exists, keep it in the mSessions by removing from
- // sessionTokensToRemove.
- if (!sessionTokensToRemove.remove(token)) {
- // New session service is found.
- notifySessionTokensUpdated |= addSessionRecordLocked(token);
- }
- }
- for (SessionToken2 token : sessionTokensToRemove) {
- mSessionRecords.remove(token);
- notifySessionTokensUpdated |= removeSessionRecordLocked(token);
- }
-
- if (notifySessionTokensUpdated) {
- // TODO(jaewan): Pass proper user id to postSessionTokensUpdated(...)
- postSessionTokensUpdated(UserHandle.USER_ALL);
- }
- }
- if (DEBUG) {
- Log.d(TAG, "Found " + mSessionRecords.size() + " session services");
- for (SessionToken2 token : mSessionRecords.keySet()) {
- Log.d(TAG, " " + token);
- }
- }
- }
-
private void enforcePackageName(String packageName, int uid) {
if (TextUtils.isEmpty(packageName)) {
throw new IllegalArgumentException("packageName may not be empty");
@@ -2245,11 +2091,7 @@
synchronized (mLock) {
List<Bundle> tokens = new ArrayList<>();
for (SessionToken2 token : mSessionRecords.keySet()) {
- // TODO(jaewan): Remove the check for UserHandle.USER_ALL (shouldn't happen).
- // This happens when called form buildMediaSessionService2List(...).
- // (b/73760382)
- if (UserHandle.getUserId(token.getUid()) == userId
- || UserHandle.USER_ALL == userId) {
+ if (UserHandle.getUserId(token.getUid()) == userId) {
tokens.add(token.toBundle());
}
}
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index a6ea6b2..fccff57 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -396,8 +396,9 @@
}
synchronized (mLock) {
if (isValidMediaProjection(asBinder())) {
- throw new IllegalStateException(
- "Cannot start already started MediaProjection");
+ Slog.w(TAG, "UID " + Binder.getCallingUid()
+ + " attempted to start already started MediaProjection");
+ return;
}
mCallback = callback;
registerCallback(mCallback);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 1c7572e..32990ce 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -86,6 +86,7 @@
import static com.android.server.utils.PriorityDump.PRIORITY_ARG_NORMAL;
import android.Manifest;
+import android.Manifest.permission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -398,6 +399,10 @@
private static final String TAG_NOTIFICATION_POLICY = "notification-policy";
private static final String ATTR_VERSION = "version";
+ private static final String LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_TAG =
+ "allow-secure-notifications-on-lockscreen";
+ private static final String LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_VALUE = "value";
+
private RankingHelper mRankingHelper;
private PreferencesHelper mPreferencesHelper;
@@ -406,6 +411,7 @@
private NotificationAssistants mAssistants;
private ConditionProviders mConditionProviders;
private NotificationUsageStats mUsageStats;
+ private boolean mLockScreenAllowSecureNotifications;
private static final int MY_UID = Process.myUid();
private static final int MY_PID = Process.myPid();
@@ -552,6 +558,11 @@
mConditionProviders.readXml(parser, mAllowedManagedServicePackages);
migratedManagedServices = true;
}
+ if (LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_TAG.equals(parser.getName())) {
+ mLockScreenAllowSecureNotifications =
+ safeBoolean(parser.getAttributeValue(null,
+ LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_VALUE), true);
+ }
}
if (!migratedManagedServices) {
@@ -626,6 +637,7 @@
mListeners.writeXml(out, forBackup);
mAssistants.writeXml(out, forBackup);
mConditionProviders.writeXml(out, forBackup);
+ writeSecureNotificationsPolicy(out);
out.endTag(null, TAG_NOTIFICATION_POLICY);
out.endDocument();
}
@@ -870,6 +882,7 @@
}
if (expanded && userAction) {
r.recordExpanded();
+ reportUserInteraction(r);
}
EventLogTags.writeNotificationExpansion(key,
userAction ? 1 : 0, expanded ? 1 : 0,
@@ -885,6 +898,9 @@
NotificationRecord r = mNotificationsByKey.get(key);
if (r != null) {
r.recordDirectReplied();
+ mMetricsLogger.write(r.getLogMaker()
+ .setCategory(MetricsEvent.NOTIFICATION_DIRECT_REPLY_ACTION)
+ .setType(MetricsEvent.TYPE_ACTION));
reportUserInteraction(r);
}
}
@@ -1160,6 +1176,7 @@
mConditionProviders.onUserSwitched(userId);
mListeners.onUserSwitched(userId);
mZenModeHelper.onUserSwitched(userId);
+ mPreferencesHelper.onUserSwitched(userId);
}
// assistant is the only thing that cares about managed profiles specifically
mAssistants.onUserSwitched(userId);
@@ -1188,6 +1205,7 @@
mConditionProviders.onUserUnlocked(userId);
mListeners.onUserUnlocked(userId);
mZenModeHelper.onUserUnlocked(userId);
+ mPreferencesHelper.onUserUnlocked(userId);
}
}
}
@@ -1982,7 +2000,7 @@
}
/**
- * Report to usage stats that the notification was clicked.
+ * Report to usage stats that the user interacted with the notification.
* @param r notification record
*/
protected void reportUserInteraction(NotificationRecord r) {
@@ -2525,6 +2543,19 @@
}
@Override
+ public int getAppsBypassingDndCount(int userId) {
+ checkCallerIsSystem();
+ return mPreferencesHelper.getAppsBypassingDndCount(userId);
+ }
+
+ @Override
+ public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(
+ String pkg, int userId) {
+ checkCallerIsSystem();
+ return mPreferencesHelper.getNotificationChannelsBypassingDnd(pkg, userId);
+ }
+
+ @Override
public boolean areChannelsBypassingDnd() {
return mPreferencesHelper.areChannelsBypassingDnd();
}
@@ -3674,6 +3705,31 @@
return new ParceledListSlice<>(groups);
}
+ @Override
+ public void setPrivateNotificationsAllowed(boolean allow) {
+ if (PackageManager.PERMISSION_GRANTED
+ != getContext().checkCallingPermission(
+ permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)) {
+ throw new SecurityException(
+ "Requires CONTROL_KEYGUARD_SECURE_NOTIFICATIONS permission");
+ }
+ if (allow != mLockScreenAllowSecureNotifications) {
+ mLockScreenAllowSecureNotifications = allow;
+ savePolicyFile();
+ }
+ }
+
+ @Override
+ public boolean getPrivateNotificationsAllowed() {
+ if (PackageManager.PERMISSION_GRANTED
+ != getContext().checkCallingPermission(
+ permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)) {
+ throw new SecurityException(
+ "Requires CONTROL_KEYGUARD_SECURE_NOTIFICATIONS permission");
+ }
+ return mLockScreenAllowSecureNotifications;
+ }
+
private void verifyPrivilegedListener(INotificationListener token, UserHandle user,
boolean assistantAllowed) {
ManagedServiceInfo info;
@@ -4521,6 +4577,7 @@
mDuration)
.addTaggedData(MetricsEvent.NOTIFICATION_SNOOZED_CRITERIA,
mSnoozeCriterionId == null ? 0 : 1));
+ reportUserInteraction(r);
boolean wasPosted = removeFromNotificationListsLocked(r);
cancelNotificationLocked(r, false, REASON_SNOOZED, wasPosted, null);
updateLightsLocked();
@@ -5529,7 +5586,7 @@
ArrayList<ArrayList<SnoozeCriterion>> snoozeCriteriaBefore = new ArrayList<>(N);
ArrayList<Integer> userSentimentBefore = new ArrayList<>(N);
ArrayList<Integer> suppressVisuallyBefore = new ArrayList<>(N);
- ArrayList<ArrayList<Notification.Action>> smartActionsBefore = new ArrayList<>(N);
+ ArrayList<ArrayList<Notification.Action>> systemSmartActionsBefore = new ArrayList<>(N);
ArrayList<ArrayList<CharSequence>> smartRepliesBefore = new ArrayList<>(N);
for (int i = 0; i < N; i++) {
final NotificationRecord r = mNotificationList.get(i);
@@ -5542,7 +5599,7 @@
snoozeCriteriaBefore.add(r.getSnoozeCriteria());
userSentimentBefore.add(r.getUserSentiment());
suppressVisuallyBefore.add(r.getSuppressedVisualEffects());
- smartActionsBefore.add(r.getSmartActions());
+ systemSmartActionsBefore.add(r.getSystemGeneratedSmartActions());
smartRepliesBefore.add(r.getSmartReplies());
mRankingHelper.extractSignals(r);
}
@@ -5559,7 +5616,8 @@
|| !Objects.equals(userSentimentBefore.get(i), r.getUserSentiment())
|| !Objects.equals(suppressVisuallyBefore.get(i),
r.getSuppressedVisualEffects())
- || !Objects.equals(smartActionsBefore.get(i), r.getSmartActions())
+ || !Objects.equals(systemSmartActionsBefore.get(i),
+ r.getSystemGeneratedSmartActions())
|| !Objects.equals(smartRepliesBefore.get(i), r.getSmartReplies())) {
mHandler.scheduleSendRankingUpdate();
return;
@@ -6561,7 +6619,7 @@
Bundle showBadge = new Bundle();
Bundle userSentiment = new Bundle();
Bundle hidden = new Bundle();
- Bundle smartActions = new Bundle();
+ Bundle systemGeneratedSmartActions = new Bundle();
Bundle smartReplies = new Bundle();
Bundle audiblyAlerted = new Bundle();
Bundle noisy = new Bundle();
@@ -6592,7 +6650,8 @@
showBadge.putBoolean(key, record.canShowBadge());
userSentiment.putInt(key, record.getUserSentiment());
hidden.putBoolean(key, record.isHidden());
- smartActions.putParcelableArrayList(key, record.getSmartActions());
+ systemGeneratedSmartActions.putParcelableArrayList(key,
+ record.getSystemGeneratedSmartActions());
smartReplies.putCharSequenceArrayList(key, record.getSmartReplies());
audiblyAlerted.putBoolean(key, record.getAudiblyAlerted());
noisy.putBoolean(key, record.getSound() != null || record.getVibration() != null);
@@ -6607,7 +6666,7 @@
return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides,
suppressedVisualEffects, importanceAr, explanation, overrideGroupKeys,
channels, overridePeople, snoozeCriteria, showBadge, userSentiment, hidden,
- smartActions, smartReplies, audiblyAlerted, noisy);
+ systemGeneratedSmartActions, smartReplies, audiblyAlerted, noisy);
}
boolean hasCompanionDevice(ManagedServiceInfo info) {
@@ -7540,4 +7599,16 @@
getOutPrintWriter().println(USAGE);
}
}
+
+ private void writeSecureNotificationsPolicy(XmlSerializer out) throws IOException {
+ out.startTag(null, LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_TAG);
+ out.attribute(null, LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_VALUE,
+ Boolean.toString(mLockScreenAllowSecureNotifications));
+ out.endTag(null, LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_TAG);
+ }
+
+ private static boolean safeBoolean(String val, boolean defValue) {
+ if (TextUtils.isEmpty(val)) return defValue;
+ return Boolean.parseBoolean(val);
+ }
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index a11b03f..1a9257c 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -163,7 +163,11 @@
private Light mLight;
private String mGroupLogTag;
private String mChannelIdLogTag;
- private ArrayList<Notification.Action> mSmartActions;
+ /**
+ * This list contains system generated smart actions from NAS, app-generated smart actions are
+ * stored in Notification.actions marked as SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION.
+ */
+ private ArrayList<Notification.Action> mSystemGeneratedSmartActions;
private ArrayList<CharSequence> mSmartReplies;
private final List<Adjustment> mAdjustments;
@@ -653,10 +657,11 @@
}
}
if (signals.containsKey(Adjustment.KEY_SMART_ACTIONS)) {
- setSmartActions(signals.getParcelableArrayList(Adjustment.KEY_SMART_ACTIONS));
+ setSystemGeneratedSmartActions(
+ signals.getParcelableArrayList(Adjustment.KEY_SMART_ACTIONS));
MetricsLogger.action(getAdjustmentLogMaker()
.addTaggedData(MetricsEvent.ADJUSTMENT_KEY_SMART_ACTIONS,
- getSmartActions().size()));
+ getSystemGeneratedSmartActions().size()));
}
if (signals.containsKey(Adjustment.KEY_SMART_REPLIES)) {
setSmartReplies(signals.getCharSequenceArrayList(Adjustment.KEY_SMART_REPLIES));
@@ -1132,12 +1137,13 @@
mHasSeenSmartReplies = hasSeenSmartReplies;
}
- public void setSmartActions(ArrayList<Notification.Action> smartActions) {
- mSmartActions = smartActions;
+ public void setSystemGeneratedSmartActions(
+ ArrayList<Notification.Action> systemGeneratedSmartActions) {
+ mSystemGeneratedSmartActions = systemGeneratedSmartActions;
}
- public ArrayList<Notification.Action> getSmartActions() {
- return mSmartActions;
+ public ArrayList<Notification.Action> getSystemGeneratedSmartActions() {
+ return mSystemGeneratedSmartActions;
}
public void setSmartReplies(ArrayList<CharSequence> smartReplies) {
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 8fce5e3..fd65ebe 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -111,7 +111,6 @@
// pkg => PackagePreferences
private final ArrayMap<String, PackagePreferences> mRestoredWithoutUids = new ArrayMap<>();
-
private final Context mContext;
private final PackageManager mPm;
private final RankingHandler mRankingHandler;
@@ -120,7 +119,6 @@
private SparseBooleanArray mBadgingEnabled;
private boolean mAreChannelsBypassingDnd;
-
public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
ZenModeHelper zenHelper) {
mContext = context;
@@ -129,11 +127,7 @@
mPm = pm;
updateBadgingEnabled();
-
- mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state &
- NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
- updateChannelsBypassingDnd();
-
+ syncChannelsBypassingDnd(mContext.getUserId());
}
public void readXml(XmlPullParser parser, boolean forRestore)
@@ -525,6 +519,7 @@
// but the system can
if (group.isBlocked() != oldGroup.isBlocked()) {
group.lockFields(NotificationChannelGroup.USER_LOCKED_BLOCKED_STATE);
+ updateChannelsBypassingDnd(mContext.getUserId());
}
if (group.canOverlayApps() != oldGroup.canOverlayApps()) {
group.lockFields(NotificationChannelGroup.USER_LOCKED_ALLOW_APP_OVERLAY);
@@ -571,6 +566,7 @@
// Apps are allowed to downgrade channel importance if the user has not changed any
// fields on this channel yet.
+ final int previousExistingImportance = existing.getImportance();
if (existing.getUserLockedFields() == 0 &&
channel.getImportance() < existing.getImportance()) {
existing.setImportance(channel.getImportance());
@@ -582,8 +578,9 @@
boolean bypassDnd = channel.canBypassDnd();
existing.setBypassDnd(bypassDnd);
- if (bypassDnd != mAreChannelsBypassingDnd) {
- updateChannelsBypassingDnd();
+ if (bypassDnd != mAreChannelsBypassingDnd
+ || previousExistingImportance != existing.getImportance()) {
+ updateChannelsBypassingDnd(mContext.getUserId());
}
}
@@ -613,7 +610,7 @@
r.channels.put(channel.getId(), channel);
if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
- updateChannelsBypassingDnd();
+ updateChannelsBypassingDnd(mContext.getUserId());
}
MetricsLogger.action(getChannelLog(channel, pkg).setType(
com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
@@ -663,8 +660,9 @@
MetricsLogger.action(getChannelLog(updatedChannel, pkg));
}
- if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd) {
- updateChannelsBypassingDnd();
+ if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd
+ || channel.getImportance() != updatedChannel.getImportance()) {
+ updateChannelsBypassingDnd(mContext.getUserId());
}
updateConfig();
}
@@ -701,7 +699,7 @@
MetricsLogger.action(lm);
if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
- updateChannelsBypassingDnd();
+ updateChannelsBypassingDnd(mContext.getUserId());
}
}
}
@@ -859,6 +857,27 @@
}
/**
+ * Gets all notification channels associated with the given pkg and userId that can bypass dnd
+ */
+ public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(String pkg,
+ int userId) {
+ List<NotificationChannel> channels = new ArrayList<>();
+ synchronized (mPackagePreferences) {
+ final PackagePreferences r = mPackagePreferences.get(
+ packagePreferencesKey(pkg, userId));
+ // notifications from this package aren't blocked
+ if (r != null && r.importance != IMPORTANCE_NONE) {
+ for (NotificationChannel channel : r.channels.values()) {
+ if (channelIsLive(r, channel) && channel.canBypassDnd()) {
+ channels.add(channel);
+ }
+ }
+ }
+ }
+ return new ParceledListSlice<>(channels);
+ }
+
+ /**
* True for pre-O apps that only have the default channel, or pre O apps that have no
* channels yet. This method will create the default channel for pre-O apps that don't have it.
* Should never be true for O+ targeting apps, but that's enforced on boot/when an app
@@ -922,18 +941,62 @@
return count;
}
- public void updateChannelsBypassingDnd() {
+ /**
+ * Returns the number of apps that have at least one notification channel that can bypass DND
+ * for given particular user
+ */
+ public int getAppsBypassingDndCount(int userId) {
+ int count = 0;
synchronized (mPackagePreferences) {
- final int numPackagePreferencess = mPackagePreferences.size();
- for (int PackagePreferencesIndex = 0; PackagePreferencesIndex < numPackagePreferencess;
- PackagePreferencesIndex++) {
- final PackagePreferences r = mPackagePreferences.valueAt(PackagePreferencesIndex);
- final int numChannels = r.channels.size();
+ final int numPackagePreferences = mPackagePreferences.size();
+ for (int i = 0; i < numPackagePreferences; i++) {
+ final PackagePreferences r = mPackagePreferences.valueAt(i);
+ // Package isn't associated with this userId or notifications from this package are
+ // blocked
+ if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
+ continue;
+ }
- for (int channelIndex = 0; channelIndex < numChannels; channelIndex++) {
- NotificationChannel channel = r.channels.valueAt(channelIndex);
- if (!channel.isDeleted() && channel.canBypassDnd()) {
- // If any channel bypasses DND, synchronize state and return early.
+ for (NotificationChannel channel : r.channels.values()) {
+ if (channelIsLive(r, channel) && channel.canBypassDnd()) {
+ count++;
+ break;
+ }
+ }
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Syncs {@link #mAreChannelsBypassingDnd} with the user's notification policy before
+ * updating
+ * @param userId
+ */
+ private void syncChannelsBypassingDnd(int userId) {
+ mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state
+ & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
+ updateChannelsBypassingDnd(userId);
+ }
+
+ /**
+ * Updates the user's NotificationPolicy based on whether the given userId
+ * has channels bypassing DND
+ * @param userId
+ */
+ private void updateChannelsBypassingDnd(int userId) {
+ synchronized (mPackagePreferences) {
+ final int numPackagePreferences = mPackagePreferences.size();
+ for (int i = 0; i < numPackagePreferences; i++) {
+ final PackagePreferences r = mPackagePreferences.valueAt(i);
+ // Package isn't associated with this userId or notifications from this package are
+ // blocked
+ if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
+ continue;
+ }
+
+ for (NotificationChannel channel : r.channels.values()) {
+ if (channelIsLive(r, channel) && channel.canBypassDnd()) {
if (!mAreChannelsBypassingDnd) {
mAreChannelsBypassingDnd = true;
updateZenPolicy(true);
@@ -943,7 +1006,6 @@
}
}
}
-
// If no channels bypass DND, update the zen policy once to disable DND bypass.
if (mAreChannelsBypassingDnd) {
mAreChannelsBypassingDnd = false;
@@ -951,6 +1013,22 @@
}
}
+ private boolean channelIsLive(PackagePreferences pkgPref, NotificationChannel channel) {
+ // Channel is in a group that's blocked
+ if (!TextUtils.isEmpty(channel.getGroup())) {
+ if (pkgPref.groups.get(channel.getGroup()).isBlocked()) {
+ return false;
+ }
+ }
+
+ // Channel is deleted or is blocked
+ if (channel.isDeleted() || channel.getImportance() == IMPORTANCE_NONE) {
+ return false;
+ }
+
+ return true;
+ }
+
public void updateZenPolicy(boolean areChannelsBypassingDnd) {
NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
@@ -1329,6 +1407,20 @@
return packageChannels;
}
+ /**
+ * Called when user switches
+ */
+ public void onUserSwitched(int userId) {
+ syncChannelsBypassingDnd(userId);
+ }
+
+ /**
+ * Called when user is unlocked
+ */
+ public void onUserUnlocked(int userId) {
+ syncChannelsBypassingDnd(userId);
+ }
+
public void onUserRemoved(int userId) {
synchronized (mPackagePreferences) {
int N = mPackagePreferences.size();
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index 807c343..731e6bc 100644
--- a/services/core/java/com/android/server/om/IdmapManager.java
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -16,36 +16,46 @@
package com.android.server.om;
+import static android.content.Context.IDMAP_SERVICE;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+
import static com.android.server.om.OverlayManagerService.DEBUG;
import static com.android.server.om.OverlayManagerService.TAG;
import android.annotation.NonNull;
import android.content.om.OverlayInfo;
import android.content.pm.PackageInfo;
+import android.os.IBinder;
+import android.os.IIdmap2;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.Slog;
-import com.android.server.pm.Installer.InstallerException;
+import com.android.internal.os.BackgroundThread;
import com.android.server.pm.Installer;
-import java.io.DataInputStream;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
/**
* Handle the creation and deletion of idmap files.
*
* The actual work is performed by the idmap binary, launched through Installer
- * and installd.
+ * and installd (or idmap2).
*
* Note: this class is subclassed in the OMS unit tests, and hence not marked as final.
*/
class IdmapManager {
+ private static final boolean FEATURE_FLAG_IDMAP2 = false;
+
private final Installer mInstaller;
+ private IIdmap2 mIdmap2Service;
IdmapManager(final Installer installer) {
mInstaller = installer;
+ if (FEATURE_FLAG_IDMAP2) {
+ connectToIdmap2d();
+ }
}
boolean createIdmap(@NonNull final PackageInfo targetPackage,
@@ -59,8 +69,12 @@
final String targetPath = targetPackage.applicationInfo.getBaseCodePath();
final String overlayPath = overlayPackage.applicationInfo.getBaseCodePath();
try {
- mInstaller.idmap(targetPath, overlayPath, sharedGid);
- } catch (InstallerException e) {
+ if (FEATURE_FLAG_IDMAP2) {
+ mIdmap2Service.createIdmap(targetPath, overlayPath, userId);
+ } else {
+ mInstaller.idmap(targetPath, overlayPath, sharedGid);
+ }
+ } catch (Exception e) {
Slog.w(TAG, "failed to generate idmap for " + targetPath + " and "
+ overlayPath + ": " + e.getMessage());
return false;
@@ -69,13 +83,16 @@
}
boolean removeIdmap(@NonNull final OverlayInfo oi, final int userId) {
- // unused userId: see comment in OverlayManagerServiceImpl.removeIdmapIfPossible
if (DEBUG) {
Slog.d(TAG, "remove idmap for " + oi.baseCodePath);
}
try {
- mInstaller.removeIdmap(oi.baseCodePath);
- } catch (InstallerException e) {
+ if (FEATURE_FLAG_IDMAP2) {
+ mIdmap2Service.removeIdmap(oi.baseCodePath, userId);
+ } else {
+ mInstaller.removeIdmap(oi.baseCodePath);
+ }
+ } catch (Exception e) {
Slog.w(TAG, "failed to remove idmap for " + oi.baseCodePath + ": " + e.getMessage());
return false;
}
@@ -83,19 +100,58 @@
}
boolean idmapExists(@NonNull final OverlayInfo oi) {
- // unused OverlayInfo.userId: see comment in OverlayManagerServiceImpl.removeIdmapIfPossible
- return new File(getIdmapPath(oi.baseCodePath)).isFile();
+ return new File(getIdmapPath(oi.baseCodePath, oi.userId)).isFile();
}
boolean idmapExists(@NonNull final PackageInfo overlayPackage, final int userId) {
- // unused userId: see comment in OverlayManagerServiceImpl.removeIdmapIfPossible
- return new File(getIdmapPath(overlayPackage.applicationInfo.getBaseCodePath())).isFile();
+ return new File(getIdmapPath(overlayPackage.applicationInfo.getBaseCodePath(), userId))
+ .isFile();
}
- private String getIdmapPath(@NonNull final String baseCodePath) {
- final StringBuilder sb = new StringBuilder("/data/resource-cache/");
- sb.append(baseCodePath.substring(1).replace('/', '@'));
- sb.append("@idmap");
- return sb.toString();
+ private @NonNull String getIdmapPath(@NonNull final String overlayPackagePath,
+ final int userId) {
+ if (FEATURE_FLAG_IDMAP2) {
+ try {
+ return mIdmap2Service.getIdmapPath(overlayPackagePath, userId);
+ } catch (Exception e) {
+ Slog.w(TAG, "failed to get idmap path for " + overlayPackagePath + ": "
+ + e.getMessage());
+ return "";
+ }
+ } else {
+ final StringBuilder sb = new StringBuilder("/data/resource-cache/");
+ sb.append(overlayPackagePath.substring(1).replace('/', '@'));
+ sb.append("@idmap");
+ return sb.toString();
+ }
+ }
+
+ private void connectToIdmap2d() {
+ IBinder binder = ServiceManager.getService(IDMAP_SERVICE);
+ if (binder != null) {
+ try {
+ binder.linkToDeath(new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ Slog.w(TAG, "service '" + IDMAP_SERVICE + "' died; reconnecting...");
+ connectToIdmap2d();
+ }
+
+ }, 0);
+ } catch (RemoteException e) {
+ binder = null;
+ }
+ }
+ if (binder != null) {
+ mIdmap2Service = IIdmap2.Stub.asInterface(binder);
+ if (DEBUG) {
+ Slog.d(TAG, "service '" + IDMAP_SERVICE + "' connected");
+ }
+ } else {
+ Slog.w(TAG, "service '" + IDMAP_SERVICE + "' not found; trying again...");
+ BackgroundThread.getHandler().postDelayed(() -> {
+ connectToIdmap2d();
+ }, SECOND_IN_MILLIS);
+ }
}
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index 36bf83d..572d368 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -179,19 +179,13 @@
List<OverlayInfo> getOverlaysForTarget(@NonNull final String targetPackageName,
final int userId) {
- // Static RROs targeting "android" are loaded from AssetManager, and so they should be
- // ignored in OverlayManagerService.
return selectWhereTarget(targetPackageName, userId)
- .filter((i) -> !(i.isStatic() && "android".equals(i.getTargetPackageName())))
.map(SettingsItem::getOverlayInfo)
.collect(Collectors.toList());
}
ArrayMap<String, List<OverlayInfo>> getOverlaysForUser(final int userId) {
- // Static RROs targeting "android" are loaded from AssetManager, and so they should be
- // ignored in OverlayManagerService.
return selectWhereUser(userId)
- .filter((i) -> !(i.isStatic() && "android".equals(i.getTargetPackageName())))
.map(SettingsItem::getOverlayInfo)
.collect(Collectors.groupingBy(info -> info.targetPackageName, ArrayMap::new,
Collectors.toList()));
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 275f3dc..b490381 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -362,7 +362,7 @@
}
private static boolean shouldShowHiddenApp(ApplicationInfo appInfo) {
- if (appInfo.isSystemApp() || appInfo.isUpdatedSystemApp()) {
+ if (appInfo == null || appInfo.isSystemApp() || appInfo.isUpdatedSystemApp()) {
return false;
}
return true;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8a0c416..9856a2b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -68,6 +68,7 @@
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
import static android.content.pm.PackageManager.MATCH_ALL;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.content.pm.PackageManager.MATCH_APEX;
import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
@@ -125,6 +126,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.apex.ApexInfo;
+import android.apex.IApexService;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppDetailsActivity;
@@ -7727,6 +7730,8 @@
if (!sUserManager.exists(userId)) return ParceledListSlice.emptyList();
flags = updateFlagsForPackage(flags, userId, null);
final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
+ final boolean listApex = (flags & MATCH_APEX) != 0;
+
mPermissionManager.enforceCrossUserPermission(callingUid, userId,
false /* requireFullPermission */, false /* checkShell */,
"get installed packages");
@@ -7765,7 +7770,22 @@
}
}
}
-
+ if (listApex) {
+ final IApexService apex = IApexService.Stub.asInterface(
+ ServiceManager.getService("apexservice"));
+ if (apex != null) {
+ try {
+ final ApexInfo[] activePkgs = apex.getActivePackages();
+ for (ApexInfo apexInfo : activePkgs) {
+ list.add(new PackageInfo(apexInfo));
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to retrieve packages from apexservice: " + e.toString());
+ }
+ } else {
+ Log.e(TAG, "Unable to connect to apexservice for querying packages.");
+ }
+ }
return new ParceledListSlice<>(list);
}
}
@@ -12937,6 +12957,25 @@
}
}
+ @Override
+ public boolean canSuspendPackageForUser(String packageName, int userId) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS,
+ "canSuspendPackageForUser");
+ final int callingUid = Binder.getCallingUid();
+ if (UserHandle.getUserId(callingUid) != userId) {
+ throw new SecurityException("Calling uid " + callingUid
+ + " cannot query canSuspendPackageForUser for user " + userId);
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mPackages) {
+ return canSuspendPackageForUserLocked(packageName, userId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
@GuardedBy("mPackages")
private boolean canSuspendPackageForUserLocked(String packageName, int userId) {
if (isPackageDeviceAdmin(packageName, userId)) {
@@ -13000,7 +13039,7 @@
}
if (PLATFORM_PACKAGE_NAME.equals(packageName)) {
- Slog.w(TAG, "Cannot suspend package: " + packageName);
+ Slog.w(TAG, "Cannot suspend the platform package: " + packageName);
return false;
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 4c93441..6009bd3 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2637,7 +2637,7 @@
}
for (final SharedUserSetting sus : mSharedUsers.values()) {
- knownSet.remove(sus.getSandboxName());
+ knownSet.remove(sus.getStorageSandboxName());
}
// Remove any unclaimed mappings
@@ -2653,7 +2653,8 @@
void writeKernelMappingLPr(SharedUserSetting sus) {
if (mKernelMappingFilename == null || sus == null || sus.name == null) return;
- writeKernelMappingLPr(sus.getSandboxName(), sus.userId, sus.getNotInstalledUserIds());
+ writeKernelMappingLPr(sus.getStorageSandboxName(),
+ sus.userId, sus.getNotInstalledUserIds());
}
void writeKernelMappingLPr(PackageSetting ps) {
diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java
index 32826e5..d67144e 100644
--- a/services/core/java/com/android/server/pm/SharedUserSetting.java
+++ b/services/core/java/com/android/server/pm/SharedUserSetting.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageParser;
+import android.os.storage.StorageManager;
import android.service.pm.PackageServiceDumpProto;
import android.util.ArraySet;
import android.util.proto.ProtoOutputStream;
@@ -166,8 +167,8 @@
return excludedUserIds == null ? EmptyArray.INT : excludedUserIds;
}
- public String getSandboxName() {
- return "shared:" + name;
+ public String getStorageSandboxName() {
+ return StorageManager.SHARED_SANDBOX_PREFIX + name;
}
/** Updates all fields in this shared user setting from another. */
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 580e4f4..3a74ab5 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -42,7 +42,6 @@
import com.android.server.pm.PackageDexOptimizer;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.PackageManagerServiceUtils;
-import com.android.server.pm.PackageManagerServiceCompilerMapping;
import java.io.File;
import java.io.IOException;
diff --git a/services/core/java/com/android/server/pm/dex/DexoptOptions.java b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
index a7a7686..de3c9f2 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptOptions.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
@@ -18,8 +18,6 @@
import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
-import android.annotation.Nullable;
-
/**
* Options used for dexopt invocations.
*/
diff --git a/services/core/java/com/android/server/pm/dex/DexoptUtils.java b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
index d2600b5..9a12a2f 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptUtils.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
@@ -24,8 +24,6 @@
import com.android.server.pm.PackageDexOptimizer;
import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
public final class DexoptUtils {
diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
index 86f7380..519a20d 100644
--- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
+++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
@@ -35,13 +35,10 @@
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
-import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 32b2bf0..774134c 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -1205,6 +1205,21 @@
if (DEBUG) {
Log.i(TAG, "Granted " + (systemFixed ? "fixed " : "not fixed ")
+ permission + " to default handler " + pkg);
+
+ int appOp = AppOpsManager.permissionToOpCode(permission);
+ if (appOp != AppOpsManager.OP_NONE
+ && AppOpsManager.opToDefaultMode(appOp)
+ != AppOpsManager.MODE_ALLOWED) {
+ // Permission has a corresponding appop which is not allowed by default
+ // We must allow it as well, as it's usually checked alongside the
+ // permission
+ if (DEBUG) {
+ Log.i(TAG, "Granting OP_" + AppOpsManager.opToName(appOp)
+ + " to " + pkg.packageName);
+ }
+ mContext.getSystemService(AppOpsManager.class).setUidMode(
+ appOp, pkg.applicationInfo.uid, AppOpsManager.MODE_ALLOWED);
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index b788935..c5d38db 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1242,6 +1242,9 @@
&& ps.getRuntimePermissionState(sourcePerm, userId).isGranted()) {
isGranted = true;
break;
+ } else if (ps.hasInstallPermission(sourcePerm)) {
+ isGranted = true;
+ break;
}
}
@@ -1348,8 +1351,18 @@
}
}
} else {
- if (!origPs.hasRequestedPermission(sourcePerms)) {
- // Both permissions are new, do nothing
+ boolean inheritsFromInstallPerm = false;
+ for (int sourcePermNum = 0; sourcePermNum < sourcePerms.size();
+ sourcePermNum++) {
+ if (ps.hasInstallPermission(sourcePerms.valueAt(sourcePermNum))) {
+ inheritsFromInstallPerm = true;
+ break;
+ }
+ }
+
+ if (!origPs.hasRequestedPermission(sourcePerms)
+ && !inheritsFromInstallPerm) {
+ // Both permissions are new so nothing to inherit.
if (DEBUG_PERMISSIONS) {
Slog.i(TAG, newPerm + " does not inherit from " + sourcePerms
+ " for " + pkgName
@@ -1358,6 +1371,7 @@
break;
} else {
+ // Inherit from new install or existing runtime permissions
inheritPermissionStateToNewImplicitPermissionLocked(sourcePerms,
newPerm, ps, pkg, userId);
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 97af045..282746a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2258,8 +2258,7 @@
}
/**
- * @return whether the navigation bar can be hidden, e.g. the device has a
- * navigation bar and touch exploration is not enabled
+ * @return whether the navigation bar can be hidden, e.g. the device has a navigation bar
*/
private boolean canHideNavigationBar() {
return mDefaultDisplayPolicy.hasNavigationBar();
@@ -4305,8 +4304,7 @@
final boolean screenDecor = (pfl & PRIVATE_FLAG_IS_SCREEN_DECOR) != 0;
if (layoutInScreenAndInsetDecor && !screenDecor) {
- if (canHideNavigationBar() &&
- (sysUiVis & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0) {
+ if ((sysUiVis & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0) {
outFrame.set(displayFrames.mUnrestricted);
} else {
outFrame.set(displayFrames.mRestricted);
@@ -4965,8 +4963,7 @@
of.set(displayFrames.mOverscan);
df.set(displayFrames.mOverscan);
pf.set(displayFrames.mOverscan);
- } else if (canHideNavigationBar()
- && (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
+ } else if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
&& (type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW
|| type == TYPE_VOLUME_OVERLAY)) {
// Asking for layout as if the nav bar is hidden, lets the application
@@ -5062,8 +5059,7 @@
of.set(displayFrames.mOverscan);
df.set(displayFrames.mOverscan);
pf.set(displayFrames.mOverscan);
- } else if (canHideNavigationBar()
- && (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
+ } else if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
&& (type == TYPE_STATUS_BAR
|| type == TYPE_TOAST
|| type == TYPE_DOCK_DIVIDER
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
index a55b49f..f78d263 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
@@ -20,7 +20,7 @@
import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.security.IKeystoreService;
+import android.security.keystore.IKeystoreService;
import android.util.Slog;
import com.android.internal.policy.IKeyguardService;
diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java
index 6d7b04c..c5139b5 100644
--- a/services/core/java/com/android/server/power/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java
@@ -492,7 +492,7 @@
public final boolean forceAllAppsStandby;
/**
- * Whether to put all apps in the stand-by mode.
+ * Whether to force background check.
*/
public final boolean forceBackgroundCheck;
diff --git a/services/core/java/com/android/server/role/RemoteRoleControllerService.java b/services/core/java/com/android/server/role/RemoteRoleControllerService.java
index b670291..7d34270 100644
--- a/services/core/java/com/android/server/role/RemoteRoleControllerService.java
+++ b/services/core/java/com/android/server/role/RemoteRoleControllerService.java
@@ -16,10 +16,10 @@
package com.android.server.role;
-import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
import android.app.role.IRoleManagerCallback;
import android.app.role.RoleManagerCallback;
import android.content.ComponentName;
@@ -34,6 +34,7 @@
import android.rolecontrollerservice.RoleControllerService;
import android.util.Slog;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.util.function.pooled.PooledLambda;
import java.util.ArrayDeque;
@@ -44,9 +45,13 @@
*/
public class RemoteRoleControllerService {
+ static final boolean DEBUG = false;
private static final String LOG_TAG = RemoteRoleControllerService.class.getSimpleName();
@NonNull
+ private static final Handler sCallbackHandler = BackgroundThread.getHandler();
+
+ @NonNull
private final Connection mConnection;
public RemoteRoleControllerService(@UserIdInt int userId, @NonNull Context context) {
@@ -87,6 +92,16 @@
service.onClearRoleHolders(roleName, callbackDelegate), callback));
}
+ /**
+ * Performs granting of default roles and permissions and appops
+ *
+ * @see RoleControllerService#onGrantDefaultRoles(RoleManagerCallback)
+ */
+ public void onGrantDefaultRoles(@NonNull IRoleManagerCallback callback) {
+ mConnection.enqueueCall(
+ new Connection.Call(IRoleControllerService::onGrantDefaultRoles, callback));
+ }
+
private static final class Connection implements ServiceConnection {
private static final long UNBIND_DELAY_MILLIS = 15 * 1000;
@@ -106,9 +121,6 @@
private final Queue<Call> mPendingCalls = new ArrayDeque<>();
@NonNull
- private final Handler mMainHandler = Handler.getMain();
-
- @NonNull
private final Runnable mUnbindRunnable = this::unbind;
Connection(@UserIdInt int userId, @NonNull Context context) {
@@ -116,14 +128,14 @@
mContext = context;
}
- @MainThread
@Override
+ @WorkerThread
public void onServiceConnected(@NonNull ComponentName name, @NonNull IBinder service) {
mService = IRoleControllerService.Stub.asInterface(service);
executePendingCalls();
}
- @MainThread
+ @WorkerThread
private void executePendingCalls() {
while (!mPendingCalls.isEmpty()) {
Call call = mPendingCalls.poll();
@@ -132,26 +144,33 @@
scheduleUnbind();
}
- @MainThread
@Override
+ @WorkerThread
public void onServiceDisconnected(@NonNull ComponentName name) {
mService = null;
}
- @MainThread
@Override
+ @WorkerThread
public void onBindingDied(@NonNull ComponentName name) {
unbind();
}
public void enqueueCall(@NonNull Call call) {
- mMainHandler.post(PooledLambda.obtainRunnable(this::executeCall, call));
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Enqueue " + call);
+ }
+ sCallbackHandler.executeOrSendMessage(PooledLambda.obtainMessage(
+ Connection::executeCall, this, call));
}
- @MainThread
+ @WorkerThread
private void executeCall(@NonNull Call call) {
ensureBound();
if (mService == null) {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Delaying until service connected: " + call);
+ }
mPendingCalls.offer(call);
return;
}
@@ -159,24 +178,28 @@
scheduleUnbind();
}
- @MainThread
+ @WorkerThread
private void ensureBound() {
- mMainHandler.removeCallbacks(mUnbindRunnable);
+ sCallbackHandler.removeCallbacks(mUnbindRunnable);
if (!mBound) {
Intent intent = new Intent(RoleControllerService.SERVICE_INTERFACE);
intent.setPackage(mContext.getPackageManager()
.getPermissionControllerPackageName());
+ // Use direct handler to ensure onServiceConnected callback happens in the same
+ // call frame, as required by onGrantDefaultRoles
+ //
+ // Note that as a result, onServiceConnected may happen not on main thread!
mBound = mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE,
- UserHandle.of(mUserId));
+ sCallbackHandler, UserHandle.of(mUserId));
}
}
private void scheduleUnbind() {
- mMainHandler.removeCallbacks(mUnbindRunnable);
- mMainHandler.postDelayed(mUnbindRunnable, UNBIND_DELAY_MILLIS);
+ sCallbackHandler.removeCallbacks(mUnbindRunnable);
+ sCallbackHandler.postDelayed(mUnbindRunnable, UNBIND_DELAY_MILLIS);
}
- @MainThread
+ @WorkerThread
private void unbind() {
if (mBound) {
mService = null;
@@ -196,9 +219,6 @@
private final IRoleManagerCallback mCallback;
@NonNull
- private final Handler mMainHandler = Handler.getMain();
-
- @NonNull
private final Runnable mTimeoutRunnable = () -> notifyCallback(false);
private boolean mCallbackNotified;
@@ -209,10 +229,13 @@
mCallback = callback;
}
- @MainThread
+ @WorkerThread
public void execute(IRoleControllerService service) {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Executing " + this);
+ }
try {
- mMainHandler.postDelayed(mTimeoutRunnable, TIMEOUT_MILLIS);
+ sCallbackHandler.postDelayed(mTimeoutRunnable, TIMEOUT_MILLIS);
mCallExecutor.execute(service, new CallbackDelegate());
} catch (RemoteException e) {
Slog.e(LOG_TAG, "Error calling RoleControllerService", e);
@@ -220,13 +243,13 @@
}
}
- @MainThread
+ @WorkerThread
private void notifyCallback(boolean success) {
if (mCallbackNotified) {
return;
}
mCallbackNotified = true;
- mMainHandler.removeCallbacks(mTimeoutRunnable);
+ sCallbackHandler.removeCallbacks(mTimeoutRunnable);
try {
if (success) {
mCallback.onSuccess();
@@ -239,10 +262,15 @@
}
}
+ @Override
+ public String toString() {
+ return "Call with callback: " + mCallback;
+ }
+
@FunctionalInterface
public interface CallExecutor {
- @MainThread
+ @WorkerThread
void execute(IRoleControllerService service, IRoleManagerCallback callbackDelegate)
throws RemoteException;
}
@@ -251,13 +279,14 @@
@Override
public void onSuccess() throws RemoteException {
- mMainHandler.post(PooledLambda.obtainRunnable(Call.this::notifyCallback, true));
+ sCallbackHandler.sendMessage(PooledLambda.obtainMessage(
+ Call::notifyCallback, Call.this, true));
}
@Override
public void onFailure() throws RemoteException {
- mMainHandler.post(PooledLambda.obtainRunnable(Call.this::notifyCallback,
- false));
+ sCallbackHandler.sendMessage(PooledLambda.obtainMessage(
+ Call::notifyCallback, Call.this, false));
}
}
}
diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java
index ded075d..d01e762 100644
--- a/services/core/java/com/android/server/role/RoleManagerService.java
+++ b/services/core/java/com/android/server/role/RoleManagerService.java
@@ -45,6 +45,10 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
* Service for role management.
@@ -105,17 +109,37 @@
public void onStart() {
publishBinderService(Context.ROLE_SERVICE, new Stub());
//TODO add watch for new user creation and run default grants for them
+ //TODO add package update watch to detect PermissionController upgrade and run def. grants
}
@Override
public void onStartUser(@UserIdInt int userId) {
synchronized (mLock) {
//TODO only call into PermissionController if it or system upgreaded (for boot time)
- // (add package changes watch;
- // we can detect upgrade using build fingerprint and app version)
getUserStateLocked(userId);
- //TODO call permission grant policy here
+ }
+ //TODO consider calling grants only when certain conditions are met
+ // such as OS or PermissionController upgrade
+ if (RemoteRoleControllerService.DEBUG) {
Slog.i(LOG_TAG, "Granting default permissions...");
+ CompletableFuture<Void> result = new CompletableFuture<>();
+ getControllerService(userId).onGrantDefaultRoles(
+ new IRoleManagerCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ result.complete(null);
+ }
+
+ @Override
+ public void onFailure() {
+ result.completeExceptionally(new RuntimeException());
+ }
+ });
+ try {
+ result.get(5, TimeUnit.SECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ Slog.e(LOG_TAG, "Failed to grant defaults for user " + userId, e);
+ }
}
}
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index cef484f..d2ca850 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -19,14 +19,15 @@
import static android.os.Process.getUidForPid;
import static com.android.internal.util.Preconditions.checkNotNull;
-import static com.android.server.am.MemoryStatUtil.MEMORY_STAT_INTERESTING_NATIVE_PROCESSES;
import static com.android.server.am.MemoryStatUtil.readCmdlineFromProcfs;
import static com.android.server.am.MemoryStatUtil.readMemoryStatFromProcfs;
+import static com.android.server.am.MemoryStatUtil.readRssHighWaterMarkFromProcfs;
import android.annotation.Nullable;
import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
import android.app.AlarmManager.OnAlarmListener;
+import android.app.ProcessMemoryHighWaterMark;
import android.app.ProcessMemoryState;
import android.app.StatsManager;
import android.bluetooth.BluetoothActivityEnergyInfo;
@@ -41,9 +42,13 @@
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.hardware.fingerprint.FingerprintManager;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkRequest;
import android.net.NetworkStats;
import android.net.wifi.IWifiManager;
import android.net.wifi.WifiActivityEnergyInfo;
+import android.os.BatteryStats;
import android.os.BatteryStatsInternal;
import android.os.Binder;
import android.os.Bundle;
@@ -84,6 +89,8 @@
import com.android.internal.app.procstats.IProcessStats;
import com.android.internal.app.procstats.ProcessStats;
import com.android.internal.net.NetworkStatsFactory;
+import com.android.internal.os.BatterySipper;
+import com.android.internal.os.BatteryStatsHelper;
import com.android.internal.os.BinderCallsStats.ExportedCallStat;
import com.android.internal.os.KernelCpuSpeedReader;
import com.android.internal.os.KernelCpuThreadReader;
@@ -159,6 +166,37 @@
*/
public static final String EXTRA_LAST_REPORT_TIME = "android.app.extra.LAST_REPORT_TIME";
public static final int DEATH_THRESHOLD = 10;
+ /**
+ * Which native processes to snapshot memory for.
+ *
+ * <p>Processes are matched by their cmdline in procfs. Example: cat /proc/pid/cmdline returns
+ * /system/bin/statsd for the stats daemon.
+ */
+ private static final String[] MEMORY_INTERESTING_NATIVE_PROCESSES = new String[]{
+ "/system/bin/statsd", // Stats daemon.
+ "/system/bin/surfaceflinger",
+ "/system/bin/apexd", // APEX daemon.
+ "/system/bin/audioserver",
+ "/system/bin/cameraserver",
+ "/system/bin/drmserver",
+ "/system/bin/healthd",
+ "/system/bin/incidentd",
+ "/system/bin/installd",
+ "/system/bin/lmkd", // Low memory killer daemon.
+ "/system/bin/logd",
+ "media.codec",
+ "media.extractor",
+ "media.metrics",
+ "/system/bin/mediadrmserver",
+ "/system/bin/mediaserver",
+ "/system/bin/performanced",
+ "/system/bin/tombstoned",
+ "/system/bin/traced", // Perfetto.
+ "/system/bin/traced_probes", // Perfetto.
+ "webview_zygote",
+ "zygote",
+ "zygote64",
+ };
static final class CompanionHandler extends Handler {
@@ -202,6 +240,10 @@
@Nullable
private final KernelCpuThreadReader mKernelCpuThreadReader;
+ private BatteryStatsHelper mBatteryStatsHelper = null;
+ private static final int MAX_BATTERY_STATS_HELPER_FREQUENCY_MS = 1000;
+ private long mBatteryStatsHelperTimestampMs = -MAX_BATTERY_STATS_HELPER_FREQUENCY_MS;
+
private static IThermalService sThermalService;
private File mBaseDir =
new File(SystemServiceManager.ensureSystemDir(), "stats_companion");
@@ -271,6 +313,12 @@
Slog.e(TAG, "cannot find thermalservice, no throttling push notifications");
}
+ // Default NetworkRequest should cover all transport types.
+ final NetworkRequest request = new NetworkRequest.Builder().build();
+ final ConnectivityManager connectivityManager =
+ (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ connectivityManager.registerNetworkCallback(request, new ConnectivityStatsCallback());
+
HandlerThread handlerThread = new HandlerThread(TAG);
handlerThread.start();
mHandler = new CompanionHandler(handlerThread.getLooper());
@@ -357,6 +405,8 @@
List<Integer> uids = new ArrayList<>();
List<Long> versions = new ArrayList<>();
List<String> apps = new ArrayList<>();
+ List<String> versionStrings = new ArrayList<>();
+ List<String> installers = new ArrayList<>();
// Add in all the apps for every user/profile.
for (UserInfo profile : users) {
@@ -364,14 +414,24 @@
pm.getInstalledPackagesAsUser(PackageManager.MATCH_KNOWN_PACKAGES, profile.id);
for (int j = 0; j < pi.size(); j++) {
if (pi.get(j).applicationInfo != null) {
+ String installer;
+ try {
+ installer = pm.getInstallerPackageName(pi.get(j).packageName);
+ } catch (IllegalArgumentException e) {
+ installer = "";
+ }
+ installers.add(installer == null ? "" : installer);
uids.add(pi.get(j).applicationInfo.uid);
versions.add(pi.get(j).getLongVersionCode());
+ versionStrings.add(pi.get(j).versionName);
apps.add(pi.get(j).packageName);
}
}
}
- sStatsd.informAllUidData(toIntArray(uids), toLongArray(versions), apps.toArray(new
- String[apps.size()]));
+ sStatsd.informAllUidData(toIntArray(uids), toLongArray(versions),
+ versionStrings.toArray(new String[versionStrings.size()]),
+ apps.toArray(new String[apps.size()]),
+ installers.toArray(new String[installers.size()]));
if (DEBUG) {
Slog.d(TAG, "Sent data for " + uids.size() + " apps");
}
@@ -413,7 +473,14 @@
int uid = b.getInt(Intent.EXTRA_UID);
String app = intent.getData().getSchemeSpecificPart();
PackageInfo pi = pm.getPackageInfo(app, PackageManager.MATCH_ANY_USER);
- sStatsd.informOnePackage(app, uid, pi.getLongVersionCode());
+ String installer;
+ try {
+ installer = pm.getInstallerPackageName(app);
+ } catch (IllegalArgumentException e) {
+ installer = "";
+ }
+ sStatsd.informOnePackage(app, uid, pi.getLongVersionCode(), pi.versionName,
+ installer == null ? "" : installer);
}
} catch (Exception e) {
Slog.w(TAG, "Failed to inform statsd of an app update", e);
@@ -1019,7 +1086,7 @@
private void pullNativeProcessMemoryState(
int tagId, long elapsedNanos, long wallClockNanos,
List<StatsLogEventWrapper> pulledData) {
- int[] pids = getPidsForCommands(MEMORY_STAT_INTERESTING_NATIVE_PROCESSES);
+ int[] pids = getPidsForCommands(MEMORY_INTERESTING_NATIVE_PROCESSES);
for (int i = 0; i < pids.length; i++) {
int pid = pids[i];
MemoryStat memoryStat = readMemoryStatFromProcfs(pid);
@@ -1040,6 +1107,33 @@
}
}
+ private void pullProcessMemoryHighWaterMark(
+ int tagId, long elapsedNanos, long wallClockNanos,
+ List<StatsLogEventWrapper> pulledData) {
+ List<ProcessMemoryHighWaterMark> results = LocalServices.getService(
+ ActivityManagerInternal.class).getMemoryHighWaterMarkForProcesses();
+ for (ProcessMemoryHighWaterMark processMemoryHighWaterMark : results) {
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+ e.writeInt(processMemoryHighWaterMark.uid);
+ e.writeString(processMemoryHighWaterMark.processName);
+ e.writeLong(processMemoryHighWaterMark.rssHighWaterMarkInBytes);
+ pulledData.add(e);
+ }
+ int[] pids = getPidsForCommands(MEMORY_INTERESTING_NATIVE_PROCESSES);
+ for (int i = 0; i < pids.length; i++) {
+ final int pid = pids[i];
+ final int uid = getUidForPid(pid);
+ final String processName = readCmdlineFromProcfs(pid);
+ final long rssHighWaterMarkInBytes = readRssHighWaterMarkFromProcfs(pid);
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+ e.writeInt(uid);
+ e.writeString(processName);
+ e.writeLong(rssHighWaterMarkInBytes);
+ pulledData.add(e);
+ }
+ // TODO(b/119598534): Reset HWM counters here.
+ }
+
private void pullBinderCallsStats(
int tagId, long elapsedNanos, long wallClockNanos,
List<StatsLogEventWrapper> pulledData) {
@@ -1421,6 +1515,73 @@
pulledData.add(e);
}
+ private BatteryStatsHelper getBatteryStatsHelper() {
+ if (mBatteryStatsHelper == null) {
+ final long callingToken = Binder.clearCallingIdentity();
+ try {
+ // clearCallingIdentity required for BatteryStatsHelper.checkWifiOnly().
+ mBatteryStatsHelper = new BatteryStatsHelper(mContext, false);
+ } finally {
+ Binder.restoreCallingIdentity(callingToken);
+ }
+ mBatteryStatsHelper.create((Bundle) null);
+ }
+ long currentTime = SystemClock.elapsedRealtime();
+ if (currentTime - mBatteryStatsHelperTimestampMs >= MAX_BATTERY_STATS_HELPER_FREQUENCY_MS) {
+ // Load BatteryStats and do all the calculations.
+ mBatteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.USER_ALL);
+ // Calculations are done so we don't need to save the raw BatteryStats data in RAM.
+ mBatteryStatsHelper.clearStats();
+ mBatteryStatsHelperTimestampMs = currentTime;
+ }
+ return mBatteryStatsHelper;
+ }
+
+ private void pullDeviceCalculatedPowerUse(int tagId,
+ long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
+ BatteryStatsHelper bsHelper = getBatteryStatsHelper();
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+ e.writeFloat((float) bsHelper.getComputedPower());
+ pulledData.add(e);
+ }
+
+ private void pullDeviceCalculatedPowerBlameUid(int tagId,
+ long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
+ final List<BatterySipper> sippers = getBatteryStatsHelper().getUsageList();
+ if (sippers == null) {
+ return;
+ }
+ for (BatterySipper bs : sippers) {
+ if (bs.drainType != bs.drainType.APP) {
+ continue;
+ }
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+ e.writeInt(bs.uidObj.getUid());
+ e.writeFloat((float) bs.totalPowerMah);
+ pulledData.add(e);
+ }
+ }
+
+ private void pullDeviceCalculatedPowerBlameOther(int tagId,
+ long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
+ final List<BatterySipper> sippers = getBatteryStatsHelper().getUsageList();
+ if (sippers == null) {
+ return;
+ }
+ for (BatterySipper bs : sippers) {
+ if (bs.drainType == bs.drainType.APP) {
+ continue; // This is a separate atom; see pullDeviceCalculatedPowerBlameUid().
+ }
+ if (bs.drainType == bs.drainType.USER) {
+ continue; // This is not supported. We purposefully calculate over USER_ALL.
+ }
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+ e.writeInt(bs.drainType.ordinal());
+ e.writeFloat((float) bs.totalPowerMah);
+ pulledData.add(e);
+ }
+ }
+
private void pullDiskIo(int tagId, long elapsedNanos, final long wallClockNanos,
List<StatsLogEventWrapper> pulledData) {
mStoragedUidIoStatsReader.readAbsolute((uid, fgCharsRead, fgCharsWrite, fgBytesRead,
@@ -1589,6 +1750,10 @@
pullNativeProcessMemoryState(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+ case StatsLog.PROCESS_MEMORY_HIGH_WATER_MARK: {
+ pullProcessMemoryHighWaterMark(tagId, elapsedNanos, wallClockNanos, ret);
+ break;
+ }
case StatsLog.BINDER_CALLS: {
pullBinderCallsStats(tagId, elapsedNanos, wallClockNanos, ret);
break;
@@ -1646,6 +1811,18 @@
pullCpuTimePerThreadFreq(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+ case StatsLog.DEVICE_CALCULATED_POWER_USE: {
+ pullDeviceCalculatedPowerUse(tagId, elapsedNanos, wallClockNanos, ret);
+ break;
+ }
+ case StatsLog.DEVICE_CALCULATED_POWER_BLAME_UID: {
+ pullDeviceCalculatedPowerBlameUid(tagId, elapsedNanos, wallClockNanos, ret);
+ break;
+ }
+ case StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER: {
+ pullDeviceCalculatedPowerBlameOther(tagId, elapsedNanos, wallClockNanos, ret);
+ break;
+ }
default:
Slog.w(TAG, "No such tagId data as " + tagId);
return null;
@@ -1875,4 +2052,19 @@
temp.getValue());
}
}
+
+ private static final class ConnectivityStatsCallback extends
+ ConnectivityManager.NetworkCallback {
+ @Override
+ public void onAvailable(Network network) {
+ StatsLog.write(StatsLog.CONNECTIVITY_STATE_CHANGED, network.netId,
+ StatsLog.CONNECTIVITY_STATE_CHANGED__STATE__CONNECTED);
+ }
+
+ @Override
+ public void onLost(Network network) {
+ StatsLog.write(StatsLog.CONNECTIVITY_STATE_CHANGED, network.netId,
+ StatsLog.CONNECTIVITY_STATE_CHANGED__STATE__DISCONNECTED);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 10121c4..3e07ebe 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -37,6 +37,7 @@
import android.service.notification.NotificationStats;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.Log;
import android.util.Slog;
import com.android.internal.R;
@@ -671,6 +672,20 @@
// Ensure state for the current user is applied, even if passed a non-current user.
final int net1 = gatherDisableActionsLocked(mCurrentUserId, 1);
final int net2 = gatherDisableActionsLocked(mCurrentUserId, 2);
+
+ // TODO(b/113914868): investigation log for disappearing home button
+ if (whichFlag == 1 && pkg.contains("systemui")) {
+ String disabledData = "{ ";
+ for (int i = 0; i < mDisableRecords.size(); i++) {
+ DisableRecord tok = mDisableRecords.get(i);
+ disabledData += " ([" + i + "] " + tok + "), ";
+ }
+ disabledData += " }";
+ Log.d(TAG, "disabledlocked (b/113914868): net1=" + net1 + ", mDisabled1=" + mDisabled1
+ + ", token=" + token + ", mDisableRecords=" + mDisableRecords.size() + " => "
+ + disabledData);
+ }
+
if (net1 != mDisabled1 || net2 != mDisabled2) {
mDisabled1 = net1;
mDisabled2 = net2;
diff --git a/services/core/java/com/android/server/timezone/RulesManagerService.java b/services/core/java/com/android/server/timezone/RulesManagerService.java
index 23c4a33..c4d2851 100644
--- a/services/core/java/com/android/server/timezone/RulesManagerService.java
+++ b/services/core/java/com/android/server/timezone/RulesManagerService.java
@@ -47,6 +47,7 @@
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import libcore.icu.ICU;
+import libcore.timezone.TzDataSetVersion;
import libcore.util.TimeZoneFinder;
import libcore.util.ZoneInfoDB;
@@ -66,8 +67,8 @@
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
static final DistroFormatVersion DISTRO_FORMAT_VERSION_SUPPORTED =
new DistroFormatVersion(
- DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
- DistroVersion.CURRENT_FORMAT_MINOR_VERSION);
+ TzDataSetVersion.currentFormatMajorVersion(),
+ TzDataSetVersion.currentFormatMinorVersion());
public static class Lifecycle extends SystemService {
public Lifecycle(Context context) {
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 61e1414..1c08d03 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -383,7 +383,7 @@
return;
}
- if (launchedActivity != null && launchedActivity.nowVisible) {
+ if (launchedActivity != null && launchedActivity.mDrawn) {
// Launched activity is already visible. We cannot measure windows drawn delay.
reset(true /* abort */, info, "launched activity already visible");
return;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 23f8125..c43e64e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -20,6 +20,7 @@
import static android.app.ActivityManager.TaskDescription.ATTR_TASKDESCRIPTION_PREFIX;
import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
import static android.app.ActivityTaskManager.INVALID_STACK_ID;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
import static android.app.WaitResult.INVALID_DELAY;
@@ -75,6 +76,25 @@
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
+import static com.android.server.am.ActivityRecordProto.CONFIGURATION_CONTAINER;
+import static com.android.server.am.ActivityRecordProto.FRONT_OF_TASK;
+import static com.android.server.am.ActivityRecordProto.IDENTIFIER;
+import static com.android.server.am.ActivityRecordProto.PROC_ID;
+import static com.android.server.am.ActivityRecordProto.STATE;
+import static com.android.server.am.ActivityRecordProto.TRANSLUCENT;
+import static com.android.server.am.ActivityRecordProto.VISIBLE;
+import static com.android.server.am.EventLogTags.AM_RELAUNCH_ACTIVITY;
+import static com.android.server.am.EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY;
+import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING;
+import static com.android.server.wm.ActivityStack.ActivityState.PAUSED;
+import static com.android.server.wm.ActivityStack.ActivityState.PAUSING;
+import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
+import static com.android.server.wm.ActivityStack.ActivityState.STOPPED;
+import static com.android.server.wm.ActivityStack.ActivityState.STOPPING;
+import static com.android.server.wm.ActivityStack.LAUNCH_TICK;
+import static com.android.server.wm.ActivityStack.LAUNCH_TICK_MSG;
+import static com.android.server.wm.ActivityStack.PAUSE_TIMEOUT_MSG;
+import static com.android.server.wm.ActivityStack.STOP_TIMEOUT_MSG;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONFIGURATION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_FOCUS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SAVED_STATE;
@@ -89,34 +109,14 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_VISIBILITY;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.am.ActivityRecordProto.CONFIGURATION_CONTAINER;
-import static com.android.server.am.ActivityRecordProto.FRONT_OF_TASK;
-import static com.android.server.am.ActivityRecordProto.IDENTIFIER;
-import static com.android.server.am.ActivityRecordProto.PROC_ID;
-import static com.android.server.am.ActivityRecordProto.STATE;
-import static com.android.server.am.ActivityRecordProto.TRANSLUCENT;
-import static com.android.server.am.ActivityRecordProto.VISIBLE;
-import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING;
-import static com.android.server.wm.ActivityStack.ActivityState.PAUSED;
-import static com.android.server.wm.ActivityStack.ActivityState.PAUSING;
-import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
-import static com.android.server.wm.ActivityStack.ActivityState.STOPPED;
-import static com.android.server.wm.ActivityStack.ActivityState.STOPPING;
-import static com.android.server.wm.ActivityStack.LAUNCH_TICK;
-import static com.android.server.wm.ActivityStack.LAUNCH_TICK_MSG;
-import static com.android.server.wm.ActivityStack.PAUSE_TIMEOUT_MSG;
-import static com.android.server.wm.ActivityStack.STOP_TIMEOUT_MSG;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
-import static com.android.server.am.EventLogTags.AM_RELAUNCH_ACTIVITY;
-import static com.android.server.am.EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY;
-import static com.android.server.wm.TaskPersister.DEBUG;
-import static com.android.server.wm.TaskPersister.IMAGE_EXTENSION;
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.IdentifierProto.USER_ID;
+import static com.android.server.wm.TaskPersister.DEBUG;
+import static com.android.server.wm.TaskPersister.IMAGE_EXTENSION;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.END_TAG;
@@ -179,9 +179,9 @@
import com.android.server.AttributeCache.Entry;
import com.android.server.am.AppTimeTracker;
import com.android.server.am.PendingIntentRecord;
+import com.android.server.uri.UriPermissionOwner;
import com.android.server.wm.ActivityMetricsLogger.WindowingModeTransitionInfoSnapshot;
import com.android.server.wm.ActivityStack.ActivityState;
-import com.android.server.uri.UriPermissionOwner;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -303,6 +303,7 @@
// process that it is hidden.
boolean sleeping; // have we told the activity to sleep?
boolean nowVisible; // is this activity's window visible?
+ boolean mDrawn; // is this activity's window drawn?
boolean mClientVisibilityDeferred;// was the visibility change message to client deferred?
boolean idle; // has the activity gone idle?
boolean hasBeenLaunched;// has this activity ever been launched?
@@ -869,6 +870,7 @@
inHistory = false;
visible = false;
nowVisible = false;
+ mDrawn = false;
idle = false;
hasBeenLaunched = false;
mStackSupervisor = supervisor;
@@ -1944,8 +1946,12 @@
}
@Override
- public void onWindowsDrawn(long timestamp) {
+ public void onWindowsDrawn(boolean drawn, long timestamp) {
synchronized (service.mGlobalLock) {
+ mDrawn = drawn;
+ if (!drawn) {
+ return;
+ }
final WindowingModeTransitionInfoSnapshot info = mStackSupervisor
.getActivityMetricsLogger().notifyWindowsDrawn(getWindowingMode(), timestamp);
final int windowsDrawnDelayMs = info != null ? info.windowsDrawnDelayMs : INVALID_DELAY;
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 97eaafc..082f521 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -121,6 +121,7 @@
import android.app.ProfilerInfo;
import android.app.ResultInfo;
import android.app.WaitResult;
+import android.app.WindowConfiguration;
import android.app.WindowConfiguration.ActivityType;
import android.app.WindowConfiguration.WindowingMode;
import android.app.servertransaction.ActivityLifecycleItem;
@@ -145,6 +146,7 @@
import android.hardware.display.DisplayManagerInternal;
import android.hardware.power.V1_0.PowerHint;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
import android.os.FactoryTest;
@@ -835,9 +837,13 @@
}
final boolean supportMultipleInstance = homeInfo.launchMode != LAUNCH_SINGLE_TASK
- && homeInfo.launchMode != LAUNCH_SINGLE_INSTANCE;
+ && homeInfo.launchMode != LAUNCH_SINGLE_INSTANCE
+ && homeInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q;
if (!supportMultipleInstance) {
- // Can't launch home on other displays if it requested to be single instance.
+ // Can't launch home on other displays if it requested to be single instance. Also we
+ // don't allow home applications that target before Q to have multiple home activity
+ // instances because they may not be expected to have multiple home scenario and
+ // haven't explicitly request for single instance.
return false;
}
@@ -2400,7 +2406,7 @@
<T extends ActivityStack> T getLaunchStack(@Nullable ActivityRecord r,
@Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, boolean onTop) {
- return getLaunchStack(r, options, candidateTask, onTop, INVALID_DISPLAY);
+ return getLaunchStack(r, options, candidateTask, onTop, null /* launchParams */);
}
/**
@@ -2409,12 +2415,13 @@
* @param r The activity we are trying to launch. Can be null.
* @param options The activity options used to the launch. Can be null.
* @param candidateTask The possible task the activity might be launched in. Can be null.
+ * @params launchParams The resolved launch params to use.
*
* @return The stack to use for the launch or INVALID_STACK_ID.
*/
<T extends ActivityStack> T getLaunchStack(@Nullable ActivityRecord r,
@Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, boolean onTop,
- int candidateDisplayId) {
+ @Nullable LaunchParamsController.LaunchParams launchParams) {
int taskId = INVALID_TASK_ID;
int displayId = INVALID_DISPLAY;
//Rect bounds = null;
@@ -2422,9 +2429,6 @@
// We give preference to the launch preference in activity options.
if (options != null) {
taskId = options.getLaunchTaskId();
- displayId = options.getLaunchDisplayId();
- // TODO: Need to work this into the equation...
- //bounds = options.getLaunchBounds();
}
// First preference for stack goes to the task Id set in the activity options. Use the stack
@@ -2441,16 +2445,16 @@
}
final int activityType = resolveActivityType(r, options, candidateTask);
- T stack = null;
+ T stack;
- // Next preference for stack goes to the display Id set in the activity options or the
- // candidate display.
- if (displayId == INVALID_DISPLAY) {
- displayId = candidateDisplayId;
+ // Next preference for stack goes to the display Id set the candidate display.
+ if (launchParams != null) {
+ displayId = launchParams.mPreferredDisplayId;
}
if (displayId != INVALID_DISPLAY && canLaunchOnDisplay(r, displayId)) {
if (r != null) {
- stack = (T) getValidLaunchStackOnDisplay(displayId, r, candidateTask, options);
+ stack = (T) getValidLaunchStackOnDisplay(displayId, r, candidateTask, options,
+ launchParams);
if (stack != null) {
return stack;
}
@@ -2477,8 +2481,12 @@
if (stack != null) {
display = stack.getDisplay();
if (display != null && canLaunchOnDisplay(r, display.mDisplayId)) {
- final int windowingMode =
- display.resolveWindowingMode(r, options, candidateTask, activityType);
+ int windowingMode = launchParams != null ? launchParams.mWindowingMode
+ : WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+ if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
+ windowingMode = display.resolveWindowingMode(r, options, candidateTask,
+ activityType);
+ }
if (stack.isCompatible(windowingMode, activityType)) {
return stack;
}
@@ -2518,8 +2526,9 @@
* @param candidateTask The possible task the activity might be put in.
* @return Existing stack if there is a valid one, new dynamic stack if it is valid or null.
*/
- ActivityStack getValidLaunchStackOnDisplay(int displayId, @NonNull ActivityRecord r,
- @Nullable TaskRecord candidateTask, @Nullable ActivityOptions options) {
+ private ActivityStack getValidLaunchStackOnDisplay(int displayId, @NonNull ActivityRecord r,
+ @Nullable TaskRecord candidateTask, @Nullable ActivityOptions options,
+ @Nullable LaunchParamsController.LaunchParams launchParams) {
final ActivityDisplay activityDisplay = getActivityDisplayOrCreateLocked(displayId);
if (activityDisplay == null) {
throw new IllegalArgumentException(
@@ -2547,8 +2556,18 @@
// If there is no valid stack on the external display - check if new dynamic stack will do.
if (displayId != DEFAULT_DISPLAY) {
+ final int windowingMode;
+ if (launchParams != null) {
+ // When launch params is not null, we always defer to its windowing mode. Sometimes
+ // it could be unspecified, which indicates it should inherit windowing mode from
+ // display.
+ windowingMode = launchParams.mWindowingMode;
+ } else {
+ windowingMode = options != null ? options.getLaunchWindowingMode()
+ : r.getWindowingMode();
+ }
return activityDisplay.createStack(
- options != null ? options.getLaunchWindowingMode() : r.getWindowingMode(),
+ windowingMode,
options != null ? options.getLaunchActivityType() : r.getActivityType(),
true /*onTop*/);
}
@@ -2558,8 +2577,10 @@
}
ActivityStack getValidLaunchStackOnDisplay(int displayId, @NonNull ActivityRecord r,
- @Nullable ActivityOptions options) {
- return getValidLaunchStackOnDisplay(displayId, r, null /* candidateTask */, options);
+ @Nullable ActivityOptions options,
+ @Nullable LaunchParamsController.LaunchParams launchParams) {
+ return getValidLaunchStackOnDisplay(displayId, r, null /* candidateTask */, options,
+ launchParams);
}
// TODO: Can probably be consolidated into getLaunchStack()...
@@ -2639,7 +2660,7 @@
continue;
}
final ActivityStack stack = getValidLaunchStackOnDisplay(display.mDisplayId, r,
- null /* options */);
+ null /* options */, null /* launchParams */);
if (stack != null) {
return stack;
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 83db8de..d4c1bca 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2145,7 +2145,7 @@
if (mTargetStack == null && targetDisplayId != sourceStack.mDisplayId) {
// Can't use target display, lets find a stack on the source display.
mTargetStack = mSupervisor.getValidLaunchStackOnDisplay(
- sourceStack.mDisplayId, mStartActivity, mOptions);
+ sourceStack.mDisplayId, mStartActivity, mOptions, mLaunchParams);
}
if (mTargetStack == null) {
// There are no suitable stacks on the target and source display(s). Look on all
@@ -2368,7 +2368,8 @@
if (mPreferredDisplayId != DEFAULT_DISPLAY) {
// Try to put the activity in a stack on a secondary display.
- stack = mSupervisor.getValidLaunchStackOnDisplay(mPreferredDisplayId, r, aOptions);
+ stack = mSupervisor.getValidLaunchStackOnDisplay(mPreferredDisplayId, r, aOptions,
+ mLaunchParams);
if (stack == null) {
// If source display is not suitable - look for topmost valid stack in the system.
if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
@@ -2432,9 +2433,12 @@
|| mPreferredDisplayId != DEFAULT_DISPLAY) {
// We don't pass in the default display id into the get launch stack call so it can do a
// full resolution.
- final int candidateDisplay =
+ mLaunchParams.mPreferredDisplayId =
mPreferredDisplayId != DEFAULT_DISPLAY ? mPreferredDisplayId : INVALID_DISPLAY;
- return mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP, candidateDisplay);
+ final ActivityStack stack = mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP,
+ mLaunchParams);
+ mLaunchParams.mPreferredDisplayId = mPreferredDisplayId;
+ return stack;
}
// Otherwise handle adjacent launch.
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index 3cbb2577..bd1460a 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -82,6 +82,7 @@
private final class H extends Handler {
public static final int NOTIFY_WINDOWS_DRAWN = 1;
public static final int NOTIFY_STARTING_WINDOW_DRAWN = 2;
+ public static final int NOTIFY_WINDOWS_NOTDRAWN = 3;
public H(Looper looper) {
super(looper);
@@ -96,16 +97,24 @@
}
if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting drawn in "
+ AppWindowContainerController.this.mToken);
- mListener.onWindowsDrawn(msg.getWhen());
+ mListener.onWindowsDrawn(true /* drawn */, msg.getWhen());
break;
case NOTIFY_STARTING_WINDOW_DRAWN:
if (mListener == null) {
return;
}
- if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting drawn in "
+ if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting starting window drawn in "
+ AppWindowContainerController.this.mToken);
mListener.onStartingWindowDrawn(msg.getWhen());
break;
+ case NOTIFY_WINDOWS_NOTDRAWN:
+ if (mListener == null) {
+ return;
+ }
+ if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting not drawn in "
+ + AppWindowContainerController.this.mToken);
+ mListener.onWindowsDrawn(false /* drawn */, msg.getWhen());
+ break;
default:
break;
}
@@ -762,6 +771,10 @@
mHandler.sendMessage(mHandler.obtainMessage(H.NOTIFY_WINDOWS_DRAWN));
}
+ void reportWindowsNotDrawn() {
+ mHandler.sendMessage(mHandler.obtainMessage(H.NOTIFY_WINDOWS_NOTDRAWN));
+ }
+
void reportWindowsVisible() {
mHandler.post(mOnWindowsVisible);
}
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerListener.java b/services/core/java/com/android/server/wm/AppWindowContainerListener.java
index 8a39a74..ad27669 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerListener.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerListener.java
@@ -18,8 +18,8 @@
/** Interface used by the creator of the controller to listen to changes with the container. */
public interface AppWindowContainerListener extends WindowContainerListener {
- /** Called when the windows associated app window container are drawn. */
- void onWindowsDrawn(long timestamp);
+ /** Called when the windows associated app window container drawn state changes. */
+ void onWindowsDrawn(boolean drawn, long timestamp);
/** Called when the windows associated app window container are visible. */
void onWindowsVisible();
/** Called when the windows associated app window container are no longer visible. */
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 3cece11..92944a0 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -95,6 +95,7 @@
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
import android.view.IApplicationToken;
+import android.view.InputApplicationHandle;
import android.view.RemoteAnimationDefinition;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
@@ -105,7 +106,6 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ToBooleanFunction;
-import com.android.server.input.InputApplicationHandle;
import com.android.server.policy.WindowManagerPolicy.StartingSurface;
import com.android.server.wm.WindowManagerService.H;
@@ -366,6 +366,10 @@
if (controller != null) {
controller.reportWindowsDrawn();
}
+ } else {
+ if (controller != null) {
+ controller.reportWindowsNotDrawn();
+ }
}
reportedDrawn = nowDrawn;
}
@@ -2015,9 +2019,7 @@
clearThumbnail();
setClientHidden(isHidden() && hiddenRequested);
- if (mService.mInputMethodTarget != null && mService.mInputMethodTarget.mAppToken == this) {
- getDisplayContent().computeImeTarget(true /* updateImeTarget */);
- }
+ getDisplayContent().computeImeTargetIfNeeded(this);
if (DEBUG_ANIM) Slog.v(TAG, "Animation done in " + this
+ ": reportedVisible=" + reportedVisible
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index 3accaf8..cc14afc 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -126,9 +126,10 @@
DimState(SurfaceControl dimLayer) {
mDimLayer = dimLayer;
mDimming = true;
- mSurfaceAnimator = new SurfaceAnimator(new DimAnimatable(dimLayer), () -> {
+ final DimAnimatable dimAnimatable = new DimAnimatable(dimLayer);
+ mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, () -> {
if (!mDimming) {
- mDimLayer.destroy();
+ dimAnimatable.getPendingTransaction().destroy(mDimLayer);
}
}, mHost.mService);
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 767a6ef..4b93986 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -100,7 +100,6 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.CUSTOM_SCREEN_ROTATION;
-import static com.android.server.wm.WindowManagerService.H.REPORT_FOCUS_CHANGE;
import static com.android.server.wm.WindowManagerService.H.REPORT_LOSING_FOCUS;
import static com.android.server.wm.WindowManagerService.H.SEND_NEW_CONFIGURATION;
import static com.android.server.wm.WindowManagerService.H.UPDATE_DOCKED_STACK_DIVIDER;
@@ -496,6 +495,15 @@
*/
WindowState mInputMethodWindow;
+ /**
+ * This just indicates the window the input method is on top of, not
+ * necessarily the window its input is going to.
+ */
+ WindowState mInputMethodTarget;
+
+ /** If true hold off on modifying the animation layer of mInputMethodTarget */
+ boolean mInputMethodTargetWaitingAnim;
+
private final PointerEventDispatcher mPointerEventDispatcher;
private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
@@ -699,7 +707,7 @@
private final Consumer<WindowState> mApplyPostLayoutPolicy =
w -> mService.mPolicy.applyPostLayoutPolicyLw(w, w.mAttrs, w.getParentWindow(),
- mService.mInputMethodTarget);
+ mInputMethodTarget);
private final Consumer<WindowState> mApplySurfaceChangesTransaction = w -> {
final WindowSurfacePlacer surfacePlacer = mService.mWindowPlacerLocked;
@@ -972,11 +980,17 @@
if (prevDc == this) {
return;
}
- if (prevDc != null && prevDc.mTokenMap.remove(token.token) != null
- && token.asAppWindowToken() == null) {
- // Removed the token from the map, but made sure it's not an app token before removing
- // from parent.
- token.getParent().removeChild(token);
+ if (prevDc != null) {
+ if (prevDc.mTokenMap.remove(token.token) != null && token.asAppWindowToken() == null) {
+ // Removed the token from the map, but made sure it's not an app token before
+ // removing from parent.
+ token.getParent().removeChild(token);
+ }
+ if (prevDc.mLastFocus == mCurrentFocus) {
+ // The window has become the focus of this display, so it should not be notified
+ // that it lost focus from the previous display.
+ prevDc.mLastFocus = null;
+ }
}
addWindowToken(token.token, token);
@@ -1928,7 +1942,7 @@
* rather than directly above their target.
*/
private boolean skipTraverseChild(WindowContainer child) {
- if (child == mImeWindowsContainers && mService.mInputMethodTarget != null
+ if (child == mImeWindowsContainers && mInputMethodTarget != null
&& !hasSplitScreenPrimaryStack()) {
return true;
}
@@ -2800,13 +2814,12 @@
if (mCurrentFocus == newFocus) {
return false;
}
- mService.mH.obtainMessage(REPORT_FOCUS_CHANGE, this).sendToTarget();
boolean imWindowChanged = false;
// TODO (b/111080190): Multi-Session IME
if (!focusFound) {
final WindowState imWindow = mInputMethodWindow;
if (imWindow != null) {
- final WindowState prevTarget = mService.mInputMethodTarget;
+ final WindowState prevTarget = mInputMethodTarget;
final WindowState newTarget = computeImeTarget(true /* updateImeTarget*/);
imWindowChanged = prevTarget != newTarget;
@@ -2998,13 +3011,13 @@
// There isn't an IME so there shouldn't be a target...That was easy!
if (updateImeTarget) {
if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from "
- + mService.mInputMethodTarget + " to null since mInputMethodWindow is null");
- setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim);
+ + mInputMethodTarget + " to null since mInputMethodWindow is null");
+ setInputMethodTarget(null, mInputMethodTargetWaitingAnim);
}
return null;
}
- final WindowState curTarget = mService.mInputMethodTarget;
+ final WindowState curTarget = mInputMethodTarget;
if (!canUpdateImeTarget()) {
if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Defer updating IME target");
return curTarget;
@@ -3031,7 +3044,7 @@
}
if (DEBUG_INPUT_METHOD && updateImeTarget) Slog.v(TAG_WM,
- "Proposed new IME target: " + target);
+ "Proposed new IME target: " + target + " for display: " + getDisplayId());
// Now, a special case -- if the last target's window is in the process of exiting, but
// not removed, and the new target is home, keep on the last target to avoid flicker.
@@ -3052,7 +3065,7 @@
if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget
+ " to null." + (SHOW_STACK_CRAWLS ? " Callers="
+ Debug.getCallers(4) : ""));
- setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim);
+ setInputMethodTarget(null, mInputMethodTargetWaitingAnim);
}
return null;
@@ -3091,14 +3104,23 @@
return target;
}
+ /**
+ * Calling {@link #computeImeTarget(boolean)} to update the input method target window in
+ * the candidate app window token if needed.
+ */
+ void computeImeTargetIfNeeded(AppWindowToken candidate) {
+ if (mInputMethodTarget != null && mInputMethodTarget.mAppToken == candidate) {
+ computeImeTarget(true /* updateImeTarget */);
+ }
+ }
+
private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim) {
- if (target == mService.mInputMethodTarget
- && mService.mInputMethodTargetWaitingAnim == targetWaitingAnim) {
+ if (target == mInputMethodTarget && mInputMethodTargetWaitingAnim == targetWaitingAnim) {
return;
}
- mService.mInputMethodTarget = target;
- mService.mInputMethodTargetWaitingAnim = targetWaitingAnim;
+ mInputMethodTarget = target;
+ mInputMethodTargetWaitingAnim = targetWaitingAnim;
assignWindowLayers(false /* setLayoutNeeded */);
}
@@ -4487,7 +4509,7 @@
mTaskStackContainers.assignLayer(t, 1);
mAboveAppWindowsContainers.assignLayer(t, 2);
- WindowState imeTarget = mService.mInputMethodTarget;
+ final WindowState imeTarget = mInputMethodTarget;
boolean needAssignIme = true;
// In the case where we have an IME target that is not in split-screen
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index ce8c979..7ed078a 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -36,7 +36,7 @@
import android.view.View;
import com.android.internal.util.Preconditions;
-import com.android.server.input.InputWindowHandle;
+import android.view.InputWindowHandle;
import com.android.server.wm.WindowManagerInternal.IDragDropCallback;
import java.util.concurrent.atomic.AtomicReference;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 5483602..a379266 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -57,8 +57,8 @@
import com.android.internal.view.IDragAndDropPermissions;
import com.android.server.LocalServices;
-import com.android.server.input.InputApplicationHandle;
-import com.android.server.input.InputWindowHandle;
+import android.view.InputApplicationHandle;
+import android.view.InputWindowHandle;
import java.util.ArrayList;
@@ -223,7 +223,7 @@
mDragApplicationHandle.dispatchingTimeoutNanos =
WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
- mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null, null,
+ mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null,
display.getDisplayId());
mDragWindowHandle.name = "drag";
mDragWindowHandle.inputChannel = mServerChannel;
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index 585a4f5..49bedc9 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -23,8 +23,8 @@
import android.view.InputChannel;
import android.view.WindowManager;
-import com.android.server.input.InputApplicationHandle;
-import com.android.server.input.InputWindowHandle;
+import android.view.InputApplicationHandle;
+import android.view.InputWindowHandle;
import java.io.PrintWriter;
@@ -63,7 +63,7 @@
mApplicationHandle.dispatchingTimeoutNanos =
WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
- mWindowHandle = new InputWindowHandle(mApplicationHandle, null, null, displayId);
+ mWindowHandle = new InputWindowHandle(mApplicationHandle, null, displayId);
mWindowHandle.name = name;
mWindowHandle.inputChannel = mServerChannel;
mWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index f823caa..92ea1a9 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -8,16 +8,19 @@
import android.app.ActivityManager;
import android.os.Debug;
+import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import android.view.KeyEvent;
import android.view.WindowManager;
-import com.android.server.input.InputApplicationHandle;
+import android.view.InputApplicationHandle;
import com.android.server.input.InputManagerService;
-import com.android.server.input.InputWindowHandle;
+import android.view.InputWindowHandle;
+import android.view.InputChannel;
import java.io.PrintWriter;
+import java.util.HashMap;
final class InputManagerCallback implements InputManagerService.WindowManagerCallbacks {
private final WindowManagerService mService;
@@ -48,13 +51,13 @@
* Called by the InputManager.
*/
@Override
- public void notifyInputChannelBroken(InputWindowHandle inputWindowHandle) {
- if (inputWindowHandle == null) {
+ public void notifyInputChannelBroken(IBinder token) {
+ if (token == null) {
return;
}
synchronized (mService.mGlobalLock) {
- WindowState windowState = (WindowState) inputWindowHandle.windowState;
+ WindowState windowState = mService.windowForClientLocked(null, token, false);
if (windowState != null) {
Slog.i(TAG_WM, "WINDOW DIED " + windowState);
windowState.removeIfPossible();
@@ -70,13 +73,13 @@
*/
@Override
public long notifyANR(InputApplicationHandle inputApplicationHandle,
- InputWindowHandle inputWindowHandle, String reason) {
+ IBinder token, String reason) {
AppWindowToken appWindowToken = null;
WindowState windowState = null;
boolean aboveSystem = false;
synchronized (mService.mGlobalLock) {
- if (inputWindowHandle != null) {
- windowState = (WindowState) inputWindowHandle.windowState;
+ if (token != null) {
+ windowState = mService.windowForClientLocked(null, token, false);
if (windowState != null) {
appWindowToken = windowState.mAppToken;
}
@@ -188,8 +191,8 @@
*/
@Override
public long interceptKeyBeforeDispatching(
- InputWindowHandle focus, KeyEvent event, int policyFlags) {
- WindowState windowState = focus != null ? (WindowState) focus.windowState : null;
+ IBinder focus, KeyEvent event, int policyFlags) {
+ WindowState windowState = mService.windowForClientLocked(null, focus, false);
return mService.mPolicy.interceptKeyBeforeDispatching(windowState, event, policyFlags);
}
@@ -199,8 +202,8 @@
*/
@Override
public KeyEvent dispatchUnhandledKey(
- InputWindowHandle focus, KeyEvent event, int policyFlags) {
- WindowState windowState = focus != null ? (WindowState) focus.windowState : null;
+ IBinder focus, KeyEvent event, int policyFlags) {
+ WindowState windowState = mService.windowForClientLocked(null, focus, false);
return mService.mPolicy.dispatchUnhandledKey(windowState, event, policyFlags);
}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 0e4ab53..61bc4e4 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -42,9 +42,12 @@
import android.util.Slog;
import android.view.InputChannel;
import android.view.InputEventReceiver;
+import android.view.KeyEvent;
+import android.view.WindowManager;
+import android.view.InputApplicationHandle;
+import android.view.InputWindowHandle;
-import com.android.server.input.InputApplicationHandle;
-import com.android.server.input.InputWindowHandle;
+import com.android.server.input.InputManagerService;
import com.android.server.policy.WindowManagerPolicy;
import java.io.PrintWriter;
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index c4fbee9..6627c2d 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -43,6 +43,7 @@
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
+import android.view.InputWindowHandle;
import android.view.IRecentsAnimationController;
import android.view.IRecentsAnimationRunner;
import android.view.RemoteAnimationTarget;
@@ -50,7 +51,6 @@
import android.view.SurfaceControl.Transaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
-import com.android.server.input.InputWindowHandle;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
import com.android.server.wm.utils.InsetUtils;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 01b05c3..1baca32 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -122,7 +122,7 @@
// The ID of the display which is responsible for receiving display-unspecified key and pointer
// events.
- private int mTopFocusedDisplayId = INVALID_DISPLAY;
+ int mTopFocusedDisplayId = INVALID_DISPLAY;
// Only a seperate transaction until we seperate the apply surface changes
// transaction from the global transaction.
@@ -156,7 +156,8 @@
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
boolean changed = false;
int topFocusedDisplayId = INVALID_DISPLAY;
- for (int i = mChildren.size() - 1; i >= 0; i--) {
+
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
final DisplayContent dc = mChildren.get(i);
changed |= dc.updateFocusedWindowLocked(mode, updateInputWindows,
topFocusedDisplayId != INVALID_DISPLAY /* focusFound */);
@@ -167,12 +168,35 @@
if (topFocusedDisplayId == INVALID_DISPLAY) {
topFocusedDisplayId = DEFAULT_DISPLAY;
}
+ // TODO(b/118865114): Review if need callback top focus display change to view component.
+ // (i.e. Activity or View)
+ // Currently we only tracked topFocusedDisplayChanged for notifying InputMethodManager via
+ // ViewRootImpl.windowFocusChanged to refocus IME window when top display focus changed
+ // but window focus remain the same case.
+ // It may need to review if any use case that need to add new callback for reporting
+ // this change.
+ final boolean topFocusedDisplayChanged =
+ mTopFocusedDisplayId != topFocusedDisplayId && mode == UPDATE_FOCUS_NORMAL;
if (mTopFocusedDisplayId != topFocusedDisplayId) {
mTopFocusedDisplayId = topFocusedDisplayId;
- mService.mInputManager.setFocusedDisplay(topFocusedDisplayId);
+ mService.mInputManager.setFocusedDisplay(mTopFocusedDisplayId);
if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "New topFocusedDisplayId="
- + topFocusedDisplayId);
+ + mTopFocusedDisplayId);
}
+
+ // Report window focus or top display focus changed through REPORT_FOCUS_CHANGE.
+ forAllDisplays((dc) -> {
+ final boolean windowFocusChanged =
+ dc.mCurrentFocus != null && dc.mCurrentFocus != dc.mLastFocus;
+ final boolean isTopFocusedDisplay =
+ topFocusedDisplayChanged && dc.getDisplayId() == mTopFocusedDisplayId;
+ if (windowFocusChanged || isTopFocusedDisplay) {
+ final Message msg = mService.mH.obtainMessage(
+ WindowManagerService.H.REPORT_FOCUS_CHANGE, dc);
+ msg.arg1 = topFocusedDisplayChanged ? 1 : 0;
+ mService.mH.sendMessage(msg);
+ }
+ });
final WindowState topFocusedWindow = getTopFocusedDisplayContent().mCurrentFocus;
mService.mInputManager.setFocusedWindow(
topFocusedWindow != null ? topFocusedWindow.mInputWindowHandle : null);
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 66063c40..31c0c7f 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -305,7 +305,7 @@
.setName(surface + " - animation-leash")
.setSize(width, height);
final SurfaceControl leash = builder.build();
- t.setWindowCrop(surface, width, height);
+ t.setWindowCrop(leash, width, height);
if (!hidden) {
t.show(leash);
}
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 66ebc9b..7182ad6 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -49,8 +49,9 @@
import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.input.InputApplicationHandle;
-import com.android.server.input.InputWindowHandle;
+import android.view.InputApplicationHandle;
+import android.view.InputWindowHandle;
+import com.android.server.wm.WindowManagerService.H;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -265,7 +266,7 @@
mDragApplicationHandle.dispatchingTimeoutNanos =
WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
- mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null, null,
+ mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null,
display.getDisplayId());
mDragWindowHandle.name = TAG;
mDragWindowHandle.inputChannel = mServerChannel;
diff --git a/services/core/java/com/android/server/wm/TaskPositioningController.java b/services/core/java/com/android/server/wm/TaskPositioningController.java
index f2d3dca..51567a0 100644
--- a/services/core/java/com/android/server/wm/TaskPositioningController.java
+++ b/services/core/java/com/android/server/wm/TaskPositioningController.java
@@ -29,7 +29,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.server.input.InputManagerService;
-import com.android.server.input.InputWindowHandle;
+import android.view.InputWindowHandle;
/**
* Controller for task positioning by drag.
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 073601d..912cb7f 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -809,7 +809,7 @@
updateBoundsForDisplayChanges();
}
- if (mAnimationBackgroundSurface != null) {
+ if (mAnimationBackgroundSurface == null) {
mAnimationBackgroundSurface = makeChildSurface(null).setColorLayer(true)
.setName("animation background stackId=" + mStackId)
.build();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 02904d4..c47b22f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -623,13 +623,6 @@
*/
final Handler mAnimationHandler = new Handler(AnimationThread.getHandler().getLooper());
- /** This just indicates the window the input method is on top of, not
- * necessarily the window its input is going to. */
- WindowState mInputMethodTarget = null;
-
- /** If true hold off on modifying the animation layer of mInputMethodTarget */
- boolean mInputMethodTargetWaitingAnim;
-
boolean mHardKeyboardAvailable;
WindowManagerInternal.OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener;
SettingsObserver mSettingsObserver;
@@ -4400,6 +4393,7 @@
AccessibilityController accessibilityController = null;
+ final boolean topFocusedDisplayChanged = msg.arg1 != 0;
synchronized (mGlobalLock) {
// TODO(multidisplay): Accessibility supported only of default desiplay.
if (mAccessibilityController != null && displayContent.isDefaultDisplay) {
@@ -4408,10 +4402,24 @@
lastFocus = displayContent.mLastFocus;
newFocus = displayContent.mCurrentFocus;
- if (lastFocus == newFocus) {
- // Focus is not changing, so nothing to do.
- return;
+ }
+ if (lastFocus == newFocus) {
+ // Report focus to ViewRootImpl when top focused display changes.
+ // Or, nothing to do for no window focus change.
+ if (topFocusedDisplayChanged && newFocus != null) {
+ if (DEBUG_FOCUS_LIGHT) {
+ Slog.d(TAG, "Reporting focus: " + newFocus
+ + " due to top focused display change.");
+ }
+ // See {@link IWindow#windowFocusChanged} to know why set
+ // reportToClient as false.
+ newFocus.reportFocusChangedSerialized(true, mInTouchMode,
+ false /* reportToClient */);
+ notifyFocusChanged();
}
+ return;
+ }
+ synchronized (mGlobalLock) {
displayContent.mLastFocus = newFocus;
if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Focus moving from " + lastFocus +
" to " + newFocus + " displayId=" + displayContent.getDisplayId());
@@ -4430,13 +4438,15 @@
if (newFocus != null) {
if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Gaining focus: " + newFocus);
- newFocus.reportFocusChangedSerialized(true, mInTouchMode);
+ newFocus.reportFocusChangedSerialized(true, mInTouchMode,
+ true /* reportToClient */);
notifyFocusChanged();
}
if (lastFocus != null) {
if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Losing focus: " + lastFocus);
- lastFocus.reportFocusChangedSerialized(false, mInTouchMode);
+ lastFocus.reportFocusChangedSerialized(false, mInTouchMode,
+ true /* reportToClient */);
}
} break;
@@ -4453,7 +4463,8 @@
for (int i = 0; i < N; i++) {
if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Losing delayed focus: " +
losers.get(i));
- losers.get(i).reportFocusChangedSerialized(false, mInTouchMode);
+ losers.get(i).reportFocusChangedSerialized(false, mInTouchMode,
+ true /* reportToClient */);
}
} break;
@@ -5930,9 +5941,13 @@
pw.print(" mGlobalConfiguration="); pw.println(mRoot.getConfiguration());
pw.print(" mHasPermanentDpad="); pw.println(mHasPermanentDpad);
mRoot.dumpTopFocusedDisplayId(pw);
- if (mInputMethodTarget != null) {
- pw.print(" mInputMethodTarget="); pw.println(mInputMethodTarget);
- }
+ mRoot.forAllDisplays(dc -> {
+ final WindowState inputMethodTarget = dc.mInputMethodTarget;
+ if (inputMethodTarget != null) {
+ pw.print(" mInputMethodTarget in display# "); pw.print(dc.getDisplayId());
+ pw.print(' '); pw.println(inputMethodTarget);
+ }
+ });
pw.print(" mInTouchMode="); pw.println(mInTouchMode);
pw.print(" mLastDisplayFreezeDuration=");
TimeUtils.formatDuration(mLastDisplayFreezeDuration, pw);
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 4c9788d..484bd8c 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -898,6 +898,11 @@
}
}
+ @Override
+ public String toString() {
+ return mOwner.toString();
+ }
+
public void dump(PrintWriter pw, String prefix) {
synchronized (mAtm.mGlobalLock) {
if (mActivities.size() > 0) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 99f65c3..a117cf3 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -191,7 +191,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ToBooleanFunction;
-import com.android.server.input.InputWindowHandle;
+import android.view.InputWindowHandle;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
import com.android.server.wm.utils.InsetUtils;
@@ -718,7 +718,7 @@
mLastRequestedHeight = 0;
mLayer = 0;
mInputWindowHandle = new InputWindowHandle(
- mAppToken != null ? mAppToken.mInputApplicationHandle : null, this, c,
+ mAppToken != null ? mAppToken.mInputApplicationHandle : null, c,
getDisplayId());
}
@@ -2047,7 +2047,7 @@
// Create dummy event receiver that simply reports all events as handled.
mDeadWindowEventReceiver = new DeadWindowEventReceiver(mClientChannel);
}
- mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);
+ mService.mInputManager.registerInputChannel(mInputChannel, mClient.asBinder());
}
void disposeInputChannel() {
@@ -2059,6 +2059,7 @@
// unregister server channel first otherwise it complains about broken channel
if (mInputChannel != null) {
mService.mInputManager.unregisterInputChannel(mInputChannel);
+
mInputChannel.dispose();
mInputChannel = null;
}
@@ -2841,12 +2842,13 @@
* Report a focus change. Must be called with no locks held, and consistently
* from the same serialized thread (such as dispatched from a handler).
*/
- void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) {
+ void reportFocusChangedSerialized(boolean focused, boolean inTouchMode,
+ boolean reportToClient) {
try {
- mClient.windowFocusChanged(focused, inTouchMode);
+ mClient.windowFocusChanged(focused, inTouchMode, reportToClient);
} catch (RemoteException e) {
}
- if (mFocusCallbacks != null) {
+ if (mFocusCallbacks != null && reportToClient) {
final int N = mFocusCallbacks.beginBroadcast();
for (int i=0; i<N; i++) {
IWindowFocusObserver obs = mFocusCallbacks.getBroadcastItem(i);
@@ -4476,8 +4478,9 @@
@Override
boolean needsZBoost() {
- if (mIsImWindow && mService.mInputMethodTarget != null) {
- final AppWindowToken appToken = mService.mInputMethodTarget.mAppToken;
+ final WindowState inputMethodTarget = getDisplayContent().mInputMethodTarget;
+ if (mIsImWindow && inputMethodTarget != null) {
+ final AppWindowToken appToken = inputMethodTarget.mAppToken;
if (appToken != null) {
return appToken.needsZBoost();
}
@@ -4607,7 +4610,7 @@
// Likewise if we share a token with the Input method target and are ordered
// above it but not necessarily a child (e.g. a Dialog) then we also need
// this promotion.
- final WindowState imeTarget = mService.mInputMethodTarget;
+ final WindowState imeTarget = getDisplayContent().mInputMethodTarget;
boolean inTokenWithAndAboveImeTarget = imeTarget != null && imeTarget != this
&& imeTarget.mToken == mToken && imeTarget.compareTo(this) <= 0;
return inTokenWithAndAboveImeTarget;
@@ -4684,7 +4687,7 @@
@Override
public boolean isInputMethodTarget() {
- return mService.mInputMethodTarget == this;
+ return getDisplayContent().mInputMethodTarget == this;
}
long getFrameNumber() {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 3943dba..e2db807 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -42,6 +42,8 @@
#include <utils/Trace.h>
#include <utils/SortedVector.h>
+#include <binder/IServiceManager.h>
+
#include <input/PointerController.h>
#include <input/SpriteController.h>
@@ -63,6 +65,7 @@
#include "android_hardware_input_InputApplicationHandle.h"
#include "android_hardware_input_InputWindowHandle.h"
#include "android_hardware_display_DisplayViewport.h"
+#include "android_util_Binder.h"
#include <vector>
@@ -153,15 +156,6 @@
getInputApplicationHandleObjLocalRef(env);
}
-static jobject getInputWindowHandleObjLocalRef(JNIEnv* env,
- const sp<InputWindowHandle>& inputWindowHandle) {
- if (inputWindowHandle == nullptr) {
- return nullptr;
- }
- return static_cast<NativeInputWindowHandle*>(inputWindowHandle.get())->
- getInputWindowHandleObjLocalRef(env);
-}
-
static void loadSystemIconAsSpriteWithPointerIcon(JNIEnv* env, jobject contextObj, int32_t style,
PointerIcon* outPointerIcon, SpriteIcon* outSpriteIcon) {
status_t status = android_view_PointerIcon_loadSystemIcon(env,
@@ -216,8 +210,7 @@
void setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray);
- status_t registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel,
- const sp<InputWindowHandle>& inputWindowHandle, int32_t displayId);
+ status_t registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel, int32_t displayId);
status_t unregisterInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel);
void setInputWindows(JNIEnv* env, jobjectArray windowHandleObjArray, int32_t displayId);
@@ -253,17 +246,17 @@
uint32_t policyFlags);
virtual void notifyConfigurationChanged(nsecs_t when);
virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
- const sp<InputWindowHandle>& inputWindowHandle,
+ const sp<IBinder>& token,
const std::string& reason);
- virtual void notifyInputChannelBroken(const sp<InputWindowHandle>& inputWindowHandle);
+ virtual void notifyInputChannelBroken(const sp<IBinder>& token);
virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags);
virtual void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig);
virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags);
virtual void interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags);
virtual nsecs_t interceptKeyBeforeDispatching(
- const sp<InputWindowHandle>& inputWindowHandle,
+ const sp<IBinder>& token,
const KeyEvent* keyEvent, uint32_t policyFlags);
- virtual bool dispatchUnhandledKey(const sp<InputWindowHandle>& inputWindowHandle,
+ virtual bool dispatchUnhandledKey(const sp<IBinder>& token,
const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent);
virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType);
virtual bool checkInjectEventsPermissionNonReentrant(
@@ -442,11 +435,10 @@
}
status_t NativeInputManager::registerInputChannel(JNIEnv* /* env */,
- const sp<InputChannel>& inputChannel, const sp<InputWindowHandle>& inputWindowHandle,
- int32_t displayId) {
+ const sp<InputChannel>& inputChannel, int32_t displayId) {
ATRACE_CALL();
- return mInputManager->getDispatcher()->registerInputChannel(inputChannel, inputWindowHandle,
- displayId);
+ return mInputManager->getDispatcher()->registerInputChannel(
+ inputChannel, displayId);
}
status_t NativeInputManager::unregisterInputChannel(JNIEnv* /* env */,
@@ -657,7 +649,7 @@
}
nsecs_t NativeInputManager::notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
- const sp<InputWindowHandle>& inputWindowHandle, const std::string& reason) {
+ const sp<IBinder>& token, const std::string& reason) {
#if DEBUG_INPUT_DISPATCHER_POLICY
ALOGD("notifyANR");
#endif
@@ -667,12 +659,11 @@
jobject inputApplicationHandleObj =
getInputApplicationHandleObjLocalRef(env, inputApplicationHandle);
- jobject inputWindowHandleObj =
- getInputWindowHandleObjLocalRef(env, inputWindowHandle);
+ jobject tokenObj = javaObjectForIBinder(env, token);
jstring reasonObj = env->NewStringUTF(reason.c_str());
jlong newTimeout = env->CallLongMethod(mServiceObj,
- gServiceClassInfo.notifyANR, inputApplicationHandleObj, inputWindowHandleObj,
+ gServiceClassInfo.notifyANR, inputApplicationHandleObj, tokenObj,
reasonObj);
if (checkAndClearExceptionFromCallback(env, "notifyANR")) {
newTimeout = 0; // abort dispatch
@@ -681,12 +672,11 @@
}
env->DeleteLocalRef(reasonObj);
- env->DeleteLocalRef(inputWindowHandleObj);
env->DeleteLocalRef(inputApplicationHandleObj);
return newTimeout;
}
-void NativeInputManager::notifyInputChannelBroken(const sp<InputWindowHandle>& inputWindowHandle) {
+void NativeInputManager::notifyInputChannelBroken(const sp<IBinder>& token) {
#if DEBUG_INPUT_DISPATCHER_POLICY
ALOGD("notifyInputChannelBroken");
#endif
@@ -694,14 +684,11 @@
JNIEnv* env = jniEnv();
- jobject inputWindowHandleObj =
- getInputWindowHandleObjLocalRef(env, inputWindowHandle);
- if (inputWindowHandleObj) {
+ jobject tokenObj = javaObjectForIBinder(env, token);
+ if (tokenObj) {
env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyInputChannelBroken,
- inputWindowHandleObj);
+ tokenObj);
checkAndClearExceptionFromCallback(env, "notifyInputChannelBroken");
-
- env->DeleteLocalRef(inputWindowHandleObj);
}
}
@@ -1061,7 +1048,7 @@
}
nsecs_t NativeInputManager::interceptKeyBeforeDispatching(
- const sp<InputWindowHandle>& inputWindowHandle,
+ const sp<IBinder>& token,
const KeyEvent* keyEvent, uint32_t policyFlags) {
ATRACE_CALL();
// Policy:
@@ -1072,13 +1059,14 @@
if (policyFlags & POLICY_FLAG_TRUSTED) {
JNIEnv* env = jniEnv();
- // Note: inputWindowHandle may be null.
- jobject inputWindowHandleObj = getInputWindowHandleObjLocalRef(env, inputWindowHandle);
+ // Token may be null
+ jobject tokenObj = javaObjectForIBinder(env, token);
+
jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
if (keyEventObj) {
jlong delayMillis = env->CallLongMethod(mServiceObj,
gServiceClassInfo.interceptKeyBeforeDispatching,
- inputWindowHandleObj, keyEventObj, policyFlags);
+ tokenObj, keyEventObj, policyFlags);
bool error = checkAndClearExceptionFromCallback(env, "interceptKeyBeforeDispatching");
android_view_KeyEvent_recycle(env, keyEventObj);
env->DeleteLocalRef(keyEventObj);
@@ -1092,12 +1080,11 @@
} else {
ALOGE("Failed to obtain key event object for interceptKeyBeforeDispatching.");
}
- env->DeleteLocalRef(inputWindowHandleObj);
}
return result;
}
-bool NativeInputManager::dispatchUnhandledKey(const sp<InputWindowHandle>& inputWindowHandle,
+bool NativeInputManager::dispatchUnhandledKey(const sp<IBinder>& token,
const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) {
ATRACE_CALL();
// Policy:
@@ -1106,13 +1093,13 @@
if (policyFlags & POLICY_FLAG_TRUSTED) {
JNIEnv* env = jniEnv();
- // Note: inputWindowHandle may be null.
- jobject inputWindowHandleObj = getInputWindowHandleObjLocalRef(env, inputWindowHandle);
+ // Note: tokenObj may be null.
+ jobject tokenObj = javaObjectForIBinder(env, token);
jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
if (keyEventObj) {
jobject fallbackKeyEventObj = env->CallObjectMethod(mServiceObj,
gServiceClassInfo.dispatchUnhandledKey,
- inputWindowHandleObj, keyEventObj, policyFlags);
+ tokenObj, keyEventObj, policyFlags);
if (checkAndClearExceptionFromCallback(env, "dispatchUnhandledKey")) {
fallbackKeyEventObj = nullptr;
}
@@ -1131,7 +1118,6 @@
} else {
ALOGE("Failed to obtain key event object for dispatchUnhandledKey.");
}
- env->DeleteLocalRef(inputWindowHandleObj);
}
return result;
}
@@ -1316,7 +1302,7 @@
}
static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */,
- jlong ptr, jobject inputChannelObj, jobject inputWindowHandleObj, jint displayId) {
+ jlong ptr, jobject inputChannelObj, jint displayId) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
@@ -1325,12 +1311,10 @@
throwInputChannelNotInitialized(env);
return;
}
+ bool monitor = inputChannel->getToken() == nullptr && displayId != ADISPLAY_ID_NONE;
- sp<InputWindowHandle> inputWindowHandle =
- android_server_InputWindowHandle_getHandle(env, inputWindowHandleObj);
+ status_t status = im->registerInputChannel(env, inputChannel, displayId);
- status_t status = im->registerInputChannel(
- env, inputChannel, inputWindowHandle, displayId);
if (status) {
std::string message;
message += StringPrintf("Failed to register input channel. status=%d", status);
@@ -1339,7 +1323,7 @@
}
// If inputWindowHandle is null and displayId >= 0, treat inputChannel as monitor.
- if (inputWindowHandle != nullptr || displayId == ADISPLAY_ID_NONE) {
+ if (!monitor) {
android_view_InputChannel_setDisposeCallback(env, inputChannelObj,
handleInputChannelDisposed, im);
}
@@ -1640,7 +1624,7 @@
{ "nativeHasKeys", "(JII[I[Z)Z",
(void*) nativeHasKeys },
{ "nativeRegisterInputChannel",
- "(JLandroid/view/InputChannel;Lcom/android/server/input/InputWindowHandle;I)V",
+ "(JLandroid/view/InputChannel;I)V",
(void*) nativeRegisterInputChannel },
{ "nativeUnregisterInputChannel", "(JLandroid/view/InputChannel;)V",
(void*) nativeUnregisterInputChannel },
@@ -1650,9 +1634,9 @@
(void*) nativeInjectInputEvent },
{ "nativeToggleCapsLock", "(JI)V",
(void*) nativeToggleCapsLock },
- { "nativeSetInputWindows", "(J[Lcom/android/server/input/InputWindowHandle;I)V",
+ { "nativeSetInputWindows", "(J[Landroid/view/InputWindowHandle;I)V",
(void*) nativeSetInputWindows },
- { "nativeSetFocusedApplication", "(JILcom/android/server/input/InputApplicationHandle;)V",
+ { "nativeSetFocusedApplication", "(JILandroid/view/InputApplicationHandle;)V",
(void*) nativeSetFocusedApplication },
{ "nativeSetFocusedDisplay", "(JI)V",
(void*) nativeSetFocusedDisplay },
@@ -1731,11 +1715,11 @@
"notifySwitch", "(JII)V");
GET_METHOD_ID(gServiceClassInfo.notifyInputChannelBroken, clazz,
- "notifyInputChannelBroken", "(Lcom/android/server/input/InputWindowHandle;)V");
+ "notifyInputChannelBroken", "(Landroid/os/IBinder;)V");
GET_METHOD_ID(gServiceClassInfo.notifyANR, clazz,
"notifyANR",
- "(Lcom/android/server/input/InputApplicationHandle;Lcom/android/server/input/InputWindowHandle;Ljava/lang/String;)J");
+ "(Landroid/view/InputApplicationHandle;Landroid/os/IBinder;Ljava/lang/String;)J");
GET_METHOD_ID(gServiceClassInfo.filterInputEvent, clazz,
"filterInputEvent", "(Landroid/view/InputEvent;I)Z");
@@ -1748,11 +1732,11 @@
GET_METHOD_ID(gServiceClassInfo.interceptKeyBeforeDispatching, clazz,
"interceptKeyBeforeDispatching",
- "(Lcom/android/server/input/InputWindowHandle;Landroid/view/KeyEvent;I)J");
+ "(Landroid/os/IBinder;Landroid/view/KeyEvent;I)J");
GET_METHOD_ID(gServiceClassInfo.dispatchUnhandledKey, clazz,
"dispatchUnhandledKey",
- "(Lcom/android/server/input/InputWindowHandle;Landroid/view/KeyEvent;I)Landroid/view/KeyEvent;");
+ "(Landroid/os/IBinder;Landroid/view/KeyEvent;I)Landroid/view/KeyEvent;");
GET_METHOD_ID(gServiceClassInfo.checkInjectEventsPermission, clazz,
"checkInjectEventsPermission", "(II)Z");
diff --git a/services/intelligence/java/com/android/server/intelligence/ContentCaptureSession.java b/services/intelligence/java/com/android/server/intelligence/ContentCaptureSession.java
index 9cab1ed..b7d34d7 100644
--- a/services/intelligence/java/com/android/server/intelligence/ContentCaptureSession.java
+++ b/services/intelligence/java/com/android/server/intelligence/ContentCaptureSession.java
@@ -76,7 +76,7 @@
}
/**
- * Cleans up the session and remove itself from the service.
+ * Cleans up the session and removes it from the service.
*
* @param notifyRemoteService whether it should trigger a {@link
* IntelligenceService#onDestroyInteractionSession(InteractionSessionId)}
@@ -85,14 +85,30 @@
@GuardedBy("mLock")
public void removeSelfLocked(boolean notifyRemoteService) {
try {
- if (notifyRemoteService) {
- mRemoteService.onSessionLifecycleRequest(/* context= */ null, mId);
- }
+ destroyLocked(notifyRemoteService);
} finally {
mService.removeSessionLocked(mId);
}
}
+ /**
+ * Cleans up the session, but not removes it from the service.
+ *
+ * @param notifyRemoteService whether it should trigger a {@link
+ * IntelligenceService#onDestroyInteractionSession(InteractionSessionId)}
+ * request.
+ */
+ @GuardedBy("mLock")
+ public void destroyLocked(boolean notifyRemoteService) {
+ if (mService.isVerbose()) {
+ Slog.v(TAG, "destroyLocked(notifyRemoteService=" + notifyRemoteService + ")");
+ }
+ // TODO(b/111276913): must call client to set session as FINISHED_BY_SERVER
+ if (notifyRemoteService) {
+ mRemoteService.onSessionLifecycleRequest(/* context= */ null, mId);
+ }
+ }
+
@Override // from RemoteScreenObservationServiceCallbacks
public void onServiceDied(AbstractRemoteService service) {
// TODO(b/111276913): implement (remove session from PerUserSession?)
diff --git a/services/intelligence/java/com/android/server/intelligence/IntelligenceManagerService.java b/services/intelligence/java/com/android/server/intelligence/IntelligenceManagerService.java
index 43d4a44..3d13570 100644
--- a/services/intelligence/java/com/android/server/intelligence/IntelligenceManagerService.java
+++ b/services/intelligence/java/com/android/server/intelligence/IntelligenceManagerService.java
@@ -19,6 +19,7 @@
import static android.content.Context.INTELLIGENCE_MANAGER_SERVICE;
import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.content.ComponentName;
import android.content.Context;
@@ -45,26 +46,28 @@
* <p>The data collected by this service can be analyzed and combined with other sources to provide
* contextual data in other areas of the system such as Autofill.
*/
-public final class IntelligenceManagerService
- extends AbstractMasterSystemService<IntelligencePerUserService> {
+public final class IntelligenceManagerService extends
+ AbstractMasterSystemService<IntelligenceManagerService, IntelligencePerUserService> {
private static final String TAG = "IntelligenceManagerService";
@GuardedBy("mLock")
private ActivityManagerInternal mAm;
+ private final LocalService mLocalService = new LocalService();
+
public IntelligenceManagerService(Context context) {
super(context, UserManager.DISALLOW_INTELLIGENCE_CAPTURE);
}
- @Override // from MasterSystemService
+ @Override // from AbstractMasterSystemService
protected String getServiceSettingsProperty() {
// TODO(b/111276913): STOPSHIP temporary settings, until it's set by resourcs + cmd
return "intel_service";
}
- @Override // from MasterSystemService
- protected IntelligencePerUserService newServiceLocked(int resolvedUserId,
+ @Override // from AbstractMasterSystemService
+ protected IntelligencePerUserService newServiceLocked(@UserIdInt int resolvedUserId,
boolean disabled) {
return new IntelligencePerUserService(this, mLock, resolvedUserId);
}
@@ -73,6 +76,13 @@
public void onStart() {
publishBinderService(INTELLIGENCE_MANAGER_SERVICE,
new IntelligenceManagerServiceStub());
+ publishLocalService(IntelligenceManagerInternal.class, mLocalService);
+ }
+
+ @Override // from AbstractMasterSystemService
+ protected void onServiceRemoved(@NonNull IntelligencePerUserService service,
+ @UserIdInt int userId) {
+ service.destroyLocked();
}
private ActivityManagerInternal getAmInternal() {
@@ -139,4 +149,19 @@
}
}
}
+
+ private final class LocalService extends IntelligenceManagerInternal {
+
+ @Override
+ public boolean isIntelligenceServiceForUser(int uid, int userId) {
+ synchronized (mLock) {
+ final IntelligencePerUserService service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ return service.isIntelligenceServiceForUserLocked(uid);
+ }
+ }
+
+ return false;
+ }
+ }
}
diff --git a/services/intelligence/java/com/android/server/intelligence/IntelligencePerUserService.java b/services/intelligence/java/com/android/server/intelligence/IntelligencePerUserService.java
index 584b872..3b9c4e2 100644
--- a/services/intelligence/java/com/android/server/intelligence/IntelligencePerUserService.java
+++ b/services/intelligence/java/com/android/server/intelligence/IntelligencePerUserService.java
@@ -42,7 +42,8 @@
* Per-user instance of {@link IntelligenceManagerService}.
*/
final class IntelligencePerUserService
- extends AbstractPerUserSystemService<IntelligencePerUserService> {
+ extends AbstractPerUserSystemService<IntelligencePerUserService,
+ IntelligenceManagerService> {
private static final String TAG = "IntelligencePerUserService";
@@ -88,12 +89,18 @@
@NonNull ComponentName componentName, int taskId, int displayId,
@NonNull InteractionSessionId sessionId, int flags,
@NonNull IResultReceiver resultReceiver) {
+ if (!isEnabledLocked()) {
+ sendToClient(resultReceiver, IntelligenceManager.STATE_DISABLED);
+ return;
+ }
final ComponentName serviceComponentName = getServiceComponentName();
if (serviceComponentName == null) {
// TODO(b/111276913): this happens when the system service is starting, we should
// probably handle it in a more elegant way (like waiting for boot_complete or
// something like that
- Slog.w(TAG, "startSession(" + activityToken + "): hold your horses");
+ if (mMaster.debug) {
+ Slog.d(TAG, "startSession(" + activityToken + "): hold your horses");
+ }
return;
}
@@ -128,9 +135,15 @@
// TODO(b/111276913): log metrics
@GuardedBy("mLock")
public void finishSessionLocked(@NonNull InteractionSessionId sessionId) {
+ if (!isEnabledLocked()) {
+ return;
+ }
+
final ContentCaptureSession session = mSessions.get(sessionId);
if (session == null) {
- Slog.w(TAG, "finishSession(): no session with id" + sessionId);
+ if (mMaster.debug) {
+ Slog.d(TAG, "finishSession(): no session with id" + sessionId);
+ }
return;
}
if (mMaster.verbose) {
@@ -139,12 +152,19 @@
session.removeSelfLocked(true);
}
+ // TODO(b/111276913): need to figure out why some events are sent before session is started;
+ // probably because IntelligenceManager is not buffering them until it gets the session back
@GuardedBy("mLock")
public void sendEventsLocked(@NonNull InteractionSessionId sessionId,
@NonNull List<ContentCaptureEvent> events) {
+ if (!isEnabledLocked()) {
+ return;
+ }
final ContentCaptureSession session = mSessions.get(sessionId);
if (session == null) {
- Slog.w(TAG, "sendEvents(): no session for " + sessionId);
+ if (mMaster.verbose) {
+ Slog.v(TAG, "sendEvents(): no session for " + sessionId);
+ }
return;
}
if (mMaster.verbose) {
@@ -158,6 +178,27 @@
mSessions.remove(sessionId);
}
+ @GuardedBy("mLock")
+ public boolean isIntelligenceServiceForUserLocked(int uid) {
+ return uid == getServiceUidLocked();
+ }
+
+ /**
+ * Destroys the service and all state associated with it.
+ *
+ * <p>Called when the service was disabled (for example, if the settings change).
+ */
+ @GuardedBy("mLock")
+ public void destroyLocked() {
+ if (mMaster.debug) Slog.d(TAG, "destroyLocked()");
+ final int numSessions = mSessions.size();
+ for (int i = 0; i < numSessions; i++) {
+ final ContentCaptureSession session = mSessions.valueAt(i);
+ session.destroyLocked(true);
+ }
+ mSessions.clear();
+ }
+
@Override
protected void dumpLocked(String prefix, PrintWriter pw) {
super.dumpLocked(prefix, pw);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 73990f8..f8ac41f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -35,6 +35,7 @@
import android.content.res.Resources.Theme;
import android.database.sqlite.SQLiteCompatibilityWalFlags;
import android.database.sqlite.SQLiteGlobal;
+import android.hardware.display.ColorDisplayManager;
import android.hardware.display.DisplayManagerInternal;
import android.os.BaseBundle;
import android.os.Binder;
@@ -62,7 +63,6 @@
import android.view.WindowManager;
import com.android.internal.R;
-import com.android.internal.app.ColorDisplayController;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BinderInternal;
@@ -150,6 +150,7 @@
import java.util.concurrent.Future;
public final class SystemServer {
+
private static final String TAG = "SystemServer";
// Tag for timing measurement of main thread.
@@ -263,9 +264,8 @@
private static final int sMaxBinderThreads = 31;
/**
- * Default theme used by the system context. This is used to style
- * system-provided dialogs, such as the Power Off dialog, and other
- * visual content.
+ * Default theme used by the system context. This is used to style system-provided dialogs, such
+ * as the Power Off dialog, and other visual content.
*/
private static final int DEFAULT_SYSTEM_THEME =
com.android.internal.R.style.Theme_DeviceDefault_System;
@@ -306,8 +306,7 @@
private static native void startSensorService();
/**
- * Start all HIDL services that are run inside the system server. This
- * may take some time.
+ * Start all HIDL services that are run inside the system server. This may take some time.
*/
private static native void startHidlServices();
@@ -343,7 +342,7 @@
//
// Default the timezone property to GMT if not set.
//
- String timezoneProperty = SystemProperties.get("persist.sys.timezone");
+ String timezoneProperty = SystemProperties.get("persist.sys.timezone");
if (timezoneProperty == null || timezoneProperty.isEmpty()) {
Slog.w(TAG, "Timezone not set; setting to GMT.");
SystemProperties.set("persist.sys.timezone", "GMT");
@@ -424,7 +423,7 @@
// Prepare the main looper thread (this thread).
android.os.Process.setThreadPriority(
- android.os.Process.THREAD_PRIORITY_FOREGROUND);
+ android.os.Process.THREAD_PRIORITY_FOREGROUND);
android.os.Process.setCanSelfBackground(false);
Looper.prepareMainLooper();
Looper.getMainLooper().setSlowLogThresholdMs(
@@ -529,7 +528,7 @@
if (filename != null && filename.startsWith("/data")) {
if (!new File(BLOCK_MAP_FILE).exists()) {
Slog.e(TAG, "Can't find block map file, uncrypt failed or " +
- "unexpected runtime restart?");
+ "unexpected runtime restart?");
return;
}
}
@@ -562,11 +561,10 @@
}
/**
- * Starts the small tangle of critical services that are needed to get
- * the system off the ground. These services have complex mutual dependencies
- * which is why we initialize them all in one place here. Unless your service
- * is also entwined in these dependencies, it should be initialized in one of
- * the other functions.
+ * Starts the small tangle of critical services that are needed to get the system off the
+ * ground. These services have complex mutual dependencies which is why we initialize them all
+ * in one place here. Unless your service is also entwined in these dependencies, it should be
+ * initialized in one of the other functions.
*/
private void startBootstrapServices() {
Slog.i(TAG, "Reading configuration...");
@@ -783,8 +781,7 @@
}
/**
- * Starts a miscellaneous grab bag of stuff that has yet to be refactored
- * and organized.
+ * Starts a miscellaneous grab bag of stuff that has yet to be refactored and organized.
*/
private void startOtherServices() {
final Context context = mSystemContext;
@@ -795,7 +792,7 @@
NetworkStatsService networkStats = null;
NetworkPolicyManagerService networkPolicy = null;
ConnectivityService connectivity = null;
- NsdService serviceDiscovery= null;
+ NsdService serviceDiscovery = null;
WindowManagerService wm = null;
SerialService serial = null;
NetworkTimeUpdateService networkTimeUpdater = null;
@@ -807,8 +804,11 @@
boolean disableSystemTextClassifier = SystemProperties.getBoolean(
"config.disable_systemtextclassifier", false);
+
+ //TODO(b/111276913): temporarily disabled until the manager is properly implemented to
+ // ignore events when disabled and buffer when enabled
boolean disableIntelligence = SystemProperties.getBoolean(
- "config.disable_intelligence", false);
+ "config.disable_intelligence", true);
boolean disableNetworkTime = SystemProperties.getBoolean("config.disable_networktime",
false);
boolean disableCameraService = SystemProperties.getBoolean("config.disable_cameraservice",
@@ -984,7 +984,7 @@
} else if (mFactoryTestMode == FactoryTest.FACTORY_TEST_LOW_LEVEL) {
Slog.i(TAG, "No Bluetooth Service (factory test)");
} else if (!context.getPackageManager().hasSystemFeature
- (PackageManager.FEATURE_BLUETOOTH)) {
+ (PackageManager.FEATURE_BLUETOOTH)) {
Slog.i(TAG, "No Bluetooth Service (Bluetooth Hardware Not Present)");
} else {
traceBeginAndSlog("StartBluetoothService");
@@ -1098,7 +1098,7 @@
try {
mSystemServiceManager.startService(LOCK_SETTINGS_SERVICE_CLASS);
lockSettings = ILockSettings.Stub.asInterface(
- ServiceManager.getService("lock_settings"));
+ ServiceManager.getService("lock_settings"));
} catch (Throwable e) {
reportWtf("starting LockSettingsService service", e);
}
@@ -1139,6 +1139,15 @@
traceEnd();
}
+ if (!disableIntelligence) {
+ traceBeginAndSlog("StartIntelligenceService");
+ mSystemServiceManager.startService(INTELLIGENCE_MANAGER_SERVICE_CLASS);
+ traceEnd();
+ } else {
+ Slog.d(TAG, "IntelligenceService disabled");
+ }
+
+ // NOTE: ClipboardService indirectly depends on IntelligenceService
traceBeginAndSlog("StartClipboardService");
mSystemServiceManager.startService(ClipboardService.class);
traceEnd();
@@ -1167,7 +1176,8 @@
if (!disableSystemTextClassifier) {
traceBeginAndSlog("StartTextClassificationManagerService");
- mSystemServiceManager.startService(TextClassificationManagerService.Lifecycle.class);
+ mSystemServiceManager
+ .startService(TextClassificationManagerService.Lifecycle.class);
traceEnd();
}
@@ -1196,41 +1206,41 @@
if (!mOnlyCore) {
if (context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_WIFI)) {
+ PackageManager.FEATURE_WIFI)) {
// Wifi Service must be started first for wifi-related services.
traceBeginAndSlog("StartWifi");
mSystemServiceManager.startService(WIFI_SERVICE_CLASS);
traceEnd();
traceBeginAndSlog("StartWifiScanning");
mSystemServiceManager.startService(
- "com.android.server.wifi.scanner.WifiScanningService");
+ "com.android.server.wifi.scanner.WifiScanningService");
traceEnd();
}
if (context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_WIFI_RTT)) {
+ PackageManager.FEATURE_WIFI_RTT)) {
traceBeginAndSlog("StartRttService");
mSystemServiceManager.startService(
- "com.android.server.wifi.rtt.RttService");
+ "com.android.server.wifi.rtt.RttService");
traceEnd();
}
if (context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_WIFI_AWARE)) {
+ PackageManager.FEATURE_WIFI_AWARE)) {
traceBeginAndSlog("StartWifiAware");
mSystemServiceManager.startService(WIFI_AWARE_SERVICE_CLASS);
traceEnd();
}
if (context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_WIFI_DIRECT)) {
+ PackageManager.FEATURE_WIFI_DIRECT)) {
traceBeginAndSlog("StartWifiP2P");
mSystemServiceManager.startService(WIFI_P2P_SERVICE_CLASS);
traceEnd();
}
if (context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_LOWPAN)) {
+ PackageManager.FEATURE_LOWPAN)) {
traceBeginAndSlog("StartLowpan");
mSystemServiceManager.startService(LOWPAN_SERVICE_CLASS);
traceEnd();
@@ -1238,7 +1248,7 @@
}
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_ETHERNET) ||
- mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
+ mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
traceBeginAndSlog("StartEthernet");
mSystemServiceManager.startService(ETHERNET_SERVICE_CLASS);
traceEnd();
@@ -1247,10 +1257,10 @@
traceBeginAndSlog("StartConnectivityService");
try {
connectivity = new ConnectivityService(
- context, networkManagement, networkStats, networkPolicy);
+ context, networkManagement, networkStats, networkPolicy);
ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity,
- /* allowIsolated= */ false,
- DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
+ /* allowIsolated= */ false,
+ DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
networkStats.bindConnectivityManager(connectivity);
networkPolicy.bindConnectivityManager(connectivity);
} catch (Throwable e) {
@@ -1262,7 +1272,7 @@
try {
serviceDiscovery = NsdService.create(context);
ServiceManager.addService(
- Context.NSD_SERVICE, serviceDiscovery);
+ Context.NSD_SERVICE, serviceDiscovery);
} catch (Throwable e) {
reportWtf("starting Service Discovery Service", e);
}
@@ -1280,7 +1290,7 @@
traceBeginAndSlog("StartUpdateLockService");
try {
ServiceManager.addService(Context.UPDATE_LOCK_SERVICE,
- new UpdateLockService(context));
+ new UpdateLockService(context));
} catch (Throwable e) {
reportWtf("starting UpdateLockService", e);
}
@@ -1398,9 +1408,9 @@
}
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)
- || mPackageManager.hasSystemFeature(
- PackageManager.FEATURE_USB_ACCESSORY)
- || isEmulator) {
+ || mPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_USB_ACCESSORY)
+ || isEmulator) {
// Manage USB host and device support
traceBeginAndSlog("StartUsbService");
mSystemServiceManager.startService(USB_SERVICE_CLASS);
@@ -1432,7 +1442,7 @@
try {
hardwarePropertiesService = new HardwarePropertiesManagerService(context);
ServiceManager.addService(Context.HARDWARE_PROPERTIES_SERVICE,
- hardwarePropertiesService);
+ hardwarePropertiesService);
} catch (Throwable e) {
Slog.e(TAG, "Failure starting HardwarePropertiesManagerService", e);
}
@@ -1442,8 +1452,8 @@
mSystemServiceManager.startService(TwilightService.class);
traceEnd();
- if (ColorDisplayController.isAvailable(context)) {
- traceBeginAndSlog("StartNightDisplay");
+ if (ColorDisplayManager.isNightDisplayAvailable(context)) {
+ traceBeginAndSlog("StartColorDisplay");
mSystemServiceManager.startService(ColorDisplayService.class);
traceEnd();
}
@@ -1467,7 +1477,7 @@
}
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)
- || context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) {
+ || context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) {
traceBeginAndSlog("StartAppWidgetService");
mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS);
traceEnd();
@@ -1553,7 +1563,7 @@
traceBeginAndSlog("AddGraphicsStatsService");
ServiceManager.addService(GraphicsStatsService.GRAPHICS_STATS_SERVICE,
- new GraphicsStatsService(context));
+ new GraphicsStatsService(context));
traceEnd();
if (CoverageService.ENABLED) {
@@ -1769,12 +1779,6 @@
traceEnd();
}
- if (!disableIntelligence) {
- traceBeginAndSlog("StartIntelligenceService");
- mSystemServiceManager.startService(INTELLIGENCE_MANAGER_SERVICE_CLASS);
- traceEnd();
- }
-
traceBeginAndSlog("AppServiceManager");
mSystemServiceManager.startService(AppBindingService.Lifecycle.class);
traceEnd();
@@ -1825,7 +1829,7 @@
// propagate to it.
final Configuration config = wm.computeNewConfiguration(DEFAULT_DISPLAY);
DisplayMetrics metrics = new DisplayMetrics();
- WindowManager w = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+ WindowManager w = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
w.getDefaultDisplay().getMetrics(metrics);
context.getResources().updateConfiguration(config, metrics);
@@ -1949,7 +1953,9 @@
traceEnd();
traceBeginAndSlog("MakeNetworkManagementServiceReady");
try {
- if (networkManagementF != null) networkManagementF.systemReady();
+ if (networkManagementF != null) {
+ networkManagementF.systemReady();
+ }
} catch (Throwable e) {
reportWtf("making Network Managment Service ready", e);
}
@@ -1961,21 +1967,27 @@
traceEnd();
traceBeginAndSlog("MakeIpSecServiceReady");
try {
- if (ipSecServiceF != null) ipSecServiceF.systemReady();
+ if (ipSecServiceF != null) {
+ ipSecServiceF.systemReady();
+ }
} catch (Throwable e) {
reportWtf("making IpSec Service ready", e);
}
traceEnd();
traceBeginAndSlog("MakeNetworkStatsServiceReady");
try {
- if (networkStatsF != null) networkStatsF.systemReady();
+ if (networkStatsF != null) {
+ networkStatsF.systemReady();
+ }
} catch (Throwable e) {
reportWtf("making Network Stats Service ready", e);
}
traceEnd();
traceBeginAndSlog("MakeConnectivityServiceReady");
try {
- if (connectivityF != null) connectivityF.systemReady();
+ if (connectivityF != null) {
+ connectivityF.systemReady();
+ }
} catch (Throwable e) {
reportWtf("making Connectivity Service ready", e);
}
@@ -2010,21 +2022,27 @@
traceBeginAndSlog("MakeLocationServiceReady");
try {
- if (locationF != null) locationF.systemRunning();
+ if (locationF != null) {
+ locationF.systemRunning();
+ }
} catch (Throwable e) {
reportWtf("Notifying Location Service running", e);
}
traceEnd();
traceBeginAndSlog("MakeCountryDetectionServiceReady");
try {
- if (countryDetectorF != null) countryDetectorF.systemRunning();
+ if (countryDetectorF != null) {
+ countryDetectorF.systemRunning();
+ }
} catch (Throwable e) {
reportWtf("Notifying CountryDetectorService running", e);
}
traceEnd();
traceBeginAndSlog("MakeNetworkTimeUpdateReady");
try {
- if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemRunning();
+ if (networkTimeUpdaterF != null) {
+ networkTimeUpdaterF.systemRunning();
+ }
} catch (Throwable e) {
reportWtf("Notifying NetworkTimeService running", e);
}
@@ -2032,28 +2050,36 @@
traceBeginAndSlog("MakeInputManagerServiceReady");
try {
// TODO(BT) Pass parameter to input manager
- if (inputManagerF != null) inputManagerF.systemRunning();
+ if (inputManagerF != null) {
+ inputManagerF.systemRunning();
+ }
} catch (Throwable e) {
reportWtf("Notifying InputManagerService running", e);
}
traceEnd();
traceBeginAndSlog("MakeTelephonyRegistryReady");
try {
- if (telephonyRegistryF != null) telephonyRegistryF.systemRunning();
+ if (telephonyRegistryF != null) {
+ telephonyRegistryF.systemRunning();
+ }
} catch (Throwable e) {
reportWtf("Notifying TelephonyRegistry running", e);
}
traceEnd();
traceBeginAndSlog("MakeMediaRouterServiceReady");
try {
- if (mediaRouterF != null) mediaRouterF.systemRunning();
+ if (mediaRouterF != null) {
+ mediaRouterF.systemRunning();
+ }
} catch (Throwable e) {
reportWtf("Notifying MediaRouterService running", e);
}
traceEnd();
traceBeginAndSlog("MakeMmsServiceReady");
try {
- if (mmsServiceF != null) mmsServiceF.systemRunning();
+ if (mmsServiceF != null) {
+ mmsServiceF.systemRunning();
+ }
} catch (Throwable e) {
reportWtf("Notifying MmsService running", e);
}
@@ -2065,7 +2091,9 @@
// in the build and should reliably be there.
final IIncidentManager incident = IIncidentManager.Stub.asInterface(
ServiceManager.getService(Context.INCIDENT_SERVICE));
- if (incident != null) incident.systemRunning();
+ if (incident != null) {
+ incident.systemRunning();
+ }
} catch (Throwable e) {
reportWtf("Notifying incident daemon running", e);
}
@@ -2076,7 +2104,7 @@
static final void startSystemUi(Context context, WindowManagerService windowManager) {
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.android.systemui",
- "com.android.systemui.SystemUIService"));
+ "com.android.systemui.SystemUIService"));
intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
//Slog.d(TAG, "Starting service: " + intent);
context.startServiceAsUser(intent, UserHandle.SYSTEM);
diff --git a/services/robotests/Android.mk b/services/robotests/Android.mk
index 9ab06a1..565152c 100644
--- a/services/robotests/Android.mk
+++ b/services/robotests/Android.mk
@@ -27,7 +27,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
services.backup \
- services.core
+ services.core \
+ services.net
include $(BUILD_PACKAGE)
diff --git a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java
index c4cb593..25d1cc7 100644
--- a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -11,7 +11,7 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
package com.android.server.backup;
@@ -75,6 +75,7 @@
import java.io.File;
import java.util.List;
+/** Tests for the system service {@link BackupManagerService} that performs backup/restore. */
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowAppBackupUtils.class})
@Presubmit
@@ -94,6 +95,10 @@
private String mTransportName;
private ShadowPackageManager mShadowPackageManager;
+ /**
+ * Initialize state that {@link BackupManagerService} operations interact with. This includes
+ * setting up the transport, starting the backup thread, and creating backup data directories.
+ */
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -119,12 +124,20 @@
mDataDir = new File(cacheDir, "data");
}
+ /**
+ * Clean up and reset state that was created for testing {@link BackupManagerService}
+ * operations.
+ */
@After
public void tearDown() throws Exception {
mBackupThread.quit();
ShadowAppBackupUtils.reset();
}
+ /**
+ * Test verifying that {@link BackupManagerService#MORE_DEBUG} is set to {@code false}. This is
+ * specifically to prevent overloading the logs in production.
+ */
@Test
public void testMoreDebug_isFalse() throws Exception {
boolean moreDebug = BackupManagerService.MORE_DEBUG;
@@ -132,8 +145,10 @@
assertThat(moreDebug).isFalse();
}
- /* Tests for destination string */
-
+ /**
+ * Test verifying that {@link BackupManagerService#getDestinationString(String)} returns the
+ * current destination string of inputted transport if the transport is registered.
+ */
@Test
public void testDestinationString() throws Exception {
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -146,6 +161,10 @@
assertThat(destination).isEqualTo("destinationString");
}
+ /**
+ * Test verifying that {@link BackupManagerService#getDestinationString(String)} returns {@code
+ * null} if the inputted transport is not registered.
+ */
@Test
public void testDestinationString_whenTransportNotRegistered() throws Exception {
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -158,6 +177,10 @@
assertThat(destination).isNull();
}
+ /**
+ * Test verifying that {@link BackupManagerService#getDestinationString(String)} throws a {@link
+ * SecurityException} if the caller does not have backup permission.
+ */
@Test
public void testDestinationString_withoutPermission() throws Exception {
mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
@@ -170,8 +193,10 @@
() -> backupManagerService.getDestinationString(mTransportName));
}
- /* Tests for app eligibility */
-
+ /**
+ * Test verifying that {@link BackupManagerService#isAppEligibleForBackup(String)} returns
+ * {@code false} when the given app is not eligible for backup.
+ */
@Test
public void testIsAppEligibleForBackup_whenAppNotEligible() throws Exception {
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -183,6 +208,10 @@
assertThat(result).isFalse();
}
+ /**
+ * Test verifying that {@link BackupManagerService#isAppEligibleForBackup(String)} returns
+ * {@code true} when the given app is eligible for backup.
+ */
@Test
public void testIsAppEligibleForBackup_whenAppEligible() throws Exception {
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -197,6 +226,10 @@
.disposeOfTransportClient(eq(transportMock.transportClient), any());
}
+ /**
+ * Test verifying that {@link BackupManagerService#isAppEligibleForBackup(String)} throws a
+ * {@link SecurityException} if the caller does not have backup permission.
+ */
@Test
public void testIsAppEligibleForBackup_withoutPermission() throws Exception {
mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
@@ -209,6 +242,11 @@
() -> backupManagerService.isAppEligibleForBackup(PACKAGE_1));
}
+ /**
+ * Test verifying that {@link BackupManagerService#filterAppsEligibleForBackup(String[])}
+ * returns an {@code array} of only apps that are eligible for backup from an {@array} of
+ * inputted apps.
+ */
@Test
public void testFilterAppsEligibleForBackup() throws Exception {
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -225,6 +263,10 @@
.disposeOfTransportClient(eq(transportMock.transportClient), any());
}
+ /**
+ * Test verifying that {@link BackupManagerService#filterAppsEligibleForBackup(String[])}
+ * returns an empty {@code array} if no inputted apps are eligible for backup.
+ */
@Test
public void testFilterAppsEligibleForBackup_whenNoneIsEligible() throws Exception {
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -237,6 +279,10 @@
assertThat(filtered).isEmpty();
}
+ /**
+ * Test verifying that {@link BackupManagerService#filterAppsEligibleForBackup(String[])} throws
+ * a {@link SecurityException} if the caller does not have backup permission.
+ */
@Test
public void testFilterAppsEligibleForBackup_withoutPermission() throws Exception {
mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
@@ -270,6 +316,11 @@
.thenReturn(mOldTransport.transportName);
}
+ /**
+ * Test verifying that {@link BackupManagerService#selectBackupTransport(String)} successfully
+ * switches the current transport to the inputted transport, returns the name of the old
+ * transport, and disposes of the transport client after the operation.
+ */
@Test
public void testSelectBackupTransport() throws Exception {
setUpForSelectTransport();
@@ -285,6 +336,10 @@
.disposeOfTransportClient(eq(mNewTransportMock.transportClient), any());
}
+ /**
+ * Test verifying that {@link BackupManagerService#selectBackupTransport(String)} throws a
+ * {@link SecurityException} if the caller does not have backup permission.
+ */
@Test
public void testSelectBackupTransport_withoutPermission() throws Exception {
setUpForSelectTransport();
@@ -296,6 +351,11 @@
() -> backupManagerService.selectBackupTransport(mNewTransport.transportName));
}
+ /**
+ * Test verifying that {@link BackupManagerService#selectBackupTransportAsync(ComponentName,
+ * ISelectBackupTransportCallback)} successfully switches the current transport to the inputted
+ * transport and disposes of the transport client after the operation.
+ */
@Test
public void testSelectBackupTransportAsync() throws Exception {
setUpForSelectTransport();
@@ -314,6 +374,12 @@
.disposeOfTransportClient(eq(mNewTransportMock.transportClient), any());
}
+ /**
+ * Test verifying that {@link BackupManagerService#selectBackupTransportAsync(ComponentName,
+ * ISelectBackupTransportCallback)} does not switch the current transport to the inputted
+ * transport and notifies the inputted callback of failure when it fails to register the
+ * transport.
+ */
@Test
public void testSelectBackupTransportAsync_whenRegistrationFails() throws Exception {
setUpForSelectTransport();
@@ -330,6 +396,11 @@
verify(callback).onFailure(anyInt());
}
+ /**
+ * Test verifying that {@link BackupManagerService#selectBackupTransportAsync(ComponentName,
+ * ISelectBackupTransportCallback)} does not switch the current transport to the inputted
+ * transport and notifies the inputted callback of failure when the transport gets unregistered.
+ */
@Test
public void testSelectBackupTransportAsync_whenTransportGetsUnregistered() throws Exception {
setUpTransports(mTransportManager, mTransport.unregistered());
@@ -347,6 +418,11 @@
verify(callback).onFailure(anyInt());
}
+ /**
+ * Test verifying that {@link BackupManagerService#selectBackupTransportAsync(ComponentName,
+ * ISelectBackupTransportCallback)} throws a {@link SecurityException} if the caller does not
+ * have backup permission.
+ */
@Test
public void testSelectBackupTransportAsync_withoutPermission() throws Exception {
setUpForSelectTransport();
@@ -366,8 +442,10 @@
mContext.getContentResolver(), Settings.Secure.BACKUP_TRANSPORT);
}
- /* Tests for transport attributes */
-
+ /**
+ * Test verifying that {@link BackupManagerService#getCurrentTransportComponent()} returns the
+ * {@link ComponentName} of the currently selected transport.
+ */
@Test
public void testGetCurrentTransportComponent() throws Exception {
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -380,6 +458,10 @@
assertThat(transportComponent).isEqualTo(mTransport.getTransportComponent());
}
+ /**
+ * Test verifying that {@link BackupManagerService#getCurrentTransportComponent()} returns
+ * {@code null} if there is no currently selected transport.
+ */
@Test
public void testGetCurrentTransportComponent_whenNoTransportSelected() throws Exception {
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -391,6 +473,10 @@
assertThat(transportComponent).isNull();
}
+ /**
+ * Test verifying that {@link BackupManagerService#getCurrentTransportComponent()} returns
+ * {@code null} if the currently selected transport is not registered.
+ */
@Test
public void testGetCurrentTransportComponent_whenTransportNotRegistered() throws Exception {
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -403,6 +489,10 @@
assertThat(transportComponent).isNull();
}
+ /**
+ * Test verifying that {@link BackupManagerService#getCurrentTransportComponent()} throws a
+ * {@link SecurityException} if the caller does not have backup permission.
+ */
@Test
public void testGetCurrentTransportComponent_withoutPermission() throws Exception {
mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
@@ -428,9 +518,14 @@
mTransportUid = mContext.getPackageManager().getPackageUid(transportPackage, 0);
}
+ /**
+ * Test verifying that {@link BackupManagerService#updateTransportAttributes(int, ComponentName,
+ * String, Intent, String, Intent, String)} succeeds if the uid of the transport is same as the
+ * uid of the caller.
+ */
@Test
public void
- testUpdateTransportAttributes_whenTransportUidEqualsToCallingUid_callsThroughToTransportManager()
+ testUpdateTransportAttributes_whenTransportUidEqualsCallingUid_callsTransportManager()
throws Exception {
setUpForUpdateTransportAttributes();
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -457,6 +552,11 @@
eq("dataManagementLabel"));
}
+ /**
+ * Test verifying that {@link BackupManagerService#updateTransportAttributes(int, ComponentName,
+ * String, Intent, String, Intent, String)} throws a {@link SecurityException} if the uid of the
+ * transport is not equal to the uid of the caller.
+ */
@Test
public void testUpdateTransportAttributes_whenTransportUidNotEqualToCallingUid_throwsException()
throws Exception {
@@ -477,6 +577,11 @@
"dataManagementLabel"));
}
+ /**
+ * Test verifying that {@link BackupManagerService#updateTransportAttributes(int, ComponentName,
+ * String, Intent, String, Intent, String)} throws a {@link RuntimeException} if given a {@code
+ * null} transport component.
+ */
@Test
public void testUpdateTransportAttributes_whenTransportComponentNull_throwsException()
throws Exception {
@@ -497,6 +602,11 @@
"dataManagementLabel"));
}
+ /**
+ * Test verifying that {@link BackupManagerService#updateTransportAttributes(int, ComponentName,
+ * String, Intent, String, Intent, String)} throws a {@link RuntimeException} if given a {@code
+ * null} transport name.
+ */
@Test
public void testUpdateTransportAttributes_whenNameNull_throwsException() throws Exception {
setUpForUpdateTransportAttributes();
@@ -516,6 +626,11 @@
"dataManagementLabel"));
}
+ /**
+ * Test verifying that {@link BackupManagerService#updateTransportAttributes(int, ComponentName,
+ * String, Intent, String, Intent, String)} throws a {@link RuntimeException} if given a {@code
+ * null} destination string.
+ */
@Test
public void testUpdateTransportAttributes_whenCurrentDestinationStringNull_throwsException()
throws Exception {
@@ -536,9 +651,14 @@
"dataManagementLabel"));
}
+ /**
+ * Test verifying that {@link BackupManagerService#updateTransportAttributes(int, ComponentName,
+ * String, Intent, String, Intent, String)} throws a {@link RuntimeException} if given either a
+ * {@code null} data management label or {@code null} data management intent, but not both.
+ */
@Test
public void
- testUpdateTransportAttributes_whenDataManagementArgumentsNullityDontMatch_throwsException()
+ testUpdateTransportAttributes_whenDataManagementArgsNullityDontMatch_throwsException()
throws Exception {
setUpForUpdateTransportAttributes();
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -569,6 +689,10 @@
null));
}
+ /**
+ * Test verifying that {@link BackupManagerService#updateTransportAttributes(int, ComponentName,
+ * String, Intent, String, Intent, String)} succeeds if the caller has backup permission.
+ */
@Test
public void testUpdateTransportAttributes_whenPermissionGranted_callsThroughToTransportManager()
throws Exception {
@@ -597,6 +721,11 @@
eq("dataManagementLabel"));
}
+ /**
+ * Test verifying that {@link BackupManagerService#updateTransportAttributes(int, ComponentName,
+ * String, Intent, String, Intent, String)} throws a {@link SecurityException} if the caller
+ * does not have backup permission.
+ */
@Test
public void testUpdateTransportAttributes_whenPermissionDenied_throwsSecurityException()
throws Exception {
@@ -634,6 +763,10 @@
ShadowKeyValueBackupTask.reset();
}
+ /**
+ * Test verifying that {@link BackupManagerService#requestBackup(String[], IBackupObserver,
+ * int)} throws a {@link SecurityException} if the caller does not have backup permission.
+ */
@Test
public void testRequestBackup_whenPermissionDenied() throws Exception {
mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
@@ -644,6 +777,10 @@
() -> backupManagerService.requestBackup(new String[] {PACKAGE_1}, mObserver, 0));
}
+ /**
+ * Test verifying that {@link BackupManagerService#requestBackup(String[], IBackupObserver,
+ * int)} throws an {@link IllegalArgumentException} if passed {@null} for packages.
+ */
@Test
public void testRequestBackup_whenPackagesNull() throws Exception {
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -655,6 +792,11 @@
verify(mObserver).backupFinished(BackupManager.ERROR_TRANSPORT_ABORTED);
}
+ /**
+ * Test verifying that {@link BackupManagerService#requestBackup(String[], IBackupObserver,
+ * int)} throws an {@link IllegalArgumentException} if passed an empty {@code array} for
+ * packages.
+ */
@Test
public void testRequestBackup_whenPackagesEmpty() throws Exception {
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -666,6 +808,10 @@
verify(mObserver).backupFinished(BackupManager.ERROR_TRANSPORT_ABORTED);
}
+ /**
+ * Test verifying that {@link BackupManagerService#requestBackup(String[], IBackupObserver,
+ * int)} returns {@link BackupManager#ERROR_BACKUP_NOT_ALLOWED} if backup is disabled.
+ */
@Test
public void testRequestBackup_whenBackupDisabled() throws Exception {
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -678,6 +824,11 @@
verify(mObserver).backupFinished(BackupManager.ERROR_BACKUP_NOT_ALLOWED);
}
+ /**
+ * Test verifying that {@link BackupManagerService#requestBackup(String[], IBackupObserver,
+ * int)} returns {@link BackupManager#ERROR_BACKUP_NOT_ALLOWED} if the system user hasn't gone
+ * through SUW.
+ */
@Test
public void testRequestBackup_whenNotProvisioned() throws Exception {
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -690,6 +841,11 @@
verify(mObserver).backupFinished(BackupManager.ERROR_BACKUP_NOT_ALLOWED);
}
+ /**
+ * Test verifying that {@link BackupManagerService#requestBackup(String[], IBackupObserver,
+ * int)} returns {@link BackupManager#ERROR_TRANSPORT_ABORTED} if the current transport is not
+ * registered.
+ */
@Test
public void testRequestBackup_whenTransportNotRegistered() throws Exception {
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -704,6 +860,11 @@
verify(mObserver).backupFinished(BackupManager.ERROR_TRANSPORT_ABORTED);
}
+ /**
+ * Test verifying that {@link BackupManagerService#requestBackup(String[], IBackupObserver,
+ * int)} returns {@link BackupManager#SUCCESS} and notifies the observer of {@link
+ * BackupManager#ERROR_BACKUP_NOT_ALLOWED} if the specified app is not eligible for backup.
+ */
@Test
public void testRequestBackup_whenAppNotEligibleForBackup() throws Exception {
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -722,6 +883,11 @@
tearDownForRequestBackup();
}
+ /**
+ * Test verifying that {@link BackupManagerService#requestBackup(String[], IBackupObserver,
+ * int)} returns {@link BackupManager#SUCCESS} and updates bookkeeping if backup for a key value
+ * package succeeds.
+ */
@Test
@Config(shadows = ShadowKeyValueBackupTask.class)
public void testRequestBackup_whenPackageIsKeyValue() throws Exception {
@@ -739,6 +905,11 @@
tearDownForRequestBackup();
}
+ /**
+ * Test verifying that {@link BackupManagerService#requestBackup(String[], IBackupObserver,
+ * int)} returns {@link BackupManager#SUCCESS} and updates bookkeeping if backup for a full
+ * backup package succeeds.
+ */
@Test
@Config(shadows = ShadowKeyValueBackupTask.class)
public void testRequestBackup_whenPackageIsFullBackup() throws Exception {
@@ -757,6 +928,10 @@
tearDownForRequestBackup();
}
+ /**
+ * Test verifying that {@link BackupManagerService#backupNow()} clears the calling identity
+ * for scheduling a job and then restores the original calling identity after the operation.
+ */
@Test
@Config(shadows = {ShadowBinder.class, ShadowKeyValueBackupJob.class})
public void testBackupNow_clearsCallingIdentityForJobScheduler() {
@@ -771,6 +946,10 @@
assertThat(ShadowBinder.getCallingUid()).isEqualTo(1);
}
+ /**
+ * Test verifying that {@link BackupManagerService#backupNow()} restores the original calling
+ * identity if an exception is thrown during execution.
+ */
@Test
@Config(shadows = {ShadowBinder.class, ShadowKeyValueBackupJobException.class})
public void testBackupNow_whenExceptionThrown_restoresCallingIdentity() {
@@ -792,8 +971,11 @@
return backupManagerService;
}
- /* Miscellaneous tests */
-
+ /**
+ * Test verifying that {@link BackupManagerService#BackupManagerService(Context, Trampoline,
+ * HandlerThread, File, File, TransportManager)} posts a transport registration task to the
+ * backup handler thread.
+ */
@Test
public void testConstructor_postRegisterTransports() {
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -804,6 +986,11 @@
verify(mTransportManager).registerTransports();
}
+ /**
+ * Test verifying that the {@link BackupManagerService#BackupManagerService(Context, Trampoline,
+ * HandlerThread, File, File, TransportManager)} does not directly register transports in its
+ * own thread.
+ */
@Test
public void testConstructor_doesNotRegisterTransportsSynchronously() {
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
@@ -842,6 +1029,10 @@
*/
@Implements(KeyValueBackupJob.class)
public static class ShadowKeyValueBackupJobException extends ShadowKeyValueBackupJob {
+ /**
+ * Implementation of {@link ShadowKeyValueBackupJob#schedule(Context, long,
+ * BackupManagerConstants)} that throws an {@link IllegalArgumentException}.
+ */
public static void schedule(Context ctx, long delay, BackupManagerConstants constants) {
ShadowKeyValueBackupJob.schedule(ctx, delay, constants);
throw new IllegalArgumentException();
diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunkerTest.java b/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunkerTest.java
new file mode 100644
index 0000000..77b7347
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunkerTest.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking.cdc;
+
+import static com.android.server.backup.testing.CryptoTestUtils.generateAesKey;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Random;
+
+import javax.crypto.SecretKey;
+
+/** Tests for {@link ContentDefinedChunker}. */
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class ContentDefinedChunkerTest {
+ private static final int WINDOW_SIZE_BYTES = 31;
+ private static final int MIN_SIZE_BYTES = 40;
+ private static final int MAX_SIZE_BYTES = 300;
+ private static final String CHUNK_BOUNDARY = "<----------BOUNDARY----------->";
+ private static final byte[] CHUNK_BOUNDARY_BYTES = CHUNK_BOUNDARY.getBytes(UTF_8);
+ private static final String CHUNK_1 = "This is the first chunk";
+ private static final String CHUNK_2 = "And this is the second chunk";
+ private static final String CHUNK_3 = "And finally here is the third chunk";
+ private static final String SMALL_CHUNK = "12345678";
+
+ private FingerprintMixer mFingerprintMixer;
+ private RabinFingerprint64 mRabinFingerprint64;
+ private ContentDefinedChunker mChunker;
+
+ /** Set up a {@link ContentDefinedChunker} and dependencies for use in the tests. */
+ @Before
+ public void setUp() throws Exception {
+ SecretKey secretKey = generateAesKey();
+ byte[] salt = new byte[FingerprintMixer.SALT_LENGTH_BYTES];
+ Random random = new Random();
+ random.nextBytes(salt);
+ mFingerprintMixer = new FingerprintMixer(secretKey, salt);
+
+ mRabinFingerprint64 = new RabinFingerprint64();
+ long chunkBoundaryFingerprint = calculateFingerprint(CHUNK_BOUNDARY_BYTES);
+ mChunker =
+ new ContentDefinedChunker(
+ MIN_SIZE_BYTES,
+ MAX_SIZE_BYTES,
+ mRabinFingerprint64,
+ mFingerprintMixer,
+ (fingerprint) -> fingerprint == chunkBoundaryFingerprint);
+ }
+
+ /**
+ * Creating a {@link ContentDefinedChunker} with a minimum chunk size that is smaller than the
+ * window size should throw an {@link IllegalArgumentException}.
+ */
+ @Test
+ public void create_withMinChunkSizeSmallerThanWindowSize_throwsIllegalArgumentException() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ new ContentDefinedChunker(
+ WINDOW_SIZE_BYTES - 1,
+ MAX_SIZE_BYTES,
+ mRabinFingerprint64,
+ mFingerprintMixer,
+ null));
+ }
+
+ /**
+ * Creating a {@link ContentDefinedChunker} with a maximum chunk size that is smaller than the
+ * minimum chunk size should throw an {@link IllegalArgumentException}.
+ */
+ @Test
+ public void create_withMaxChunkSizeSmallerThanMinChunkSize_throwsIllegalArgumentException() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ new ContentDefinedChunker(
+ MIN_SIZE_BYTES,
+ MIN_SIZE_BYTES - 1,
+ mRabinFingerprint64,
+ mFingerprintMixer,
+ null));
+ }
+
+ /**
+ * {@link ContentDefinedChunker#chunkify(InputStream, Chunker.ChunkConsumer)} should split the
+ * input stream across chunk boundaries by default.
+ */
+ @Test
+ public void chunkify_withLargeChunks_splitsIntoChunksAcrossBoundaries() throws Exception {
+ byte[] input =
+ (CHUNK_1 + CHUNK_BOUNDARY + CHUNK_2 + CHUNK_BOUNDARY + CHUNK_3).getBytes(UTF_8);
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(input);
+ ArrayList<String> result = new ArrayList<>();
+
+ mChunker.chunkify(inputStream, (chunk) -> result.add(new String(chunk, UTF_8)));
+
+ assertThat(result)
+ .containsExactly(CHUNK_1 + CHUNK_BOUNDARY, CHUNK_2 + CHUNK_BOUNDARY, CHUNK_3)
+ .inOrder();
+ }
+
+ /** Chunks should be combined across boundaries until they reach the minimum chunk size. */
+ @Test
+ public void chunkify_withSmallChunks_combinesChunksUntilMinSize() throws Exception {
+ byte[] input =
+ (SMALL_CHUNK + CHUNK_BOUNDARY + CHUNK_2 + CHUNK_BOUNDARY + CHUNK_3).getBytes(UTF_8);
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(input);
+ ArrayList<String> result = new ArrayList<>();
+
+ mChunker.chunkify(inputStream, (chunk) -> result.add(new String(chunk, UTF_8)));
+
+ assertThat(result)
+ .containsExactly(SMALL_CHUNK + CHUNK_BOUNDARY + CHUNK_2 + CHUNK_BOUNDARY, CHUNK_3)
+ .inOrder();
+ assertThat(result.get(0).length()).isAtLeast(MIN_SIZE_BYTES);
+ }
+
+ /** Chunks can not be larger than the maximum chunk size. */
+ @Test
+ public void chunkify_doesNotProduceChunksLargerThanMaxSize() throws Exception {
+ byte[] largeInput = new byte[MAX_SIZE_BYTES * 10];
+ Arrays.fill(largeInput, "a".getBytes(UTF_8)[0]);
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(largeInput);
+ ArrayList<String> result = new ArrayList<>();
+
+ mChunker.chunkify(inputStream, (chunk) -> result.add(new String(chunk, UTF_8)));
+
+ byte[] expectedChunkBytes = new byte[MAX_SIZE_BYTES];
+ Arrays.fill(expectedChunkBytes, "a".getBytes(UTF_8)[0]);
+ String expectedChunk = new String(expectedChunkBytes, UTF_8);
+ assertThat(result)
+ .containsExactly(
+ expectedChunk,
+ expectedChunk,
+ expectedChunk,
+ expectedChunk,
+ expectedChunk,
+ expectedChunk,
+ expectedChunk,
+ expectedChunk,
+ expectedChunk,
+ expectedChunk)
+ .inOrder();
+ }
+
+ /**
+ * If the input stream signals zero availablility, {@link
+ * ContentDefinedChunker#chunkify(InputStream, Chunker.ChunkConsumer)} should still work.
+ */
+ @Test
+ public void chunkify_withInputStreamReturningZeroAvailability_returnsChunks() throws Exception {
+ byte[] input = (SMALL_CHUNK + CHUNK_BOUNDARY + CHUNK_2).getBytes(UTF_8);
+ ZeroAvailabilityInputStream zeroAvailabilityInputStream =
+ new ZeroAvailabilityInputStream(input);
+ ArrayList<String> result = new ArrayList<>();
+
+ mChunker.chunkify(
+ zeroAvailabilityInputStream, (chunk) -> result.add(new String(chunk, UTF_8)));
+
+ assertThat(result).containsExactly(SMALL_CHUNK + CHUNK_BOUNDARY + CHUNK_2).inOrder();
+ }
+
+ /**
+ * {@link ContentDefinedChunker#chunkify(InputStream, Chunker.ChunkConsumer)} should rethrow any
+ * exception thrown by its consumer.
+ */
+ @Test
+ public void chunkify_whenConsumerThrowsException_rethrowsException() throws Exception {
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[] {1});
+
+ assertThrows(
+ GeneralSecurityException.class,
+ () ->
+ mChunker.chunkify(
+ inputStream,
+ (chunk) -> {
+ throw new GeneralSecurityException();
+ }));
+ }
+
+ private long calculateFingerprint(byte[] bytes) {
+ long fingerprint = 0;
+ for (byte inByte : bytes) {
+ fingerprint =
+ mRabinFingerprint64.computeFingerprint64(
+ /*inChar=*/ inByte, /*outChar=*/ (byte) 0, fingerprint);
+ }
+ return mFingerprintMixer.mix(fingerprint);
+ }
+
+ private static class ZeroAvailabilityInputStream extends ByteArrayInputStream {
+ ZeroAvailabilityInputStream(byte[] wrapped) {
+ super(wrapped);
+ }
+
+ @Override
+ public synchronized int available() {
+ return 0;
+ }
+ }
+}
diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixerTest.java b/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixerTest.java
new file mode 100644
index 0000000..936b5dc
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixerTest.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking.cdc;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.util.HashSet;
+import java.util.Random;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+/** Tests for {@link FingerprintMixer}. */
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class FingerprintMixerTest {
+ private static final String KEY_ALGORITHM = "AES";
+ private static final int SEED = 42;
+ private static final int SALT_LENGTH_BYTES = 256 / 8;
+ private static final int KEY_SIZE_BITS = 256;
+
+ private Random mSeededRandom;
+ private FingerprintMixer mFingerprintMixer;
+
+ /** Set up a {@link FingerprintMixer} with deterministic key and salt generation. */
+ @Before
+ public void setUp() throws Exception {
+ // Seed so that the tests are deterministic.
+ mSeededRandom = new Random(SEED);
+ mFingerprintMixer = new FingerprintMixer(randomKey(), randomSalt());
+ }
+
+ /**
+ * Construcing a {@link FingerprintMixer} with a salt that is too small should throw an {@link
+ * IllegalArgumentException}.
+ */
+ @Test
+ public void create_withIncorrectSaltSize_throwsIllegalArgumentException() {
+ byte[] tooSmallSalt = new byte[SALT_LENGTH_BYTES - 1];
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> new FingerprintMixer(randomKey(), tooSmallSalt));
+ }
+
+ /**
+ * Constructing a {@link FingerprintMixer} with a secret key that can't be encoded should throw
+ * an {@link InvalidKeyException}.
+ */
+ @Test
+ public void create_withUnencodableSecretKey_throwsInvalidKeyException() {
+ byte[] keyBytes = new byte[KEY_SIZE_BITS / 8];
+ UnencodableSecretKeySpec keySpec =
+ new UnencodableSecretKeySpec(keyBytes, 0, keyBytes.length, KEY_ALGORITHM);
+
+ assertThrows(InvalidKeyException.class, () -> new FingerprintMixer(keySpec, randomSalt()));
+ }
+
+ /**
+ * {@link FingerprintMixer#getAddend()} should not return the same addend for two different
+ * keys.
+ */
+ @Test
+ public void getAddend_withDifferentKey_returnsDifferentResult() throws Exception {
+ int iterations = 100_000;
+ HashSet<Long> returnedAddends = new HashSet<>();
+ byte[] salt = randomSalt();
+
+ for (int i = 0; i < iterations; i++) {
+ FingerprintMixer fingerprintMixer = new FingerprintMixer(randomKey(), salt);
+ long addend = fingerprintMixer.getAddend();
+ returnedAddends.add(addend);
+ }
+
+ assertThat(returnedAddends).containsNoDuplicates();
+ }
+
+ /**
+ * {@link FingerprintMixer#getMultiplicand()} should not return the same multiplicand for two
+ * different keys.
+ */
+ @Test
+ public void getMultiplicand_withDifferentKey_returnsDifferentResult() throws Exception {
+ int iterations = 100_000;
+ HashSet<Long> returnedMultiplicands = new HashSet<>();
+ byte[] salt = randomSalt();
+
+ for (int i = 0; i < iterations; i++) {
+ FingerprintMixer fingerprintMixer = new FingerprintMixer(randomKey(), salt);
+ long multiplicand = fingerprintMixer.getMultiplicand();
+ returnedMultiplicands.add(multiplicand);
+ }
+
+ assertThat(returnedMultiplicands).containsNoDuplicates();
+ }
+
+ /** The multiplicant returned by {@link FingerprintMixer} should always be odd. */
+ @Test
+ public void getMultiplicand_isOdd() throws Exception {
+ int iterations = 100_000;
+
+ for (int i = 0; i < iterations; i++) {
+ FingerprintMixer fingerprintMixer = new FingerprintMixer(randomKey(), randomSalt());
+
+ long multiplicand = fingerprintMixer.getMultiplicand();
+
+ assertThat(isOdd(multiplicand)).isTrue();
+ }
+ }
+
+ /** {@link FingerprintMixer#mix(long)} should have a random distribution. */
+ @Test
+ public void mix_randomlyDistributesBits() throws Exception {
+ int iterations = 100_000;
+ float tolerance = 0.1f;
+ int[] totals = new int[64];
+
+ for (int i = 0; i < iterations; i++) {
+ long n = mFingerprintMixer.mix(mSeededRandom.nextLong());
+ for (int j = 0; j < 64; j++) {
+ int bit = (int) (n >> j & 1);
+ totals[j] += bit;
+ }
+ }
+
+ for (int i = 0; i < 64; i++) {
+ float mean = ((float) totals[i]) / iterations;
+ float diff = Math.abs(mean - 0.5f);
+ assertThat(diff).isLessThan(tolerance);
+ }
+ }
+
+ /**
+ * {@link FingerprintMixer#mix(long)} should always produce a number that's different from the
+ * input.
+ */
+ @Test
+ public void mix_doesNotProduceSameNumberAsInput() {
+ int iterations = 100_000;
+
+ for (int i = 0; i < iterations; i++) {
+ assertThat(mFingerprintMixer.mix(i)).isNotEqualTo(i);
+ }
+ }
+
+ private byte[] randomSalt() {
+ byte[] salt = new byte[SALT_LENGTH_BYTES];
+ mSeededRandom.nextBytes(salt);
+ return salt;
+ }
+
+ /**
+ * Not a secure way of generating keys. We want to deterministically generate the same keys for
+ * each test run, though, to ensure the test is deterministic.
+ */
+ private SecretKey randomKey() {
+ byte[] keyBytes = new byte[KEY_SIZE_BITS / 8];
+ mSeededRandom.nextBytes(keyBytes);
+ return new SecretKeySpec(keyBytes, 0, keyBytes.length, KEY_ALGORITHM);
+ }
+
+ private static boolean isOdd(long n) {
+ return Math.abs(n % 2) == 1;
+ }
+
+ /**
+ * Subclass of {@link SecretKeySpec} that does not provide an encoded version. As per its
+ * contract in {@link Key}, that means {@code getEncoded()} always returns null.
+ */
+ private class UnencodableSecretKeySpec extends SecretKeySpec {
+ UnencodableSecretKeySpec(byte[] key, int offset, int len, String algorithm) {
+ super(key, offset, len, algorithm);
+ }
+
+ @Override
+ public byte[] getEncoded() {
+ return null;
+ }
+ }
+}
diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/HkdfTest.java b/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/HkdfTest.java
new file mode 100644
index 0000000..5494374
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/HkdfTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking.cdc;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/** Tests for {@link Hkdf}. */
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class HkdfTest {
+ /** HKDF Test Case 1 IKM from RFC 5869 */
+ private static final byte[] HKDF_CASE1_IKM = {
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b
+ };
+
+ /** HKDF Test Case 1 salt from RFC 5869 */
+ private static final byte[] HKDF_CASE1_SALT = {
+ 0x00, 0x01, 0x02, 0x03, 0x04,
+ 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x0a, 0x0b, 0x0c
+ };
+
+ /** HKDF Test Case 1 info from RFC 5869 */
+ private static final byte[] HKDF_CASE1_INFO = {
+ (byte) 0xf0, (byte) 0xf1, (byte) 0xf2, (byte) 0xf3, (byte) 0xf4,
+ (byte) 0xf5, (byte) 0xf6, (byte) 0xf7, (byte) 0xf8, (byte) 0xf9
+ };
+
+ /** First 32 bytes of HKDF Test Case 1 OKM (output) from RFC 5869 */
+ private static final byte[] HKDF_CASE1_OKM = {
+ (byte) 0x3c, (byte) 0xb2, (byte) 0x5f, (byte) 0x25, (byte) 0xfa,
+ (byte) 0xac, (byte) 0xd5, (byte) 0x7a, (byte) 0x90, (byte) 0x43,
+ (byte) 0x4f, (byte) 0x64, (byte) 0xd0, (byte) 0x36, (byte) 0x2f,
+ (byte) 0x2a, (byte) 0x2d, (byte) 0x2d, (byte) 0x0a, (byte) 0x90,
+ (byte) 0xcf, (byte) 0x1a, (byte) 0x5a, (byte) 0x4c, (byte) 0x5d,
+ (byte) 0xb0, (byte) 0x2d, (byte) 0x56, (byte) 0xec, (byte) 0xc4,
+ (byte) 0xc5, (byte) 0xbf
+ };
+
+ /** Test the example from RFC 5869. */
+ @Test
+ public void hkdf_derivesKeyMaterial() throws Exception {
+ byte[] result = Hkdf.hkdf(HKDF_CASE1_IKM, HKDF_CASE1_SALT, HKDF_CASE1_INFO);
+
+ assertThat(result).isEqualTo(HKDF_CASE1_OKM);
+ }
+
+ /** Providing a key that is null should throw a {@link java.lang.NullPointerException}. */
+ @Test
+ public void hkdf_withNullKey_throwsNullPointerException() throws Exception {
+ assertThrows(
+ NullPointerException.class,
+ () -> Hkdf.hkdf(null, HKDF_CASE1_SALT, HKDF_CASE1_INFO));
+ }
+
+ /** Providing a salt that is null should throw a {@link java.lang.NullPointerException}. */
+ @Test
+ public void hkdf_withNullSalt_throwsNullPointerException() throws Exception {
+ assertThrows(
+ NullPointerException.class, () -> Hkdf.hkdf(HKDF_CASE1_IKM, null, HKDF_CASE1_INFO));
+ }
+
+ /** Providing data that is null should throw a {@link java.lang.NullPointerException}. */
+ @Test
+ public void hkdf_withNullData_throwsNullPointerException() throws Exception {
+ assertThrows(
+ NullPointerException.class, () -> Hkdf.hkdf(HKDF_CASE1_IKM, HKDF_CASE1_SALT, null));
+ }
+}
diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpointTest.java b/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpointTest.java
new file mode 100644
index 0000000..277dc37
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpointTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking.cdc;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.Random;
+
+/** Tests for {@link IsChunkBreakpoint}. */
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class IsChunkBreakpointTest {
+ private static final int RANDOM_SEED = 42;
+ private static final double TOLERANCE = 0.01;
+ private static final int NUMBER_OF_TESTS = 10000;
+ private static final int BITS_PER_LONG = 64;
+
+ private Random mRandom;
+
+ /** Make sure that tests are deterministic. */
+ @Before
+ public void setUp() {
+ mRandom = new Random(RANDOM_SEED);
+ }
+
+ /**
+ * Providing a negative average number of trials should throw an {@link
+ * IllegalArgumentException}.
+ */
+ @Test
+ public void create_withNegativeAverageNumberOfTrials_throwsIllegalArgumentException() {
+ assertThrows(IllegalArgumentException.class, () -> new IsChunkBreakpoint(-1));
+ }
+
+ // Note: the following three tests are compute-intensive, so be cautious adding more.
+
+ /**
+ * If the provided average number of trials is zero, a breakpoint should be expected after one
+ * trial on average.
+ */
+ @Test
+ public void
+ isBreakpoint_withZeroAverageNumberOfTrials_isTrueOnAverageAfterOneTrial() {
+ assertExpectedTrials(new IsChunkBreakpoint(0), /*expectedTrials=*/ 1);
+ }
+
+ /**
+ * If the provided average number of trials is 512, a breakpoint should be expected after 512
+ * trials on average.
+ */
+ @Test
+ public void
+ isBreakpoint_with512AverageNumberOfTrials_isTrueOnAverageAfter512Trials() {
+ assertExpectedTrials(new IsChunkBreakpoint(512), /*expectedTrials=*/ 512);
+ }
+
+ /**
+ * If the provided average number of trials is 1024, a breakpoint should be expected after 1024
+ * trials on average.
+ */
+ @Test
+ public void
+ isBreakpoint_with1024AverageNumberOfTrials_isTrueOnAverageAfter1024Trials() {
+ assertExpectedTrials(new IsChunkBreakpoint(1024), /*expectedTrials=*/ 1024);
+ }
+
+ /** The number of leading zeros should be the logarithm of the average number of trials. */
+ @Test
+ public void getLeadingZeros_squaredIsAverageNumberOfTrials() {
+ for (int i = 0; i < BITS_PER_LONG; i++) {
+ long averageNumberOfTrials = (long) Math.pow(2, i);
+
+ int leadingZeros = new IsChunkBreakpoint(averageNumberOfTrials).getLeadingZeros();
+
+ assertThat(leadingZeros).isEqualTo(i);
+ }
+ }
+
+ private void assertExpectedTrials(IsChunkBreakpoint isChunkBreakpoint, long expectedTrials) {
+ long sum = 0;
+ for (int i = 0; i < NUMBER_OF_TESTS; i++) {
+ sum += numberOfTrialsTillBreakpoint(isChunkBreakpoint);
+ }
+ long averageTrials = sum / NUMBER_OF_TESTS;
+ assertThat((double) Math.abs(averageTrials - expectedTrials))
+ .isLessThan(TOLERANCE * expectedTrials);
+ }
+
+ private int numberOfTrialsTillBreakpoint(IsChunkBreakpoint isChunkBreakpoint) {
+ int trials = 0;
+
+ while (true) {
+ trials++;
+ if (isChunkBreakpoint.isBreakpoint(mRandom.nextLong())) {
+ return trials;
+ }
+ }
+ }
+}
diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64Test.java b/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64Test.java
new file mode 100644
index 0000000..729580c
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64Test.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking.cdc;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/** Tests for {@link RabinFingerprint64}. */
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class RabinFingerprint64Test {
+ private static final int WINDOW_SIZE = 31;
+ private static final ImmutableList<String> TEST_STRINGS =
+ ImmutableList.of(
+ "ervHTtChYXO6eXivYqThlyyzqkbRaOR",
+ "IxaVunH9ZC3qneWfhj1GkBH4ys9CYqz",
+ "wZRVjlE1p976icCFPX9pibk4PEBvjSH",
+ "pHIVaT8x8If9D6s9croksgNmJpmGYWI");
+
+ private final RabinFingerprint64 mRabinFingerprint64 = new RabinFingerprint64();
+
+ /**
+ * No matter where in the input buffer a string occurs, {@link
+ * RabinFingerprint64#computeFingerprint64(byte, byte, long)} should return the same
+ * fingerprint.
+ */
+ @Test
+ public void computeFingerprint64_forSameWindow_returnsSameFingerprint() {
+ long fingerprint1 =
+ computeFingerprintAtPosition(getBytes(TEST_STRINGS.get(0)), WINDOW_SIZE - 1);
+ long fingerprint2 =
+ computeFingerprintAtPosition(
+ getBytes(TEST_STRINGS.get(1), TEST_STRINGS.get(0)), WINDOW_SIZE * 2 - 1);
+ long fingerprint3 =
+ computeFingerprintAtPosition(
+ getBytes(TEST_STRINGS.get(2), TEST_STRINGS.get(3), TEST_STRINGS.get(0)),
+ WINDOW_SIZE * 3 - 1);
+ String stub = "abc";
+ long fingerprint4 =
+ computeFingerprintAtPosition(
+ getBytes(stub, TEST_STRINGS.get(0)), WINDOW_SIZE + stub.length() - 1);
+
+ // Assert that all fingerprints are exactly the same
+ assertThat(ImmutableSet.of(fingerprint1, fingerprint2, fingerprint3, fingerprint4))
+ .hasSize(1);
+ }
+
+ /** The computed fingerprint should be different for different inputs. */
+ @Test
+ public void computeFingerprint64_withDifferentInput_returnsDifferentFingerprint() {
+ long fingerprint1 = computeFingerprintOf(TEST_STRINGS.get(0));
+ long fingerprint2 = computeFingerprintOf(TEST_STRINGS.get(1));
+ long fingerprint3 = computeFingerprintOf(TEST_STRINGS.get(2));
+ long fingerprint4 = computeFingerprintOf(TEST_STRINGS.get(3));
+
+ assertThat(ImmutableList.of(fingerprint1, fingerprint2, fingerprint3, fingerprint4))
+ .containsNoDuplicates();
+ }
+
+ /**
+ * An input with the same characters in a different order should return a different fingerprint.
+ */
+ @Test
+ public void computeFingerprint64_withSameInputInDifferentOrder_returnsDifferentFingerprint() {
+ long fingerprint1 = computeFingerprintOf("abcdefghijklmnopqrstuvwxyz12345");
+ long fingerprint2 = computeFingerprintOf("54321zyxwvutsrqponmlkjihgfedcba");
+ long fingerprint3 = computeFingerprintOf("4bcdefghijklmnopqrstuvwxyz123a5");
+ long fingerprint4 = computeFingerprintOf("bacdefghijklmnopqrstuvwxyz12345");
+
+ assertThat(ImmutableList.of(fingerprint1, fingerprint2, fingerprint3, fingerprint4))
+ .containsNoDuplicates();
+ }
+
+ /** UTF-8 bytes of all the given strings in order. */
+ private byte[] getBytes(String... strings) {
+ StringBuilder sb = new StringBuilder();
+ for (String s : strings) {
+ sb.append(s);
+ }
+ return sb.toString().getBytes(UTF_8);
+ }
+
+ /**
+ * The Rabin fingerprint of a window of bytes ending at {@code position} in the {@code bytes}
+ * array.
+ */
+ private long computeFingerprintAtPosition(byte[] bytes, int position) {
+ assertThat(position).isAtMost(bytes.length - 1);
+ long fingerprint = 0;
+ for (int i = 0; i <= position; i++) {
+ byte outChar;
+ if (i >= WINDOW_SIZE) {
+ outChar = bytes[i - WINDOW_SIZE];
+ } else {
+ outChar = (byte) 0;
+ }
+ fingerprint =
+ mRabinFingerprint64.computeFingerprint64(
+ /*inChar=*/ bytes[i], outChar, fingerprint);
+ }
+ return fingerprint;
+ }
+
+ private long computeFingerprintOf(String s) {
+ assertThat(s.length()).isEqualTo(WINDOW_SIZE);
+ return computeFingerprintAtPosition(s.getBytes(UTF_8), WINDOW_SIZE - 1);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/StorageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/StorageManagerServiceTest.java
index ee42ce8..e6b328a 100644
--- a/services/tests/servicestests/src/com/android/server/StorageManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/StorageManagerServiceTest.java
@@ -125,7 +125,7 @@
"/storage/emulated/0/foo.jpg",
PID_GREY, UID_GREY);
assertTranslation(
- "/storage/emulated/0/Android/sandbox/shared:colors/foo.jpg",
+ "/storage/emulated/0/Android/sandbox/shared-colors/foo.jpg",
"/storage/emulated/0/foo.jpg",
PID_RED, UID_COLORS);
}
@@ -137,7 +137,7 @@
"/storage/0000-0000/foo/bar.jpg",
PID_GREY, UID_GREY);
assertTranslation(
- "/storage/0000-0000/Android/sandbox/shared:colors/foo/bar.jpg",
+ "/storage/0000-0000/Android/sandbox/shared-colors/foo/bar.jpg",
"/storage/0000-0000/foo/bar.jpg",
PID_RED, UID_COLORS);
}
@@ -152,7 +152,7 @@
// Accessing other package paths goes into sandbox
assertTranslation(
- "/storage/emulated/0/Android/sandbox/shared:colors/"
+ "/storage/emulated/0/Android/sandbox/shared-colors/"
+ "Android/data/com.grey/foo.jpg",
"/storage/emulated/0/Android/data/com.grey/foo.jpg",
PID_RED, UID_COLORS);
@@ -201,7 +201,7 @@
// Sandboxes can't see paths in other sandboxes
try {
mService.translateSystemToApp(
- "/storage/emulated/0/Android/sandbox/shared:colors/foo.jpg",
+ "/storage/emulated/0/Android/sandbox/shared-colors/foo.jpg",
PID_GREY, UID_GREY);
fail();
} catch (SecurityException expected) {
diff --git a/services/tests/servicestests/src/com/android/server/am/GlobalSettingsToPropertiesMapperTest.java b/services/tests/servicestests/src/com/android/server/am/GlobalSettingsToPropertiesMapperTest.java
deleted file mode 100644
index c162c3b..0000000
--- a/services/tests/servicestests/src/com/android/server/am/GlobalSettingsToPropertiesMapperTest.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.am;
-
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
-import android.content.ContentResolver;
-import android.provider.Settings;
-import android.test.mock.MockContentResolver;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.test.FakeSettingsProvider;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Tests for {@link GlobalSettingsToPropertiesMapper}
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:GlobalSettingsToPropertiesMapperTest
- */
-@SmallTest
-public class GlobalSettingsToPropertiesMapperTest {
- private static final String[][] TEST_MAPPING = new String[][] {
- {Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "TestProperty"}
- };
-
- private TestMapper mTestMapper;
- private MockContentResolver mMockContentResolver;
-
- @Before
- public void setup() {
- // Use FakeSettingsProvider to not affect global state
- mMockContentResolver = new MockContentResolver(getInstrumentation().getTargetContext());
- mMockContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
- mTestMapper = new TestMapper(mMockContentResolver);
- }
-
- @Test
- public void testUpdatePropertiesFromGlobalSettings() {
- Settings.Global.putString(mMockContentResolver,
- Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "testValue");
-
- mTestMapper.updatePropertiesFromGlobalSettings();
- String propValue = mTestMapper.systemPropertiesGet("TestProperty");
- assertEquals("testValue", propValue);
-
- Settings.Global.putString(mMockContentResolver,
- Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "testValue2");
- mTestMapper.updatePropertyFromSetting(Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS,
- "TestProperty");
- propValue = mTestMapper.systemPropertiesGet("TestProperty");
- assertEquals("testValue2", propValue);
-
- Settings.Global.putString(mMockContentResolver,
- Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, null);
- mTestMapper.updatePropertyFromSetting(Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS,
- "TestProperty");
- propValue = mTestMapper.systemPropertiesGet("TestProperty");
- assertEquals("", propValue);
- }
-
- @Test
- public void testUpdatePropertiesFromGlobalSettings_PropertyAndSettingNotPresent() {
- // Test that empty property will not not be set if setting is not set
- mTestMapper.updatePropertiesFromGlobalSettings();
- String propValue = mTestMapper.systemPropertiesGet("TestProperty");
- assertNull("Property should not be set if setting is null", propValue);
- }
-
- private static class TestMapper extends GlobalSettingsToPropertiesMapper {
- private final Map<String, String> mProps = new HashMap<>();
-
- TestMapper(ContentResolver contentResolver) {
- super(contentResolver, TEST_MAPPING);
- }
-
- @Override
- protected String systemPropertiesGet(String key) {
- Preconditions.checkNotNull(key);
- return mProps.get(key);
- }
-
- @Override
- protected void systemPropertiesSet(String key, String value) {
- Preconditions.checkNotNull(value);
- Preconditions.checkNotNull(key);
- mProps.put(key, value);
- }
- }
-}
-
diff --git a/services/tests/servicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java b/services/tests/servicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
new file mode 100644
index 0000000..d965f8a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+import android.text.TextUtils;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.test.FakeSettingsProvider;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Tests for {@link SettingsToPropertiesMapper}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SettingsToPropertiesMapperTest {
+ private static final String NAME_VALID_CHARACTERS_REGEX = "^[\\w\\.\\-@:]*$";
+ private static final String[] TEST_MAPPING = new String[] {
+ Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS
+ };
+
+ private TestMapper mTestMapper;
+ private MockContentResolver mMockContentResolver;
+
+ @Before
+ public void setupForEach() {
+ // Use FakeSettingsProvider to not affect global state
+ mMockContentResolver = new MockContentResolver(InstrumentationRegistry.getContext());
+ mMockContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ mTestMapper = new TestMapper(mMockContentResolver);
+ }
+
+ @Test
+ public void validateRegisteredGlobalSettings() {
+ HashSet<String> hashSet = new HashSet<>();
+ for (String globalSetting : SettingsToPropertiesMapper.sGlobalSettings) {
+ if (hashSet.contains(globalSetting)) {
+ Assert.fail("globalSetting "
+ + globalSetting
+ + " is registered more than once in "
+ + "SettingsToPropertiesMapper.sGlobalSettings.");
+ }
+ hashSet.add(globalSetting);
+ if (TextUtils.isEmpty(globalSetting)) {
+ Assert.fail("empty globalSetting registered.");
+ }
+ if (!globalSetting.matches(NAME_VALID_CHARACTERS_REGEX)) {
+ Assert.fail(globalSetting + " contains invalid characters. "
+ + "Only alphanumeric characters, '.', '-', '@', ':' and '_' are valid.");
+ }
+ }
+ }
+
+ @Test
+ public void testUpdatePropertiesFromSettings() {
+ Settings.Global.putString(mMockContentResolver,
+ Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "testValue");
+
+ String systemPropertyName = "persist.device_config.global_settings."
+ + "sqlite_compatibility_wal_flags";
+
+ mTestMapper.updatePropertiesFromSettings();
+ String propValue = mTestMapper.systemPropertiesGet(systemPropertyName);
+ Assert.assertEquals("testValue", propValue);
+
+ Settings.Global.putString(mMockContentResolver,
+ Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "testValue2");
+ mTestMapper.updatePropertyFromSetting(
+ Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS,
+ systemPropertyName,
+ true);
+ propValue = mTestMapper.systemPropertiesGet(systemPropertyName);
+ Assert.assertEquals("testValue2", propValue);
+
+ Settings.Global.putString(mMockContentResolver,
+ Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, null);
+ mTestMapper.updatePropertyFromSetting(
+ Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS,
+ systemPropertyName,
+ true);
+ propValue = mTestMapper.systemPropertiesGet(systemPropertyName);
+ Assert.assertEquals("", propValue);
+ }
+
+ @Test
+ public void testMakePropertyName() {
+ try {
+ Assert.assertEquals("persist.device_config.test_category.test_flag",
+ SettingsToPropertiesMapper.makePropertyName("test_category", "test_flag"));
+ } catch (Exception e) {
+ Assert.fail("Unexpected exception: " + e.getMessage());
+ }
+
+ try {
+ Assert.assertEquals(null,
+ SettingsToPropertiesMapper.makePropertyName("test_category!!!", "test_flag"));
+ } catch (Exception e) {
+ Assert.fail("Unexpected exception: " + e.getMessage());
+ }
+
+ try {
+ Assert.assertEquals(null,
+ SettingsToPropertiesMapper.makePropertyName("test_category", ".test_flag"));
+ } catch (Exception e) {
+ Assert.fail("Unexpected exception: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testUpdatePropertiesFromSettings_PropertyAndSettingNotPresent() {
+ // Test that empty property will not not be set if setting is not set
+ mTestMapper.updatePropertiesFromSettings();
+ String propValue = mTestMapper.systemPropertiesGet("TestProperty");
+ Assert.assertNull("Property should not be set if setting is null", propValue);
+ }
+
+ @Test
+ public void testIsNativeFlagsResetPerformed() {
+ mTestMapper.systemPropertiesSet("device_config.reset_performed", "true");
+ Assert.assertTrue(mTestMapper.isNativeFlagsResetPerformed());
+
+ mTestMapper.systemPropertiesSet("device_config.reset_performed", "false");
+ Assert.assertFalse(mTestMapper.isNativeFlagsResetPerformed());
+
+ mTestMapper.systemPropertiesSet("device_config.reset_performed", "");
+ Assert.assertFalse(mTestMapper.isNativeFlagsResetPerformed());
+ }
+
+ @Test
+ public void testGetResetNativeCategories() {
+ mTestMapper.systemPropertiesSet("device_config.reset_performed", "");
+ Assert.assertEquals(mTestMapper.getResetNativeCategories().length, 0);
+
+ mTestMapper.systemPropertiesSet("device_config.reset_performed", "true");
+ mTestMapper.setFileContent("");
+ Assert.assertEquals(mTestMapper.getResetNativeCategories().length, 0);
+
+ mTestMapper.systemPropertiesSet("device_config.reset_performed", "true");
+ mTestMapper.setFileContent("persist.device_config.category1.flag;"
+ + "persist.device_config.category2.flag;persist.device_config.category3.flag;"
+ + "persist.device_config.category3.flag2");
+ List<String> categories = Arrays.asList(mTestMapper.getResetNativeCategories());
+ Assert.assertEquals(3, categories.size());
+ Assert.assertTrue(categories.contains("category1"));
+ Assert.assertTrue(categories.contains("category2"));
+ Assert.assertTrue(categories.contains("category3"));
+ }
+
+ private static class TestMapper extends SettingsToPropertiesMapper {
+ private final Map<String, String> mProps = new HashMap<>();
+
+ private String mFileContent = "";
+
+ TestMapper(ContentResolver contentResolver) {
+ super(contentResolver, TEST_MAPPING, new String[] {});
+ }
+
+ @Override
+ protected String systemPropertiesGet(String key) {
+ Preconditions.checkNotNull(key);
+ return mProps.get(key);
+ }
+
+ @Override
+ protected void systemPropertiesSet(String key, String value) {
+ Preconditions.checkNotNull(value);
+ Preconditions.checkNotNull(key);
+ mProps.put(key, value);
+ }
+
+ protected void setFileContent(String fileContent) {
+ mFileContent = fileContent;
+ }
+
+ @Override
+ protected String getResetFlagsFileContent() {
+ return mFileContent;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index 910d433..9424461 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
@@ -99,17 +100,18 @@
}
}
- private static ImeSubtypeListItem createDummyItem(String imeName,
- String subtypeName, String subtypeLocale, int subtypeIndex, String systemLocale) {
+ private static ImeSubtypeListItem createDummyItem(ComponentName imeComponentName,
+ String imeName, String subtypeName, String subtypeLocale, int subtypeIndex,
+ String systemLocale) {
final ResolveInfo ri = new ResolveInfo();
final ServiceInfo si = new ServiceInfo();
final ApplicationInfo ai = new ApplicationInfo();
- ai.packageName = DUMMY_PACKAGE_NAME;
+ ai.packageName = imeComponentName.getPackageName();
ai.enabled = true;
si.applicationInfo = ai;
si.enabled = true;
- si.packageName = DUMMY_PACKAGE_NAME;
- si.name = imeName;
+ si.packageName = imeComponentName.getPackageName();
+ si.name = imeComponentName.getClassName();
si.exported = true;
si.nonLocalizedLabel = DUMMY_IME_LABEL;
ri.serviceInfo = si;
@@ -367,52 +369,56 @@
@Test
public void testImeSubtypeListComparator() throws Exception {
+ final ComponentName imeX1 = new ComponentName("com.example.imeX", "Ime1");
+ final ComponentName imeX2 = new ComponentName("com.example.imeX", "Ime2");
+ final ComponentName imeY1 = new ComponentName("com.example.imeY", "Ime1");
+ final ComponentName imeZ1 = new ComponentName("com.example.imeZ", "Ime1");
{
final List<ImeSubtypeListItem> items = Arrays.asList(
// Subtypes of IME "X".
// Subtypes that has the same locale of the system's.
- createDummyItem("X", "E", "en_US", 0, "en_US"),
- createDummyItem("X", "Z", "en_US", 3, "en_US"),
- createDummyItem("X", "", "en_US", 6, "en_US"),
+ createDummyItem(imeX1, "X", "E", "en_US", 0, "en_US"),
+ createDummyItem(imeX1, "X", "Z", "en_US", 3, "en_US"),
+ createDummyItem(imeX1, "X", "", "en_US", 6, "en_US"),
// Subtypes that has the same language of the system's.
- createDummyItem("X", "E", "en", 1, "en_US"),
- createDummyItem("X", "Z", "en", 4, "en_US"),
- createDummyItem("X", "", "en", 7, "en_US"),
+ createDummyItem(imeX1, "X", "E", "en", 1, "en_US"),
+ createDummyItem(imeX1, "X", "Z", "en", 4, "en_US"),
+ createDummyItem(imeX1, "X", "", "en", 7, "en_US"),
// Subtypes that has different language than the system's.
- createDummyItem("X", "A", "hi_IN", 27, "en_US"),
- createDummyItem("X", "E", "ja", 2, "en_US"),
- createDummyItem("X", "Z", "ja", 5, "en_US"),
- createDummyItem("X", "", "ja", 8, "en_US"),
+ createDummyItem(imeX1, "X", "A", "hi_IN", 27, "en_US"),
+ createDummyItem(imeX1, "X", "E", "ja", 2, "en_US"),
+ createDummyItem(imeX1, "X", "Z", "ja", 5, "en_US"),
+ createDummyItem(imeX1, "X", "", "ja", 8, "en_US"),
// Subtypes of IME "Y".
// Subtypes that has the same locale of the system's.
- createDummyItem("Y", "E", "en_US", 9, "en_US"),
- createDummyItem("Y", "Z", "en_US", 12, "en_US"),
- createDummyItem("Y", "", "en_US", 15, "en_US"),
+ createDummyItem(imeY1, "Y", "E", "en_US", 9, "en_US"),
+ createDummyItem(imeY1, "Y", "Z", "en_US", 12, "en_US"),
+ createDummyItem(imeY1, "Y", "", "en_US", 15, "en_US"),
// Subtypes that has the same language of the system's.
- createDummyItem("Y", "E", "en", 10, "en_US"),
- createDummyItem("Y", "Z", "en", 13, "en_US"),
- createDummyItem("Y", "", "en", 16, "en_US"),
+ createDummyItem(imeY1, "Y", "E", "en", 10, "en_US"),
+ createDummyItem(imeY1, "Y", "Z", "en", 13, "en_US"),
+ createDummyItem(imeY1, "Y", "", "en", 16, "en_US"),
// Subtypes that has different language than the system's.
- createDummyItem("Y", "A", "hi_IN", 28, "en_US"),
- createDummyItem("Y", "E", "ja", 11, "en_US"),
- createDummyItem("Y", "Z", "ja", 14, "en_US"),
- createDummyItem("Y", "", "ja", 17, "en_US"),
+ createDummyItem(imeY1, "Y", "A", "hi_IN", 28, "en_US"),
+ createDummyItem(imeY1, "Y", "E", "ja", 11, "en_US"),
+ createDummyItem(imeY1, "Y", "Z", "ja", 14, "en_US"),
+ createDummyItem(imeY1, "Y", "", "ja", 17, "en_US"),
- // Subtypes of IME "".
+ // Subtypes of IME Z.
// Subtypes that has the same locale of the system's.
- createDummyItem("", "E", "en_US", 18, "en_US"),
- createDummyItem("", "Z", "en_US", 21, "en_US"),
- createDummyItem("", "", "en_US", 24, "en_US"),
+ createDummyItem(imeZ1, "", "E", "en_US", 18, "en_US"),
+ createDummyItem(imeZ1, "", "Z", "en_US", 21, "en_US"),
+ createDummyItem(imeZ1, "", "", "en_US", 24, "en_US"),
// Subtypes that has the same language of the system's.
- createDummyItem("", "E", "en", 19, "en_US"),
- createDummyItem("", "Z", "en", 22, "en_US"),
- createDummyItem("", "", "en", 25, "en_US"),
+ createDummyItem(imeZ1, "", "E", "en", 19, "en_US"),
+ createDummyItem(imeZ1, "", "Z", "en", 22, "en_US"),
+ createDummyItem(imeZ1, "", "", "en", 25, "en_US"),
// Subtypes that has different language than the system's.
- createDummyItem("", "A", "hi_IN", 29, "en_US"),
- createDummyItem("", "E", "ja", 20, "en_US"),
- createDummyItem("", "Z", "ja", 23, "en_US"),
- createDummyItem("", "", "ja", 26, "en_US"));
+ createDummyItem(imeZ1, "", "A", "hi_IN", 29, "en_US"),
+ createDummyItem(imeZ1, "", "E", "ja", 20, "en_US"),
+ createDummyItem(imeZ1, "", "Z", "ja", 23, "en_US"),
+ createDummyItem(imeZ1, "", "", "ja", 26, "en_US"));
// Ensure {@link java.lang.Comparable#compareTo} contracts are satisfied.
for (int i = 0; i < items.size(); ++i) {
@@ -432,14 +438,25 @@
{
// Following two items have the same priority.
final ImeSubtypeListItem nonSystemLocale1 =
- createDummyItem("X", "A", "ja_JP", 0, "en_US");
+ createDummyItem(imeX1, "X", "A", "ja_JP", 0, "en_US");
final ImeSubtypeListItem nonSystemLocale2 =
- createDummyItem("X", "A", "hi_IN", 1, "en_US");
+ createDummyItem(imeX1, "X", "A", "hi_IN", 1, "en_US");
assertTrue(nonSystemLocale1.compareTo(nonSystemLocale2) == 0);
assertTrue(nonSystemLocale2.compareTo(nonSystemLocale1) == 0);
// But those aren't equal to each other.
assertFalse(nonSystemLocale1.equals(nonSystemLocale2));
assertFalse(nonSystemLocale2.equals(nonSystemLocale1));
}
+
+ {
+ // Currently ComponentName is not used for sorting.
+ final ImeSubtypeListItem ime1 = createDummyItem(imeX1, "X", "A", "ja_JP", 0, "en_US");
+ final ImeSubtypeListItem ime2 = createDummyItem(imeX2, "X", "A", "ja_JP", 0, "en_US");
+ assertTrue(ime1.compareTo(ime2) == 0);
+ assertTrue(ime2.compareTo(ime1) == 0);
+ // But those aren't equal to each other.
+ assertFalse(ime1.equals(ime2));
+ assertFalse(ime2.equals(ime1));
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
index c252609..1b106dd 100644
--- a/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
@@ -40,6 +40,7 @@
import javax.annotation.Nullable;
import libcore.io.IoUtils;
+import libcore.timezone.TzDataSetVersion;
import static com.android.server.timezone.RulesManagerService.REQUIRED_QUERY_PERMISSION;
import static com.android.server.timezone.RulesManagerService.REQUIRED_UPDATER_PERMISSION;
@@ -128,15 +129,15 @@
configureDeviceSystemRulesVersion("2016a");
DistroVersion stagedDistroVersion = new DistroVersion(
- DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
- DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+ TzDataSetVersion.currentFormatMajorVersion(),
+ TzDataSetVersion.currentFormatMinorVersion() - 1,
"2016c",
3);
configureStagedInstall(stagedDistroVersion);
DistroVersion installedDistroVersion = new DistroVersion(
- DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
- DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+ TzDataSetVersion.currentFormatMajorVersion(),
+ TzDataSetVersion.currentFormatMinorVersion() - 1,
"2016b",
4);
configureInstalledDistroVersion(installedDistroVersion);
@@ -162,8 +163,8 @@
configureNoStagedOperation();
DistroVersion installedDistroVersion = new DistroVersion(
- DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
- DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+ TzDataSetVersion.currentFormatMajorVersion(),
+ TzDataSetVersion.currentFormatMinorVersion() - 1,
"2016b",
4);
configureInstalledDistroVersion(installedDistroVersion);
@@ -187,8 +188,8 @@
configureStagedUninstall();
DistroVersion installedDistroVersion = new DistroVersion(
- DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
- DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+ TzDataSetVersion.currentFormatMajorVersion(),
+ TzDataSetVersion.currentFormatMinorVersion() - 1,
"2016b",
4);
configureInstalledDistroVersion(installedDistroVersion);
@@ -231,8 +232,8 @@
configureDeviceCannotReadStagedDistroOperation();
DistroVersion installedDistroVersion = new DistroVersion(
- DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
- DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+ TzDataSetVersion.currentFormatMajorVersion(),
+ TzDataSetVersion.currentFormatMinorVersion() - 1,
"2016b",
4);
configureInstalledDistroVersion(installedDistroVersion);
@@ -275,8 +276,8 @@
configureDeviceSystemRulesVersion(systemRulesVersion);
DistroVersion installedDistroVersion = new DistroVersion(
- DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
- DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+ TzDataSetVersion.currentFormatMajorVersion(),
+ TzDataSetVersion.currentFormatMinorVersion() - 1,
installedRulesVersion,
revision);
configureInstalledDistroVersion(installedDistroVersion);
diff --git a/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java b/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java
index b6a7cfb..991981f 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java
@@ -37,7 +37,7 @@
/**
* Build/Install/Run:
- * atest FrameworksServicesTests:DimmerTests;
+ * atest FrameworksServicesTests:DimmerTests
*/
@Presubmit
public class DimmerTests extends WindowTestsBase {
@@ -211,7 +211,7 @@
mDimmer.updateDims(mTransaction, new Rect());
verify(mSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class), any(
SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean());
- verify(dimLayer).destroy();
+ verify(mHost.getPendingTransaction()).destroy(dimLayer);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index f4da4b3..228ece5 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -40,6 +40,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doNothing;
@@ -108,7 +109,7 @@
final WindowState imeAppTarget =
createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
- mWm.mInputMethodTarget = imeAppTarget;
+ mDisplayContent.mInputMethodTarget = imeAppTarget;
assertForAllWindowsOrder(Arrays.asList(
mWallpaperWindow,
@@ -124,8 +125,8 @@
}
@Test
- public void testForAllWindows_WithChildWindowImeTarget() {
- mWm.mInputMethodTarget = mChildAppWindowAbove;
+ public void testForAllWindows_WithChildWindowImeTarget() throws Exception {
+ mDisplayContent.mInputMethodTarget = mChildAppWindowAbove;
assertForAllWindowsOrder(Arrays.asList(
mWallpaperWindow,
@@ -140,8 +141,8 @@
}
@Test
- public void testForAllWindows_WithStatusBarImeTarget() {
- mWm.mInputMethodTarget = mStatusBarWindow;
+ public void testForAllWindows_WithStatusBarImeTarget() throws Exception {
+ mDisplayContent.mInputMethodTarget = mStatusBarWindow;
assertForAllWindowsOrder(Arrays.asList(
mWallpaperWindow,
@@ -548,6 +549,18 @@
}
@Test
+ public void testClearLastFocusWhenReparentingFocusedWindow() {
+ final DisplayContent defaultDisplay = mWm.getDefaultDisplayContentLocked();
+ final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION,
+ defaultDisplay, "window");
+ defaultDisplay.mLastFocus = window;
+ mDisplayContent.mCurrentFocus = window;
+ mDisplayContent.reParentWindowToken(window.mToken);
+
+ assertNull(defaultDisplay.mLastFocus);
+ }
+
+ @Test
public void testGetPreferredOptionsPanelGravityFromDifferentDisplays() {
final DisplayContent portraitDisplay = createNewDisplay();
portraitDisplay.mInitialDisplayHeight = 2000;
@@ -568,6 +581,32 @@
assertFalse(isOptionsPanelAtRight(landscapeDisplay.getDisplayId()));
}
+ @Test
+ public void testInputMethodTargetUpdateWhenSwitchingOnDisplays() {
+ final DisplayContent newDisplay = createNewDisplay();
+
+ final WindowState appWin = createWindow(null, TYPE_APPLICATION, mDisplayContent, "appWin");
+ final WindowState appWin1 = createWindow(null, TYPE_APPLICATION, newDisplay, "appWin1");
+ appWin.setHasSurface(true);
+ appWin1.setHasSurface(true);
+
+ // Set current input method window on default display, make sure the input method target
+ // is appWin & null on the other display.
+ mDisplayContent.setInputMethodWindowLocked(mImeWindow);
+ newDisplay.setInputMethodWindowLocked(null);
+ assertTrue("appWin should be IME target window",
+ appWin.equals(mDisplayContent.mInputMethodTarget));
+ assertNull("newDisplay Ime target: ", newDisplay.mInputMethodTarget);
+
+ // Switch input method window on new display & make sure the input method target also
+ // switched as expected.
+ newDisplay.setInputMethodWindowLocked(mImeWindow);
+ mDisplayContent.setInputMethodWindowLocked(null);
+ assertTrue("appWin1 should be IME target window",
+ appWin1.equals(newDisplay.mInputMethodTarget));
+ assertNull("default display Ime target: ", mDisplayContent.mInputMethodTarget);
+ }
+
private boolean isOptionsPanelAtRight(int displayId) {
return (mWm.getPreferredOptionsPanelGravity(displayId) & Gravity.RIGHT) == Gravity.RIGHT;
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java b/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java
index e8d0a06..99deeb9 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java
@@ -53,7 +53,8 @@
}
@Override
- public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) throws RemoteException {
+ public void windowFocusChanged(boolean hasFocus, boolean inTouchMode, boolean reportToClient)
+ throws RemoteException {
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTraversalTests.java
index 2b8b934..fcde08e 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTraversalTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTraversalTests.java
@@ -52,7 +52,7 @@
WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD,
TYPE_BASE_APPLICATION, mDisplayContent, "splitScreenSecondaryWindow");
- mWm.mInputMethodTarget = splitScreenWindow;
+ mDisplayContent.mInputMethodTarget = splitScreenWindow;
Consumer<WindowState> c = mock(Consumer.class);
mDisplayContent.forAllWindows(c, false);
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 2abe64d..53858c7 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -192,7 +192,7 @@
mWm.getDefaultDisplayContentLocked().mAppTransition
.removeAppTransitionTimeoutCallbacks();
mWm.mH.removeMessages(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT);
- mWm.mInputMethodTarget = null;
+ mDisplayContent.mInputMethodTarget = null;
}
// Wait until everything is really cleaned up.
diff --git a/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java
index 3c8ae3c..3dcea75 100644
--- a/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java
@@ -209,7 +209,7 @@
@Test
public void testAssignWindowLayers_ForImeWithNoTarget() {
- mWm.mInputMethodTarget = null;
+ mDisplayContent.mInputMethodTarget = null;
mDisplayContent.assignChildLayers(mTransaction);
// The Ime has an higher base layer than app windows and lower base layer than system
@@ -227,7 +227,7 @@
@Test
public void testAssignWindowLayers_ForImeWithAppTarget() {
final WindowState imeAppTarget = createWindow("imeAppTarget");
- mWm.mInputMethodTarget = imeAppTarget;
+ mDisplayContent.mInputMethodTarget = imeAppTarget;
mDisplayContent.assignChildLayers(mTransaction);
@@ -253,7 +253,7 @@
TYPE_APPLICATION_MEDIA_OVERLAY, imeAppTarget.mToken,
"imeAppTargetChildBelowWindow");
- mWm.mInputMethodTarget = imeAppTarget;
+ mDisplayContent.mInputMethodTarget = imeAppTarget;
mDisplayContent.assignChildLayers(mTransaction);
// Ime should be above all app windows except for child windows that are z-ordered above it
@@ -275,7 +275,7 @@
final WindowState imeAppTarget = createWindow("imeAppTarget");
final WindowState appAboveImeTarget = createWindow("appAboveImeTarget");
- mWm.mInputMethodTarget = imeAppTarget;
+ mDisplayContent.mInputMethodTarget = imeAppTarget;
mDisplayContent.assignChildLayers(mTransaction);
// Ime should be above all app windows except for non-fullscreen app window above it and
@@ -298,7 +298,7 @@
mDisplayContent, "imeSystemOverlayTarget",
true /* ownerCanAddInternalSystemWindow */);
- mWm.mInputMethodTarget = imeSystemOverlayTarget;
+ mDisplayContent.mInputMethodTarget = imeSystemOverlayTarget;
mDisplayContent.assignChildLayers(mTransaction);
// The IME target base layer is higher than all window except for the nav bar window, so the
@@ -321,7 +321,7 @@
@Test
public void testAssignWindowLayers_ForStatusBarImeTarget() {
- mWm.mInputMethodTarget = mStatusBarWindow;
+ mDisplayContent.mInputMethodTarget = mStatusBarWindow;
mDisplayContent.assignChildLayers(mTransaction);
assertWindowHigher(mImeWindow, mChildAppWindowAbove);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
index f17a30d..410ab87 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
@@ -72,7 +72,7 @@
assertTrue(r.getGroupKey().contains(GroupHelper.AUTOGROUP_KEY));
assertEquals(people, r.getPeopleOverride());
assertEquals(snoozeCriteria, r.getSnoozeCriteria());
- assertEquals(smartActions, r.getSmartActions());
+ assertEquals(smartActions, r.getSystemGeneratedSmartActions());
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 9b41fdd..8690110 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -708,14 +708,14 @@
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
false /* lights */, false /* defaultLights */, groupId /* group */);
NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
- assertNull(record.getSmartActions());
+ assertNull(record.getSystemGeneratedSmartActions());
ArrayList<Notification.Action> smartActions = new ArrayList<>();
smartActions.add(new Notification.Action.Builder(
Icon.createWithResource(getContext(), R.drawable.btn_default),
"text", null).build());
- record.setSmartActions(smartActions);
- assertEquals(smartActions, record.getSmartActions());
+ record.setSystemGeneratedSmartActions(smartActions);
+ assertEquals(smartActions, record.getSystemGeneratedSmartActions());
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 3fe381b..1a218b2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -27,7 +27,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -98,7 +97,7 @@
private static final UserHandle USER = UserHandle.of(0);
private static final int UID_O = 1111;
private static final String SYSTEM_PKG = "android";
- private static final int SYSTEM_UID= 1000;
+ private static final int SYSTEM_UID = 1000;
private static final UserHandle USER2 = UserHandle.of(10);
private static final String TEST_CHANNEL_ID = "test_channel_id";
private static final String TEST_AUTHORITY = "test";
@@ -1091,6 +1090,158 @@
}
@Test
+ public void testGetChannelsBypassingDndCount_noChannelsBypassing() throws Exception {
+ assertEquals(0, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+ USER.getIdentifier()).getList().size());
+ }
+
+ @Test
+ public void testGetChannelsBypassingDnd_noChannelsForUserIdBypassing()
+ throws Exception {
+ int user = 9;
+ NotificationChannel channel = new NotificationChannel("id", "name",
+ NotificationManager.IMPORTANCE_MAX);
+ channel.setBypassDnd(true);
+ mHelper.createNotificationChannel(PKG_N_MR1, 111, channel, true, true);
+
+ assertEquals(0, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+ user).getList().size());
+ }
+
+ @Test
+ public void testGetChannelsBypassingDndCount_oneChannelBypassing_groupBlocked() {
+ int user = USER.getIdentifier();
+ NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+ NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+ NotificationManager.IMPORTANCE_MAX);
+ channel1.setBypassDnd(true);
+ channel1.setGroup(ncg.getId());
+ mHelper.createNotificationChannelGroup(PKG_N_MR1, user, ncg, /* fromTargetApp */ true);
+ mHelper.createNotificationChannel(PKG_N_MR1, user, channel1, true, /*has DND access*/ true);
+
+ assertEquals(1, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+ user).getList().size());
+
+ // disable group
+ ncg.setBlocked(true);
+ mHelper.createNotificationChannelGroup(PKG_N_MR1, user, ncg, /* fromTargetApp */ false);
+ assertEquals(0, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+ user).getList().size());
+ }
+
+ @Test
+ public void testGetChannelsBypassingDndCount_multipleChannelsBypassing() {
+ int user = USER.getIdentifier();
+ NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+ NotificationManager.IMPORTANCE_MAX);
+ NotificationChannel channel2 = new NotificationChannel("id2", "name2",
+ NotificationManager.IMPORTANCE_MAX);
+ NotificationChannel channel3 = new NotificationChannel("id3", "name3",
+ NotificationManager.IMPORTANCE_MAX);
+ channel1.setBypassDnd(true);
+ channel2.setBypassDnd(true);
+ channel3.setBypassDnd(true);
+ // has DND access, so can set bypassDnd attribute
+ mHelper.createNotificationChannel(PKG_N_MR1, user, channel1, true, /*has DND access*/ true);
+ mHelper.createNotificationChannel(PKG_N_MR1, user, channel2, true, true);
+ mHelper.createNotificationChannel(PKG_N_MR1, user, channel3, true, true);
+ assertEquals(3, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+ user).getList().size());
+
+ // block notifications from this app
+ mHelper.setEnabled(PKG_N_MR1, user, false);
+ assertEquals(0, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+ user).getList().size());
+
+ // re-enable notifications from this app
+ mHelper.setEnabled(PKG_N_MR1, user, true);
+ assertEquals(3, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+ user).getList().size());
+
+ // setBypassDnd false for some channels
+ channel1.setBypassDnd(false);
+ channel2.setBypassDnd(false);
+ assertEquals(1, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+ user).getList().size());
+
+ // setBypassDnd false for rest of the channels
+ channel3.setBypassDnd(false);
+ assertEquals(0, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+ user).getList().size());
+ }
+
+ @Test
+ public void testGetAppsBypassingDndCount_noAppsBypassing() throws Exception {
+ assertEquals(0, mHelper.getAppsBypassingDndCount(USER.getIdentifier()));
+ }
+
+ @Test
+ public void testGetAppsBypassingDndCount_noAppsForUserIdBypassing() throws Exception {
+ int user = 9;
+ NotificationChannel channel = new NotificationChannel("id", "name",
+ NotificationManager.IMPORTANCE_MAX);
+ channel.setBypassDnd(true);
+ mHelper.createNotificationChannel(PKG_N_MR1, 111, channel, true, true);
+
+ assertEquals(0, mHelper.getAppsBypassingDndCount(user));
+ }
+
+ @Test
+ public void testGetAppsBypassingDndCount_oneChannelBypassing_groupBlocked() {
+ int user = USER.getIdentifier();
+ NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+ NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+ NotificationManager.IMPORTANCE_MAX);
+ channel1.setBypassDnd(true);
+ channel1.setGroup(ncg.getId());
+ mHelper.createNotificationChannelGroup(PKG_N_MR1, user, ncg, /* fromTargetApp */ true);
+ mHelper.createNotificationChannel(PKG_N_MR1, user, channel1, true, /*has DND access*/ true);
+
+ assertEquals(1, mHelper.getAppsBypassingDndCount(user));
+
+ // disable group
+ ncg.setBlocked(true);
+ mHelper.createNotificationChannelGroup(PKG_N_MR1, user, ncg, /* fromTargetApp */ false);
+ assertEquals(0, mHelper.getAppsBypassingDndCount(user));
+ }
+
+ @Test
+ public void testGetAppsBypassingDndCount_oneAppBypassing() {
+ int user = USER.getIdentifier();
+ NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+ NotificationManager.IMPORTANCE_MAX);
+ NotificationChannel channel2 = new NotificationChannel("id2", "name2",
+ NotificationManager.IMPORTANCE_MAX);
+ NotificationChannel channel3 = new NotificationChannel("id3", "name3",
+ NotificationManager.IMPORTANCE_MAX);
+ channel1.setBypassDnd(true);
+ channel2.setBypassDnd(true);
+ channel3.setBypassDnd(true);
+ // has DND access, so can set bypassDnd attribute
+ mHelper.createNotificationChannel(PKG_N_MR1, user, channel1, true, /*has DND access*/ true);
+ mHelper.createNotificationChannel(PKG_N_MR1, user, channel2, true, true);
+ mHelper.createNotificationChannel(PKG_N_MR1, user, channel3, true, true);
+ assertEquals(1, mHelper.getAppsBypassingDndCount(user));
+
+ // block notifications from this app
+ mHelper.setEnabled(PKG_N_MR1, user, false);
+ assertEquals(0, mHelper.getAppsBypassingDndCount(user)); // no apps can bypass dnd
+
+ // re-enable notifications from this app
+ mHelper.setEnabled(PKG_N_MR1, user, true);
+ assertEquals(1, mHelper.getAppsBypassingDndCount(user));
+
+ // setBypassDnd false for some channels
+ channel1.setBypassDnd(false);
+ channel2.setBypassDnd(false);
+ assertEquals(1, mHelper.getAppsBypassingDndCount(user));
+
+ // setBypassDnd false for rest of the channels
+ channel3.setBypassDnd(false);
+ assertEquals(0, mHelper.getAppsBypassingDndCount(user));
+ }
+
+ @Test
public void testCreateAndDeleteCanChannelsBypassDnd() throws Exception {
// create notification channel that can't bypass dnd
// expected result: areChannelsBypassingDnd = false
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
index f692a57..16dd92f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
@@ -25,6 +25,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
+import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.server.wm.ActivityDisplay.POSITION_TOP;
@@ -57,6 +58,7 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.graphics.Rect;
+import android.os.Build;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.MediumTest;
@@ -433,6 +435,26 @@
eq(activity), eq(null /* targetOptions */));
}
+ /**
+ * Tests home activities that targeted sdk before Q cannot start on secondary display.
+ */
+ @Test
+ public void testStartHomeTargetSdkBeforeQ() throws Exception {
+ final TestActivityDisplay secondDisplay = spy(createNewActivityDisplay());
+ mSupervisor.addChild(secondDisplay, POSITION_TOP);
+ doReturn(true).when(secondDisplay).supportsSystemDecorations();
+
+ final ActivityInfo info = new ActivityInfo();
+ info.launchMode = LAUNCH_MULTIPLE;
+ info.applicationInfo = new ApplicationInfo();
+ info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.Q;
+ assertTrue(mSupervisor.canStartHomeOnDisplay(info, secondDisplay.mDisplayId,
+ false /* allowInstrumenting */));
+
+ info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.P;
+ assertFalse(mSupervisor.canStartHomeOnDisplay(info, secondDisplay.mDisplayId,
+ false /* allowInstrumenting */));
+ }
/**
* Tests that home activities can be started on the displays that supports system decorations.
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index f7d7ad6..88479ec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -346,7 +346,7 @@
doReturn(stack).when(mService.mStackSupervisor)
.getLaunchStack(any(), any(), any(), anyBoolean());
doReturn(stack).when(mService.mStackSupervisor)
- .getLaunchStack(any(), any(), any(), anyBoolean(), anyInt());
+ .getLaunchStack(any(), any(), any(), anyBoolean(), any());
}
// Set up mock package manager internal and make sure no unmocked methods are called
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 32875da..94d7dbb 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -915,8 +915,12 @@
return "SCREEN_INTERACTIVE";
case UsageEvents.Event.SCREEN_NON_INTERACTIVE:
return "SCREEN_NON_INTERACTIVE";
+ case UsageEvents.Event.KEYGUARD_SHOWN:
+ return "KEYGUARD_SHOWN";
+ case UsageEvents.Event.KEYGUARD_HIDDEN:
+ return "KEYGUARD_HIDDEN";
default:
- return "UNKNOWN";
+ return "UNKNOWN_TYPE_" + eventType;
}
}
diff --git a/startop/view_compiler/dex_builder.cc b/startop/view_compiler/dex_builder.cc
index 33df6f9..906d64c 100644
--- a/startop/view_compiler/dex_builder.cc
+++ b/startop/view_compiler/dex_builder.cc
@@ -49,18 +49,27 @@
case Instruction::Op::kReturn:
out << "kReturn";
return out;
+ case Instruction::Op::kReturnObject:
+ out << "kReturnObject";
+ return out;
case Instruction::Op::kMove:
out << "kMove";
return out;
case Instruction::Op::kInvokeVirtual:
out << "kInvokeVirtual";
return out;
+ case Instruction::Op::kInvokeDirect:
+ out << "kInvokeDirect";
+ return out;
case Instruction::Op::kBindLabel:
out << "kBindLabel";
return out;
case Instruction::Op::kBranchEqz:
out << "kBranchEqz";
return out;
+ case Instruction::Op::kNew:
+ out << "kNew";
+ return out;
}
}
@@ -137,6 +146,9 @@
entry = Alloc<ir::String>();
// +1 for null terminator
entry->data = slicer::MemView{buffer.get(), header_length + string.size() + 1};
+ ::dex::u4 const new_index = dex_file_->strings_indexes.AllocateIndex();
+ dex_file_->strings_map[new_index] = entry;
+ entry->orig_index = new_index;
string_data_.push_back(std::move(buffer));
}
return entry;
@@ -161,6 +173,8 @@
ir::Type* type = Alloc<ir::Type>();
type->descriptor = GetOrAddString(descriptor);
types_by_descriptor_[descriptor] = type;
+ type->orig_index = dex_file_->types_indexes.AllocateIndex();
+ dex_file_->types_map[type->orig_index] = type;
return type;
}
@@ -217,9 +231,10 @@
decl_->prototype->param_types != nullptr ? decl_->prototype->param_types->types.size() : 0;
code->registers = num_registers_ + num_args;
code->ins_count = num_args;
- code->outs_count = decl_->prototype->return_type == dex_->GetOrAddType("V") ? 0 : 1;
EncodeInstructions();
code->instructions = slicer::ArrayView<const ::dex::u2>(buffer_.data(), buffer_.size());
+ size_t const return_count = decl_->prototype->return_type == dex_->GetOrAddType("V") ? 0 : 1;
+ code->outs_count = std::max(return_count, max_args_);
method->code = code;
class_->direct_methods.push_back(method);
@@ -240,8 +255,9 @@
void MethodBuilder::BuildReturn() { AddInstruction(Instruction::OpNoArgs(Op::kReturn)); }
-void MethodBuilder::BuildReturn(Value src) {
- AddInstruction(Instruction::OpWithArgs(Op::kReturn, /*destination=*/{}, src));
+void MethodBuilder::BuildReturn(Value src, bool is_object) {
+ AddInstruction(Instruction::OpWithArgs(
+ is_object ? Op::kReturnObject : Op::kReturn, /*destination=*/{}, src));
}
void MethodBuilder::BuildConst4(Value target, int value) {
@@ -249,6 +265,11 @@
AddInstruction(Instruction::OpWithArgs(Op::kMove, target, Value::Immediate(value)));
}
+void MethodBuilder::BuildConstString(Value target, const std::string& value) {
+ const ir::String* const dex_string = dex_->GetOrAddString(value);
+ AddInstruction(Instruction::OpWithArgs(Op::kMove, target, Value::String(dex_string->orig_index)));
+}
+
void MethodBuilder::EncodeInstructions() {
buffer_.clear();
for (const auto& instruction : instructions_) {
@@ -259,27 +280,32 @@
void MethodBuilder::EncodeInstruction(const Instruction& instruction) {
switch (instruction.opcode()) {
case Instruction::Op::kReturn:
- return EncodeReturn(instruction);
+ return EncodeReturn(instruction, ::art::Instruction::RETURN);
+ case Instruction::Op::kReturnObject:
+ return EncodeReturn(instruction, ::art::Instruction::RETURN_OBJECT);
case Instruction::Op::kMove:
return EncodeMove(instruction);
case Instruction::Op::kInvokeVirtual:
- return EncodeInvokeVirtual(instruction);
+ return EncodeInvoke(instruction, art::Instruction::INVOKE_VIRTUAL);
+ case Instruction::Op::kInvokeDirect:
+ return EncodeInvoke(instruction, art::Instruction::INVOKE_DIRECT);
case Instruction::Op::kBindLabel:
return BindLabel(instruction.args()[0]);
case Instruction::Op::kBranchEqz:
return EncodeBranch(art::Instruction::IF_EQZ, instruction);
+ case Instruction::Op::kNew:
+ return EncodeNew(instruction);
}
}
-void MethodBuilder::EncodeReturn(const Instruction& instruction) {
- DCHECK_EQ(Instruction::Op::kReturn, instruction.opcode());
+void MethodBuilder::EncodeReturn(const Instruction& instruction, ::art::Instruction::Code opcode) {
DCHECK(!instruction.dest().has_value());
if (instruction.args().size() == 0) {
- buffer_.push_back(art::Instruction::RETURN_VOID);
+ Encode10x(art::Instruction::RETURN_VOID);
} else {
- DCHECK(instruction.args().size() == 1);
+ DCHECK_EQ(1, instruction.args().size());
size_t source = RegisterValue(instruction.args()[0]);
- buffer_.push_back(art::Instruction::RETURN | source << 8);
+ Encode11x(opcode, source);
}
}
@@ -294,31 +320,43 @@
if (source.is_immediate()) {
// TODO: support more registers
DCHECK_LT(RegisterValue(*instruction.dest()), 16);
- DCHECK_LT(source.value(), 16);
- buffer_.push_back(art::Instruction::CONST_4 | (source.value() << 12) |
- (RegisterValue(*instruction.dest()) << 8));
+ Encode11n(art::Instruction::CONST_4, RegisterValue(*instruction.dest()), source.value());
+ } else if (source.is_string()) {
+ constexpr size_t kMaxRegisters = 256;
+ DCHECK_LT(RegisterValue(*instruction.dest()), kMaxRegisters);
+ DCHECK_LT(source.value(), 65536); // make sure we don't need a jumbo string
+ Encode21c(::art::Instruction::CONST_STRING, RegisterValue(*instruction.dest()), source.value());
} else {
UNIMPLEMENTED(FATAL);
}
}
-void MethodBuilder::EncodeInvokeVirtual(const Instruction& instruction) {
- DCHECK_EQ(Instruction::Op::kInvokeVirtual, instruction.opcode());
+void MethodBuilder::EncodeInvoke(const Instruction& instruction, ::art::Instruction::Code opcode) {
+ constexpr size_t kMaxArgs = 5;
- // TODO: support more than one argument (i.e. the this argument) and change this to DCHECK_GE
- DCHECK_EQ(1, instruction.args().size());
+ CHECK_LE(instruction.args().size(), kMaxArgs);
- const Value& this_arg = instruction.args()[0];
-
- size_t real_reg = RegisterValue(this_arg) & 0xf;
- buffer_.push_back(1 << 12 | art::Instruction::INVOKE_VIRTUAL);
- buffer_.push_back(instruction.method_id());
- buffer_.push_back(real_reg);
-
- if (instruction.dest().has_value()) {
- real_reg = RegisterValue(*instruction.dest());
- buffer_.push_back(real_reg << 8 | art::Instruction::MOVE_RESULT);
+ uint8_t arguments[kMaxArgs]{};
+ for (size_t i = 0; i < instruction.args().size(); ++i) {
+ CHECK(instruction.args()[i].is_variable());
+ arguments[i] = RegisterValue(instruction.args()[i]);
}
+
+ Encode35c(opcode,
+ instruction.args().size(),
+ instruction.method_id(),
+ arguments[0],
+ arguments[1],
+ arguments[2],
+ arguments[3],
+ arguments[4]);
+
+ // If there is a return value, add a move-result instruction
+ if (instruction.dest().has_value()) {
+ Encode11x(art::Instruction::MOVE_RESULT, RegisterValue(*instruction.dest()));
+ }
+
+ max_args_ = std::max(max_args_, instruction.args().size());
}
// Encodes a conditional branch that tests a single argument.
@@ -331,9 +369,21 @@
CHECK(branch_target.is_label());
size_t instruction_offset = buffer_.size();
- buffer_.push_back(op | (RegisterValue(test_value) << 8));
- size_t field_offset = buffer_.size();
- buffer_.push_back(LabelValue(branch_target, instruction_offset, field_offset));
+ size_t field_offset = buffer_.size() + 1;
+ Encode21c(
+ op, RegisterValue(test_value), LabelValue(branch_target, instruction_offset, field_offset));
+}
+
+void MethodBuilder::EncodeNew(const Instruction& instruction) {
+ DCHECK_EQ(Instruction::Op::kNew, instruction.opcode());
+ DCHECK(instruction.dest().has_value());
+ DCHECK(instruction.dest()->is_variable());
+ DCHECK_EQ(1, instruction.args().size());
+
+ const Value& type = instruction.args()[0];
+ DCHECK_LT(RegisterValue(*instruction.dest()), 256);
+ DCHECK(type.is_type());
+ Encode21c(::art::Instruction::NEW_INSTANCE, RegisterValue(*instruction.dest()), type.value());
}
size_t MethodBuilder::RegisterValue(const Value& value) const {
diff --git a/startop/view_compiler/dex_builder.h b/startop/view_compiler/dex_builder.h
index 0744151..adf82bf 100644
--- a/startop/view_compiler/dex_builder.h
+++ b/startop/view_compiler/dex_builder.h
@@ -110,18 +110,22 @@
static constexpr Value Local(size_t id) { return Value{id, Kind::kLocalRegister}; }
static constexpr Value Parameter(size_t id) { return Value{id, Kind::kParameter}; }
static constexpr Value Immediate(size_t value) { return Value{value, Kind::kImmediate}; }
+ static constexpr Value String(size_t value) { return Value{value, Kind::kString}; }
static constexpr Value Label(size_t id) { return Value{id, Kind::kLabel}; }
+ static constexpr Value Type(size_t id) { return Value{id, Kind::kType}; }
bool is_register() const { return kind_ == Kind::kLocalRegister; }
bool is_parameter() const { return kind_ == Kind::kParameter; }
bool is_variable() const { return is_register() || is_parameter(); }
bool is_immediate() const { return kind_ == Kind::kImmediate; }
+ bool is_string() const { return kind_ == Kind::kString; }
bool is_label() const { return kind_ == Kind::kLabel; }
+ bool is_type() const { return kind_ == Kind::kType; }
size_t value() const { return value_; }
private:
- enum class Kind { kLocalRegister, kParameter, kImmediate, kLabel };
+ enum class Kind { kLocalRegister, kParameter, kImmediate, kString, kLabel, kType };
const size_t value_;
const Kind kind_;
@@ -137,7 +141,16 @@
public:
// The operation performed by this instruction. These are virtual instructions that do not
// correspond exactly to DEX instructions.
- enum class Op { kReturn, kMove, kInvokeVirtual, kBindLabel, kBranchEqz };
+ enum class Op {
+ kReturn,
+ kReturnObject,
+ kMove,
+ kInvokeVirtual,
+ kInvokeDirect,
+ kBindLabel,
+ kBranchEqz,
+ kNew
+ };
////////////////////////
// Named Constructors //
@@ -158,6 +171,12 @@
Value this_arg, T... args) {
return Instruction{Op::kInvokeVirtual, method_id, dest, this_arg, args...};
}
+ // For direct calls (basically, constructors).
+ template <typename... T>
+ static inline Instruction InvokeDirect(size_t method_id, std::optional<const Value> dest,
+ Value this_arg, T... args) {
+ return Instruction{Op::kInvokeDirect, method_id, dest, this_arg, args...};
+ }
///////////////
// Accessors //
@@ -187,6 +206,12 @@
// Needed for CHECK_EQ, DCHECK_EQ, etc.
std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode);
+// Keeps track of information needed to manipulate or call a method.
+struct MethodDeclData {
+ size_t id;
+ ir::MethodDecl* decl;
+};
+
// Tools to help build methods and their bodies.
class MethodBuilder {
public:
@@ -210,19 +235,74 @@
// return-void
void BuildReturn();
- void BuildReturn(Value src);
+ void BuildReturn(Value src, bool is_object = false);
// const/4
void BuildConst4(Value target, int value);
+ void BuildConstString(Value target, const std::string& value);
+ template <typename... T>
+ void BuildNew(Value target, TypeDescriptor type, Prototype constructor, T... args);
// TODO: add builders for more instructions
private:
void EncodeInstructions();
void EncodeInstruction(const Instruction& instruction);
- void EncodeReturn(const Instruction& instruction);
+
+ // Encodes a return instruction. For instructions with no return value, the opcode field is
+ // ignored. Otherwise, this specifies which return instruction will be used (return,
+ // return-object, etc.)
+ void EncodeReturn(const Instruction& instruction, ::art::Instruction::Code opcode);
+
void EncodeMove(const Instruction& instruction);
- void EncodeInvokeVirtual(const Instruction& instruction);
+ void EncodeInvoke(const Instruction& instruction, ::art::Instruction::Code opcode);
void EncodeBranch(art::Instruction::Code op, const Instruction& instruction);
+ void EncodeNew(const Instruction& instruction);
+
+ // Low-level instruction format encoding. See
+ // https://source.android.com/devices/tech/dalvik/instruction-formats for documentation of
+ // formats.
+
+ inline void Encode10x(art::Instruction::Code opcode) {
+ // 00|op
+ buffer_.push_back(opcode);
+ }
+
+ inline void Encode11x(art::Instruction::Code opcode, uint8_t a) {
+ // aa|op
+ buffer_.push_back((a << 8) | opcode);
+ }
+
+ inline void Encode11n(art::Instruction::Code opcode, uint8_t a, int8_t b) {
+ // b|a|op
+
+ // Make sure the fields are in bounds (4 bits for a, 4 bits for b).
+ CHECK_LT(a, 16);
+ CHECK_LE(-8, b);
+ CHECK_LT(b, 8);
+
+ buffer_.push_back(((b & 0xf) << 12) | (a << 8) | opcode);
+ }
+
+ inline void Encode21c(art::Instruction::Code opcode, uint8_t a, uint16_t b) {
+ // aa|op|bbbb
+ buffer_.push_back((a << 8) | opcode);
+ buffer_.push_back(b);
+ }
+
+ inline void Encode35c(art::Instruction::Code opcode, size_t a, uint16_t b, uint8_t c, uint8_t d,
+ uint8_t e, uint8_t f, uint8_t g) {
+ // a|g|op|bbbb|f|e|d|c
+
+ CHECK_LE(a, 5);
+ CHECK_LT(c, 16);
+ CHECK_LT(d, 16);
+ CHECK_LT(e, 16);
+ CHECK_LT(f, 16);
+ CHECK_LT(g, 16);
+ buffer_.push_back((a << 12) | (g << 8) | opcode);
+ buffer_.push_back(b);
+ buffer_.push_back((f << 12) | (e << 8) | (d << 4) | c);
+ }
// Converts a register or parameter to its DEX register number.
size_t RegisterValue(const Value& value) const;
@@ -262,6 +342,10 @@
};
std::vector<LabelData> labels_;
+
+ // During encoding, keep track of the largest number of arguments needed, so we can use it for our
+ // outs count
+ size_t max_args_{0};
};
// A helper to build class definitions.
@@ -281,12 +365,6 @@
ir::Class* const class_;
};
-// Keeps track of information needed to manipulate or call a method.
-struct MethodDeclData {
- size_t id;
- ir::MethodDecl* decl;
-};
-
// Builds Dex files from scratch.
class DexBuilder {
public:
@@ -355,6 +433,17 @@
std::map<Prototype, ir::Proto*> proto_map_;
};
+template <typename... T>
+void MethodBuilder::BuildNew(Value target, TypeDescriptor type, Prototype constructor, T... args) {
+ MethodDeclData constructor_data{dex_->GetOrDeclareMethod(type, "<init>", constructor)};
+ // allocate the object
+ ir::Type* type_def = dex_->GetOrAddType(type.descriptor());
+ AddInstruction(
+ Instruction::OpWithArgs(Instruction::Op::kNew, target, Value::Type(type_def->orig_index)));
+ // call the constructor
+ AddInstruction(Instruction::InvokeDirect(constructor_data.id, /*dest=*/{}, target, args...));
+};
+
} // namespace dex
} // namespace startop
diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
index 169c633..e20f3a9 100644
--- a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
+++ b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
@@ -50,6 +50,14 @@
}
@Test
+ public void returnInteger5() throws Exception {
+ ClassLoader loader = loadDexFile("simple.dex");
+ Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
+ Method method = clazz.getMethod("returnInteger5");
+ Assert.assertEquals(5, method.invoke(null));
+ }
+
+ @Test
public void returnParam() throws Exception {
ClassLoader loader = loadDexFile("simple.dex");
Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
@@ -82,4 +90,38 @@
Method method = clazz.getMethod("backwardsBranch");
Assert.assertEquals(2, method.invoke(null));
}
+
+ @Test
+ public void returnNull() throws Exception {
+ ClassLoader loader = loadDexFile("simple.dex");
+ Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
+ Method method = clazz.getMethod("returnNull");
+ Assert.assertEquals(null, method.invoke(null));
+ }
+
+ @Test
+ public void makeString() throws Exception {
+ ClassLoader loader = loadDexFile("simple.dex");
+ Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
+ Method method = clazz.getMethod("makeString");
+ Assert.assertEquals("Hello, World!", method.invoke(null));
+ }
+
+ @Test
+ public void returnStringIfZeroAB() throws Exception {
+ ClassLoader loader = loadDexFile("simple.dex");
+ Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
+ Method method = clazz.getMethod("returnStringIfZeroAB", int.class);
+ Assert.assertEquals("a", method.invoke(null, 0));
+ Assert.assertEquals("b", method.invoke(null, 1));
+ }
+
+ @Test
+ public void returnStringIfZeroBA() throws Exception {
+ ClassLoader loader = loadDexFile("simple.dex");
+ Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
+ Method method = clazz.getMethod("returnStringIfZeroBA", int.class);
+ Assert.assertEquals("b", method.invoke(null, 0));
+ Assert.assertEquals("a", method.invoke(null, 1));
+ }
}
diff --git a/startop/view_compiler/dex_testcase_generator.cc b/startop/view_compiler/dex_testcase_generator.cc
index c521bf2..e2bf43bc 100644
--- a/startop/view_compiler/dex_testcase_generator.cc
+++ b/startop/view_compiler/dex_testcase_generator.cc
@@ -53,6 +53,19 @@
}
return5.Encode();
+ // int return5() { return 5; }
+ auto integer_type{TypeDescriptor::FromClassname("java.lang.Integer")};
+ auto returnInteger5{cbuilder.CreateMethod("returnInteger5", Prototype{integer_type})};
+ [&](MethodBuilder& method) {
+ Value five{method.MakeRegister()};
+ method.BuildConst4(five, 5);
+ Value object{method.MakeRegister()};
+ method.BuildNew(
+ object, integer_type, Prototype{TypeDescriptor::Void(), TypeDescriptor::Int()}, five);
+ method.BuildReturn(object, /*is_object=*/true);
+ }(returnInteger5);
+ returnInteger5.Encode();
+
// // int returnParam(int x) { return x; }
auto returnParam{cbuilder.CreateMethod("returnParam",
Prototype{TypeDescriptor::Int(), TypeDescriptor::Int()})};
@@ -138,6 +151,71 @@
}(backwardsBranch);
backwardsBranch.Encode();
+ // Test that we can make a null value. Basically:
+ //
+ // public static String returnNull() { return null; }
+ MethodBuilder returnNull{cbuilder.CreateMethod("returnNull", Prototype{string_type})};
+ [](MethodBuilder& method) {
+ Value zero = method.MakeRegister();
+ method.BuildConst4(zero, 0);
+ method.BuildReturn(zero, /*is_object=*/true);
+ }(returnNull);
+ returnNull.Encode();
+
+ // Test that we can make String literals. Basically:
+ //
+ // public static String makeString() { return "Hello, World!"; }
+ MethodBuilder makeString{cbuilder.CreateMethod("makeString", Prototype{string_type})};
+ [](MethodBuilder& method) {
+ Value string = method.MakeRegister();
+ method.BuildConstString(string, "Hello, World!");
+ method.BuildReturn(string, /*is_object=*/true);
+ }(makeString);
+ makeString.Encode();
+
+ // Make sure strings are sorted correctly.
+ //
+ // int returnStringIfZeroAB(int x) { if (x == 0) { return "a"; } else { return "b"; } }
+ MethodBuilder returnStringIfZeroAB{
+ cbuilder.CreateMethod("returnStringIfZeroAB", Prototype{string_type, TypeDescriptor::Int()})};
+ [&](MethodBuilder& method) {
+ Value resultIfZero{method.MakeRegister()};
+ Value else_target{method.MakeLabel()};
+ method.AddInstruction(Instruction::OpWithArgs(
+ Instruction::Op::kBranchEqz, /*dest=*/{}, Value::Parameter(0), else_target));
+ // else branch
+ method.BuildConstString(resultIfZero, "b");
+ method.AddInstruction(
+ Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero));
+ // then branch
+ method.AddInstruction(
+ Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target));
+ method.BuildConstString(resultIfZero, "a");
+ method.AddInstruction(
+ Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero));
+ method.Encode();
+ }(returnStringIfZeroAB);
+ // int returnStringIfZeroAB(int x) { if (x == 0) { return "b"; } else { return "a"; } }
+ MethodBuilder returnStringIfZeroBA{
+ cbuilder.CreateMethod("returnStringIfZeroBA", Prototype{string_type, TypeDescriptor::Int()})};
+ [&](MethodBuilder& method) {
+ Value resultIfZero{method.MakeRegister()};
+ Value else_target{method.MakeLabel()};
+ method.AddInstruction(Instruction::OpWithArgs(
+ Instruction::Op::kBranchEqz, /*dest=*/{}, Value::Parameter(0), else_target));
+ // else branch
+ method.BuildConstString(resultIfZero, "a");
+ method.AddInstruction(
+ Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero));
+ // then branch
+ method.AddInstruction(
+ Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target));
+ method.BuildConstString(resultIfZero, "b");
+ method.AddInstruction(
+ Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero));
+ method.Encode();
+ }(returnStringIfZeroBA);
+
slicer::MemView image{dex_file.CreateImage()};
std::ofstream out_file(outdir + "/simple.dex");
out_file.write(image.ptr<const char>(), image.size());
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index fbc54ae6..bcc0e6b 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -25,6 +25,7 @@
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
import android.content.Context;
import android.os.PersistableBundle;
import android.os.RemoteException;
@@ -1153,11 +1154,20 @@
*/
public static final String KEY_CARRIER_NAME_STRING = "carrier_name_string";
- /**
- * The Component Name of a carrier-provided CallScreeningService implementation. Telecom will
- * bind to this CallScreeningService for ALL incoming calls and provide the carrier
- * CallScreeningService with the opportunity to allow or block calls.
- */
+ /**
+ * The Component Name of a carrier-provided CallScreeningService implementation. Telecom will
+ * bind to {@link android.telecom.CallScreeningService} for ALL incoming calls and provide
+ * the carrier
+ * CallScreeningService with the opportunity to allow or block calls.
+ * <p>
+ * The String includes the package name/the class name.
+ * Example:
+ * <item>com.android.carrier/com.android.carrier.callscreeningserviceimpl</item>
+ * <p>
+ * Using {@link ComponentName#flattenToString()} to convert a ComponentName object to String.
+ * Using {@link ComponentName#unflattenFromString(String)} to convert a String object to a
+ * ComponentName.
+ */
public static final String KEY_CARRIER_CALL_SCREENING_APP_STRING = "call_screening_app";
/**
@@ -1320,18 +1330,13 @@
public static final String KEY_MMS_CLOSE_CONNECTION_BOOL = "mmsCloseConnection";
/**
- * If carriers require differentiate un-provisioned status: cold sim or out of credit sim
- * a package name and activity name can be provided to launch a supported carrier application
- * that check the sim provisioning status
- * The first element is the package name and the second element is the activity name
- * of the provisioning app
- * example:
- * <item>com.google.android.carrierPackageName</item>
- * <item>com.google.android.carrierPackageName.CarrierActivityName</item>
- * The ComponentName of the carrier activity that can setup the device and activate with the
- * network as part of the Setup Wizard flow.
+ * The flatten {@link android.content.ComponentName componentName} of the activity that can
+ * setup the device and activate with the network per carrier requirements.
+ *
+ * e.g, com.google.android.carrierPackageName/.CarrierActivityName
* @hide
*/
+ @SystemApi
public static final String KEY_CARRIER_SETUP_APP_STRING = "carrier_setup_app_string";
/**
@@ -2297,6 +2302,45 @@
public static final String KEY_SUPPORT_EMERGENCY_DIALER_SHORTCUT_BOOL =
"support_emergency_dialer_shortcut_bool";
+ /**
+ * Call forwarding uses USSD command without SS command.
+ * When {@code true}, the call forwarding query/set by ussd command and UI only display Call
+ * Forwarding when unanswered.
+ * When {@code false}, don't use USSD to query/set call forwarding.
+ * @hide
+ */
+ public static final String KEY_USE_CALL_FORWARDING_USSD_BOOL = "use_call_forwarding_ussd_bool";
+
+ /**
+ * This flag specifies whether to support for the caller id set command by ussd.
+ * When {@code true}, device shall sync caller id ussd result to ss command.
+ * When {@code false}, caller id don't support ussd command.
+ * @hide
+ */
+ public static final String KEY_USE_CALLER_ID_USSD_BOOL = "use_caller_id_ussd_bool";
+
+ /**
+ * Specifies the service class for call waiting service.
+ * Default value is
+ * {@link com.android.internal.telephony.CommandsInterface#SERVICE_CLASS_VOICE}.
+ * <p>
+ * See 27.007 +CCFC or +CLCK.
+ * The value set as below:
+ * {@link com.android.internal.telephony.CommandsInterface#SERVICE_CLASS_NONE}
+ * {@link com.android.internal.telephony.CommandsInterface#SERVICE_CLASS_VOICE}
+ * {@link com.android.internal.telephony.CommandsInterface#SERVICE_CLASS_DATA}
+ * {@link com.android.internal.telephony.CommandsInterface#SERVICE_CLASS_FAX}
+ * {@link com.android.internal.telephony.CommandsInterface#SERVICE_CLASS_SMS}
+ * {@link com.android.internal.telephony.CommandsInterface#SERVICE_CLASS_DATA_SYNC}
+ * {@link com.android.internal.telephony.CommandsInterface#SERVICE_CLASS_DATA_ASYNC}
+ * {@link com.android.internal.telephony.CommandsInterface#SERVICE_CLASS_PACKET}
+ * {@link com.android.internal.telephony.CommandsInterface#SERVICE_CLASS_PAD}
+ * {@link com.android.internal.telephony.CommandsInterface#SERVICE_CLASS_MAX}
+ * @hide
+ */
+ public static final String KEY_CALL_WAITING_SERVICE_CLASS_INT =
+ "call_waiting_service_class_int";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -2653,6 +2697,9 @@
sDefaults.putBoolean(KEY_CALL_WAITING_OVER_UT_WARNING_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL, true);
sDefaults.putBoolean(KEY_SUPPORT_EMERGENCY_DIALER_SHORTCUT_BOOL, true);
+ sDefaults.putBoolean(KEY_USE_CALL_FORWARDING_USSD_BOOL, false);
+ sDefaults.putBoolean(KEY_USE_CALLER_ID_USSD_BOOL, false);
+ sDefaults.putInt(KEY_CALL_WAITING_SERVICE_CLASS_INT, 1 /* SERVICE_CLASS_VOICE */);
}
/**
diff --git a/telephony/java/android/telephony/CellConfigLte.java b/telephony/java/android/telephony/CellConfigLte.java
new file mode 100644
index 0000000..35769f0
--- /dev/null
+++ b/telephony/java/android/telephony/CellConfigLte.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * The container of LTE cell related configs.
+ * @hide
+ */
+public class CellConfigLte implements Parcelable {
+ private final boolean mIsEndcAvailable;
+
+ /** @hide */
+ public CellConfigLte() {
+ mIsEndcAvailable = false;
+ }
+
+ /** @hide */
+ public CellConfigLte(boolean isEndcAvailable) {
+ mIsEndcAvailable = isEndcAvailable;
+ }
+
+ /** @hide */
+ public CellConfigLte(CellConfigLte config) {
+ mIsEndcAvailable = config.mIsEndcAvailable;
+ }
+
+ /**
+ * Indicates that if E-UTRA-NR Dual Connectivity (EN-DC) is supported by the LTE cell.
+ *
+ * Reference: 3GPP TS 36.331 v15.2.2 6.3.1 System information blocks.
+ *
+ * @return {@code true} if E-UTRA-NR Dual Connectivity (EN-DC) is supported by the LTE cell.
+ *
+ */
+ boolean isEndcAvailable() {
+ return mIsEndcAvailable;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mIsEndcAvailable);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof CellConfigLte)) return false;
+
+ CellConfigLte o = (CellConfigLte) other;
+ return mIsEndcAvailable == o.mIsEndcAvailable;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mIsEndcAvailable);
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder().append(this.getClass().getName())
+ .append(" :{")
+ .append(" isEndcAvailable = " + mIsEndcAvailable)
+ .append(" }")
+ .toString();
+ }
+
+ private CellConfigLte(Parcel in) {
+ mIsEndcAvailable = in.readBoolean();
+ }
+
+ public static final Creator<CellConfigLte> CREATOR = new Creator<CellConfigLte>() {
+ @Override
+ public CellConfigLte createFromParcel(Parcel in) {
+ return new CellConfigLte(in);
+ }
+
+ @Override
+ public CellConfigLte[] newArray(int size) {
+ return new CellConfigLte[0];
+ }
+ };
+}
diff --git a/telephony/java/android/telephony/CellInfoLte.java b/telephony/java/android/telephony/CellInfoLte.java
index 389f643..7d5388b 100644
--- a/telephony/java/android/telephony/CellInfoLte.java
+++ b/telephony/java/android/telephony/CellInfoLte.java
@@ -19,7 +19,8 @@
import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
-import android.telephony.Rlog;
+
+import java.util.Objects;
/**
* A {@link CellInfo} representing an LTE cell that provides identity and measurement info.
@@ -31,6 +32,7 @@
private CellIdentityLte mCellIdentityLte;
private CellSignalStrengthLte mCellSignalStrengthLte;
+ private CellConfigLte mCellConfig;
/** @hide */
@UnsupportedAppUsage
@@ -38,6 +40,7 @@
super();
mCellIdentityLte = new CellIdentityLte();
mCellSignalStrengthLte = new CellSignalStrengthLte();
+ mCellConfig = new CellConfigLte();
}
/** @hide */
@@ -45,6 +48,7 @@
super(ci);
this.mCellIdentityLte = ci.mCellIdentityLte.copy();
this.mCellSignalStrengthLte = ci.mCellSignalStrengthLte.copy();
+ this.mCellConfig = new CellConfigLte(ci.mCellConfig);
}
@Override
@@ -71,26 +75,37 @@
mCellSignalStrengthLte = css;
}
+ /** @hide */
+ public void setCellConfig(CellConfigLte cellConfig) {
+ if (DBG) log("setCellConfig: " + cellConfig);
+ mCellConfig = cellConfig;
+ }
+
+ /** @hide */
+ public CellConfigLte getCellConfig() {
+ if (DBG) log("getCellConfig: " + mCellConfig);
+ return mCellConfig;
+ }
+
/**
* @return hash code
*/
@Override
public int hashCode() {
- return super.hashCode() + mCellIdentityLte.hashCode() + mCellSignalStrengthLte.hashCode();
+ return Objects.hash(
+ super.hashCode(),
+ mCellIdentityLte.hashCode(),
+ mCellSignalStrengthLte.hashCode(),
+ mCellConfig.hashCode());
}
@Override
public boolean equals(Object other) {
- if (!super.equals(other)) {
- return false;
- }
- try {
- CellInfoLte o = (CellInfoLte) other;
- return mCellIdentityLte.equals(o.mCellIdentityLte)
- && mCellSignalStrengthLte.equals(o.mCellSignalStrengthLte);
- } catch (ClassCastException e) {
- return false;
- }
+ if (!(other instanceof CellInfoLte)) return false;
+ CellInfoLte o = (CellInfoLte) other;
+ return super.equals(o) && mCellIdentityLte.equals(o.mCellIdentityLte)
+ && mCellSignalStrengthLte.equals(o.mCellSignalStrengthLte)
+ && mCellConfig.equals(o.mCellConfig);
}
@Override
@@ -101,6 +116,7 @@
sb.append(super.toString());
sb.append(" ").append(mCellIdentityLte);
sb.append(" ").append(mCellSignalStrengthLte);
+ sb.append(" ").append(mCellConfig);
sb.append("}");
return sb.toString();
@@ -119,6 +135,7 @@
super.writeToParcel(dest, flags, TYPE_LTE);
mCellIdentityLte.writeToParcel(dest, flags);
mCellSignalStrengthLte.writeToParcel(dest, flags);
+ mCellConfig.writeToParcel(dest, flags);
}
/**
@@ -129,6 +146,7 @@
super(in);
mCellIdentityLte = CellIdentityLte.CREATOR.createFromParcel(in);
mCellSignalStrengthLte = CellSignalStrengthLte.CREATOR.createFromParcel(in);
+ mCellConfig = CellConfigLte.CREATOR.createFromParcel(in);
if (DBG) log("CellInfoLte(Parcel): " + toString());
}
diff --git a/telephony/java/android/telephony/CellSignalStrength.java b/telephony/java/android/telephony/CellSignalStrength.java
index 6090d5c..fd21d42 100644
--- a/telephony/java/android/telephony/CellSignalStrength.java
+++ b/telephony/java/android/telephony/CellSignalStrength.java
@@ -21,15 +21,20 @@
*/
public abstract class CellSignalStrength {
- public static final int SIGNAL_STRENGTH_NONE_OR_UNKNOWN = 0;
+ public static final int SIGNAL_STRENGTH_NONE_OR_UNKNOWN =
+ TelephonyProtoEnums.SIGNAL_STRENGTH_NONE_OR_UNKNOWN; // 0
- public static final int SIGNAL_STRENGTH_POOR = 1;
+ public static final int SIGNAL_STRENGTH_POOR =
+ TelephonyProtoEnums.SIGNAL_STRENGTH_POOR; // 1
- public static final int SIGNAL_STRENGTH_MODERATE = 2;
+ public static final int SIGNAL_STRENGTH_MODERATE =
+ TelephonyProtoEnums.SIGNAL_STRENGTH_MODERATE; // 2
- public static final int SIGNAL_STRENGTH_GOOD = 3;
+ public static final int SIGNAL_STRENGTH_GOOD =
+ TelephonyProtoEnums.SIGNAL_STRENGTH_GOOD; // 3
- public static final int SIGNAL_STRENGTH_GREAT = 4;
+ public static final int SIGNAL_STRENGTH_GREAT =
+ TelephonyProtoEnums.SIGNAL_STRENGTH_GREAT; // 4
/** @hide */
public static final int NUM_SIGNAL_STRENGTH_BINS = 5;
diff --git a/telephony/java/android/telephony/DataSpecificRegistrationStates.java b/telephony/java/android/telephony/DataSpecificRegistrationStates.java
index 97e3037..b6e6cba 100644
--- a/telephony/java/android/telephony/DataSpecificRegistrationStates.java
+++ b/telephony/java/android/telephony/DataSpecificRegistrationStates.java
@@ -17,17 +17,40 @@
*/
public final int maxDataCalls;
- DataSpecificRegistrationStates(int maxDataCalls) {
+ /**
+ * Indicates if the use of dual connectivity with NR is restricted.
+ * Reference: 3GPP TS 24.301 v15.03 section 9.3.3.12A.
+ */
+ public final boolean isDcNrRestricted;
+
+ /**
+ * Indicates if NR is supported by the selected PLMN.
+ *
+ * {@code true} if the bit N is in the PLMN-InfoList-r15 is true and the selected PLMN is
+ * present in plmn-IdentityList at position N.
+ * Reference: 3GPP TS 36.331 v15.2.2 section 6.3.1 PLMN-InfoList-r15.
+ * 3GPP TS 36.331 v15.2.2 section 6.2.2 SystemInformationBlockType1 message.
+ */
+ public final boolean isNrAvailable;
+
+ DataSpecificRegistrationStates(
+ int maxDataCalls, boolean isDcNrRestricted, boolean isNrAvailable) {
this.maxDataCalls = maxDataCalls;
+ this.isDcNrRestricted = isDcNrRestricted;
+ this.isNrAvailable = isNrAvailable;
}
private DataSpecificRegistrationStates(Parcel source) {
maxDataCalls = source.readInt();
+ isDcNrRestricted = source.readBoolean();
+ isNrAvailable = source.readBoolean();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(maxDataCalls);
+ dest.writeBoolean(isDcNrRestricted);
+ dest.writeBoolean(isNrAvailable);
}
@Override
@@ -37,24 +60,30 @@
@Override
public String toString() {
- return "DataSpecificRegistrationStates {" + " mMaxDataCalls=" + maxDataCalls + "}";
+ return new StringBuilder().append(this.getClass().getName())
+ .append(" :{")
+ .append(" maxDataCalls = " + maxDataCalls)
+ .append(" isDcNrRestricted = " + isDcNrRestricted)
+ .append(" isNrAvailable = " + isNrAvailable)
+ .append(" }")
+ .toString();
}
@Override
public int hashCode() {
- return Objects.hash(maxDataCalls);
+ return Objects.hash(maxDataCalls, isDcNrRestricted, isNrAvailable);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (o == null || !(o instanceof DataSpecificRegistrationStates)) {
- return false;
- }
+ if (!(o instanceof DataSpecificRegistrationStates)) return false;
DataSpecificRegistrationStates other = (DataSpecificRegistrationStates) o;
- return this.maxDataCalls == other.maxDataCalls;
+ return this.maxDataCalls == other.maxDataCalls
+ && this.isDcNrRestricted == other.isDcNrRestricted
+ && this.isNrAvailable == other.isNrAvailable;
}
public static final Parcelable.Creator<DataSpecificRegistrationStates> CREATOR =
diff --git a/telephony/java/android/telephony/ICellInfoCallback.aidl b/telephony/java/android/telephony/ICellInfoCallback.aidl
new file mode 100644
index 0000000..7fb62682
--- /dev/null
+++ b/telephony/java/android/telephony/ICellInfoCallback.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+package android.telephony;
+
+import android.telephony.CellInfo;
+
+import java.util.List;
+
+/**
+ * Callback to provide asynchronous CellInfo.
+ * @hide
+ */
+oneway interface ICellInfoCallback
+{
+ void onCellInfo(in List<CellInfo> state);
+}
diff --git a/telephony/java/android/telephony/NetworkRegistrationState.java b/telephony/java/android/telephony/NetworkRegistrationState.java
index 68e512e..aee744f 100644
--- a/telephony/java/android/telephony/NetworkRegistrationState.java
+++ b/telephony/java/android/telephony/NetworkRegistrationState.java
@@ -70,6 +70,43 @@
/** Registered on roaming network */
public static final int REG_STATE_ROAMING = 5;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "NR_STATUS_",
+ value = {NR_STATUS_NONE, NR_STATUS_RESTRICTED, NR_STATUS_NOT_RESTRICTED,
+ NR_STATUS_CONNECTED})
+ public @interface NRStatus {}
+
+ /**
+ * The device isn't camped on an LTE cell or the LTE cell doesn't support E-UTRA-NR
+ * Dual Connectivity(EN-DC).
+ * @hide
+ */
+ public static final int NR_STATUS_NONE = -1;
+
+ /**
+ * The device is camped on an LTE cell that supports E-UTRA-NR Dual Connectivity(EN-DC) but
+ * either the use of dual connectivity with NR(DCNR) is restricted or NR is not supported by
+ * the selected PLMN.
+ * @hide
+ */
+ public static final int NR_STATUS_RESTRICTED = 1;
+
+ /**
+ * The device is camped on an LTE cell that supports E-UTRA-NR Dual Connectivity(EN-DC) and both
+ * the use of dual connectivity with NR(DCNR) is not restricted and NR is supported by the
+ * selected PLMN.
+ * @hide
+ */
+ public static final int NR_STATUS_NOT_RESTRICTED = 2;
+
+ /**
+ * The device is camped on an LTE cell that supports E-UTRA-NR Dual Connectivity(EN-DC) and
+ * also connected to at least one 5G cell as a secondary serving cell.
+ * @hide
+ */
+ public static final int NR_STATUS_CONNECTED = 3;
+
/**
* Supported service type
* @hide
@@ -104,6 +141,9 @@
private int mAccessNetworkTechnology;
+ @NRStatus
+ private int mNrStatus;
+
private final int mRejectCause;
private final boolean mEmergencyOnly;
@@ -154,6 +194,7 @@
mAvailableServices = availableServices;
mCellIdentity = cellIdentity;
mEmergencyOnly = emergencyOnly;
+ mNrStatus = NR_STATUS_NONE;
}
/**
@@ -161,11 +202,9 @@
* @hide
*/
public NetworkRegistrationState(int domain, int transportType, int regState,
- int accessNetworkTechnology, int rejectCause,
- boolean emergencyOnly, int[] availableServices,
- @Nullable CellIdentity cellIdentity, boolean cssSupported,
- int roamingIndicator, int systemIsInPrl,
- int defaultRoamingIndicator) {
+ int accessNetworkTechnology, int rejectCause, boolean emergencyOnly,
+ int[] availableServices, @Nullable CellIdentity cellIdentity, boolean cssSupported,
+ int roamingIndicator, int systemIsInPrl, int defaultRoamingIndicator) {
this(domain, transportType, regState, accessNetworkTechnology, rejectCause, emergencyOnly,
availableServices, cellIdentity);
@@ -178,13 +217,14 @@
* @hide
*/
public NetworkRegistrationState(int domain, int transportType, int regState,
- int accessNetworkTechnology, int rejectCause,
- boolean emergencyOnly, int[] availableServices,
- @Nullable CellIdentity cellIdentity, int maxDataCalls) {
+ int accessNetworkTechnology, int rejectCause, boolean emergencyOnly,
+ int[] availableServices, @Nullable CellIdentity cellIdentity, int maxDataCalls,
+ boolean isDcNrRestricted, boolean isNrAvailable) {
this(domain, transportType, regState, accessNetworkTechnology, rejectCause, emergencyOnly,
availableServices, cellIdentity);
- mDataSpecificStates = new DataSpecificRegistrationStates(maxDataCalls);
+ mDataSpecificStates = new DataSpecificRegistrationStates(
+ maxDataCalls, isDcNrRestricted, isNrAvailable);
}
protected NetworkRegistrationState(Parcel source) {
@@ -201,6 +241,7 @@
VoiceSpecificRegistrationStates.class.getClassLoader());
mDataSpecificStates = source.readParcelable(
DataSpecificRegistrationStates.class.getClassLoader());
+ mNrStatus = source.readInt();
}
/**
@@ -214,6 +255,19 @@
public @Domain int getDomain() { return mDomain; }
/**
+ * @return the 5G NR connection status.
+ * @hide
+ */
+ public @NRStatus int getNrStatus() {
+ return mNrStatus;
+ }
+
+ /** @hide */
+ public void setNrStatus(@NRStatus int nrStatus) {
+ mNrStatus = nrStatus;
+ }
+
+ /**
* @return The registration state.
*/
public @RegState int getRegState() {
@@ -316,6 +370,19 @@
return "Unknown reg state " + regState;
}
+ private static String nrStatusToString(@NRStatus int nrStatus) {
+ switch (nrStatus) {
+ case NR_STATUS_RESTRICTED:
+ return "RESTRICTED";
+ case NR_STATUS_NOT_RESTRICTED:
+ return "NOT_RESTRICTED";
+ case NR_STATUS_CONNECTED:
+ return "CONNECTED";
+ default:
+ return "NONE";
+ }
+ }
+
@Override
public String toString() {
return new StringBuilder("NetworkRegistrationState{")
@@ -331,6 +398,7 @@
.append(" cellIdentity=").append(mCellIdentity)
.append(" voiceSpecificStates=").append(mVoiceSpecificStates)
.append(" dataSpecificStates=").append(mDataSpecificStates)
+ .append(" nrStatus=").append(nrStatusToString(mNrStatus))
.append("}").toString();
}
@@ -338,14 +406,14 @@
public int hashCode() {
return Objects.hash(mDomain, mTransportType, mRegState, mRoamingType,
mAccessNetworkTechnology, mRejectCause, mEmergencyOnly, mAvailableServices,
- mCellIdentity, mVoiceSpecificStates, mDataSpecificStates);
+ mCellIdentity, mVoiceSpecificStates, mDataSpecificStates, mNrStatus);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (o == null || !(o instanceof NetworkRegistrationState)) {
+ if (!(o instanceof NetworkRegistrationState)) {
return false;
}
@@ -357,11 +425,11 @@
&& mAccessNetworkTechnology == other.mAccessNetworkTechnology
&& mRejectCause == other.mRejectCause
&& mEmergencyOnly == other.mEmergencyOnly
- && (mAvailableServices == other.mAvailableServices
- || Arrays.equals(mAvailableServices, other.mAvailableServices))
- && equals(mCellIdentity, other.mCellIdentity)
- && equals(mVoiceSpecificStates, other.mVoiceSpecificStates)
- && equals(mDataSpecificStates, other.mDataSpecificStates);
+ && Arrays.equals(mAvailableServices, other.mAvailableServices)
+ && Objects.equals(mCellIdentity, other.mCellIdentity)
+ && Objects.equals(mVoiceSpecificStates, other.mVoiceSpecificStates)
+ && Objects.equals(mDataSpecificStates, other.mDataSpecificStates)
+ && mNrStatus == other.mNrStatus;
}
@Override
@@ -377,6 +445,7 @@
dest.writeParcelable(mCellIdentity, 0);
dest.writeParcelable(mVoiceSpecificStates, 0);
dest.writeParcelable(mDataSpecificStates, 0);
+ dest.writeInt(mNrStatus);
}
public static final Parcelable.Creator<NetworkRegistrationState> CREATOR =
@@ -391,14 +460,4 @@
return new NetworkRegistrationState[size];
}
};
-
- private static boolean equals(Object o1, Object o2) {
- if (o1 == o2) {
- return true;
- } else if (o1 == null) {
- return false;
- } else {
- return o1.equals(o2);
- }
- }
}
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index f5dff20..e8a28ca 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -21,16 +21,19 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
+import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.Looper;
-import android.os.Message;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IPhoneStateListener;
import java.lang.ref.WeakReference;
import java.util.List;
+import java.util.concurrent.Executor;
/**
* A listener class for monitoring changes in specific telephony states
@@ -231,34 +234,35 @@
public static final int LISTEN_CARRIER_NETWORK_CHANGE = 0x00010000;
/**
- * Listen for changes to the sim voice activation state
- * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATING
- * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED
- * @see TelephonyManager#SIM_ACTIVATION_STATE_DEACTIVATED
- * @see TelephonyManager#SIM_ACTIVATION_STATE_RESTRICTED
- * @see TelephonyManager#SIM_ACTIVATION_STATE_UNKNOWN
- * {@more}
- * Example: TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED indicates voice service has been
- * fully activated
+ * Listen for changes to the sim voice activation state
+ * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATING
+ * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED
+ * @see TelephonyManager#SIM_ACTIVATION_STATE_DEACTIVATED
+ * @see TelephonyManager#SIM_ACTIVATION_STATE_RESTRICTED
+ * @see TelephonyManager#SIM_ACTIVATION_STATE_UNKNOWN
+ * {@more}
+ * Example: TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED indicates voice service has been
+ * fully activated
*
- * @see #onVoiceActivationStateChanged
- * @hide
+ * @see #onVoiceActivationStateChanged
+ * @hide
*/
+ @SystemApi
public static final int LISTEN_VOICE_ACTIVATION_STATE = 0x00020000;
/**
- * Listen for changes to the sim data activation state
- * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATING
- * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED
- * @see TelephonyManager#SIM_ACTIVATION_STATE_DEACTIVATED
- * @see TelephonyManager#SIM_ACTIVATION_STATE_RESTRICTED
- * @see TelephonyManager#SIM_ACTIVATION_STATE_UNKNOWN
- * {@more}
- * Example: TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED indicates data service has been
- * fully activated
+ * Listen for changes to the sim data activation state
+ * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATING
+ * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED
+ * @see TelephonyManager#SIM_ACTIVATION_STATE_DEACTIVATED
+ * @see TelephonyManager#SIM_ACTIVATION_STATE_RESTRICTED
+ * @see TelephonyManager#SIM_ACTIVATION_STATE_UNKNOWN
+ * {@more}
+ * Example: TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED indicates data service has been
+ * fully activated
*
- * @see #onDataActivationStateChanged
- * @hide
+ * @see #onDataActivationStateChanged
+ * @hide
*/
public static final int LISTEN_DATA_ACTIVATION_STATE = 0x00040000;
@@ -320,7 +324,12 @@
@UnsupportedAppUsage
protected Integer mSubId;
- private final Handler mHandler;
+ /**
+ * @hide
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @UnsupportedAppUsage
+ public final IPhoneStateListener callback;
/**
* Create a PhoneStateListener for the Phone with the default subscription.
@@ -335,7 +344,7 @@
* using a particular non-null Looper.
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public PhoneStateListener(Looper looper) {
this(null, looper);
}
@@ -346,7 +355,7 @@
* own non-null Looper use PhoneStateListener(int subId, Looper looper) below.
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public PhoneStateListener(Integer subId) {
this(subId, Looper.myLooper());
}
@@ -356,97 +365,29 @@
* and non-null Looper.
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public PhoneStateListener(Integer subId, Looper looper) {
- if (DBG) log("ctor: subId=" + subId + " looper=" + looper);
+ this(subId, new HandlerExecutor(new Handler(looper)));
+ }
+
+ /**
+ * Create a PhoneStateListener for the Phone using the specified Executor
+ *
+ * <p>Create a PhoneStateListener with a specified Executor for handling necessary callbacks.
+ * The Executor must not be null.
+ *
+ * @param executor a non-null Executor that will execute callbacks for the PhoneStateListener.
+ */
+ public PhoneStateListener(@NonNull Executor executor) {
+ this(null, executor);
+ }
+
+ private PhoneStateListener(Integer subId, Executor e) {
+ if (e == null) {
+ throw new IllegalArgumentException("PhoneStateListener Executor must be non-null");
+ }
mSubId = subId;
- mHandler = new Handler(looper) {
- public void handleMessage(Message msg) {
- if (DBG) {
- log("mSubId=" + mSubId + " what=0x" + Integer.toHexString(msg.what)
- + " msg=" + msg);
- }
- switch (msg.what) {
- case LISTEN_SERVICE_STATE:
- PhoneStateListener.this.onServiceStateChanged((ServiceState)msg.obj);
- break;
- case LISTEN_SIGNAL_STRENGTH:
- PhoneStateListener.this.onSignalStrengthChanged(msg.arg1);
- break;
- case LISTEN_MESSAGE_WAITING_INDICATOR:
- PhoneStateListener.this.onMessageWaitingIndicatorChanged(msg.arg1 != 0);
- break;
- case LISTEN_CALL_FORWARDING_INDICATOR:
- PhoneStateListener.this.onCallForwardingIndicatorChanged(msg.arg1 != 0);
- break;
- case LISTEN_CELL_LOCATION:
- PhoneStateListener.this.onCellLocationChanged((CellLocation)msg.obj);
- break;
- case LISTEN_CALL_STATE:
- PhoneStateListener.this.onCallStateChanged(msg.arg1, (String)msg.obj);
- break;
- case LISTEN_DATA_CONNECTION_STATE:
- PhoneStateListener.this.onDataConnectionStateChanged(msg.arg1, msg.arg2);
- PhoneStateListener.this.onDataConnectionStateChanged(msg.arg1);
- break;
- case LISTEN_DATA_ACTIVITY:
- PhoneStateListener.this.onDataActivity(msg.arg1);
- break;
- case LISTEN_SIGNAL_STRENGTHS:
- PhoneStateListener.this.onSignalStrengthsChanged((SignalStrength)msg.obj);
- break;
- case LISTEN_OTASP_CHANGED:
- PhoneStateListener.this.onOtaspChanged(msg.arg1);
- break;
- case LISTEN_CELL_INFO:
- PhoneStateListener.this.onCellInfoChanged((List<CellInfo>)msg.obj);
- break;
- case LISTEN_PRECISE_CALL_STATE:
- PhoneStateListener.this.onPreciseCallStateChanged((PreciseCallState)msg.obj);
- break;
- case LISTEN_PRECISE_DATA_CONNECTION_STATE:
- PhoneStateListener.this.onPreciseDataConnectionStateChanged(
- (PreciseDataConnectionState)msg.obj);
- break;
- case LISTEN_DATA_CONNECTION_REAL_TIME_INFO:
- PhoneStateListener.this.onDataConnectionRealTimeInfoChanged(
- (DataConnectionRealTimeInfo)msg.obj);
- break;
- case LISTEN_SRVCC_STATE_CHANGED:
- PhoneStateListener.this.onSrvccStateChanged((int) msg.obj);
- break;
- case LISTEN_VOICE_ACTIVATION_STATE:
- PhoneStateListener.this.onVoiceActivationStateChanged((int)msg.obj);
- break;
- case LISTEN_DATA_ACTIVATION_STATE:
- PhoneStateListener.this.onDataActivationStateChanged((int)msg.obj);
- break;
- case LISTEN_USER_MOBILE_DATA_STATE:
- PhoneStateListener.this.onUserMobileDataStateChanged((boolean)msg.obj);
- break;
- case LISTEN_OEM_HOOK_RAW_EVENT:
- PhoneStateListener.this.onOemHookRawEvent((byte[])msg.obj);
- break;
- case LISTEN_CARRIER_NETWORK_CHANGE:
- PhoneStateListener.this.onCarrierNetworkChange((boolean)msg.obj);
- break;
- case LISTEN_PHYSICAL_CHANNEL_CONFIGURATION:
- PhoneStateListener.this.onPhysicalChannelConfigurationChanged(
- (List<PhysicalChannelConfig>)msg.obj);
- break;
- case LISTEN_PHONE_CAPABILITY_CHANGE:
- PhoneStateListener.this.onPhoneCapabilityChanged(
- (PhoneCapability) msg.obj);
- break;
- case LISTEN_PREFERRED_DATA_SUBID_CHANGE:
- PhoneStateListener.this.onPreferredDataSubIdChanged((int) msg.obj);
- break;
- case LISTEN_RADIO_POWER_STATE_CHANGED:
- PhoneStateListener.this.onRadioPowerStateChanged((int) msg.obj);
- break;
- }
- }
- };
+ callback = new IPhoneStateListenerStub(this, e);
}
/**
@@ -630,8 +571,8 @@
* @param state is the current SIM voice activation state
* @hide
*/
- public void onVoiceActivationStateChanged(int state) {
-
+ @SystemApi
+ public void onVoiceActivationStateChanged(@TelephonyManager.SimActivationState int state) {
}
/**
@@ -639,8 +580,7 @@
* @param state is the current SIM data activation state
* @hide
*/
- public void onDataActivationStateChanged(int state) {
-
+ public void onDataActivationStateChanged(@TelephonyManager.SimActivationState int state) {
}
/**
@@ -735,127 +675,217 @@
*/
private static class IPhoneStateListenerStub extends IPhoneStateListener.Stub {
private WeakReference<PhoneStateListener> mPhoneStateListenerWeakRef;
+ private Executor mExecutor;
- public IPhoneStateListenerStub(PhoneStateListener phoneStateListener) {
+ IPhoneStateListenerStub(PhoneStateListener phoneStateListener, Executor executor) {
mPhoneStateListenerWeakRef = new WeakReference<PhoneStateListener>(phoneStateListener);
- }
-
- private void send(int what, int arg1, int arg2, Object obj) {
- PhoneStateListener listener = mPhoneStateListenerWeakRef.get();
- if (listener != null) {
- Message.obtain(listener.mHandler, what, arg1, arg2, obj).sendToTarget();
- }
+ mExecutor = executor;
}
public void onServiceStateChanged(ServiceState serviceState) {
- send(LISTEN_SERVICE_STATE, 0, 0, serviceState);
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> psl.onServiceStateChanged(serviceState)));
}
public void onSignalStrengthChanged(int asu) {
- send(LISTEN_SIGNAL_STRENGTH, asu, 0, null);
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> psl.onSignalStrengthChanged(asu)));
}
public void onMessageWaitingIndicatorChanged(boolean mwi) {
- send(LISTEN_MESSAGE_WAITING_INDICATOR, mwi ? 1 : 0, 0, null);
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> psl.onMessageWaitingIndicatorChanged(mwi)));
}
public void onCallForwardingIndicatorChanged(boolean cfi) {
- send(LISTEN_CALL_FORWARDING_INDICATOR, cfi ? 1 : 0, 0, null);
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> psl.onCallForwardingIndicatorChanged(cfi)));
}
public void onCellLocationChanged(Bundle bundle) {
CellLocation location = CellLocation.newFromBundle(bundle);
- send(LISTEN_CELL_LOCATION, 0, 0, location);
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> psl.onCellLocationChanged(location)));
}
public void onCallStateChanged(int state, String incomingNumber) {
- send(LISTEN_CALL_STATE, state, 0, incomingNumber);
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> psl.onCallStateChanged(state, incomingNumber)));
}
public void onDataConnectionStateChanged(int state, int networkType) {
- send(LISTEN_DATA_CONNECTION_STATE, state, networkType, null);
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(
+ () -> psl.onDataConnectionStateChanged(state, networkType)));
}
public void onDataActivity(int direction) {
- send(LISTEN_DATA_ACTIVITY, direction, 0, null);
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> psl.onDataActivity(direction)));
}
public void onSignalStrengthsChanged(SignalStrength signalStrength) {
- send(LISTEN_SIGNAL_STRENGTHS, 0, 0, signalStrength);
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> psl.onSignalStrengthsChanged(signalStrength)));
}
public void onOtaspChanged(int otaspMode) {
- send(LISTEN_OTASP_CHANGED, otaspMode, 0, null);
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> psl.onOtaspChanged(otaspMode)));
}
public void onCellInfoChanged(List<CellInfo> cellInfo) {
- send(LISTEN_CELL_INFO, 0, 0, cellInfo);
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> psl.onCellInfoChanged(cellInfo)));
}
public void onPreciseCallStateChanged(PreciseCallState callState) {
- send(LISTEN_PRECISE_CALL_STATE, 0, 0, callState);
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> psl.onPreciseCallStateChanged(callState)));
}
public void onPreciseDataConnectionStateChanged(
PreciseDataConnectionState dataConnectionState) {
- send(LISTEN_PRECISE_DATA_CONNECTION_STATE, 0, 0, dataConnectionState);
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(
+ () -> psl.onPreciseDataConnectionStateChanged(dataConnectionState)));
}
- public void onDataConnectionRealTimeInfoChanged(
- DataConnectionRealTimeInfo dcRtInfo) {
- send(LISTEN_DATA_CONNECTION_REAL_TIME_INFO, 0, 0, dcRtInfo);
+ public void onDataConnectionRealTimeInfoChanged(DataConnectionRealTimeInfo dcRtInfo) {
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(
+ () -> psl.onDataConnectionRealTimeInfoChanged(dcRtInfo)));
}
public void onSrvccStateChanged(int state) {
- send(LISTEN_SRVCC_STATE_CHANGED, 0, 0, state);
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> psl.onSrvccStateChanged(state)));
}
public void onVoiceActivationStateChanged(int activationState) {
- send(LISTEN_VOICE_ACTIVATION_STATE, 0, 0, activationState);
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(
+ () -> psl.onVoiceActivationStateChanged(activationState)));
}
public void onDataActivationStateChanged(int activationState) {
- send(LISTEN_DATA_ACTIVATION_STATE, 0, 0, activationState);
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(
+ () -> psl.onDataActivationStateChanged(activationState)));
}
public void onUserMobileDataStateChanged(boolean enabled) {
- send(LISTEN_USER_MOBILE_DATA_STATE, 0, 0, enabled);
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(
+ () -> psl.onUserMobileDataStateChanged(enabled)));
}
public void onOemHookRawEvent(byte[] rawData) {
- send(LISTEN_OEM_HOOK_RAW_EVENT, 0, 0, rawData);
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> psl.onOemHookRawEvent(rawData)));
}
public void onCarrierNetworkChange(boolean active) {
- send(LISTEN_CARRIER_NETWORK_CHANGE, 0, 0, active);
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> psl.onCarrierNetworkChange(active)));
}
public void onPhysicalChannelConfigurationChanged(List<PhysicalChannelConfig> configs) {
- send(LISTEN_PHYSICAL_CHANNEL_CONFIGURATION, 0, 0, configs);
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(
+ () -> psl.onPhysicalChannelConfigurationChanged(configs)));
}
public void onPhoneCapabilityChanged(PhoneCapability capability) {
- send(LISTEN_PHONE_CAPABILITY_CHANGE, 0, 0, capability);
- }
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
- public void onPreferredDataSubIdChanged(int subId) {
- send(LISTEN_PREFERRED_DATA_SUBID_CHANGE, 0, 0, subId);
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> psl.onPhoneCapabilityChanged(capability)));
}
public void onRadioPowerStateChanged(@TelephonyManager.RadioPowerState int state) {
- send(LISTEN_RADIO_POWER_STATE_CHANGED, 0, 0, state);
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> psl.onRadioPowerStateChanged(state)));
}
+ public void onPreferredDataSubIdChanged(int subId) {
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> psl.onPreferredDataSubIdChanged(subId)));
+ }
}
- /**
- * @hide
- */
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- @UnsupportedAppUsage
- public final IPhoneStateListener callback = new IPhoneStateListenerStub(this);
private void log(String s) {
Rlog.d(LOG_TAG, s);
}
-}
\ No newline at end of file
+}
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 0937b10..777d219 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -83,7 +83,45 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef({DUPLEX_MODE_UNKNOWN, DUPLEX_MODE_FDD, DUPLEX_MODE_TDD})
+ @IntDef(prefix = "FREQUENCY_RANGE_",
+ value = {FREQUENCY_RANGE_UNKNOWN, FREQUENCY_RANGE_LOW, FREQUENCY_RANGE_MID,
+ FREQUENCY_RANGE_HIGH, FREQUENCY_RANGE_MMWAVE})
+ public @interface FrequencyRange {}
+
+ /**
+ * Indicates frequency range is unknown.
+ * @hide
+ */
+ public static final int FREQUENCY_RANGE_UNKNOWN = -1;
+
+ /**
+ * Indicates the frequency range is below 1GHz.
+ * @hide
+ */
+ public static final int FREQUENCY_RANGE_LOW = 1;
+
+ /**
+ * Indicates the frequency range is between 1GHz to 3GHz.
+ * @hide
+ */
+ public static final int FREQUENCY_RANGE_MID = 2;
+
+ /**
+ * Indicates the frequency range is between 3GHz and 6GHz.
+ * @hide
+ */
+ public static final int FREQUENCY_RANGE_HIGH = 3;
+
+ /**
+ * Indicates the frequency range is above 6GHz (millimeter wave frequency).
+ * @hide
+ */
+ public static final int FREQUENCY_RANGE_MMWAVE = 4;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "DUPLEX_MODE_",
+ value = {DUPLEX_MODE_UNKNOWN, DUPLEX_MODE_FDD, DUPLEX_MODE_TDD})
public @interface DuplexMode {}
/**
@@ -175,7 +213,7 @@
* IWLAN
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public static final int RIL_RADIO_TECHNOLOGY_IWLAN = 18;
/**
@@ -283,6 +321,8 @@
@UnsupportedAppUsage
private boolean mIsUsingCarrierAggregation;
+ @FrequencyRange
+ private int mNrFrequencyRange;
private int mChannelNumber;
private int[] mCellBandwidths = new int[0];
@@ -375,6 +415,7 @@
mLteEarfcnRsrpBoost = s.mLteEarfcnRsrpBoost;
mNetworkRegistrationStates = s.mNetworkRegistrationStates == null ? null :
new ArrayList<>(s.mNetworkRegistrationStates);
+ mNrFrequencyRange = s.mNrFrequencyRange;
}
/**
@@ -406,6 +447,7 @@
in.readList(mNetworkRegistrationStates, NetworkRegistrationState.class.getClassLoader());
mChannelNumber = in.readInt();
mCellBandwidths = in.createIntArray();
+ mNrFrequencyRange = in.readInt();
}
public void writeToParcel(Parcel out, int flags) {
@@ -433,6 +475,7 @@
out.writeList(mNetworkRegistrationStates);
out.writeInt(mChannelNumber);
out.writeIntArray(mCellBandwidths);
+ out.writeInt(mNrFrequencyRange);
}
public int describeContents() {
@@ -552,7 +595,7 @@
* @return roaming type
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public @RoamingType int getVoiceRoamingType() {
final NetworkRegistrationState regState = getNetworkRegistrationState(
NetworkRegistrationState.DOMAIN_CS, AccessNetworkConstants.TransportType.WWAN);
@@ -591,7 +634,7 @@
* @return roaming type
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public @RoamingType int getDataRoamingType() {
final NetworkRegistrationState regState = getNetworkRegistrationState(
NetworkRegistrationState.DOMAIN_PS, AccessNetworkConstants.TransportType.WWAN);
@@ -742,7 +785,7 @@
* @return numeric format of operator, null if unregistered or unknown
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public String getVoiceOperatorNumeric() {
return mVoiceOperatorNumeric;
}
@@ -792,7 +835,8 @@
mIsEmergencyOnly,
mIsUsingCarrierAggregation,
mLteEarfcnRsrpBoost,
- mNetworkRegistrationStates);
+ mNetworkRegistrationStates,
+ mNrFrequencyRange);
}
@Override
@@ -823,7 +867,8 @@
&& mIsUsingCarrierAggregation == s.mIsUsingCarrierAggregation)
&& (mNetworkRegistrationStates == null ? s.mNetworkRegistrationStates == null :
s.mNetworkRegistrationStates != null &&
- mNetworkRegistrationStates.containsAll(s.mNetworkRegistrationStates));
+ mNetworkRegistrationStates.containsAll(s.mNetworkRegistrationStates))
+ && mNrFrequencyRange == s.mNrFrequencyRange;
}
/**
@@ -958,6 +1003,7 @@
.append(", mIsUsingCarrierAggregation=").append(mIsUsingCarrierAggregation)
.append(", mLteEarfcnRsrpBoost=").append(mLteEarfcnRsrpBoost)
.append(", mNetworkRegistrationStates=").append(mNetworkRegistrationStates)
+ .append(", mNrFrequencyRange=").append(mNrFrequencyRange)
.append("}").toString();
}
@@ -987,6 +1033,7 @@
mIsUsingCarrierAggregation = false;
mLteEarfcnRsrpBoost = 0;
mNetworkRegistrationStates = new ArrayList<>();
+ mNrFrequencyRange = FREQUENCY_RANGE_UNKNOWN;
}
public void setStateOutOfService() {
@@ -1225,6 +1272,7 @@
m.putInt("LteEarfcnRsrpBoost", mLteEarfcnRsrpBoost);
m.putInt("ChannelNumber", mChannelNumber);
m.putIntArray("CellBandwidths", mCellBandwidths);
+ m.putInt("mNrFrequencyRange", mNrFrequencyRange);
}
/** @hide */
@@ -1288,6 +1336,22 @@
mIsUsingCarrierAggregation = ca;
}
+ /**
+ * @return the frequency range of 5G NR.
+ * @hide
+ */
+ public @FrequencyRange int getNrFrequencyRange() {
+ return mNrFrequencyRange;
+ }
+
+ /**
+ * @param nrFrequencyRange the frequency range of 5G NR.
+ * @hide
+ */
+ public void setNrFrequencyRange(@FrequencyRange int nrFrequencyRange) {
+ mNrFrequencyRange = nrFrequencyRange;
+ }
+
/** @hide */
public int getLteEarfcnRsrpBoost() {
return mLteEarfcnRsrpBoost;
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index bc832c3..240b8a9 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -17,12 +17,12 @@
package android.telephony;
import android.annotation.UnsupportedAppUsage;
+import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.CarrierConfigManager;
import android.util.Log;
-import android.content.res.Resources;
import java.util.ArrayList;
import java.util.Arrays;
@@ -37,25 +37,25 @@
private static final boolean DBG = false;
/** @hide */
- @UnsupportedAppUsage
- public static final int SIGNAL_STRENGTH_NONE_OR_UNKNOWN
- = TelephonyProtoEnums.SIGNAL_STRENGTH_NONE_OR_UNKNOWN; // = 0
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public static final int SIGNAL_STRENGTH_NONE_OR_UNKNOWN =
+ CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN; // = 0
/** @hide */
- @UnsupportedAppUsage
- public static final int SIGNAL_STRENGTH_POOR
- = TelephonyProtoEnums.SIGNAL_STRENGTH_POOR; // = 1
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public static final int SIGNAL_STRENGTH_POOR =
+ CellSignalStrength.SIGNAL_STRENGTH_POOR; // = 1
/** @hide */
- @UnsupportedAppUsage
- public static final int SIGNAL_STRENGTH_MODERATE
- = TelephonyProtoEnums.SIGNAL_STRENGTH_MODERATE; // = 2
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public static final int SIGNAL_STRENGTH_MODERATE =
+ CellSignalStrength.SIGNAL_STRENGTH_MODERATE; // = 2
/** @hide */
- @UnsupportedAppUsage
- public static final int SIGNAL_STRENGTH_GOOD
- = TelephonyProtoEnums.SIGNAL_STRENGTH_GOOD; // = 3
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public static final int SIGNAL_STRENGTH_GOOD =
+ CellSignalStrength.SIGNAL_STRENGTH_GOOD; // = 3
/** @hide */
- @UnsupportedAppUsage
- public static final int SIGNAL_STRENGTH_GREAT
- = TelephonyProtoEnums.SIGNAL_STRENGTH_GREAT; // = 4
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public static final int SIGNAL_STRENGTH_GREAT =
+ CellSignalStrength.SIGNAL_STRENGTH_GREAT; // = 4
/** @hide */
@UnsupportedAppUsage
public static final int NUM_SIGNAL_STRENGTH_BINS = 5;
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 22c1e58..b41e14e 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -143,9 +143,11 @@
private boolean mIsOpportunistic;
/**
- * SubId of the parent subscription, if there is one.
+ * A UUID assigned to the subscription group. It returns
+ * null if not assigned.
*/
- private int mParentSubId;
+ @Nullable
+ private String mGroupUUID;
/**
* @hide
@@ -156,7 +158,7 @@
@Nullable UiccAccessRule[] accessRules, String cardId) {
this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardId,
- false, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ false, null);
}
/**
@@ -166,7 +168,7 @@
CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@Nullable UiccAccessRule[] accessRules, String cardId, boolean isOpportunistic,
- int parentSubId) {
+ @Nullable String groupUUID) {
this.mId = id;
this.mIccId = iccId;
this.mSimSlotIndex = simSlotIndex;
@@ -184,7 +186,7 @@
this.mAccessRules = accessRules;
this.mCardId = cardId;
this.mIsOpportunistic = isOpportunistic;
- this.mParentSubId = parentSubId;
+ this.mGroupUUID = groupUUID;
}
/**
@@ -388,16 +390,16 @@
}
/**
- * Used in scenarios where a child subscription is bundled with a primary parent subscription.
- * The child subscription will typically be opportunistic (see {@link #isOpportunistic()})
- * and will be used to provide data services where available, with the parent being the primary
- * fallback subscription.
+ * Used in scenarios where different subscriptions are bundled as a group.
+ * It's typically a primary and an opportunistic subscription. (see {@link #isOpportunistic()})
+ * Such that those subscriptions will have some affiliated behaviors such as opportunistic
+ * subscription may be invisible to the user.
*
- * @return subId of parent subscription if it’s bundled with a primary subscription.
- * If there isn't one, {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}
+ * @return group UUID a String of group UUID if it belongs to a group. Otherwise
+ * it will return null.
*/
- public int getParentSubId() {
- return mParentSubId;
+ public String getGroupUuid() {
+ return mGroupUUID;
}
/**
@@ -493,11 +495,11 @@
UiccAccessRule[] accessRules = source.createTypedArray(UiccAccessRule.CREATOR);
String cardId = source.readString();
boolean isOpportunistic = source.readBoolean();
- int parentSubId = source.readInt();
+ String groupUUID = source.readString();
return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName,
nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso,
- isEmbedded, accessRules, cardId, isOpportunistic, parentSubId);
+ isEmbedded, accessRules, cardId, isOpportunistic, groupUUID);
}
@Override
@@ -525,7 +527,7 @@
dest.writeTypedArray(mAccessRules, flags);
dest.writeString(mCardId);
dest.writeBoolean(mIsOpportunistic);
- dest.writeInt(mParentSubId);
+ dest.writeString(mGroupUUID);
}
@Override
@@ -559,13 +561,13 @@
+ " mnc " + mMnc + "mCountryIso=" + mCountryIso + " isEmbedded " + mIsEmbedded
+ " accessRules " + Arrays.toString(mAccessRules)
+ " cardId=" + cardIdToPrint + " isOpportunistic " + mIsOpportunistic
- + " parentSubId=" + mParentSubId + "}";
+ + " mGroupUUID=" + mGroupUUID + "}";
}
@Override
public int hashCode() {
return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded,
- mIsOpportunistic, mParentSubId, mIccId, mNumber, mMcc, mMnc, mCountryIso,
+ mIsOpportunistic, mGroupUUID, mIccId, mNumber, mMcc, mMnc, mCountryIso,
mCardId, mDisplayName, mCarrierName, mAccessRules);
}
@@ -588,7 +590,7 @@
&& mDataRoaming == toCompare.mDataRoaming
&& mIsEmbedded == toCompare.mIsEmbedded
&& mIsOpportunistic == toCompare.mIsOpportunistic
- && mParentSubId == toCompare.mParentSubId
+ && Objects.equals(mGroupUUID, toCompare.mGroupUUID)
&& Objects.equals(mIccId, toCompare.mIccId)
&& Objects.equals(mNumber, toCompare.mNumber)
&& Objects.equals(mMcc, toCompare.mMcc)
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 31770ec..3200aea 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -119,7 +119,6 @@
@UnsupportedAppUsage
public static final Uri CONTENT_URI = Uri.parse("content://telephony/siminfo");
-
/**
* Generates a content {@link Uri} used to receive updates on simInfo change
* on the given subscriptionId
@@ -577,6 +576,15 @@
public static final String PARENT_SUB_ID = "parent_sub_id";
/**
+ * TelephonyProvider column name for group ID. Subscriptions with same group ID
+ * are considered bundled together, and should behave as a single subscription at
+ * certain scenarios.
+ *
+ * @hide
+ */
+ public static final String GROUP_UUID = "group_uuid";
+
+ /**
* Broadcast Action: The user has changed one of the default subs related to
* data, phone calls, or sms</p>
*
@@ -1413,8 +1421,9 @@
/**
* Get an array of Subscription Ids for specified slot Index.
- * @param slotIndex the slot Index.
- * @return subscription Ids or null if the given slot Index is not valid.
+ * @param slotIndex the slot index.
+ * @return subscription Ids or null if the given slot Index is not valid or there are no active
+ * subscriptions in the slot.
*/
@Nullable
public static int[] getSubscriptionIds(int slotIndex) {
@@ -2290,7 +2299,7 @@
* subscription dynamically in multi-SIM devices.
*
* @param subId which subscription is preferred to for cellular data. If it's
- * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}, it means
+ * {@link SubscriptionManager#DEFAULT_SUBSCRIPTION_ID}, it means
* it's unset and {@link SubscriptionManager#getDefaultDataSubscriptionId()}
* is used to determine which modem is preferred.
* @hide
@@ -2365,19 +2374,40 @@
}
/**
- * Set parent subId by simInfo index
+ * Inform SubscriptionManager that subscriptions in the list are bundled
+ * as a group. Typically it's a primary subscription and an opportunistic
+ * subscription. It should only affect multi-SIM scenarios where primary
+ * and opportunistic subscriptions can be activated together.
+ * Being in the same group means they might be activated or deactivated
+ * together, some of them may be invisible to the users, etc.
*
- * @param parentSubId subId of its parent subscription.
- * @param subId the unique SubscriptionInfo index in database
- * @return the number of records updated
- * @hide
+ * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE}
+ * permission or can manage all subscriptions in the list, according to their
+ * acess rules.
+ *
+ * @param subIdList list of subId that will be in the same group
+ * @return groupUUID a UUID assigned to the subscription group. It returns
+ * null if fails.
*
*/
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public int setParentSubId(int parentSubId, int subId) {
- if (VDBG) logd("[setParentSubId]+ parentSubId:" + parentSubId + " subId:" + subId);
- return setSubscriptionPropertyHelper(subId, "parentSubId",
- (iSub)-> iSub.setParentSubId(parentSubId, subId));
+ public String setSubscriptionGroup(int[] subIdList) {
+ String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>";
+ if (VDBG) {
+ logd("[setSubscriptionGroup]+ subIdList:" + Arrays.toString(subIdList));
+ }
+
+ String groupUUID = null;
+ try {
+ ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ if (iSub != null) {
+ groupUUID = iSub.setSubscriptionGroup(subIdList, pkgForDebug);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return groupUUID;
}
private interface CallISubMethodHelper {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index bd1a0fb..8324f00 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -21,6 +21,7 @@
import static com.android.internal.util.Preconditions.checkNotNull;
import android.Manifest;
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -43,6 +44,7 @@
import android.net.Uri;
import android.os.AsyncTask;
import android.os.BatteryStats;
+import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -52,6 +54,7 @@
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.SystemProperties;
+import android.os.WorkSource;
import android.provider.Settings.SettingNotFoundException;
import android.service.carrier.CarrierIdentifier;
import android.telecom.PhoneAccount;
@@ -4750,37 +4753,42 @@
}
/**
- * Returns all observed cell information from all radios on the
- * device including the primary and neighboring cells. Calling this method does
- * not trigger a call to {@link android.telephony.PhoneStateListener#onCellInfoChanged
- * onCellInfoChanged()}, or change the rate at which
- * {@link android.telephony.PhoneStateListener#onCellInfoChanged
- * onCellInfoChanged()} is called.
+ * Requests all available cell information from all radios on the device including the
+ * camped/registered, serving, and neighboring cells.
*
- *<p>
- * The list can include one or more {@link android.telephony.CellInfoGsm CellInfoGsm},
+ * <p>The response can include one or more {@link android.telephony.CellInfoGsm CellInfoGsm},
* {@link android.telephony.CellInfoCdma CellInfoCdma},
+ * {@link android.telephony.CellInfoTdscdma CellInfoTdscdma},
* {@link android.telephony.CellInfoLte CellInfoLte}, and
* {@link android.telephony.CellInfoWcdma CellInfoWcdma} objects, in any combination.
- * On devices with multiple radios it is typical to see instances of
- * one or more of any these in the list. In addition, zero, one, or more
- * of the returned objects may be considered registered; that is, their
+ * It is typical to see instances of one or more of any these in the list. In addition, zero
+ * or more of the returned objects may be considered registered; that is, their
* {@link android.telephony.CellInfo#isRegistered CellInfo.isRegistered()}
- * methods may return true.
+ * methods may return true, indicating that the cell is being used or would be used for
+ * signaling communication if necessary.
*
- * <p>This method returns valid data for registered cells on devices with
- * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY}. In cases where only
- * partial information is available for a particular CellInfo entry, unavailable fields
- * will be reported as Integer.MAX_VALUE. All reported cells will include at least a
- * valid set of technology-specific identification info and a power level measurement.
+ * <p>Beginning with {@link android.os.Build.VERSION_CODES#Q Android Q},
+ * if this API results in a change of the cached CellInfo, that change will be reported via
+ * {@link android.telephony.PhoneStateListener#onCellInfoChanged onCellInfoChanged()}.
*
- *<p>
- * This method is preferred over using {@link
+ * <p>Apps targeting {@link android.os.Build.VERSION_CODES#Q Android Q} or higher will no
+ * longer trigger a refresh of the cached CellInfo by invoking this API. Instead, those apps
+ * will receive the latest cached results. Apps targeting
+ * {@link android.os.Build.VERSION_CODES#Q Android Q} or higher that wish to request updated
+ * CellInfo should call
+ * {android.telephony.TelephonyManager#requestCellInfoUpdate requestCellInfoUpdate()} and
+ * listen for responses via {@link android.telephony.PhoneStateListener#onCellInfoChanged
+ * onCellInfoChanged()}.
+ *
+ * <p>This method returns valid data for devices with
+ * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY FEATURE_TELEPHONY}. In cases
+ * where only partial information is available for a particular CellInfo entry, unavailable
+ * fields will be reported as {@link android.telephony.CellInfo#UNAVAILABLE}. All reported
+ * cells will include at least a valid set of technology-specific identification info and a
+ * power level measurement.
+ *
+ * <p>This method is preferred over using {@link
* android.telephony.TelephonyManager#getCellLocation getCellLocation()}.
- * However, for older devices, <code>getAllCellInfo()</code> may return
- * null. In these cases, you should call {@link
- * android.telephony.TelephonyManager#getCellLocation getCellLocation()}
- * instead.
*
* @return List of {@link android.telephony.CellInfo}; null if cell
* information is unavailable.
@@ -4791,11 +4799,92 @@
ITelephony telephony = getITelephony();
if (telephony == null)
return null;
- return telephony.getAllCellInfo(getOpPackageName());
+ return telephony.getAllCellInfo(
+ getOpPackageName());
} catch (RemoteException ex) {
- return null;
} catch (NullPointerException ex) {
- return null;
+ }
+ return null;
+ }
+
+ /** Callback for providing asynchronous {@link CellInfo} on request */
+ public abstract static class CellInfoCallback {
+ /**
+ * Response to
+ * {@link android.telephony.TelephonyManager#requestCellInfoUpdate requestCellInfoUpdate()}.
+ *
+ * <p>Invoked when there is a response to
+ * {@link android.telephony.TelephonyManager#requestCellInfoUpdate requestCellInfoUpdate()}
+ * to provide a list of {@link CellInfo}. If no {@link CellInfo} is available then an empty
+ * list will be provided. If an error occurs, null will be provided.
+ *
+ * @param cellInfo a list of {@link CellInfo}, an empty list, or null.
+ *
+ * {@see android.telephony.TelephonyManager#getAllCellInfo getAllCellInfo()}
+ */
+ public abstract void onCellInfo(List<CellInfo> cellInfo);
+ };
+
+ /**
+ * Requests all available cell information from the current subscription for observed
+ * camped/registered, serving, and neighboring cells.
+ *
+ * <p>Any available results from this request will be provided by calls to
+ * {@link android.telephony.PhoneStateListener#onCellInfoChanged onCellInfoChanged()}
+ * for each active subscription.
+ *
+ * @param executor the executor on which callback will be invoked.
+ * @param callback a callback to receive CellInfo.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+ public void requestCellInfoUpdate(
+ @NonNull Executor executor, @NonNull CellInfoCallback callback) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony == null) return;
+ telephony.requestCellInfoUpdate(
+ getSubId(),
+ new ICellInfoCallback.Stub() {
+ public void onCellInfo(List<CellInfo> cellInfo) {
+ Binder.withCleanCallingIdentity(() ->
+ executor.execute(() -> callback.onCellInfo(cellInfo)));
+ }
+ }, getOpPackageName());
+
+ } catch (RemoteException ex) {
+ }
+ }
+
+ /**
+ * Requests all available cell information from the current subscription for observed
+ * camped/registered, serving, and neighboring cells.
+ *
+ * <p>Any available results from this request will be provided by calls to
+ * {@link android.telephony.PhoneStateListener#onCellInfoChanged onCellInfoChanged()}
+ * for each active subscription.
+ *
+ * @param workSource the requestor to whom the power consumption for this should be attributed.
+ * @param executor the executor on which callback will be invoked.
+ * @param callback a callback to receive CellInfo.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {android.Manifest.permission.ACCESS_COARSE_LOCATION,
+ android.Manifest.permission.MODIFY_PHONE_STATE})
+ public void requestCellInfoUpdate(@NonNull WorkSource workSource,
+ @NonNull @CallbackExecutor Executor executor, @NonNull CellInfoCallback callback) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony == null) return;
+ telephony.requestCellInfoUpdateWithWorkSource(
+ getSubId(),
+ new ICellInfoCallback.Stub() {
+ public void onCellInfo(List<CellInfo> cellInfo) {
+ Binder.withCleanCallingIdentity(() ->
+ executor.execute(() -> callback.onCellInfo(cellInfo)));
+ }
+ }, getOpPackageName(), workSource);
+ } catch (RemoteException ex) {
}
}
@@ -6349,7 +6438,6 @@
/**
* Set the preferred network type.
- * Used for device configuration by some CDMA operators.
*
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
@@ -7586,7 +7674,7 @@
* @see SubscriptionManager#getDefaultSubscriptionId()
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public boolean isImsRegistered() {
try {
return getITelephony().isImsRegistered(getSubId());
@@ -7603,7 +7691,7 @@
* @see SubscriptionManager#getDefaultSubscriptionId()
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public boolean isVolteAvailable() {
try {
return getITelephony().isAvailable(getSubId(),
@@ -7622,7 +7710,7 @@
* @return true if VT is available, or false if it is unavailable or unknown.
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public boolean isVideoTelephonyAvailable() {
try {
return getITelephony().isVideoTelephonyAvailable(getSubId());
@@ -7637,7 +7725,7 @@
* @return true if VoWiFi is available, or false if it is unavailable or unknown.
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public boolean isWifiCallingAvailable() {
try {
return getITelephony().isWifiCallingAvailable(getSubId());
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 2e9bffe..4fd7066 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -60,6 +60,7 @@
private static final String V3_FORMAT_REGEX = "^\\[ApnSettingV3\\]\\s*";
private static final String V4_FORMAT_REGEX = "^\\[ApnSettingV4\\]\\s*";
private static final String V5_FORMAT_REGEX = "^\\[ApnSettingV5\\]\\s*";
+ private static final String V6_FORMAT_REGEX = "^\\[ApnSettingV6\\]\\s*";
/**
* Default value for mtu if it's not set. Moved from PhoneConstants.
@@ -268,6 +269,7 @@
private final int mApnSetId;
private boolean mPermanentFailed = false;
+ private final int mCarrierId;
/**
* Returns the MTU size of the mobile interface to which the APN connected.
@@ -596,6 +598,16 @@
return mMvnoType;
}
+ /**
+ * Returns the carrier id for this APN.
+ *
+ * @see Builder#setCarrierId(int)
+ * @return the carrier id
+ */
+ public int getCarrierId() {
+ return mCarrierId;
+ }
+
private ApnSetting(Builder builder) {
this.mEntryName = builder.mEntryName;
this.mApnName = builder.mApnName;
@@ -623,47 +635,53 @@
this.mMvnoType = builder.mMvnoType;
this.mMvnoMatchData = builder.mMvnoMatchData;
this.mApnSetId = builder.mApnSetId;
+ this.mCarrierId = builder.mCarrierId;
}
- /** @hide */
+ /**
+ * @hide
+ */
public static ApnSetting makeApnSetting(int id, String operatorNumeric, String entryName,
String apnName, String proxyAddress, int proxyPort, Uri mmsc,
String mmsProxyAddress, int mmsProxyPort, String user, String password,
int authType, int mApnTypeBitmask, int protocol, int roamingProtocol,
- boolean carrierEnabled, int networkTypeBitmask, int profileId, boolean modemCognitive,
- int maxConns, int waitTime, int maxConnsTime, int mtu, int mvnoType,
- String mvnoMatchData, int apnSetId) {
+ boolean carrierEnabled, int networkTypeBitmask, int profileId,
+ boolean modemCognitive, int maxConns, int waitTime, int maxConnsTime, int mtu,
+ int mvnoType, String mvnoMatchData, int apnSetId, int carrierId) {
return new Builder()
- .setId(id)
- .setOperatorNumeric(operatorNumeric)
- .setEntryName(entryName)
- .setApnName(apnName)
- .setProxyAddress(proxyAddress)
- .setProxyPort(proxyPort)
- .setMmsc(mmsc)
- .setMmsProxyAddress(mmsProxyAddress)
- .setMmsProxyPort(mmsProxyPort)
- .setUser(user)
- .setPassword(password)
- .setAuthType(authType)
- .setApnTypeBitmask(mApnTypeBitmask)
- .setProtocol(protocol)
- .setRoamingProtocol(roamingProtocol)
- .setCarrierEnabled(carrierEnabled)
- .setNetworkTypeBitmask(networkTypeBitmask)
- .setProfileId(profileId)
- .setModemCognitive(modemCognitive)
- .setMaxConns(maxConns)
- .setWaitTime(waitTime)
- .setMaxConnsTime(maxConnsTime)
- .setMtu(mtu)
- .setMvnoType(mvnoType)
- .setMvnoMatchData(mvnoMatchData)
- .setApnSetId(apnSetId)
- .buildWithoutCheck();
+ .setId(id)
+ .setOperatorNumeric(operatorNumeric)
+ .setEntryName(entryName)
+ .setApnName(apnName)
+ .setProxyAddress(proxyAddress)
+ .setProxyPort(proxyPort)
+ .setMmsc(mmsc)
+ .setMmsProxyAddress(mmsProxyAddress)
+ .setMmsProxyPort(mmsProxyPort)
+ .setUser(user)
+ .setPassword(password)
+ .setAuthType(authType)
+ .setApnTypeBitmask(mApnTypeBitmask)
+ .setProtocol(protocol)
+ .setRoamingProtocol(roamingProtocol)
+ .setCarrierEnabled(carrierEnabled)
+ .setNetworkTypeBitmask(networkTypeBitmask)
+ .setProfileId(profileId)
+ .setModemCognitive(modemCognitive)
+ .setMaxConns(maxConns)
+ .setWaitTime(waitTime)
+ .setMaxConnsTime(maxConnsTime)
+ .setMtu(mtu)
+ .setMvnoType(mvnoType)
+ .setMvnoMatchData(mvnoMatchData)
+ .setApnSetId(apnSetId)
+ .setCarrierId(carrierId)
+ .buildWithoutCheck();
}
- /** @hide */
+ /**
+ * @hide
+ */
public static ApnSetting makeApnSetting(int id, String operatorNumeric, String entryName,
String apnName, String proxyAddress, int proxyPort, Uri mmsc,
String mmsProxyAddress, int mmsProxyPort, String user, String password,
@@ -675,10 +693,12 @@
mmsc, mmsProxyAddress, mmsProxyPort, user, password, authType, mApnTypeBitmask,
protocol, roamingProtocol, carrierEnabled, networkTypeBitmask, profileId,
modemCognitive, maxConns, waitTime, maxConnsTime, mtu, mvnoType, mvnoMatchData,
- Carriers.NO_SET_SET);
+ Carriers.NO_SET_SET, TelephonyManager.UNKNOWN_CARRIER_ID);
}
- /** @hide */
+ /**
+ * @hide
+ */
public static ApnSetting makeApnSetting(Cursor cursor) {
final int apnTypesBitmask = getApnTypesBitmaskFromString(
cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.TYPE)));
@@ -688,60 +708,64 @@
final int bearerBitmask = cursor.getInt(cursor.getColumnIndexOrThrow(
Telephony.Carriers.BEARER_BITMASK));
networkTypeBitmask =
- ServiceState.convertBearerBitmaskToNetworkTypeBitmask(bearerBitmask);
+ ServiceState.convertBearerBitmaskToNetworkTypeBitmask(bearerBitmask);
}
return makeApnSetting(
- cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers._ID)),
- cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.NUMERIC)),
- cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.NAME)),
- cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.APN)),
- cursor.getString(
- cursor.getColumnIndexOrThrow(Telephony.Carriers.PROXY)),
- portFromString(cursor.getString(
- cursor.getColumnIndexOrThrow(Telephony.Carriers.PORT))),
- UriFromString(cursor.getString(
- cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSC))),
- cursor.getString(
- cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSPROXY)),
- portFromString(cursor.getString(
- cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSPORT))),
- cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.USER)),
- cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PASSWORD)),
- cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.AUTH_TYPE)),
- apnTypesBitmask,
- getProtocolIntFromString(
- cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PROTOCOL))),
- getProtocolIntFromString(
- cursor.getString(cursor.getColumnIndexOrThrow(
- Telephony.Carriers.ROAMING_PROTOCOL))),
- cursor.getInt(cursor.getColumnIndexOrThrow(
- Telephony.Carriers.CARRIER_ENABLED)) == 1,
- networkTypeBitmask,
- cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.PROFILE_ID)),
- cursor.getInt(cursor.getColumnIndexOrThrow(
- Telephony.Carriers.MODEM_COGNITIVE)) == 1,
- cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MAX_CONNS)),
- cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.WAIT_TIME)),
- cursor.getInt(cursor.getColumnIndexOrThrow(
- Telephony.Carriers.MAX_CONNS_TIME)),
- cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU)),
- getMvnoTypeIntFromString(
- cursor.getString(cursor.getColumnIndexOrThrow(
- Telephony.Carriers.MVNO_TYPE))),
+ cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers._ID)),
+ cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.NUMERIC)),
+ cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.NAME)),
+ cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.APN)),
+ cursor.getString(
+ cursor.getColumnIndexOrThrow(Telephony.Carriers.PROXY)),
+ portFromString(cursor.getString(
+ cursor.getColumnIndexOrThrow(Telephony.Carriers.PORT))),
+ UriFromString(cursor.getString(
+ cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSC))),
+ cursor.getString(
+ cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSPROXY)),
+ portFromString(cursor.getString(
+ cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSPORT))),
+ cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.USER)),
+ cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PASSWORD)),
+ cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.AUTH_TYPE)),
+ apnTypesBitmask,
+ getProtocolIntFromString(
+ cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PROTOCOL))),
+ getProtocolIntFromString(
cursor.getString(cursor.getColumnIndexOrThrow(
- Telephony.Carriers.MVNO_MATCH_DATA)),
- cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.APN_SET_ID)));
+ Telephony.Carriers.ROAMING_PROTOCOL))),
+ cursor.getInt(cursor.getColumnIndexOrThrow(
+ Telephony.Carriers.CARRIER_ENABLED)) == 1,
+ networkTypeBitmask,
+ cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.PROFILE_ID)),
+ cursor.getInt(cursor.getColumnIndexOrThrow(
+ Telephony.Carriers.MODEM_COGNITIVE)) == 1,
+ cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MAX_CONNS)),
+ cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.WAIT_TIME)),
+ cursor.getInt(cursor.getColumnIndexOrThrow(
+ Telephony.Carriers.MAX_CONNS_TIME)),
+ cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU)),
+ getMvnoTypeIntFromString(
+ cursor.getString(cursor.getColumnIndexOrThrow(
+ Telephony.Carriers.MVNO_TYPE))),
+ cursor.getString(cursor.getColumnIndexOrThrow(
+ Telephony.Carriers.MVNO_MATCH_DATA)),
+ cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.APN_SET_ID)),
+ cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.CARRIER_ID)));
}
- /** @hide */
+ /**
+ * @hide
+ */
public static ApnSetting makeApnSetting(ApnSetting apn) {
return makeApnSetting(apn.mId, apn.mOperatorNumeric, apn.mEntryName, apn.mApnName,
- apn.mProxyAddress, apn.mProxyPort, apn.mMmsc, apn.mMmsProxyAddress,
- apn.mMmsProxyPort, apn.mUser, apn.mPassword, apn.mAuthType, apn.mApnTypeBitmask,
- apn.mProtocol, apn.mRoamingProtocol, apn.mCarrierEnabled, apn.mNetworkTypeBitmask,
- apn.mProfileId, apn.mPersistent, apn.mMaxConns, apn.mWaitTime,
- apn.mMaxConnsTime, apn.mMtu, apn.mMvnoType, apn.mMvnoMatchData, apn.mApnSetId);
+ apn.mProxyAddress, apn.mProxyPort, apn.mMmsc, apn.mMmsProxyAddress,
+ apn.mMmsProxyPort, apn.mUser, apn.mPassword, apn.mAuthType, apn.mApnTypeBitmask,
+ apn.mProtocol, apn.mRoamingProtocol, apn.mCarrierEnabled, apn.mNetworkTypeBitmask,
+ apn.mProfileId, apn.mPersistent, apn.mMaxConns, apn.mWaitTime,
+ apn.mMaxConnsTime, apn.mMtu, apn.mMvnoType, apn.mMvnoMatchData, apn.mApnSetId,
+ apn.mCarrierId);
}
/**
@@ -783,6 +807,13 @@
* <profileId>, <modemCognitive>, <maxConns>, <waitTime>, <maxConnsTime>, <mtu>,
* <mvnoType>, <mvnoMatchData>, <networkTypeBitmask>, <apnSetId>
*
+ * v6 format:
+ * [ApnSettingV6] <carrier>, <apn>, <proxy>, <port>, <user>, <password>, <server>,
+ * <mmsc>, <mmsproxy>, <mmsport>, <mcc>, <mnc>, <authtype>,
+ * <type>[| <type>...], <protocol>, <roaming_protocol>, <carrierEnabled>, <bearerBitmask>,
+ * <profileId>, <modemCognitive>, <maxConns>, <waitTime>, <maxConnsTime>, <mtu>,
+ * <mvnoType>, <mvnoMatchData>, <networkTypeBitmask>, <apnSetId>, <carrierId>
+ *
* Note that the strings generated by {@link #toString()} do not contain the username
* and password and thus cannot be read by this method.
*
@@ -795,7 +826,10 @@
int version;
// matches() operates on the whole string, so append .* to the regex.
- if (data.matches(V5_FORMAT_REGEX + ".*")) {
+ if (data.matches(V6_FORMAT_REGEX + ".*")) {
+ version = 6;
+ data = data.replaceFirst(V6_FORMAT_REGEX, "");
+ } else if (data.matches(V5_FORMAT_REGEX + ".*")) {
version = 5;
data = data.replaceFirst(V5_FORMAT_REGEX, "");
} else if (data.matches(V4_FORMAT_REGEX + ".*")) {
@@ -837,6 +871,7 @@
String mvnoType = "";
String mvnoMatchData = "";
int apnSetId = Carriers.NO_SET_SET;
+ int carrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
if (version == 1) {
typeArray = new String[a.length - 13];
System.arraycopy(a, 13, typeArray, 0, a.length - 13);
@@ -880,6 +915,9 @@
if (a.length > 27) {
apnSetId = Integer.parseInt(a[27]);
}
+ if (a.length > 28) {
+ carrierId = Integer.parseInt(a[28]);
+ }
}
// If both bearerBitmask and networkTypeBitmask were specified, bearerBitmask would be
@@ -894,7 +932,8 @@
getApnTypesBitmaskFromString(TextUtils.join(",", typeArray)),
getProtocolIntFromString(protocol), getProtocolIntFromString(roamingProtocol),
carrierEnabled, networkTypeBitmask, profileId, modemCognitive, maxConns, waitTime,
- maxConnsTime, mtu, getMvnoTypeIntFromString(mvnoType), mvnoMatchData, apnSetId);
+ maxConnsTime, mtu, getMvnoTypeIntFromString(mvnoType), mvnoMatchData, apnSetId,
+ carrierId);
}
/**
@@ -1013,7 +1052,10 @@
// TODO - if we have this function we should also have hashCode.
// Also should handle changes in type order and perhaps case-insensitivity.
- /** @hide */
+
+ /**
+ * @hide
+ */
public boolean equals(Object o) {
if (o instanceof ApnSetting == false) {
return false;
@@ -1022,31 +1064,32 @@
ApnSetting other = (ApnSetting) o;
return mEntryName.equals(other.mEntryName)
- && Objects.equals(mId, other.mId)
- && Objects.equals(mOperatorNumeric, other.mOperatorNumeric)
- && Objects.equals(mApnName, other.mApnName)
- && Objects.equals(mProxyAddress, other.mProxyAddress)
- && Objects.equals(mMmsc, other.mMmsc)
- && Objects.equals(mMmsProxyAddress, other.mMmsProxyAddress)
- && Objects.equals(mMmsProxyPort, other.mMmsProxyPort)
- && Objects.equals(mProxyPort, other.mProxyPort)
- && Objects.equals(mUser, other.mUser)
- && Objects.equals(mPassword, other.mPassword)
- && Objects.equals(mAuthType, other.mAuthType)
- && Objects.equals(mApnTypeBitmask, other.mApnTypeBitmask)
- && Objects.equals(mProtocol, other.mProtocol)
- && Objects.equals(mRoamingProtocol, other.mRoamingProtocol)
- && Objects.equals(mCarrierEnabled, other.mCarrierEnabled)
- && Objects.equals(mProfileId, other.mProfileId)
- && Objects.equals(mPersistent, other.mPersistent)
- && Objects.equals(mMaxConns, other.mMaxConns)
- && Objects.equals(mWaitTime, other.mWaitTime)
- && Objects.equals(mMaxConnsTime, other.mMaxConnsTime)
- && Objects.equals(mMtu, other.mMtu)
- && Objects.equals(mMvnoType, other.mMvnoType)
- && Objects.equals(mMvnoMatchData, other.mMvnoMatchData)
- && Objects.equals(mNetworkTypeBitmask, other.mNetworkTypeBitmask)
- && Objects.equals(mApnSetId, other.mApnSetId);
+ && Objects.equals(mId, other.mId)
+ && Objects.equals(mOperatorNumeric, other.mOperatorNumeric)
+ && Objects.equals(mApnName, other.mApnName)
+ && Objects.equals(mProxyAddress, other.mProxyAddress)
+ && Objects.equals(mMmsc, other.mMmsc)
+ && Objects.equals(mMmsProxyAddress, other.mMmsProxyAddress)
+ && Objects.equals(mMmsProxyPort, other.mMmsProxyPort)
+ && Objects.equals(mProxyPort, other.mProxyPort)
+ && Objects.equals(mUser, other.mUser)
+ && Objects.equals(mPassword, other.mPassword)
+ && Objects.equals(mAuthType, other.mAuthType)
+ && Objects.equals(mApnTypeBitmask, other.mApnTypeBitmask)
+ && Objects.equals(mProtocol, other.mProtocol)
+ && Objects.equals(mRoamingProtocol, other.mRoamingProtocol)
+ && Objects.equals(mCarrierEnabled, other.mCarrierEnabled)
+ && Objects.equals(mProfileId, other.mProfileId)
+ && Objects.equals(mPersistent, other.mPersistent)
+ && Objects.equals(mMaxConns, other.mMaxConns)
+ && Objects.equals(mWaitTime, other.mWaitTime)
+ && Objects.equals(mMaxConnsTime, other.mMaxConnsTime)
+ && Objects.equals(mMtu, other.mMtu)
+ && Objects.equals(mMvnoType, other.mMvnoType)
+ && Objects.equals(mMvnoMatchData, other.mMvnoMatchData)
+ && Objects.equals(mNetworkTypeBitmask, other.mNetworkTypeBitmask)
+ && Objects.equals(mApnSetId, other.mApnSetId)
+ && Objects.equals(mCarrierId, other.mCarrierId);
}
/**
@@ -1069,29 +1112,30 @@
ApnSetting other = (ApnSetting) o;
return mEntryName.equals(other.mEntryName)
- && Objects.equals(mOperatorNumeric, other.mOperatorNumeric)
- && Objects.equals(mApnName, other.mApnName)
- && Objects.equals(mProxyAddress, other.mProxyAddress)
- && Objects.equals(mMmsc, other.mMmsc)
- && Objects.equals(mMmsProxyAddress, other.mMmsProxyAddress)
- && Objects.equals(mMmsProxyPort, other.mMmsProxyPort)
- && Objects.equals(mProxyPort, other.mProxyPort)
- && Objects.equals(mUser, other.mUser)
- && Objects.equals(mPassword, other.mPassword)
- && Objects.equals(mAuthType, other.mAuthType)
- && Objects.equals(mApnTypeBitmask, other.mApnTypeBitmask)
- && (isDataRoaming || Objects.equals(mProtocol, other.mProtocol))
- && (!isDataRoaming || Objects.equals(mRoamingProtocol, other.mRoamingProtocol))
- && Objects.equals(mCarrierEnabled, other.mCarrierEnabled)
- && Objects.equals(mProfileId, other.mProfileId)
- && Objects.equals(mPersistent, other.mPersistent)
- && Objects.equals(mMaxConns, other.mMaxConns)
- && Objects.equals(mWaitTime, other.mWaitTime)
- && Objects.equals(mMaxConnsTime, other.mMaxConnsTime)
- && Objects.equals(mMtu, other.mMtu)
- && Objects.equals(mMvnoType, other.mMvnoType)
- && Objects.equals(mMvnoMatchData, other.mMvnoMatchData)
- && Objects.equals(mApnSetId, other.mApnSetId);
+ && Objects.equals(mOperatorNumeric, other.mOperatorNumeric)
+ && Objects.equals(mApnName, other.mApnName)
+ && Objects.equals(mProxyAddress, other.mProxyAddress)
+ && Objects.equals(mMmsc, other.mMmsc)
+ && Objects.equals(mMmsProxyAddress, other.mMmsProxyAddress)
+ && Objects.equals(mMmsProxyPort, other.mMmsProxyPort)
+ && Objects.equals(mProxyPort, other.mProxyPort)
+ && Objects.equals(mUser, other.mUser)
+ && Objects.equals(mPassword, other.mPassword)
+ && Objects.equals(mAuthType, other.mAuthType)
+ && Objects.equals(mApnTypeBitmask, other.mApnTypeBitmask)
+ && (isDataRoaming || Objects.equals(mProtocol, other.mProtocol))
+ && (!isDataRoaming || Objects.equals(mRoamingProtocol, other.mRoamingProtocol))
+ && Objects.equals(mCarrierEnabled, other.mCarrierEnabled)
+ && Objects.equals(mProfileId, other.mProfileId)
+ && Objects.equals(mPersistent, other.mPersistent)
+ && Objects.equals(mMaxConns, other.mMaxConns)
+ && Objects.equals(mWaitTime, other.mWaitTime)
+ && Objects.equals(mMaxConnsTime, other.mMaxConnsTime)
+ && Objects.equals(mMtu, other.mMtu)
+ && Objects.equals(mMvnoType, other.mMvnoType)
+ && Objects.equals(mMvnoMatchData, other.mMvnoMatchData)
+ && Objects.equals(mApnSetId, other.mApnSetId)
+ && Objects.equals(mCarrierId, other.mCarrierId);
}
/**
@@ -1103,22 +1147,23 @@
*/
public boolean similar(ApnSetting other) {
return (!this.canHandleType(TYPE_DUN)
- && !other.canHandleType(TYPE_DUN)
- && Objects.equals(this.mApnName, other.mApnName)
- && !typeSameAny(this, other)
- && xorEquals(this.mProxyAddress, other.mProxyAddress)
- && xorEqualsInt(this.mProxyPort, other.mProxyPort)
- && xorEquals(this.mProtocol, other.mProtocol)
- && xorEquals(this.mRoamingProtocol, other.mRoamingProtocol)
- && Objects.equals(this.mCarrierEnabled, other.mCarrierEnabled)
- && Objects.equals(this.mProfileId, other.mProfileId)
- && Objects.equals(this.mMvnoType, other.mMvnoType)
- && Objects.equals(this.mMvnoMatchData, other.mMvnoMatchData)
- && xorEquals(this.mMmsc, other.mMmsc)
- && xorEquals(this.mMmsProxyAddress, other.mMmsProxyAddress)
- && xorEqualsInt(this.mMmsProxyPort, other.mMmsProxyPort))
- && Objects.equals(this.mNetworkTypeBitmask, other.mNetworkTypeBitmask)
- && Objects.equals(mApnSetId, other.mApnSetId);
+ && !other.canHandleType(TYPE_DUN)
+ && Objects.equals(this.mApnName, other.mApnName)
+ && !typeSameAny(this, other)
+ && xorEquals(this.mProxyAddress, other.mProxyAddress)
+ && xorEqualsInt(this.mProxyPort, other.mProxyPort)
+ && xorEquals(this.mProtocol, other.mProtocol)
+ && xorEquals(this.mRoamingProtocol, other.mRoamingProtocol)
+ && Objects.equals(this.mCarrierEnabled, other.mCarrierEnabled)
+ && Objects.equals(this.mProfileId, other.mProfileId)
+ && Objects.equals(this.mMvnoType, other.mMvnoType)
+ && Objects.equals(this.mMvnoMatchData, other.mMvnoMatchData)
+ && xorEquals(this.mMmsc, other.mMmsc)
+ && xorEquals(this.mMmsProxyAddress, other.mMmsProxyAddress)
+ && xorEqualsInt(this.mMmsProxyPort, other.mMmsProxyPort))
+ && Objects.equals(this.mNetworkTypeBitmask, other.mNetworkTypeBitmask)
+ && Objects.equals(mApnSetId, other.mApnSetId)
+ && Objects.equals(mCarrierId, other.mCarrierId);
}
// Equal or one is null.
@@ -1164,6 +1209,7 @@
apnValue.put(Telephony.Carriers.CARRIER_ENABLED, mCarrierEnabled);
apnValue.put(Telephony.Carriers.MVNO_TYPE, getMvnoTypeStringFromInt(mMvnoType));
apnValue.put(Telephony.Carriers.NETWORK_TYPE_BITMASK, mNetworkTypeBitmask);
+ apnValue.put(Telephony.Carriers.CARRIER_ID, mCarrierId);
return apnValue;
}
@@ -1321,6 +1367,8 @@
dest.writeBoolean(mCarrierEnabled);
dest.writeInt(mMvnoType);
dest.writeInt(mNetworkTypeBitmask);
+ dest.writeInt(mApnSetId);
+ dest.writeInt(mCarrierId);
}
private static ApnSetting readFromParcel(Parcel in) {
@@ -1330,7 +1378,7 @@
final String apnName = in.readString();
final String proxy = in.readString();
final int port = in.readInt();
- final Uri mmsc = (Uri)in.readValue(Uri.class.getClassLoader());
+ final Uri mmsc = (Uri) in.readValue(Uri.class.getClassLoader());
final String mmsProxy = in.readString();
final int mmsPort = in.readInt();
final String user = in.readString();
@@ -1342,11 +1390,13 @@
final boolean carrierEnabled = in.readBoolean();
final int mvnoType = in.readInt();
final int networkTypeBitmask = in.readInt();
+ final int apnSetId = in.readInt();
+ final int carrierId = in.readInt();
return makeApnSetting(id, operatorNumeric, entryName, apnName,
proxy, port, mmsc, mmsProxy, mmsPort, user, password, authType, apnTypesBitmask,
protocol, roamingProtocol, carrierEnabled, networkTypeBitmask, 0, false,
- 0, 0, 0, 0, mvnoType, null);
+ 0, 0, 0, 0, mvnoType, null, apnSetId, carrierId);
}
public static final Parcelable.Creator<ApnSetting> CREATOR =
@@ -1422,6 +1472,7 @@
private int mMvnoType = UNSPECIFIED_INT;
private String mMvnoMatchData;
private int mApnSetId;
+ private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
/**
* Default constructor for Builder.
@@ -1751,6 +1802,19 @@
}
/**
+ * Sets the carrier id for this APN.
+ *
+ * See {@link TelephonyManager#getSimCarrierId()} which provides more background for what a
+ * carrier ID is.
+ *
+ * @param carrierId the carrier id to set for this APN
+ */
+ public Builder setCarrierId(int carrierId) {
+ this.mCarrierId = carrierId;
+ return this;
+ }
+
+ /**
* Builds {@link ApnSetting} from this builder.
*
* @return {@code null} if {@link #setApnName(String)} or {@link #setEntryName(String)}
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index b732d4d..ebf1987 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -186,6 +186,7 @@
* whether the user choses to use eUICC to set up network in SUW.
* @hide
*/
+ @SystemApi
public static final String EXTRA_FORCE_PROVISION =
"android.telephony.euicc.extra.FORCE_PROVISION";
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index c9cf473..e06c372 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -292,7 +292,7 @@
* Create an instance of ImsManager for the subscription id specified.
*
* @param context
- * @param subId The ID of the subscription that this ImsManager will use.
+ * @param subId The ID of the subscription that this ImsMmTelManager will use.
* @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList()
* @throws IllegalArgumentException if the subscription is invalid or
* the subscription ID is not an active subscription.
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
new file mode 100644
index 0000000..916e282
--- /dev/null
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims;
+
+import android.Manifest;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.ims.aidl.IImsConfigCallback;
+import android.telephony.ims.stub.ImsConfigImplBase;
+
+import com.android.internal.telephony.ITelephony;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Manages IMS provisioning and configuration parameters, as well as callbacks for apps to listen
+ * to changes in these configurations.
+ *
+ * Note: IMS provisioning keys are defined per carrier or OEM using OMA-DM or other provisioning
+ * applications and may vary.
+ * @hide
+ */
+@SystemApi
+public class ProvisioningManager {
+
+ /**
+ * Callback for IMS provisioning changes.
+ */
+ public static class Callback {
+
+ private static class CallbackBinder extends IImsConfigCallback.Stub {
+
+ private final Callback mLocalConfigurationCallback;
+ private Executor mExecutor;
+
+ private CallbackBinder(Callback localConfigurationCallback) {
+ mLocalConfigurationCallback = localConfigurationCallback;
+ }
+
+ @Override
+ public final void onIntConfigChanged(int item, int value) {
+ Binder.withCleanCallingIdentity(() ->
+ mExecutor.execute(() ->
+ mLocalConfigurationCallback.onProvisioningIntChanged(item, value)));
+ }
+
+ @Override
+ public final void onStringConfigChanged(int item, String value) {
+ Binder.withCleanCallingIdentity(() ->
+ mExecutor.execute(() ->
+ mLocalConfigurationCallback.onProvisioningStringChanged(item,
+ value)));
+ }
+
+ private void setExecutor(Executor executor) {
+ mExecutor = executor;
+ }
+ }
+
+ private final CallbackBinder mBinder = new CallbackBinder(this);
+
+ /**
+ * Called when a provisioning item has changed.
+ * @param item the IMS provisioning key constant, as defined by the OEM.
+ * @param value the new integer value of the IMS provisioning key.
+ */
+ public void onProvisioningIntChanged(int item, int value) {
+ // Base Implementation
+ }
+
+ /**
+ * Called when a provisioning item has changed.
+ * @param item the IMS provisioning key constant, as defined by the OEM.
+ * @param value the new String value of the IMS configuration constant.
+ */
+ public void onProvisioningStringChanged(int item, String value) {
+ // Base Implementation
+ }
+
+ /**@hide*/
+ public final IImsConfigCallback getBinder() {
+ return mBinder;
+ }
+
+ /**@hide*/
+ public void setExecutor(Executor executor) {
+ mBinder.setExecutor(executor);
+ }
+ }
+
+ private int mSubId;
+
+ /**
+ * Create a new {@link ProvisioningManager} for the subscription specified.
+ * @param context The context that this manager will use.
+ * @param subId The ID of the subscription that this ProvisioningManager will use.
+ * @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList()
+ * @throws IllegalArgumentException if the subscription is invalid or
+ * the subscription ID is not an active subscription.
+ */
+ public static ProvisioningManager createForSubscriptionId(Context context, int subId) {
+ if (!SubscriptionManager.isValidSubscriptionId(subId)
+ || !getSubscriptionManager(context).isActiveSubscriptionId(subId)) {
+ throw new IllegalArgumentException("Invalid subscription ID");
+ }
+
+ return new ProvisioningManager(subId);
+ }
+
+ private ProvisioningManager(int subId) {
+ mSubId = subId;
+ }
+
+ /**
+ * Register a new {@link Callback} to listen to changes to changes in
+ * IMS provisioning. Use {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to
+ * Subscription changed events and call
+ * {@link #unregisterProvisioningChangedCallback(Callback)} to clean up after a
+ * subscription is removed.
+ * @param executor The {@link Executor} to call the callback methods on
+ * @param callback The provisioning callbackto be registered.
+ * @see #unregisterProvisioningChangedCallback(Callback)
+ * @see SubscriptionManager.OnSubscriptionsChangedListener
+ */
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public void registerProvisioningChangedCallback(@CallbackExecutor Executor executor,
+ @NonNull Callback callback) {
+ callback.setExecutor(executor);
+ try {
+ getITelephony().registerImsProvisioningChangedCallback(mSubId,
+ callback.getBinder());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Unregister an existing {@link Callback}. Ensure to call this method when cleaning
+ * up to avoid memory leaks or when the subscription is removed.
+ * @param callback The existing {@link Callback} to be removed.
+ * @see #registerProvisioningChangedCallback(Executor, Callback)
+ */
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public void unregisterProvisioningChangedCallback(@NonNull Callback callback) {
+ try {
+ getITelephony().unregisterImsProvisioningChangedCallback(mSubId,
+ callback.getBinder());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Query for the integer value associated with the provided key.
+ * @param key An integer that represents the provisioning key, which is defined by the OEM.
+ * @return an integer value for the provided key.
+ * @throws IllegalArgumentException if the key provided was invalid.
+ */
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public int getProvisioningIntValue(int key) {
+ try {
+ return getITelephony().getImsProvisioningInt(mSubId, key);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Query for the String value associated with the provided key.
+ * @param key An integer that represents the provisioning key, which is defined by the OEM.
+ * @return a String value for the provided key, or {@code null} if the key doesn't exist.
+ * @throws IllegalArgumentException if the key provided was invalid.
+ */
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public String getProvisioningStringValue(int key) {
+ try {
+ return getITelephony().getImsProvisioningString(mSubId, key);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Set the integer value associated with the provided key.
+ * @param key An integer that represents the provisioning key, which is defined by the OEM.
+ * @param value a integer value for the provided key.
+ * @return the result of setting the configuration value.
+ */
+ @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ public @ImsConfigImplBase.SetConfigResult int setProvisioningIntValue(int key, int value) {
+ try {
+ return getITelephony().setImsProvisioningInt(mSubId, key, value);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Set the String value associated with the provided key.
+ *
+ * @param key An integer that represents the provisioning key, which is defined by the OEM.
+ * @param value a String value for the provided key.
+ * @return the result of setting the configuration value.
+ */
+ @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ public @ImsConfigImplBase.SetConfigResult int setProvisioningStringValue(int key,
+ String value) {
+ try {
+ return getITelephony().setImsProvisioningString(mSubId, key, value);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ private static SubscriptionManager getSubscriptionManager(Context context) {
+ SubscriptionManager manager = context.getSystemService(SubscriptionManager.class);
+ if (manager == null) {
+ throw new RuntimeException("Could not find SubscriptionManager.");
+ }
+ return manager;
+ }
+
+ private static ITelephony getITelephony() {
+ ITelephony binder = ITelephony.Stub.asInterface(
+ ServiceManager.getService(Context.TELEPHONY_SERVICE));
+ if (binder == null) {
+ throw new RuntimeException("Could not find Telephony Service.");
+ }
+ return binder;
+ }
+}
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index 7f69f43..3f22f98 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -211,12 +211,19 @@
* Contains the capabilities defined and supported by an ImsFeature in the form of a bit mask.
* @hide
*/
+ @SystemApi // SystemApi only because it was leaked through type usage in a previous release.
public static class Capabilities {
protected int mCapabilities = 0;
+ /**
+ * @hide
+ */
public Capabilities() {
}
+ /**
+ * @hide
+ */
protected Capabilities(int capabilities) {
mCapabilities = capabilities;
}
@@ -224,6 +231,7 @@
/**
* @param capabilities Capabilities to be added to the configuration in the form of a
* bit mask.
+ * @hide
*/
public void addCapabilities(int capabilities) {
mCapabilities |= capabilities;
@@ -232,6 +240,7 @@
/**
* @param capabilities Capabilities to be removed to the configuration in the form of a
* bit mask.
+ * @hide
*/
public void removeCapabilities(int capabilities) {
mCapabilities &= ~capabilities;
@@ -239,6 +248,7 @@
/**
* @return true if all of the capabilities specified are capable.
+ * @hide
*/
public boolean isCapable(int capabilities) {
return (mCapabilities & capabilities) == capabilities;
@@ -246,6 +256,7 @@
/**
* @return a deep copy of the Capabilites.
+ * @hide
*/
public Capabilities copy() {
return new Capabilities(mCapabilities);
@@ -253,6 +264,7 @@
/**
* @return a bitmask containing the capability flags directly.
+ * @hide
*/
public int getMask() {
return mCapabilities;
diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
index dcd7ea7..321bfff 100644
--- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
@@ -16,9 +16,9 @@
package android.telephony.ims.stub;
+import android.annotation.IntDef;
import android.annotation.SystemApi;
import android.content.Context;
-import android.content.Intent;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.telephony.ims.aidl.IImsConfig;
@@ -28,6 +28,8 @@
import com.android.ims.ImsConfig;
import com.android.internal.annotations.VisibleForTesting;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.HashMap;
@@ -215,41 +217,6 @@
}
/**
- * Callback that the framework uses for receiving Configuration change updates.
- * {@hide}
- */
- public static class Callback extends IImsConfigCallback.Stub {
-
- @Override
- public final void onIntConfigChanged(int item, int value) throws RemoteException {
- onConfigChanged(item, value);
- }
-
- @Override
- public final void onStringConfigChanged(int item, String value) throws RemoteException {
- onConfigChanged(item, value);
- }
-
- /**
- * Called when the IMS configuration has changed.
- * @param item the IMS configuration key constant, as defined in ImsConfig.
- * @param value the new integer value of the IMS configuration constant.
- */
- public void onConfigChanged(int item, int value) {
- // Base Implementation
- }
-
- /**
- * Called when the IMS configuration has changed.
- * @param item the IMS configuration key constant, as defined in ImsConfig.
- * @param value the new String value of the IMS configuration constant.
- */
- public void onConfigChanged(int item, String value) {
- // Base Implementation
- }
- }
-
- /**
* The configuration requested resulted in an unknown result. This may happen if the
* IMS configurations are unavailable.
*/
@@ -263,6 +230,16 @@
*/
public static final int CONFIG_RESULT_FAILED = 1;
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "CONFIG_RESULT_", value = {
+ CONFIG_RESULT_SUCCESS,
+ CONFIG_RESULT_FAILED
+ })
+ public @interface SetConfigResult {}
+
private final RemoteCallbackList<IImsConfigCallback> mCallbacks = new RemoteCallbackList<>();
ImsConfigStub mImsConfigStub;
@@ -279,17 +256,16 @@
}
/**
- * Adds a {@link Callback} to the list of callbacks notified when a value in the configuration
- * changes.
+ * Adds a {@link android.telephony.ims.ProvisioningManager.Callback} to the list of callbacks
+ * notified when a value in the configuration changes.
* @param c callback to add.
*/
private void addImsConfigCallback(IImsConfigCallback c) {
mCallbacks.register(c);
}
/**
- * Removes a {@link Callback} to the list of callbacks notified when a value in the
- * configuration changes.
- *
+ * Removes a {@link android.telephony.ims.ProvisioningManager.Callback} to the list of callbacks
+ * notified when a value in the configuration changes.
* @param c callback to remove.
*/
private void removeImsConfigCallback(IImsConfigCallback c) {
@@ -370,10 +346,9 @@
*
* @param item an integer key.
* @param value an integer containing the configuration value.
- * @return the result of setting the configuration value, defined as either
- * {@link #CONFIG_RESULT_FAILED} or {@link #CONFIG_RESULT_SUCCESS}.
+ * @return the result of setting the configuration value.
*/
- public int setConfig(int item, int value) {
+ public @SetConfigResult int setConfig(int item, int value) {
// Base Implementation - To be overridden.
return CONFIG_RESULT_FAILED;
}
@@ -383,10 +358,9 @@
*
* @param item an integer key.
* @param value a String containing the new configuration value.
- * @return Result of setting the configuration value, defined as either
- * {@link #CONFIG_RESULT_FAILED} or {@link #CONFIG_RESULT_SUCCESS}.
+ * @return Result of setting the configuration value.
*/
- public int setConfig(int item, String value) {
+ public @SetConfigResult int setConfig(int item, String value) {
// Base Implementation - To be overridden.
return CONFIG_RESULT_FAILED;
}
diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java
index 90e9880..71a2174 100644
--- a/telephony/java/com/android/ims/ImsConfig.java
+++ b/telephony/java/com/android/ims/ImsConfig.java
@@ -16,12 +16,17 @@
package com.android.ims;
-import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Looper;
import android.os.RemoteException;
import android.telephony.Rlog;
import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ProvisioningManager;
import android.telephony.ims.aidl.IImsConfig;
-import android.telephony.ims.stub.ImsConfigImplBase;
+import android.telephony.ims.aidl.IImsConfigCallback;
+
+import java.util.concurrent.Executor;
/**
* Provides APIs to get/set the IMS service feature/capability/parameters.
@@ -29,8 +34,10 @@
* 1) Items provisioned by the operator.
* 2) Items configured by user. Mainly service feature class.
*
+ * @deprecated Use {@link ProvisioningManager} to change these configurations in the ImsService.
* @hide
*/
+@Deprecated
public class ImsConfig {
private static final String TAG = "ImsConfig";
private boolean DBG = true;
@@ -46,7 +53,7 @@
/**
* Broadcast action: the configuration was changed
- * @deprecated Use {@link ImsConfig#addConfigCallback(ImsConfigImplBase.Callback)} instead.
+ * @deprecated Use {@link android.telephony.ims.ProvisioningManager.Callback} instead.
* @hide
*/
public static final String ACTION_IMS_CONFIG_CHANGED =
@@ -673,13 +680,25 @@
}
/**
- * Adds a {@link ImsConfigImplBase.Callback} to the ImsService to notify when a Configuration
+ * Adds a {@link ProvisioningManager.Callback} to the ImsService to notify when a Configuration
* item has changed.
*
- * Make sure to call {@link #removeConfigCallback(ImsConfigImplBase.Callback)} when finished
+ * Make sure to call {@link #removeConfigCallback(IImsConfigCallback)} when finished
* using this callback.
*/
- public void addConfigCallback(ImsConfigImplBase.Callback callback) throws ImsException {
+ public void addConfigCallback(ProvisioningManager.Callback callback) throws ImsException {
+ callback.setExecutor(getThreadExecutor());
+ addConfigCallback(callback.getBinder());
+ }
+
+ /**
+ * Adds a {@link IImsConfigCallback} to the ImsService to notify when a Configuration
+ * item has changed.
+ *
+ * Make sure to call {@link #removeConfigCallback(IImsConfigCallback)} when finished
+ * using this callback.
+ */
+ public void addConfigCallback(IImsConfigCallback callback) throws ImsException {
if (DBG) Rlog.d(TAG, "addConfigCallback: " + callback);
try {
miConfig.addImsConfigCallback(callback);
@@ -690,10 +709,9 @@
}
/**
- * Removes a {@link ImsConfigImplBase.Callback} from the ImsService that was previously added
- * by {@link #addConfigCallback(ImsConfigImplBase.Callback)}.
+ * Removes an existing {@link IImsConfigCallback} from the ImsService.
*/
- public void removeConfigCallback(ImsConfigImplBase.Callback callback) throws ImsException {
+ public void removeConfigCallback(IImsConfigCallback callback) throws ImsException {
if (DBG) Rlog.d(TAG, "removeConfigCallback: " + callback);
try {
miConfig.removeImsConfigCallback(callback);
@@ -709,4 +727,11 @@
public boolean isBinderAlive() {
return miConfig.asBinder().isBinderAlive();
}
+
+ private Executor getThreadExecutor() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ return new HandlerExecutor(new Handler(Looper.myLooper()));
+ }
}
diff --git a/telephony/java/com/android/internal/telephony/IApnSourceService.aidl b/telephony/java/com/android/internal/telephony/IApnSourceService.aidl
index 07bb18b..34c9067 100644
--- a/telephony/java/com/android/internal/telephony/IApnSourceService.aidl
+++ b/telephony/java/com/android/internal/telephony/IApnSourceService.aidl
@@ -20,5 +20,5 @@
interface IApnSourceService {
/** Retreive APNs. */
- ContentValues[] getApns();
+ ContentValues[] getApns(int subId);
}
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 4bdec08..bc44519 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -165,13 +165,23 @@
int setOpportunistic(boolean opportunistic, int subId);
/**
- * Set parent subId by simInfo index
+ * Inform SubscriptionManager that subscriptions in the list are bundled
+ * as a group. Typically it's a primary subscription and an opportunistic
+ * subscription. It should only affect multi-SIM scenarios where primary
+ * and opportunistic subscriptions can be activated together.
+ * Being in the same group means they might be activated or deactivated
+ * together, some of them may be invisible to the users, etc.
*
- * @param parentSubId: subId of its parent subscription.
- * @param subId the unique SubscriptionInfo index in database
- * @return the number of records updated
+ * Caller will either have {@link android.Manifest.permission.MODIFY_PHONE_STATE}
+ * permission or can manage all subscriptions in the list, according to their
+ * acess rules.
+ *
+ * @param subIdList list of subId that will be in the same group
+ * @return groupUUID a UUID assigned to the subscription group. It returns
+ * null if fails.
+ *
*/
- int setParentSubId(int parentSubId, int subId);
+ String setSubscriptionGroup(in int[] subIdList, String callingPackage);
/**
* Set which subscription is preferred for cellular data. It's
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 3aaa323..f0e8586 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -22,6 +22,7 @@
import android.os.IBinder;
import android.os.Messenger;
import android.os.ResultReceiver;
+import android.os.WorkSource;
import android.net.NetworkStats;
import android.net.Uri;
import android.service.carrier.CarrierIdentifier;
@@ -30,6 +31,7 @@
import android.telephony.CellInfo;
import android.telephony.ClientRequestStats;
import android.telephony.IccOpenLogicalChannelResponse;
+import android.telephony.ICellInfoCallback;
import android.telephony.ModemActivityInfo;
import android.telephony.NeighboringCellInfo;
import android.telephony.NetworkScanRequest;
@@ -40,6 +42,7 @@
import android.telephony.VisualVoicemailSmsFilterSettings;
import android.telephony.ims.aidl.IImsCapabilityCallback;
import android.telephony.ims.aidl.IImsConfig;
+import android.telephony.ims.aidl.IImsConfigCallback;
import android.telephony.ims.aidl.IImsMmTelFeature;
import android.telephony.ims.aidl.IImsRcsFeature;
import android.telephony.ims.aidl.IImsRegistration;
@@ -506,11 +509,26 @@
int getLteOnCdmaModeForSubscriber(int subId, String callingPackage);
/**
- * Returns the all observed cell information of the device.
+ * Returns all observed cell information of the device.
*/
List<CellInfo> getAllCellInfo(String callingPkg);
/**
+ * Request a cell information update for the specified subscription,
+ * reported via the CellInfoCallback.
+ */
+ void requestCellInfoUpdate(int subId, in ICellInfoCallback cb, String callingPkg);
+
+ /**
+ * Request a cell information update for the specified subscription,
+ * reported via the CellInfoCallback.
+ *
+ * @param workSource the requestor to whom the power consumption for this should be attributed.
+ */
+ void requestCellInfoUpdateWithWorkSource(
+ int subId, in ICellInfoCallback cb, in String callingPkg, in WorkSource ws);
+
+ /**
* Sets minimum time in milli-seconds between onCellInfoChanged
*/
void setCellInfoListRate(int rateInMillis);
@@ -1569,24 +1587,24 @@
/**
* Adds an IMS registration status callback for the subscription id specified.
*/
- oneway void addImsRegistrationCallback(int subId, IImsRegistrationCallback c,
+ void addImsRegistrationCallback(int subId, IImsRegistrationCallback c,
String callingPackage);
/**
* Removes an existing IMS registration status callback for the subscription specified.
*/
- oneway void removeImsRegistrationCallback(int subId, IImsRegistrationCallback c,
+ void removeImsRegistrationCallback(int subId, IImsRegistrationCallback c,
String callingPackage);
/**
* Adds an IMS MmTel capabilities callback for the subscription specified.
*/
- oneway void addMmTelCapabilityCallback(int subId, IImsCapabilityCallback c,
+ void addMmTelCapabilityCallback(int subId, IImsCapabilityCallback c,
String callingPackage);
/**
* Removes an existing IMS MmTel capabilities callback for the subscription specified.
*/
- oneway void removeMmTelCapabilityCallback(int subId, IImsCapabilityCallback c,
+ void removeMmTelCapabilityCallback(int subId, IImsCapabilityCallback c,
String callingPackage);
/**
@@ -1691,4 +1709,34 @@
* Return a list of certs in hex string from loaded carrier privileges access rules.
*/
List<String> getCertsFromCarrierPrivilegeAccessRules(int subId);
+
+ /**
+ * Register an IMS provisioning change callback with Telephony.
+ */
+ void registerImsProvisioningChangedCallback(int subId, IImsConfigCallback callback);
+
+ /**
+ * unregister an existing IMS provisioning change callback.
+ */
+ void unregisterImsProvisioningChangedCallback(int subId, IImsConfigCallback callback);
+
+ /**
+ * Return an integer containing the provisioning value for the specified provisioning key.
+ */
+ int getImsProvisioningInt(int subId, int key);
+
+ /**
+ * return a String containing the provisioning value for the provisioning key specified.
+ */
+ String getImsProvisioningString(int subId, int key);
+
+ /**
+ * Set the integer provisioning value for the provisioning key specified.
+ */
+ int setImsProvisioningInt(int subId, int key, int value);
+
+ /**
+ * Set the String provisioning value for the provisioning key specified.
+ */
+ int setImsProvisioningString(int subId, int key, String value);
}
diff --git a/test-mock/api/current.txt b/test-mock/api/current.txt
index fc9b4c6..f91d74a 100644
--- a/test-mock/api/current.txt
+++ b/test-mock/api/current.txt
@@ -31,6 +31,7 @@
public class MockContext extends android.content.Context {
ctor public MockContext();
+ method public boolean bindIsolatedService(android.content.Intent, android.content.ServiceConnection, int, java.lang.String);
method public boolean bindService(android.content.Intent, android.content.ServiceConnection, int);
method public int checkCallingOrSelfPermission(java.lang.String);
method public int checkCallingOrSelfUriPermission(android.net.Uri, int);
diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java
index fa5b896..66be6d9 100644
--- a/test-mock/src/android/test/mock/MockContext.java
+++ b/test-mock/src/android/test/mock/MockContext.java
@@ -576,6 +576,13 @@
throw new UnsupportedOperationException();
}
+ @Override
+ public boolean bindIsolatedService(Intent service,
+ ServiceConnection conn, int flags,
+ String instanceName) {
+ throw new UnsupportedOperationException();
+ }
+
/** @hide */
@Override
public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
diff --git a/test-runner/src/android/test/IsolatedContext.java b/test-runner/src/android/test/IsolatedContext.java
index 6e4c41e..73db451 100644
--- a/test-runner/src/android/test/IsolatedContext.java
+++ b/test-runner/src/android/test/IsolatedContext.java
@@ -75,6 +75,12 @@
}
@Override
+ public boolean bindIsolatedService(Intent service, ServiceConnection conn, int flags,
+ String instanceName) {
+ return false;
+ }
+
+ @Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
return null;
}
diff --git a/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java
index b9222a8..17486e0 100644
--- a/tests/net/java/android/net/MacAddressTest.java
+++ b/tests/net/java/android/net/MacAddressTest.java
@@ -16,6 +16,7 @@
package android.net;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -24,12 +25,13 @@
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import java.util.Arrays;
-import java.util.Random;
-
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.net.Inet6Address;
+import java.util.Arrays;
+import java.util.Random;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class MacAddressTest {
@@ -285,6 +287,19 @@
MacAddress.fromString("00:00:00:00:00:00")));
}
+ /**
+ * Tests that link-local address generation from MAC is valid.
+ */
+ @Test
+ public void testLinkLocalFromMacGeneration() {
+ MacAddress mac = MacAddress.fromString("52:74:f2:b1:a8:7f");
+ byte[] inet6ll = {(byte) 0xfe, (byte) 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x74,
+ (byte) 0xf2, (byte) 0xff, (byte) 0xfe, (byte) 0xb1, (byte) 0xa8, 0x7f};
+ Inet6Address llv6 = mac.getLinkLocalIpv6FromEui48Mac();
+ assertTrue(llv6.isLinkLocalAddress());
+ assertArrayEquals(inet6ll, llv6.getAddress());
+ }
+
static byte[] toByteArray(int... in) {
byte[] out = new byte[in.length];
for (int i = 0; i < in.length; i++) {
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 0bc5221..583f14a 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -431,7 +431,7 @@
void Debug::DumpResStringPool(const android::ResStringPool* pool, text::Printer* printer) {
using namespace android;
-
+
if (pool->getError() == NO_INIT) {
printer->Print("String pool is unitialized.\n");
return;
@@ -460,7 +460,7 @@
const size_t NS = pool->size();
for (size_t s=0; s<NS; s++) {
String8 str = pool->string8ObjectAt(s);
- printer->Print(StringPrintf("String #%zd : %s\n", s, str.string()));
+ printer->Print(StringPrintf("String #%zd: %s\n", s, str.string()));
}
}
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index dbe5ac5..da22e88 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -718,7 +718,7 @@
// This must be a FileReference.
std::unique_ptr<FileReference> file_ref =
util::make_unique<FileReference>(dst_pool->MakeRef(
- str, StringPool::Context(StringPool::Context::kHighPriority, config)));
+ str, StringPool::Context(StringPool::Context::kHighPriority, config), data));
if (type == ResourceType::kRaw) {
file_ref->type = ResourceFile::Type::kUnknown;
} else if (util::EndsWith(*file_ref->path, ".xml")) {
@@ -730,7 +730,7 @@
}
// There are no styles associated with this string, so treat it as a simple string.
- return util::make_unique<String>(dst_pool->MakeRef(str, StringPool::Context(config)));
+ return util::make_unique<String>(dst_pool->MakeRef(str, StringPool::Context(config), data));
}
} break;
diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp
index 8eabd32..a8c2666 100644
--- a/tools/aapt2/StringPool.cpp
+++ b/tools/aapt2/StringPool.cpp
@@ -165,12 +165,13 @@
return MakeRefImpl(str, Context{}, true);
}
-StringPool::Ref StringPool::MakeRef(const StringPiece& str, const Context& context) {
- return MakeRefImpl(str, context, true);
+StringPool::Ref StringPool::MakeRef(const StringPiece& str, const Context& context,
+ Maybe<size_t> index) {
+ return MakeRefImpl(str, context, true, index);
}
StringPool::Ref StringPool::MakeRefImpl(const StringPiece& str, const Context& context,
- bool unique) {
+ bool unique, Maybe<size_t> index) {
if (unique) {
auto range = indexed_strings_.equal_range(str);
for (auto iter = range.first; iter != range.second; ++iter) {
@@ -180,15 +181,26 @@
}
}
+ const size_t size = strings_.size();
+ // Insert the string at the end of the string vector if no index is specified
+ const size_t insertion_index = index ? index.value() : size;
+
std::unique_ptr<Entry> entry(new Entry());
entry->value = str.to_string();
entry->context = context;
- entry->index_ = strings_.size();
+ entry->index_ = insertion_index;
entry->ref_ = 0;
entry->pool_ = this;
Entry* borrow = entry.get();
- strings_.emplace_back(std::move(entry));
+ if (insertion_index == size) {
+ strings_.emplace_back(std::move(entry));
+ } else {
+ // Allocate enough space for the string at the index
+ strings_.resize(std::max(insertion_index + 1, size));
+ strings_[insertion_index] = std::move(entry);
+ }
+
indexed_strings_.insert(std::make_pair(StringPiece(borrow->value), borrow));
return Ref(borrow);
}
diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h
index 1006ca9..115d5d3 100644
--- a/tools/aapt2/StringPool.h
+++ b/tools/aapt2/StringPool.h
@@ -166,7 +166,8 @@
// Adds a string to the pool, unless it already exists, with a context object that can be used
// when sorting the string pool. Returns a reference to the string in the pool.
- Ref MakeRef(const android::StringPiece& str, const Context& context);
+ Ref MakeRef(const android::StringPiece& str, const Context& context,
+ Maybe<size_t> index = {});
// Adds a string from another string pool. Returns a reference to the string in the string pool.
Ref MakeRef(const Ref& ref);
@@ -210,7 +211,8 @@
static bool Flatten(BigBuffer* out, const StringPool& pool, bool utf8, IDiagnostics* diag);
- Ref MakeRefImpl(const android::StringPiece& str, const Context& context, bool unique);
+ Ref MakeRefImpl(const android::StringPiece& str, const Context& context, bool unique,
+ Maybe<size_t> index = {});
void ReAssignIndices();
std::vector<std::unique_ptr<Entry>> strings_;
diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp
index 9a7238b..648be7d 100644
--- a/tools/aapt2/StringPool_test.cpp
+++ b/tools/aapt2/StringPool_test.cpp
@@ -84,6 +84,24 @@
EXPECT_THAT(ref_c.index(), Eq(2u));
}
+TEST(StringPoolTest, AssignStringIndex) {
+ StringPool pool;
+
+ StringPool::Ref ref_a = pool.MakeRef("0", StringPool::Context{}, 0u);
+ StringPool::Ref ref_b = pool.MakeRef("1", StringPool::Context{}, 1u);
+ StringPool::Ref ref_c = pool.MakeRef("5", StringPool::Context{}, 5u);
+ StringPool::Ref ref_d = pool.MakeRef("2", StringPool::Context{}, 2u);
+ StringPool::Ref ref_e = pool.MakeRef("4", StringPool::Context{}, 4u);
+ StringPool::Ref ref_f = pool.MakeRef("3", StringPool::Context{}, 3u);
+
+ EXPECT_THAT(ref_a.index(), Eq(0u));
+ EXPECT_THAT(ref_b.index(), Eq(1u));
+ EXPECT_THAT(ref_d.index(), Eq(2u));
+ EXPECT_THAT(ref_f.index(), Eq(3u));
+ EXPECT_THAT(ref_e.index(), Eq(4u));
+ EXPECT_THAT(ref_c.index(), Eq(5u));
+}
+
TEST(StringPoolTest, PruneStringsWithNoReferences) {
StringPool pool;
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index 3ea1755..4492f6b 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -57,78 +57,6 @@
Source source_;
};
-bool ConvertApk(IAaptContext* context, unique_ptr<LoadedApk> apk, IApkSerializer* serializer,
- IArchiveWriter* writer) {
- io::IFile* manifest = apk->GetFileCollection()->FindFile(kAndroidManifestPath);
- if (!serializer->SerializeXml(apk->GetManifest(), kAndroidManifestPath, true /*utf16*/, writer,
- (manifest != nullptr && manifest->WasCompressed())
- ? ArchiveEntry::kCompress : 0u)) {
- context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
- << "failed to serialize AndroidManifest.xml");
- return false;
- }
-
- if (apk->GetResourceTable() != nullptr) {
- // The table might be modified by below code.
- auto converted_table = apk->GetResourceTable();
-
- // Resources
- for (const auto& package : converted_table->packages) {
- for (const auto& type : package->types) {
- for (const auto& entry : type->entries) {
- for (const auto& config_value : entry->values) {
- FileReference* file = ValueCast<FileReference>(config_value->value.get());
- if (file != nullptr) {
- if (file->file == nullptr) {
- context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
- << "no file associated with " << *file);
- return false;
- }
-
- if (!serializer->SerializeFile(file, writer)) {
- context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
- << "failed to serialize file " << *file->path);
- return false;
- }
- } // file
- } // config_value
- } // entry
- } // type
- } // package
-
- // Converted resource table
- if (!serializer->SerializeTable(converted_table, writer)) {
- context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
- << "failed to serialize the resource table");
- return false;
- }
- }
-
- // Other files
- std::unique_ptr<io::IFileCollectionIterator> iterator = apk->GetFileCollection()->Iterator();
- while (iterator->HasNext()) {
- io::IFile* file = iterator->Next();
- std::string path = file->GetSource().path;
-
- // Manifest, resource table and resources have already been taken care of.
- if (path == kAndroidManifestPath ||
- path == kApkResourceTablePath ||
- path == kProtoResourceTablePath ||
- path.find("res/") == 0) {
- continue;
- }
-
- if (!io::CopyFileToArchivePreserveCompression(context, file, path, writer)) {
- context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
- << "failed to copy file " << path);
- return false;
- }
- }
-
- return true;
-}
-
-
class BinaryApkSerializer : public IApkSerializer {
public:
BinaryApkSerializer(IAaptContext* context, const Source& source,
@@ -323,12 +251,97 @@
StdErrDiagnostics diag_;
};
+int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer,
+ ApkFormat output_format, TableFlattenerOptions& options) {
+ // Do not change the ordering of strings in the values string pool
+ options.sort_stringpool_entries = false;
+
+ unique_ptr<IApkSerializer> serializer;
+ if (output_format == ApkFormat::kBinary) {
+ serializer.reset(new BinaryApkSerializer(context, apk->GetSource(), options));
+ } else if (output_format == ApkFormat::kProto) {
+ serializer.reset(new ProtoApkSerializer(context, apk->GetSource()));
+ } else {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "Cannot convert APK to unknown format");
+ return 1;
+ }
+
+ io::IFile* manifest = apk->GetFileCollection()->FindFile(kAndroidManifestPath);
+ if (!serializer->SerializeXml(apk->GetManifest(), kAndroidManifestPath, true /*utf16*/,
+ output_writer, (manifest != nullptr && manifest->WasCompressed())
+ ? ArchiveEntry::kCompress : 0u)) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "failed to serialize AndroidManifest.xml");
+ return 1;
+ }
+
+ if (apk->GetResourceTable() != nullptr) {
+ // The table might be modified by below code.
+ auto converted_table = apk->GetResourceTable();
+
+ // Resources
+ for (const auto& package : converted_table->packages) {
+ for (const auto& type : package->types) {
+ for (const auto& entry : type->entries) {
+ for (const auto& config_value : entry->values) {
+ FileReference* file = ValueCast<FileReference>(config_value->value.get());
+ if (file != nullptr) {
+ if (file->file == nullptr) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "no file associated with " << *file);
+ return 1;
+ }
+
+ if (!serializer->SerializeFile(file, output_writer)) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "failed to serialize file " << *file->path);
+ return 1;
+ }
+ } // file
+ } // config_value
+ } // entry
+ } // type
+ } // package
+
+ // Converted resource table
+ if (!serializer->SerializeTable(converted_table, output_writer)) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "failed to serialize the resource table");
+ return 1;
+ }
+ }
+
+ // Other files
+ std::unique_ptr<io::IFileCollectionIterator> iterator = apk->GetFileCollection()->Iterator();
+ while (iterator->HasNext()) {
+ io::IFile* file = iterator->Next();
+ std::string path = file->GetSource().path;
+
+ // Manifest, resource table and resources have already been taken care of.
+ if (path == kAndroidManifestPath ||
+ path == kApkResourceTablePath ||
+ path == kProtoResourceTablePath ||
+ path.find("res/") == 0) {
+ continue;
+ }
+
+ if (!io::CopyFileToArchivePreserveCompression(context, file, path, output_writer)) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "failed to copy file " << path);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
const char* ConvertCommand::kOutputFormatProto = "proto";
const char* ConvertCommand::kOutputFormatBinary = "binary";
int ConvertCommand::Action(const std::vector<std::string>& args) {
if (args.size() != 1) {
- std::cerr << "must supply a single proto APK\n";
+ std::cerr << "must supply a single APK\n";
Usage(&std::cerr);
return 1;
}
@@ -341,34 +354,31 @@
return 1;
}
- Maybe<AppInfo> app_info =
- ExtractAppInfoFromBinaryManifest(*apk->GetManifest(), context.GetDiagnostics());
+ Maybe<AppInfo> app_info = ExtractAppInfoFromBinaryManifest(*apk->GetManifest(),
+ context.GetDiagnostics());
if (!app_info) {
return 1;
}
context.package_ = app_info.value().package;
-
- unique_ptr<IArchiveWriter> writer =
- CreateZipFileArchiveWriter(context.GetDiagnostics(), output_path_);
+ unique_ptr<IArchiveWriter> writer = CreateZipFileArchiveWriter(context.GetDiagnostics(),
+ output_path_);
if (writer == nullptr) {
return 1;
}
- unique_ptr<IApkSerializer> serializer;
+ ApkFormat format;
if (!output_format_ || output_format_.value() == ConvertCommand::kOutputFormatBinary) {
-
- serializer.reset(new BinaryApkSerializer(&context, apk->GetSource(), options_));
+ format = ApkFormat::kBinary;
} else if (output_format_.value() == ConvertCommand::kOutputFormatProto) {
- serializer.reset(new ProtoApkSerializer(&context, apk->GetSource()));
+ format = ApkFormat::kProto;
} else {
- context.GetDiagnostics()->Error(DiagMessage(path)
- << "Invalid value for flag --output-format: "
- << output_format_.value());
+ context.GetDiagnostics()->Error(DiagMessage(path) << "Invalid value for flag --output-format: "
+ << output_format_.value());
return 1;
}
- return ConvertApk(&context, std::move(apk), serializer.get(), writer.get()) ? 0 : 1;
+ return Convert(&context, apk.get(), writer.get(), format, options_);
}
} // namespace aapt
diff --git a/tools/aapt2/cmd/Convert.h b/tools/aapt2/cmd/Convert.h
index fcec23d..6a6719c 100644
--- a/tools/aapt2/cmd/Convert.h
+++ b/tools/aapt2/cmd/Convert.h
@@ -18,6 +18,7 @@
#define AAPT2_CONVERT_H
#include "Command.h"
+#include "LoadedApk.h"
#include "format/binary/TableFlattener.h"
namespace aapt {
@@ -49,6 +50,9 @@
bool verbose_ = false;
};
-}// namespace aapt
+int Convert(IAaptContext* context, LoadedApk* input, IArchiveWriter* output_writer,
+ ApkFormat output_format, TableFlattenerOptions& options);
+
+} // namespace aapt
#endif //AAPT2_CONVERT_H
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 6a7da0c..1b5601d 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -236,6 +236,9 @@
case OutputFormat::kProto: {
pb::XmlNode pb_node;
+ // Strip whitespace text nodes from tha AndroidManifest.xml
+ SerializeXmlOptions options;
+ options.remove_empty_text_nodes = (path == kAndroidManifestPath);
SerializeXmlResourceToPb(xml_res, &pb_node);
return io::CopyProtoToArchive(context, &pb_node, path.to_string(), ArchiveEntry::kCompress,
writer);
@@ -1543,7 +1546,7 @@
bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set, xml::XmlResource* manifest,
ResourceTable* table) {
const bool keep_raw_values = context_->GetPackageType() == PackageType::kStaticLib;
- bool result = FlattenXml(context_, *manifest, "AndroidManifest.xml", keep_raw_values,
+ bool result = FlattenXml(context_, *manifest, kAndroidManifestPath, keep_raw_values,
true /*utf16*/, options_.output_format, writer);
if (!result) {
return false;
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 8a86f63a..6c1a9ba 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -573,15 +573,17 @@
} // namespace
bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) {
- // We must do this before writing the resources, since the string pool IDs may change.
- table->string_pool.Prune();
- table->string_pool.Sort([](const StringPool::Context& a, const StringPool::Context& b) -> int {
- int diff = util::compare(a.priority, b.priority);
- if (diff == 0) {
- diff = a.config.compare(b.config);
- }
- return diff;
- });
+ if (options_.sort_stringpool_entries) {
+ // We must do this before writing the resources, since the string pool IDs may change.
+ table->string_pool.Prune();
+ table->string_pool.Sort([](const StringPool::Context &a, const StringPool::Context &b) -> int {
+ int diff = util::compare(a.priority, b.priority);
+ if (diff == 0) {
+ diff = a.config.compare(b.config);
+ }
+ return diff;
+ });
+ }
// Write the ResTable header.
ChunkWriter table_writer(buffer_);
diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h
index c2e1d4b..635cb21 100644
--- a/tools/aapt2/format/binary/TableFlattener.h
+++ b/tools/aapt2/format/binary/TableFlattener.h
@@ -43,6 +43,9 @@
// Set of whitelisted resource names to avoid altering in key stringpool
std::set<std::string> whitelisted_resources;
+
+ // When true, sort the entries in the values string pool by priority and configuration.
+ bool sort_stringpool_entries = true;
};
class TableFlattener : public IResourceTableConsumer {
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index f1e96d6..ecf34d1 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -621,7 +621,8 @@
pb_src->set_column_number(node.column_number);
}
-void SerializeXmlToPb(const xml::Element& el, pb::XmlNode* out_node) {
+void SerializeXmlToPb(const xml::Element& el, pb::XmlNode* out_node,
+ const SerializeXmlOptions options) {
SerializeXmlCommon(el, out_node);
pb::XmlElement* pb_element = out_node->mutable_element();
@@ -657,7 +658,12 @@
if (const xml::Element* child_el = xml::NodeCast<xml::Element>(child.get())) {
SerializeXmlToPb(*child_el, pb_element->add_child());
} else if (const xml::Text* text_el = xml::NodeCast<xml::Text>(child.get())) {
- pb::XmlNode* pb_child_node = pb_element->add_child();
+ if (options.remove_empty_text_nodes && util::TrimWhitespace(text_el->text).empty()) {
+ // Do not serialize whitespace text nodes if told not to
+ continue;
+ }
+
+ pb::XmlNode *pb_child_node = pb_element->add_child();
SerializeXmlCommon(*text_el, pb_child_node);
pb_child_node->set_text(text_el->text);
} else {
@@ -666,8 +672,9 @@
}
}
-void SerializeXmlResourceToPb(const xml::XmlResource& resource, pb::XmlNode* out_node) {
- SerializeXmlToPb(*resource.root, out_node);
+void SerializeXmlResourceToPb(const xml::XmlResource& resource, pb::XmlNode* out_node,
+ const SerializeXmlOptions options) {
+ SerializeXmlToPb(*resource.root, out_node, options);
}
} // namespace aapt
diff --git a/tools/aapt2/format/proto/ProtoSerialize.h b/tools/aapt2/format/proto/ProtoSerialize.h
index c40e5dd..33ffd18 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.h
+++ b/tools/aapt2/format/proto/ProtoSerialize.h
@@ -30,6 +30,11 @@
namespace aapt {
+struct SerializeXmlOptions {
+ /** Remove text nodes that only contain whitespace. */
+ bool remove_empty_text_nodes = false;
+};
+
// Serializes a Value to its protobuf representation. An optional StringPool will hold the
// source path string.
void SerializeValueToPb(const Value& value, pb::Value* out_value, StringPool* src_pool = nullptr);
@@ -39,10 +44,12 @@
void SerializeItemToPb(const Item& item, pb::Item* out_item);
// Serializes an XML element into its protobuf representation.
-void SerializeXmlToPb(const xml::Element& el, pb::XmlNode* out_node);
+void SerializeXmlToPb(const xml::Element& el, pb::XmlNode* out_node,
+ const SerializeXmlOptions options = {});
// Serializes an XmlResource into its protobuf representation. The ResourceFile is NOT serialized.
-void SerializeXmlResourceToPb(const xml::XmlResource& resource, pb::XmlNode* out_node);
+void SerializeXmlResourceToPb(const xml::XmlResource& resource, pb::XmlNode* out_node,
+ const SerializeXmlOptions options = {});
// Serializes a StringPool into its protobuf representation, which is really just the binary
// ResStringPool representation stuffed into a bytes field.
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index 95dbbeb..1cd2f0b 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -257,6 +257,42 @@
EXPECT_THAT(child_text->text, StrEq("woah there"));
}
+TEST(ProtoSerializeTest, SerializeAndDeserializeXmlTrimEmptyWhitepsace) {
+ xml::Element element;
+ element.line_number = 22;
+ element.column_number = 23;
+ element.name = "element";
+
+ std::unique_ptr<xml::Text> trim_text = util::make_unique<xml::Text>();
+ trim_text->line_number = 25;
+ trim_text->column_number = 3;
+ trim_text->text = " \n ";
+ element.AppendChild(std::move(trim_text));
+
+ std::unique_ptr<xml::Text> keep_text = util::make_unique<xml::Text>();
+ keep_text->line_number = 26;
+ keep_text->column_number = 3;
+ keep_text->text = " hello ";
+ element.AppendChild(std::move(keep_text));
+
+ pb::XmlNode pb_xml;
+ SerializeXmlOptions options;
+ options.remove_empty_text_nodes = true;
+ SerializeXmlToPb(element, &pb_xml, options);
+
+ StringPool pool;
+ xml::Element actual_el;
+ std::string error;
+ ASSERT_TRUE(DeserializeXmlFromPb(pb_xml, &actual_el, &pool, &error));
+ ASSERT_THAT(error, IsEmpty());
+
+ // Only the child that does not consist of only whitespace should remain
+ ASSERT_THAT(actual_el.children, SizeIs(1u));
+ const xml::Text* child_text_keep = xml::NodeCast<xml::Text>(actual_el.children[0].get());
+ ASSERT_THAT(child_text_keep, NotNull());
+ EXPECT_THAT(child_text_keep->text, StrEq( " hello "));
+}
+
TEST(ProtoSerializeTest, SerializeAndDeserializePrimitives) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
std::unique_ptr<ResourceTable> table =
diff --git a/tools/aapt2/io/FileStream_test.cpp b/tools/aapt2/io/FileStream_test.cpp
index 7872738..cc9cd28 100644
--- a/tools/aapt2/io/FileStream_test.cpp
+++ b/tools/aapt2/io/FileStream_test.cpp
@@ -18,7 +18,6 @@
#include "android-base/file.h"
#include "android-base/macros.h"
-#include "android-base/test_utils.h"
#include "test/Test.h"
diff --git a/tools/incident_section_gen/main.cpp b/tools/incident_section_gen/main.cpp
index 0cf1046..3f42275 100644
--- a/tools/incident_section_gen/main.cpp
+++ b/tools/incident_section_gen/main.cpp
@@ -21,6 +21,10 @@
#include <sstream>
#include <string>
+#ifndef FALLTHROUGH_INTENDED
+#define FALLTHROUGH_INTENDED [[fallthrough]]
+#endif
+
using namespace android;
using namespace android::os;
using namespace google::protobuf;
@@ -355,6 +359,7 @@
printPrivacy(fieldName, field, "NULL", fieldDest, fieldName + "_patterns");
break;
}
+ FALLTHROUGH_INTENDED;
// else treat string field as primitive field and goes to default
default:
if (!hasDefaultFlags[i]) printPrivacy(fieldName, field, "NULL", fieldDest, "NULL");
diff --git a/wifi/java/android/net/wifi/INetworkRequestMatchCallback.aidl b/wifi/java/android/net/wifi/INetworkRequestMatchCallback.aidl
index f472a02..d14ec57 100644
--- a/wifi/java/android/net/wifi/INetworkRequestMatchCallback.aidl
+++ b/wifi/java/android/net/wifi/INetworkRequestMatchCallback.aidl
@@ -17,6 +17,7 @@
package android.net.wifi;
import android.net.wifi.INetworkRequestUserSelectionCallback;
+import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
/**
@@ -28,7 +29,9 @@
{
void onUserSelectionCallbackRegistration(in INetworkRequestUserSelectionCallback userSelectionCallback);
- void onMatch(in List<WifiConfiguration> wificonfigurations);
+ void onAbort();
+
+ void onMatch(in List<ScanResult> scanResults);
void onUserSelectionConnectSuccess(in WifiConfiguration wificonfiguration);
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 1fd68ec..3ec8a41 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -33,6 +33,7 @@
import android.net.wifi.WifiActivityEnergyInfo;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiNetworkSuggestion;
import android.os.Messenger;
import android.os.ResultReceiver;
@@ -61,11 +62,9 @@
ParceledListSlice getPrivilegedConfiguredNetworks();
- WifiConfiguration getMatchingWifiConfig(in ScanResult scanResult);
+ List<WifiConfiguration> getAllMatchingWifiConfigs(in List<ScanResult> scanResult);
- List<WifiConfiguration> getAllMatchingWifiConfigs(in ScanResult scanResult);
-
- List<OsuProvider> getMatchingOsuProviders(in ScanResult scanResult);
+ List<OsuProvider> getMatchingOsuProviders(in List<ScanResult> scanResult);
int addOrUpdateNetwork(in WifiConfiguration config, String packageName);
@@ -190,5 +189,9 @@
void registerNetworkRequestMatchCallback(in IBinder binder, in INetworkRequestMatchCallback callback, int callbackIdentifier);
void unregisterNetworkRequestMatchCallback(int callbackIdentifier);
+
+ boolean addNetworkSuggestions(in List<WifiNetworkSuggestion> networkSuggestions, in String packageName);
+
+ boolean removeNetworkSuggestions(in List<WifiNetworkSuggestion> networkSuggestions, in String packageName);
}
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 8fc9b97..64f8adb 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -58,7 +58,7 @@
/**
* Current Version of the Backup Serializer.
*/
- private static final int BACKUP_VERSION = 2;
+ private static final int BACKUP_VERSION = 3;
/** {@hide} */
public static final String ssidVarName = "ssid";
/** {@hide} */
@@ -762,6 +762,13 @@
}
/**
+ * Indicate whther the network is trusted or not. Networks are considered trusted
+ * if the user explicitly allowed this network connection.
+ * @hide
+ */
+ public boolean trusted;
+
+ /**
* Indicates if the creator of this configuration has expressed that it
* should be considered metered.
*
@@ -1638,6 +1645,7 @@
selfAdded = false;
didSelfAdd = false;
ephemeral = false;
+ trusted = true; // Networks are considered trusted by default.
meteredHint = false;
meteredOverride = METERED_OVERRIDE_NONE;
useExternalScores = false;
@@ -1747,10 +1755,11 @@
if (this.selfAdded) sbuf.append(" selfAdded");
if (this.validatedInternetAccess) sbuf.append(" validatedInternetAccess");
if (this.ephemeral) sbuf.append(" ephemeral");
+ if (this.trusted) sbuf.append(" trusted");
if (this.meteredHint) sbuf.append(" meteredHint");
if (this.useExternalScores) sbuf.append(" useExternalScores");
if (this.didSelfAdd || this.selfAdded || this.validatedInternetAccess
- || this.ephemeral || this.meteredHint || this.useExternalScores) {
+ || this.ephemeral || this.trusted || this.meteredHint || this.useExternalScores) {
sbuf.append("\n");
}
if (this.meteredOverride != METERED_OVERRIDE_NONE) {
@@ -2235,6 +2244,7 @@
validatedInternetAccess = source.validatedInternetAccess;
isLegacyPasspointConfig = source.isLegacyPasspointConfig;
ephemeral = source.ephemeral;
+ trusted = source.trusted;
meteredHint = source.meteredHint;
meteredOverride = source.meteredOverride;
useExternalScores = source.useExternalScores;
@@ -2310,6 +2320,7 @@
dest.writeInt(validatedInternetAccess ? 1 : 0);
dest.writeInt(isLegacyPasspointConfig ? 1 : 0);
dest.writeInt(ephemeral ? 1 : 0);
+ dest.writeInt(trusted ? 1 : 0);
dest.writeInt(meteredHint ? 1 : 0);
dest.writeInt(meteredOverride);
dest.writeInt(useExternalScores ? 1 : 0);
@@ -2379,6 +2390,7 @@
config.validatedInternetAccess = in.readInt() != 0;
config.isLegacyPasspointConfig = in.readInt() != 0;
config.ephemeral = in.readInt() != 0;
+ config.trusted = in.readInt() != 0;
config.meteredHint = in.readInt() != 0;
config.meteredOverride = in.readInt();
config.useExternalScores = in.readInt() != 0;
@@ -2420,6 +2432,7 @@
out.writeInt(apChannel);
BackupUtils.writeString(out, preSharedKey);
out.writeInt(getAuthType());
+ out.writeBoolean(hiddenSSID);
return baos.toByteArray();
}
@@ -2442,6 +2455,9 @@
config.apChannel = in.readInt();
config.preSharedKey = BackupUtils.readString(in);
config.allowedKeyManagement.set(in.readInt());
+ if (version >= 3) {
+ config.hiddenSSID = in.readBoolean();
+ }
return config;
}
}
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index e37a856..e5dcef0 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -110,6 +110,8 @@
private boolean mEphemeral;
+ private boolean mTrusted;
+
/**
* Running total count of lost (not ACKed) transmitted unicast data packets.
* @hide
@@ -215,6 +217,7 @@
mMacAddress = source.mMacAddress;
mMeteredHint = source.mMeteredHint;
mEphemeral = source.mEphemeral;
+ mTrusted = source.mTrusted;
txBad = source.txBad;
txRetries = source.txRetries;
txSuccess = source.txSuccess;
@@ -397,6 +400,17 @@
return mEphemeral;
}
+ /** {@hide} */
+ public void setTrusted(boolean trusted) {
+ mTrusted = trusted;
+ }
+
+ /** {@hide} */
+ public boolean isTrusted() {
+ return mTrusted;
+ }
+
+
/** @hide */
@UnsupportedAppUsage
public void setNetworkId(int id) {
@@ -539,6 +553,7 @@
dest.writeString(mMacAddress);
dest.writeInt(mMeteredHint ? 1 : 0);
dest.writeInt(mEphemeral ? 1 : 0);
+ dest.writeInt(mTrusted ? 1 : 0);
dest.writeInt(score);
dest.writeLong(txSuccess);
dest.writeDouble(txSuccessRate);
@@ -573,6 +588,7 @@
info.mMacAddress = in.readString();
info.mMeteredHint = in.readInt() != 0;
info.mEphemeral = in.readInt() != 0;
+ info.mTrusted = in.readInt() != 0;
info.score = in.readInt();
info.txSuccess = in.readLong();
info.txSuccessRate = in.readDouble();
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 954b51f..7aff03c 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -25,7 +25,6 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.UnsupportedAppUsage;
-import android.app.PendingIntent;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.net.ConnectivityManager;
@@ -871,6 +870,28 @@
public static final String ACTION_REQUEST_DISABLE = "android.net.wifi.action.REQUEST_DISABLE";
/**
+ * Directed broadcast intent action indicating that the device has connected to one of the
+ * network suggestions provided by the app. This will be sent post connection to a network
+ * which was created with {@link WifiNetworkConfigBuilder#setIsAppInteractionRequired()} flag
+ * set.
+ * <p>
+ * Note: The broadcast is sent to the app only if it holds either one of
+ * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
+ * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission.
+ *
+ * @see #EXTRA_NETWORK_SUGGESTION
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION =
+ "android.net.wifi.action.WIFI_NETWORK_SUGGESTION_POST_CONNECTION";
+ /**
+ * Sent as as a part of {@link #ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} that holds
+ * an instance of {@link WifiNetworkSuggestion} corresponding to the connected network.
+ */
+ public static final String EXTRA_NETWORK_SUGGESTION =
+ "android.net.wifi.extra.NETWORK_SUGGESTION";
+
+ /**
* Internally used Wi-Fi lock mode representing the case were no locks are held.
* @hide
*/
@@ -1036,7 +1057,7 @@
* @deprecated
* a) See {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} for new
* mechanism to trigger connection to a Wi-Fi network.
- * b) See {@link #addNetworkSuggestions(List, PendingIntent)},
+ * b) See {@link #addNetworkSuggestions(List)},
* {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
* when auto-connecting to wifi.
* <b>Compatibility Note:</b> For applications targeting
@@ -1073,55 +1094,43 @@
}
/**
- * Returns a WifiConfiguration matching this ScanResult
- *
- * @param scanResult scanResult that represents the BSSID
- * @return {@link WifiConfiguration} that matches this BSSID or null
- * @throws UnsupportedOperationException if Passpoint is not enabled on the device.
- * @hide
- */
- @UnsupportedAppUsage
- public WifiConfiguration getMatchingWifiConfig(ScanResult scanResult) {
- try {
- return mService.getMatchingWifiConfig(scanResult);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Return all matching WifiConfigurations for this ScanResult.
+ * Returns all matching WifiConfigurations for a given list of ScanResult.
*
* An empty list will be returned when no configurations are installed or if no configurations
* match the ScanResult.
- *
- * @param scanResult scanResult that represents the BSSID
- * @return A list of {@link WifiConfiguration}
+
+ * @param scanResults a list of scanResult that represents the BSSID
+ * @return A list of {@link WifiConfiguration} that can have duplicate entries.
* @throws UnsupportedOperationException if Passpoint is not enabled on the device.
* @hide
*/
- public List<WifiConfiguration> getAllMatchingWifiConfigs(ScanResult scanResult) {
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public List<WifiConfiguration> getAllMatchingWifiConfigs(
+ @NonNull List<ScanResult> scanResults) {
try {
- return mService.getAllMatchingWifiConfigs(scanResult);
+ return mService.getAllMatchingWifiConfigs(scanResults);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
-
/**
- * Returns a list of Hotspot 2.0 OSU (Online Sign-Up) providers associated with the given AP.
+ * Returns a list of unique Hotspot 2.0 OSU (Online Sign-Up) providers associated with a given
+ * list of ScanResult.
*
* An empty list will be returned if no match is found.
*
- * @param scanResult scanResult that represents the BSSID
- * @return list of {@link OsuProvider}
+ * @param scanResults a list of ScanResult
+ * @return A list of {@link OsuProvider} that does not contain duplicate entries.
* @throws UnsupportedOperationException if Passpoint is not enabled on the device.
* @hide
*/
- public List<OsuProvider> getMatchingOsuProviders(ScanResult scanResult) {
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public List<OsuProvider> getMatchingOsuProviders(List<ScanResult> scanResults) {
try {
- return mService.getMatchingOsuProviders(scanResult);
+ return mService.getMatchingOsuProviders(scanResults);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1146,7 +1155,7 @@
* @deprecated
* a) See {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} for new
* mechanism to trigger connection to a Wi-Fi network.
- * b) See {@link #addNetworkSuggestions(List, PendingIntent)},
+ * b) See {@link #addNetworkSuggestions(List)},
* {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
* when auto-connecting to wifi.
* <b>Compatibility Note:</b> For applications targeting
@@ -1181,7 +1190,7 @@
* @deprecated
* a) See {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} for new
* mechanism to trigger connection to a Wi-Fi network.
- * b) See {@link #addNetworkSuggestions(List, PendingIntent)},
+ * b) See {@link #addNetworkSuggestions(List)},
* {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
* when auto-connecting to wifi.
* <b>Compatibility Note:</b> For applications targeting
@@ -1260,14 +1269,23 @@
@NonNull NetworkRequestUserSelectionCallback userSelectionCallback);
/**
+ * Invoked when the active network request is aborted, either because
+ * <li> The app released the request, OR</li>
+ * <li> Request was overridden by a new request</li>
+ * This signals the end of processing for the current request and should stop the UI
+ * component. No subsequent calls from the UI component will be handled by the platform.
+ */
+ void onAbort();
+
+ /**
* Invoked when a network request initiated by an app matches some networks in scan results.
* This may be invoked multiple times for a single network request as the platform finds new
- * networks in scan results.
+ * matching networks in scan results.
*
- * @param wifiConfigurations List of {@link WifiConfiguration} objects corresponding to the
- * networks matching the request.
+ * @param scanResults List of {@link ScanResult} objects corresponding to the networks
+ * matching the request.
*/
- void onMatch(@NonNull List<WifiConfiguration> wifiConfigurations);
+ void onMatch(@NonNull List<ScanResult> scanResults);
/**
* Invoked on a successful connection with the network that the user selected
@@ -1356,13 +1374,23 @@
}
@Override
- public void onMatch(List<WifiConfiguration> wifiConfigurations) {
+ public void onAbort() {
if (mVerboseLoggingEnabled) {
- Log.v(TAG, "NetworkRequestMatchCallbackProxy: onMatch wificonfigurations: "
- + wifiConfigurations);
+ Log.v(TAG, "NetworkRequestMatchCallbackProxy: onAbort");
}
mHandler.post(() -> {
- mCallback.onMatch(wifiConfigurations);
+ mCallback.onAbort();
+ });
+ }
+
+ @Override
+ public void onMatch(List<ScanResult> scanResults) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "NetworkRequestMatchCallbackProxy: onMatch scanResults: "
+ + scanResults);
+ }
+ mHandler.post(() -> {
+ mCallback.onMatch(scanResults);
});
}
@@ -1451,13 +1479,10 @@
/**
* Provide a list of network suggestions to the device. See {@link WifiNetworkSuggestion}
* for a detailed explanation of the parameters.
- *<p>
- * When the device decides to connect to one of the provided network suggestions, platform fires
- * the associated {@code pendingIntent} if
+ * When the device decides to connect to one of the provided network suggestions, platform sends
+ * a directed broadcast {@link #ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} to the app if
* the network was created with {@link WifiNetworkConfigBuilder#setIsAppInteractionRequired()}
- * flag set and the provided {@code pendingIntent} is non-null.
- *<p>
- * Registration of a non-null pending intent {@code pendingIntent} requires
+ * flag set and the app holds either one of
* {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
* {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission.
*<p>
@@ -1472,19 +1497,17 @@
* suggestion back using this API.</li>
*
* @param networkSuggestions List of network suggestions provided by the app.
- * @param pendingIntent Pending intent to be fired post connection for networks. These will be
- * fired only when connecting to a network that was created with
- * {@link WifiNetworkConfigBuilder#setIsAppInteractionRequired()} flag set.
- * Pending intent must hold a foreground service, else will be rejected.
* @return true on success, false if any of the suggestions match (See
* {@link WifiNetworkSuggestion#equals(Object)} any previously provided suggestions by the app.
* @throws {@link SecurityException} if the caller is missing required permissions.
*/
- public boolean addNetworkSuggestions(
- @NonNull List<WifiNetworkSuggestion> networkSuggestions,
- @Nullable PendingIntent pendingIntent) {
- // TODO(b/115504887): Implementation
- return false;
+ @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE)
+ public boolean addNetworkSuggestions(@NonNull List<WifiNetworkSuggestion> networkSuggestions) {
+ try {
+ return mService.addNetworkSuggestions(networkSuggestions, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
@@ -1500,10 +1523,15 @@
* previously provided by the app. Any matching suggestions are removed from the device and
* will not be considered for any further connection attempts.
*/
+ @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE)
public boolean removeNetworkSuggestions(
@NonNull List<WifiNetworkSuggestion> networkSuggestions) {
- // TODO(b/115504887): Implementation
- return false;
+ try {
+ return mService.removeNetworkSuggestions(
+ networkSuggestions, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -1625,7 +1653,7 @@
* @deprecated
* a) See {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} for new
* mechanism to trigger connection to a Wi-Fi network.
- * b) See {@link #addNetworkSuggestions(List, PendingIntent)},
+ * b) See {@link #addNetworkSuggestions(List)},
* {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
* when auto-connecting to wifi.
* <b>Compatibility Note:</b> For applications targeting
@@ -1669,7 +1697,7 @@
* @deprecated
* a) See {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} for new
* mechanism to trigger connection to a Wi-Fi network.
- * b) See {@link #addNetworkSuggestions(List, PendingIntent)},
+ * b) See {@link #addNetworkSuggestions(List)},
* {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
* when auto-connecting to wifi.
* <b>Compatibility Note:</b> For applications targeting
@@ -1701,7 +1729,7 @@
* @deprecated
* a) See {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} for new
* mechanism to trigger connection to a Wi-Fi network.
- * b) See {@link #addNetworkSuggestions(List, PendingIntent)},
+ * b) See {@link #addNetworkSuggestions(List)},
* {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
* when auto-connecting to wifi.
* <b>Compatibility Note:</b> For applications targeting
@@ -1724,7 +1752,7 @@
* @deprecated
* a) See {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} for new
* mechanism to trigger connection to a Wi-Fi network.
- * b) See {@link #addNetworkSuggestions(List, PendingIntent)},
+ * b) See {@link #addNetworkSuggestions(List)},
* {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
* when auto-connecting to wifi.
* <b>Compatibility Note:</b> For applications targeting
@@ -1749,7 +1777,7 @@
* @deprecated
* a) See {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} for new
* mechanism to trigger connection to a Wi-Fi network.
- * b) See {@link #addNetworkSuggestions(List, PendingIntent)},
+ * b) See {@link #addNetworkSuggestions(List)},
* {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
* when auto-connecting to wifi.
* <b>Compatibility Note:</b> For applications targeting
@@ -1774,7 +1802,7 @@
* @deprecated
* a) See {@link WifiNetworkConfigBuilder#buildNetworkSpecifier()} for new
* mechanism to trigger connection to a Wi-Fi network.
- * b) See {@link #addNetworkSuggestions(List, PendingIntent)},
+ * b) See {@link #addNetworkSuggestions(List)},
* {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
* when auto-connecting to wifi.
* <b>Compatibility Note:</b> For applications targeting
@@ -4256,27 +4284,21 @@
/**
* @return true if this device supports WPA3-Personal SAE
- * @hide
*/
- @SystemApi
public boolean isWpa3SaeSupported() {
return isFeatureSupported(WIFI_FEATURE_WPA3_SAE);
}
/**
* @return true if this device supports WPA3-Enterprise Suite-B-192
- * @hide
*/
- @SystemApi
public boolean isWpa3SuiteBSupported() {
return isFeatureSupported(WIFI_FEATURE_WPA3_SUITE_B);
}
/**
* @return true if this device supports Wi-Fi Enhanced Open (OWE)
- * @hide
*/
- @SystemApi
public boolean isOweSupported() {
return isFeatureSupported(WIFI_FEATURE_OWE);
}
diff --git a/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java
index 55fde4c..955e040 100644
--- a/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java
+++ b/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java
@@ -99,11 +99,7 @@
if (other instanceof WifiNetworkSpecifier) {
return satisfiesNetworkSpecifier((WifiNetworkSpecifier) other);
}
- if (other instanceof WifiNetworkAgentSpecifier) {
- throw new IllegalStateException("WifiNetworkAgentSpecifier instances should never be "
- + "compared");
- }
- return false;
+ return equals(other);
}
/**
@@ -172,7 +168,7 @@
@Override
public String toString() {
StringBuilder sb = new StringBuilder("WifiNetworkAgentSpecifier [");
- sb.append(", WifiConfiguration=").append(
+ sb.append("WifiConfiguration=").append(
mWifiConfiguration == null ? null : mWifiConfiguration.configKey())
.append(", mOriginalRequestorUid=").append(mOriginalRequestorUid)
.append("]");
diff --git a/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java b/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java
index 67e2189..aa8d325 100644
--- a/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java
+++ b/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java
@@ -20,7 +20,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.PendingIntent;
import android.net.MacAddress;
import android.net.NetworkRequest;
import android.net.NetworkSpecifier;
@@ -38,7 +37,7 @@
* <li>See {@link #buildNetworkSpecifier()} for creating a network specifier to use in
* {@link NetworkRequest}.</li>
* <li>See {@link #buildNetworkSuggestion()} for creating a network suggestion to use in
- * {@link WifiManager#addNetworkSuggestions(List, PendingIntent)}.</li>
+ * {@link WifiManager#addNetworkSuggestions(List)}.</li>
*/
public class WifiNetworkConfigBuilder {
private static final String MATCH_ALL_SSID_PATTERN_PATH = ".*";
@@ -242,13 +241,11 @@
/**
* Specifies whether the app needs to log in to a captive portal to obtain Internet access.
* <p>
- * This will dictate if the associated pending intent in
- * {@link WifiManager#addNetworkSuggestions(List, PendingIntent)} will be sent after
- * successfully connecting to the network.
+ * This will dictate if the directed broadcast
+ * {@link WifiManager#ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} will be sent to the app
+ * after successfully connecting to the network.
* Use this for captive portal type networks where the app needs to authenticate the user
* before the device can access the network.
- * This setting will be ignored if the {@code PendingIntent} used to add this network
- * suggestion is null.
* <p>
* <li>Only allowed for creating network suggestion, i.e {@link #buildNetworkSuggestion()}.</li>
* <li>If not set, defaults to false (i.e no app interaction required).</li>
@@ -481,7 +478,7 @@
/**
* Create a network suggestion object use in
- * {@link WifiManager#addNetworkSuggestions(List, PendingIntent)}.
+ * {@link WifiManager#addNetworkSuggestions(List)}.
* See {@link WifiNetworkSuggestion}.
*
* @return Instance of {@link WifiNetworkSuggestion}.
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.aidl b/wifi/java/android/net/wifi/WifiNetworkSuggestion.aidl
new file mode 100644
index 0000000..eb6995f
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.wifi;
+
+parcelable WifiNetworkSuggestion;
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index 04b9cb5..25121e2 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -18,7 +18,6 @@
import static com.android.internal.util.Preconditions.checkNotNull;
-import android.app.PendingIntent;
import android.os.Parcel;
import android.os.Parcelable;
@@ -32,7 +31,7 @@
* of this object.
*<p>
* Apps can provide a list of such networks to the platform using
- * {@link WifiManager#addNetworkSuggestions(List, PendingIntent)}.
+ * {@link WifiManager#addNetworkSuggestions(List)}.
*/
public final class WifiNetworkSuggestion implements Parcelable {
/**
@@ -43,9 +42,6 @@
/**
* Whether app needs to log in to captive portal to obtain Internet access.
- * This will dictate if the associated pending intent in
- * {@link WifiManager#addNetworkSuggestions(List, PendingIntent)} needs to be sent after
- * successfully connecting to the network.
* @hide
*/
public final boolean isAppInteractionRequired;
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index 71d4173..26bdb18 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -20,10 +20,10 @@
import android.net.wifi.hotspot2.pps.HomeSp;
import android.net.wifi.hotspot2.pps.Policy;
import android.net.wifi.hotspot2.pps.UpdateParameter;
+import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
-import android.os.Parcel;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
@@ -467,24 +467,54 @@
}
/**
- * Validate the configuration data.
+ * Validate the R1 configuration data.
*
* @return true on success or false on failure
* @hide
*/
public boolean validate() {
- if (mHomeSp == null || !mHomeSp.validate()) {
- return false;
- }
- if (mCredential == null || !mCredential.validate()) {
- return false;
- }
- if (mPolicy != null && !mPolicy.validate()) {
- return false;
- }
+ // Optional: PerProviderSubscription/<X+>/SubscriptionUpdate
if (mSubscriptionUpdate != null && !mSubscriptionUpdate.validate()) {
return false;
}
+ return validateForCommonR1andR2(true);
+ }
+
+ /**
+ * Validate the R2 configuration data.
+ *
+ * @return true on success or false on failure
+ * @hide
+ */
+ public boolean validateForR2() {
+ // Required: PerProviderSubscription/UpdateIdentifier
+ if (mUpdateIdentifier == Integer.MIN_VALUE) {
+ return false;
+ }
+
+ // Required: PerProviderSubscription/<X+>/SubscriptionUpdate
+ if (mSubscriptionUpdate == null || !mSubscriptionUpdate.validate()) {
+ return false;
+ }
+ return validateForCommonR1andR2(false);
+ }
+
+ private boolean validateForCommonR1andR2(boolean isR1) {
+ // Required: PerProviderSubscription/<X+>/HomeSP
+ if (mHomeSp == null || !mHomeSp.validate()) {
+ return false;
+ }
+
+ // Required: PerProviderSubscription/<X+>/Credential
+ if (mCredential == null || !mCredential.validate(isR1)) {
+ return false;
+ }
+
+ // Optional: PerProviderSubscription/<X+>/Policy
+ if (mPolicy != null && !mPolicy.validate()) {
+ return false;
+ }
+
if (mTrustRootCertList != null) {
for (Map.Entry<String, byte[]> entry : mTrustRootCertList.entrySet()) {
String url = entry.getKey();
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
index e8fcbfd..7689fc3 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
@@ -18,8 +18,8 @@
import android.net.wifi.EAPConstants;
import android.net.wifi.ParcelUtil;
-import android.os.Parcelable;
import android.os.Parcel;
+import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
@@ -1019,10 +1019,11 @@
/**
* Validate the configuration data.
*
+ * @param isR1 {@code true} if the configuration is for R1
* @return true on success or false on failure
* @hide
*/
- public boolean validate() {
+ public boolean validate(boolean isR1) {
if (TextUtils.isEmpty(mRealm)) {
Log.d(TAG, "Missing realm");
return false;
@@ -1035,11 +1036,11 @@
// Verify the credential.
if (mUserCredential != null) {
- if (!verifyUserCredential()) {
+ if (!verifyUserCredential(isR1)) {
return false;
}
} else if (mCertCredential != null) {
- if (!verifyCertCredential()) {
+ if (!verifyCertCredential(isR1)) {
return false;
}
} else if (mSimCredential != null) {
@@ -1081,9 +1082,10 @@
/**
* Verify user credential.
*
+ * @param isR1 {@code true} if credential is for R1
* @return true if user credential is valid, false otherwise.
*/
- private boolean verifyUserCredential() {
+ private boolean verifyUserCredential(boolean isR1) {
if (mUserCredential == null) {
Log.d(TAG, "Missing user credential");
return false;
@@ -1095,7 +1097,10 @@
if (!mUserCredential.validate()) {
return false;
}
- if (mCaCertificate == null) {
+
+ // CA certificate is required for R1 Passpoint profile.
+ // For R2, it is downloaded using cert URL provided in PPS MO after validation completes.
+ if (isR1 && mCaCertificate == null) {
Log.d(TAG, "Missing CA Certificate for user credential");
return false;
}
@@ -1106,9 +1111,10 @@
* Verify certificate credential, which is used for EAP-TLS. This will verify
* that the necessary client key and certificates are provided.
*
+ * @param isR1 {@code true} if credential is for R1
* @return true if certificate credential is valid, false otherwise.
*/
- private boolean verifyCertCredential() {
+ private boolean verifyCertCredential(boolean isR1) {
if (mCertCredential == null) {
Log.d(TAG, "Missing certificate credential");
return false;
@@ -1123,7 +1129,9 @@
}
// Verify required key and certificates for certificate credential.
- if (mCaCertificate == null) {
+ // CA certificate is required for R1 Passpoint profile.
+ // For R2, it is downloaded using cert URL provided in PPS MO after validation completes.
+ if (isR1 && mCaCertificate == null) {
Log.d(TAG, "Missing CA Certificate for certificate credential");
return false;
}
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index e6892be..f58a006 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -491,6 +491,17 @@
/** @hide */
public static final int FACTORY_RESET_SUCCEEDED = BASE + 84;
+ /** @hide */
+ public static final int REQUEST_ONGOING_PEER_CONFIG = BASE + 85;
+ /** @hide */
+ public static final int RESPONSE_ONGOING_PEER_CONFIG = BASE + 86;
+ /** @hide */
+ public static final int SET_ONGOING_PEER_CONFIG = BASE + 87;
+ /** @hide */
+ public static final int SET_ONGOING_PEER_CONFIG_FAILED = BASE + 88;
+ /** @hide */
+ public static final int SET_ONGOING_PEER_CONFIG_SUCCEEDED = BASE + 89;
+
/**
* Create a new WifiP2pManager instance. Applications use
* {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
@@ -680,6 +691,18 @@
}
/**
+ * Interface for callback invocation when ongoing peer info is available
+ * @hide
+ */
+ public interface OngoingPeerInfoListener {
+ /**
+ * The requested ongoing WifiP2pConfig is available
+ * @param peerConfig WifiP2pConfig for current connecting session
+ */
+ void onOngoingPeerAvailable(WifiP2pConfig peerConfig);
+ }
+
+ /**
* A channel that connects the application to the Wifi p2p framework.
* Most p2p operations require a Channel as an argument. An instance of Channel is obtained
* by doing a call on {@link #initialize}
@@ -787,6 +810,7 @@
case SET_CHANNEL_FAILED:
case REPORT_NFC_HANDOVER_FAILED:
case FACTORY_RESET_FAILED:
+ case SET_ONGOING_PEER_CONFIG_FAILED:
if (listener != null) {
((ActionListener) listener).onFailure(message.arg1);
}
@@ -814,6 +838,7 @@
case SET_CHANNEL_SUCCEEDED:
case REPORT_NFC_HANDOVER_SUCCEEDED:
case FACTORY_RESET_SUCCEEDED:
+ case SET_ONGOING_PEER_CONFIG_SUCCEEDED:
if (listener != null) {
((ActionListener) listener).onSuccess();
}
@@ -857,6 +882,13 @@
.onHandoverMessageAvailable(handoverMessage);
}
break;
+ case RESPONSE_ONGOING_PEER_CONFIG:
+ WifiP2pConfig peerConfig = (WifiP2pConfig) message.obj;
+ if (listener != null) {
+ ((OngoingPeerInfoListener) listener)
+ .onOngoingPeerAvailable(peerConfig);
+ }
+ break;
default:
Log.d(TAG, "Ignored " + message);
break;
@@ -1536,6 +1568,7 @@
/**
* Removes all saved p2p groups.
+ *
* @param c is the channel created at {@link #initialize}.
* @param listener for callback on success or failure. Can be null.
* @hide
@@ -1550,4 +1583,37 @@
callingPackage);
}
+ /**
+ * Request saved WifiP2pConfig which used for an ongoing peer connection
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callback when ongoing peer config updated. Can't be null.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
+ public void requestOngoingPeerConfig(@NonNull Channel c,
+ @NonNull OngoingPeerInfoListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(REQUEST_ONGOING_PEER_CONFIG,
+ Binder.getCallingUid(), c.putListener(listener));
+ }
+
+ /**
+ * Set saved WifiP2pConfig which used for an ongoing peer connection
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param config used for change an ongoing peer connection
+ * @param listener for callback when ongoing peer config updated. Can be null.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
+ public void setOngoingPeerConfig(@NonNull Channel c, @NonNull WifiP2pConfig config,
+ @Nullable ActionListener listener) {
+ checkChannel(c);
+ checkP2pConfig(config);
+ c.mAsyncChannel.sendMessage(SET_ONGOING_PEER_CONFIG, 0,
+ c.putListener(listener), config);
+ }
}
diff --git a/wifi/java/com/android/server/wifi/AbstractWifiService.java b/wifi/java/com/android/server/wifi/AbstractWifiService.java
index eede23b..04bc557 100644
--- a/wifi/java/com/android/server/wifi/AbstractWifiService.java
+++ b/wifi/java/com/android/server/wifi/AbstractWifiService.java
@@ -25,11 +25,11 @@
import android.net.wifi.ISoftApCallback;
import android.net.wifi.ITrafficStateCallback;
import android.net.wifi.IWifiManager;
-import android.net.wifi.PasspointManagementObjectDefinition;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiActivityEnergyInfo;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiNetworkSuggestion;
import android.net.wifi.hotspot2.IProvisioningCallback;
import android.net.wifi.hotspot2.OsuProvider;
import android.net.wifi.hotspot2.PasspointConfiguration;
@@ -37,7 +37,6 @@
import android.os.Messenger;
import android.os.ResultReceiver;
import android.os.WorkSource;
-import android.util.Slog;
import java.util.List;
@@ -83,22 +82,51 @@
throw new UnsupportedOperationException();
}
- @Override
+ /**
+ * Returns a WifiConfiguration matching this ScanResult
+ * @param scanResult a single ScanResult Object
+ * @return
+ * @deprecated use {@link #getAllMatchingWifiConfigs(List)} instead.
+ */
+ @Deprecated
public WifiConfiguration getMatchingWifiConfig(ScanResult scanResult) {
throw new UnsupportedOperationException();
}
- @Override
+ /**
+ * Returns all matching WifiConfigurations for this ScanResult.
+ * @param scanResult a single ScanResult Object
+ * @return
+ * @deprecated use {@link #getAllMatchingWifiConfigs(List)} instead.
+ */
+ @Deprecated
public List<WifiConfiguration> getAllMatchingWifiConfigs(ScanResult scanResult) {
throw new UnsupportedOperationException();
}
@Override
+ public List<WifiConfiguration> getAllMatchingWifiConfigs(List<ScanResult> scanResults) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns a list of Hotspot 2.0 OSU (Online Sign-Up) providers associated with the given AP.
+ *
+ * @param scanResult a single ScanResult Object
+ * @return
+ * @deprecated use {@link #getMatchingOsuProviders(List)} instead.
+ */
+ @Deprecated
public List<OsuProvider> getMatchingOsuProviders(ScanResult scanResult) {
throw new UnsupportedOperationException();
}
@Override
+ public List<OsuProvider> getMatchingOsuProviders(List<ScanResult> scanResults) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public int addOrUpdateNetwork(WifiConfiguration config, String packageName) {
throw new UnsupportedOperationException();
}
@@ -412,4 +440,16 @@
public void unregisterNetworkRequestMatchCallback(int callbackIdentifier) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public boolean addNetworkSuggestions(
+ List<WifiNetworkSuggestion> networkSuggestions, String callingPackageName) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean removeNetworkSuggestions(
+ List<WifiNetworkSuggestion> networkSuggestions, String callingPackageName) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index 5f3e1b2..8d97307 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -23,6 +23,7 @@
import static org.junit.Assert.assertTrue;
import android.net.MacAddress;
+import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
import android.os.Parcel;
import android.support.test.filters.SmallTest;
@@ -30,6 +31,9 @@
import org.junit.Before;
import org.junit.Test;
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+
/**
* Unit tests for {@link android.net.wifi.WifiConfiguration}.
*/
@@ -53,6 +57,7 @@
String cookie = "C O.o |<IE";
WifiConfiguration config = new WifiConfiguration();
config.setPasspointManagementObjectTree(cookie);
+ config.trusted = false;
MacAddress macBeforeParcel = config.getOrCreateRandomizedMacAddress();
Parcel parcelW = Parcel.obtain();
config.writeToParcel(parcelW, 0);
@@ -67,6 +72,7 @@
// lacking a useful config.equals, check two fields near the end.
assertEquals(cookie, reconfig.getMoTree());
assertEquals(macBeforeParcel, reconfig.getOrCreateRandomizedMacAddress());
+ assertFalse(reconfig.trusted);
Parcel parcelWW = Parcel.obtain();
reconfig.writeToParcel(parcelWW, 0);
@@ -242,4 +248,30 @@
config.setRandomizedMacAddress(null);
assertEquals(defaultMac, config.getRandomizedMacAddress());
}
+
+ /**
+ * Verifies that the serialization/de-serialization for softap config works.
+ */
+ @Test
+ public void testSoftApConfigBackupAndRestore() throws Exception {
+ WifiConfiguration config = new WifiConfiguration();
+ config.SSID = "TestAP";
+ config.apBand = WifiConfiguration.AP_BAND_5GHZ;
+ config.apChannel = 40;
+ config.allowedKeyManagement.set(KeyMgmt.WPA2_PSK);
+ config.preSharedKey = "TestPsk";
+ config.hiddenSSID = true;
+
+ byte[] data = config.getBytesForBackup();
+ ByteArrayInputStream bais = new ByteArrayInputStream(data);
+ DataInputStream in = new DataInputStream(bais);
+ WifiConfiguration restoredConfig = WifiConfiguration.getWifiConfigFromBackup(in);
+
+ assertEquals(config.SSID, restoredConfig.SSID);
+ assertEquals(config.preSharedKey, restoredConfig.preSharedKey);
+ assertEquals(config.getAuthType(), restoredConfig.getAuthType());
+ assertEquals(config.apBand, restoredConfig.apBand);
+ assertEquals(config.apChannel, restoredConfig.apChannel);
+ assertEquals(config.hiddenSSID, restoredConfig.hiddenSSID);
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/WifiInfoTest.java b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
index f5a8b29..f9fb062 100644
--- a/wifi/tests/src/android/net/wifi/WifiInfoTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import android.os.Parcel;
import android.support.test.filters.SmallTest;
@@ -44,6 +45,7 @@
writeWifiInfo.txRetries = TEST_TX_RETRIES;
writeWifiInfo.txBad = TEST_TX_BAD;
writeWifiInfo.rxSuccess = TEST_RX_SUCCESS;
+ writeWifiInfo.setTrusted(true);
Parcel parcel = Parcel.obtain();
writeWifiInfo.writeToParcel(parcel, 0);
@@ -56,5 +58,6 @@
assertEquals(TEST_TX_RETRIES, readWifiInfo.txRetries);
assertEquals(TEST_TX_BAD, readWifiInfo.txBad);
assertEquals(TEST_RX_SUCCESS, readWifiInfo.rxSuccess);
+ assertTrue(readWifiInfo.isTrusted());
}
}
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index ea41bb3..8fe5af9 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -35,7 +35,20 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyList;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -62,6 +75,7 @@
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.List;
/**
* Unit tests for {@link android.net.wifi.WifiManager}.
@@ -1188,7 +1202,12 @@
mock(INetworkRequestUserSelectionCallback.class);
assertEquals(0, mLooper.dispatchAll());
- callbackCaptor.getValue().onMatch(new ArrayList<WifiConfiguration>());
+
+ callbackCaptor.getValue().onAbort();
+ assertEquals(1, mLooper.dispatchAll());
+ verify(mNetworkRequestMatchCallback).onAbort();
+
+ callbackCaptor.getValue().onMatch(new ArrayList<ScanResult>());
assertEquals(1, mLooper.dispatchAll());
verify(mNetworkRequestMatchCallback).onMatch(anyList());
@@ -1250,4 +1269,42 @@
userSelectionCallbackCaptor.getValue().reject();
verify(iUserSelectionCallback).reject();
}
+
+ /**
+ * Check the call to getAllMatchingWifiConfigs calls getAllMatchingWifiConfigs of WifiService
+ * with the provided a list of ScanResult.
+ */
+ @Test
+ public void testGetAllMatchingWifiConfigs() throws Exception {
+ mWifiManager.getAllMatchingWifiConfigs(new ArrayList<>());
+
+ verify(mWifiService).getAllMatchingWifiConfigs(any(List.class));
+ }
+
+ /**
+ * Check the call to getMatchingOsuProviders calls getMatchingOsuProviders of WifiService
+ * with the provided a list of ScanResult.
+ */
+ @Test
+ public void testGetMatchingOsuProviders() throws Exception {
+ mWifiManager.getMatchingOsuProviders(new ArrayList<>());
+
+ verify(mWifiService).getMatchingOsuProviders(any(List.class));
+ }
+
+ /**
+ * Verify calls to {@link WifiManager#addNetworkSuggestions(List)} and
+ * {@link WifiManager#removeNetworkSuggestions(List)}.
+ */
+ @Test
+ public void addRemoveNetworkSuggestions() throws Exception {
+ when(mWifiService.addNetworkSuggestions(any(List.class), anyString())).thenReturn(true);
+ when(mWifiService.removeNetworkSuggestions(any(List.class), anyString())).thenReturn(true);
+
+ assertTrue(mWifiManager.addNetworkSuggestions(new ArrayList<>()));
+ verify(mWifiService).addNetworkSuggestions(anyList(), eq(TEST_PACKAGE_NAME));
+
+ assertTrue(mWifiManager.removeNetworkSuggestions(new ArrayList<>()));
+ verify(mWifiService).removeNetworkSuggestions(anyList(), eq(TEST_PACKAGE_NAME));
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java
index 1b0007c..f8ab8a2 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java
@@ -23,7 +23,6 @@
import android.net.MacAddress;
import android.net.MatchAllNetworkSpecifier;
import android.net.NetworkRequest;
-import android.net.NetworkSpecifier;
import android.os.Parcel;
import android.os.PatternMatcher;
import android.support.test.filters.SmallTest;
@@ -182,11 +181,10 @@
* Validate NetworkAgentSpecifier matching with itself.
* a) Create network agent specifier 1 for WPA_PSK network
* b) Create network agent specifier 2 with the same params as specifier 1.
- * c) Ensure that invoking {@link NetworkSpecifier#satisfiedBy(NetworkSpecifier)} on 2
- * {@link WifiNetworkAgentSpecifier} throws an exception.
+ * c) Ensure that the agent specifier is satisfied by itself.
*/
- @Test(expected = IllegalStateException.class)
- public void testWifiNetworkAgentSpecifierDoesNotSatisifySame() {
+ @Test
+ public void testWifiNetworkAgentSpecifierDoesSatisifySame() {
WifiNetworkAgentSpecifier specifier1 = createDefaultNetworkAgentSpecifier();
WifiNetworkAgentSpecifier specifier2 = createDefaultNetworkAgentSpecifier();
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index 940adc8..775ce21 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -146,6 +146,7 @@
*/
private static PasspointConfiguration createConfig() {
PasspointConfiguration config = new PasspointConfiguration();
+ config.setUpdateIdentifier(1234);
config.setHomeSp(createHomeSp());
config.setCredential(createCredential());
config.setPolicy(createPolicy());
@@ -273,18 +274,37 @@
@Test
public void validateDefaultConfig() throws Exception {
PasspointConfiguration config = new PasspointConfiguration();
+
assertFalse(config.validate());
+ assertFalse(config.validateForR2());
}
/**
- * Verify that a configuration contained all fields is valid.
+ * Verify that a configuration containing all fields is valid for R1/R2.
*
* @throws Exception
*/
@Test
public void validateFullConfig() throws Exception {
PasspointConfiguration config = createConfig();
+
assertTrue(config.validate());
+ assertTrue(config.validateForR2());
+ }
+
+ /**
+ * Verify that a configuration containing all fields except for UpdateIdentifier is valid for
+ * R1, but invalid for R2.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateFullConfigWithoutUpdateIdentifier() throws Exception {
+ PasspointConfiguration config = createConfig();
+ config.setUpdateIdentifier(Integer.MIN_VALUE);
+
+ assertTrue(config.validate());
+ assertFalse(config.validateForR2());
}
/**
@@ -296,7 +316,9 @@
public void validateConfigWithoutCredential() throws Exception {
PasspointConfiguration config = createConfig();
config.setCredential(null);
+
assertFalse(config.validate());
+ assertFalse(config.validateForR2());
}
/**
@@ -308,12 +330,14 @@
public void validateConfigWithoutHomeSp() throws Exception {
PasspointConfiguration config = createConfig();
config.setHomeSp(null);
+
assertFalse(config.validate());
+ assertFalse(config.validateForR2());
}
/**
* Verify that a configuration without Policy is valid, since Policy configurations
- * are optional (applied for Hotspot 2.0 Release only).
+ * are optional for R1 and R2.
*
* @throws Exception
*/
@@ -321,12 +345,14 @@
public void validateConfigWithoutPolicy() throws Exception {
PasspointConfiguration config = createConfig();
config.setPolicy(null);
+
assertTrue(config.validate());
+ assertTrue(config.validateForR2());
}
/**
- * Verify that a configuration without subscription update is valid, since subscription
- * update configurations are optional (applied for Hotspot 2.0 Release only).
+ * Verify that a configuration without subscription update is valid for R1 and invalid for R2,
+ * since subscription update configuration is only applicable for R2.
*
* @throws Exception
*/
@@ -334,7 +360,9 @@
public void validateConfigWithoutSubscriptionUpdate() throws Exception {
PasspointConfiguration config = createConfig();
config.setSubscriptionUpdate(null);
+
assertTrue(config.validate());
+ assertFalse(config.validateForR2());
}
/**
@@ -352,12 +380,15 @@
trustRootCertList.put(new String(rawUrlBytes, StandardCharsets.UTF_8),
new byte[CERTIFICATE_FINGERPRINT_BYTES]);
config.setTrustRootCertList(trustRootCertList);
+
assertFalse(config.validate());
trustRootCertList = new HashMap<>();
trustRootCertList.put(null, new byte[CERTIFICATE_FINGERPRINT_BYTES]);
config.setTrustRootCertList(trustRootCertList);
+
assertFalse(config.validate());
+ assertFalse(config.validateForR2());
}
/**
@@ -382,6 +413,7 @@
trustRootCertList.put("test.cert.com", null);
config.setTrustRootCertList(trustRootCertList);
assertFalse(config.validate());
+ assertFalse(config.validateForR2());
}
/**
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
index c5ad7de..c07db6c 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
@@ -24,14 +24,13 @@
import android.os.Parcel;
import android.support.test.filters.SmallTest;
+import org.junit.Test;
+
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
-import java.util.Arrays;
-
-import org.junit.Test;
/**
* Unit tests for {@link android.net.wifi.hotspot2.pps.CredentialTest}.
@@ -169,7 +168,12 @@
@Test
public void validateUserCredential() throws Exception {
Credential cred = createCredentialWithUserCredential();
- assertTrue(cred.validate());
+
+ // For R1 validation
+ assertTrue(cred.validate(true));
+
+ // For R2 validation
+ assertTrue(cred.validate(false));
}
/**
@@ -181,7 +185,12 @@
public void validateUserCredentialWithoutCaCert() throws Exception {
Credential cred = createCredentialWithUserCredential();
cred.setCaCertificate(null);
- assertFalse(cred.validate());
+
+ // For R1 validation
+ assertFalse(cred.validate(true));
+
+ // For R2 validation
+ assertTrue(cred.validate(false));
}
/**
@@ -193,7 +202,12 @@
public void validateUserCredentialWithEapTls() throws Exception {
Credential cred = createCredentialWithUserCredential();
cred.getUserCredential().setEapType(EAPConstants.EAP_TLS);
- assertFalse(cred.validate());
+
+ // For R1 validation
+ assertFalse(cred.validate(true));
+
+ // For R2 validation
+ assertFalse(cred.validate(false));
}
@@ -206,7 +220,12 @@
public void validateUserCredentialWithoutRealm() throws Exception {
Credential cred = createCredentialWithUserCredential();
cred.setRealm(null);
- assertFalse(cred.validate());
+
+ // For R1 validation
+ assertFalse(cred.validate(true));
+
+ // For R2 validation
+ assertFalse(cred.validate(false));
}
/**
@@ -218,7 +237,12 @@
public void validateUserCredentialWithoutUsername() throws Exception {
Credential cred = createCredentialWithUserCredential();
cred.getUserCredential().setUsername(null);
- assertFalse(cred.validate());
+
+ // For R1 validation
+ assertFalse(cred.validate(true));
+
+ // For R2 validation
+ assertFalse(cred.validate(false));
}
/**
@@ -230,7 +254,12 @@
public void validateUserCredentialWithoutPassword() throws Exception {
Credential cred = createCredentialWithUserCredential();
cred.getUserCredential().setPassword(null);
- assertFalse(cred.validate());
+
+ // For R1 validation
+ assertFalse(cred.validate(true));
+
+ // For R2 validation
+ assertFalse(cred.validate(false));
}
/**
@@ -242,7 +271,12 @@
public void validateUserCredentialWithoutAuthMethod() throws Exception {
Credential cred = createCredentialWithUserCredential();
cred.getUserCredential().setNonEapInnerMethod(null);
- assertFalse(cred.validate());
+
+ // For R1 validation
+ assertFalse(cred.validate(true));
+
+ // For R2 validation
+ assertFalse(cred.validate(false));
}
/**
@@ -255,7 +289,12 @@
@Test
public void validateCertCredential() throws Exception {
Credential cred = createCredentialWithCertificateCredential();
- assertTrue(cred.validate());
+
+ // For R1 validation
+ assertTrue(cred.validate(true));
+
+ // For R2 validation
+ assertTrue(cred.validate(true));
}
/**
@@ -267,7 +306,12 @@
public void validateCertCredentialWithoutCaCert() throws Exception {
Credential cred = createCredentialWithCertificateCredential();
cred.setCaCertificate(null);
- assertFalse(cred.validate());
+
+ // For R1 validation
+ assertFalse(cred.validate(true));
+
+ // For R2 validation
+ assertTrue(cred.validate(false));
}
/**
@@ -279,7 +323,12 @@
public void validateCertCredentialWithoutClientCertChain() throws Exception {
Credential cred = createCredentialWithCertificateCredential();
cred.setClientCertificateChain(null);
- assertFalse(cred.validate());
+
+ // For R1 validation
+ assertFalse(cred.validate(true));
+
+ // For R2 validation
+ assertFalse(cred.validate(false));
}
/**
@@ -291,7 +340,12 @@
public void validateCertCredentialWithoutClientPrivateKey() throws Exception {
Credential cred = createCredentialWithCertificateCredential();
cred.setClientPrivateKey(null);
- assertFalse(cred.validate());
+
+ // For R1 validation
+ assertFalse(cred.validate(true));
+
+ // For R2 validation
+ assertFalse(cred.validate(false));
}
/**
@@ -304,7 +358,12 @@
public void validateCertCredentialWithMismatchFingerprint() throws Exception {
Credential cred = createCredentialWithCertificateCredential();
cred.getCertCredential().setCertSha256Fingerprint(new byte[32]);
- assertFalse(cred.validate());
+
+ // For R1 validation
+ assertFalse(cred.validate(true));
+
+ // For R2 validation
+ assertFalse(cred.validate(false));
}
/**
@@ -315,7 +374,12 @@
@Test
public void validateSimCredentialWithEapSim() throws Exception {
Credential cred = createCredentialWithSimCredential();
- assertTrue(cred.validate());
+
+ // For R1 validation
+ assertTrue(cred.validate(true));
+
+ // For R2 validation
+ assertTrue(cred.validate(false));
}
/**
@@ -327,7 +391,12 @@
public void validateSimCredentialWithEapAka() throws Exception {
Credential cred = createCredentialWithSimCredential();
cred.getSimCredential().setEapType(EAPConstants.EAP_AKA);
- assertTrue(cred.validate());
+
+ // For R1 validation
+ assertTrue(cred.validate(true));
+
+ // For R2 validation
+ assertTrue(cred.validate(false));
}
/**
@@ -339,7 +408,12 @@
public void validateSimCredentialWithEapAkaPrime() throws Exception {
Credential cred = createCredentialWithSimCredential();
cred.getSimCredential().setEapType(EAPConstants.EAP_AKA_PRIME);
- assertTrue(cred.validate());
+
+ // For R1 validation
+ assertTrue(cred.validate(true));
+
+ // For R2 validation
+ assertTrue(cred.validate(false));
}
/**
@@ -351,7 +425,12 @@
public void validateSimCredentialWithoutIMSI() throws Exception {
Credential cred = createCredentialWithSimCredential();
cred.getSimCredential().setImsi(null);
- assertFalse(cred.validate());
+
+ // For R1 validation
+ assertFalse(cred.validate(true));
+
+ // For R2 validation
+ assertFalse(cred.validate(false));
}
/**
@@ -363,7 +442,12 @@
public void validateSimCredentialWithInvalidIMSI() throws Exception {
Credential cred = createCredentialWithSimCredential();
cred.getSimCredential().setImsi("dummy");
- assertFalse(cred.validate());
+
+ // For R1 validation
+ assertFalse(cred.validate(true));
+
+ // For R2 validation
+ assertFalse(cred.validate(false));
}
/**
@@ -375,7 +459,12 @@
public void validateSimCredentialWithEapTls() throws Exception {
Credential cred = createCredentialWithSimCredential();
cred.getSimCredential().setEapType(EAPConstants.EAP_TLS);
- assertFalse(cred.validate());
+
+ // For R1 validation
+ assertFalse(cred.validate(true));
+
+ // For R2 validation
+ assertFalse(cred.validate(false));
}
/**
@@ -391,7 +480,12 @@
simCredential.setImsi("1234*");
simCredential.setEapType(EAPConstants.EAP_SIM);
cred.setSimCredential(simCredential);
- assertFalse(cred.validate());
+
+ // For R1 validation
+ assertFalse(cred.validate(true));
+
+ // For R2 validation
+ assertFalse(cred.validate(false));
}
/**