diff options
431 files changed, 14147 insertions, 9367 deletions
diff --git a/Android.bp b/Android.bp index abeeb4395015..3095ca5d0052 100644 --- a/Android.bp +++ b/Android.bp @@ -536,6 +536,7 @@ java_defaults { "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", diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java index 35d380232bec..f60cbee62c54 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 @@ public class StaticLayoutPerfTest { } @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 ea6f190ca44c..56433f3e2ad3 100755 --- a/api/current.txt +++ b/api/current.txt @@ -4226,8 +4226,10 @@ package android.app { 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 +5372,7 @@ package android.app { 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 @@ -23250,6 +23253,7 @@ package android.media { 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(); @@ -24464,6 +24468,7 @@ package android.media { } public static final class MediaExtractor.CasInfo { + method public byte[] getPrivateData(); method public android.media.MediaCas.Session getSession(); method public int getSystemId(); } @@ -28980,7 +28985,7 @@ package android.net.wifi { 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); @@ -29000,11 +29005,14 @@ package android.net.wifi { 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(); @@ -29021,9 +29029,11 @@ package android.net.wifi { 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"; @@ -43326,6 +43336,7 @@ package android.telephony { 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>); @@ -43551,6 +43562,7 @@ package android.telephony { 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(); @@ -43558,7 +43570,6 @@ package android.telephony { 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(); @@ -43592,6 +43603,7 @@ package android.telephony { 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>); @@ -43723,6 +43735,7 @@ package android.telephony { 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); @@ -43825,6 +43838,11 @@ package android.telephony { 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); @@ -43913,6 +43931,7 @@ package android.telephony.data { 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(); @@ -43963,6 +43982,7 @@ package android.telephony.data { 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); @@ -54354,6 +54374,9 @@ package android.widget { method public void show(float, float); method public void show(float, float, float, float); method public void update(); + field public static final int SOURCE_BOUND_MAX_IN_SURFACE = 0; // 0x0 + field public static final int SOURCE_BOUND_MAX_IN_VIEW = 1; // 0x1 + field public static final int SOURCE_BOUND_MAX_VISIBLE = 2; // 0x2 } public static class Magnifier.Builder { @@ -54364,6 +54387,7 @@ package android.widget { method public android.widget.Magnifier.Builder setElevation(float); method public android.widget.Magnifier.Builder setForcePositionWithinWindowSystemInsetsBounds(boolean); method public android.widget.Magnifier.Builder setSize(int, int); + method public android.widget.Magnifier.Builder setSourceBounds(int, int, int, int); method public android.widget.Magnifier.Builder setZoom(float); } diff --git a/api/system-current.txt b/api/system-current.txt index fa49f07abf52..81a3aa17e6b1 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -123,6 +123,7 @@ package android { 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 +138,7 @@ package android { 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"; @@ -1205,6 +1207,7 @@ package android.content.pm { 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 +3642,16 @@ package android.net.wifi { 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 +4111,7 @@ package android.os { } 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 +4548,6 @@ package android.provider { 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 +4591,10 @@ package android.provider { 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 +4656,11 @@ package android.security.keystore { 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 +4791,16 @@ package android.service.autofill { } +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 +4868,7 @@ package android.service.euicc { 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 +5480,7 @@ package android.telephony { 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 +5555,10 @@ package android.telephony { 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 +5715,7 @@ package android.telephony { 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 +6010,7 @@ package android.telephony.euicc { 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 +6624,22 @@ package android.telephony.ims { 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 +6707,7 @@ package android.telephony.ims.feature { 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); diff --git a/api/test-current.txt b/api/test-current.txt index 5531014cae75..1c01cf1daf18 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1037,6 +1037,11 @@ package android.security.keystore { 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/screencap/Android.bp b/cmds/screencap/Android.bp new file mode 100644 index 000000000000..248c67589696 --- /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 72e3c56bc6e4..000000000000 --- 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 eb498f596141..a9819972cfc7 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -370,11 +370,9 @@ void StatsLogProcessor::onConfigMetricsReportLocked(const ConfigKey& key, // 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 27685fc108a0..7fa05be29b9d 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -787,21 +787,24 @@ status_t StatsService::cmd_print_logs(int out, const Vector<String8>& args) { } 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 4a5f05fef034..cd4d601a606f 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -73,8 +73,10 @@ public: 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 53d967363765..fa0a01825234 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -27,6 +27,7 @@ import "frameworks/base/core/proto/android/app/job/enums.proto"; 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 @@ message Atom { 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; @@ -148,10 +149,17 @@ message Atom { 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; @@ -192,6 +200,10 @@ message Atom { 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. @@ -486,7 +498,6 @@ message SensorStateChanged { optional State state = 3; } - /** * Logs when GPS state changes. * @@ -503,6 +514,16 @@ message GpsScanStateChanged { 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. @@ -1574,6 +1595,8 @@ message AppCrashOccurred { FOREGROUND = 2; } optional ForegroundState foreground_state = 7; + + optional android.server.ErrorSource error_source = 8; } /** @@ -1592,6 +1615,8 @@ message WTFOccurred { // The pid if available. -1 means not available. optional sint32 pid = 4; + + optional android.server.ErrorSource error_source = 5; } /** @@ -1629,6 +1654,10 @@ message ANROccurred { FOREGROUND = 2; } optional ForegroundState foreground_state = 6; + + optional android.server.ErrorSource error_source = 7; + + optional string package_name = 8; } /** @@ -2131,20 +2160,92 @@ message Notification { } /* + * 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; + // 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 { - UNKNOWN = 0; - CONNECTED = 1; - DISCONNECTED = 2; + START = 1; + STOP = 2; } - // Connected state of a network. - optional State state = 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; } ////////////////////////////////////////////////////////////////////// @@ -2437,7 +2538,8 @@ message ProcessMemoryState { // 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. @@ -2465,7 +2567,8 @@ message NativeProcessMemoryState { // 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. @@ -2473,6 +2576,22 @@ message NativeProcessMemoryState { } /* + * 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 { @@ -3198,3 +3317,63 @@ message CpuTimePerThreadFreq { // Time spent in frequency in milliseconds, since thread start. optional uint32 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 8378ae15c1ef..a375dd659f7c 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -180,6 +180,11 @@ const std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { {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 @@ const std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { {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 febb9229bc95..625294ce5e49 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -156,9 +156,6 @@ LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, 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 @@ LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, 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 @@ LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, 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 @@ LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, 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 @@ LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, 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 @@ LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, con 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 @@ LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, 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/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp index 4244d5bed23b..ac34f4760a12 100644 --- a/cmds/statsd/src/metrics/MetricsManager.cpp +++ b/cmds/statsd/src/metrics/MetricsManager.cpp @@ -77,6 +77,8 @@ MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config, 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 a4672b68f2bc..a31efbd3c8a6 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -83,6 +83,14 @@ public: 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 @@ private: 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/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp index 37a00673959c..59f3f0448e0e 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_VERSION = 2; 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_UID = 4; 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 @@ int64_t UidMap::getAppVersion(int uid, const string& packageName) const { } 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 @@ void UidMap::updateMap(const int64_t& timestamp, const vector<int32_t>& uid, 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::updateMap(const int64_t& timestamp, const vector<int32_t>& uid, } 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 @@ void UidMap::updateApp(const int64_t& timestamp, const String16& app_16, const i // 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 @@ void UidMap::removeApp(const int64_t& timestamp, const String16& app_16, const i 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 @@ void UidMap::removeApp(const int64_t& timestamp, const String16& app_16, const i 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 @@ size_t UidMap::getBytesUsed() const { 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 @@ void UidMap::appendUidMap(const int64_t& timestamp, const ConfigKey& key, 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 @@ void UidMap::appendUidMap(const int64_t& timestamp, const ConfigKey& key, 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 @@ void UidMap::printUidMap(int out) const { 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 4598369f1222..75ff507ef09a 100644 --- a/cmds/statsd/src/packages/UidMap.h +++ b/cmds/statsd/src/packages/UidMap.h @@ -44,12 +44,16 @@ namespace statsd { 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 @@ struct ChangeRecord { 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 @@ public: * 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 @@ public: // 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 5d0f3d1db8c9..32ee5af9ee21 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -233,6 +233,14 @@ message UidMapping { 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 @@ message UidMapping { 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 aa789c799056..f955df29aee0 100644 --- a/cmds/statsd/src/statsd_config.proto +++ b/cmds/statsd/src/statsd_config.proto @@ -409,6 +409,10 @@ message StatsdConfig { 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 4c6671dcd663..2b9528f7d1de 100644 --- a/cmds/statsd/tests/LogEntryMatcher_test.cpp +++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp @@ -148,8 +148,12 @@ TEST(AtomMatcherTest, TestAttributionMatcher) { 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 @@ TEST(AtomMatcherTest, TestNeqAnyStringMatcher) { 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 @@ TEST(AtomMatcherTest, TestEqAnyStringMatcher) { 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 8864252bcf4b..355df2986a0b 100644 --- a/cmds/statsd/tests/StatsLogProcessor_test.cpp +++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp @@ -153,7 +153,8 @@ TEST(StatsLogProcessorTest, TestUidMapHasSnapshot) { // 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 @@ TEST(StatsLogProcessorTest, TestEmptyConfigHasNoUidMap) { // 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 99082cc647f6..f0d9cf188661 100644 --- a/cmds/statsd/tests/UidMap_test.cpp +++ b/cmds/statsd/tests/UidMap_test.cpp @@ -71,14 +71,20 @@ TEST(UidMapTest, TestMatching) { 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 @@ TEST(UidMapTest, TestAddAndRemove) { 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 @@ TEST(UidMapTest, TestAddAndRemove) { 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, TestAddAndRemove) { 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 @@ TEST(UidMapTest, TestUpdateApp) { 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 @@ TEST(UidMapTest, TestOutputIncludesAtLeastOneSnapshot) { 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 @@ TEST(UidMapTest, TestRemovedAppRetained) { 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 @@ TEST(UidMapTest, TestRemovedAppOverGuardrail) { 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 @@ TEST(UidMapTest, TestClearingOutput) { 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 @@ TEST(UidMapTest, TestClearingOutput) { 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 @@ TEST(UidMapTest, TestMemoryComputed) { 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 @@ TEST(UidMapTest, TestMemoryGuardrail) { 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 5c47af797eea..a9841c91ada2 100644 --- a/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp @@ -69,8 +69,10 @@ TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid) { // 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 @@ TEST(AttributionE2eTest, TestAttributionMatchAndSliceByChain) { // 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/PartialBucket_e2e_test.cpp b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp index 3cb553fd9a16..2b0285b37473 100644 --- a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp @@ -132,7 +132,8 @@ TEST(PartialBucketE2eTest, TestCountMetricNoSplitOnNewApp) { 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 @@ TEST(PartialBucketE2eTest, TestCountMetricSplitOnUpgrade) { 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 @@ TEST(PartialBucketE2eTest, TestCountMetricSplitOnRemoval) { 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, TestCountMetricSplitOnRemoval) { 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, TestValueMetricWithoutMinPartialBucket) { 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, TestValueMetricWithMinPartialBucket) { 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, TestGaugeMetricWithoutMinPartialBucket) { 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 60bd4a7e07d9..737408d3591d 100644 --- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp @@ -608,15 +608,6 @@ TEST(GaugeMetricProducerTest, TestPullOnTrigger) { .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 @@ TEST(GaugeMetricProducerTest, TestPullOnTrigger) { 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 @@ TEST(GaugeMetricProducerTest, TestPullOnTrigger) { 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()); - - allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(10); - event->init(); - allData.push_back(event); + EXPECT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size()); + trigger.setElapsedTimestampNs(bucket2StartTimeNs + 1); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger); - 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() - ->second.back() - .mGaugeAtoms[0] - .mFields->begin() - ->mValue.int_value); + EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms.size()); EXPECT_EQ(4, gaugeProducer.mPastBuckets.begin() ->second.back() - .mGaugeAtoms[1] + .mGaugeAtoms[0] .mFields->begin() ->mValue.int_value); EXPECT_EQ(5, gaugeProducer.mPastBuckets.begin() ->second.back() - .mGaugeAtoms[2] + .mGaugeAtoms[1] .mFields->begin() ->mValue.int_value); } @@ -731,7 +708,8 @@ TEST(GaugeMetricProducerTest, TestRemoveDimensionInOutput) { 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 @@ TEST(GaugeMetricProducerTest, TestRemoveDimensionInOutput) { 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 851e35b5e9f0..39b327c3674b 100644 --- a/config/hiddenapi-light-greylist.txt +++ b/config/hiddenapi-light-greylist.txt @@ -1320,18 +1320,6 @@ Landroid/R$styleable;->Window_windowFrame:I 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/KeymasterIntArgument;->value:I 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; diff --git a/config/preloaded-classes b/config/preloaded-classes index 14597ee919bf..550e795bd1e6 100644 --- a/config/preloaded-classes +++ b/config/preloaded-classes @@ -6171,6 +6171,7 @@ libcore.reflect.ParameterizedTypeImpl 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$CleanerRunner 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 0e5b976f4301..1edd7f5e429d 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -161,6 +161,13 @@ public abstract class ActivityManagerInternal { 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 cfaeec9096ed..ae632915dd2d 100644 --- a/core/java/android/app/AppComponentFactory.java +++ b/core/java/android/app/AppComponentFactory.java @@ -20,6 +20,7 @@ import android.annotation.Nullable; 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 @@ import android.content.Intent; 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 @@ public class AppComponentFactory { 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 2be5dc966746..9e109c534ba9 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1625,9 +1625,6 @@ public class AppOpsManager { 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 +1637,24 @@ public class AppOpsManager { 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 fcd9a0511265..8bb704d76e85 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -2277,6 +2277,15 @@ public class ApplicationPackageManager extends PackageManager { } @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/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index bd9cf6dda7ff..362f4aedc900 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -88,6 +88,8 @@ interface INotificationManager 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. diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index b827d01314ce..da4f77baac41 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -117,6 +117,7 @@ public final class LoadedApk { 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 @@ public final class LoadedApk { 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 @@ public final class LoadedApk { 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 @@ public final class LoadedApk { getClassLoader()); } } - mAppComponentFactory = createAppFactory(aInfo, mClassLoader); + mAppComponentFactory = createAppFactory(aInfo, mDefaultClassLoader); } private void setApplicationInfo(ApplicationInfo aInfo) { @@ -633,11 +641,12 @@ public final class LoadedApk { } 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 @@ public final class LoadedApk { // 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 @@ public final class LoadedApk { mAppComponentFactory = AppComponentFactory.DEFAULT; } + if (mClassLoader == null) { + mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader); + } + return; } @@ -741,16 +754,16 @@ public final class LoadedApk { ", 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 @@ public final class LoadedApk { // 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 @@ public final class LoadedApk { 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 @@ public final class LoadedApk { 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 @@ public final class LoadedApk { 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 df37a02047b5..450efdf16656 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -207,7 +207,8 @@ public class Notification implements Parcelable 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 class Notification implements Parcelable */ 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 @@ public class Notification implements Parcelable 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 @@ public class Notification implements Parcelable 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 @@ public class Notification implements Parcelable 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 @@ public class Notification implements Parcelable 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 000000000000..5fea8ef92bd2 --- /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 d1492433a8db..9df4fff1e776 100644 --- a/core/java/android/app/ProcessMemoryState.java +++ b/core/java/android/app/ProcessMemoryState.java @@ -32,6 +32,7 @@ public final class ProcessMemoryState implements Parcelable { 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 4de1dfcc12ba..949cdd6878c0 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -42,6 +42,7 @@ import android.graphics.ImageDecoder.Source; 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 @@ public abstract class ContentResolver { 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 03eba7efea91..004417b80e26 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4335,6 +4335,16 @@ public abstract class Context { /** * 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/Intent.java b/core/java/android/content/Intent.java index 02f38a790570..e9b240448eed 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -7508,7 +7508,7 @@ public class Intent implements Parcelable, Cloneable { * * @see #putExtra(String, String) */ - public String getStringExtra(String name) { + public @Nullable String getStringExtra(String name) { return mExtras == null ? null : mExtras.getString(name); } @@ -7522,7 +7522,7 @@ public class Intent implements Parcelable, Cloneable { * * @see #putExtra(String, CharSequence) */ - public CharSequence getCharSequenceExtra(String name) { + public @Nullable CharSequence getCharSequenceExtra(String name) { return mExtras == null ? null : mExtras.getCharSequence(name); } @@ -7536,7 +7536,7 @@ public class Intent implements Parcelable, Cloneable { * * @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 +7550,7 @@ public class Intent implements Parcelable, Cloneable { * * @see #putExtra(String, Parcelable[]) */ - public Parcelable[] getParcelableArrayExtra(String name) { + public @Nullable Parcelable[] getParcelableArrayExtra(String name) { return mExtras == null ? null : mExtras.getParcelableArray(name); } @@ -7565,7 +7565,7 @@ public class Intent implements Parcelable, Cloneable { * * @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 +7579,7 @@ public class Intent implements Parcelable, Cloneable { * * @see #putExtra(String, Serializable) */ - public Serializable getSerializableExtra(String name) { + public @Nullable Serializable getSerializableExtra(String name) { return mExtras == null ? null : mExtras.getSerializable(name); } @@ -7594,7 +7594,7 @@ public class Intent implements Parcelable, Cloneable { * * @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 +7609,7 @@ public class Intent implements Parcelable, Cloneable { * * @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 +7624,7 @@ public class Intent implements Parcelable, Cloneable { * * @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 +7638,7 @@ public class Intent implements Parcelable, Cloneable { * * @see #putExtra(String, boolean[]) */ - public boolean[] getBooleanArrayExtra(String name) { + public @Nullable boolean[] getBooleanArrayExtra(String name) { return mExtras == null ? null : mExtras.getBooleanArray(name); } @@ -7652,7 +7652,7 @@ public class Intent implements Parcelable, Cloneable { * * @see #putExtra(String, byte[]) */ - public byte[] getByteArrayExtra(String name) { + public @Nullable byte[] getByteArrayExtra(String name) { return mExtras == null ? null : mExtras.getByteArray(name); } @@ -7666,7 +7666,7 @@ public class Intent implements Parcelable, Cloneable { * * @see #putExtra(String, short[]) */ - public short[] getShortArrayExtra(String name) { + public @Nullable short[] getShortArrayExtra(String name) { return mExtras == null ? null : mExtras.getShortArray(name); } @@ -7680,7 +7680,7 @@ public class Intent implements Parcelable, Cloneable { * * @see #putExtra(String, char[]) */ - public char[] getCharArrayExtra(String name) { + public @Nullable char[] getCharArrayExtra(String name) { return mExtras == null ? null : mExtras.getCharArray(name); } @@ -7694,7 +7694,7 @@ public class Intent implements Parcelable, Cloneable { * * @see #putExtra(String, int[]) */ - public int[] getIntArrayExtra(String name) { + public @Nullable int[] getIntArrayExtra(String name) { return mExtras == null ? null : mExtras.getIntArray(name); } @@ -7708,7 +7708,7 @@ public class Intent implements Parcelable, Cloneable { * * @see #putExtra(String, long[]) */ - public long[] getLongArrayExtra(String name) { + public @Nullable long[] getLongArrayExtra(String name) { return mExtras == null ? null : mExtras.getLongArray(name); } @@ -7722,7 +7722,7 @@ public class Intent implements Parcelable, Cloneable { * * @see #putExtra(String, float[]) */ - public float[] getFloatArrayExtra(String name) { + public @Nullable float[] getFloatArrayExtra(String name) { return mExtras == null ? null : mExtras.getFloatArray(name); } @@ -7736,7 +7736,7 @@ public class Intent implements Parcelable, Cloneable { * * @see #putExtra(String, double[]) */ - public double[] getDoubleArrayExtra(String name) { + public @Nullable double[] getDoubleArrayExtra(String name) { return mExtras == null ? null : mExtras.getDoubleArray(name); } @@ -7750,7 +7750,7 @@ public class Intent implements Parcelable, Cloneable { * * @see #putExtra(String, String[]) */ - public String[] getStringArrayExtra(String name) { + public @Nullable String[] getStringArrayExtra(String name) { return mExtras == null ? null : mExtras.getStringArray(name); } @@ -7764,7 +7764,7 @@ public class Intent implements Parcelable, Cloneable { * * @see #putExtra(String, CharSequence[]) */ - public CharSequence[] getCharSequenceArrayExtra(String name) { + public @Nullable CharSequence[] getCharSequenceArrayExtra(String name) { return mExtras == null ? null : mExtras.getCharSequenceArray(name); } @@ -7778,7 +7778,7 @@ public class Intent implements Parcelable, Cloneable { * * @see #putExtra(String, Bundle) */ - public Bundle getBundleExtra(String name) { + public @Nullable Bundle getBundleExtra(String name) { return mExtras == null ? null : mExtras.getBundle(name); } @@ -8584,7 +8584,7 @@ public class Intent implements Parcelable, Cloneable { * @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 +8607,7 @@ public class Intent implements Parcelable, Cloneable { * @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 +8630,7 @@ public class Intent implements Parcelable, Cloneable { * @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 +8653,7 @@ public class Intent implements Parcelable, Cloneable { * @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 +8677,7 @@ public class Intent implements Parcelable, Cloneable { * @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 +8700,8 @@ public class Intent implements Parcelable, Cloneable { * @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 +8724,7 @@ public class Intent implements Parcelable, Cloneable { * @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 +8748,7 @@ public class Intent implements Parcelable, Cloneable { * @see #getCharSequenceArrayListExtra(String) */ public @NonNull Intent putCharSequenceArrayListExtra(String name, - ArrayList<CharSequence> value) { + @Nullable ArrayList<CharSequence> value) { if (mExtras == null) { mExtras = new Bundle(); } @@ -8770,7 +8771,7 @@ public class Intent implements Parcelable, Cloneable { * @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 +8794,7 @@ public class Intent implements Parcelable, Cloneable { * @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 +8817,7 @@ public class Intent implements Parcelable, Cloneable { * @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 +8840,7 @@ public class Intent implements Parcelable, Cloneable { * @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 +8863,7 @@ public class Intent implements Parcelable, Cloneable { * @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 +8886,7 @@ public class Intent implements Parcelable, Cloneable { * @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 +8909,7 @@ public class Intent implements Parcelable, Cloneable { * @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 +8932,7 @@ public class Intent implements Parcelable, Cloneable { * @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 +8955,7 @@ public class Intent implements Parcelable, Cloneable { * @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 +8978,7 @@ public class Intent implements Parcelable, Cloneable { * @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 +9001,7 @@ public class Intent implements Parcelable, Cloneable { * @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 +9024,7 @@ public class Intent implements Parcelable, Cloneable { * @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 000000000000..1c26fd917f76 --- /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 a87ee572b6d0..d0eff2e0fda9 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -276,6 +276,8 @@ interface IPackageManager { 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/PackageManager.java b/core/java/android/content/pm/PackageManager.java index d3e40452d9fb..e14b17e5cdd3 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -5797,6 +5797,30 @@ public abstract class PackageManager { } /** + * 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 5f2374995775..4371c772c047 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -58,6 +58,7 @@ import java.util.HashMap; 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 @@ public final class AssetManager implements AutoCloseable { 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 @@ public final class AssetManager implements AutoCloseable { 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 000000000000..58e65b28f0e0 --- /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 7952c4104d1f..bd149fd05f59 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -426,6 +426,31 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** + * 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 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan 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 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan 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 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan 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 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan 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 000000000000..0a76c2bc724e --- /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 2717c4ee5313..a83a33eac1fb 100644 --- a/core/java/android/hardware/location/ContextHubClient.java +++ b/core/java/android/hardware/location/ContextHubClient.java @@ -19,7 +19,6 @@ import android.annotation.NonNull; 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 @@ public class ContextHubClient implements Closeable { */ 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 @@ public class ContextHubClient implements Closeable { * 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 @@ public class ContextHubClient implements Closeable { } /** - * 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 @@ public class ContextHubClient implements Closeable { 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 36123e3d4229..51daa92d138e 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 @@ public class ContextHubInfo implements Parcelable { 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 96e74969a2e8..539c49408c8f 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 @@ import com.android.internal.util.Preconditions; /** * 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 @@ public class ContextHubIntentEvent { /** * 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 @@ public class ContextHubIntentEvent { 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 9acefa567ed6..88fb3de24c91 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -25,6 +25,7 @@ import android.annotation.SystemApi; 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 @@ public final class ContextHubManager { 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 @@ public final class ContextHubManager { } /** - * 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 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. + * + * 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. * - * 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. - * - * @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 - * @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 @@ public final class ContextHubManager { } /** - * 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 b53941491c24..e33545c40360 100644 --- a/core/java/android/hardware/location/IContextHubClient.aidl +++ b/core/java/android/hardware/location/IContextHubClient.aidl @@ -29,10 +29,4 @@ interface IContextHubClient { // 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 9b0acdf14724..04cc5634bf2c 100644 --- a/core/java/android/hardware/location/IContextHubService.aidl +++ b/core/java/android/hardware/location/IContextHubService.aidl @@ -59,12 +59,11 @@ interface IContextHubService { 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 857434ea75b9..3d9e85955850 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 @@ public class MemoryRegion implements Parcelable{ } @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 66352581dac3..9f90d5966e4d 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 @@ public final class NanoAppMessage implements Parcelable { 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 4cd000113b7e..4e551756198e 100644 --- a/core/java/android/net/MacAddress.java +++ b/core/java/android/net/MacAddress.java @@ -18,6 +18,7 @@ package android.net; 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 com.android.internal.util.Preconditions; 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 @@ public final class MacAddress implements Parcelable { 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 9cf7de586d17..c437dde2dacc 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -2421,7 +2421,7 @@ public abstract class BatteryStats implements Parcelable { 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 da4b82308313..c7184c0743ce 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -382,7 +382,9 @@ public class Binder implements IBinder { /** * 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 @@ public class Binder implements IBinder { * * <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 @@ public class Binder implements IBinder { * @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 @@ public class Binder implements IBinder { * * <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 @@ public class Binder implements IBinder { // 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 @@ public class Binder implements IBinder { @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 @@ public class Binder implements IBinder { // 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 @@ public class Binder implements IBinder { } 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 1f47f933cde9..28ea553c8800 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -1390,3 +1390,4 @@ public class FileUtils { } } } + diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java index f3a9a504a765..e8704afd0139 100644 --- a/core/java/android/os/Handler.java +++ b/core/java/android/os/Handler.java @@ -739,7 +739,7 @@ public class Handler { 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 124f2072933e..74d434c51781 100644 --- a/core/java/android/os/IStatsManager.aidl +++ b/core/java/android/os/IStatsManager.aidl @@ -62,12 +62,15 @@ interface IStatsManager { * 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 5b8ababb8ca4..a8d1215f48e6 100644 --- a/core/java/android/os/Looper.java +++ b/core/java/android/os/Looper.java @@ -204,8 +204,8 @@ public final class Looper { 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 @@ public final class Looper { } 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 126588a3a817..44b9e311dc0b 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -1056,6 +1056,9 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { /** * 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 a307cd80e724..1c1db68babc3 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -24,6 +24,7 @@ import android.annotation.SystemApi; 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 @@ public final class PowerManager { } /** + * 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 72e1ab972846..866bd9a17f41 100644 --- a/core/java/android/os/StatsLogEventWrapper.java +++ b/core/java/android/os/StatsLogEventWrapper.java @@ -104,14 +104,6 @@ public final class StatsLogEventWrapper implements Parcelable { } /** - * 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 000000000000..894b1cc475b0 --- /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 df1d275f358d..000000000000 --- 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/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 67e52aad9206..16d454dd0179 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.ContentResolver; 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.Parcelable; 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 final class DocumentsContract { 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 @@ public final class DocumentsContract { } /** + * 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 @@ public final class DocumentsContract { 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 68f8acd8a586..58f82134ec50 100644 --- a/core/java/android/provider/DocumentsProvider.java +++ b/core/java/android/provider/DocumentsProvider.java @@ -32,7 +32,6 @@ import static android.provider.DocumentsContract.buildDocumentUriMaybeUsingTree; 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.ContentValues; 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 @@ public abstract class DocumentsProvider extends ContentProvider { } /** + * 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 @@ public abstract class DocumentsProvider extends ContentProvider { * {@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 @@ public abstract class DocumentsProvider extends ContentProvider { * @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 @@ public abstract class DocumentsProvider extends ContentProvider { 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 @@ public abstract class DocumentsProvider extends ContentProvider { 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 @@ public abstract class DocumentsProvider extends ContentProvider { // 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 040222217db4..b2666482c10d 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1825,53 +1825,6 @@ public final class Settings { }) 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 final class Settings { 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 @@ public final class Settings { 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 @@ public final class Settings { 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 0000b9f8c76c..ac5be06abff3 100644 --- a/core/java/android/rolecontrollerservice/IRoleControllerService.aidl +++ b/core/java/android/rolecontrollerservice/IRoleControllerService.aidl @@ -30,4 +30,6 @@ oneway interface IRoleControllerService { 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 da11bca220d4..44c45bb32acd 100644 --- a/core/java/android/rolecontrollerservice/RoleControllerService.java +++ b/core/java/android/rolecontrollerservice/RoleControllerService.java @@ -89,6 +89,13 @@ public abstract class RoleControllerService extends Service { 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 class RoleControllerService extends Service { 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 c104671fc129..1ab79fbee0b6 100644 --- a/core/java/android/security/keymaster/ExportResult.java +++ b/core/java/android/security/keymaster/ExportResult.java @@ -28,6 +28,11 @@ public class ExportResult implements Parcelable { 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 555863efec91..a4fe75d6d0c0 100644 --- a/core/java/android/security/keymaster/KeyCharacteristics.java +++ b/core/java/android/security/keymaster/KeyCharacteristics.java @@ -52,6 +52,14 @@ public class KeyCharacteristics implements Parcelable { 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 243b9fe5f7c6..00a1a1c06e79 100644 --- a/core/java/android/security/keymaster/KeymasterCertificateChain.java +++ b/core/java/android/security/keymaster/KeymasterCertificateChain.java @@ -54,6 +54,14 @@ public class KeymasterCertificateChain implements Parcelable { 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 f4dcce1e7e58..15ded8d1b7b1 100644 --- a/core/java/android/security/keymaster/KeymasterDefs.java +++ b/core/java/android/security/keymaster/KeymasterDefs.java @@ -154,7 +154,7 @@ public final class KeymasterDefs { // 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 2943211a45f5..bc4f36000b85 100644 --- a/core/java/android/security/keymaster/OperationResult.java +++ b/core/java/android/security/keymaster/OperationResult.java @@ -59,6 +59,10 @@ public class OperationResult implements Parcelable { 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 000000000000..d53eb37ca786 --- /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 b87faef5bb44..49a7320dab6d 100644 --- a/core/java/android/service/euicc/EuiccService.java +++ b/core/java/android/service/euicc/EuiccService.java @@ -82,6 +82,13 @@ public abstract class EuiccService extends Service { // 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/text/Layout.java b/core/java/android/text/Layout.java index e5fd2921227d..2d5f3bf8c862 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -1270,7 +1270,13 @@ public abstract class Layout { */ 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 abstract class Layout { */ 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 abstract class Layout { 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 44dfd115f7f2..6eb433abf16c 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 @@ import java.util.ArrayList; 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 @@ public class TextLine { } } - mCharsValid = hasReplacement || hasTabs || directions != Layout.DIRS_ALL_LEFT_TO_RIGHT; + mCharsValid = hasReplacement; if (mCharsValid) { if (mChars == null || mChars.length < mLen) { @@ -232,6 +235,10 @@ public class TextLine { 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 @@ public class TextLine { * @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; - - 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; + 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 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 || codept == '\t') { - h += drawRun(c, segstart, j, runIsRtl, x+h, top, y, bottom, - i != lastRunIndex || j != mLen); + if (j == runLimit || charAt(j) == TAB_CHAR) { + h += drawRun(c, segStart, j, runIsRtl, x + h, top, y, bottom, + runIndex != (runCount - 1) || 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 @@ public class TextLine { } /** - * 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 || codept == '\t') { - boolean inSegment = target >= segstart && target < j; + if (j == runLimit || charAt(j) == TAB_CHAR) { + final boolean targetIsInThisSegment = target >= segStart && target < j; + final boolean sameDirection = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl; - 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 @@ public class TextLine { } } - segstart = j + 1; + segStart = j + 1; } } } @@ -426,62 +411,29 @@ public class TextLine { } 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 @@ public class TextLine { } } - segstart = j + 1; + segStart = j + 1; } } } @@ -863,7 +815,6 @@ public class TextLine { } 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/view/DisplayListCanvas.java b/core/java/android/view/DisplayListCanvas.java new file mode 100644 index 000000000000..03d99553f125 --- /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 07a57fb73a0d..4b8b7f304b0f 100644 --- a/core/java/android/view/IWindow.aidl +++ b/core/java/android/view/IWindow.aidl @@ -58,10 +58,33 @@ oneway interface IWindow { 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 index 3cf7edcd6b2e..dc1e505ef36d 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 b2dd6ac9e971..84c8e7a2221e 100644 --- a/core/java/android/view/InputChannel.java +++ b/core/java/android/view/InputChannel.java @@ -18,6 +18,7 @@ package android.view; import android.annotation.UnsupportedAppUsage; import android.os.Parcel; +import android.os.IBinder; import android.os.Parcelable; import android.util.Slog; @@ -50,15 +51,17 @@ public final class InputChannel implements Parcelable { @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 @@ public final class InputChannel implements Parcelable { } 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 index bb29bf823eb8..621ee89fac45 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 @@ public final class InputWindowHandle { // 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 @@ public final class InputWindowHandle { 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 fa30221d2a30..4a5ccdff0dd3 100644 --- a/core/java/android/view/NotificationHeaderView.java +++ b/core/java/android/view/NotificationHeaderView.java @@ -218,11 +218,6 @@ public class NotificationHeaderView extends ViewGroup { 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 9d31bd16b452..78ad0dabc81a 100644 --- a/core/java/android/view/RenderNodeAnimator.java +++ b/core/java/android/view/RenderNodeAnimator.java @@ -293,6 +293,12 @@ public class RenderNodeAnimator extends Animator { 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 3d16eb89c41d..a7a5024cd2a6 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -153,6 +153,9 @@ public class SurfaceControl implements Parcelable { 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 @@ public class SurfaceControl implements Parcelable { 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 ec16828265fc..5f1336f903d6 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3406,18 +3406,33 @@ public class View implements Drawable.Callback, KeyEvent.Callback, 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * {@link #onProvideVirtualStructure}. */ public void dispatchProvideStructure(ViewStructure structure) { - dispatchProvideStructureForAssistOrAutofill(structure, false, 0); + dispatchProvideStructure(structure, VIEW_STRUCTURE_FOR_ASSIST, /* flags= */ 0); } /** @@ -9126,12 +9152,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @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 a23d68b17245..484c6f38e962 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1449,6 +1449,9 @@ public final class ViewRootImpl implements ViewParent, } if (mStopped) { + if (mSurfaceHolder != null) { + notifySurfaceDestroyed(); + } destroySurface(); } } @@ -2393,13 +2396,7 @@ public final class ViewRootImpl implements ViewParent, } 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 @@ public final class ViewRootImpl implements ViewParent, 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 @@ public final class ViewRootImpl implements ViewParent, } } - private void handleWindowFocusChanged() { + private void handleWindowFocusChanged(boolean reportToClient) { final boolean hasWindowFocus; final boolean inTouchMode; synchronized (this) { @@ -2730,8 +2737,9 @@ public final class ViewRootImpl implements ViewParent, } 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 @@ public final class ViewRootImpl implements ViewParent, } 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 @@ public final class ViewRootImpl implements ViewParent, } break; case MSG_WINDOW_FOCUS_CHANGED: { - handleWindowFocusChanged(); + handleWindowFocusChanged(msg.arg1 != 0 /* reportToClient */); } break; case MSG_DIE: doDie(); @@ -7263,7 +7277,7 @@ public final class ViewRootImpl implements ViewParent, } if (stage != null) { - handleWindowFocusChanged(); + handleWindowFocusChanged(true /* reportToClient */); stage.deliver(q); } else { finishInputEvent(q); @@ -7580,6 +7594,11 @@ public final class ViewRootImpl implements ViewParent, } 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 @@ public final class ViewRootImpl implements ViewParent, } Message msg = Message.obtain(); msg.what = MSG_WINDOW_FOCUS_CHANGED; + msg.arg1 = reportToClient ? 1 : 0; mHandler.sendMessage(msg); } @@ -8131,10 +8151,11 @@ public final class ViewRootImpl implements ViewParent, } @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/intelligence/IntelligenceManager.java b/core/java/android/view/intelligence/IntelligenceManager.java index c02fb3218a2e..242bf6abe4b1 100644 --- a/core/java/android/view/intelligence/IntelligenceManager.java +++ b/core/java/android/view/intelligence/IntelligenceManager.java @@ -86,6 +86,13 @@ public final class IntelligenceManager { */ 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 +173,7 @@ public final class IntelligenceManager { 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 +196,7 @@ public final class IntelligenceManager { 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 +205,13 @@ public final class IntelligenceManager { // 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,7 +386,7 @@ public final class IntelligenceManager { //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; } /** @@ -509,6 +515,8 @@ public final class IntelligenceManager { 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 896b516bbf9a..8558a462fa40 100644 --- a/core/java/android/view/textclassifier/ModelFileManager.java +++ b/core/java/android/view/textclassifier/ModelFileManager.java @@ -251,6 +251,9 @@ public final class ModelFileManager { 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/widget/AdapterView.java b/core/java/android/widget/AdapterView.java index f3fe16e8a675..ddff8581d568 100644 --- a/core/java/android/widget/AdapterView.java +++ b/core/java/android/widget/AdapterView.java @@ -1309,13 +1309,23 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { @Override public void onProvideAutofillStructure(ViewStructure structure, int flags) { super.onProvideAutofillStructure(structure, flags); + } - final Adapter adapter = getAdapter(); - if (adapter == null) return; - - final CharSequence[] options = adapter.getAutofillOptions(); - if (options != null) { - structure.setAutofillOptions(options); + /** @hide */ + @Override + protected void onProvideStructure(@NonNull ViewStructure structure, + @ViewStructureType int viewFor, int flags) { + super.onProvideStructure(structure, viewFor, flags); + + 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 b754d84fbaf8..eb35587cb7e7 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 @@ public class AppSecurityPermissions { 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 @@ public class AppSecurityPermissions { } 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 8d094898d909..d35bec8d08d5 100644 --- a/core/java/android/widget/CompoundButton.java +++ b/core/java/android/widget/CompoundButton.java @@ -578,11 +578,16 @@ public abstract class CompoundButton extends Button implements Checkable { stream.addProperty("checked", isChecked()); } + + /** @hide */ @Override - public void onProvideAutofillStructure(ViewStructure structure, int flags) { - super.onProvideAutofillStructure(structure, flags); + protected void onProvideStructure(@NonNull ViewStructure structure, + @ViewStructureType int viewFor, int flags) { + super.onProvideStructure(structure, viewFor, flags); - structure.setDataIsSensitive(!mCheckedFromResource); + if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { + structure.setDataIsSensitive(!mCheckedFromResource); + } } @Override diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java index 9f509b1f3d0b..7756a19d847f 100644 --- a/core/java/android/widget/Magnifier.java +++ b/core/java/android/widget/Magnifier.java @@ -17,6 +17,7 @@ package android.widget; import android.annotation.FloatRange; +import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; @@ -40,6 +41,7 @@ import android.graphics.RenderNode; import android.os.Handler; import android.os.HandlerThread; import android.os.Message; +import android.util.Log; import android.view.ContextThemeWrapper; import android.view.Display; import android.view.PixelCopy; @@ -55,11 +57,15 @@ import android.view.ViewRootImpl; import com.android.internal.R; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Android magnifier widget. Can be used by any view which is attached to a window. */ @UiThread public final class Magnifier { + private static final String TAG = "Magnifier"; // Use this to specify that a previous configuration value does not exist. private static final int NONEXISTENT_PREVIOUS_CONFIG_VALUE = -1; // The callbacks of the pixel copy requests will be invoked on @@ -83,8 +89,8 @@ public final class Magnifier { private int mSourceWidth; // The height of the content that will be copied to the magnifier. private int mSourceHeight; - // Whether the zoom of the magnifier has changed since last content copy. - private boolean mDirtyZoom; + // Whether the zoom of the magnifier or the view position have changed since last content copy. + private boolean mDirtyState; // The elevation of the window containing the magnifier. private final float mWindowElevation; // The corner radius of the window containing the magnifier. @@ -95,6 +101,14 @@ public final class Magnifier { private final int mDefaultVerticalSourceToMagnifierOffset; // Whether the magnifier will be clamped inside the main surface and not overlap system insets. private final boolean mForcePositionWithinWindowSystemInsetsBounds; + // The behavior of the left bound of the rectangle where the content can be copied from. + private @SourceBound int mLeftContentBound; + // The behavior of the top bound of the rectangle where the content can be copied from. + private @SourceBound int mTopContentBound; + // The behavior of the right bound of the rectangle where the content can be copied from. + private @SourceBound int mRightContentBound; + // The behavior of the bottom bound of the rectangle where the content can be copied from. + private @SourceBound int mBottomContentBound; // The parent surface for the magnifier surface. private SurfaceInfo mParentSurface; // The surface where the content will be copied from. @@ -145,6 +159,10 @@ public final class Magnifier { params.mVerticalDefaultSourceToMagnifierOffset; mForcePositionWithinWindowSystemInsetsBounds = params.mForcePositionWithinWindowSystemInsetsBounds; + mLeftContentBound = params.mLeftContentBound; + mTopContentBound = params.mTopContentBound; + mRightContentBound = params.mRightContentBound; + mBottomContentBound = params.mBottomContentBound; // The view's surface coordinates will not be updated until the magnifier is first shown. mViewCoordinatesInSurface = new int[2]; } @@ -195,8 +213,6 @@ public final class Magnifier { public void show(@FloatRange(from = 0) float sourceCenterX, @FloatRange(from = 0) float sourceCenterY, float magnifierCenterX, float magnifierCenterY) { - sourceCenterX = Math.max(0, Math.min(sourceCenterX, mView.getWidth())); - sourceCenterY = Math.max(0, Math.min(sourceCenterY, mView.getHeight())); obtainSurfaces(); obtainContentCoordinates(sourceCenterX, sourceCenterY); @@ -205,7 +221,7 @@ public final class Magnifier { final int startX = mClampedCenterZoomCoords.x - mSourceWidth / 2; final int startY = mClampedCenterZoomCoords.y - mSourceHeight / 2; if (sourceCenterX != mPrevShowSourceCoords.x || sourceCenterY != mPrevShowSourceCoords.y - || mDirtyZoom) { + || mDirtyState) { if (mWindow == null) { synchronized (mLock) { mWindow = new InternalPopupWindow(mView.getContext(), mView.getDisplay(), @@ -262,13 +278,13 @@ public final class Magnifier { public void update() { if (mWindow != null) { obtainSurfaces(); - if (!mDirtyZoom) { + if (!mDirtyState) { // Update the content shown in the magnifier. performPixelCopy(mPrevStartCoordsInSurface.x, mPrevStartCoordsInSurface.y, false /* update window position */); } else { - // If the zoom has changed, we cannot use the same top left coordinates - // as before, so just #show again to have them recomputed. + // If for example the zoom has changed, we cannot use the same top left + // coordinates as before, so just #show again to have them recomputed. show(mPrevShowSourceCoords.x, mPrevShowSourceCoords.y, mPrevShowWindowCoords.x, mPrevShowWindowCoords.y); } @@ -315,6 +331,7 @@ public final class Magnifier { /** * Sets the zoom to be applied to the chosen content before being copied to the magnifier popup. + * The change will become effective at the next #show or #update call. * @param zoom the zoom to be set */ public void setZoom(@FloatRange(from = 0f) float zoom) { @@ -322,7 +339,7 @@ public final class Magnifier { mZoom = zoom; mSourceWidth = Math.round(mWindowWidth / mZoom); mSourceHeight = Math.round(mWindowHeight / mZoom); - mDirtyZoom = true; + mDirtyState = true; } /** @@ -480,7 +497,14 @@ public final class Magnifier { * magnifier. These are relative to the surface the content is copied from. */ private void obtainContentCoordinates(final float xPosInView, final float yPosInView) { + final int prevViewXInSurface = mViewCoordinatesInSurface[0]; + final int prevViewYInSurface = mViewCoordinatesInSurface[1]; mView.getLocationInSurface(mViewCoordinatesInSurface); + if (mViewCoordinatesInSurface[0] != prevViewXInSurface + || mViewCoordinatesInSurface[1] != prevViewYInSurface) { + mDirtyState = true; + } + final int zoomCenterX; final int zoomCenterY; if (mView instanceof SurfaceView) { @@ -492,8 +516,25 @@ public final class Magnifier { zoomCenterY = Math.round(yPosInView + mViewCoordinatesInSurface[1]); } - // Clamp the x location to avoid magnifying content which does not belong - // to the magnified view. This will not take into account overlapping views. + final Rect[] bounds = new Rect[3]; // [MAX_IN_SURFACE, MAX_IN_VIEW, MAX_VISIBLE] + // Obtain the surface bounds rectangle. + final Rect surfaceBounds = new Rect(0, 0, + mContentCopySurface.mWidth, mContentCopySurface.mHeight); + bounds[0] = surfaceBounds; + // Obtain the view bounds rectangle. + final Rect viewBounds; + if (mView instanceof SurfaceView) { + viewBounds = new Rect(0, 0, mContentCopySurface.mWidth, mContentCopySurface.mHeight); + } else { + viewBounds = new Rect( + mViewCoordinatesInSurface[0], + mViewCoordinatesInSurface[1], + mViewCoordinatesInSurface[0] + mView.getWidth(), + mViewCoordinatesInSurface[1] + mView.getHeight() + ); + } + bounds[1] = viewBounds; + // Obtain the visible view region rectangle. final Rect viewVisibleRegion = new Rect(); mView.getGlobalVisibleRect(viewVisibleRegion); if (mView.getViewRootImpl() != null) { @@ -505,9 +546,40 @@ public final class Magnifier { // If we copy content from a SurfaceView, clamp coordinates relative to it. viewVisibleRegion.offset(-mViewCoordinatesInSurface[0], -mViewCoordinatesInSurface[1]); } - mClampedCenterZoomCoords.x = Math.max(viewVisibleRegion.left + mSourceWidth / 2, Math.min( - zoomCenterX, viewVisibleRegion.right - mSourceWidth / 2)); - mClampedCenterZoomCoords.y = zoomCenterY; + bounds[2] = viewVisibleRegion; + + // Aggregate the above to obtain the bounds where the content copy will be restricted. + int resolvedLeft = Integer.MIN_VALUE; + for (int i = mLeftContentBound; i >= 0; --i) { + resolvedLeft = Math.max(resolvedLeft, bounds[i].left); + } + int resolvedTop = Integer.MIN_VALUE; + for (int i = mTopContentBound; i >= 0; --i) { + resolvedTop = Math.max(resolvedTop, bounds[i].top); + } + int resolvedRight = Integer.MAX_VALUE; + for (int i = mRightContentBound; i >= 0; --i) { + resolvedRight = Math.min(resolvedRight, bounds[i].right); + } + int resolvedBottom = Integer.MAX_VALUE; + for (int i = mBottomContentBound; i >= 0; --i) { + resolvedBottom = Math.min(resolvedBottom, bounds[i].bottom); + } + // Adjust <left-right> and <top-bottom> pairs of bounds to make sense. + resolvedLeft = Math.min(resolvedLeft, mContentCopySurface.mWidth - mSourceWidth); + resolvedTop = Math.min(resolvedTop, mContentCopySurface.mHeight - mSourceHeight); + if (resolvedLeft < 0 || resolvedTop < 0) { + Log.e(TAG, "Magnifier's content is copied from a surface smaller than" + + "the content requested size. This will probably lead to distorted content."); + } + resolvedRight = Math.max(resolvedRight, resolvedLeft + mSourceWidth); + resolvedBottom = Math.max(resolvedBottom, resolvedTop + mSourceHeight); + + // Finally compute the coordinates of the source center. + mClampedCenterZoomCoords.x = Math.max(resolvedLeft + mSourceWidth / 2, Math.min( + zoomCenterX, resolvedRight - mSourceWidth / 2)); + mClampedCenterZoomCoords.y = Math.max(resolvedTop + mSourceHeight / 2, Math.min( + zoomCenterY, resolvedBottom - mSourceHeight / 2)); } /** @@ -539,20 +611,16 @@ public final class Magnifier { if (mContentCopySurface.mSurface == null || !mContentCopySurface.mSurface.isValid()) { return; } - // Clamp copy coordinates inside the surface to avoid displaying distorted content. - final int clampedStartXInSurface = Math.max(0, - Math.min(startXInSurface, mContentCopySurface.mWidth - mSourceWidth)); - final int clampedStartYInSurface = Math.max(0, - Math.min(startYInSurface, mContentCopySurface.mHeight - mSourceHeight)); + // Clamp window coordinates inside the parent surface, to avoid displaying // the magnifier out of screen or overlapping with system insets. final Point windowCoords = getCurrentClampedWindowCoordinates(); // Perform the pixel copy. - mPixelCopyRequestRect.set(clampedStartXInSurface, - clampedStartYInSurface, - clampedStartXInSurface + mSourceWidth, - clampedStartYInSurface + mSourceHeight); + mPixelCopyRequestRect.set(startXInSurface, + startYInSurface, + startXInSurface + mSourceWidth, + startYInSurface + mSourceHeight); final InternalPopupWindow currentWindowInstance = mWindow; final Bitmap bitmap = Bitmap.createBitmap(mSourceWidth, mSourceHeight, Bitmap.Config.ARGB_8888); @@ -573,7 +641,7 @@ public final class Magnifier { sPixelCopyHandlerThread.getThreadHandler()); mPrevStartCoordsInSurface.x = startXInSurface; mPrevStartCoordsInSurface.y = startYInSurface; - mDirtyZoom = false; + mDirtyState = false; } /** @@ -912,6 +980,10 @@ public final class Magnifier { private int mHorizontalDefaultSourceToMagnifierOffset; private int mVerticalDefaultSourceToMagnifierOffset; private boolean mForcePositionWithinWindowSystemInsetsBounds; + private @SourceBound int mLeftContentBound; + private @SourceBound int mTopContentBound; + private @SourceBound int mRightContentBound; + private @SourceBound int mBottomContentBound; /** * Construct a new builder for {@link Magnifier} objects. @@ -937,6 +1009,10 @@ public final class Magnifier { a.getDimensionPixelSize(R.styleable.Magnifier_magnifierVerticalOffset, 0); a.recycle(); mForcePositionWithinWindowSystemInsetsBounds = true; + mLeftContentBound = SOURCE_BOUND_MAX_VISIBLE; + mTopContentBound = SOURCE_BOUND_MAX_IN_SURFACE; + mRightContentBound = SOURCE_BOUND_MAX_VISIBLE; + mBottomContentBound = SOURCE_BOUND_MAX_IN_SURFACE; } /** @@ -1044,6 +1120,52 @@ public final class Magnifier { } /** + * Defines the bounds of the rectangle where the magnifier will be able to copy its content + * from. The content will always be copied from the {@link Surface} of the main application + * window unless the magnified view is a {@link SurfaceView}, in which case its backing + * surface will be used. Each bound can have a different behavior, with the options being: + * <ul> + * <li>{@link #SOURCE_BOUND_MAX_VISIBLE}, which extends the bound as much as possible + * while remaining in the visible region of the magnified view, as given by + * {@link android.view.View#getGlobalVisibleRect(Rect)}. For example, this will take into + * account the case when the view is contained in a scrollable container, and the + * magnifier will refuse to copy content outside of the visible view region</li> + * <li>{@link #SOURCE_BOUND_MAX_IN_VIEW}, which extends the bound as much as possible + * while remaining in the bounds of the view. Note that, although this option is + * used, the magnifier will always only display content visible on the screen: if the + * view lays outside the screen or is covered by a different view either partially or + * totally, the magnifier will not show any view region not visible on the screen.</li> + * <li>{@link #SOURCE_BOUND_MAX_IN_SURFACE}, which extends the bound as much + * as possible while remaining inside the surface the content is copied from.</li> + * </ul> + * Note that if either of the first three options is used, the bound will be compared to + * the bound of the surface (i.e. as if {@link #SOURCE_BOUND_MAX_IN_SURFACE} was used), + * and the more restrictive one will be chosen. In other words, no attempt to copy content + * from outside the surface will be permitted. If two opposite bounds are not well-behaved + * (i.e. left + sourceWidth > right or top + sourceHeight > bottom), the left and top + * bounds will have priority and the others will be extended accordingly. If the pairs + * obtained this way still remain out of bounds, the smallest possible offset will be added + * to the pairs to bring them inside the surface bounds. If this is impossible + * (i.e. the surface is too small for the size of the content we try to copy on either + * dimension), an error will be logged and the magnifier content will look distorted. + * The default values assumed by the builder for the source bounds are + * left: {@link #SOURCE_BOUND_MAX_VISIBLE}, top: {@link #SOURCE_BOUND_MAX_IN_SURFACE}, + * right: {@link #SOURCE_BOUND_MAX_VISIBLE}, bottom: {@link #SOURCE_BOUND_MAX_IN_SURFACE}. + * @param left the left bound for content copy + * @param top the top bound for content copy + * @param right the right bound for content copy + * @param bottom the bottom bound for content copy + */ + public Builder setSourceBounds(@SourceBound int left, @SourceBound int top, + @SourceBound int right, @SourceBound int bottom) { + mLeftContentBound = left; + mTopContentBound = top; + mRightContentBound = right; + mBottomContentBound = bottom; + return this; + } + + /** * Builds a {@link Magnifier} instance based on the configuration of this {@link Builder}. */ public @NonNull Magnifier build() { @@ -1051,6 +1173,38 @@ public final class Magnifier { } } + /** + * A source bound that will extend as much as possible, while remaining within the surface + * the content is copied from. + */ + + public static final int SOURCE_BOUND_MAX_IN_SURFACE = 0; + /** + * A source bound that will extend as much as possible, while remaining within the + * magnified view. + */ + + public static final int SOURCE_BOUND_MAX_IN_VIEW = 1; + + /** + * A source bound that will extend as much as possible, while remaining within the + * visible region of the magnified view, as determined by + * {@link View#getGlobalVisibleRect(Rect)}. + */ + public static final int SOURCE_BOUND_MAX_VISIBLE = 2; + + + /** + * Used to describe the {@link Surface} rectangle where the magnifier's content is allowed + * to be copied from. For more details, see method + * {@link Magnifier.Builder#setSourceBounds(int, int, int, int)} + * + * @hide + */ + @IntDef({SOURCE_BOUND_MAX_IN_SURFACE, SOURCE_BOUND_MAX_IN_VIEW, SOURCE_BOUND_MAX_VISIBLE}) + @Retention(RetentionPolicy.SOURCE) + public @interface SourceBound {} + // The rest of the file consists of test APIs. /** diff --git a/core/java/android/widget/MediaControlView2.java b/core/java/android/widget/MediaControlView2.java deleted file mode 100644 index f52854a87159..000000000000 --- 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 327a5c1d2527..ab12eaccad45 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 @@ public class RadioGroup extends LinearLayout { } } + /** @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 3bdd7b8e91be..35be7669a4d8 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -6076,7 +6076,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (needEditableForNotification) { sendAfterTextChanged((Editable) text); } else { - notifyManagersAfterTextChanged(); + notifyListeningManagersAfterTextChanged(); } // SelectionModifierCursorController depends on textCanBeSelected, which depends on text @@ -10124,13 +10124,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - // 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return TextView.class.getName(); } + /** @hide */ @Override - public void onProvideStructure(ViewStructure structure) { - super.onProvideStructure(structure); - onProvideStructureForAssistOrAutofillOrViewCapture(structure, /* forAutofill = */ false, - /* forViewCapture= */ false); - } - - @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; - } + protected void onProvideStructure(@NonNull ViewStructure structure, + @ViewStructureType int viewFor, int flags) { + super.onProvideStructure(structure, viewFor, flags); - 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 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 0724294a4e22..000000000000 --- 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 75151806cfcf..213bb75e6c6c 100644 --- a/core/java/com/android/internal/app/ColorDisplayController.java +++ b/core/java/com/android/internal/app/ColorDisplayController.java @@ -560,13 +560,6 @@ public final class ColorDisplayController { } /** - * 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 81dab2f6aeef..8bc90a891352 100644 --- a/core/java/com/android/internal/content/FileSystemProvider.java +++ b/core/java/com/android/internal/content/FileSystemProvider.java @@ -389,14 +389,18 @@ public abstract class FileSystemProvider extends DocumentsProvider { * @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 @@ public abstract class FileSystemProvider extends DocumentsProvider { 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 @@ public abstract class FileSystemProvider extends DocumentsProvider { } } + /** + * 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 0baf73cc024a..02c9542fa40d 100644 --- a/core/java/com/android/internal/os/BatterySipper.java +++ b/core/java/com/android/internal/os/BatterySipper.java @@ -130,6 +130,10 @@ public class BatterySipper implements Comparable<BatterySipper> { 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 70fc72fac8a6..34e8ed406200 100644 --- a/core/java/com/android/internal/os/BinderCallsStats.java +++ b/core/java/com/android/internal/os/BinderCallsStats.java @@ -421,7 +421,7 @@ public class BinderCallsStats implements BinderInternal.Observer { } 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 7c82a7eb97c4..ade5d05b592e 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.os.Process; 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 @@ import java.util.ArrayList; * 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 @@ public class KernelCpuThreadReader { 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 @@ public class KernelCpuThreadReader { */ 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 @@ public class KernelCpuThreadReader { 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 @@ public class KernelCpuThreadReader { 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 @@ public class KernelCpuThreadReader { } /** + * 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 8f87f9193c1f..8bdb000aad0e 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -173,12 +173,13 @@ public class ZygoteInit { } 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/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java index e3490f1fba7e..137ca7f2ac27 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 @@ public class BaseIWindow extends IWindow.Stub { } @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 110782896d5a..21e63c5f2ed9 100644 --- a/core/java/com/android/internal/widget/SubtitleView.java +++ b/core/java/com/android/internal/widget/SubtitleView.java @@ -58,7 +58,7 @@ public class SubtitleView extends View { /** 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 8ace8da77b2f..5887fa7a0841 100644 --- a/core/jni/android_hardware_input_InputApplicationHandle.cpp +++ b/core/jni/android_hardware_input_InputApplicationHandle.cpp @@ -135,13 +135,13 @@ static const JNINativeMethod gInputApplicationHandleMethods[] = { 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 f4829ad26aef..6ecb5de35ae1 100644 --- a/core/jni/android_hardware_input_InputWindowHandle.cpp +++ b/core/jni/android_hardware_input_InputWindowHandle.cpp @@ -80,51 +80,47 @@ bool NativeInputWindowHandle::updateInfo() { 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 @@ bool NativeInputWindowHandle::updateInfo() { 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 @@ static const JNINativeMethod gInputWindowHandleMethods[] = { 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 b2d44e73d861..7b564ae162ce 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 capget(cap_user_header_t hdrp, cap_user_data_t datap); 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 @@ static void NativeVerifySystemIdmaps(JNIEnv* /*env*/, jclass /*clazz*/) { 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 void NativeVerifySystemIdmaps(JNIEnv* /*env*/, jclass /*clazz*/) { } } +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 @@ static const JNINativeMethod gAssetManagerMethods[] = { // 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 fd042b39f7c2..4f8bbc1396c8 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -904,19 +904,24 @@ static jint android_os_Binder_getThreadStrictModePolicy() 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 @@ static const JNINativeMethod gBinderMethods[] = { // @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 2f179078aed8..fb6be6b4c91a 100644 --- a/core/jni/android_view_InputChannel.cpp +++ b/core/jni/android_view_InputChannel.cpp @@ -249,6 +249,24 @@ static void android_view_InputChannel_nativeDup(JNIEnv* env, jobject obj, jobjec } } +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 @@ static const JNINativeMethod gInputChannelMethods[] = { (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 4eda3abdd0d6..ec9c8606e8df 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 @@ static void nativeSetAlpha(JNIEnv* env, jclass clazz, jlong transactionObj, 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 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (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 258a55c7123a..ac0e60030fc5 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 2465759a9fa5..a398e498a301 100644 --- a/core/jni/fd_utils.cpp +++ b/core/jni/fd_utils.cpp @@ -85,7 +85,7 @@ bool FileDescriptorWhitelist::IsAllowed(const std::string& path) const { // 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 @@ bool FileDescriptorWhitelist::IsAllowed(const std::string& path) const { 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 ef024382d8a9..89f7010e8d81 100644 --- a/core/proto/android/server/enums.proto +++ b/core/proto/android/server/enums.proto @@ -28,3 +28,13 @@ enum DeviceIdleModeEnum { // 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 000000000000..b6dc58932ce7 --- /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 297687933018..20fec3c0a4a7 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" /> diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index 4ee97316083f..433ae399c88c 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 931674a8c0cb..84c6446e81ce 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 @@ easier. <!-- 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 32671ac8f752..ea7c00919527 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 0fe80a154f7a..ded916fbe7a3 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 26f3370f0dc5..829d6f57f896 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 c1ca33e430cd..5014a29f4eab 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 4eb723eb973d..626206b9b9d9 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2808,6 +2808,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 3385527ee6ff..fa009bd60c72 100644 --- a/core/res/res/values/themes_device_defaults.xml +++ b/core/res/res/values/themes_device_defaults.xml @@ -1475,6 +1475,8 @@ easier. <!-- 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 1beb598663d1..534c5cdec259 100644 --- a/core/tests/coretests/src/android/os/BinderTest.java +++ b/core/tests/coretests/src/android/os/BinderTest.java @@ -21,17 +21,26 @@ import android.test.suitebuilder.annotation.SmallTest; 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 8c91c370fcf6..002b6a84f592 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -494,6 +494,7 @@ public class SettingsBackupTest { 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/textclassifier/ModelFileManagerTest.java b/core/tests/coretests/src/android/view/textclassifier/ModelFileManagerTest.java index f2efabf5f722..88d162b34144 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 @@ public class ModelFileManagerTest { } @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 e81f6789185e..7467114a7596 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.InstrumentationRegistry; 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 @@ public class BatteryStatsHelperTest extends TestCase { 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 b9ef4349e414..c866bc4c3e00 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 @@ public class KernelCpuThreadReaderTest { 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/data/etc/Android.mk b/data/etc/Android.mk index 936ad22d4fc5..d24c140ad19a 100644 --- a/data/etc/Android.mk +++ b/data/etc/Android.mk @@ -47,3 +47,11 @@ LOCAL_MODULE_CLASS := ETC 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 000000000000..60a66e22027d --- /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 68f24fb7b661..a4c5ed2ee30f 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 84cb5f80ece3..c5bd03941ff0 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -96,6 +96,7 @@ applications that come with the platform <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"/> diff --git a/graphics/java/android/graphics/RecordingCanvas.java b/graphics/java/android/graphics/RecordingCanvas.java index fd5d62406da7..67ad4045868e 100644 --- a/graphics/java/android/graphics/RecordingCanvas.java +++ b/graphics/java/android/graphics/RecordingCanvas.java @@ -19,6 +19,7 @@ package android.graphics; 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 @@ import dalvik.annotation.optimization.FastNative; * {@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 @@ public final class RecordingCanvas extends BaseRecordingCanvas { // 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 c10e482f1d33..6d58d95e6fb6 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -23,6 +23,7 @@ import android.app.Application; 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.KeymasterBlob; 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.math.BigInteger; 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 @@ public class KeyStore { } } + /** + * 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 class KeyStore { 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 @@ public class KeyStore { 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 @@ public class KeyStore { 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 @@ public class KeyStore { 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 @@ public class KeyStore { 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 @@ public class KeyStore { 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 @@ public class KeyStore { 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 @@ public class KeyStore { 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 @@ public class KeyStore { 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 @@ public class KeyStore { 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 @@ public class KeyStore { 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 @@ public class KeyStore { 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 @@ public class KeyStore { 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 @@ public class KeyStore { 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 @@ public class KeyStore { } } + 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 7bbc09964584..a2d23558616b 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java @@ -182,8 +182,8 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { 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 e18d193b043b..13f50b144e30 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 f829bb7cfeed..52896b59ddaf 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.KeymasterArguments; import android.security.keymaster.KeymasterDefs; import java.security.ProviderException; +import java.util.ArrayList; +import java.util.List; /** * @hide @@ -121,35 +123,44 @@ public abstract class KeymasterUtils { 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()); + } + + 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); - args.addUnsignedLong( - KeymasterDefs.KM_TAG_USER_SECURE_ID, KeymasterArguments.toUint64(sid)); - args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, KeymasterDefs.HW_AUTH_FINGERPRINT); 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 @@ public abstract class KeymasterUtils { 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 000000000000..3a229cb610a8 --- /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 843c1461e21b..1cb0d25d8c08 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/AssetManager.cpp @@ -72,7 +72,7 @@ static volatile int32_t gCount = 0; 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/include/androidfw/AssetManager.h b/libs/androidfw/include/androidfw/AssetManager.h index cdb87bcb8e11..e22e2d239a55 100644 --- a/libs/androidfw/include/androidfw/AssetManager.h +++ b/libs/androidfw/include/androidfw/AssetManager.h @@ -59,13 +59,13 @@ class AssetManager : public AAssetManager { 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/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index d401b385075e..6ae59990d0ab 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -164,7 +164,11 @@ bool SkiaOpenGLPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior, 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 d4ffddde8def..65ced6ad9316 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -261,7 +261,7 @@ void EglManager::createPBufferSurface() { } } -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 EglManager::createSurface(EGLNativeWindowType window, ColorMode color 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 55c81d42d8a0..2a44f7e10b80 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 @@ public: 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 079520836bb5..a6073ebb5c74 100644 --- a/libs/hwui/tests/unit/RenderNodeTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeTests.cpp @@ -321,7 +321,7 @@ RENDERTHREAD_TEST(RenderNode, prepareTree_HwLayer_AVD_enqueueDamage) { // 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 000000000000..7f33f2e3424d --- /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 a1a6d9fe0e22..04c4629b5432 100644 --- a/libs/services/src/os/StatsLogEventWrapper.cpp +++ b/libs/services/src/os/StatsLogEventWrapper.cpp @@ -85,9 +85,6 @@ status_t StatsLogEventWrapper::readFromParcel(const Parcel* in) { 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 8a02a82194df..057a4ae879f4 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.SystemClock; 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 @@ public class GnssMetrics { 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 @@ public class GnssMetrics { /* 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 @@ public class GnssMetrics { 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 1cc650bf2702..823af656abaa 100644 --- a/media/java/android/media/AudioPresentation.java +++ b/media/java/android/media/AudioPresentation.java @@ -18,7 +18,6 @@ package android.media; 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 @@ public final class AudioPresentation { 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 @@ public final class AudioPresentation { 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 3ec595d9ac11..d37f8ab529a1 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 android.util.Log; 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 @@ public class AudioTrack extends PlayerBase */ 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 @@ public class AudioTrack extends PlayerBase 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 @@ public class AudioTrack extends PlayerBase } /** + * 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/MediaDrm.java b/media/java/android/media/MediaDrm.java index 24b7f36e228b..cdbc7b44f905 100644 --- a/media/java/android/media/MediaDrm.java +++ b/media/java/android/media/MediaDrm.java @@ -418,9 +418,6 @@ public final class MediaDrm implements AutoCloseable { /** * 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 @@ public final class MediaDrm implements AutoCloseable { * 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 @@ public final class MediaDrm implements AutoCloseable { /** * 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 @@ public final class MediaDrm implements AutoCloseable { * 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 @@ public final class MediaDrm implements AutoCloseable { * 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 @@ public final class MediaDrm implements AutoCloseable { * 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 @@ public final class MediaDrm implements AutoCloseable { * 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 4919eeb4dacb..c203fa9ca71b 100644 --- a/media/java/android/media/MediaExtractor.java +++ b/media/java/android/media/MediaExtractor.java @@ -272,10 +272,12 @@ final public class MediaExtractor { 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 @@ final public class MediaExtractor { } /** + * 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 @@ final public class MediaExtractor { 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 @@ final public class MediaExtractor { 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 d10cbbc295c7..5dee16e03542 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -919,7 +919,7 @@ public final class MediaFormat { * 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 @@ public final class MediaFormat { * 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/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java index 4e901629b114..a80511a118cf 100644 --- a/media/java/android/media/MediaPlayer2.java +++ b/media/java/android/media/MediaPlayer2.java @@ -21,22 +21,59 @@ import android.annotation.IntDef; 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,27 +262,109 @@ import java.util.concurrent.Executor; * 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 { - private final CloseGuard mGuard = CloseGuard.get(); - - /** - * Create a MediaPlayer2 object. - * - * @return A MediaPlayer2 object created - */ - public static final MediaPlayer2 create(Context context) { - return new MediaPlayer2Impl(context); + 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(); + /** - * @hide + * 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> */ - // add hidden empty constructor so it doesn't show in SDK - public MediaPlayer2() { + 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)); } + private native void native_setup(int sessionId, Object mediaplayer2This); + /** * Releases the resources held by this {@code MediaPlayer2} object. * @@ -275,8 +394,41 @@ public abstract class MediaPlayer2 implements AutoCloseable 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 @@ public abstract class MediaPlayer2 implements AutoCloseable } 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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable */ // 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 @@ public abstract class MediaPlayer2 implements AutoCloseable * 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 @@ public abstract class MediaPlayer2 implements AutoCloseable */ // 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 @@ public abstract class MediaPlayer2 implements AutoCloseable */ // 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 @@ public abstract class MediaPlayer2 implements AutoCloseable * 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 @@ public abstract class MediaPlayer2 implements AutoCloseable * * 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 @@ public abstract class MediaPlayer2 implements AutoCloseable */ // 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 @@ public abstract class MediaPlayer2 implements AutoCloseable */ // 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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * 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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * 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 abstract class MediaPlayer2 implements AutoCloseable 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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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(); - - /* 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"; + public List<TrackInfo> getTrackInfo() { + TrackInfo[] trackInfo = getInbandTrackInfo(); + return Arrays.asList(trackInfo); + } - /** - * 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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable 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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 abstract class MediaPlayer2 implements AutoCloseable 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 abstract class MediaPlayer2 implements AutoCloseable 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 @@ public abstract class MediaPlayer2 implements AutoCloseable * '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 @@ public abstract class MediaPlayer2 implements AutoCloseable * 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 abstract class MediaPlayer2 implements AutoCloseable 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 @@ public abstract class MediaPlayer2 implements AutoCloseable 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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 abstract class MediaPlayer2 implements AutoCloseable 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 @@ public abstract class MediaPlayer2 implements AutoCloseable * 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 @@ public abstract class MediaPlayer2 implements AutoCloseable * @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 @@ public abstract class MediaPlayer2 implements AutoCloseable * 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 abstract class MediaPlayer2 implements AutoCloseable 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 9b97b1060c8a..000000000000 --- a/media/java/android/media/MediaPlayer2Impl.java +++ /dev/null @@ -1,3155 +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() { - 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( - 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 000000000000..c6dee22813a0 --- /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/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java index 97468423219a..7480fa0f2101 100644 --- a/media/java/android/media/audiofx/AudioEffect.java +++ b/media/java/android/media/audiofx/AudioEffect.java @@ -989,7 +989,7 @@ public class AudioEffect { // -------------------- /** * 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 @@ public class AudioEffect { /** * 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 900e3bb62fcf..b5e221324c97 100644 --- a/media/java/android/media/projection/MediaProjectionManager.java +++ b/media/java/android/media/projection/MediaProjectionManager.java @@ -90,6 +90,8 @@ public final class MediaProjectionManager { * 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/update/MediaControlView2Provider.java b/media/java/android/media/update/MediaControlView2Provider.java deleted file mode 100644 index 8e69653c6a31..000000000000 --- 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/StaticProvider.java b/media/java/android/media/update/StaticProvider.java index 8687b802add8..2bc17a9f1c08 100644 --- a/media/java/android/media/update/StaticProvider.java +++ b/media/java/android/media/update/StaticProvider.java @@ -16,7 +16,6 @@ package android.media.update; -import android.annotation.Nullable; import android.app.Notification; import android.content.Context; import android.media.MediaBrowser2; @@ -48,9 +47,6 @@ 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 +58,6 @@ import java.util.concurrent.Executor; * @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); diff --git a/media/java/android/media/update/VideoView2Provider.java b/media/java/android/media/update/VideoView2Provider.java deleted file mode 100644 index 27b436fd1b79..000000000000 --- 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 6b4f15d0fdb7..000000000000 --- 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 67e8cea871e9..000000000000 --- 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 0f531c9e61e8..faf4301a7245 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -97,7 +97,6 @@ cc_library_shared { 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 0769e5c84674..f7de2e78e822 100644 --- a/media/jni/android_media_MediaPlayer2.cpp +++ b/media/jni/android_media_MediaPlayer2.cpp @@ -175,7 +175,7 @@ JNIMediaPlayer2Listener::JNIMediaPlayer2Listener(JNIEnv* env, jobject thiz, jobj // 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 @@ setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface, jboolean mediaPlaye 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 @@ android_media_MediaPlayer2_native_init(JNIEnv *env) { jclass clazz; - clazz = env->FindClass("android/media/MediaPlayer2Impl"); + clazz = env->FindClass("android/media/MediaPlayer2"); if (clazz == NULL) { return; } @@ -1009,10 +1009,11 @@ android_media_MediaPlayer2_native_init(JNIEnv *env) } 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_native_finalize(JNIEnv *env, jobject thiz) 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 @@ static void android_media_MediaPlayer2_set_audio_session_id(JNIEnv *env, jobjec 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 @@ static const JNINativeMethod gMethods[] = { {"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 @@ static const JNINativeMethod gMethods[] = { {"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 @@ static const JNINativeMethod gMethods[] = { }; // 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 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) } 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 942614815c3d..942eafdbc48d 100644 --- a/native/graphics/jni/Android.bp +++ b/native/graphics/jni/Android.bp @@ -37,6 +37,7 @@ cc_library_shared { 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 000000000000..d8c5ac969128 --- /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 6a33fe208416..000000000000 --- 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 89beb754b52c..56825cee4520 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 9ecaa03d6e53..5ab66324930d 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 000000000000..d460041e59ae --- /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 000000000000..ffd57a430662 --- /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 fa6f3fae2e33..cf4dc824f597 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 36d5e19d8a84..cdcb5a54daed 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 8eb259b57b77..7773eeb22e04 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 8ce9deb1eb78..4dd8aa0c536c 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 b75d76e69f7c..fb703cfaadc9 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 fe189edaf311..a2c9ed809ba3 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 09dcc5f5989f..be649a50f26c 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 dc0dd17c0bf4..8183105a1aff 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 d8f7be991846..a9b7415d8427 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 cb6171093f12..16bf6e22761d 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 2e8d1f082d1f..f940299af6a8 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 2e8d1f082d1f..f940299af6a8 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 5d7ba9163895..c01166474074 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 da2eae9038bf..65244e7e9156 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 41fcb9a6bd6a..e4c4c9801d5c 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 2e4cc5134e1b..27b9b7f15fab 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 1976f7d1c1e6..8086fbf96088 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 8f98bb5131ca..39fc5692bc21 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 1bacc4680f1c..d924fffb8c1a 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 e44cd3b22cf2..11b1dd3f50e9 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 f15fb49634e1..145e2abd0906 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 10e3de6b9f24..4a335dd38979 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 a01a55339da4..2cc4038fbe63 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 8e7915d83cc6..527e69247104 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 e275b95849c1..bcc8686f8c65 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 75f2b48d98c2..7a7f7e075b30 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 17da83fd15ce..158f7cea00d8 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 95b855884c11..a42cb220a0d1 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 933721ace248..aaa51c8fbe3f 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 0dd5b6ceefa5..29c23ed2ee61 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 1c5960155f4d..2cbca06c13dd 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 17f20df33717..9ba066ebb12d 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 94b9d60f69da..5bef235af136 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 3d1064cf98d9..ebe4148fcca9 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 cf1b6b5cd478..e2e4eac97876 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 6966bcd69290..c0153e6d7611 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 54763be28912..8ba24b1c4ed9 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 7dd0b374f483..b7d9a8a81b8b 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 f6042897e493..967c8ba87ac1 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 8cf70413ef67..75356f01d81f 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 1c8b6e1c9dcd..feb2ddeba9c1 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 9a3a626d7dbe..11a2131dc64c 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 565ef8f486e4..07a247992197 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 73d2455dcf3a..cdedd3306a01 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 0e818d3e3ca2..0f4cd1672427 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 e51d2aa6cf9e..9c702b953fd5 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 ce822e7f24bf..70c2a08682af 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 9010e1eebbb6..df1c700582ff 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 5b535e2a62cf..2a2e39729f2e 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 866ba187df16..794364588f94 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 f486fe4c5ddf..e9698dbbd784 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 0ba37aedc8f7..83084c519f19 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.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC; 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.NetworkRequest; 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 androidx.swiperefreshlayout.widget.SwipeRefreshLayout; 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.SslErrorHandler; 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 @@ public class CaptivePortalLoginActivity extends Activity { @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 @@ public class CaptivePortalLoginActivity extends Activity { 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 @@ public class CaptivePortalLoginActivity extends Activity { 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 @@ public class CaptivePortalLoginActivity extends Activity { 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 @@ public class CaptivePortalLoginActivity extends Activity { " 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 @@ public class CaptivePortalLoginActivity extends Activity { 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 @@ public class CaptivePortalLoginActivity extends Activity { 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/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 4abcf73af109..c9ee5c87de0f 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -541,14 +541,14 @@ public class ExternalStorageProvider extends FileSystemProvider { } @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 8447b083b9e1..8af20e20ede9 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 5773d9ef710c..000000000000 --- 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 60efed41870c..b81dd83d2586 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 02b7ea6ef7a8..66bbb3a6c890 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 0dbc037f2298..2f082b959e68 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 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro 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 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro * {@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 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro 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/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml index b51ad1cb4922..51f6a4b92413 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 000000000000..5bf345d4f73b --- /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 000000000000..572e97b6badc --- /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 000000000000..eb7b8eeb9251 --- /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 000000000000..ff136eb44aac --- /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 8247c27ff850..36d06591460b 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 000000000000..eb3e9632dd35 --- /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 000000000000..d6bdf9fbaa78 --- /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 ddefb6a43a6f..cbdd51b24388 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 b5e24a04f85e..2f7d486a1372 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 000000000000..f05f7bad36ba --- /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 f554150ab0d0..890bf5d8ac45 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 980442c488f4..f34161e285da 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 e7f2c51d124b..22b8d2ff4db0 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 bb0c6f6acb06..df858f0c54e2 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 0e41a7fd7d28..97f5f8672b7d 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,34 @@ 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> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 50454fc9bcf2..4a0bc9b81378 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 6244e1c4a509..e9aa1b65f48a 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 dc4eb3b71faa..65c52200a7d8 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 @@ public class SyncRtSurfaceTransactionApplier { 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/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index 79966f78373c..c41ef0ede89e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -243,46 +243,6 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe } } - /** - * 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/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index e3584cf7493e..36664004a4a6 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -88,8 +88,6 @@ public class SwipeHelper implements Gefingerpoken { 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 @@ public class SwipeHelper implements Gefingerpoken { mDisableHwLayers = disableHwLayers; } - public boolean isSwipingInProgress() { - return mSwipingInProgress; - } - private float getPos(MotionEvent ev) { return mSwipeDirection == X ? ev.getX() : ev.getY(); } @@ -325,7 +319,6 @@ public class SwipeHelper implements Gefingerpoken { 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 @@ public class SwipeHelper implements Gefingerpoken { wasRemoved = row.isRemoved(); } if (!mCancelled || wasRemoved) { - mSwipingInProgress = false; mCallback.onChildDismissed(animView); } if (endAction != null) { @@ -637,7 +629,6 @@ public class SwipeHelper implements Gefingerpoken { !swipedFastEnough() /* useAccelerateInterpolator */); } else { // snappity - mSwipingInProgress = false; mCallback.onDragCancelled(mCurrView); snapChild(mCurrView, 0 /* leftTarget */, velocity); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java index ce84b84935c5..01a234544c5b 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 android.provider.Settings; 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 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi * --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 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi 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 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi } 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/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java index c61e10aa77ab..4557b4de4901 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 @@ public class DozeService extends DreamService private DozeMachine mDozeMachine; private DozeServicePlugin mDozePlugin; + private PluginManager mPluginManager; public DozeService() { setDebug(DEBUG); @@ -53,14 +54,14 @@ public class DozeService extends DreamService 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/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt index fc1baeff706e..d3715d04b7bc 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 @@ class OngoingPrivacyChip @JvmOverloads constructor( 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 @@ class OngoingPrivacyChip @JvmOverloads constructor( 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 @@ class OngoingPrivacyChip @JvmOverloads constructor( 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 1d0e16ed3334..f6a95af4a075 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.AlertDialog 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 @@ class OngoingPrivacyDialog constructor( 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 @@ class OngoingPrivacyDialog constructor( 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 + + title.setText(dialogBuilder.getDialogTitle()) - 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) + 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 - } - addView(image, LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.MATCH_PARENT) + app.icon?.let { + appIcon.setImageDrawable(it) } - dialogBuilder.generateIcons().forEach { - it.mutate() - it.setTint(iconColor) - iconsContainer.addIcon(it) - } - dialogBuilder.app.let { - it?.icon?.let { iconsContainer.addIcon(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) + } + icons.visibility = View.VISIBLE + } else { + icons.visibility = View.GONE } + 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 5ce4ee738373..519df19f0e20 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt @@ -15,59 +15,53 @@ package com.android.systemui.privacy import android.content.Context +import android.graphics.drawable.Drawable import com.android.systemui.R -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 + } + + fun generateIconsForApp(types: List<PrivacyType>): List<Drawable> { + return types.sorted().map { it.getIcon(context) } } - 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 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) } } diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt index f4099021a0bd..85e99f05f895 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 @@ import com.android.systemui.R 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 5141e5055e9b..3fa3e8eec0ab 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 @@ class PrivacyItemController(val context: Context, val callback: Callback) { 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/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index e3f85d93b8da..427f638b0d30 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 @@ public class QuickStatusBarHeader extends RelativeLayout implements 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 @@ public class QuickStatusBarHeader extends RelativeLayout implements 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 class QuickStatusBarHeader extends RelativeLayout implements 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/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java index 938dc0be2d85..90890c076a3c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java @@ -21,6 +21,7 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_ 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; @@ -54,7 +55,6 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> 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; @@ -65,7 +65,7 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> @Override public boolean isAvailable() { - return ColorDisplayController.isAvailable(mContext); + return ColorDisplayManager.isNightDisplayAvailable(mContext); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 97534edf1cb3..bed0c45b7db9 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.ClipData; 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.os.UserHandle; 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.shared.system.ActivityManagerWrapper; 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 SaveImageInBackgroundData { 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 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { 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 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { } @Override - protected Void doInBackground(Void... params) { + protected Void doInBackground(Void... paramsUnused) { if (isCancelled()) { return null; } @@ -252,36 +244,27 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { 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/statusbar/NotificationUiAdjustment.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java index f42c6ef7356d..f23ae3f6bfb7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java @@ -53,7 +53,8 @@ public class NotificationUiAdjustment { 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/notification/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java index 4e712a5054ea..da6d977f3f5b 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 class NotificationData { 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 @@ public class NotificationData { 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 3bea7db14313..274d4b07b017 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -702,7 +702,6 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. && !mPresenter.isPresenterFullyCollapsed(); row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); - row.setSmartActions(entry.smartActions); row.setEntry(entry); row.updateInflationFlag(FLAG_CONTENT_VIEW_HEADS_UP, shouldHeadsUp(entry)); 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 5166e061c3ef..c7876cf43026 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 @@ -39,7 +39,6 @@ import android.animation.ObjectAnimator; 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; @@ -1567,10 +1566,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mNotificationInflater.setUsesIncreasedHeight(use); } - public void setSmartActions(List<Notification.Action> smartActions) { - mNotificationInflater.setSmartActions(smartActions); - } - public void setUseIncreasedHeadsUpHeight(boolean use) { mUseIncreasedHeadsUpHeight = use; mNotificationInflater.setUsesIncreasedHeadsUpHeight(use); 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 b838c9b5482d..37bf06edde4f 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.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; 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 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx } 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 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx /** * 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 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx 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 38d6b3593be2..e1c2f7359ce3 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 com.android.systemui.util.Assert; 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 @@ public class NotificationInflater { 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 @@ public class NotificationInflater { mUsesIncreasedHeight = usesIncreasedHeight; } - public void setSmartActions(List<Notification.Action> smartActions) { - mSmartActions = smartActions; - } - public void setUsesIncreasedHeadsUpHeight(boolean usesIncreasedHeight) { mUsesIncreasedHeadsUpHeight = usesIncreasedHeight; } @@ -258,8 +250,7 @@ public class NotificationInflater { 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 @@ public class NotificationInflater { 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 @@ public class NotificationInflater { 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 @@ public class NotificationInflater { = Notification.Builder.recoverBuilder(mContext, mSbn.getNotification()); - applyChanges(recoveredBuilder); - Context packageContext = mSbn.getPackageContext(mContext); Notification notification = mSbn.getNotification(); if (notification.isMediaNotification()) { @@ -834,18 +818,6 @@ public class NotificationInflater { } } - /** - * 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 522da4d51470..3a7091bb843a 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 @@ import java.util.List; 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 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G 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 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G 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 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G // 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 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G 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 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G 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 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G bindHeader(); bindPrompt(); bindButtons(); + + if (action != ACTION_NONE) { + swapContent(action, false /* don't animate */); + } } private void bindHeader() throws RemoteException { @@ -351,7 +358,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G } private void saveImportance() { - if (!mIsNonblockable) { + if (!mIsNonblockable + || mExitReason != NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS) { updateImportance(); } } @@ -421,7 +429,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G } } - private void swapContent(@SwapContentAction int action) { + private void swapContent(@NotificationInfoAction int action, boolean animate) { if (mExpandAnimation != null) { mExpandAnimation.cancel(); } @@ -432,10 +440,11 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G 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 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G 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 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G 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; - - @Override - public void onAnimationCancel(Animator animation) { - cancelled = true; - } + 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); + + mExpandAnimation = new AnimatorSet(); + mExpandAnimation.playTogether(promptAnim, confirmAnim); + mExpandAnimation.setDuration(150); + mExpandAnimation.addListener(new AnimatorListenerAdapter() { + boolean mCancelled = false; + + @Override + public void onAnimationCancel(Animator animation) { + mCancelled = true; + } - @Override - public void onAnimationEnd(Animator animation) { - if (!cancelled) { - prompt.setVisibility(isUndo ? VISIBLE : GONE); - confirmation.setVisibility(isUndo ? GONE : VISIBLE); + @Override + public void onAnimationEnd(Animator animation) { + if (!mCancelled) { + prompt.setVisibility(isUndo ? VISIBLE : GONE); + confirmation.setVisibility(isUndo ? GONE : VISIBLE); + } } - } - }); - mExpandAnimation.start(); + }); + 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 674c8ee3f35c..c16b28fbab9e 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.Interpolators; 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 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl 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 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl @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 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl // 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 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl 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 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl // 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 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl 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 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl 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 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl * 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 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl iv.setAlpha(1f); mMenuView = iv; } - mContentDescription = s; + mContentDescription = contentDescription; mGutsContent = content; } @@ -737,4 +786,23 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl 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/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java index a8ced7a01d84..1be2afe744a9 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 @@ -38,6 +38,7 @@ 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; @@ -69,7 +70,8 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { 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/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index a7329b0f181a..ff31b261eb85 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd */ private float mBackgroundXFactor = 1f; + private boolean mSwipingInProgress; + private boolean mUsingLightTheme; private boolean mQsExpanded; private boolean mForwardScrollable; @@ -3286,7 +3288,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd || 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @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 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } } + @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 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @Override public void onDragCancelled(View v) { + setSwipingInProgress(false); mFalsingManager.onNotificatonStopDismissing(); } @@ -5669,6 +5680,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd */ public void handleChildViewDismissed(View view) { + setSwipingInProgress(false); if (mDismissAllInProgress) { return; } @@ -5737,6 +5749,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @Override public void onBeginDrag(View v) { mFalsingManager.onNotificatonStartDismissing(); + setSwipingInProgress(true); mAmbientState.onBeginDrag(v); updateContinuousShadowDrawing(); if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(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 599da3b280be..f1d9549b9284 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 @@ class NotificationSwipeHelper extends SwipeHelper 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 @@ class NotificationSwipeHelper extends SwipeHelper 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 @@ class NotificationSwipeHelper extends SwipeHelper } } - 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 @@ class NotificationSwipeHelper extends SwipeHelper 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 @@ class NotificationSwipeHelper extends SwipeHelper @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 @@ class NotificationSwipeHelper extends SwipeHelper public void onMenuShown(View animView) { setExposedMenuView(getTranslatingParentView()); - mSwipingInProgress = false; mCallback.onDragCancelled(animView); Handler handler = getHandler(); @@ -422,4 +423,4 @@ class NotificationSwipeHelper extends SwipeHelper void onDismiss(); } -}
\ No newline at end of file +} 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 d5056b75a383..1d7e899afc10 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java @@ -15,6 +15,7 @@ package com.android.systemui.statusbar.phone; import android.content.Context; +import android.hardware.display.ColorDisplayManager; import android.os.Handler; import android.provider.Settings.Secure; @@ -81,7 +82,7 @@ public class AutoTileManager { Dependency.get(ManagedProfileController.class).addCallback(mProfileCallback); } if (!mAutoTracker.isAdded(NIGHT) - && ColorDisplayController.isAvailable(mContext)) { + && ColorDisplayManager.isNightDisplayAvailable(mContext)) { Dependency.get(ColorDisplayController.class).setListener(mColorDisplayCallback); } } @@ -94,7 +95,9 @@ public class AutoTileManager { 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/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 543949768f05..21c506b68319 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.telecom.TelecomManager; 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 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mDarkAmount = darkAmount; mIndicationController.setDarkAmount(darkAmount); mLockIcon.setDarkAmount(darkAmount); + dozeTimeTick(); } private static boolean isSuccessfulLaunch(int result) { @@ -840,12 +842,10 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } 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/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java index d5067b5c7e8c..1be39750d561 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.Color; 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 @@ public class LockIcon extends KeyguardAffordanceView implements OnUserInfoChange 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 @@ public class LockIcon extends KeyguardAffordanceView implements OnUserInfoChange 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/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index fa71df25c482..851e6d0b4dea 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 @@ public class NotificationPanelView extends PanelView implements } 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/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index 0e6efc8472b6..c84f3db8acb0 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.DockedStackExistsListener; 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 @@ import java.util.Locale; */ 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 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, 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 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, 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 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, 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 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, 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 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, 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 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, 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 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, 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 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, 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/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index ade063d9718f..286eea02aab3 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 enum ScrimState { 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; 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 a6a9d74a894c..1c4538e22725 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -3264,6 +3264,10 @@ public class StatusBar extends SystemUI implements DemoMode, 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(); } 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 d85e18c17252..67da8a54b68a 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 @@ public class AccessibilityManagerWrapper implements 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/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 798f8bcd7938..b5883052c9a2 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 android.view.View.VISIBLE; 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.ContextThemeWrapper; 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 @@ public class VolumeDialogImpl implements VolumeDialog { 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 @@ public class VolumeDialogImpl implements VolumeDialog { @Override public void destroy() { - mAccessibility.destroy(); mController.removeCallback(mControllerCallbackH); mHandler.removeCallbacksAndMessages(null); } @@ -356,8 +355,6 @@ public class VolumeDialogImpl implements VolumeDialog { 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 @@ public class VolumeDialogImpl implements VolumeDialog { } 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 @@ public class VolumeDialogImpl implements VolumeDialog { } 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 @@ public class VolumeDialogImpl implements VolumeDialog { 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/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt index 7204d310a76d..b23f667e4388 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 @@ import org.junit.runner.RunWith @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 8e6bfe3a5f91..f59bfaebb31d 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.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.Collections; +import java.util.List; @SmallTest @RunWith(AndroidTestingRunner.class) @@ -390,10 +392,16 @@ public class NotificationDataTest extends SysuiTestCase { @Test public void testCreateNotificationDataEntry_RankingUpdate() { Ranking ranking = mock(Ranking.class); + initStatusBarNotification(false); + + List<Notification.Action> appGeneratedSmartActions = + Collections.singletonList(createContextualAction("appGeneratedAction")); + mMockStatusBarNotification.getNotification().actions = + appGeneratedSmartActions.toArray(new Notification.Action[0]); - ArrayList<Notification.Action> smartActions = new ArrayList<>(); - smartActions.add(createAction()); - when(ranking.getSmartActions()).thenReturn(smartActions); + List<Notification.Action> systemGeneratedSmartActions = + Collections.singletonList(createAction("systemGeneratedAction")); + when(ranking.getSmartActions()).thenReturn(systemGeneratedSmartActions); when(ranking.getChannel()).thenReturn(NOTIFICATION_CHANNEL); @@ -407,7 +415,7 @@ public class NotificationDataTest extends SysuiTestCase { 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 @@ public class NotificationDataTest extends SysuiTestCase { } } - 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), + 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), - "action", + 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 9f8a5cc0afdf..d1fe5af406d3 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 @@ public class NotificationEntryManagerTest extends SysuiTestCase { 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 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntryManager.updateNotificationRanking(mRankingMap); verify(mRow, never()).setEntry(eq(mEntry)); - assertEquals(0, mEntry.smartActions.size()); + assertEquals(0, mEntry.systemGeneratedSmartActions.size()); } @Test @@ -467,8 +467,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase { 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 @@ public class NotificationEntryManagerTest extends SysuiTestCase { 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 626726d4aa4f..3d2ea708e90e 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 @@ public class NotificationGutsManagerTest extends SysuiTestCase { 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 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(true) /* isForBlockingHelper */, eq(true) /* isUserSentimentNegative */, eq(false) /*isNoisy */, - eq(0)); + eq(0), + eq(NotificationInfo.ACTION_NONE)); } @Test @@ -313,7 +315,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase { 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 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(false) /* isForBlockingHelper */, eq(true) /* isUserSentimentNegative */, eq(false) /*isNoisy */, - eq(0)); + eq(0), + eq(NotificationInfo.ACTION_NONE)); } @Test @@ -343,7 +347,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase { 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 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(true) /* isForBlockingHelper */, eq(true) /* isUserSentimentNegative */, eq(true) /*isNoisy */, - eq(0)); + eq(0), + eq(NotificationInfo.ACTION_NONE)); } @Test @@ -373,7 +379,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase { 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 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(true) /* isForBlockingHelper */, eq(true) /* isUserSentimentNegative */, eq(false) /*isNoisy */, - eq(IMPORTANCE_DEFAULT)); + eq(IMPORTANCE_DEFAULT), + eq(NotificationInfo.ACTION_NONE)); } @Test @@ -403,7 +411,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase { 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 @@ public class NotificationGutsManagerTest extends SysuiTestCase { 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 37441963e32d..1cc1c637983b 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { .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 class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { .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 class NotificationInfoTest extends SysuiTestCase { 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 class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 class NotificationInfoTest extends SysuiTestCase { 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 class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { (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 class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 class NotificationInfoTest extends SysuiTestCase { 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 class NotificationInfoTest extends SysuiTestCase { 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 class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { (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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 class NotificationInfoTest extends SysuiTestCase { 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 class NotificationInfoTest extends SysuiTestCase { 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 class NotificationInfoTest extends SysuiTestCase { 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 class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 @@ public class NotificationInfoTest extends SysuiTestCase { 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 dcd59462a7f7..db39373d3ae4 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.animation.ValueAnimator.AnimatorUpdateListener; 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.plugins.statusbar.NotificationSwipeActionHelper.Snoo 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 @@ import org.mockito.stubbing.Answer; * 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 23365a419d31..1481aef6c2f4 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 @@ package com.android.systemui.statusbar.phone; 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 @@ public class AutoTileManagerTest extends SysuiTestCase { @Test public void nightTileAdded_whenActivated() { - if (!ColorDisplayController.isAvailable(mContext)) { + if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) { return; } mAutoTileManager.mColorDisplayCallback.onActivated(true); @@ -65,7 +65,7 @@ public class AutoTileManagerTest extends SysuiTestCase { @Test public void nightTileNotAdded_whenDeactivated() { - if (!ColorDisplayController.isAvailable(mContext)) { + if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) { return; } mAutoTileManager.mColorDisplayCallback.onActivated(false); @@ -74,7 +74,7 @@ public class AutoTileManagerTest extends SysuiTestCase { @Test public void nightTileAdded_whenNightModeTwilight() { - if (!ColorDisplayController.isAvailable(mContext)) { + if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) { return; } mAutoTileManager.mColorDisplayCallback.onAutoModeChanged( @@ -84,7 +84,7 @@ public class AutoTileManagerTest extends SysuiTestCase { @Test public void nightTileAdded_whenNightModeCustom() { - if (!ColorDisplayController.isAvailable(mContext)) { + if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) { return; } mAutoTileManager.mColorDisplayCallback.onAutoModeChanged( @@ -94,7 +94,7 @@ public class AutoTileManagerTest extends SysuiTestCase { @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/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index c536dca68def..5f02fad06b50 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 com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS; 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.plugins.VolumeDialogController.State; 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 @@ import java.util.function.Predicate; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper -@Ignore public class VolumeDialogImplTest extends SysuiTestCase { VolumeDialogImpl mDialog; @@ -113,6 +118,45 @@ public class VolumeDialogImplTest extends SysuiTestCase { + " 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 75d03633f5c8..295b3d8bc979 100644 --- a/packages/services/PacProcessor/Android.mk +++ b/packages/services/PacProcessor/Android.mk @@ -29,5 +29,3 @@ LOCAL_CERTIFICATE := platform 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 000000000000..2a94237f2aed --- /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 254cbc23c471..000000000000 --- 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 8ae587296d45..3e07d12b9a99 100644 --- a/proto/src/metrics_constants/metrics_constants.proto +++ b/proto/src/metrics_constants/metrics_constants.proto @@ -844,6 +844,8 @@ message MetricsEvent { // 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 @@ message MetricsEvent { // 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/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 4205ac7d9f86..31238df6ea10 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -166,12 +166,12 @@ public final class AutofillManagerService 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 @@ public final class AutofillManagerService 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 @@ public final class AutofillManagerService } } - @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 @@ public final class AutofillManagerService } // 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/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 1ad83ecb1237..7621ada75976 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.SystemService; 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.PerformInitializeTask; 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.Set; 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 class BackupManagerService { 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 class BackupManagerService { 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 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { // 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 @@ public class BackupManagerService { // 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(); + + // 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; - static Trampoline getInstance() { - // Always constructed during system bringup, so no need to lazy-init - return sInstance; + // 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 @@ public class BackupManagerService { return mPendingInits; } + /** Clear all pending transport initializations. */ public void clearPendingInits() { mPendingInits.clear(); } @@ -562,28 +781,10 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { // [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 @@ public class BackupManagerService { } } - // 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 @@ public class BackupManagerService { } } - // 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 @@ public class BackupManagerService { // 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 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { // !!! 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 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { } // 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 @@ public class BackupManagerService { return agent; } + /** Unbind from a backup agent. */ public void unbindAgent(ApplicationInfo app) { try { mActivityManager.unbindBackupAgent(app); @@ -1541,11 +1503,13 @@ public class BackupManagerService { } } - // 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 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { } } - // 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 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { } } + /** 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 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { } } + /** + * 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 @@ public class BackupManagerService { // 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 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { } } - // ----- 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 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { } } + /** For adb backup/restore. */ public boolean deviceIsEncrypted() { try { return mStorageManager.getEncryptionState() @@ -1961,8 +1947,8 @@ public class BackupManagerService { */ @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 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { // 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 @@ public class BackupManagerService { // ----- 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 @@ public class BackupManagerService { }); } - // 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 @@ public class BackupManagerService { } } - // 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 @@ public class BackupManagerService { } } - // 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 @@ public class BackupManagerService { } } + /** 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 @@ public class BackupManagerService { } } + /** 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 @@ public class BackupManagerService { } } + /** + * 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 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { } } + /** Called when adb backup/restore has completed. */ public void signalAdbBackupRestoreCompletion(AdbParams params) { synchronized (params.latch) { params.latch.set(true); @@ -2698,8 +2695,10 @@ public class BackupManagerService { } } - // 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 @@ public class BackupManagerService { } } - 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 @@ public class BackupManagerService { } } - // 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 @@ public class BackupManagerService { } } - // 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 @@ public class BackupManagerService { */ } - // 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 @@ public class BackupManagerService { } } - // 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 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { } } - /** 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 @@ public class BackupManagerService { } } + /** + * 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 @@ public class BackupManagerService { } } - // 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 @@ public class BackupManagerService { } } - // 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 @@ public class BackupManagerService { } } - // 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 @@ public class BackupManagerService { } } - // 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 @@ public class BackupManagerService { } } - // 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 @@ public class BackupManagerService { } } - // 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 @@ public class BackupManagerService { mWakelock.acquire(); OnTaskFinishedListener listener = caller -> { - mTransportManager.disposeOfTransportClient(transportClient, caller); - mWakelock.release(); + mTransportManager.disposeOfTransportClient(transportClient, caller); + mWakelock.release(); }; if (MORE_DEBUG) { @@ -3324,7 +3296,7 @@ public class BackupManagerService { } } - // 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 @@ public class BackupManagerService { return mActiveRestoreSession; } + /** Clear the specified restore session. */ public void clearRestoreSession(ActiveRestoreSession currentSession) { synchronized (this) { if (currentSession != mActiveRestoreSession) { @@ -3388,8 +3361,10 @@ public class BackupManagerService { } } - // 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 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { } } + /** Checks if the package is eligible for backup. */ public boolean isAppEligibleForBackup(String packageName) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BACKUP, "isAppEligibleForBackup"); @@ -3448,6 +3424,7 @@ public class BackupManagerService { } } + /** 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 @@ public class BackupManagerService { } } + /** 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 @@ public class BackupManagerService { 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 @@ public class BackupManagerService { 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/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java index e1080260697f..755095ec1b18 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.backup.IBackupTransport; 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 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba 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 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba 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 888ad1da3fcc..617430090998 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -3,6 +3,7 @@ java_library_static { 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 @@ java_library_static { 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 c955daf2fae4..6cae887d3ffc 100644 --- a/services/core/java/com/android/server/AbstractMasterSystemService.java +++ b/services/core/java/com/android/server/AbstractMasterSystemService.java @@ -244,7 +244,7 @@ public abstract class AbstractMasterSystemService<S extends AbstractPerUserSyste */ @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 +254,7 @@ public abstract class AbstractMasterSystemService<S extends AbstractPerUserSyste * 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 +262,7 @@ public abstract class AbstractMasterSystemService<S extends AbstractPerUserSyste * 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 +274,7 @@ public abstract class AbstractMasterSystemService<S extends AbstractPerUserSyste * @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 +304,7 @@ public abstract class AbstractMasterSystemService<S extends AbstractPerUserSyste * <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 +314,23 @@ public abstract class AbstractMasterSystemService<S extends AbstractPerUserSyste */ @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 201abe689283..97977df2e8a6 100644 --- a/services/core/java/com/android/server/AbstractPerUserSystemService.java +++ b/services/core/java/com/android/server/AbstractPerUserSystemService.java @@ -167,7 +167,7 @@ public abstract class AbstractPerUserSystemService<S extends AbstractPerUserSyst @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 +267,18 @@ public abstract class AbstractPerUserSystemService<S extends AbstractPerUserSyst @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 5814064e5fbd..fa98da541e57 100644 --- a/services/core/java/com/android/server/AppOpsService.java +++ b/services/core/java/com/android/server/AppOpsService.java @@ -80,6 +80,7 @@ import android.util.SparseIntArray; 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 +220,7 @@ public class AppOpsService extends IAppOpsService.Stub { SparseIntArray mProfileOwners; + @GuardedBy("this") private CheckOpsDelegate mCheckOpsDelegate; /** @@ -1589,24 +1591,28 @@ public class AppOpsService extends IAppOpsService.Stub { 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 +1621,7 @@ public class AppOpsService extends IAppOpsService.Stub { && 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 +1633,31 @@ public class AppOpsService extends IAppOpsService.Stub { 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) { - synchronized (this) { - boolean suspended; - try { - suspended = isPackageSuspendedForUser(packageName, uid); - } catch (IllegalArgumentException ex) { - // Package not found. - suspended = false; - } + 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; - } + if (suspended) { + Slog.i(TAG, "Audio disabled for suspended package=" + packageName + + " for uid=" + uid); + return AppOpsManager.MODE_IGNORED; + } + synchronized (this) { final int mode = checkRestrictionLocked(code, usage, uid, packageName); if (mode != AppOpsManager.MODE_ALLOWED) { return mode; @@ -1754,11 +1760,11 @@ public class AppOpsService extends IAppOpsService.Stub { 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); } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index b5217ad7af19..c660cc6c6f75 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -677,6 +677,8 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { 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(); } @@ -856,6 +861,8 @@ public final class ActiveServices { } } + 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(); } @@ -2517,6 +2524,8 @@ public final class ActiveServices { 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(); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 771d3765d902..4e417bab7d6a 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_AM; 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.Instrumentation; 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub } } - 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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); @@ -18757,6 +18762,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (memoryStat == null) { continue; } + // TODO(rslawik): Delete RSS high-water mark field. ProcessMemoryState processMemoryState = new ProcessMemoryState(uid, r.processName, @@ -18775,6 +18781,20 @@ public class ActivityManagerService extends IActivityManager.Stub } @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/GlobalSettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java deleted file mode 100644 index 1366c218299e..000000000000 --- 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/MemoryStatUtil.java b/services/core/java/com/android/server/am/MemoryStatUtil.java index 80b4f77c294f..cc3da1c63c00 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 @@ import java.util.regex.Pattern; * 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 @@ public final class MemoryStatUtil { 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 79c98e550774..5208ca5eb0db 100644 --- a/services/core/java/com/android/server/am/OWNERS +++ b/services/core/java/com/android/server/am/OWNERS @@ -27,4 +27,4 @@ toddke@google.com 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 745c126367ac..faf85615eb0d 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.SystemClock; 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 @@ final class ProcessRecord implements WindowProcessListener { 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 @@ final class ProcessRecord implements WindowProcessListener { : 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/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java new file mode 100644 index 000000000000..a5848ca0235a --- /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 278c55f74ebb..5f09189bd84a 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 class BiometricService extends SystemService { 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 @@ public class BiometricService extends SystemService { 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 a769590447ab..65537adccbc6 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 android.util.SparseArray; 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 @@ public class ClipboardService extends SystemService { 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 @@ public class ClipboardService extends SystemService { 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 @@ public class ClipboardService extends SystemService { 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 @@ public class ClipboardService extends SystemService { switch (op) { case AppOpsManager.OP_READ_CLIPBOARD: - // Clipboard can only be read by applications with focus. - boolean uidFocused = mWm.isUidFocused(callingUid); - if (!uidFocused) { + // 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); + } + if (!allowed) { Slog.e(TAG, "Denying clipboard access to " + callingPackage - + ", application is not in focus."); + + ", application is not in focus neither is the IntelligeService for " + + "user " + userId); } - return uidFocused; + 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 0e727c5e0b5f..929dfc4d1511 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 @@ public class LingerMonitor { 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 c20079e8a685..3a31c9c5e05f 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.IInputFilter; 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 @@ public class InputManagerService extends IInputManager.Stub 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 @@ public class InputManagerService extends IInputManager.Stub } 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 @@ public class InputManagerService extends IInputManager.Stub * @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 @@ public class InputManagerService extends IInputManager.Stub } // 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 @@ public class InputManagerService extends IInputManager.Stub } // 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 class InputManagerService extends IInputManager.Stub 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 000000000000..0ed56ff4ca13 --- /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 002d4e1049fb..6612d0264aff 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 @@ import java.util.function.Supplier; * 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 @@ public class ContextHubClientBroker extends IContextHubClient.Stub /* * 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 class ContextHubClientBroker extends IContextHubClient.Stub 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, + 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) { + 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 @@ public class ContextHubClientBroker extends IContextHubClient.Stub 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 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } /** - * @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 @@ public class ContextHubClientBroker extends IContextHubClient.Stub */ @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 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } /** - * @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) + && 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 */); } - return (pendingIntent != null) && pendingIntent.equals(intent); } /** @@ -446,11 +370,29 @@ public class ContextHubClientBroker extends IContextHubClient.Stub // 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 fe93a1a0f613..00b7d62738c0 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.ContextHubInfo; 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 @@ import java.util.function.Consumer; /* * 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 @@ import java.util.function.Consumer; /** * 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 @@ import java.util.function.Consumer; } /** - * 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"); - } - - if (!broker.setCallback(clientCallback)) { - return null; // Client process has died, so we return null + /* 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"; + } } - 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 @@ import java.util.function.Consumer; } /** - * @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 @@ import java.util.function.Consumer; * @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 215e67c0884f..36b0342c21d8 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 @@ public class ContextHubService extends IContextHubService.Stub { 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 @@ public class ContextHubService extends IContextHubService.Stub { /** * 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 @@ public class ContextHubService extends IContextHubService.Stub { */ @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 @@ public class ContextHubService extends IContextHubService.Stub { } 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/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index a6ea6b2e1aeb..fccff57e3234 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 @@ public final class MediaProjectionManagerService extends SystemService } 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 1c7572ee4e2c..6195ed9e0d79 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -870,6 +870,7 @@ public class NotificationManagerService extends SystemService { } if (expanded && userAction) { r.recordExpanded(); + reportUserInteraction(r); } EventLogTags.writeNotificationExpansion(key, userAction ? 1 : 0, expanded ? 1 : 0, @@ -885,6 +886,9 @@ public class NotificationManagerService extends SystemService { 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 +1164,7 @@ public class NotificationManagerService extends SystemService { 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 +1193,7 @@ public class NotificationManagerService extends SystemService { mConditionProviders.onUserUnlocked(userId); mListeners.onUserUnlocked(userId); mZenModeHelper.onUserUnlocked(userId); + mPreferencesHelper.onUserUnlocked(userId); } } } @@ -1982,7 +1988,7 @@ public class NotificationManagerService extends SystemService { } /** - * 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 +2531,19 @@ public class NotificationManagerService extends SystemService { } @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(); } @@ -4521,6 +4540,7 @@ public class NotificationManagerService extends SystemService { 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 +5549,7 @@ public class NotificationManagerService extends SystemService { 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 +5562,7 @@ public class NotificationManagerService extends SystemService { 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 +5579,8 @@ public class NotificationManagerService extends SystemService { || !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 +6582,7 @@ public class NotificationManagerService extends SystemService { 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 +6613,8 @@ public class NotificationManagerService extends SystemService { 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 +6629,7 @@ public class NotificationManagerService extends SystemService { 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) { diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index a11b03f68667..1a9257cf17fc 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 @@ public final class NotificationRecord { 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 @@ public final class NotificationRecord { } } 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 @@ public final class NotificationRecord { 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 8fce5e3acdc2..fd65ebe102ac 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 @@ public class PreferencesHelper implements RankingConfig { // 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 @@ public class PreferencesHelper implements RankingConfig { private SparseBooleanArray mBadgingEnabled; private boolean mAreChannelsBypassingDnd; - public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler, ZenModeHelper zenHelper) { mContext = context; @@ -129,11 +127,7 @@ public class PreferencesHelper implements RankingConfig { 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 @@ public class PreferencesHelper implements RankingConfig { // 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 @@ public class PreferencesHelper implements RankingConfig { // 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 @@ public class PreferencesHelper implements RankingConfig { boolean bypassDnd = channel.canBypassDnd(); existing.setBypassDnd(bypassDnd); - if (bypassDnd != mAreChannelsBypassingDnd) { - updateChannelsBypassingDnd(); + if (bypassDnd != mAreChannelsBypassingDnd + || previousExistingImportance != existing.getImportance()) { + updateChannelsBypassingDnd(mContext.getUserId()); } } @@ -613,7 +610,7 @@ public class PreferencesHelper implements RankingConfig { 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 @@ public class PreferencesHelper implements RankingConfig { 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 @@ public class PreferencesHelper implements RankingConfig { MetricsLogger.action(lm); if (mAreChannelsBypassingDnd && channel.canBypassDnd()) { - updateChannelsBypassingDnd(); + updateChannelsBypassingDnd(mContext.getUserId()); } } } @@ -859,6 +857,27 @@ public class PreferencesHelper implements RankingConfig { } /** + * 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 @@ public class PreferencesHelper implements RankingConfig { 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 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()) { + 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 numPackagePreferencess = mPackagePreferences.size(); - for (int PackagePreferencesIndex = 0; PackagePreferencesIndex < numPackagePreferencess; - PackagePreferencesIndex++) { - final PackagePreferences r = mPackagePreferences.valueAt(PackagePreferencesIndex); - final int numChannels = r.channels.size(); - - 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. + 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 @@ public class PreferencesHelper implements RankingConfig { } } } - // If no channels bypass DND, update the zen policy once to disable DND bypass. if (mAreChannelsBypassingDnd) { mAreChannelsBypassingDnd = false; @@ -951,6 +1013,22 @@ public class PreferencesHelper implements RankingConfig { } } + 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 @@ public class PreferencesHelper implements RankingConfig { 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 807c343d0d10..731e6bcfb074 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 @@ class IdmapManager { 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 @@ class IdmapManager { } 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 @@ class IdmapManager { } 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 36bf83dfe92c..572d36897040 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 @@ final class OverlayManagerSettings { 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 275f3dcdb6d2..b4903817f787 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 @@ public class LauncherAppsService extends SystemService { } 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 8a0c416ece04..e4e8010ac9e9 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -12937,6 +12937,25 @@ public class PackageManagerService extends IPackageManager.Stub } } + @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 +13019,7 @@ public class PackageManagerService extends IPackageManager.Stub } 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/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index 580e4f481a27..3a74ab51e9c7 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.Installer.InstallerException; 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 a7a7686b2a6b..de3c9f28218d 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 @@ package com.android.server.pm.dex; 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 d2600b5060b5..9a12a2f140bf 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.internal.os.ClassLoaderFactory; 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 86f7380e331b..519a20d51ddb 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.OutputStreamWriter; 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/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 97af04524d5a..282746af2784 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { } /** - * @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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 a55b49fe028d..f78d2639df1a 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.app.ActivityManager; 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/role/RemoteRoleControllerService.java b/services/core/java/com/android/server/role/RemoteRoleControllerService.java index b670291ba94b..7d34270734df 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.IRoleControllerService; 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 @@ import java.util.Queue; */ 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 @@ public class RemoteRoleControllerService { 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 @@ public class RemoteRoleControllerService { 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 @@ public class RemoteRoleControllerService { 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 @@ public class RemoteRoleControllerService { 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 @@ public class RemoteRoleControllerService { 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 @@ public class RemoteRoleControllerService { 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 @@ public class RemoteRoleControllerService { 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 @@ public class RemoteRoleControllerService { } } - @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 @@ public class RemoteRoleControllerService { } } + @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 @@ public class RemoteRoleControllerService { @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 ded075d5742c..d01e7621dd39 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 com.android.server.SystemService; 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 class RoleManagerService extends SystemService { 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 01d02d61cc83..d2ca85023fe8 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.getPidsForCommands; 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; @@ -47,6 +48,7 @@ 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; @@ -87,6 +89,8 @@ import com.android.internal.annotations.GuardedBy; 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; @@ -162,6 +166,37 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { */ 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 { @@ -205,6 +240,10 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @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"); @@ -366,6 +405,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { 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) { @@ -373,14 +414,24 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { 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"); } @@ -422,7 +473,14 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { 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); @@ -1028,7 +1086,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { 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); @@ -1049,6 +1107,33 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } + 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) { @@ -1430,6 +1515,73 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { 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, @@ -1598,6 +1750,10 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { 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; @@ -1655,6 +1811,18 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { 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; diff --git a/services/core/java/com/android/server/timezone/RulesManagerService.java b/services/core/java/com/android/server/timezone/RulesManagerService.java index 23c4a337001b..c4d285115ce7 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.Arrays; 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 @@ public final class RulesManagerService extends IRulesManager.Stub { @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 61e14143f162..1c08d039207b 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 @@ class ActivityMetricsLogger { 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 23f812559566..c43e64ec5b98 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.LOCK_TASK_MODE_NONE; 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,20 +76,6 @@ import static android.os.Process.SYSTEM_UID; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT; -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; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_VISIBILITY; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_FOCUS; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SAVED_STATE; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_STATES; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH; -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; @@ -96,6 +83,8 @@ 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; @@ -106,17 +95,28 @@ 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; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_VISIBILITY; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_FOCUS; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SAVED_STATE; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_STATES; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH; +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.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; 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 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo // 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 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo inHistory = false; visible = false; nowVisible = false; + mDrawn = false; idle = false; hasBeenLaunched = false; mStackSupervisor = supervisor; @@ -1944,8 +1946,12 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } @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 97eaafcdc622..082f5213cb5d 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.AppOpsManager; 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.DisplayManager.DisplayListener; 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 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } 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 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D <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 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D * @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 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // 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 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } 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 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D 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 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D * @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 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // 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 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } 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 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D 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 83db8de3ff98..d4c1bcad57b3 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 @@ class ActivityStarter { 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 @@ class ActivityStarter { 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 @@ class ActivityStarter { || 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 3cbb25776420..bd1460ae4566 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 @@ public class AppWindowContainerController 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 @@ public class AppWindowContainerController } 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 @@ public class AppWindowContainerController 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 8a39a7408058..ad27669fc030 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 @@ package com.android.server.wm; /** 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 3cece11c0f38..92944a02f77a 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.Slog; 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 android.view.animation.Animation; 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 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree if (controller != null) { controller.reportWindowsDrawn(); } + } else { + if (controller != null) { + controller.reportWindowsNotDrawn(); + } } reportedDrawn = nowDrawn; } @@ -2015,9 +2019,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree 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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 767a6ef49414..a3e80294a53c 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.SHOW_TRANSACTIONS; 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 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo */ 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 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo 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; @@ -1928,7 +1936,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * 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 +2808,12 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo 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 +3005,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // 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 +3038,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } 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 +3059,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo 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 +3098,23 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo 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 +4503,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo 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 ce8c979d3f87..7ed078a93375 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.SurfaceSession; 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 5483602487a2..a379266fe533 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 android.view.animation.Interpolator; 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 @@ class DragState { 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 585a4f55c9c2..49bedc97a38a 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.os.UserHandle; 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 @@ class InputConsumerImpl implements IBinder.DeathRecipient { 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 f823caadad79..92ea1a90735c 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 static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; 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 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal * 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 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal */ @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 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal */ @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 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal */ @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 0e4ab53e5c56..61bc4e405385 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.Log; 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 c4fbee931df3..6627c2dc5cc3 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.Slog; 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; 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 01b05c38df67..1baca321d01d 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 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { // 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 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { 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 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { 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 66063c405943..31c0c7f588c3 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 @@ class SurfaceAnimator { .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 66ebc9b27358..7182ad6fccfe 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.MotionEvent; 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 @@ class TaskPositioner { 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 f2d3dca27cf9..51567a0d6ad0 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 android.view.IWindow; 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 073601ddefc6..912cb7fe449a 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 @@ public class TaskStack extends WindowContainer<Task> implements 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 02904d413b35..c47b22fd4082 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 @@ public class WindowManagerService extends IWindowManager.Stub */ 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 99f65c327375..a117cf33e596 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 android.view.animation.Interpolator; 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @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 3943dba7092e..e2db80744294 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 @@ static jobject getInputApplicationHandleObjLocalRef(JNIEnv* env, 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 @@ public: 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 @@ public: 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 @@ void NativeInputManager::setDisplayViewports(JNIEnv* env, jobjectArray viewportO } 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 @@ void NativeInputManager::notifyConfigurationChanged(nsecs_t when) { } 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 @@ nsecs_t NativeInputManager::notifyANR(const sp<InputApplicationHandle>& inputApp 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 @@ nsecs_t NativeInputManager::notifyANR(const sp<InputApplicationHandle>& inputApp } 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 @@ void NativeInputManager::notifyInputChannelBroken(const sp<InputWindowHandle>& i 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 @@ void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when, } 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 @@ nsecs_t NativeInputManager::interceptKeyBeforeDispatching( 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 @@ nsecs_t NativeInputManager::interceptKeyBeforeDispatching( } 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 @@ bool NativeInputManager::dispatchUnhandledKey(const sp<InputWindowHandle>& input 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 @@ bool NativeInputManager::dispatchUnhandledKey(const sp<InputWindowHandle>& input } else { ALOGE("Failed to obtain key event object for dispatchUnhandledKey."); } - env->DeleteLocalRef(inputWindowHandleObj); } return result; } @@ -1316,7 +1302,7 @@ static void handleInputChannelDisposed(JNIEnv* env, } 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 @@ static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */, 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 @@ static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */, } // 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 @@ static const JNINativeMethod gInputManagerMethods[] = { { "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 @@ static const JNINativeMethod gInputManagerMethods[] = { (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 @@ int register_android_server_InputManager(JNIEnv* env) { "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 @@ int register_android_server_InputManager(JNIEnv* env) { 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 9cab1ed15b9e..b7d34d7a8025 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 @@ final class ContentCaptureSession implements RemoteIntelligenceServiceCallbacks } /** - * 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 @@ final class ContentCaptureSession implements RemoteIntelligenceServiceCallbacks @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 43d4a4476c11..fcfd24625161 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 @@ package com.android.server.intelligence; 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; @@ -53,18 +54,20 @@ public final class 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 final class IntelligenceManagerService 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 @@ public final class IntelligenceManagerService } } } + + 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 584b872c64d0..471b40f82d45 100644 --- a/services/intelligence/java/com/android/server/intelligence/IntelligencePerUserService.java +++ b/services/intelligence/java/com/android/server/intelligence/IntelligencePerUserService.java @@ -88,12 +88,18 @@ final class IntelligencePerUserService @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 +134,15 @@ final class IntelligencePerUserService // 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 +151,19 @@ final class IntelligencePerUserService 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 +177,27 @@ final class IntelligencePerUserService 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 73990f800ac1..f8ac41f3c40c 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.Configuration; 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.util.TimingsTraceLog; 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.CountDownLatch; 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 @@ public final class SystemServer { 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 @@ public final class SystemServer { 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 @@ public final class SystemServer { // // 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 @@ public final class SystemServer { // 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 @@ public final class SystemServer { 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 @@ public final class SystemServer { } /** - * 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 @@ public final class SystemServer { } /** - * 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 @@ public final class SystemServer { 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 @@ public final class SystemServer { 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 @@ public final class SystemServer { } 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 @@ public final class SystemServer { 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 @@ public final class SystemServer { 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 @@ public final class SystemServer { if (!disableSystemTextClassifier) { traceBeginAndSlog("StartTextClassificationManagerService"); - mSystemServiceManager.startService(TextClassificationManagerService.Lifecycle.class); + mSystemServiceManager + .startService(TextClassificationManagerService.Lifecycle.class); traceEnd(); } @@ -1196,41 +1206,41 @@ public final class SystemServer { 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 @@ public final class SystemServer { } 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 @@ public final class SystemServer { 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 @@ public final class SystemServer { 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 @@ public final class SystemServer { 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 @@ public final class SystemServer { } 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 @@ public final class SystemServer { 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 @@ public final class SystemServer { 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 @@ public final class SystemServer { } 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 @@ public final class SystemServer { traceBeginAndSlog("AddGraphicsStatsService"); ServiceManager.addService(GraphicsStatsService.GRAPHICS_STATS_SERVICE, - new GraphicsStatsService(context)); + new GraphicsStatsService(context)); traceEnd(); if (CoverageService.ENABLED) { @@ -1769,12 +1779,6 @@ public final class SystemServer { traceEnd(); } - if (!disableIntelligence) { - traceBeginAndSlog("StartIntelligenceService"); - mSystemServiceManager.startService(INTELLIGENCE_MANAGER_SERVICE_CLASS); - traceEnd(); - } - traceBeginAndSlog("AppServiceManager"); mSystemServiceManager.startService(AppBindingService.Lifecycle.class); traceEnd(); @@ -1825,7 +1829,7 @@ public final class SystemServer { // 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 @@ public final class SystemServer { 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 @@ public final class SystemServer { 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 @@ public final class SystemServer { 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 @@ public final class SystemServer { 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 @@ public final class SystemServer { // 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 @@ public final class SystemServer { 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 9ab06a129d88..565152c31448 100644 --- a/services/robotests/Android.mk +++ b/services/robotests/Android.mk @@ -27,7 +27,8 @@ LOCAL_PRIVILEGED_MODULE := true 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 c4cb59339aee..25d1cc7fbc19 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 org.robolectric.shadows.ShadowSettings; 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { () -> 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { .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 @@ public class BackupManagerServiceTest { () -> 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 @@ public class BackupManagerServiceTest { .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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { .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 @@ public class BackupManagerServiceTest { .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 @@ public class BackupManagerServiceTest { () -> 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 @@ public class BackupManagerServiceTest { .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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { "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 @@ public class BackupManagerServiceTest { "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 @@ public class BackupManagerServiceTest { "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 @@ public class BackupManagerServiceTest { "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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { () -> 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { 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 @@ public class BackupManagerServiceTest { */ @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/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 c162c3b9cc42..000000000000 --- 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 000000000000..d965f8a34fa4 --- /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/timezone/RulesManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java index c252609b5d88..1b106dd37163 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 java.util.concurrent.Executor; 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 @@ public class RulesManagerServiceTest { 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 @@ public class RulesManagerServiceTest { 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 @@ public class RulesManagerServiceTest { 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 @@ public class RulesManagerServiceTest { 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 @@ public class RulesManagerServiceTest { 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/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java index f4da4b338cef..c1655bcdb451 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.hamcrest.Matchers.is; 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 @@ public class DisplayContentTests extends WindowTestsBase { final WindowState imeAppTarget = createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget"); - mWm.mInputMethodTarget = imeAppTarget; + mDisplayContent.mInputMethodTarget = imeAppTarget; assertForAllWindowsOrder(Arrays.asList( mWallpaperWindow, @@ -124,8 +125,8 @@ public class DisplayContentTests extends WindowTestsBase { } @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 @@ public class DisplayContentTests extends WindowTestsBase { } @Test - public void testForAllWindows_WithStatusBarImeTarget() { - mWm.mInputMethodTarget = mStatusBarWindow; + public void testForAllWindows_WithStatusBarImeTarget() throws Exception { + mDisplayContent.mInputMethodTarget = mStatusBarWindow; assertForAllWindowsOrder(Arrays.asList( mWallpaperWindow, @@ -568,6 +569,32 @@ public class DisplayContentTests extends WindowTestsBase { 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 e8d0a066dade..99deeb9e9397 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 @@ public class TestIWindow extends IWindow.Stub { } @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 2b8b93428701..fcde08e18a6f 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 @@ public class WindowContainerTraversalTests extends WindowTestsBase { 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 2abe64dd80a4..53858c7c8905 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 @@ class WindowTestsBase { 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 3c8ae3cecad6..3dcea75b8ae5 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 @@ public class ZOrderingTests extends WindowTestsBase { @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 @@ public class ZOrderingTests extends WindowTestsBase { @Test public void testAssignWindowLayers_ForImeWithAppTarget() { final WindowState imeAppTarget = createWindow("imeAppTarget"); - mWm.mInputMethodTarget = imeAppTarget; + mDisplayContent.mInputMethodTarget = imeAppTarget; mDisplayContent.assignChildLayers(mTransaction); @@ -253,7 +253,7 @@ public class ZOrderingTests extends WindowTestsBase { 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 @@ public class ZOrderingTests extends WindowTestsBase { 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 @@ public class ZOrderingTests extends WindowTestsBase { 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 @@ public class ZOrderingTests extends WindowTestsBase { @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 f17a30ddb1b6..410ab8732a08 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 @@ public class NotificationAdjustmentExtractorTest extends UiServiceTestCase { 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 9b41fdd9fd68..8690110ca66a 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 @@ public class NotificationRecordTest extends UiServiceTestCase { 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 3fe381b0abe2..1a218b23368d 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 junit.framework.Assert.fail; 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 @@ public class PreferencesHelperTest extends UiServiceTestCase { 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 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @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 f692a571b019..16dd92f5f6c3 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_FULLSCREEN_OR_SPLIT 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.app.WaitResult; 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 @@ public class ActivityStackSupervisorTests extends ActivityTestsBase { 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 f7d7ad6d986f..88479ec432c5 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 @@ public class ActivityStarterTests extends ActivityTestsBase { 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 32875dab465f..94d7dbb49b74 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 @@ class UserUsageStatsService { 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 33df6f9c37d7..906d64c1f619 100644 --- a/startop/view_compiler/dex_builder.cc +++ b/startop/view_compiler/dex_builder.cc @@ -49,18 +49,27 @@ std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode) { 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 @@ ir::String* DexBuilder::GetOrAddString(const std::string& string) { 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* DexBuilder::GetOrAddType(const std::string& descriptor) { 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 @@ ir::EncodedMethod* MethodBuilder::Encode() { 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::AddInstruction(Instruction instruction) { 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 @@ void MethodBuilder::BuildConst4(Value target, int value) { 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::EncodeInstructions() { 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 @@ void MethodBuilder::EncodeMove(const Instruction& instruction) { 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]; + 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]); + } - 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); + 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()) { - real_reg = RegisterValue(*instruction.dest()); - buffer_.push_back(real_reg << 8 | art::Instruction::MOVE_RESULT); + 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 @@ void MethodBuilder::EncodeBranch(art::Instruction::Code op, const Instruction& i 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 07441518ea32..adf82bf9a01a 100644 --- a/startop/view_compiler/dex_builder.h +++ b/startop/view_compiler/dex_builder.h @@ -110,18 +110,22 @@ class Value { 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 @@ class Instruction { 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 @@ class Instruction { 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 @@ class Instruction { // 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 @@ class MethodBuilder { // 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 @@ class MethodBuilder { }; 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 @@ class ClassBuilder { 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 @@ class DexBuilder { 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 169c63374cb7..e20f3a9406c0 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 @@ public class DexBuilderTest { } @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 @@ public class DexBuilderTest { 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 c521bf2b8ccf..e2bf43bc1d0c 100644 --- a/startop/view_compiler/dex_testcase_generator.cc +++ b/startop/view_compiler/dex_testcase_generator.cc @@ -53,6 +53,19 @@ void GenerateSimpleTestCases(const string& outdir) { } 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 @@ void GenerateSimpleTestCases(const string& outdir) { }(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 fbc54ae6a857..c7ecdfa9cce2 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1320,18 +1320,13 @@ public class CarrierConfigManager { 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 +2292,45 @@ public class CarrierConfigManager { 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 +2687,9 @@ public class CarrierConfigManager { 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/CellSignalStrength.java b/telephony/java/android/telephony/CellSignalStrength.java index 6090d5c7b555..fd21d42b1e9b 100644 --- a/telephony/java/android/telephony/CellSignalStrength.java +++ b/telephony/java/android/telephony/CellSignalStrength.java @@ -21,15 +21,20 @@ package android.telephony; */ 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/ICellInfoCallback.aidl b/telephony/java/android/telephony/ICellInfoCallback.aidl new file mode 100644 index 000000000000..7fb62682703a --- /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 75e8eda33cdc..aee744fac20c 100644 --- a/telephony/java/android/telephony/NetworkRegistrationState.java +++ b/telephony/java/android/telephony/NetworkRegistrationState.java @@ -70,6 +70,43 @@ public class NetworkRegistrationState implements Parcelable { /** 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 @@ public class NetworkRegistrationState implements Parcelable { private int mAccessNetworkTechnology; + @NRStatus + private int mNrStatus; + private final int mRejectCause; private final boolean mEmergencyOnly; @@ -154,6 +194,7 @@ public class NetworkRegistrationState implements Parcelable { mAvailableServices = availableServices; mCellIdentity = cellIdentity; mEmergencyOnly = emergencyOnly; + mNrStatus = NR_STATUS_NONE; } /** @@ -200,6 +241,7 @@ public class NetworkRegistrationState implements Parcelable { VoiceSpecificRegistrationStates.class.getClassLoader()); mDataSpecificStates = source.readParcelable( DataSpecificRegistrationStates.class.getClassLoader()); + mNrStatus = source.readInt(); } /** @@ -213,6 +255,19 @@ public class NetworkRegistrationState implements Parcelable { 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() { @@ -315,6 +370,19 @@ public class NetworkRegistrationState implements Parcelable { 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{") @@ -330,6 +398,7 @@ public class NetworkRegistrationState implements Parcelable { .append(" cellIdentity=").append(mCellIdentity) .append(" voiceSpecificStates=").append(mVoiceSpecificStates) .append(" dataSpecificStates=").append(mDataSpecificStates) + .append(" nrStatus=").append(nrStatusToString(mNrStatus)) .append("}").toString(); } @@ -337,7 +406,7 @@ public class NetworkRegistrationState implements Parcelable { public int hashCode() { return Objects.hash(mDomain, mTransportType, mRegState, mRoamingType, mAccessNetworkTechnology, mRejectCause, mEmergencyOnly, mAvailableServices, - mCellIdentity, mVoiceSpecificStates, mDataSpecificStates); + mCellIdentity, mVoiceSpecificStates, mDataSpecificStates, mNrStatus); } @Override @@ -359,7 +428,8 @@ public class NetworkRegistrationState implements Parcelable { && Arrays.equals(mAvailableServices, other.mAvailableServices) && Objects.equals(mCellIdentity, other.mCellIdentity) && Objects.equals(mVoiceSpecificStates, other.mVoiceSpecificStates) - && Objects.equals(mDataSpecificStates, other.mDataSpecificStates); + && Objects.equals(mDataSpecificStates, other.mDataSpecificStates) + && mNrStatus == other.mNrStatus; } @Override @@ -375,6 +445,7 @@ public class NetworkRegistrationState implements Parcelable { dest.writeParcelable(mCellIdentity, 0); dest.writeParcelable(mVoiceSpecificStates, 0); dest.writeParcelable(mDataSpecificStates, 0); + dest.writeInt(mNrStatus); } public static final Parcelable.Creator<NetworkRegistrationState> CREATOR = diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java index f5dff20eedf2..3c5ad84a62b0 100644 --- a/telephony/java/android/telephony/PhoneStateListener.java +++ b/telephony/java/android/telephony/PhoneStateListener.java @@ -21,16 +21,18 @@ import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; +import android.os.Binder; 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 +233,35 @@ public class PhoneStateListener { 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 +323,12 @@ public class PhoneStateListener { @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. @@ -358,95 +366,27 @@ public class PhoneStateListener { */ @UnsupportedAppUsage 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 +570,8 @@ public class PhoneStateListener { * @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 +579,7 @@ public class PhoneStateListener { * @param state is the current SIM data activation state * @hide */ - public void onDataActivationStateChanged(int state) { - + public void onDataActivationStateChanged(@TelephonyManager.SimActivationState int state) { } /** @@ -735,127 +674,217 @@ public class PhoneStateListener { */ 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 0937b1071e23..ab80e252c03d 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -83,7 +83,45 @@ public class ServiceState implements Parcelable { /** @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 {} /** @@ -283,6 +321,8 @@ public class ServiceState implements Parcelable { @UnsupportedAppUsage private boolean mIsUsingCarrierAggregation; + @FrequencyRange + private int mNrFrequencyRange; private int mChannelNumber; private int[] mCellBandwidths = new int[0]; @@ -375,6 +415,7 @@ public class ServiceState implements Parcelable { mLteEarfcnRsrpBoost = s.mLteEarfcnRsrpBoost; mNetworkRegistrationStates = s.mNetworkRegistrationStates == null ? null : new ArrayList<>(s.mNetworkRegistrationStates); + mNrFrequencyRange = s.mNrFrequencyRange; } /** @@ -406,6 +447,7 @@ public class ServiceState implements Parcelable { 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 @@ public class ServiceState implements Parcelable { out.writeList(mNetworkRegistrationStates); out.writeInt(mChannelNumber); out.writeIntArray(mCellBandwidths); + out.writeInt(mNrFrequencyRange); } public int describeContents() { @@ -792,7 +835,8 @@ public class ServiceState implements Parcelable { mIsEmergencyOnly, mIsUsingCarrierAggregation, mLteEarfcnRsrpBoost, - mNetworkRegistrationStates); + mNetworkRegistrationStates, + mNrFrequencyRange); } @Override @@ -823,7 +867,8 @@ public class ServiceState implements Parcelable { && 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 @@ public class ServiceState implements Parcelable { .append(", mIsUsingCarrierAggregation=").append(mIsUsingCarrierAggregation) .append(", mLteEarfcnRsrpBoost=").append(mLteEarfcnRsrpBoost) .append(", mNetworkRegistrationStates=").append(mNetworkRegistrationStates) + .append(", mNrFrequencyRange=").append(mNrFrequencyRange) .append("}").toString(); } @@ -987,6 +1033,7 @@ public class ServiceState implements Parcelable { mIsUsingCarrierAggregation = false; mLteEarfcnRsrpBoost = 0; mNetworkRegistrationStates = new ArrayList<>(); + mNrFrequencyRange = FREQUENCY_RANGE_UNKNOWN; } public void setStateOutOfService() { @@ -1225,6 +1272,7 @@ public class ServiceState implements Parcelable { m.putInt("LteEarfcnRsrpBoost", mLteEarfcnRsrpBoost); m.putInt("ChannelNumber", mChannelNumber); m.putIntArray("CellBandwidths", mCellBandwidths); + m.putInt("mNrFrequencyRange", mNrFrequencyRange); } /** @hide */ @@ -1288,6 +1336,22 @@ public class ServiceState implements Parcelable { 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 bc832c3fbfa5..240b8a981e70 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 @@ public class SignalStrength implements Parcelable { 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 22c1e58449de..b41e14e09554 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -143,9 +143,11 @@ public class SubscriptionInfo implements Parcelable { 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 @@ public class SubscriptionInfo implements Parcelable { @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 @@ public class SubscriptionInfo implements Parcelable { 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 @@ public class SubscriptionInfo implements Parcelable { this.mAccessRules = accessRules; this.mCardId = cardId; this.mIsOpportunistic = isOpportunistic; - this.mParentSubId = parentSubId; + this.mGroupUUID = groupUUID; } /** @@ -388,16 +390,16 @@ public class SubscriptionInfo implements Parcelable { } /** - * 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 @@ public class SubscriptionInfo implements Parcelable { 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 @@ public class SubscriptionInfo implements Parcelable { dest.writeTypedArray(mAccessRules, flags); dest.writeString(mCardId); dest.writeBoolean(mIsOpportunistic); - dest.writeInt(mParentSubId); + dest.writeString(mGroupUUID); } @Override @@ -559,13 +561,13 @@ public class SubscriptionInfo implements Parcelable { + " 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 @@ public class SubscriptionInfo implements Parcelable { && 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 31770ec1dce2..a6fa6bf79a9c 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -119,7 +119,6 @@ public class SubscriptionManager { @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 class SubscriptionManager { 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> * @@ -2290,7 +2298,7 @@ public class SubscriptionManager { * 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 +2373,40 @@ public class SubscriptionManager { } /** - * 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 bd1a0fbdcb47..d72b5ecd9da8 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -21,6 +21,7 @@ import static android.content.Context.TELECOM_SERVICE; 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.NetworkStats; 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.RemoteException; 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 @@ public class TelephonyManager { } /** - * 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. - * - * <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> - * This method is preferred over using {@link + * methods may return true, indicating that the cell is being used or would be used for + * signaling communication if necessary. + * + * <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>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 @@ public class TelephonyManager { 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 @@ public class TelephonyManager { /** * 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 diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index 2e9bffe9e0d9..4fd706642179 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -60,6 +60,7 @@ public class ApnSetting implements Parcelable { 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 @@ public class ApnSetting implements Parcelable { 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 @@ public class ApnSetting implements Parcelable { 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 @@ public class ApnSetting implements Parcelable { 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 @@ public class ApnSetting implements Parcelable { 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 @@ public class ApnSetting implements Parcelable { 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.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_MATCH_DATA)), - cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.APN_SET_ID))); + 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 @@ public class ApnSetting implements Parcelable { * <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 @@ public class ApnSetting implements Parcelable { 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 @@ public class ApnSetting implements Parcelable { 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 @@ public class ApnSetting implements Parcelable { 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 @@ public class ApnSetting implements Parcelable { 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 @@ public class ApnSetting implements Parcelable { // 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 @@ public class ApnSetting implements Parcelable { 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 @@ public class ApnSetting implements Parcelable { 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 class ApnSetting implements Parcelable { */ 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 @@ public class ApnSetting implements Parcelable { 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 @@ public class ApnSetting implements Parcelable { 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 @@ public class ApnSetting implements Parcelable { 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 @@ public class ApnSetting implements Parcelable { 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 @@ public class ApnSetting implements Parcelable { 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 @@ public class ApnSetting implements Parcelable { } /** + * 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 b732d4d92127..ebf198702bb9 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -186,6 +186,7 @@ public class EuiccManager { * 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 c9cf473bb482..e06c3728836c 100644 --- a/telephony/java/android/telephony/ims/ImsMmTelManager.java +++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java @@ -292,7 +292,7 @@ public class ImsMmTelManager { * 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 000000000000..916e282f642e --- /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 7f69f43f6cea..3f22f9804866 100644 --- a/telephony/java/android/telephony/ims/feature/ImsFeature.java +++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java @@ -211,12 +211,19 @@ public abstract class ImsFeature { * 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 @@ public abstract class ImsFeature { /** * @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 @@ public abstract class ImsFeature { /** * @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 @@ public abstract class ImsFeature { /** * @return true if all of the capabilities specified are capable. + * @hide */ public boolean isCapable(int capabilities) { return (mCapabilities & capabilities) == capabilities; @@ -246,6 +256,7 @@ public abstract class ImsFeature { /** * @return a deep copy of the Capabilites. + * @hide */ public Capabilities copy() { return new Capabilities(mCapabilities); @@ -253,6 +264,7 @@ public abstract class ImsFeature { /** * @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 dcd7ea714f8c..321bfff40652 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 android.util.Log; 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 @@ public class ImsConfigImplBase { } /** - * 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 class ImsConfigImplBase { */ 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 @@ public class ImsConfigImplBase { } /** - * 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 @@ public class ImsConfigImplBase { * * @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 @@ public class ImsConfigImplBase { * * @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 90e9880bd03e..71a21743a449 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 @@ import android.telephony.ims.stub.ImsConfigImplBase; * 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 @@ public class ImsConfig { /** * 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 @@ public class ImsConfig { } /** - * 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 @@ public class ImsConfig { } /** - * 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 class ImsConfig { 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 07bb18b6cd1b..34c9067c3f2f 100644 --- a/telephony/java/com/android/internal/telephony/IApnSourceService.aidl +++ b/telephony/java/com/android/internal/telephony/IApnSourceService.aidl @@ -20,5 +20,5 @@ import android.content.ContentValues; 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 4bdec088b6a1..bc4451977d92 100755 --- a/telephony/java/com/android/internal/telephony/ISub.aidl +++ b/telephony/java/com/android/internal/telephony/ISub.aidl @@ -165,13 +165,23 @@ interface ISub { 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. + * + * 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. * - * @param parentSubId: subId of its parent subscription. - * @param subId the unique SubscriptionInfo index in database - * @return the number of records updated */ - 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 3aaa32344e0b..f0e858692ffe 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.Bundle; 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.telecom.PhoneAccountHandle; 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.TelephonyHistogram; 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 @@ interface ITelephony { 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 @@ interface ITelephony { /** * 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 @@ interface ITelephony { * 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/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java index b9222a86a092..17486e09bcf9 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 static org.junit.Assert.fail; 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 @@ public class MacAddressTest { 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 0bc5221245ca..583f14ac0cbd 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -431,7 +431,7 @@ void Debug::DumpHex(const void* data, size_t len) { 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 @@ void Debug::DumpResStringPool(const android::ResStringPool* pool, text::Printer* 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 dbe5ac5adb98..da22e885b917 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -718,7 +718,7 @@ std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const Config // 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 @@ std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const Config } // 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 8eabd3225d87..a8c26668b3a5 100644 --- a/tools/aapt2/StringPool.cpp +++ b/tools/aapt2/StringPool.cpp @@ -165,12 +165,13 @@ StringPool::Ref StringPool::MakeRef(const StringPiece& str) { 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 @@ StringPool::Ref StringPool::MakeRefImpl(const StringPiece& str, const Context& c } } + 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 1006ca970dc5..115d5d315b8f 100644 --- a/tools/aapt2/StringPool.h +++ b/tools/aapt2/StringPool.h @@ -166,7 +166,8 @@ class StringPool { // 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 @@ class StringPool { 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 9a7238b584ba..648be7d33754 100644 --- a/tools/aapt2/StringPool_test.cpp +++ b/tools/aapt2/StringPool_test.cpp @@ -84,6 +84,24 @@ TEST(StringPoolTest, MaintainInsertionOrderIndex) { 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 3ea17552ea7c..4492f6b49cf0 100644 --- a/tools/aapt2/cmd/Convert.cpp +++ b/tools/aapt2/cmd/Convert.cpp @@ -57,78 +57,6 @@ class IApkSerializer { 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 @@ class Context : public IAaptContext { 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 @@ int ConvertCommand::Action(const std::vector<std::string>& args) { 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 fcec23d16f78..6a6719c91b58 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 @@ class ConvertCommand : public Command { 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/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index 8a86f63a30c1..6c1a9ba2cbad 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -573,15 +573,17 @@ class PackageFlattener { } // 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 c2e1d4b4ce8c..635cb21f514c 100644 --- a/tools/aapt2/format/binary/TableFlattener.h +++ b/tools/aapt2/format/binary/TableFlattener.h @@ -43,6 +43,9 @@ struct TableFlattenerOptions { // 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/wifi/java/android/net/wifi/INetworkRequestMatchCallback.aidl b/wifi/java/android/net/wifi/INetworkRequestMatchCallback.aidl index f472a021b031..d14ec57ea07a 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 @@ oneway interface INetworkRequestMatchCallback { 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 1fd68ec1df70..3ec8a4155292 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.ScanResult; 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 @@ interface IWifiManager 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 @@ interface IWifiManager 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 b34ac264ade4..64f8adb36a93 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -762,6 +762,13 @@ public class WifiConfiguration implements Parcelable { } /** + * 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 @@ public class WifiConfiguration implements Parcelable { 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 @@ public class WifiConfiguration implements Parcelable { 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 @@ public class WifiConfiguration implements Parcelable { 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 @@ public class WifiConfiguration implements Parcelable { 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 @@ public class WifiConfiguration implements Parcelable { 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; diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java index e37a85670972..e5dcef00f001 100644 --- a/wifi/java/android/net/wifi/WifiInfo.java +++ b/wifi/java/android/net/wifi/WifiInfo.java @@ -110,6 +110,8 @@ public class WifiInfo implements Parcelable { private boolean mEphemeral; + private boolean mTrusted; + /** * Running total count of lost (not ACKed) transmitted unicast data packets. * @hide @@ -215,6 +217,7 @@ public class WifiInfo implements Parcelable { 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 @@ public class WifiInfo implements Parcelable { 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 @@ public class WifiInfo implements Parcelable { 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 @@ public class WifiInfo implements Parcelable { 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 954b51f02820..7aff03c00dd3 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -25,7 +25,6 @@ import android.annotation.SdkConstant.SdkConstantType; 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 class WifiManager { 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 @@ public class WifiManager { * @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 @@ public class WifiManager { } /** - * 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 @@ public class WifiManager { * @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 @@ public class WifiManager { * @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 @@ public class WifiManager { @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 @@ public class WifiManager { } @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 @@ public class WifiManager { /** * 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 @@ public class WifiManager { * 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 @@ public class WifiManager { * 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 @@ public class WifiManager { * @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 @@ public class WifiManager { * @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 @@ public class WifiManager { * @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 @@ public class WifiManager { * @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 @@ public class WifiManager { * @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 @@ public class WifiManager { * @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 @@ public class WifiManager { /** * @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 55fde4ca335e..955e040a46f1 100644 --- a/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java +++ b/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java @@ -99,11 +99,7 @@ public final class WifiNetworkAgentSpecifier extends NetworkSpecifier implements 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 @@ public final class WifiNetworkAgentSpecifier extends NetworkSpecifier implements @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 67e218972994..aa8d325419f0 100644 --- a/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java +++ b/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java @@ -20,7 +20,6 @@ import static com.android.internal.util.Preconditions.checkNotNull; 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 @@ import java.util.List; * <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 @@ public class WifiNetworkConfigBuilder { /** * 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 @@ public class WifiNetworkConfigBuilder { /** * 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 000000000000..eb6995f561de --- /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 04b9cb5dfb8d..25121e2dc8c7 100644 --- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java +++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java @@ -18,7 +18,6 @@ package android.net.wifi; import static com.android.internal.util.Preconditions.checkNotNull; -import android.app.PendingIntent; import android.os.Parcel; import android.os.Parcelable; @@ -32,7 +31,7 @@ import java.util.Objects; * 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 @@ public final class WifiNetworkSuggestion implements Parcelable { /** * 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 71d4173a389c..26bdb181e4a1 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.Credential; 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 @@ public final class PasspointConfiguration implements Parcelable { } /** - * 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()) { + // Optional: PerProviderSubscription/<X+>/SubscriptionUpdate + if (mSubscriptionUpdate != null && !mSubscriptionUpdate.validate()) { return false; } - if (mCredential == null || !mCredential.validate()) { + 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; } - if (mPolicy != null && !mPolicy.validate()) { + + // Required: PerProviderSubscription/<X+>/SubscriptionUpdate + if (mSubscriptionUpdate == null || !mSubscriptionUpdate.validate()) { return false; } - if (mSubscriptionUpdate != null && !mSubscriptionUpdate.validate()) { + 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 e8fcbfd6731e..7689fc34ff5b 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 @@ package android.net.wifi.hotspot2.pps; 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 @@ public final class Credential implements Parcelable { /** * 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 @@ public final class Credential implements Parcelable { // 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 @@ public final class Credential implements Parcelable { /** * 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 @@ public final class Credential implements Parcelable { 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 @@ public final class Credential implements Parcelable { * 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 @@ public final class Credential implements Parcelable { } // 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 e6892bea4595..f58a006278d2 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java @@ -491,6 +491,17 @@ public class WifiP2pManager { /** @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 @@ public class WifiP2pManager { } /** + * 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 @@ public class WifiP2pManager { 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 @@ public class WifiP2pManager { 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 @@ public class WifiP2pManager { .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 @@ public class WifiP2pManager { /** * 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 @@ public class WifiP2pManager { 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 eede23b22387..04bc55710dfd 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.INetworkRequestMatchCallback; 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.IBinder; import android.os.Messenger; import android.os.ResultReceiver; import android.os.WorkSource; -import android.util.Slog; import java.util.List; @@ -83,22 +82,51 @@ public abstract class AbstractWifiService extends IWifiManager.Stub { 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 abstract class AbstractWifiService extends IWifiManager.Stub { 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 bf6feac1aba7..8d97307df9ca 100644 --- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java @@ -57,6 +57,7 @@ public class WifiConfigurationTest { 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); @@ -71,6 +72,7 @@ public class WifiConfigurationTest { // 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); diff --git a/wifi/tests/src/android/net/wifi/WifiInfoTest.java b/wifi/tests/src/android/net/wifi/WifiInfoTest.java index f5a8b29167bf..f9fb0626adaf 100644 --- a/wifi/tests/src/android/net/wifi/WifiInfoTest.java +++ b/wifi/tests/src/android/net/wifi/WifiInfoTest.java @@ -18,6 +18,7 @@ package android.net.wifi; 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 @@ public class WifiInfoTest { 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 @@ public class WifiInfoTest { 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 ea41bb39c4d5..8fe5af998dcf 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.assertNotNull; 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.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.List; /** * Unit tests for {@link android.net.wifi.WifiManager}. @@ -1188,7 +1202,12 @@ i * Verify that a call to cancel WPS immediately returns a failure. 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 @@ i * Verify that a call to cancel WPS immediately returns a failure. 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 1b0007c9e732..f8ab8a2cd5d7 100644 --- a/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java +++ b/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java @@ -23,7 +23,6 @@ import static org.junit.Assert.assertTrue; 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 @@ public class WifiNetworkAgentSpecifierTest { * 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 940adc809535..775ce21656f7 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 @@ public class PasspointConfigurationTest { */ private static PasspointConfiguration createConfig() { PasspointConfiguration config = new PasspointConfiguration(); + config.setUpdateIdentifier(1234); config.setHomeSp(createHomeSp()); config.setCredential(createCredential()); config.setPolicy(createPolicy()); @@ -273,18 +274,37 @@ public class PasspointConfigurationTest { @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 class PasspointConfigurationTest { public void validateConfigWithoutCredential() throws Exception { PasspointConfiguration config = createConfig(); config.setCredential(null); + assertFalse(config.validate()); + assertFalse(config.validateForR2()); } /** @@ -308,12 +330,14 @@ public class PasspointConfigurationTest { 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 class PasspointConfigurationTest { 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 class PasspointConfigurationTest { public void validateConfigWithoutSubscriptionUpdate() throws Exception { PasspointConfiguration config = createConfig(); config.setSubscriptionUpdate(null); + assertTrue(config.validate()); + assertFalse(config.validateForR2()); } /** @@ -352,12 +380,15 @@ public class PasspointConfigurationTest { 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 @@ public class PasspointConfigurationTest { 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 c5ad7de12d19..c07db6c323a2 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.net.wifi.FakeKeys; 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 @@ public class CredentialTest { @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 class CredentialTest { 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 class CredentialTest { 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 class CredentialTest { 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 class CredentialTest { 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 class CredentialTest { 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 class CredentialTest { 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 @@ public class CredentialTest { @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 class CredentialTest { 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 class CredentialTest { 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 class CredentialTest { 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 class CredentialTest { 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 @@ public class CredentialTest { @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 class CredentialTest { 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 class CredentialTest { 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 class CredentialTest { 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 class CredentialTest { 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 class CredentialTest { 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 @@ public class CredentialTest { 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)); } /** |