diff options
184 files changed, 4967 insertions, 2009 deletions
diff --git a/api/current.txt b/api/current.txt index 3193d3026abb..26fe641c59da 100644 --- a/api/current.txt +++ b/api/current.txt @@ -4265,6 +4265,7 @@ package android.app { public class Application extends android.content.ContextWrapper implements android.content.ComponentCallbacks2 { ctor public Application(); + method public static java.lang.String getProcessName(); method public void onConfigurationChanged(android.content.res.Configuration); method public void onCreate(); method public void onLowMemory(); @@ -7277,7 +7278,6 @@ package android.app.slice { public abstract class SliceProvider extends android.content.ContentProvider { ctor public SliceProvider(); method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]); - method public final java.lang.String getBindingPackage(); method public final java.lang.String getType(android.net.Uri); method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues); method public android.app.slice.Slice onBindSlice(android.net.Uri, java.util.List<android.app.slice.SliceSpec>); @@ -23257,6 +23257,7 @@ package android.media { field public static final int HEVCProfileMain = 1; // 0x1 field public static final int HEVCProfileMain10 = 2; // 0x2 field public static final int HEVCProfileMain10HDR10 = 4096; // 0x1000 + field public static final int HEVCProfileMainStill = 4; // 0x4 field public static final int MPEG2LevelH14 = 2; // 0x2 field public static final int MPEG2LevelHL = 3; // 0x3 field public static final int MPEG2LevelHP = 4; // 0x4 @@ -24050,13 +24051,13 @@ package android.media { ctor public MediaMetadataRetriever(); method public java.lang.String extractMetadata(int); method public byte[] getEmbeddedPicture(); - method public android.graphics.Bitmap getFrameAtIndex(int); + method public android.graphics.Bitmap getFrameAtIndex(int, android.media.MediaMetadataRetriever.BitmapParams); method public android.graphics.Bitmap getFrameAtTime(long, int); method public android.graphics.Bitmap getFrameAtTime(long); method public android.graphics.Bitmap getFrameAtTime(); - method public android.graphics.Bitmap[] getFramesAtIndex(int, int); - method public android.graphics.Bitmap getImageAtIndex(int); - method public android.graphics.Bitmap getPrimaryImage(); + method public java.util.List<android.graphics.Bitmap> getFramesAtIndex(int, int, android.media.MediaMetadataRetriever.BitmapParams); + method public android.graphics.Bitmap getImageAtIndex(int, android.media.MediaMetadataRetriever.BitmapParams); + method public android.graphics.Bitmap getPrimaryImage(android.media.MediaMetadataRetriever.BitmapParams); method public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int); method public void release(); method public void setDataSource(java.lang.String) throws java.lang.IllegalArgumentException; @@ -24102,6 +24103,13 @@ package android.media { field public static final int OPTION_PREVIOUS_SYNC = 0; // 0x0 } + public static final class MediaMetadataRetriever.BitmapParams { + ctor public MediaMetadataRetriever.BitmapParams(); + method public android.graphics.Bitmap.Config getActualConfig(); + method public android.graphics.Bitmap.Config getPreferredConfig(); + method public void setPreferredConfig(android.graphics.Bitmap.Config); + } + public final class MediaMuxer { ctor public MediaMuxer(java.lang.String, int) throws java.io.IOException; ctor public MediaMuxer(java.io.FileDescriptor, int) throws java.io.IOException; @@ -24349,7 +24357,6 @@ package android.media { method public abstract android.media.MediaDrm.KeyRequest getDrmKeyRequest(byte[], byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer2.NoDrmSchemeException; method public abstract java.lang.String getDrmPropertyString(java.lang.String) throws android.media.MediaPlayer2.NoDrmSchemeException; method public abstract long getDuration(); - method public abstract int getMediaPlayer2State(); method public abstract android.os.PersistableBundle getMetrics(); method public abstract android.media.PlaybackParams getPlaybackParams(); method public abstract int getSelectedTrack(int); @@ -24375,33 +24382,35 @@ package android.media { method public abstract void setPlaybackParams(android.media.PlaybackParams); method public abstract void setSurface(android.view.Surface); method public abstract void setSyncParams(android.media.SyncParams); - field public static final int MEDIAPLAYER2_STATE_ERROR = 5; // 0x5 - field public static final int MEDIAPLAYER2_STATE_IDLE = 1; // 0x1 - field public static final int MEDIAPLAYER2_STATE_PAUSED = 3; // 0x3 - field public static final int MEDIAPLAYER2_STATE_PLAYING = 4; // 0x4 - field public static final int MEDIAPLAYER2_STATE_PREPARED = 2; // 0x2 - field public static final int MEDIA_CALL_ATTACH_AUX_EFFECT = 1; // 0x1 - field public static final int MEDIA_CALL_DESELECT_TRACK = 2; // 0x2 - field public static final int MEDIA_CALL_LOOP_CURRENT = 3; // 0x3 - field public static final int MEDIA_CALL_PAUSE = 4; // 0x4 - field public static final int MEDIA_CALL_PLAY = 5; // 0x5 - field public static final int MEDIA_CALL_PREPARE = 6; // 0x6 - field public static final int MEDIA_CALL_RELEASE_DRM = 12; // 0xc - field public static final int MEDIA_CALL_RESTORE_DRM_KEYS = 13; // 0xd - field public static final int MEDIA_CALL_SEEK_TO = 14; // 0xe - field public static final int MEDIA_CALL_SELECT_TRACK = 15; // 0xf - field public static final int MEDIA_CALL_SET_AUDIO_ATTRIBUTES = 16; // 0x10 - field public static final int MEDIA_CALL_SET_AUDIO_SESSION_ID = 17; // 0x11 - field public static final int MEDIA_CALL_SET_AUX_EFFECT_SEND_LEVEL = 18; // 0x12 - field public static final int MEDIA_CALL_SET_DATA_SOURCE = 19; // 0x13 - field public static final int MEDIA_CALL_SET_NEXT_DATA_SOURCE = 22; // 0x16 - field public static final int MEDIA_CALL_SET_NEXT_DATA_SOURCES = 23; // 0x17 - field public static final int MEDIA_CALL_SET_PLAYBACK_PARAMS = 24; // 0x18 - field public static final int MEDIA_CALL_SET_PLAYBACK_SPEED = 25; // 0x19 - field public static final int MEDIA_CALL_SET_PLAYER_VOLUME = 26; // 0x1a - field public static final int MEDIA_CALL_SET_SURFACE = 27; // 0x1b - field public static final int MEDIA_CALL_SET_SYNC_PARAMS = 28; // 0x1c - field public static final int MEDIA_CALL_SKIP_TO_NEXT = 29; // 0x1d + field public static final int CALL_COMPLETED_ATTACH_AUX_EFFECT = 1; // 0x1 + field public static final int CALL_COMPLETED_DESELECT_TRACK = 2; // 0x2 + field public static final int CALL_COMPLETED_LOOP_CURRENT = 3; // 0x3 + field public static final int CALL_COMPLETED_PAUSE = 4; // 0x4 + field public static final int CALL_COMPLETED_PLAY = 5; // 0x5 + field public static final int CALL_COMPLETED_PREPARE = 6; // 0x6 + field public static final int CALL_COMPLETED_RELEASE_DRM = 12; // 0xc + field public static final int CALL_COMPLETED_RESTORE_DRM_KEYS = 13; // 0xd + field public static final int CALL_COMPLETED_SEEK_TO = 14; // 0xe + field public static final int CALL_COMPLETED_SELECT_TRACK = 15; // 0xf + field public static final int CALL_COMPLETED_SET_AUDIO_ATTRIBUTES = 16; // 0x10 + field public static final int CALL_COMPLETED_SET_AUDIO_SESSION_ID = 17; // 0x11 + field public static final int CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL = 18; // 0x12 + field public static final int CALL_COMPLETED_SET_DATA_SOURCE = 19; // 0x13 + field public static final int CALL_COMPLETED_SET_NEXT_DATA_SOURCE = 22; // 0x16 + field public static final int CALL_COMPLETED_SET_NEXT_DATA_SOURCES = 23; // 0x17 + field public static final int CALL_COMPLETED_SET_PLAYBACK_PARAMS = 24; // 0x18 + field public static final int CALL_COMPLETED_SET_PLAYBACK_SPEED = 25; // 0x19 + field public static final int CALL_COMPLETED_SET_PLAYER_VOLUME = 26; // 0x1a + field public static final int CALL_COMPLETED_SET_SURFACE = 27; // 0x1b + field public static final int CALL_COMPLETED_SET_SYNC_PARAMS = 28; // 0x1c + field public static final int CALL_COMPLETED_SKIP_TO_NEXT = 29; // 0x1d + field public static final int CALL_STATUS_BAD_VALUE = 2; // 0x2 + field public static final int CALL_STATUS_ERROR_IO = 4; // 0x4 + field public static final int CALL_STATUS_ERROR_UNKNOWN = -2147483648; // 0x80000000 + field public static final int CALL_STATUS_INVALID_OPERATION = 1; // 0x1 + field public static final int CALL_STATUS_NO_DRM_SCHEME = 5; // 0x5 + field public static final int CALL_STATUS_NO_ERROR = 0; // 0x0 + field public static final int CALL_STATUS_PERMISSION_DENIED = 3; // 0x3 field public static final int MEDIA_ERROR_IO = -1004; // 0xfffffc14 field public static final int MEDIA_ERROR_MALFORMED = -1007; // 0xfffffc11 field public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200; // 0xc8 @@ -24451,7 +24460,7 @@ package android.media { public static abstract class MediaPlayer2.MediaPlayer2EventCallback { ctor public MediaPlayer2.MediaPlayer2EventCallback(); - method public void onCallComplete(android.media.MediaPlayer2, android.media.DataSourceDesc, int, int); + method public void onCallCompleted(android.media.MediaPlayer2, android.media.DataSourceDesc, int, int); method public void onCommandLabelReached(android.media.MediaPlayer2, java.lang.Object); method public void onError(android.media.MediaPlayer2, android.media.DataSourceDesc, int, int); method public void onInfo(android.media.MediaPlayer2, android.media.DataSourceDesc, int, int); @@ -43071,8 +43080,6 @@ package android.telephony { method public android.telephony.TelephonyManager createForPhoneAccountHandle(android.telecom.PhoneAccountHandle); method public android.telephony.TelephonyManager createForSubscriptionId(int); method public java.util.List<android.telephony.CellInfo> getAllCellInfo(); - method public int getAndroidCarrierIdForSubscription(); - method public java.lang.CharSequence getAndroidCarrierNameForSubscription(); method public int getCallState(); method public android.os.PersistableBundle getCarrierConfig(); method public deprecated android.telephony.CellLocation getCellLocation(); @@ -43103,6 +43110,8 @@ package android.telephony { method public int getPhoneType(); method public android.telephony.ServiceState getServiceState(); method public android.telephony.SignalStrength getSignalStrength(); + method public int getSimCarrierId(); + method public java.lang.CharSequence getSimCarrierIdName(); method public java.lang.String getSimCountryIso(); method public java.lang.String getSimOperator(); method public java.lang.String getSimOperatorName(); @@ -46623,8 +46632,8 @@ package android.view { } public final class DisplayCutout { - ctor public DisplayCutout(android.graphics.Rect, android.graphics.Region); - method public android.graphics.Region getBounds(); + ctor public DisplayCutout(android.graphics.Rect, java.util.List<android.graphics.Rect>); + method public java.util.List<android.graphics.Rect> getBoundingRects(); method public int getSafeInsetBottom(); method public int getSafeInsetLeft(); method public int getSafeInsetRight(); @@ -49698,9 +49707,9 @@ package android.view { field public static final int LAST_SUB_WINDOW = 1999; // 0x7cf field public static final int LAST_SYSTEM_WINDOW = 2999; // 0xbb7 field public static final int LAYOUT_CHANGED = 1; // 0x1 - field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS = 1; // 0x1 field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT = 0; // 0x0 field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER = 2; // 0x2 + field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES = 1; // 0x1 field public static final int MEMORY_TYPE_CHANGED = 256; // 0x100 field public static final deprecated int MEMORY_TYPE_GPU = 2; // 0x2 field public static final deprecated int MEMORY_TYPE_HARDWARE = 1; // 0x1 @@ -51067,7 +51076,8 @@ package android.view.textclassifier { method public java.lang.String getWidgetVersion(); } - public final class SelectionEvent { + public final class SelectionEvent implements android.os.Parcelable { + method public int describeContents(); method public long getDurationSincePreviousEvent(); method public long getDurationSinceSessionStart(); method public int getEnd(); @@ -51084,6 +51094,7 @@ package android.view.textclassifier { method public int getStart(); method public java.lang.String getWidgetType(); method public java.lang.String getWidgetVersion(); + method public void writeToParcel(android.os.Parcel, int); field public static final int ACTION_ABANDON = 107; // 0x6b field public static final int ACTION_COPY = 101; // 0x65 field public static final int ACTION_CUT = 103; // 0x67 @@ -51095,6 +51106,7 @@ package android.view.textclassifier { field public static final int ACTION_SELECT_ALL = 200; // 0xc8 field public static final int ACTION_SHARE = 104; // 0x68 field public static final int ACTION_SMART_SHARE = 105; // 0x69 + field public static final android.os.Parcelable.Creator<android.view.textclassifier.SelectionEvent> CREATOR; field public static final int EVENT_AUTO_SELECTION = 5; // 0x5 field public static final int EVENT_SELECTION_MODIFIED = 2; // 0x2 field public static final int EVENT_SELECTION_STARTED = 1; // 0x1 diff --git a/api/system-current.txt b/api/system-current.txt index d35a06be45df..43425cb2da83 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -377,6 +377,7 @@ package android.app { method public boolean setBroadcastSubscriber(long, long, android.app.PendingIntent); method public boolean setDataFetchOperation(long, android.app.PendingIntent); field public static final java.lang.String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED"; + field public static final java.lang.String EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES = "android.app.extra.STATS_BROADCAST_SUBSCRIBER_COOKIES"; field public static final java.lang.String EXTRA_STATS_CONFIG_KEY = "android.app.extra.STATS_CONFIG_KEY"; field public static final java.lang.String EXTRA_STATS_CONFIG_UID = "android.app.extra.STATS_CONFIG_UID"; field public static final java.lang.String EXTRA_STATS_DIMENSIONS_VALUE = "android.app.extra.STATS_DIMENSIONS_VALUE"; @@ -716,7 +717,9 @@ package android.app.usage { } public static final class UsageEvents.Event { + method public java.lang.String getNotificationChannelId(); method public int getStandbyBucket(); + field public static final int NOTIFICATION_INTERRUPTION = 12; // 0xc field public static final int NOTIFICATION_SEEN = 10; // 0xa field public static final int STANDBY_BUCKET_CHANGED = 11; // 0xb } @@ -4703,9 +4706,11 @@ package android.service.textclassifier { public abstract class TextClassifierService extends android.app.Service { ctor public TextClassifierService(); + method public final android.view.textclassifier.TextClassifier getLocalTextClassifier(); method public final android.os.IBinder onBind(android.content.Intent); method public abstract void onClassifyText(java.lang.CharSequence, int, int, android.view.textclassifier.TextClassification.Options, android.os.CancellationSignal, android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextClassification>); method public abstract void onGenerateLinks(java.lang.CharSequence, android.view.textclassifier.TextLinks.Options, android.os.CancellationSignal, android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextLinks>); + method public void onSelectionEvent(android.view.textclassifier.SelectionEvent); method public abstract void onSuggestSelection(java.lang.CharSequence, int, int, android.view.textclassifier.TextSelection.Options, android.os.CancellationSignal, android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextSelection>); field public static final java.lang.String SERVICE_INTERFACE = "android.service.textclassifier.TextClassifierService"; } @@ -5500,6 +5505,9 @@ package android.telephony.ims { } public final class ImsCallProfile implements android.os.Parcelable { + ctor public ImsCallProfile(); + ctor public ImsCallProfile(int, int); + ctor public ImsCallProfile(int, int, android.os.Bundle, android.telephony.ims.ImsStreamMediaProfile); method public int describeContents(); method public java.lang.String getCallExtra(java.lang.String); method public java.lang.String getCallExtra(java.lang.String, java.lang.String); @@ -5523,6 +5531,7 @@ package android.telephony.ims { method public void setCallExtraInt(java.lang.String, int); method public void updateCallExtras(android.telephony.ims.ImsCallProfile); method public void updateCallType(android.telephony.ims.ImsCallProfile); + method public void updateMediaProfile(android.telephony.ims.ImsCallProfile); method public void writeToParcel(android.os.Parcel, int); field public static final int CALL_RESTRICT_CAUSE_DISABLED = 2; // 0x2 field public static final int CALL_RESTRICT_CAUSE_HD = 3; // 0x3 @@ -5860,6 +5869,7 @@ package android.telephony.ims { } public final class ImsStreamMediaProfile implements android.os.Parcelable { + ctor public ImsStreamMediaProfile(int, int, int, int, int); method public void copyFrom(android.telephony.ims.ImsStreamMediaProfile); method public int describeContents(); method public int getAudioDirection(); @@ -6667,6 +6677,7 @@ package android.webkit { method public abstract android.view.View findFocus(android.view.View); method public abstract android.view.accessibility.AccessibilityNodeProvider getAccessibilityNodeProvider(); method public abstract android.os.Handler getHandler(android.os.Handler); + method public default boolean isVisibleToUserForAutofill(int); method public abstract void onActivityResult(int, int, android.content.Intent); method public abstract void onAttachedToWindow(); method public default boolean onCheckIsTextEditor(); diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 9bfbd38f8932..8ba35b76435f 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -23,6 +23,7 @@ option java_outer_classname = "AtomsProto"; import "frameworks/base/cmds/statsd/src/atom_field_options.proto"; import "frameworks/base/core/proto/android/app/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/telecomm/enums.proto"; @@ -106,6 +107,9 @@ message Atom { KeyguardBouncerPasswordEntered keyguard_bouncer_password_entered = 64; AppDied app_died=65; ResourceConfigurationChanged resource_configuration_changed = 66; + BluetoothEnabledStateChanged bluetooth_enabled_state_changed = 67; + BluetoothConnectionStateChanged bluetooth_connection_state_changed = 68; + BluetoothA2dpAudioStateChanged bluetooth_a2dp_audio_state_changed = 69; // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15. } @@ -904,6 +908,63 @@ message ResourceConfigurationChanged { } /** + * Logs when Bluetooth is enabled and disabled. + * + * Logged from: + * services/core/java/com/android/server/BluetoothManagerService.java + */ +message BluetoothEnabledStateChanged { + repeated AttributionNode attribution_node = 1; + // Whether or not bluetooth is enabled on the device. + enum State { + UNKNOWN = 0; + ENABLED = 1; + DISABLED = 2; + } + optional State state = 2; + // The reason for being enabled/disabled. + // Eg. Airplane mode, crash, application request. + optional android.bluetooth.EnableDisableReasonEnum reason = 3; + // If the reason is an application request, this will be the package name. + optional string pkgName = 4; +} + +/** + * Logs when a Bluetooth device connects and disconnects. + * + * Logged from: + * packages/apps/Bluetooth/src/com/android/bluetooth/btservice/AdapterProperties.java + */ +message BluetoothConnectionStateChanged { + // The state of the connection. + // Eg: CONNECTING, CONNECTED, DISCONNECTING, DISCONNECTED. + optional android.bluetooth.ConnectionStateEnum state = 1; + // An identifier that can be used to match connect and disconnect events. + // Currently is last two bytes of a hash of a device level ID and + // the mac address of the bluetooth device that is connected. + optional int32 obfuscated_id = 2; + // The profile that is connected. Eg. GATT, A2DP, HEADSET. + // From android.bluetooth.BluetoothAdapter.java + optional int32 bt_profile = 3; +} + +/** + * Logs when Bluetooth A2dp audio streaming state changes. + * + * Logged from: + * TODO(b/73971848) + */ +message BluetoothA2dpAudioStateChanged { + // Whether or not audio is being played using Bluetooth A2dp. + enum State { + UNKNOWN = 0; + PLAY = 1; + STOP = 2; + } + optional State state = 1; +} + +/** * Logs the duration of a davey (jank of >=700ms) when it occurs * * Logged from: diff --git a/cmds/statsd/src/external/StatsPuller.cpp b/cmds/statsd/src/external/StatsPuller.cpp index 9513cc521af7..3b0cd349168d 100644 --- a/cmds/statsd/src/external/StatsPuller.cpp +++ b/cmds/statsd/src/external/StatsPuller.cpp @@ -21,6 +21,7 @@ #include "guardrail/StatsdStats.h" #include "puller_util.h" #include "stats_log_util.h" +#include "StatsPullerManagerImpl.h" namespace android { namespace os { @@ -34,11 +35,7 @@ void StatsPuller::SetUidMap(const sp<UidMap>& uidMap) { mUidMap = uidMap; } // ValueMetric has a minimum bucket size of 10min so that we don't pull too frequently StatsPuller::StatsPuller(const int tagId) : mTagId(tagId) { - if (StatsdStats::kPullerCooldownMap.find(tagId) == StatsdStats::kPullerCooldownMap.end()) { - mCoolDownSec = StatsdStats::kDefaultPullerCooldown; - } else { - mCoolDownSec = StatsdStats::kPullerCooldownMap[tagId]; - } + mCoolDownSec = StatsPullerManagerImpl::kAllPullAtomInfo.find(tagId)->second.coolDownSec; VLOG("Puller for tag %d created. Cooldown set to %ld", mTagId, mCoolDownSec); } diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp index d626d901cdd0..c6c4d135ad3f 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.cpp +++ b/cmds/statsd/src/guardrail/StatsdStats.cpp @@ -91,18 +91,6 @@ const int FIELD_ID_UID_MAP_BYTES_USED = 3; const int FIELD_ID_UID_MAP_DROPPED_SNAPSHOTS = 4; const int FIELD_ID_UID_MAP_DROPPED_CHANGES = 5; -std::map<int, long> StatsdStats::kPullerCooldownMap = { - {android::util::KERNEL_WAKELOCK, 1}, - {android::util::WIFI_BYTES_TRANSFER, 1}, - {android::util::MOBILE_BYTES_TRANSFER, 1}, - {android::util::WIFI_BYTES_TRANSFER_BY_FG_BG, 1}, - {android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG, 1}, - {android::util::SUBSYSTEM_SLEEP_STATE, 1}, - {android::util::CPU_TIME_PER_FREQ, 1}, - {android::util::CPU_TIME_PER_UID, 1}, - {android::util::CPU_TIME_PER_UID_FREQ, 1}, -}; - // TODO: add stats for pulled atoms. StatsdStats::StatsdStats() { mPushedAtomStats.resize(android::util::kMaxPushedAtomId + 1); diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h index c3f401324454..d05c91b8d188 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.h +++ b/cmds/statsd/src/guardrail/StatsdStats.h @@ -111,13 +111,6 @@ public: /* Min period between two checks of byte size per config key in nanoseconds. */ static const unsigned long long kMinByteSizeCheckPeriodNs = 10 * NS_PER_SEC; - // Default minimum interval between pulls for an atom. Pullers can return cached values if - // another pull request happens within this interval. - static std::map<int, long> kPullerCooldownMap; - - // Default cooldown time for a puller - static const long kDefaultPullerCooldown = 1; - // Maximum age (30 days) that files on disk can exist in seconds. static const int kMaxAgeSecond = 60 * 60 * 24 * 30; diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto index 1e8aa12112bc..1c99e2ad03bc 100644 --- a/cmds/statsd/src/statsd_config.proto +++ b/cmds/statsd/src/statsd_config.proto @@ -295,6 +295,7 @@ message PerfettoDetails { message BroadcastSubscriberDetails { optional int64 subscriber_id = 1; + repeated string cookie = 2; } message Subscription { diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.cpp b/cmds/statsd/src/subscriber/SubscriberReporter.cpp index 95ecf8033f4c..25d2257c752b 100644 --- a/cmds/statsd/src/subscriber/SubscriberReporter.cpp +++ b/cmds/statsd/src/subscriber/SubscriberReporter.cpp @@ -77,6 +77,12 @@ void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey, } int64_t subscriberId = subscription.broadcast_subscriber_details().subscriber_id(); + vector<String16> cookies; + cookies.reserve(subscription.broadcast_subscriber_details().cookie_size()); + for (auto& cookie : subscription.broadcast_subscriber_details().cookie()) { + cookies.push_back(String16(cookie.c_str())); + } + auto it1 = mIntentMap.find(configKey); if (it1 == mIntentMap.end()) { ALOGW("Cannot inform subscriber for missing config key %s ", configKey.ToString().c_str()); @@ -88,12 +94,13 @@ void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey, configKey.ToString().c_str(), (long long)subscriberId); return; } - sendBroadcastLocked(it2->second, configKey, subscription, dimKey); + sendBroadcastLocked(it2->second, configKey, subscription, cookies, dimKey); } void SubscriberReporter::sendBroadcastLocked(const sp<IBinder>& intentSender, const ConfigKey& configKey, const Subscription& subscription, + const vector<String16>& cookies, const MetricDimensionKey& dimKey) const { VLOG("SubscriberReporter::sendBroadcastLocked called."); if (mStatsCompanionService == nullptr) { @@ -101,11 +108,16 @@ void SubscriberReporter::sendBroadcastLocked(const sp<IBinder>& intentSender, return; } mStatsCompanionService->sendSubscriberBroadcast( - intentSender, configKey.GetUid(), configKey.GetId(), subscription.id(), - subscription.rule_id(), getStatsDimensionsValue(dimKey.getDimensionKeyInWhat())); + intentSender, + configKey.GetUid(), + configKey.GetId(), + subscription.id(), + subscription.rule_id(), + cookies, + getStatsDimensionsValue(dimKey.getDimensionKeyInWhat())); } -void getStatsDimensionsValueHelper(const std::vector<FieldValue>& dims, size_t* index, int depth, +void getStatsDimensionsValueHelper(const vector<FieldValue>& dims, size_t* index, int depth, int prefix, vector<StatsDimensionsValue>* output) { size_t count = dims.size(); while (*index < count) { diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.h b/cmds/statsd/src/subscriber/SubscriberReporter.h index 50100df56ff8..2a7f771a0ba4 100644 --- a/cmds/statsd/src/subscriber/SubscriberReporter.h +++ b/cmds/statsd/src/subscriber/SubscriberReporter.h @@ -26,6 +26,7 @@ #include <mutex> #include <unordered_map> +#include <vector> namespace android { namespace os { @@ -102,6 +103,7 @@ private: void sendBroadcastLocked(const sp<android::IBinder>& intentSender, const ConfigKey& configKey, const Subscription& subscription, + const std::vector<String16>& cookies, const MetricDimensionKey& dimKey) const; }; diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt index 8d5a9ec0cd8f..f134e5488937 100644 --- a/config/hiddenapi-light-greylist.txt +++ b/config/hiddenapi-light-greylist.txt @@ -685,7 +685,13 @@ Landroid/hardware/SystemSensorManager$BaseEventQueue;->dispatchFlushCompleteEven Landroid/hardware/SystemSensorManager$BaseEventQueue;->dispatchSensorEvent(I[FIJ)V Landroid/hardware/usb/IUsbManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/hardware/usb/UsbDeviceConnection;->mNativeContext:J +Landroid/hardware/usb/UsbManager;->getPorts()[Landroid/hardware/usb/UsbPort; +Landroid/hardware/usb/UsbManager;->getPortStatus(Landroid/hardware/usb/UsbPort;)Landroid/hardware/usb/UsbPortStatus; Landroid/hardware/usb/UsbManager;->setCurrentFunction(Ljava/lang/String;Z)V +Landroid/hardware/usb/UsbManager;->setPortRoles(Landroid/hardware/usb/UsbPort;II)V +Landroid/hardware/usb/UsbPortStatus;->getCurrentDataRole()I +Landroid/hardware/usb/UsbPortStatus;->isConnected()Z +Landroid/hardware/usb/UsbPortStatus;->isRoleCombinationSupported(II)Z Landroid/hardware/usb/UsbRequest;->mNativeContext:J Landroid/icu/impl/CurrencyData;-><init>()V Landroid/icu/impl/number/DecimalFormatProperties;->readObject(Ljava/io/ObjectInputStream;)V @@ -1000,6 +1006,7 @@ Landroid/net/wifi/ScanResult;->untrusted:Z Landroid/net/wifi/ScanResult;->wifiSsid:Landroid/net/wifi/WifiSsid; Landroid/net/wifi/WifiConfiguration;->apBand:I Landroid/net/wifi/WifiConfiguration;->apChannel:I +Landroid/net/wifi/WifiConfiguration;->defaultGwMacAddress:Ljava/lang/String; Landroid/net/wifi/WifiConfiguration;->mIpConfiguration:Landroid/net/IpConfiguration; Landroid/net/wifi/WifiConfiguration;->validatedInternetAccess:Z Landroid/net/wifi/WifiEnterpriseConfig;->getCaCertificateAlias()Ljava/lang/String; diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 99c5d2b9a927..a1a10a5b8942 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -124,6 +124,7 @@ import android.widget.Toast; import android.widget.Toolbar; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IVoiceInteractor; import com.android.internal.app.ToolbarActionBar; import com.android.internal.app.WindowDecorActionBar; @@ -995,9 +996,9 @@ public class Activity extends ContextThemeWrapper * cursors for data being displayed, etc. * * <p>You can call {@link #finish} from within this function, in - * which case onDestroy() will be immediately called without any of the rest - * of the activity lifecycle ({@link #onStart}, {@link #onResume}, - * {@link #onPause}, etc) executing. + * which case onDestroy() will be immediately called after {@link #onCreate} without any of the + * rest of the activity lifecycle ({@link #onStart}, {@link #onResume}, {@link #onPause}, etc) + * executing. * * <p><em>Derived classes must call through to the super class's * implementation of this method. If they do not, an exception will be @@ -7110,6 +7111,12 @@ public class Activity extends ContextThemeWrapper return mParent != null ? mParent.getActivityToken() : mToken; } + /** @hide */ + @VisibleForTesting + public final ActivityThread getActivityThread() { + return mMainThread; + } + final void performCreate(Bundle icicle) { performCreate(icicle, null); } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 2d73ce0c0594..a18ba718ecef 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -2101,15 +2101,17 @@ public class ActivityManager { private final int mOrientation; private final Rect mContentInsets; private final boolean mReducedResolution; + private final boolean mIsRealSnapshot; private final float mScale; public TaskSnapshot(GraphicBuffer snapshot, int orientation, Rect contentInsets, - boolean reducedResolution, float scale) { + boolean reducedResolution, float scale, boolean isRealSnapshot) { mSnapshot = snapshot; mOrientation = orientation; mContentInsets = new Rect(contentInsets); mReducedResolution = reducedResolution; mScale = scale; + mIsRealSnapshot = isRealSnapshot; } private TaskSnapshot(Parcel source) { @@ -2118,6 +2120,7 @@ public class ActivityManager { mContentInsets = source.readParcelable(null /* classLoader */); mReducedResolution = source.readBoolean(); mScale = source.readFloat(); + mIsRealSnapshot = source.readBoolean(); } /** @@ -2150,6 +2153,14 @@ public class ActivityManager { } /** + * @return Whether or not the snapshot is a real snapshot or an app-theme generated snapshot + * due to the task having a secure window or having previews disabled. + */ + public boolean isRealSnapshot() { + return mIsRealSnapshot; + } + + /** * @return The scale this snapshot was taken in. */ public float getScale() { @@ -2168,13 +2179,15 @@ public class ActivityManager { dest.writeParcelable(mContentInsets, 0); dest.writeBoolean(mReducedResolution); dest.writeFloat(mScale); + dest.writeBoolean(mIsRealSnapshot); } @Override public String toString() { return "TaskSnapshot{mSnapshot=" + mSnapshot + " mOrientation=" + mOrientation + " mContentInsets=" + mContentInsets.toShortString() - + " mReducedResolution=" + mReducedResolution + " mScale=" + mScale; + + " mReducedResolution=" + mReducedResolution + " mScale=" + mScale + + " mIsRealSnapshot=" + mIsRealSnapshot; } public static final Creator<TaskSnapshot> CREATOR = new Creator<TaskSnapshot>() { diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 379944e2e230..afcd51572d51 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3722,16 +3722,27 @@ public final class ActivityThread extends ClientTransactionHandler { //Slog.i(TAG, "Running services: " + mServices); } - ActivityClientRecord performResumeActivity(IBinder token, boolean clearHide, String reason) { + ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest, + String reason) { ActivityClientRecord r = mActivities.get(token); if (localLOGV) Slog.v(TAG, "Performing resume of " + r + " finished=" + r.activity.mFinished); if (r != null && !r.activity.mFinished) { if (r.getLifecycleState() == ON_RESUME) { - throw new IllegalStateException( - "Trying to resume activity which is already resumed"); + if (!finalStateRequest) { + final RuntimeException e = new IllegalStateException( + "Trying to resume activity which is already resumed"); + Slog.e(TAG, e.getMessage(), e); + Slog.e(TAG, r.getStateString()); + // TODO(lifecycler): A double resume request is possible when an activity + // receives two consequent transactions with relaunch requests and "resumed" + // final state requests and the second relaunch is omitted. We still try to + // handle two resume requests for the final state. For cases other than this + // one, we don't expect it to happen. + } + return null; } - if (clearHide) { + if (finalStateRequest) { r.hideForNow = false; r.activity.mStartedActivity = false; } @@ -3782,7 +3793,7 @@ public final class ActivityThread extends ClientTransactionHandler { } @Override - public void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, + public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. @@ -3790,7 +3801,7 @@ public final class ActivityThread extends ClientTransactionHandler { mSomeActivitiesChanged = true; // TODO Push resumeArgs into the activity for consideration - final ActivityClientRecord r = performResumeActivity(token, clearHide, reason); + final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); if (r != null) { final Activity a = r.activity; diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java index 81cbbcafe8c4..41eeb9acb5ec 100644 --- a/core/java/android/app/Application.java +++ b/core/java/android/app/Application.java @@ -191,6 +191,16 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { } } + /** + * Returns the name of the current process. A package's default process name + * is the same as its package name. Non-default processes will look like + * "$PACKAGE_NAME:$NAME", where $NAME corresponds to an android:process + * attribute within AndroidManifest.xml. + */ + public static String getProcessName() { + return ActivityThread.currentProcessName(); + } + // ------------------ Internal API ------------------ /** @@ -333,4 +343,4 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { } return null; } -}
\ No newline at end of file +} diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java index 6bc66ecdb261..206495d0db6c 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -67,9 +67,16 @@ public abstract class ClientTransactionHandler { public abstract void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving, int configChanges, PendingTransactionActions pendingActions, String reason); - /** Resume the activity. */ - public abstract void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, - String reason); + /** + * Resume the activity. + * @param token Target activity token. + * @param finalStateRequest Flag indicating if this call is handling final lifecycle state + * request for a transaction. + * @param isForward Flag indicating if next transition is forward. + * @param reason Reason for performing this operation. + */ + public abstract void handleResumeActivity(IBinder token, boolean finalStateRequest, + boolean isForward, String reason); /** Stop the activity. */ public abstract void handleStopActivity(IBinder token, boolean show, int configChanges, diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java index c2c91c2bcbd6..b12e3bc04a4d 100644 --- a/core/java/android/app/StatsManager.java +++ b/core/java/android/app/StatsManager.java @@ -54,6 +54,12 @@ public final class StatsManager { public static final String EXTRA_STATS_SUBSCRIPTION_RULE_ID = "android.app.extra.STATS_SUBSCRIPTION_RULE_ID"; /** + * List<String> of the relevant statsd_config.proto's BroadcastSubscriberDetails.cookie. + * Obtain using {@link android.content.Intent#getStringArrayListExtra(String)}. + */ + public static final String EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES = + "android.app.extra.STATS_BROADCAST_SUBSCRIBER_COOKIES"; + /** * Extra of a {@link android.os.StatsDimensionsValue} representing sliced dimension value * information. */ @@ -146,7 +152,8 @@ public final class StatsManager { * {@link #EXTRA_STATS_CONFIG_UID}, * {@link #EXTRA_STATS_CONFIG_KEY}, * {@link #EXTRA_STATS_SUBSCRIPTION_ID}, - * {@link #EXTRA_STATS_SUBSCRIPTION_RULE_ID}, and + * {@link #EXTRA_STATS_SUBSCRIPTION_RULE_ID}, + * {@link #EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES}, and * {@link #EXTRA_STATS_DIMENSIONS_VALUE}. * <p> * This function can only be called by the owner (uid) of the config. It must be called each diff --git a/core/java/android/app/servertransaction/ResumeActivityItem.java b/core/java/android/app/servertransaction/ResumeActivityItem.java index af2fb713e1bc..d16bc97cbc87 100644 --- a/core/java/android/app/servertransaction/ResumeActivityItem.java +++ b/core/java/android/app/servertransaction/ResumeActivityItem.java @@ -48,7 +48,8 @@ public class ResumeActivityItem extends ActivityLifecycleItem { public void execute(ClientTransactionHandler client, IBinder token, PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityResume"); - client.handleResumeActivity(token, true /* clearHide */, mIsForward, "RESUME_ACTIVITY"); + client.handleResumeActivity(token, true /* finalStateRequest */, mIsForward, + "RESUME_ACTIVITY"); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java index 0e52b3471504..9e9a9c57656b 100644 --- a/core/java/android/app/servertransaction/TransactionExecutor.java +++ b/core/java/android/app/servertransaction/TransactionExecutor.java @@ -191,7 +191,7 @@ public class TransactionExecutor { mTransactionHandler.handleStartActivity(r, mPendingActions); break; case ON_RESUME: - mTransactionHandler.handleResumeActivity(r.token, false /* clearHide */, + mTransactionHandler.handleResumeActivity(r.token, false /* finalStateRequest */, r.isForward, "LIFECYCLER_RESUME_ACTIVITY"); break; case ON_PAUSE: diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java index df32fb9c2e75..aa2cf46891d8 100644 --- a/core/java/android/app/slice/SliceProvider.java +++ b/core/java/android/app/slice/SliceProvider.java @@ -16,7 +16,6 @@ package android.app.slice; import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.PendingIntent; import android.content.ComponentName; import android.content.ContentProvider; @@ -35,7 +34,6 @@ import android.os.Binder; import android.os.Bundle; import android.os.CancellationSignal; import android.os.Handler; -import android.os.Looper; import android.os.Process; import android.os.StrictMode; import android.os.StrictMode.ThreadPolicy; @@ -46,7 +44,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.concurrent.CountDownLatch; /** * A SliceProvider allows an app to provide content to be displayed in system spaces. This content @@ -163,18 +160,10 @@ public abstract class SliceProvider extends ContentProvider { private static final boolean DEBUG = false; - private String mBindingPkg; - private SliceManager mSliceManager; + private static final long SLICE_BIND_ANR = 2000; - /** - * Return the package name of the caller that initiated the binding request - * currently happening. The returned package will have been - * verified to belong to the calling UID. Returns {@code null} if not - * currently performing an {@link #onBindSlice(Uri, List)}. - */ - public final @Nullable String getBindingPackage() { - return mBindingPkg; - } + private String mCallback; + private SliceManager mSliceManager; @Override public void attachInfo(Context context, ProviderInfo info) { @@ -183,12 +172,12 @@ public abstract class SliceProvider extends ContentProvider { } /** - * Implemented to create a slice. Will be called on the main thread. + * Implemented to create a slice. * <p> * onBindSlice should return as quickly as possible so that the UI tied * to this slice can be responsive. No network or other IO will be allowed * during onBindSlice. Any loading that needs to be done should happen - * off the main thread with a call to {@link ContentResolver#notifyChange(Uri, ContentObserver)} + * in the background with a call to {@link ContentResolver#notifyChange(Uri, ContentObserver)} * when the app is ready to provide the complete data in onBindSlice. * <p> * The slice returned should have a spec that is compatible with one of @@ -381,55 +370,32 @@ public abstract class SliceProvider extends ContentProvider { } private Collection<Uri> handleGetDescendants(Uri uri) { - if (Looper.myLooper() == Looper.getMainLooper()) { + mCallback = "onGetSliceDescendants"; + Handler.getMain().postDelayed(mAnr, SLICE_BIND_ANR); + try { return onGetSliceDescendants(uri); - } else { - CountDownLatch latch = new CountDownLatch(1); - Collection<Uri>[] output = new Collection[1]; - Handler.getMain().post(() -> { - output[0] = onGetSliceDescendants(uri); - latch.countDown(); - }); - try { - latch.await(); - return output[0]; - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + } finally { + Handler.getMain().removeCallbacks(mAnr); } } private void handlePinSlice(Uri sliceUri) { - if (Looper.myLooper() == Looper.getMainLooper()) { + mCallback = "onSlicePinned"; + Handler.getMain().postDelayed(mAnr, SLICE_BIND_ANR); + try { onSlicePinned(sliceUri); - } else { - CountDownLatch latch = new CountDownLatch(1); - Handler.getMain().post(() -> { - onSlicePinned(sliceUri); - latch.countDown(); - }); - try { - latch.await(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + } finally { + Handler.getMain().removeCallbacks(mAnr); } } private void handleUnpinSlice(Uri sliceUri) { - if (Looper.myLooper() == Looper.getMainLooper()) { + mCallback = "onSliceUnpinned"; + Handler.getMain().postDelayed(mAnr, SLICE_BIND_ANR); + try { onSliceUnpinned(sliceUri); - } else { - CountDownLatch latch = new CountDownLatch(1); - Handler.getMain().post(() -> { - onSliceUnpinned(sliceUri); - latch.countDown(); - }); - try { - latch.await(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + } finally { + Handler.getMain().removeCallbacks(mAnr); } } @@ -447,21 +413,12 @@ public abstract class SliceProvider extends ContentProvider { return createPermissionSlice(getContext(), sliceUri, pkg); } } - if (Looper.myLooper() == Looper.getMainLooper()) { - return onBindSliceStrict(sliceUri, supportedSpecs, pkg); - } else { - CountDownLatch latch = new CountDownLatch(1); - Slice[] output = new Slice[1]; - Handler.getMain().post(() -> { - output[0] = onBindSliceStrict(sliceUri, supportedSpecs, pkg); - latch.countDown(); - }); - try { - latch.await(); - return output[0]; - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + mCallback = "onBindSlice"; + Handler.getMain().postDelayed(mAnr, SLICE_BIND_ANR); + try { + return onBindSliceStrict(sliceUri, supportedSpecs); + } finally { + Handler.getMain().removeCallbacks(mAnr); } } @@ -513,19 +470,21 @@ public abstract class SliceProvider extends ContentProvider { } } - private Slice onBindSliceStrict(Uri sliceUri, List<SliceSpec> supportedSpecs, - String callingPackage) { + private Slice onBindSliceStrict(Uri sliceUri, List<SliceSpec> supportedSpecs) { ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); try { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyDeath() .build()); - mBindingPkg = callingPackage; return onBindSlice(sliceUri, supportedSpecs); } finally { - mBindingPkg = null; StrictMode.setThreadPolicy(oldPolicy); } } + + private final Runnable mAnr = () -> { + Process.sendSignal(Process.myPid(), Process.SIGNAL_QUIT); + Log.wtf(TAG, "Timed out while handling slice callback " + mCallback); + }; } diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index 521ab4edc4c5..f7fb84ba9d0a 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -116,6 +116,14 @@ public final class UsageEvents implements Parcelable { @SystemApi public static final int STANDBY_BUCKET_CHANGED = 11; + /** + * An event type denoting that an app posted an interruptive notification. Visual and + * audible interruptions are included. + * @hide + */ + @SystemApi + public static final int NOTIFICATION_INTERRUPTION = 12; + /** @hide */ public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0; @@ -188,6 +196,14 @@ public final class UsageEvents implements Parcelable { */ public int mBucketAndReason; + /** + * The id of the {@link android.app.NotificationChannel} to which an interruptive + * notification was posted. + * Only present for {@link #NOTIFICATION_INTERRUPTION} event types. + * {@hide} + */ + public String mNotificationChannelId; + /** @hide */ @EventFlags public int mFlags; @@ -208,6 +224,7 @@ public final class UsageEvents implements Parcelable { mContentAnnotations = orig.mContentAnnotations; mFlags = orig.mFlags; mBucketAndReason = orig.mBucketAndReason; + mNotificationChannelId = orig.mNotificationChannelId; } /** @@ -285,6 +302,16 @@ public final class UsageEvents implements Parcelable { return mBucketAndReason & 0x0000FFFF; } + /** + * Returns the ID of the {@link android.app.NotificationChannel} for this event if the + * event is of type {@link #NOTIFICATION_INTERRUPTION}, otherwise it returns null; + * @hide + */ + @SystemApi + public String getNotificationChannelId() { + return mNotificationChannelId; + } + /** @hide */ public Event getObfuscatedIfInstantApp() { if ((mFlags & FLAG_IS_PACKAGE_INSTANT_APP) == 0) { @@ -444,6 +471,9 @@ public final class UsageEvents implements Parcelable { case Event.STANDBY_BUCKET_CHANGED: p.writeInt(event.mBucketAndReason); break; + case Event.NOTIFICATION_INTERRUPTION: + p.writeString(event.mNotificationChannelId); + break; } } @@ -473,6 +503,7 @@ public final class UsageEvents implements Parcelable { eventOut.mAction = null; eventOut.mContentType = null; eventOut.mContentAnnotations = null; + eventOut.mNotificationChannelId = null; switch (eventOut.mEventType) { case Event.CONFIGURATION_CHANGE: @@ -490,6 +521,9 @@ public final class UsageEvents implements Parcelable { case Event.STANDBY_BUCKET_CHANGED: eventOut.mBucketAndReason = p.readInt(); break; + case Event.NOTIFICATION_INTERRUPTION: + eventOut.mNotificationChannelId = p.readString(); + break; } } diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java index b62b1ee0492b..09ced2648de1 100644 --- a/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -59,6 +59,16 @@ public abstract class UsageStatsManagerInternal { public abstract void reportConfigurationChange(Configuration config, @UserIdInt int userId); /** + * Reports that an application has posted an interruptive notification. + * + * @param packageName The package name of the app that posted the notification + * @param channelId The ID of the NotificationChannel to which the notification was posted + * @param userId The user in which the notification was posted + */ + public abstract void reportInterruptiveNotification(String packageName, String channelId, + @UserIdInt int userId); + + /** * Reports that an action equivalent to a ShortcutInfo is taken by the user. * * @param packageName The package name of the shortcut publisher diff --git a/core/java/android/os/IStatsCompanionService.aidl b/core/java/android/os/IStatsCompanionService.aidl index 29c298e31ae9..402c995452e8 100644 --- a/core/java/android/os/IStatsCompanionService.aidl +++ b/core/java/android/os/IStatsCompanionService.aidl @@ -74,6 +74,7 @@ interface IStatsCompanionService { */ oneway void sendSubscriberBroadcast(in IBinder intentSender, long configUid, long configId, long subscriptionId, long subscriptionRuleId, + in String[] cookies, in StatsDimensionsValue dimensionsValue); /** Tells StatsCompaionService to grab the uid map snapshot and send it to statsd. */ diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java index c7d89b0c6e4f..2cb5aeeeb2e6 100644 --- a/core/java/android/os/PowerManagerInternal.java +++ b/core/java/android/os/PowerManagerInternal.java @@ -141,6 +141,14 @@ public abstract class PowerManagerInternal { public abstract void setDozeOverrideFromDreamManager( int screenState, int screenBrightness); + /** + * Used by sidekick manager to tell the power manager if it shouldn't change the display state + * when a draw wake lock is acquired. Some processes may grab such a wake lock to do some work + * in a powered-up state, but we shouldn't give up sidekick control over the display until this + * override is lifted. + */ + public abstract void setDrawWakeLockOverrideFromSidekick(boolean keepState); + public abstract PowerSaveState getLowPowerState(int serviceType); public abstract void registerLowPowerModeObserver(LowPowerModeListener listener); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index fea9972c662c..29b7931bf7c7 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -11195,6 +11195,20 @@ public final class Settings { public static final String ZEN_MODE_CONFIG_ETAG = "zen_mode_config_etag"; /** + * If 0, turning on dnd manually will last indefinitely. + * Else if non-negative, turning on dnd manually will last for this many minutes. + * Else (if negative), turning on dnd manually will surface a dialog that prompts + * user to specify a duration. + * @hide + */ + public static final String ZEN_DURATION = "zen_duration"; + + private static final Validator ZEN_DURATION_VALIDATOR = ANY_INTEGER_VALIDATOR; + + /** @hide */ public static final int ZEN_DURATION_PROMPT = -1; + /** @hide */ public static final int ZEN_DURATION_FOREVER = 0; + + /** * Defines global heads up toggle. One of HEADS_UP_OFF, HEADS_UP_ON. * * @hide @@ -11510,7 +11524,14 @@ public final class Settings { /** * The packages whitelisted to be run in autofill compatibility mode. The list - * of packages is ":" colon delimited. + * of packages is {@code ":"} colon delimited, and each entry has the name of the + * package and an optional list of url bar resource ids (the list is delimited by + * brackets&mdash{@code [} and {@code ]}&mdash and is also comma delimited). + * + * <p>For example, a list with 3 packages {@code p1}, {@code p2}, and {@code p3}, where + * package {@code p1} have one id ({@code url_bar}, {@code p2} has none, and {@code p3 } + * have 2 ids {@code url_foo} and {@code url_bas}) would be + * {@code p1[url_bar]:p2:p3[url_foo,url_bas]} * * @hide */ @@ -11568,7 +11589,8 @@ public final class Settings { BLUETOOTH_ON, PRIVATE_DNS_MODE, PRIVATE_DNS_SPECIFIER, - SOFT_AP_TIMEOUT_ENABLED + SOFT_AP_TIMEOUT_ENABLED, + ZEN_DURATION, }; /** @@ -11609,6 +11631,7 @@ public final class Settings { VALIDATORS.put(WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON, WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON_VALIDATOR); VALIDATORS.put(APP_AUTO_RESTRICTION_ENABLED, APP_AUTO_RESTRICTION_ENABLED_VALIDATOR); + VALIDATORS.put(ZEN_DURATION, ZEN_DURATION_VALIDATOR); } /** diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java index c568b6fbe0c4..140336e93357 100644 --- a/core/java/android/provider/VoicemailContract.java +++ b/core/java/android/provider/VoicemailContract.java @@ -49,7 +49,8 @@ import java.util.List; * </ul> * * <P> The minimum permission needed to access this content provider is - * {@link android.Manifest.permission#ADD_VOICEMAIL} + * {@link android.Manifest.permission#ADD_VOICEMAIL} or carrier privileges (see + * {@link android.telephony.TelephonyManager#hasCarrierPrivileges}). * * <P>Voicemails are inserted by what is called as a "voicemail source" * application, which is responsible for syncing voicemail data between a remote diff --git a/core/java/android/service/autofill/AutofillServiceInfo.java b/core/java/android/service/autofill/AutofillServiceInfo.java index 70c4ec07ee4e..de234559d53e 100644 --- a/core/java/android/service/autofill/AutofillServiceInfo.java +++ b/core/java/android/service/autofill/AutofillServiceInfo.java @@ -32,7 +32,6 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.AttributeSet; import android.util.Log; -import android.util.Pair; import android.util.Xml; import com.android.internal.R; @@ -79,7 +78,7 @@ public final class AutofillServiceInfo { private final String mSettingsActivity; @Nullable - private final ArrayMap<String, Pair<Long, String>> mCompatibilityPackages; + private final ArrayMap<String, Long> mCompatibilityPackages; public AutofillServiceInfo(Context context, ComponentName comp, int userHandle) throws PackageManager.NameNotFoundException { @@ -117,7 +116,7 @@ public final class AutofillServiceInfo { } String settingsActivity = null; - ArrayMap<String, Pair<Long, String>> compatibilityPackages = null; + ArrayMap<String, Long> compatibilityPackages = null; try { final Resources resources = context.getPackageManager().getResourcesForApplication( @@ -153,10 +152,9 @@ public final class AutofillServiceInfo { mCompatibilityPackages = compatibilityPackages; } - private ArrayMap<String, Pair<Long, String>> parseCompatibilityPackages(XmlPullParser parser, - Resources resources) - throws IOException, XmlPullParserException { - ArrayMap<String, Pair<Long, String>> compatibilityPackages = null; + private ArrayMap<String, Long> parseCompatibilityPackages(XmlPullParser parser, + Resources resources) throws IOException, XmlPullParserException { + ArrayMap<String, Long> compatibilityPackages = null; final int outerDepth = parser.getDepth(); int type; @@ -200,13 +198,18 @@ public final class AutofillServiceInfo { } else { maxVersionCode = Long.MAX_VALUE; } - final String urlBarResourceId = cpAttributes.getString( - R.styleable.AutofillService_CompatibilityPackage_urlBarResourceId); + if (true) { // TODO(b/74445943): remove block after P DP2 is branched + final String urlBarResourceId = cpAttributes.getString( + R.styleable.AutofillService_CompatibilityPackage_urlBarResourceId); + if (urlBarResourceId != null) { + Log.e(TAG, "Service is using deprecated attribute 'urlBarResourceId'"); + } + } if (compatibilityPackages == null) { compatibilityPackages = new ArrayMap<>(); } - compatibilityPackages.put(name, new Pair<>(maxVersionCode, urlBarResourceId)); + compatibilityPackages.put(name, maxVersionCode); } finally { XmlUtils.skipCurrentTag(parser); if (cpAttributes != null) { @@ -228,23 +231,10 @@ public final class AutofillServiceInfo { return mSettingsActivity; } - public ArrayMap<String, Pair<Long, String>> getCompatibilityPackages() { + public ArrayMap<String, Long> getCompatibilityPackages() { return mCompatibilityPackages; } - /** - * Gets the resource id of the URL bar for a package. Used in compat mode - */ - // TODO: return a list of strings instead - @Nullable - public String getUrlBarResourceId(String packageName) { - if (mCompatibilityPackages == null) { - return null; - } - final Pair<Long, String> pair = mCompatibilityPackages.get(packageName); - return pair == null ? null : pair.second; - } - @Override public String toString() { final StringBuilder builder = new StringBuilder(); diff --git a/core/java/android/service/textclassifier/ITextClassifierService.aidl b/core/java/android/service/textclassifier/ITextClassifierService.aidl index d2ffe345ae38..25e9d454efb5 100644 --- a/core/java/android/service/textclassifier/ITextClassifierService.aidl +++ b/core/java/android/service/textclassifier/ITextClassifierService.aidl @@ -19,13 +19,14 @@ package android.service.textclassifier; import android.service.textclassifier.ITextClassificationCallback; import android.service.textclassifier.ITextLinksCallback; import android.service.textclassifier.ITextSelectionCallback; +import android.view.textclassifier.SelectionEvent; import android.view.textclassifier.TextClassification; import android.view.textclassifier.TextLinks; import android.view.textclassifier.TextSelection; /** * TextClassifierService binder interface. - * See TextClassifier for interface documentation. + * See TextClassifier (and TextClassifier.Logger) for interface documentation. * {@hide} */ oneway interface ITextClassifierService { @@ -44,4 +45,6 @@ oneway interface ITextClassifierService { in CharSequence text, in TextLinks.Options options, in ITextLinksCallback c); + + void onSelectionEvent(in SelectionEvent event); } diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java index 2c8c4ecaa610..88e29b0d3d11 100644 --- a/core/java/android/service/textclassifier/TextClassifierService.java +++ b/core/java/android/service/textclassifier/TextClassifierService.java @@ -33,7 +33,9 @@ import android.os.IBinder; import android.os.RemoteException; import android.text.TextUtils; import android.util.Slog; +import android.view.textclassifier.SelectionEvent; import android.view.textclassifier.TextClassification; +import android.view.textclassifier.TextClassificationManager; import android.view.textclassifier.TextClassifier; import android.view.textclassifier.TextLinks; import android.view.textclassifier.TextSelection; @@ -47,7 +49,7 @@ import android.view.textclassifier.TextSelection; * {@link android.view.textclassifier.TextClassifierImpl} is loaded in the calling app's process. * * <p>See: {@link TextClassifier}. - * See: {@link android.view.textclassifier.TextClassificationManager}. + * See: {@link TextClassificationManager}. * * <p>Include the following in the manifest: * @@ -170,6 +172,12 @@ public abstract class TextClassifierService extends Service { } }); } + + /** {@inheritDoc} */ + @Override + public void onSelectionEvent(SelectionEvent event) throws RemoteException { + TextClassifierService.this.onSelectionEvent(event); + } }; @Nullable @@ -237,6 +245,27 @@ public abstract class TextClassifierService extends Service { @NonNull Callback<TextLinks> callback); /** + * Writes the selection event. + * This is called when a selection event occurs. e.g. user changed selection; or smart selection + * happened. + * + * <p>The default implementation ignores the event. + */ + public void onSelectionEvent(@NonNull SelectionEvent event) {} + + /** + * Returns a TextClassifier that runs in this service's process. + * If the local TextClassifier is disabled, this returns {@link TextClassifier#NO_OP}. + */ + public final TextClassifier getLocalTextClassifier() { + final TextClassificationManager tcm = getSystemService(TextClassificationManager.class); + if (tcm != null) { + return tcm.getTextClassifier(TextClassifier.LOCAL); + } + return TextClassifier.NO_OP; + } + + /** * Callbacks for TextClassifierService results. * * @param <T> the type of the result diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java index bb16afddcd63..b6adee9501a6 100644 --- a/core/java/android/view/DisplayCutout.java +++ b/core/java/android/view/DisplayCutout.java @@ -18,10 +18,6 @@ package android.view; import static android.view.DisplayCutoutProto.BOUNDS; import static android.view.DisplayCutoutProto.INSETS; -import static android.view.Surface.ROTATION_0; -import static android.view.Surface.ROTATION_180; -import static android.view.Surface.ROTATION_270; -import static android.view.Surface.ROTATION_90; import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; @@ -36,23 +32,24 @@ import android.os.Parcelable; import android.text.TextUtils; import android.util.Log; import android.util.PathParser; -import android.util.Size; import android.util.proto.ProtoOutputStream; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import java.util.Objects; +import java.util.ArrayList; +import java.util.List; /** - * Represents a part of the display that is not functional for displaying content. + * Represents the area of the display that is not functional for displaying content. * * <p>{@code DisplayCutout} is immutable. */ public final class DisplayCutout { private static final String TAG = "DisplayCutout"; + private static final String BOTTOM_MARKER = "@bottom"; private static final String DP_MARKER = "@dp"; /** @@ -74,7 +71,7 @@ public final class DisplayCutout { * @hide */ public static final DisplayCutout NO_CUTOUT = new DisplayCutout(ZERO_RECT, EMPTY_REGION, - new Size(0, 0)); + false /* copyArguments */); private static final Object CACHE_LOCK = new Object(); @@ -89,38 +86,38 @@ public final class DisplayCutout { private final Rect mSafeInsets; private final Region mBounds; - private final Size mFrameSize; // TODO: move frameSize, calculateRelativeTo, etc. into WM. /** * Creates a DisplayCutout instance. * * @param safeInsets the insets from each edge which avoid the display cutout as returned by * {@link #getSafeInsetTop()} etc. - * @param bounds the bounds of the display cutout as returned by {@link #getBounds()}. + * @param boundingRects the bounding rects of the display cutouts as returned by + * {@link #getBoundingRects()} ()}. */ // TODO(b/73953958): @VisibleForTesting(visibility = PRIVATE) - public DisplayCutout(Rect safeInsets, Region bounds) { + public DisplayCutout(Rect safeInsets, List<Rect> boundingRects) { this(safeInsets != null ? new Rect(safeInsets) : ZERO_RECT, - bounds != null ? Region.obtain(bounds) : Region.obtain(), - null /* frameSize */); + boundingRectsToRegion(boundingRects), + true /* copyArguments */); } /** * Creates a DisplayCutout instance. * - * NOTE: the Rects passed into this instance are not copied and MUST remain unchanged. - * - * @hide + * @param copyArguments if true, create a copy of the arguments. If false, the passed arguments + * are not copied and MUST remain unchanged forever. */ - @VisibleForTesting - public DisplayCutout(Rect safeInsets, Region bounds, Size frameSize) { - mSafeInsets = safeInsets != null ? safeInsets : ZERO_RECT; - mBounds = bounds != null ? bounds : Region.obtain(); - mFrameSize = frameSize; + private DisplayCutout(Rect safeInsets, Region bounds, boolean copyArguments) { + mSafeInsets = safeInsets == null ? ZERO_RECT : + (copyArguments ? new Rect(safeInsets) : safeInsets); + mBounds = bounds == null ? Region.obtain() : + (copyArguments ? Region.obtain(bounds) : bounds); } /** - * Returns true if there is no cutout or it is outside of the content view. + * Returns true if the safe insets are empty (and therefore the current view does not + * overlap with the cutout or cutout area). * * @hide */ @@ -128,6 +125,15 @@ public final class DisplayCutout { return mSafeInsets.equals(ZERO_RECT); } + /** + * Returns true if there is no cutout, i.e. the bounds are empty. + * + * @hide + */ + public boolean isBoundsEmpty() { + return mBounds.isEmpty(); + } + /** Returns the inset from the top which avoids the display cutout in pixels. */ public int getSafeInsetTop() { return mSafeInsets.top; @@ -161,23 +167,60 @@ public final class DisplayCutout { /** * Returns the bounding region of the cutout. * + * <p> + * <strong>Note:</strong> There may be more than one cutout, in which case the returned + * {@code Region} will be non-contiguous and its bounding rect will be meaningless without + * intersecting it first. + * + * Example: + * <pre> + * // Getting the bounding rectangle of the top display cutout + * Region bounds = displayCutout.getBounds(); + * bounds.op(0, 0, Integer.MAX_VALUE, displayCutout.getSafeInsetTop(), Region.Op.INTERSECT); + * Rect topDisplayCutout = bounds.getBoundingRect(); + * </pre> + * * @return the bounding region of the cutout. Coordinates are relative * to the top-left corner of the content view and in pixel units. + * @hide */ public Region getBounds() { return Region.obtain(mBounds); } /** - * Returns the bounding rect of the cutout. + * Returns a list of {@code Rect}s, each of which is the bounding rectangle for a non-functional + * area on the display. * - * @return the bounding rect of the cutout. Coordinates are relative - * to the top-left corner of the content view. - * @hide + * There will be at most one non-functional area per short edge of the device, and none on + * the long edges. + * + * @return a list of bounding {@code Rect}s, one for each display cutout area. */ - public Rect getBoundingRect() { - // TODO(roosa): Inline. - return mBounds.getBounds(); + public List<Rect> getBoundingRects() { + List<Rect> result = new ArrayList<>(); + Region bounds = Region.obtain(); + // top + bounds.set(mBounds); + bounds.op(0, 0, Integer.MAX_VALUE, getSafeInsetTop(), Region.Op.INTERSECT); + if (!bounds.isEmpty()) { + result.add(bounds.getBounds()); + } + // left + bounds.set(mBounds); + bounds.op(0, 0, getSafeInsetLeft(), Integer.MAX_VALUE, Region.Op.INTERSECT); + if (!bounds.isEmpty()) { + result.add(bounds.getBounds()); + } + // right & bottom + bounds.set(mBounds); + bounds.op(getSafeInsetLeft() + 1, getSafeInsetTop() + 1, + Integer.MAX_VALUE, Integer.MAX_VALUE, Region.Op.INTERSECT); + if (!bounds.isEmpty()) { + result.add(bounds.getBounds()); + } + bounds.recycle(); + return result; } @Override @@ -195,8 +238,7 @@ public final class DisplayCutout { if (o instanceof DisplayCutout) { DisplayCutout c = (DisplayCutout) o; return mSafeInsets.equals(c.mSafeInsets) - && mBounds.equals(c.mBounds) - && Objects.equals(mFrameSize, c.mFrameSize); + && mBounds.equals(c.mBounds); } return false; } @@ -204,7 +246,7 @@ public final class DisplayCutout { @Override public String toString() { return "DisplayCutout{insets=" + mSafeInsets - + " boundingRect=" + getBoundingRect() + + " boundingRect=" + mBounds.getBounds() + "}"; } @@ -249,88 +291,19 @@ public final class DisplayCutout { } bounds.translate(-insetLeft, -insetTop); - Size frame = mFrameSize == null ? null : new Size( - mFrameSize.getWidth() - insetLeft - insetRight, - mFrameSize.getHeight() - insetTop - insetBottom); - - return new DisplayCutout(safeInsets, bounds, frame); + return new DisplayCutout(safeInsets, bounds, false /* copyArguments */); } /** - * Recalculates the cutout relative to the given reference frame. - * - * The safe insets must already have been computed, e.g. with {@link #computeSafeInsets}. + * Returns a copy of this instance with the safe insets replaced with the parameter. * - * @return a copy of this instance with the safe insets recalculated - * @hide - */ - public DisplayCutout calculateRelativeTo(Rect frame) { - return inset(frame.left, frame.top, - mFrameSize.getWidth() - frame.right, mFrameSize.getHeight() - frame.bottom); - } - - /** - * Calculates the safe insets relative to the given display size. + * @param safeInsets the new safe insets in pixels + * @return a copy of this instance with the safe insets replaced with the argument. * - * @return a copy of this instance with the safe insets calculated * @hide */ - public DisplayCutout computeSafeInsets(int width, int height) { - if (this == NO_CUTOUT || mBounds.isEmpty()) { - return NO_CUTOUT; - } - - return computeSafeInsets(new Size(width, height), mBounds); - } - - private static DisplayCutout computeSafeInsets(Size displaySize, Region bounds) { - Rect boundingRect = bounds.getBounds(); - Rect safeRect = new Rect(); - - int bestArea = 0; - int bestVariant = 0; - for (int variant = ROTATION_0; variant <= ROTATION_270; variant++) { - int area = calculateInsetVariantArea(displaySize, boundingRect, variant, safeRect); - if (bestArea < area) { - bestArea = area; - bestVariant = variant; - } - } - calculateInsetVariantArea(displaySize, boundingRect, bestVariant, safeRect); - if (safeRect.isEmpty()) { - // The entire displaySize overlaps with the cutout. - safeRect.set(0, displaySize.getHeight(), 0, 0); - } else { - // Convert safeRect to insets relative to displaySize. We're reusing the rect here to - // avoid an allocation. - safeRect.set( - Math.max(0, safeRect.left), - Math.max(0, safeRect.top), - Math.max(0, displaySize.getWidth() - safeRect.right), - Math.max(0, displaySize.getHeight() - safeRect.bottom)); - } - - return new DisplayCutout(safeRect, bounds, displaySize); - } - - private static int calculateInsetVariantArea(Size display, Rect boundingRect, int variant, - Rect outSafeRect) { - switch (variant) { - case ROTATION_0: - outSafeRect.set(0, 0, display.getWidth(), boundingRect.top); - break; - case ROTATION_90: - outSafeRect.set(0, 0, boundingRect.left, display.getHeight()); - break; - case ROTATION_180: - outSafeRect.set(0, boundingRect.bottom, display.getWidth(), display.getHeight()); - break; - case ROTATION_270: - outSafeRect.set(boundingRect.right, 0, display.getWidth(), display.getHeight()); - break; - } - - return outSafeRect.isEmpty() ? 0 : outSafeRect.width() * outSafeRect.height(); + public DisplayCutout replaceSafeInsets(Rect safeInsets) { + return new DisplayCutout(new Rect(safeInsets), mBounds, false /* copyArguments */); } private static int atLeastZero(int value) { @@ -369,7 +342,7 @@ public final class DisplayCutout { Region bounds = new Region(); bounds.setPath(path, clipRegion); clipRegion.recycle(); - return new DisplayCutout(ZERO_RECT, bounds, null /* frameSize */); + return new DisplayCutout(ZERO_RECT, bounds, false /* copyArguments */); } /** @@ -377,9 +350,9 @@ public final class DisplayCutout { * * @hide */ - public static DisplayCutout fromResources(Resources res, int displayWidth) { + public static DisplayCutout fromResources(Resources res, int displayWidth, int displayHeight) { return fromSpec(res.getString(R.string.config_mainBuiltInDisplayCutout), - displayWidth, res.getDisplayMetrics().density); + displayWidth, displayHeight, res.getDisplayMetrics().density); } /** @@ -388,7 +361,8 @@ public final class DisplayCutout { * @hide */ @VisibleForTesting(visibility = PRIVATE) - public static DisplayCutout fromSpec(String spec, int displayWidth, float density) { + public static DisplayCutout fromSpec(String spec, int displayWidth, int displayHeight, + float density) { if (TextUtils.isEmpty(spec)) { return null; } @@ -404,7 +378,14 @@ public final class DisplayCutout { spec = spec.substring(0, spec.length() - DP_MARKER.length()); } - Path p; + String bottomSpec = null; + if (spec.contains(BOTTOM_MARKER)) { + String[] splits = spec.split(BOTTOM_MARKER, 2); + spec = splits[0].trim(); + bottomSpec = splits[1].trim(); + } + + final Path p; try { p = PathParser.createPathFromPathData(spec); } catch (Throwable e) { @@ -419,6 +400,20 @@ public final class DisplayCutout { m.postTranslate(displayWidth / 2f, 0); p.transform(m); + if (bottomSpec != null) { + final Path bottomPath; + try { + bottomPath = PathParser.createPathFromPathData(bottomSpec); + } catch (Throwable e) { + Log.wtf(TAG, "Could not inflate bottom cutout: ", e); + return null; + } + // Keep top transform + m.postTranslate(0, displayHeight); + bottomPath.transform(m); + p.addPath(bottomPath); + } + final DisplayCutout result = fromBounds(p); synchronized (CACHE_LOCK) { sCachedSpec = spec; @@ -429,6 +424,16 @@ public final class DisplayCutout { return result; } + private static Region boundingRectsToRegion(List<Rect> rects) { + Region result = Region.obtain(); + if (rects != null) { + for (Rect r : rects) { + result.op(r, Region.Op.UNION); + } + } + return result; + } + /** * Helper class for passing {@link DisplayCutout} through binder. * @@ -472,12 +477,6 @@ public final class DisplayCutout { out.writeInt(1); out.writeTypedObject(cutout.mSafeInsets, flags); out.writeTypedObject(cutout.mBounds, flags); - if (cutout.mFrameSize != null) { - out.writeInt(cutout.mFrameSize.getWidth()); - out.writeInt(cutout.mFrameSize.getHeight()); - } else { - out.writeInt(-1); - } } } @@ -520,10 +519,7 @@ public final class DisplayCutout { Rect safeInsets = in.readTypedObject(Rect.CREATOR); Region bounds = in.readTypedObject(Region.CREATOR); - int width = in.readInt(); - Size frameSize = width >= 0 ? new Size(width, in.readInt()) : null; - - return new DisplayCutout(safeInsets, bounds, frameSize); + return new DisplayCutout(safeInsets, bounds, false /* copyArguments */); } public DisplayCutout get() { diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 2354f255c30b..69938cbe8bb6 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -2241,18 +2241,20 @@ public interface WindowManager extends ViewManager { /** * The window is allowed to extend into the {@link DisplayCutout} area, only if the - * {@link DisplayCutout} is fully contained within the status bar. Otherwise, the window is + * {@link DisplayCutout} is fully contained within a system bar. Otherwise, the window is * laid out such that it does not overlap with the {@link DisplayCutout} area. * * <p> * In practice, this means that if the window did not set FLAG_FULLSCREEN or - * SYSTEM_UI_FLAG_FULLSCREEN, it can extend into the cutout area in portrait. - * Otherwise (i.e. fullscreen or landscape) it is laid out such that it does overlap the + * SYSTEM_UI_FLAG_FULLSCREEN, it can extend into the cutout area in portrait if the cutout + * is at the top edge. Similarly for SYSTEM_UI_FLAG_HIDE_NAVIGATION and a cutout at the + * bottom of the screen. + * Otherwise (i.e. fullscreen or landscape) it is laid out such that it does not overlap the * cutout area. * * <p> - * The usual precautions for not overlapping with the status bar are sufficient for ensuring - * that no important content overlaps with the DisplayCutout. + * The usual precautions for not overlapping with the status and navigation bar are + * sufficient for ensuring that no important content overlaps with the DisplayCutout. * * @see DisplayCutout * @see WindowInsets @@ -2260,8 +2262,18 @@ public interface WindowManager extends ViewManager { public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT = 0; /** - * The window is always allowed to extend into the {@link DisplayCutout} area, - * even if fullscreen or in landscape. + * @deprecated use {@link #LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES} + * @hide + */ + @Deprecated + public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS = 1; + + /** + * The window is always allowed to extend into the {@link DisplayCutout} areas on the short + * edges of the screen. + * + * The window will never extend into a {@link DisplayCutout} area on the long edges of the + * screen. * * <p> * The window must make sure that no important content overlaps with the @@ -2270,7 +2282,7 @@ public interface WindowManager extends ViewManager { * @see DisplayCutout * @see WindowInsets#getDisplayCutout() */ - public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS = 1; + public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES = 1; /** * The window is never allowed to overlap with the DisplayCutout area. diff --git a/core/java/android/view/textclassifier/Logger.java b/core/java/android/view/textclassifier/Logger.java index a4f5bf17066c..9c92fd4543e2 100644 --- a/core/java/android/view/textclassifier/Logger.java +++ b/core/java/android/view/textclassifier/Logger.java @@ -94,10 +94,7 @@ public abstract class Logger { } /** - * Writes the selection event. - * - * <p><strong>NOTE: </strong>This method is designed for subclasses. - * Apps should not call it directly. + * Writes the selection event to a log. */ public abstract void writeEvent(@NonNull SelectionEvent event); diff --git a/media/java/android/media/update/PlaybackState2Provider.java b/core/java/android/view/textclassifier/SelectionEvent.aidl index 66b8fa53974b..10ed16e8df1f 100644 --- a/media/java/android/media/update/PlaybackState2Provider.java +++ b/core/java/android/view/textclassifier/SelectionEvent.aidl @@ -1,5 +1,5 @@ /* - * Copyright 2018 The Android Open Source Project + * 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. @@ -14,27 +14,6 @@ * limitations under the License. */ -package android.media.update; +package android.view.textclassifier; -import android.os.Bundle; - -/** - * @hide - */ -public interface PlaybackState2Provider { - String toString_impl(); - - int getState_impl(); - - long getPosition_impl(); - - long getBufferedPosition_impl(); - - float getPlaybackSpeed_impl(); - - long getLastPositionUpdateTime_impl(); - - long getCurrentPlaylistItemIndex_impl(); - - Bundle toBundle_impl(); -} +parcelable SelectionEvent; diff --git a/core/java/android/view/textclassifier/SelectionEvent.java b/core/java/android/view/textclassifier/SelectionEvent.java index 90fd921b024e..7ac094eda031 100644 --- a/core/java/android/view/textclassifier/SelectionEvent.java +++ b/core/java/android/view/textclassifier/SelectionEvent.java @@ -18,6 +18,8 @@ package android.view.textclassifier; import android.annotation.IntDef; import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; import android.view.textclassifier.TextClassifier.EntityType; import com.android.internal.util.Preconditions; @@ -25,12 +27,13 @@ import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Locale; +import java.util.Objects; /** * A selection event. * Specify index parameters as word token indices. */ -public final class SelectionEvent { +public final class SelectionEvent implements Parcelable { /** @hide */ @Retention(RetentionPolicy.SOURCE) @@ -121,9 +124,9 @@ public final class SelectionEvent { private String mSignature; private long mEventTime; private long mDurationSinceSessionStart; - private long mDurationSinceLastEvent; + private long mDurationSincePreviousEvent; private int mEventIndex; - private String mSessionId; + @Nullable private String mSessionId; private int mStart; private int mEnd; private int mSmartStart; @@ -146,6 +149,60 @@ public final class SelectionEvent { mInvocationMethod = invocationMethod; } + private SelectionEvent(Parcel in) { + mAbsoluteStart = in.readInt(); + mAbsoluteEnd = in.readInt(); + mEventType = in.readInt(); + mEntityType = in.readString(); + mWidgetVersion = in.readInt() > 0 ? in.readString() : null; + mPackageName = in.readString(); + mWidgetType = in.readString(); + mInvocationMethod = in.readInt(); + mSignature = in.readString(); + mEventTime = in.readLong(); + mDurationSinceSessionStart = in.readLong(); + mDurationSincePreviousEvent = in.readLong(); + mEventIndex = in.readInt(); + mSessionId = in.readInt() > 0 ? in.readString() : null; + mStart = in.readInt(); + mEnd = in.readInt(); + mSmartStart = in.readInt(); + mSmartEnd = in.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mAbsoluteStart); + dest.writeInt(mAbsoluteEnd); + dest.writeInt(mEventType); + dest.writeString(mEntityType); + dest.writeInt(mWidgetVersion != null ? 1 : 0); + if (mWidgetVersion != null) { + dest.writeString(mWidgetVersion); + } + dest.writeString(mPackageName); + dest.writeString(mWidgetType); + dest.writeInt(mInvocationMethod); + dest.writeString(mSignature); + dest.writeLong(mEventTime); + dest.writeLong(mDurationSinceSessionStart); + dest.writeLong(mDurationSincePreviousEvent); + dest.writeInt(mEventIndex); + dest.writeInt(mSessionId != null ? 1 : 0); + if (mSessionId != null) { + dest.writeString(mSessionId); + } + dest.writeInt(mStart); + dest.writeInt(mEnd); + dest.writeInt(mSmartStart); + dest.writeInt(mSmartEnd); + } + + @Override + public int describeContents() { + return 0; + } + int getAbsoluteStart() { return mAbsoluteStart; } @@ -240,11 +297,11 @@ public final class SelectionEvent { * in the selection session was triggered. */ public long getDurationSincePreviousEvent() { - return mDurationSinceLastEvent; + return mDurationSincePreviousEvent; } SelectionEvent setDurationSincePreviousEvent(long durationMs) { - this.mDurationSinceLastEvent = durationMs; + this.mDurationSincePreviousEvent = durationMs; return this; } @@ -342,15 +399,66 @@ public final class SelectionEvent { } @Override + public int hashCode() { + return Objects.hash(mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType, + mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod, mSignature, + mEventTime, mDurationSinceSessionStart, mDurationSincePreviousEvent, + mEventIndex, mSessionId, mStart, mEnd, mSmartStart, mSmartEnd); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof SelectionEvent)) { + return false; + } + + final SelectionEvent other = (SelectionEvent) obj; + return mAbsoluteStart == other.mAbsoluteStart + && mAbsoluteEnd == other.mAbsoluteEnd + && mEventType == other.mEventType + && Objects.equals(mEntityType, other.mEntityType) + && Objects.equals(mWidgetVersion, other.mWidgetVersion) + && Objects.equals(mPackageName, other.mPackageName) + && Objects.equals(mWidgetType, other.mWidgetType) + && mInvocationMethod == other.mInvocationMethod + && Objects.equals(mSignature, other.mSignature) + && mEventTime == other.mEventTime + && mDurationSinceSessionStart == other.mDurationSinceSessionStart + && mDurationSincePreviousEvent == other.mDurationSincePreviousEvent + && mEventIndex == other.mEventIndex + && Objects.equals(mSessionId, other.mSessionId) + && mStart == other.mStart + && mEnd == other.mEnd + && mSmartStart == other.mSmartStart + && mSmartEnd == other.mSmartEnd; + } + + @Override public String toString() { return String.format(Locale.US, "SelectionEvent {absoluteStart=%d, absoluteEnd=%d, eventType=%d, entityType=%s, " - + "widgetVersion=%s, packageName=%s, widgetType=%s, signature=%s, " - + "eventTime=%d, durationSinceSessionStart=%d, durationSinceLastEvent=%d, " - + "eventIndex=%d, sessionId=%s, start=%d, end=%d, smartStart=%d, smartEnd=%d}", + + "widgetVersion=%s, packageName=%s, widgetType=%s, invocationMethod=%s, " + + "signature=%s, eventTime=%d, durationSinceSessionStart=%d, " + + "durationSincePreviousEvent=%d, eventIndex=%d, sessionId=%s, start=%d, end=%d, " + + "smartStart=%d, smartEnd=%d}", mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType, - mWidgetVersion, mPackageName, mWidgetType, mSignature, - mEventTime, mDurationSinceSessionStart, mDurationSinceLastEvent, + mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod, mSignature, + mEventTime, mDurationSinceSessionStart, mDurationSincePreviousEvent, mEventIndex, mSessionId, mStart, mEnd, mSmartStart, mSmartEnd); } + + public static final Creator<SelectionEvent> CREATOR = new Creator<SelectionEvent>() { + @Override + public SelectionEvent createFromParcel(Parcel in) { + return new SelectionEvent(in); + } + + @Override + public SelectionEvent[] newArray(int size) { + return new SelectionEvent[size]; + } + }; } diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java index 2b335fb09c61..c783cae4c234 100644 --- a/core/java/android/view/textclassifier/SystemTextClassifier.java +++ b/core/java/android/view/textclassifier/SystemTextClassifier.java @@ -29,6 +29,7 @@ import android.service.textclassifier.ITextClassifierService; import android.service.textclassifier.ITextLinksCallback; import android.service.textclassifier.ITextSelectionCallback; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import java.util.concurrent.CountDownLatch; @@ -46,6 +47,12 @@ final class SystemTextClassifier implements TextClassifier { private final TextClassifier mFallback; private final String mPackageName; + private final Object mLoggerLock = new Object(); + @GuardedBy("mLoggerLock") + private Logger.Config mLoggerConfig; + @GuardedBy("mLoggerLock") + private Logger mLogger; + SystemTextClassifier(Context context, TextClassificationConstants settings) throws ServiceManager.ServiceNotFoundException { mManagerService = ITextClassifierService.Stub.asInterface( @@ -58,6 +65,7 @@ final class SystemTextClassifier implements TextClassifier { /** * @inheritDoc */ + @Override @WorkerThread public TextSelection suggestSelection( @NonNull CharSequence text, @@ -84,6 +92,7 @@ final class SystemTextClassifier implements TextClassifier { /** * @inheritDoc */ + @Override @WorkerThread public TextClassification classifyText( @NonNull CharSequence text, @@ -109,6 +118,7 @@ final class SystemTextClassifier implements TextClassifier { /** * @inheritDoc */ + @Override @WorkerThread public TextLinks generateLinks( @NonNull CharSequence text, @Nullable TextLinks.Options options) { @@ -142,11 +152,33 @@ final class SystemTextClassifier implements TextClassifier { * @inheritDoc */ @Override + @WorkerThread public int getMaxGenerateLinksTextLength() { // TODO: retrieve this from the bound service. return mFallback.getMaxGenerateLinksTextLength(); } + @Override + public Logger getLogger(@NonNull Logger.Config config) { + Preconditions.checkNotNull(config); + synchronized (mLoggerLock) { + if (mLogger == null || !config.equals(mLoggerConfig)) { + mLoggerConfig = config; + mLogger = new Logger(config) { + @Override + public void writeEvent(SelectionEvent event) { + try { + mManagerService.onSelectionEvent(event); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + }; + } + } + return mLogger; + } + private static final class TextSelectionCallback extends ITextSelectionCallback.Stub { final ResponseReceiver<TextSelection> mReceiver = new ResponseReceiver<>(); diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java index ebd2ff983bcb..887bebbd97ac 100644 --- a/core/java/android/view/textclassifier/TextClassifier.java +++ b/core/java/android/view/textclassifier/TextClassifier.java @@ -323,6 +323,7 @@ public interface TextClassifier { * @see #generateLinks(CharSequence) * @see #generateLinks(CharSequence, TextLinks.Options) */ + @WorkerThread default int getMaxGenerateLinksTextLength() { return Integer.MAX_VALUE; } diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index 0a052699590a..a09982053091 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -18,6 +18,7 @@ package android.view.textclassifier; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.WorkerThread; import android.app.SearchManager; import android.content.ComponentName; import android.content.ContentUris; @@ -42,7 +43,6 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.lang.ref.WeakReference; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Arrays; @@ -78,7 +78,6 @@ public final class TextClassifierImpl implements TextClassifier { private final Context mContext; private final TextClassifier mFallback; - private final GenerateLinksLogger mGenerateLinksLogger; private final Object mLock = new Object(); @@ -91,9 +90,9 @@ public final class TextClassifierImpl implements TextClassifier { private final Object mLoggerLock = new Object(); @GuardedBy("mLoggerLock") // Do not access outside this lock. - private WeakReference<Logger.Config> mLoggerConfig = new WeakReference<>(null); + private Logger.Config mLoggerConfig; @GuardedBy("mLoggerLock") // Do not access outside this lock. - private Logger mLogger; // Should never be null if mLoggerConfig.get() is not null. + private Logger mLogger; private final TextClassificationConstants mSettings; @@ -106,6 +105,7 @@ public final class TextClassifierImpl implements TextClassifier { /** @inheritDoc */ @Override + @WorkerThread public TextSelection suggestSelection( @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex, @Nullable TextSelection.Options options) { @@ -169,6 +169,7 @@ public final class TextClassifierImpl implements TextClassifier { /** @inheritDoc */ @Override + @WorkerThread public TextClassification classifyText( @NonNull CharSequence text, int startIndex, int endIndex, @Nullable TextClassification.Options options) { @@ -204,6 +205,7 @@ public final class TextClassifierImpl implements TextClassifier { /** @inheritDoc */ @Override + @WorkerThread public TextLinks generateLinks( @NonNull CharSequence text, @Nullable TextLinks.Options options) { Utils.validate(text, getMaxGenerateLinksTextLength(), false /* allowInMainThread */); @@ -282,16 +284,17 @@ public final class TextClassifierImpl implements TextClassifier { } } + /** @inheritDoc */ @Override public Logger getLogger(@NonNull Logger.Config config) { Preconditions.checkNotNull(config); synchronized (mLoggerLock) { - if (mLoggerConfig.get() == null || !mLoggerConfig.get().equals(config)) { - mLoggerConfig = new WeakReference<>(config); + if (mLogger == null || !config.equals(mLoggerConfig)) { + mLoggerConfig = config; mLogger = new DefaultLogger(config); } - return mLogger; } + return mLogger; } private TextClassifierImplNative getNative(LocaleList localeList) diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 5d3f1c9288cd..f39b73e671d4 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -2906,6 +2906,11 @@ public class WebView extends AbsoluteLayout mProvider.getViewDelegate().autofill(values); } + @Override + public boolean isVisibleToUserForAutofill(int virtualId) { + return mProvider.getViewDelegate().isVisibleToUserForAutofill(virtualId); + } + /** @hide */ @Override public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java index a474a8580b69..00e782bd5046 100644 --- a/core/java/android/webkit/WebViewProvider.java +++ b/core/java/android/webkit/WebViewProvider.java @@ -329,13 +329,16 @@ public interface WebViewProvider { public void onProvideVirtualStructure(android.view.ViewStructure structure); - @SuppressWarnings("unused") - public default void onProvideAutofillVirtualStructure(android.view.ViewStructure structure, - int flags) { + default void onProvideAutofillVirtualStructure( + @SuppressWarnings("unused") android.view.ViewStructure structure, + @SuppressWarnings("unused") int flags) { } - @SuppressWarnings("unused") - public default void autofill(SparseArray<AutofillValue>values) { + default void autofill(@SuppressWarnings("unused") SparseArray<AutofillValue> values) { + } + + default boolean isVisibleToUserForAutofill(@SuppressWarnings("unused") int virtualId) { + return true; // true is the default value returned by View.isVisibleToUserForAutofill() } public AccessibilityNodeProvider getAccessibilityNodeProvider(); diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java index 629f53192813..be8c34c53ae6 100644 --- a/core/java/android/widget/SelectionActionModeHelper.java +++ b/core/java/android/widget/SelectionActionModeHelper.java @@ -648,6 +648,9 @@ public final class SelectionActionModeHelper { * Part selection of a word e.g. "or" is counted as selecting the * entire word i.e. equivalent to "York", and each special character is counted as a word, e.g. * "," is at [2, 3). Whitespaces are ignored. + * + * NOTE that the definition of a word is defined by the TextClassifier's Logger's token + * iterator. */ private static final class SelectionMetricsLogger { diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java index 72cd24888dcc..6c3a58ce3908 100644 --- a/core/java/com/android/internal/util/LatencyTracker.java +++ b/core/java/com/android/internal/util/LatencyTracker.java @@ -147,8 +147,17 @@ public class LatencyTracker { } mStartRtc.delete(action); Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, NAMES[action], 0); - long duration = endRtc - startRtc; + logAction(action, (int)(endRtc - startRtc)); + } + + /** + * Logs an action that has started and ended. This needs to be called from the main thread. + * + * @param action The action to end. One of the ACTION_* values. + * @param duration The duration of the action in ms. + */ + public static void logAction(int action, int duration) { Log.i(TAG, "action=" + action + " latency=" + duration); - EventLog.writeEvent(EventLogTags.SYSUI_LATENCY, action, (int) duration); + EventLog.writeEvent(EventLogTags.SYSUI_LATENCY, action, duration); } } diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl index 5a06f7fe1990..25e1589db74e 100644 --- a/core/java/com/android/internal/widget/ILockSettings.aidl +++ b/core/java/com/android/internal/widget/ILockSettings.aidl @@ -54,13 +54,6 @@ interface ILockSettings { void userPresent(int userId); int getStrongAuthForUser(int userId); - long addEscrowToken(in byte[] token, int userId); - boolean removeEscrowToken(long handle, int userId); - boolean isEscrowTokenActive(long handle, int userId); - boolean setLockCredentialWithToken(String credential, int type, long tokenHandle, - in byte[] token, int requestedQuality, int userId); - void unlockUserWithToken(long tokenHandle, in byte[] token, int userId); - // Keystore RecoveryController methods. // {@code ServiceSpecificException} may be thrown to signal an error, which caller can // convert to {@code RecoveryManagerException}. diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 7eb2f38f5287..bf075bf800f4 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -45,6 +45,7 @@ import android.util.SparseIntArray; import android.util.SparseLongArray; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; import com.google.android.collect.Lists; import libcore.util.HexEncoding; @@ -1473,6 +1474,13 @@ public class LockPatternUtils { } } + private LockSettingsInternal getLockSettingsInternal() { + LockSettingsInternal service = LocalServices.getService(LockSettingsInternal.class); + if (service == null) { + throw new SecurityException("Only available to system server itself"); + } + return service; + } /** * Create an escrow token for the current user, which can later be used to unlock FBE * or change user password. @@ -1481,44 +1489,41 @@ public class LockPatternUtils { * confirm credential operation in order to activate the token for future use. If the user * has no secure lockscreen, then the token is activated immediately. * + * <p>This method is only available to code running in the system server process itself. + * * @return a unique 64-bit token handle which is needed to refer to this token later. */ public long addEscrowToken(byte[] token, int userId) { - try { - return getLockSettings().addEscrowToken(token, userId); - } catch (RemoteException re) { - return 0L; - } + return getLockSettingsInternal().addEscrowToken(token, userId); } /** * Remove an escrow token. + * + * <p>This method is only available to code running in the system server process itself. + * * @return true if the given handle refers to a valid token previously returned from * {@link #addEscrowToken}, whether it's active or not. return false otherwise. */ public boolean removeEscrowToken(long handle, int userId) { - try { - return getLockSettings().removeEscrowToken(handle, userId); - } catch (RemoteException re) { - return false; - } + return getLockSettingsInternal().removeEscrowToken(handle, userId); } /** * Check if the given escrow token is active or not. Only active token can be used to call * {@link #setLockCredentialWithToken} and {@link #unlockUserWithToken} + * + * <p>This method is only available to code running in the system server process itself. */ public boolean isEscrowTokenActive(long handle, int userId) { - try { - return getLockSettings().isEscrowTokenActive(handle, userId); - } catch (RemoteException re) { - return false; - } + return getLockSettingsInternal().isEscrowTokenActive(handle, userId); } /** * Change a user's lock credential with a pre-configured escrow token. * + * <p>This method is only available to code running in the system server process itself. + * * @param credential The new credential to be set * @param type Credential type: password / pattern / none. * @param requestedQuality the requested password quality by DevicePolicyManager. @@ -1530,55 +1535,55 @@ public class LockPatternUtils { */ public boolean setLockCredentialWithToken(String credential, int type, int requestedQuality, long tokenHandle, byte[] token, int userId) { - try { - if (type != CREDENTIAL_TYPE_NONE) { - if (TextUtils.isEmpty(credential) || credential.length() < MIN_LOCK_PASSWORD_SIZE) { - throw new IllegalArgumentException("password must not be null and at least " - + "of length " + MIN_LOCK_PASSWORD_SIZE); - } - final int quality = computePasswordQuality(type, credential, requestedQuality); - if (!getLockSettings().setLockCredentialWithToken(credential, type, tokenHandle, - token, quality, userId)) { - return false; - } - setLong(PASSWORD_TYPE_KEY, quality, userId); + LockSettingsInternal localService = getLockSettingsInternal(); + if (type != CREDENTIAL_TYPE_NONE) { + if (TextUtils.isEmpty(credential) || credential.length() < MIN_LOCK_PASSWORD_SIZE) { + throw new IllegalArgumentException("password must not be null and at least " + + "of length " + MIN_LOCK_PASSWORD_SIZE); + } + final int quality = computePasswordQuality(type, credential, requestedQuality); + if (!localService.setLockCredentialWithToken(credential, type, tokenHandle, + token, quality, userId)) { + return false; + } + setLong(PASSWORD_TYPE_KEY, quality, userId); - updateEncryptionPasswordIfNeeded(credential, quality, userId); - updatePasswordHistory(credential, userId); - } else { - if (!TextUtils.isEmpty(credential)) { - throw new IllegalArgumentException("password must be emtpy for NONE type"); - } - if (!getLockSettings().setLockCredentialWithToken(null, CREDENTIAL_TYPE_NONE, - tokenHandle, token, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, - userId)) { - return false; - } - setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, - userId); + updateEncryptionPasswordIfNeeded(credential, quality, userId); + updatePasswordHistory(credential, userId); + } else { + if (!TextUtils.isEmpty(credential)) { + throw new IllegalArgumentException("password must be emtpy for NONE type"); + } + if (!localService.setLockCredentialWithToken(null, CREDENTIAL_TYPE_NONE, + tokenHandle, token, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, + userId)) { + return false; + } + setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, + userId); - if (userId == UserHandle.USER_SYSTEM) { - // Set the encryption password to default. - updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null); - setCredentialRequiredToDecrypt(false); - } + if (userId == UserHandle.USER_SYSTEM) { + // Set the encryption password to default. + updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null); + setCredentialRequiredToDecrypt(false); } - onAfterChangingPassword(userId); - return true; - } catch (RemoteException re) { - Log.e(TAG, "Unable to save lock password ", re); - re.rethrowFromSystemServer(); } - return false; + onAfterChangingPassword(userId); + return true; } - public void unlockUserWithToken(long tokenHandle, byte[] token, int userId) { - try { - getLockSettings().unlockUserWithToken(tokenHandle, token, userId); - } catch (RemoteException re) { - Log.e(TAG, "Unable to unlock user with token", re); - re.rethrowFromSystemServer(); - } + /** + * Unlock the specified user by an pre-activated escrow token. This should have the same effect + * on device encryption as the user entering his lockscreen credentials for the first time after + * boot, this includes unlocking the user's credential-encrypted storage as well as the keystore + * + * <p>This method is only available to code running in the system server process itself. + * + * @return {@code true} if the supplied token is valid and unlock succeeds, + * {@code false} otherwise. + */ + public boolean unlockUserWithToken(long tokenHandle, byte[] token, int userId) { + return getLockSettingsInternal().unlockUserWithToken(tokenHandle, token, userId); } diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java new file mode 100644 index 000000000000..9de9ef7f2aea --- /dev/null +++ b/core/java/com/android/internal/widget/LockSettingsInternal.java @@ -0,0 +1,56 @@ +/* + * 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.internal.widget; + + +/** + * LockSettingsService local system service interface. + * + * @hide Only for use within the system server. + */ +public abstract class LockSettingsInternal { + + /** + * Create an escrow token for the current user, which can later be used to unlock FBE + * or change user password. + * + * After adding, if the user currently has lockscreen password, he will need to perform a + * confirm credential operation in order to activate the token for future use. If the user + * has no secure lockscreen, then the token is activated immediately. + * + * @return a unique 64-bit token handle which is needed to refer to this token later. + */ + public abstract long addEscrowToken(byte[] token, int userId); + + /** + * Remove an escrow token. + * @return true if the given handle refers to a valid token previously returned from + * {@link #addEscrowToken}, whether it's active or not. return false otherwise. + */ + public abstract boolean removeEscrowToken(long handle, int userId); + + /** + * Check if the given escrow token is active or not. Only active token can be used to call + * {@link #setLockCredentialWithToken} and {@link #unlockUserWithToken} + */ + public abstract boolean isEscrowTokenActive(long handle, int userId); + + public abstract boolean setLockCredentialWithToken(String credential, int type, + long tokenHandle, byte[] token, int requestedQuality, int userId); + + public abstract boolean unlockUserWithToken(long tokenHandle, byte[] token, int userId); +} diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index 9d4b5aa9fc73..c71e505c6790 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -22,6 +22,7 @@ import android.app.ActivityManager; import android.content.ComponentName; import android.content.pm.FeatureInfo; import android.content.pm.PackageManager; +import android.os.Build; import android.os.Environment; import android.os.Process; import android.os.storage.StorageManager; @@ -276,9 +277,12 @@ public class SystemConfig { readPermissions(Environment.buildPath( Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL); - // Allow Vendor to customize system configs around libs, features, permissions and apps - int vendorPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PERMISSIONS | - ALLOW_APP_CONFIGS | ALLOW_PRIVAPP_PERMISSIONS; + // Vendors are only allowed to customze libs, features and privapp permissions + int vendorPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PRIVAPP_PERMISSIONS; + if (Build.VERSION.FIRST_SDK_INT <= Build.VERSION_CODES.O_MR1) { + // For backward compatibility + vendorPermissionFlag |= (ALLOW_PERMISSIONS | ALLOW_APP_CONFIGS); + } readPermissions(Environment.buildPath( Environment.getVendorDirectory(), "etc", "sysconfig"), vendorPermissionFlag); readPermissions(Environment.buildPath( @@ -659,6 +663,8 @@ public class SystemConfig { } XmlUtils.skipCurrentTag(parser); } else { + Slog.w(TAG, "Tag " + name + " is unknown or not allowed in " + + permFile.getParent()); XmlUtils.skipCurrentTag(parser); continue; } diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index 5498a931718d..ce4e384f253a 100755 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -921,6 +921,28 @@ static jboolean Bitmap_compress(JNIEnv* env, jobject clazz, jlong bitmapHandle, SkBitmap skbitmap; bitmap->getSkBitmap(&skbitmap); + if (skbitmap.colorType() == kRGBA_F16_SkColorType) { + // Convert to P3 before encoding. This matches SkAndroidCodec::computeOutputColorSpace + // for wide gamuts. + auto cs = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, + SkColorSpace::kDCIP3_D65_Gamut); + auto info = skbitmap.info().makeColorType(kRGBA_8888_SkColorType) + .makeColorSpace(std::move(cs)); + SkBitmap p3; + if (!p3.tryAllocPixels(info)) { + return JNI_FALSE; + } + auto xform = SkColorSpaceXform::New(skbitmap.colorSpace(), info.colorSpace()); + if (!xform) { + return JNI_FALSE; + } + if (!xform->apply(SkColorSpaceXform::kRGBA_8888_ColorFormat, p3.getPixels(), + SkColorSpaceXform::kRGBA_F16_ColorFormat, skbitmap.getPixels(), + info.width() * info.height(), kUnpremul_SkAlphaType)) { + return JNI_FALSE; + } + skbitmap = p3; + } return SkEncodeImage(strm.get(), skbitmap, fm, quality) ? JNI_TRUE : JNI_FALSE; } diff --git a/core/proto/android/providers/settings.proto b/core/proto/android/providers/settings.proto index d7ba421ec3c6..914a7db56f1d 100644 --- a/core/proto/android/providers/settings.proto +++ b/core/proto/android/providers/settings.proto @@ -433,9 +433,11 @@ message GlobalSettingsProto { optional SettingProto show_mute_in_crash_dialog = 352 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingsProto show_zen_upgrade_notification = 354 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingsProto app_auto_restriction_enabled = 359 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingsProto zen_duration = 360 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Please insert fields in the same order as in // frameworks/base/core/java/android/provider/Settings.java. - // Next tag = 360; + // Next tag = 361; } message SecureSettingsProto { diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto index 5cb5319f4fea..64aa98b1136e 100644 --- a/core/proto/android/server/powermanagerservice.proto +++ b/core/proto/android/server/powermanagerservice.proto @@ -315,4 +315,6 @@ message PowerServiceSettingsAndConfigurationDumpProto { optional bool is_double_tap_wake_enabled = 37; // True if we are currently in VR Mode. optional bool is_vr_mode_enabled = 38; + // True if Sidekick is controlling the display and we shouldn't change its power mode. + optional bool draw_wake_lock_override_from_sidekick = 39; } diff --git a/core/res/res/drawable/ic_corp_user_badge.xml b/core/res/res/drawable/ic_corp_user_badge.xml index 6a0d9025684d..a08f2d4845e1 100644 --- a/core/res/res/drawable/ic_corp_user_badge.xml +++ b/core/res/res/drawable/ic_corp_user_badge.xml @@ -2,7 +2,8 @@ android:width="36dp" android:height="36dp" android:viewportWidth="36.0" - android:viewportHeight="36.0"> + android:viewportHeight="36.0" + android:tint="?attr/colorControlNormal"> <path android:pathData="M16.3,11.3h3.4v1.7h-3.4z" android:fillColor="#FFFFFF"/> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 021e88ea6093..4a72bf99dbca 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -2125,29 +2125,32 @@ Defaults to {@code default}. @see android.view.WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT - @see android.view.WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + @see android.view.WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES @see android.view.WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER @see android.view.DisplayCutout @see android.R.attr#layoutInDisplayCutoutMode --> <attr name="windowLayoutInDisplayCutoutMode"> <!-- The window is allowed to extend into the {@code DisplayCutout} area, only if the - {@code DisplayCutout} is fully contained within the status bar. Otherwise, the window is + {@code DisplayCutout} is fully contained within a system bar. Otherwise, the window is laid out such that it does not overlap with the {@code DisplayCutout} area. @see android.view.DisplayCutout @see android.view.WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT --> <enum name="default" value="0" /> - <!-- The window is always allowed to extend into the {@code DisplayCutout} area, - even if fullscreen or in landscape. + <!-- + The window is always allowed to extend into the {@code DisplayCutout} areas on the short + edges of the screen even if fullscreen or in landscape. + The window will never extend into a {@link DisplayCutout} area on the long edges of the + screen. <p> The window must make sure that no important content overlaps with the {@link DisplayCutout}. @see android.view.DisplayCutout - @see android.view.WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + @see android.view.WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES --> - <enum name="always" value="1" /> + <enum name="shortEdges" value="1" /> <!-- The window is never allowed to overlap with the DisplayCutout area. <p> This should be used with windows that transiently set {@code SYSTEM_UI_FLAG_FULLSCREEN} @@ -7978,9 +7981,7 @@ android.content.pm.PackageInfo#getLongVersionCode()} for the target package. --> <attr name="maxLongVersionCode" format="string" /> - <!-- The resource id of view that contains the URL bar of the HTML page being loaded. - Typically used when compatibility mode is used in a browser. - --> + <!-- TODO(b/74445943): STOPSHIP (urlBarResourceId should be removed after P DP2 is branched)--> <attr name="urlBarResourceId" format="string" /> </declare-styleable> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 7d5d1ba1366b..2c0deedc435f 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2869,6 +2869,7 @@ <public name="outlineSpotShadowColor" /> <public name="outlineAmbientShadowColor" /> <public name="maxLongVersionCode" /> + <!-- TODO(b/74445943): STOPSHIP (urlBarResourceId should be removed after P DP2 is branched)--> <public name="urlBarResourceId" /> <!-- @hide @SystemApi --> <public name="userRestriction" /> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index a5bd179826b1..dc937e63c11e 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4490,7 +4490,7 @@ <!-- Zen mode condition - summary: time duration in hours. [CHAR LIMIT=NONE] --> <plurals name="zen_mode_duration_hours_summary"> - <item quantity="one">For one hour (until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g>)</item> + <item quantity="one">For 1 hour (until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g>)</item> <item quantity="other">For %1$d hours (until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g>)</item> </plurals> @@ -4514,7 +4514,7 @@ <!-- Zen mode condition - line one: time duration in hours. [CHAR LIMIT=NONE] --> <plurals name="zen_mode_duration_hours"> - <item quantity="one">For one hour</item> + <item quantity="one">For 1 hour</item> <item quantity="other">For %d hours</item> </plurals> diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml index fdbcd8a27f91..25b053b0f0c5 100644 --- a/core/res/res/values/themes_device_defaults.xml +++ b/core/res/res/values/themes_device_defaults.xml @@ -1318,6 +1318,9 @@ easier. <!-- Progress bar attributes --> <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item> <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item> + + <!-- volume background --> + <item name="panelColorBackground">@color/primary_material_light</item> </style> <style name="Theme.DeviceDefault.QuickSettings.Dialog" parent="Theme.DeviceDefault.Light.Dialog"> diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 53c22f624e8c..0f019e711954 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -1379,6 +1379,10 @@ android:theme="@style/Theme"> </activity> + <activity android:name="android.app.activity.ActivityThreadTest$TestActivity" + android:exported="true"> + </activity> + <activity android:name="android.os.TestVrActivity" android:enableVrMode="com.android.frameworks.coretests/android.os.TestVrActivity$TestVrListenerService"> diff --git a/core/tests/coretests/res/values/styles.xml b/core/tests/coretests/res/values/styles.xml index ef3a481ae26c..bcde4c1de137 100644 --- a/core/tests/coretests/res/values/styles.xml +++ b/core/tests/coretests/res/values/styles.xml @@ -25,8 +25,8 @@ <style name="LayoutInDisplayCutoutModeDefault"> <item name="android:windowLayoutInDisplayCutoutMode">default</item> </style> - <style name="LayoutInDisplayCutoutModeAlways"> - <item name="android:windowLayoutInDisplayCutoutMode">always</item> + <style name="LayoutInDisplayCutoutModeShortEdges"> + <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> </style> <style name="LayoutInDisplayCutoutModeNever"> <item name="android:windowLayoutInDisplayCutoutMode">never</item> diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java new file mode 100644 index 000000000000..4628aa9e0f1a --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -0,0 +1,95 @@ +/* + * 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.app.activity; + +import android.app.Activity; +import android.app.IApplicationThread; +import android.app.servertransaction.ActivityRelaunchItem; +import android.app.servertransaction.ClientTransaction; +import android.app.servertransaction.ClientTransactionItem; +import android.app.servertransaction.ResumeActivityItem; +import android.content.Intent; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.MediumTest; +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; +import android.util.MergedConfiguration; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test for verifying {@link android.app.ActivityThread} class. + */ +@RunWith(AndroidJUnit4.class) +@MediumTest +public class ActivityThreadTest { + + private final ActivityTestRule mActivityTestRule = + new ActivityTestRule(TestActivity.class, true /* initialTouchMode */, + false /* launchActivity */); + + @Test + public void testDoubleRelaunch() throws Exception { + final Activity activity = mActivityTestRule.launchActivity(new Intent()); + final IApplicationThread appThread = activity.getActivityThread().getApplicationThread(); + + appThread.scheduleTransaction(newRelaunchResumeTransaction(activity)); + appThread.scheduleTransaction(newRelaunchResumeTransaction(activity)); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } + + @Test + public void testResumeAfterRelaunch() throws Exception { + final Activity activity = mActivityTestRule.launchActivity(new Intent()); + final IApplicationThread appThread = activity.getActivityThread().getApplicationThread(); + + appThread.scheduleTransaction(newRelaunchResumeTransaction(activity)); + appThread.scheduleTransaction(newResumeTransaction(activity)); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } + + private static ClientTransaction newRelaunchResumeTransaction(Activity activity) { + final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(null, + null, 0, new MergedConfiguration(), + false /* preserveWindow */); + final ResumeActivityItem resumeStateRequest = + ResumeActivityItem.obtain(true /* isForward */); + final IApplicationThread appThread = activity.getActivityThread().getApplicationThread(); + final ClientTransaction transaction = + ClientTransaction.obtain(appThread, activity.getActivityToken()); + transaction.addCallback(callbackItem); + transaction.setLifecycleStateRequest(resumeStateRequest); + + return transaction; + } + + private static ClientTransaction newResumeTransaction(Activity activity) { + final ResumeActivityItem resumeStateRequest = + ResumeActivityItem.obtain(true /* isForward */); + final IApplicationThread appThread = activity.getActivityThread().getApplicationThread(); + final ClientTransaction transaction = + ClientTransaction.obtain(appThread, activity.getActivityToken()); + transaction.setLifecycleStateRequest(resumeStateRequest); + + return transaction; + } + + // Test activity + public static class TestActivity extends Activity { + } +} diff --git a/core/tests/coretests/src/android/view/DisplayCutoutTest.java b/core/tests/coretests/src/android/view/DisplayCutoutTest.java index d80735326568..6e9401d7c7a0 100644 --- a/core/tests/coretests/src/android/view/DisplayCutoutTest.java +++ b/core/tests/coretests/src/android/view/DisplayCutoutTest.java @@ -17,7 +17,6 @@ package android.view; import static android.view.DisplayCutout.NO_CUTOUT; -import static android.view.DisplayCutout.fromBoundingRect; import static android.view.DisplayCutout.fromSpec; import static org.hamcrest.Matchers.not; @@ -29,17 +28,17 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import android.graphics.Rect; -import android.graphics.Region; import android.os.Parcel; import android.platform.test.annotations.Presubmit; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.util.Size; import android.view.DisplayCutout.ParcelableWrapper; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Arrays; + @RunWith(AndroidJUnit4.class) @SmallTest @Presubmit @@ -48,7 +47,7 @@ public class DisplayCutoutTest { /** This is not a consistent cutout. Useful for verifying insets in one go though. */ final DisplayCutout mCutoutNumbers = new DisplayCutout( new Rect(1, 2, 3, 4), - new Region(5, 6, 7, 8), new Size(9, 10)); + Arrays.asList(new Rect(5, 6, 7, 8))); final DisplayCutout mCutoutTop = createCutoutTop(); @@ -70,7 +69,7 @@ public class DisplayCutoutTest { @Test public void getBoundingRect() throws Exception { - assertEquals(new Rect(50, 0, 75, 100), mCutoutTop.getBoundingRect()); + assertEquals(new Rect(50, 0, 75, 100), mCutoutTop.getBounds().getBounds()); } @Test @@ -154,91 +153,7 @@ public class DisplayCutoutTest { public void inset_bounds() throws Exception { DisplayCutout cutout = mCutoutTop.inset(1, 2, 3, 4); - assertEquals(new Rect(49, -2, 74, 98), cutout.getBoundingRect()); - } - - @Test - public void computeSafeInsets_top() throws Exception { - DisplayCutout cutout = fromBoundingRect(0, 0, 100, 20) - .computeSafeInsets(200, 400); - - assertEquals(new Rect(0, 20, 0, 0), cutout.getSafeInsets()); - } - - @Test - public void computeSafeInsets_left() throws Exception { - DisplayCutout cutout = fromBoundingRect(0, 0, 20, 100) - .computeSafeInsets(400, 200); - - assertEquals(new Rect(20, 0, 0, 0), cutout.getSafeInsets()); - } - - @Test - public void computeSafeInsets_bottom() throws Exception { - DisplayCutout cutout = fromBoundingRect(0, 180, 100, 200) - .computeSafeInsets(100, 200); - - assertEquals(new Rect(0, 0, 0, 20), cutout.getSafeInsets()); - } - - @Test - public void computeSafeInsets_right() throws Exception { - DisplayCutout cutout = fromBoundingRect(180, 0, 200, 100) - .computeSafeInsets(200, 100); - - assertEquals(new Rect(0, 0, 20, 0), cutout.getSafeInsets()); - } - - @Test - public void computeSafeInsets_bounds() throws Exception { - DisplayCutout cutout = mCutoutTop.computeSafeInsets(1000, 2000); - - assertEquals(mCutoutTop.getBoundingRect(), cutout.getBounds().getBounds()); - } - - @Test - public void calculateRelativeTo_top() throws Exception { - DisplayCutout cutout = fromBoundingRect(0, 0, 100, 20) - .computeSafeInsets(200, 400) - .calculateRelativeTo(new Rect(5, 5, 95, 195)); - - assertEquals(new Rect(0, 15, 0, 0), cutout.getSafeInsets()); - } - - @Test - public void calculateRelativeTo_left() throws Exception { - DisplayCutout cutout = fromBoundingRect(0, 0, 20, 100) - .computeSafeInsets(400, 200) - .calculateRelativeTo(new Rect(5, 5, 195, 95)); - - assertEquals(new Rect(15, 0, 0, 0), cutout.getSafeInsets()); - } - - @Test - public void calculateRelativeTo_bottom() throws Exception { - DisplayCutout cutout = fromBoundingRect(0, 180, 100, 200) - .computeSafeInsets(100, 200) - .calculateRelativeTo(new Rect(5, 5, 95, 195)); - - assertEquals(new Rect(0, 0, 0, 15), cutout.getSafeInsets()); - } - - @Test - public void calculateRelativeTo_right() throws Exception { - DisplayCutout cutout = fromBoundingRect(180, 0, 200, 100) - .computeSafeInsets(200, 100) - .calculateRelativeTo(new Rect(5, 5, 195, 95)); - - assertEquals(new Rect(0, 0, 15, 0), cutout.getSafeInsets()); - } - - @Test - public void calculateRelativeTo_bounds() throws Exception { - DisplayCutout cutout = fromBoundingRect(0, 0, 100, 20) - .computeSafeInsets(200, 400) - .calculateRelativeTo(new Rect(5, 10, 95, 180)); - - assertEquals(new Rect(-5, -10, 95, 10), cutout.getBounds().getBounds()); + assertEquals(new Rect(49, -2, 74, 98), cutout.getBounds().getBounds()); } @Test @@ -276,26 +191,26 @@ public class DisplayCutoutTest { @Test public void fromSpec_caches() { - DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 1f); - assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 1f), sameInstance(cached)); + DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f); + assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f), sameInstance(cached)); } @Test public void fromSpec_wontCacheIfSpecChanges() { - DisplayCutout cached = fromSpec("L1,0 L1000,1000 L0,1 z", 200, 1f); - assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 1f), not(sameInstance(cached))); + DisplayCutout cached = fromSpec("L1,0 L1000,1000 L0,1 z", 200, 400, 1f); + assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f), not(sameInstance(cached))); } @Test public void fromSpec_wontCacheIfScreenWidthChanges() { - DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 2000, 1f); - assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 1f), not(sameInstance(cached))); + DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 2000, 400, 1f); + assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f), not(sameInstance(cached))); } @Test public void fromSpec_wontCacheIfDensityChanges() { - DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 2f); - assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 1f), not(sameInstance(cached))); + DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 2f); + assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f), not(sameInstance(cached))); } @Test @@ -348,7 +263,6 @@ public class DisplayCutoutTest { private static DisplayCutout createCutoutWithInsets(int left, int top, int right, int bottom) { return new DisplayCutout( new Rect(left, top, right, bottom), - new Region(50, 0, 75, 100), - null); + Arrays.asList(new Rect(50, 0, 75, 100))); } } diff --git a/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java b/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java new file mode 100644 index 000000000000..b77982b6d137 --- /dev/null +++ b/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textclassifier; + +import static org.junit.Assert.assertEquals; + +import android.os.Parcel; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class SelectionEventTest { + + @Test + public void testParcel() { + final SelectionEvent[] captured = new SelectionEvent[1]; + final Logger logger = new Logger(new Logger.Config( + InstrumentationRegistry.getTargetContext(), Logger.WIDGET_TEXTVIEW, null)) { + @Override + public void writeEvent(SelectionEvent event) { + captured[0] = event; + } + }; + logger.logSelectionStartedEvent(SelectionEvent.INVOCATION_MANUAL, 0); + final SelectionEvent event = captured[0]; + final Parcel parcel = Parcel.obtain(); + event.writeToParcel(parcel, event.describeContents()); + parcel.setDataPosition(0); + assertEquals(event, SelectionEvent.CREATOR.createFromParcel(parcel)); + } +} diff --git a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java index c8994ddc457f..002df881f216 100644 --- a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java +++ b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java @@ -19,6 +19,7 @@ package com.android.internal.policy; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; @@ -74,12 +75,12 @@ public final class PhoneWindowTest { } @Test - public void layoutInDisplayCutoutMode_always() throws Exception { - createPhoneWindowWithTheme(R.style.LayoutInDisplayCutoutModeAlways); + public void layoutInDisplayCutoutMode_shortEdges() throws Exception { + createPhoneWindowWithTheme(R.style.LayoutInDisplayCutoutModeShortEdges); installDecor(); assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode, - is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS)); + is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)); } @Test diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/TestApplication.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/TestApplication.java index dece9a45cfd6..bcf94909cefa 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/TestApplication.java +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/TestApplication.java @@ -16,7 +16,7 @@ package com.android.multidexlegacyandexception; -import android.support.multidex.MultiDexApplication; +import androidx.multidex.MultiDexApplication; public class TestApplication extends MultiDexApplication { diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/MultiDexAndroidJUnitRunner.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/MultiDexAndroidJUnitRunner.java index 758ac1dcb4a0..92a3b0c0f7c3 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/MultiDexAndroidJUnitRunner.java +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/MultiDexAndroidJUnitRunner.java @@ -17,7 +17,7 @@ package com.android.multidexlegacyandexception.tests; import android.os.Bundle; -import android.support.multidex.MultiDex; +import androidx.multidex.MultiDex; import android.support.test.runner.AndroidJUnitRunner; public class MultiDexAndroidJUnitRunner extends AndroidJUnitRunner { diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/src/com/android/multidexlegacytestapp/TestApplication.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/src/com/android/multidexlegacytestapp/TestApplication.java index c52ad292b6de..f89d13259618 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/src/com/android/multidexlegacytestapp/TestApplication.java +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/src/com/android/multidexlegacytestapp/TestApplication.java @@ -16,7 +16,7 @@ package com.android.multidexlegacytestapp; -import android.support.multidex.MultiDexApplication; +import androidx.multidex.MultiDexApplication; import java.lang.annotation.Annotation; diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/MultiDexAndroidJUnitRunner.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/MultiDexAndroidJUnitRunner.java index 963f90432dd7..9e41a925de89 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/MultiDexAndroidJUnitRunner.java +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/MultiDexAndroidJUnitRunner.java @@ -1,7 +1,7 @@ package com.android.multidexlegacytestapp.test2; import android.os.Bundle; -import android.support.multidex.MultiDex; +import androidx.multidex.MultiDex; import android.support.test.runner.AndroidJUnitRunner; public class MultiDexAndroidJUnitRunner extends AndroidJUnitRunner { diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppWithCorruptedDex/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppWithCorruptedDex/AndroidManifest.xml index 7993c6f48f83..5f006fe59a63 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppWithCorruptedDex/AndroidManifest.xml +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppWithCorruptedDex/AndroidManifest.xml @@ -9,7 +9,7 @@ android:targetSdkVersion="18" /> <application - android:name="android.support.multidex.MultiDexApplication" + android:name="androidx.multidex.MultiDexApplication" android:allowBackup="true" android:label="MultiDexLegacyTestApp_corrupted"> <activity diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java index cb0a591559db..bbb494425901 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java @@ -20,7 +20,7 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.IBinder; -import android.support.multidex.MultiDex; +import androidx.multidex.MultiDex; import android.util.Log; import java.io.File; diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/AndroidManifest.xml index c7b066d15b8a..f3f11b99e19d 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/AndroidManifest.xml +++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/AndroidManifest.xml @@ -9,7 +9,7 @@ android:targetSdkVersion="18" /> <application - android:name="android.support.multidex.MultiDexApplication" + android:name="androidx.multidex.MultiDexApplication" android:allowBackup="true" android:label="MultiDexLegacyVersionedTestApp_v1"> <activity diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/AndroidManifest.xml index 4d247931f547..e43e56b24654 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/AndroidManifest.xml +++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/AndroidManifest.xml @@ -9,7 +9,7 @@ android:targetSdkVersion="18" /> <application - android:name="android.support.multidex.MultiDexApplication" + android:name="androidx.multidex.MultiDexApplication" android:allowBackup="true" android:label="MultiDexLegacyVersionedTestApp_v2"> <activity diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/AndroidManifest.xml index 76c92dd05ca5..1d9722887998 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/AndroidManifest.xml +++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/AndroidManifest.xml @@ -9,7 +9,7 @@ android:targetSdkVersion="18" /> <application - android:name="android.support.multidex.MultiDexApplication" + android:name="androidx.multidex.MultiDexApplication" android:allowBackup="true" android:label="MultiDexLegacyVersionedTestApp_v3"> <activity diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 813eae74a051..35790b6558c7 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -6,10 +6,7 @@ cc_defaults { //"hwui_bugreport_font_cache_usage", //"hwui_compile_for_perf", "hwui_pgo", - // Disable LTO temporarily. LTO does not seem to interact well with - // PGO profile-file updates and incremental builds in the build - // servers. - // "hwui_lto", + "hwui_lto", ], cpp_std: "c++17", diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index 44d909972e37..3dd6879e4482 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -3002,6 +3002,7 @@ public final class MediaCodecInfo { // from OMX_VIDEO_HEVCPROFILETYPE public static final int HEVCProfileMain = 0x01; public static final int HEVCProfileMain10 = 0x02; + public static final int HEVCProfileMainStill = 0x04; public static final int HEVCProfileMain10HDR10 = 0x1000; // from OMX_VIDEO_HEVCLEVELTYPE @@ -3078,6 +3079,23 @@ public final class MediaCodecInfo { * {@link VideoCapabilities} to determine the codec capabilities. */ public int level; + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj instanceof CodecProfileLevel) { + CodecProfileLevel other = (CodecProfileLevel)obj; + return other.profile == profile && other.level == level; + } + return false; + } + + @Override + public int hashCode() { + return Long.hashCode(((long)profile << Integer.SIZE) | level); + } }; /** diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java index 2a3967c1d07a..b51c662539cf 100644 --- a/media/java/android/media/MediaController2.java +++ b/media/java/android/media/MediaController2.java @@ -156,17 +156,6 @@ public class MediaController2 implements AutoCloseable { @NonNull List<MediaItem2> playlist) { } /** - * Called when the playback state is changed. - * - * @param controller the controller for this event - * @param state latest playback state - * @hide - */ - // TODO(jaewan): Remove (b/73971431) - public void onPlaybackStateChanged(@NonNull MediaController2 controller, - @NonNull PlaybackState2 state) { } - - /** * Called when the player state is changed. * * @param controller the controller for this event @@ -647,20 +636,6 @@ public class MediaController2 implements AutoCloseable { } /** - * Get the lastly cached {@link PlaybackState2} from - * {@link ControllerCallback#onPlaybackStateChanged(MediaController2, PlaybackState2)}. - * <p> - * It may return {@code null} before the first callback or session has sent {@code null} - * playback state. - * - * @return a playback state. Can be {@code null} - * @hide - */ - public @Nullable PlaybackState2 getPlaybackState() { - return mProvider.getPlaybackState_impl(); - } - - /** * Get the lastly cached player state from * {@link ControllerCallback#onPlayerStateChanged(MediaController2, int)}. * @@ -748,7 +723,14 @@ public class MediaController2 implements AutoCloseable { } /** - * Return playlist from the session. + * Returns the cached playlist from + * {@link ControllerCallback#onPlaylistChanged(MediaController2, MediaPlaylistAgent, List, + * MediaMetadata2)}. + * <p> + * This list may differ with the list that was specified with + * {@link #setPlaylist(List, MediaMetadata2)} depending on the {@link MediaPlaylistAgent} + * implementation. Use media items returned here for other playlist agent APIs such as + * {@link MediaPlaylistAgent#skipToPlaylistItem(MediaItem2)}. * * @return playlist. Can be {@code null} if the controller doesn't have enough permission. */ @@ -758,12 +740,19 @@ public class MediaController2 implements AutoCloseable { /** * Sets the playlist. + * <p> + * Even when the playlist is successfully set, use the playlist returned from + * {@link #getPlaylist()} for playlist APIs such as {@link #skipToPlaylistItem(MediaItem2)}. + * Otherwise the session in the remote process can't distinguish between media items. * * @param list playlist * @param metadata metadata of the playlist + * @see #getPlaylist() + * @see ControllerCallback#onPlaylistChanged( + * MediaController2, MediaPlaylistAgent, List, MediaMetadata2) */ public void setPlaylist(@NonNull List<MediaItem2> list, @Nullable MediaMetadata2 metadata) { - // TODO(jaewan): Implement (b/74174649) + mProvider.setPlaylist_impl(list, metadata); } /** @@ -772,17 +761,20 @@ public class MediaController2 implements AutoCloseable { * @param metadata metadata of the playlist */ public void updatePlaylistMetadata(@Nullable MediaMetadata2 metadata) { - // TODO(jaewan): Implement (b/74174649) + mProvider.updatePlaylistMetadata_impl(metadata); } /** - * Returns the playlist metadata + * Gets the lastly cached playlist playlist metadata either from + * {@link ControllerCallback#onPlaylistMetadataChanged( + * MediaController2, MediaPlaylistAgent, MediaMetadata2)} or + * {@link ControllerCallback#onPlaylistChanged( + * MediaController2, MediaPlaylistAgent, List, MediaMetadata2)}. * * @return metadata metadata of the playlist, or null if none is set */ public @Nullable MediaMetadata2 getPlaylistMetadata() { - // TODO(jaewan): Implement (b/74174649) - return null; + return mProvider.getPlaylistMetadata_impl(); } /** @@ -797,15 +789,14 @@ public class MediaController2 implements AutoCloseable { } /** - * Inserts the media item to the play list at position index. + * Inserts the media item to the playlist at position index. * <p> * This will not change the currently playing media item. - * If index is less than or equal to the current index of the play list, - * the current index of the play list will be incremented correspondingly. + * If index is less than or equal to the current index of the playlist, + * the current index of the playlist will be incremented correspondingly. * * @param index the index you want to add * @param item the media item you want to add - * @throws IndexOutOfBoundsException if index is outside play list range */ public void addPlaylistItem(int index, @NonNull MediaItem2 item) { mProvider.addPlaylistItem_impl(index, item); @@ -816,6 +807,8 @@ public class MediaController2 implements AutoCloseable { *<p> * If the item is the currently playing item of the playlist, current playback * will be stopped and playback moves to next source in the list. + * + * @param item the media item you want to add */ public void removePlaylistItem(@NonNull MediaItem2 item) { mProvider.removePlaylistItem_impl(item); diff --git a/media/java/android/media/MediaItem2.java b/media/java/android/media/MediaItem2.java index 2db1392a9d83..b50c3e4bcd0b 100644 --- a/media/java/android/media/MediaItem2.java +++ b/media/java/android/media/MediaItem2.java @@ -65,6 +65,13 @@ public class MediaItem2 { } /** + * @hide + */ + public MediaItem2Provider getProvider() { + return mProvider; + } + + /** * Return this object as a bundle to share between processes. * * @return a new bundle instance @@ -141,9 +148,7 @@ public class MediaItem2 { @Override public boolean equals(Object obj) { - // TODO(jaewan): Override this. MediaItem2 may have auto-generated srcId when the DSD isn't - // set, and it should be compared for the equals. - return super.equals(obj); + return mProvider.equals_impl(obj); } /** diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java index 745eb74d6e20..1aeed6da3a31 100644 --- a/media/java/android/media/MediaMetadataRetriever.java +++ b/media/java/android/media/MediaMetadataRetriever.java @@ -17,6 +17,8 @@ package android.media; import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.ContentResolver; import android.content.Context; import android.content.res.AssetFileDescriptor; @@ -30,7 +32,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; - +import java.util.List; import java.util.Map; /** @@ -367,27 +369,79 @@ public class MediaMetadataRetriever private native Bitmap _getFrameAtTime(long timeUs, int option, int width, int height); + public static final class BitmapParams { + private Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888; + private Bitmap.Config outActualConfig = Bitmap.Config.ARGB_8888; + + /** + * Create a default BitmapParams object. By default, it uses {@link Bitmap.Config#ARGB_8888} + * as the preferred bitmap config. + */ + public BitmapParams() {} + + /** + * Set the preferred bitmap config for the decoder to decode into. + * + * If not set, or the request cannot be met, the decoder will output + * in {@link Bitmap.Config#ARGB_8888} config by default. + * + * After decode, the actual config used can be retrieved by {@link #getActualConfig()}. + * + * @param config the preferred bitmap config to use. + */ + public void setPreferredConfig(@NonNull Bitmap.Config config) { + if (config == null) { + throw new IllegalArgumentException("preferred config can't be null"); + } + inPreferredConfig = config; + } + + /** + * Retrieve the preferred bitmap config in the params. + * + * @return the preferred bitmap config. + */ + public @NonNull Bitmap.Config getPreferredConfig() { + return inPreferredConfig; + } + + /** + * Get the actual bitmap config used to decode the bitmap after the decoding. + * + * @return the actual bitmap config used. + */ + public @NonNull Bitmap.Config getActualConfig() { + return outActualConfig; + } + } + /** * This method retrieves a video frame by its index. It should only be called * after {@link #setDataSource}. * + * After the bitmap is returned, you can query the actual parameters that were + * used to create the bitmap from the {@code BitmapParams} argument, for instance + * to query the bitmap config used for the bitmap with {@link BitmapParams#getActualConfig}. + * * @param frameIndex 0-based index of the video frame. The frame index must be that of * a valid frame. The total number of frames available for retrieval can be queried * via the {@link #METADATA_KEY_VIDEO_FRAME_COUNT} key. + * @param params BitmapParams that controls the returned bitmap config (such as pixel formats). + * If null, default config will be chosen. * * @throws IllegalStateException if the container doesn't contain video or image sequences. * @throws IllegalArgumentException if the requested frame index does not exist. * * @return A Bitmap containing the requested video frame, or null if the retrieval fails. * - * @see #getFramesAtIndex(int, int) + * @see #getFramesAtIndex(int, int, BitmapParams) */ - public Bitmap getFrameAtIndex(int frameIndex) { - Bitmap[] bitmaps = getFramesAtIndex(frameIndex, 1); - if (bitmaps == null || bitmaps.length < 1) { + public Bitmap getFrameAtIndex(int frameIndex, @Nullable BitmapParams params) { + List<Bitmap> bitmaps = getFramesAtIndex(frameIndex, 1, params); + if (bitmaps == null || bitmaps.size() < 1) { return null; } - return bitmaps[0]; + return bitmaps.get(0); } /** @@ -395,24 +449,31 @@ public class MediaMetadataRetriever * specified index. It should only be called after {@link #setDataSource}. * * If the caller intends to retrieve more than one consecutive video frames, - * this method is preferred over {@link #getFrameAtIndex(int)} for efficiency. + * this method is preferred over {@link #getFrameAtIndex(int, BitmapParams)} for efficiency. + * + * After the bitmaps are returned, you can query the actual parameters that were + * used to create the bitmaps from the {@code BitmapParams} argument, for instance + * to query the bitmap config used for the bitmaps with {@link BitmapParams#getActualConfig}. * * @param frameIndex 0-based index of the first video frame to retrieve. The frame index * must be that of a valid frame. The total number of frames available for retrieval * can be queried via the {@link #METADATA_KEY_VIDEO_FRAME_COUNT} key. * @param numFrames number of consecutive video frames to retrieve. Must be a positive * value. The stream must contain at least numFrames frames starting at frameIndex. + * @param params BitmapParams that controls the returned bitmap config (such as pixel formats). + * If null, default config will be chosen. * * @throws IllegalStateException if the container doesn't contain video or image sequences. * @throws IllegalArgumentException if the frameIndex or numFrames is invalid, or the * stream doesn't contain at least numFrames starting at frameIndex. - * @return An array of Bitmaps containing the requested video frames. The returned + * @return An list of Bitmaps containing the requested video frames. The returned * array could contain less frames than requested if the retrieval fails. * - * @see #getFrameAtIndex(int) + * @see #getFrameAtIndex(int, BitmapParams) */ - public Bitmap[] getFramesAtIndex(int frameIndex, int numFrames) { + public List<Bitmap> getFramesAtIndex( + int frameIndex, int numFrames, @Nullable BitmapParams params) { if (!"yes".equals(extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO))) { throw new IllegalStateException("Does not contail video or image sequences"); } @@ -424,24 +485,32 @@ public class MediaMetadataRetriever throw new IllegalArgumentException("Invalid frameIndex or numFrames: " + frameIndex + ", " + numFrames); } - return _getFrameAtIndex(frameIndex, numFrames); + return _getFrameAtIndex(frameIndex, numFrames, params); } - private native Bitmap[] _getFrameAtIndex(int frameIndex, int numFrames); + private native List<Bitmap> _getFrameAtIndex( + int frameIndex, int numFrames, @Nullable BitmapParams params); /** * This method retrieves a still image by its index. It should only be called * after {@link #setDataSource}. * + * After the bitmap is returned, you can query the actual parameters that were + * used to create the bitmap from the {@code BitmapParams} argument, for instance + * to query the bitmap config used for the bitmap with {@link BitmapParams#getActualConfig}. + * * @param imageIndex 0-based index of the image, with negative value indicating * the primary image. + * @param params BitmapParams that controls the returned bitmap config (such as pixel formats). + * If null, default config will be chosen. + * * @throws IllegalStateException if the container doesn't contain still images. * @throws IllegalArgumentException if the requested image does not exist. * * @return the requested still image, or null if the image cannot be retrieved. * - * @see #getPrimaryImage + * @see #getPrimaryImage(BitmapParams) */ - public Bitmap getImageAtIndex(int imageIndex) { + public Bitmap getImageAtIndex(int imageIndex, @Nullable BitmapParams params) { if (!"yes".equals(extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE))) { throw new IllegalStateException("Does not contail still images"); } @@ -451,24 +520,31 @@ public class MediaMetadataRetriever throw new IllegalArgumentException("Invalid image index: " + imageCount); } - return _getImageAtIndex(imageIndex); + return _getImageAtIndex(imageIndex, params); } /** * This method retrieves the primary image of the media content. It should only * be called after {@link #setDataSource}. * + * After the bitmap is returned, you can query the actual parameters that were + * used to create the bitmap from the {@code BitmapParams} argument, for instance + * to query the bitmap config used for the bitmap with {@link BitmapParams#getActualConfig}. + * + * @param params BitmapParams that controls the returned bitmap config (such as pixel formats). + * If null, default config will be chosen. + * * @return the primary image, or null if it cannot be retrieved. * * @throws IllegalStateException if the container doesn't contain still images. * - * @see #getImageAtIndex(int) + * @see #getImageAtIndex(int, BitmapParams) */ - public Bitmap getPrimaryImage() { - return getImageAtIndex(-1); + public Bitmap getPrimaryImage(@Nullable BitmapParams params) { + return getImageAtIndex(-1, params); } - private native Bitmap _getImageAtIndex(int imageIndex); + private native Bitmap _getImageAtIndex(int imageIndex, @Nullable BitmapParams params); /** * Call this method after setDataSource(). This method finds the optional diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java index 08defbbe6697..ece19b9ca37b 100644 --- a/media/java/android/media/MediaPlayer2.java +++ b/media/java/android/media/MediaPlayer2.java @@ -214,7 +214,8 @@ import java.util.concurrent.Executor; * call returns right away, the actual seek operation may take a while to * finish, especially for audio/video being streamed. When the actual * seek operation completes, the internal player engine calls a user - * supplied MediaPlayer2EventCallback.onCallComplete() with {@link #MEDIA_CALL_SEEK_TO} + * supplied MediaPlayer2EventCallback.onCallCompleted() with + * {@link #CALL_COMPLETED_SEEK_TO} * if an MediaPlayer2EventCallback has been registered beforehand via * {@link #setMediaPlayer2EventCallback(Executor, MediaPlayer2EventCallback)}.</li> * <li>Please @@ -819,7 +820,7 @@ public abstract class MediaPlayer2 extends MediaPlayerBase * of a batch of commands. */ // This is an asynchronous call. - public void notifyWhenCommandLabelReached(Object label) { } + public void notifyWhenCommandLabelReached(@NonNull Object label) { } /** * Sets the {@link SurfaceHolder} to use for displaying the video @@ -1051,6 +1052,7 @@ public abstract class MediaPlayer2 extends MediaPlayerBase /** * MediaPlayer2 has not been prepared or just has been reset. * In this state, MediaPlayer2 doesn't fetch data. + * @hide */ public static final int MEDIAPLAYER2_STATE_IDLE = 1; @@ -1058,29 +1060,33 @@ public abstract class MediaPlayer2 extends MediaPlayerBase * MediaPlayer2 has been just prepared. * In this state, MediaPlayer2 just fetches data from media source, * but doesn't actively render data. + * @hide */ public static final int MEDIAPLAYER2_STATE_PREPARED = 2; /** * MediaPlayer2 is paused. * In this state, MediaPlayer2 doesn't actively render data. + * @hide */ public static final int MEDIAPLAYER2_STATE_PAUSED = 3; /** * MediaPlayer2 is actively playing back data. + * @hide */ public static final int MEDIAPLAYER2_STATE_PLAYING = 4; /** * MediaPlayer2 has hit some fatal error and cannot continue playback. + * @hide */ public static final int MEDIAPLAYER2_STATE_ERROR = 5; /** * @hide */ - @IntDef({ + @IntDef(flag = false, prefix = "MEDIAPLAYER2_STATE", value = { MEDIAPLAYER2_STATE_IDLE, MEDIAPLAYER2_STATE_PREPARED, MEDIAPLAYER2_STATE_PAUSED, @@ -1093,6 +1099,7 @@ public abstract class MediaPlayer2 extends MediaPlayerBase * Gets the current MediaPlayer2 state. * * @return the current MediaPlayer2 state. + * @hide */ public abstract @MediaPlayer2State int getMediaPlayer2State(); @@ -1170,8 +1177,7 @@ public abstract class MediaPlayer2 extends MediaPlayerBase public static final int PLAYBACK_RATE_AUDIO_MODE_DEFAULT = 0; /** @hide */ - @IntDef( - value = { + @IntDef(flag = false, prefix = "PLAYBACK_RATE_AUDIO_MODE", value = { PLAYBACK_RATE_AUDIO_MODE_DEFAULT, PLAYBACK_RATE_AUDIO_MODE_STRETCH, PLAYBACK_RATE_AUDIO_MODE_RESAMPLE, @@ -1276,8 +1282,7 @@ public abstract class MediaPlayer2 extends MediaPlayerBase public static final int SEEK_CLOSEST = 0x03; /** @hide */ - @IntDef( - value = { + @IntDef(flag = false, prefix = "SEEK", value = { SEEK_PREVIOUS_SYNC, SEEK_NEXT_SYNC, SEEK_CLOSEST_SYNC, @@ -1302,16 +1307,6 @@ public abstract class MediaPlayer2 extends MediaPlayerBase * If msec is negative, time position zero will be used. * If msec is larger than duration, duration will be used. * @param mode the mode indicating where exactly to seek to. - * Use {@link #SEEK_PREVIOUS_SYNC} if one wants to seek to a sync frame - * that has a timestamp earlier than or the same as msec. Use - * {@link #SEEK_NEXT_SYNC} if one wants to seek to a sync frame - * that has a timestamp later than or the same as msec. Use - * {@link #SEEK_CLOSEST_SYNC} if one wants to seek to a sync frame - * that has a timestamp closest to or the same as msec. Use - * {@link #SEEK_CLOSEST} if one wants to seek to a frame that may - * or may not be a sync frame but is closest to or the same as msec. - * {@link #SEEK_CLOSEST} often has larger performance overhead compared - * to the other options if there is no sync frame located at msec. */ // This is an asynchronous call. public abstract void seekTo(long msec, @SeekMode int mode); @@ -1753,28 +1748,20 @@ public abstract class MediaPlayer2 extends MediaPlayerBase * @param dsd the DataSourceDesc of this data source * @param data the timed metadata sample associated with this event */ - public void onTimedMetaDataAvailable(MediaPlayer2 mp, DataSourceDesc dsd, TimedMetaData data) { } + public void onTimedMetaDataAvailable( + MediaPlayer2 mp, DataSourceDesc dsd, TimedMetaData data) { } /** * Called to indicate an error. * * @param mp the MediaPlayer2 the error pertains to * @param dsd the DataSourceDesc of this data source - * @param what the type of error that has occurred: - * <ul> - * <li>{@link #MEDIA_ERROR_UNKNOWN} - * </ul> + * @param what the type of error that has occurred. * @param extra an extra code, specific to the error. Typically * implementation dependent. - * <ul> - * <li>{@link #MEDIA_ERROR_IO} - * <li>{@link #MEDIA_ERROR_MALFORMED} - * <li>{@link #MEDIA_ERROR_UNSUPPORTED} - * <li>{@link #MEDIA_ERROR_TIMED_OUT} - * <li><code>MEDIA_ERROR_SYSTEM (-2147483648)</code> - low-level system error. - * </ul> */ - public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) { } + public void onError( + MediaPlayer2 mp, DataSourceDesc dsd, @MediaError int what, int extra) { } /** * Called to indicate an info or a warning. @@ -1782,29 +1769,10 @@ public abstract class MediaPlayer2 extends MediaPlayerBase * @param mp the MediaPlayer2 the info pertains to. * @param dsd the DataSourceDesc of this data source * @param what the type of info or warning. - * <ul> - * <li>{@link #MEDIA_INFO_UNKNOWN} - * <li>{@link #MEDIA_INFO_STARTED_AS_NEXT} - * <li>{@link #MEDIA_INFO_VIDEO_RENDERING_START} - * <li>{@link #MEDIA_INFO_AUDIO_RENDERING_START} - * <li>{@link #MEDIA_INFO_PLAYBACK_COMPLETE} - * <li>{@link #MEDIA_INFO_PLAYLIST_END} - * <li>{@link #MEDIA_INFO_PREPARED} - * <li>{@link #MEDIA_INFO_VIDEO_TRACK_LAGGING} - * <li>{@link #MEDIA_INFO_BUFFERING_START} - * <li>{@link #MEDIA_INFO_BUFFERING_END} - * <li><code>MEDIA_INFO_NETWORK_BANDWIDTH (703)</code> - - * bandwidth information is available (as <code>extra</code> kbps) - * <li>{@link #MEDIA_INFO_BAD_INTERLEAVING} - * <li>{@link #MEDIA_INFO_NOT_SEEKABLE} - * <li>{@link #MEDIA_INFO_METADATA_UPDATE} - * <li>{@link #MEDIA_INFO_UNSUPPORTED_SUBTITLE} - * <li>{@link #MEDIA_INFO_SUBTITLE_TIMED_OUT} - * </ul> * @param extra an extra code, specific to the info. Typically * implementation dependent. */ - public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) { } + public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, @MediaInfo int what, int extra) { } /** * Called to acknowledge an API call. @@ -1812,33 +1780,11 @@ public abstract class MediaPlayer2 extends MediaPlayerBase * @param mp the MediaPlayer2 the call was made on. * @param dsd the DataSourceDesc of this data source * @param what the enum for the API call. - * <ul> - * <li>{@link #MEDIA_CALL_ATTACH_AUX_EFFECT} - * <li>{@link #MEDIA_CALL_DESELECT_TRACK} - * <li>{@link #MEDIA_CALL_LOOP_CURRENT} - * <li>{@link #MEDIA_CALL_PAUSE} - * <li>{@link #MEDIA_CALL_PLAY} - * <li>{@link #MEDIA_CALL_PREPARE} - * <li>{@link #MEDIA_CALL_RELEASE_DRM} - * <li>{@link #MEDIA_CALL_RESTORE_DRM_KEYS} - * <li>{@link #MEDIA_CALL_SEEK_TO} - * <li>{@link #MEDIA_CALL_SELECT_TRACK} - * <li>{@link #MEDIA_CALL_SET_AUDIO_ATTRIBUTES} - * <li>{@link #MEDIA_CALL_SET_AUDIO_SESSION_ID} - * <li>{@link #MEDIA_CALL_SET_AUX_EFFECT_SEND_LEVEL} - * <li>{@link #MEDIA_CALL_SET_DATA_SOURCE} - * <li>{@link #MEDIA_CALL_SET_NEXT_DATA_SOURCE} - * <li>{@link #MEDIA_CALL_SET_NEXT_DATA_SOURCES} - * <li>{@link #MEDIA_CALL_SET_PLAYBACK_PARAMS} - * <li>{@link #MEDIA_CALL_SET_PLAYBACK_SPEED} - * <li>{@link #MEDIA_CALL_SET_PLAYER_VOLUME} - * <li>{@link #MEDIA_CALL_SET_SURFACE} - * <li>{@link #MEDIA_CALL_SET_SYNC_PARAMS} - * <li>{@link #MEDIA_CALL_SKIP_TO_NEXT} - * </ul> * @param status the returned status code for the call. */ - public void onCallComplete(MediaPlayer2 mp, DataSourceDesc dsd, int what, int status) { } + public void onCallCompleted( + MediaPlayer2 mp, DataSourceDesc dsd, @CallCompleted int what, + @CallStatus int status) { } /** * Called to indicate media clock has changed. @@ -1847,7 +1793,8 @@ public abstract class MediaPlayer2 extends MediaPlayerBase * @param dsd the DataSourceDesc of this data source * @param timestamp the new media clock. */ - public void onMediaTimeChanged(MediaPlayer2 mp, DataSourceDesc dsd, MediaTimestamp timestamp) { } + public void onMediaTimeChanged( + MediaPlayer2 mp, DataSourceDesc dsd, MediaTimestamp timestamp) { } /** * Called to indicate {@link #notifyWhenCommandLabelReached(Object)} has been processed. @@ -1856,7 +1803,7 @@ public abstract class MediaPlayer2 extends MediaPlayerBase * @param label the application specific Object given by * {@link #notifyWhenCommandLabelReached(Object)}. */ - public void onCommandLabelReached(MediaPlayer2 mp, Object label) { } + public void onCommandLabelReached(MediaPlayer2 mp, @NonNull Object label) { } } /** @@ -1929,6 +1876,20 @@ public abstract class MediaPlayer2 extends MediaPlayerBase */ public static final int MEDIA_ERROR_SYSTEM = -2147483648; + /** + * @hide + */ + @IntDef(flag = false, prefix = "MEDIA_ERROR", value = { + MEDIA_ERROR_UNKNOWN, + MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK, + MEDIA_ERROR_IO, + MEDIA_ERROR_MALFORMED, + MEDIA_ERROR_UNSUPPORTED, + MEDIA_ERROR_TIMED_OUT, + MEDIA_ERROR_SYSTEM + }) + @Retention(RetentionPolicy.SOURCE) + public @interface MediaError {} /* Do not change these values without updating their counterparts * in include/media/mediaplayer2.h! @@ -2059,129 +2020,246 @@ public abstract class MediaPlayer2 extends MediaPlayerBase */ public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902; + /** + * @hide + */ + @IntDef(flag = false, prefix = "MEDIA_INFO", value = { + MEDIA_INFO_UNKNOWN, + MEDIA_INFO_STARTED_AS_NEXT, + MEDIA_INFO_VIDEO_RENDERING_START, + MEDIA_INFO_AUDIO_RENDERING_START, + MEDIA_INFO_PLAYBACK_COMPLETE, + MEDIA_INFO_PLAYLIST_END, + MEDIA_INFO_PREPARED, + MEDIA_INFO_VIDEO_TRACK_LAGGING, + MEDIA_INFO_BUFFERING_START, + MEDIA_INFO_BUFFERING_END, + MEDIA_INFO_NETWORK_BANDWIDTH, + MEDIA_INFO_BUFFERING_UPDATE, + MEDIA_INFO_BAD_INTERLEAVING, + MEDIA_INFO_NOT_SEEKABLE, + MEDIA_INFO_METADATA_UPDATE, + MEDIA_INFO_EXTERNAL_METADATA_UPDATE, + MEDIA_INFO_AUDIO_NOT_PLAYING, + MEDIA_INFO_VIDEO_NOT_PLAYING, + MEDIA_INFO_TIMED_TEXT_ERROR, + MEDIA_INFO_UNSUPPORTED_SUBTITLE, + MEDIA_INFO_SUBTITLE_TIMED_OUT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface MediaInfo {} + //-------------------------------------------------------------------------- /** The player just completed a call {@link #attachAuxEffect}. - * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallComplete + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted */ - public static final int MEDIA_CALL_ATTACH_AUX_EFFECT = 1; + public static final int CALL_COMPLETED_ATTACH_AUX_EFFECT = 1; /** The player just completed a call {@link #deselectTrack}. - * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallComplete + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted */ - public static final int MEDIA_CALL_DESELECT_TRACK = 2; + public static final int CALL_COMPLETED_DESELECT_TRACK = 2; /** The player just completed a call {@link #loopCurrent}. - * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallComplete + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted */ - public static final int MEDIA_CALL_LOOP_CURRENT = 3; + public static final int CALL_COMPLETED_LOOP_CURRENT = 3; /** The player just completed a call {@link #pause}. - * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallComplete + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted */ - public static final int MEDIA_CALL_PAUSE = 4; + public static final int CALL_COMPLETED_PAUSE = 4; /** The player just completed a call {@link #play}. - * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallComplete + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted */ - public static final int MEDIA_CALL_PLAY = 5; + public static final int CALL_COMPLETED_PLAY = 5; /** The player just completed a call {@link #prepare}. - * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallComplete + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted */ - public static final int MEDIA_CALL_PREPARE = 6; + public static final int CALL_COMPLETED_PREPARE = 6; /** The player just completed a call {@link #releaseDrm}. - * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallComplete + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted */ - public static final int MEDIA_CALL_RELEASE_DRM = 12; + public static final int CALL_COMPLETED_RELEASE_DRM = 12; /** The player just completed a call {@link #restoreDrmKeys}. - * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallComplete + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted */ - public static final int MEDIA_CALL_RESTORE_DRM_KEYS = 13; + public static final int CALL_COMPLETED_RESTORE_DRM_KEYS = 13; /** The player just completed a call {@link #seekTo}. - * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallComplete + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted */ - public static final int MEDIA_CALL_SEEK_TO = 14; + public static final int CALL_COMPLETED_SEEK_TO = 14; /** The player just completed a call {@link #selectTrack}. - * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallComplete + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted */ - public static final int MEDIA_CALL_SELECT_TRACK = 15; + public static final int CALL_COMPLETED_SELECT_TRACK = 15; /** The player just completed a call {@link #setAudioAttributes}. - * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallComplete + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted */ - public static final int MEDIA_CALL_SET_AUDIO_ATTRIBUTES = 16; + public static final int CALL_COMPLETED_SET_AUDIO_ATTRIBUTES = 16; /** The player just completed a call {@link #setAudioSessionId}. - * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallComplete + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted */ - public static final int MEDIA_CALL_SET_AUDIO_SESSION_ID = 17; + public static final int CALL_COMPLETED_SET_AUDIO_SESSION_ID = 17; /** The player just completed a call {@link #setAuxEffectSendLevel}. - * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallComplete + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted */ - public static final int MEDIA_CALL_SET_AUX_EFFECT_SEND_LEVEL = 18; + public static final int CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL = 18; /** The player just completed a call {@link #setDataSource}. - * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallComplete + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted */ - public static final int MEDIA_CALL_SET_DATA_SOURCE = 19; + public static final int CALL_COMPLETED_SET_DATA_SOURCE = 19; /** The player just completed a call {@link #setNextDataSource}. - * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallComplete + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted */ - public static final int MEDIA_CALL_SET_NEXT_DATA_SOURCE = 22; + public static final int CALL_COMPLETED_SET_NEXT_DATA_SOURCE = 22; /** The player just completed a call {@link #setNextDataSources}. - * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallComplete + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted */ - public static final int MEDIA_CALL_SET_NEXT_DATA_SOURCES = 23; + public static final int CALL_COMPLETED_SET_NEXT_DATA_SOURCES = 23; /** The player just completed a call {@link #setPlaybackParams}. - * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallComplete + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted */ - public static final int MEDIA_CALL_SET_PLAYBACK_PARAMS = 24; + public static final int CALL_COMPLETED_SET_PLAYBACK_PARAMS = 24; /** The player just completed a call {@link #setPlaybackSpeed}. - * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallComplete + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted */ - public static final int MEDIA_CALL_SET_PLAYBACK_SPEED = 25; + public static final int CALL_COMPLETED_SET_PLAYBACK_SPEED = 25; /** The player just completed a call {@link #setPlayerVolume}. - * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallComplete + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted */ - public static final int MEDIA_CALL_SET_PLAYER_VOLUME = 26; + public static final int CALL_COMPLETED_SET_PLAYER_VOLUME = 26; /** The player just completed a call {@link #setSurface}. - * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallComplete + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted */ - public static final int MEDIA_CALL_SET_SURFACE = 27; + public static final int CALL_COMPLETED_SET_SURFACE = 27; /** The player just completed a call {@link #setSyncParams}. - * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallComplete + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted */ - public static final int MEDIA_CALL_SET_SYNC_PARAMS = 28; + public static final int CALL_COMPLETED_SET_SYNC_PARAMS = 28; /** The player just completed a call {@link #skipToNext}. - * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallComplete + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted */ - public static final int MEDIA_CALL_SKIP_TO_NEXT = 29; + public static final int CALL_COMPLETED_SKIP_TO_NEXT = 29; /** The player just completed a call {@link #setBufferingParams}. - * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallComplete + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted * @hide */ - public static final int MEDIA_CALL_SET_BUFFERING_PARAMS = 1001; + public static final int CALL_COMPLETED_SET_BUFFERING_PARAMS = 1001; - /** The player just completed a call {@link #setPreferredDevice}. - * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallComplete + /** The player just completed a call {@code setVideoScalingMode}. + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted * @hide */ - public static final int MEDIA_CALL_SET_PREFERRED_DEVICE = 1002; + public static final int CALL_COMPLETED_SET_VIDEO_SCALING_MODE = 1002; + /** The player just completed a call {@code notifyWhenCommandLabelReached}. + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCommandLabelReached + * @hide + */ + public static final int CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED = 1003; + + /** + * @hide + */ + @IntDef(flag = false, prefix = "CALL_COMPLETED", value = { + CALL_COMPLETED_ATTACH_AUX_EFFECT, + CALL_COMPLETED_DESELECT_TRACK, + CALL_COMPLETED_LOOP_CURRENT, + CALL_COMPLETED_PAUSE, + CALL_COMPLETED_PLAY, + CALL_COMPLETED_PREPARE, + CALL_COMPLETED_RELEASE_DRM, + CALL_COMPLETED_RESTORE_DRM_KEYS, + CALL_COMPLETED_SEEK_TO, + CALL_COMPLETED_SELECT_TRACK, + CALL_COMPLETED_SET_AUDIO_ATTRIBUTES, + CALL_COMPLETED_SET_AUDIO_SESSION_ID, + CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL, + CALL_COMPLETED_SET_DATA_SOURCE, + CALL_COMPLETED_SET_NEXT_DATA_SOURCE, + CALL_COMPLETED_SET_NEXT_DATA_SOURCES, + CALL_COMPLETED_SET_PLAYBACK_PARAMS, + CALL_COMPLETED_SET_PLAYBACK_SPEED, + CALL_COMPLETED_SET_PLAYER_VOLUME, + CALL_COMPLETED_SET_SURFACE, + CALL_COMPLETED_SET_SYNC_PARAMS, + CALL_COMPLETED_SKIP_TO_NEXT, + CALL_COMPLETED_SET_BUFFERING_PARAMS, + CALL_COMPLETED_SET_VIDEO_SCALING_MODE, + CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CallCompleted {} + + /** Status code represents that call is completed without an error. + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted + */ + public static final int CALL_STATUS_NO_ERROR = 0; + + /** Status code represents that call is ended with an unknown error. + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted + */ + public static final int CALL_STATUS_ERROR_UNKNOWN = Integer.MIN_VALUE; + + /** Status code represents that the player is not in valid state for the operation. + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted + */ + public static final int CALL_STATUS_INVALID_OPERATION = 1; + + /** Status code represents that the argument is illegal. + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted + */ + public static final int CALL_STATUS_BAD_VALUE = 2; + + /** Status code represents that the operation is not allowed. + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted + */ + public static final int CALL_STATUS_PERMISSION_DENIED = 3; + + /** Status code represents a file or network related operation error. + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted + */ + public static final int CALL_STATUS_ERROR_IO = 4; + + /** Status code represents that DRM operation is called before preparing a DRM scheme through + * {@link #prepareDrm}. + * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted + */ + public static final int CALL_STATUS_NO_DRM_SCHEME = 5; + + /** + * @hide + */ + @IntDef(flag = false, prefix = "CALL_STATUS", value = { + CALL_STATUS_NO_ERROR, + CALL_STATUS_ERROR_UNKNOWN, + CALL_STATUS_INVALID_OPERATION, + CALL_STATUS_BAD_VALUE, + CALL_STATUS_PERMISSION_DENIED, + CALL_STATUS_ERROR_IO, + CALL_STATUS_NO_DRM_SCHEME}) + @Retention(RetentionPolicy.SOURCE) + public @interface CallStatus {} // Modular DRM begin @@ -2238,13 +2316,10 @@ public abstract class MediaPlayer2 extends MediaPlayerBase * * @param mp the {@code MediaPlayer2} associated with this callback * @param dsd the DataSourceDesc of this data source - * @param status the result of DRM preparation which can be - * {@link #PREPARE_DRM_STATUS_SUCCESS}, - * {@link #PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR}, - * {@link #PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR}, or - * {@link #PREPARE_DRM_STATUS_PREPARATION_ERROR}. + * @param status the result of DRM preparation. */ - public void onDrmPrepared(MediaPlayer2 mp, DataSourceDesc dsd, @PrepareDrmStatusCode int status) { } + public void onDrmPrepared( + MediaPlayer2 mp, DataSourceDesc dsd, @PrepareDrmStatusCode int status) { } } /** @@ -2288,7 +2363,7 @@ public abstract class MediaPlayer2 extends MediaPlayerBase /** @hide */ - @IntDef({ + @IntDef(flag = false, prefix = "PREPARE_DRM_STATUS", value = { PREPARE_DRM_STATUS_SUCCESS, PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR, PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR, diff --git a/media/java/android/media/MediaPlayer2Impl.java b/media/java/android/media/MediaPlayer2Impl.java index ee1bb50e5705..1c4d898acb4f 100644 --- a/media/java/android/media/MediaPlayer2Impl.java +++ b/media/java/android/media/MediaPlayer2Impl.java @@ -213,18 +213,13 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { */ @Override public void play() { - synchronized (mTaskLock) { - mPendingTasks.add(new Task(MEDIA_CALL_PLAY, false) { - @Override - int process() { - stayAwake(true); - _start(); - // TODO: define public constants for return value (status). - return 0; - } - }); - processPendingTask_l(); - } + addTask(new Task(CALL_COMPLETED_PLAY, false) { + @Override + void process() { + stayAwake(true); + _start(); + } + }); } private native void _start() throws IllegalStateException; @@ -241,17 +236,12 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { */ @Override public void prepare() { - synchronized (mTaskLock) { - mPendingTasks.add(new Task(MEDIA_CALL_PREPARE, true) { - @Override - int process() { - _prepare(); - // TODO: define public constants for return value (status). - return 0; - } - }); - processPendingTask_l(); - } + addTask(new Task(CALL_COMPLETED_PREPARE, true) { + @Override + void process() { + _prepare(); + } + }); } public native void _prepare(); @@ -264,8 +254,13 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { */ @Override public void pause() { - stayAwake(false); - _pause(); + addTask(new Task(CALL_COMPLETED_PAUSE, false) { + @Override + void process() { + stayAwake(false); + _pause(); + } + }); } private native void _pause() throws IllegalStateException; @@ -277,7 +272,12 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { */ @Override public void skipToNext() { - // TODO: switch to next data source and play + addTask(new Task(CALL_COMPLETED_SKIP_TO_NEXT, false) { + @Override + void process() { + // TODO: switch to next data source and play + } + }); } /** @@ -357,14 +357,19 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { */ @Override public void setAudioAttributes(@NonNull AudioAttributes attributes) { - if (attributes == null) { - final String msg = "Cannot set AudioAttributes to null"; - throw new IllegalArgumentException(msg); - } - Parcel pattributes = Parcel.obtain(); - attributes.writeToParcel(pattributes, AudioAttributes.FLATTEN_TAGS); - setParameter(KEY_PARAMETER_AUDIO_ATTRIBUTES, pattributes); - pattributes.recycle(); + 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); + } + Parcel pattributes = Parcel.obtain(); + attributes.writeToParcel(pattributes, AudioAttributes.FLATTEN_TAGS); + setParameter(KEY_PARAMETER_AUDIO_ATTRIBUTES, pattributes); + pattributes.recycle(); + } + }); } @Override @@ -384,16 +389,21 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { */ @Override public void setDataSource(@NonNull DataSourceDesc dsd) { - Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null"); - // TODO: setDataSource could update exist data source - synchronized (mSrcLock) { - mCurrentDSD = dsd; - mCurrentSrcId = mSrcIdGenerator++; - try { - handleDataSource(true /* isCurrent */, dsd, mCurrentSrcId); - } catch (IOException e) { + addTask(new Task(CALL_COMPLETED_SET_DATA_SOURCE, false) { + @Override + void process() { + Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null"); + // TODO: setDataSource could update exist data source + synchronized (mSrcLock) { + mCurrentDSD = dsd; + mCurrentSrcId = mSrcIdGenerator++; + try { + handleDataSource(true /* isCurrent */, dsd, mCurrentSrcId); + } catch (IOException e) { + } + } } - } + }); } /** @@ -406,21 +416,25 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { */ @Override public void setNextDataSource(@NonNull DataSourceDesc dsd) { - Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null"); - - synchronized (mSrcLock) { - mNextDSDs = new ArrayList<DataSourceDesc>(1); - mNextDSDs.add(dsd); - mNextSrcId = mSrcIdGenerator++; - mNextSourceState = NEXT_SOURCE_STATE_INIT; - mNextSourcePlayPending = false; - } - int state = getMediaPlayer2State(); - if (state != MEDIAPLAYER2_STATE_IDLE) { - synchronized (mSrcLock) { - prepareNextDataSource_l(); + addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCE, false) { + @Override + void process() { + Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null"); + synchronized (mSrcLock) { + mNextDSDs = new ArrayList<DataSourceDesc>(1); + mNextDSDs.add(dsd); + mNextSrcId = mSrcIdGenerator++; + mNextSourceState = NEXT_SOURCE_STATE_INIT; + mNextSourcePlayPending = false; + } + int state = getMediaPlayer2State(); + if (state != MEDIAPLAYER2_STATE_IDLE) { + synchronized (mSrcLock) { + prepareNextDataSource_l(); + } + } } - } + }); } /** @@ -432,28 +446,33 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { */ @Override public void setNextDataSources(@NonNull List<DataSourceDesc> dsds) { - 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."); - } - } + 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) { - mNextDSDs = new ArrayList(dsds); - mNextSrcId = mSrcIdGenerator++; - mNextSourceState = NEXT_SOURCE_STATE_INIT; - mNextSourcePlayPending = false; - } - int state = getMediaPlayer2State(); - if (state != MEDIAPLAYER2_STATE_IDLE) { - synchronized (mSrcLock) { - prepareNextDataSource_l(); + synchronized (mSrcLock) { + mNextDSDs = new ArrayList(dsds); + mNextSrcId = mSrcIdGenerator++; + mNextSourceState = NEXT_SOURCE_STATE_INIT; + mNextSourcePlayPending = false; + } + int state = getMediaPlayer2State(); + if (state != MEDIAPLAYER2_STATE_IDLE) { + synchronized (mSrcLock) { + prepareNextDataSource_l(); + } + } } - } + }); } @Override @@ -469,8 +488,13 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { */ @Override public void loopCurrent(boolean loop) { - // TODO: set the looping mode, send notification - setLooping(loop); + addTask(new Task(CALL_COMPLETED_LOOP_CURRENT, false) { + @Override + void process() { + // TODO: set the looping mode, send notification + setLooping(loop); + } + }); } private native void setLooping(boolean looping); @@ -486,8 +510,12 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { */ @Override public void setPlaybackSpeed(float speed) { - // TODO: send notification - setPlaybackParams(getPlaybackParams().setSpeed(speed)); + addTask(new Task(CALL_COMPLETED_SET_PLAYBACK_SPEED, false) { + @Override + void process() { + _setPlaybackParams(getPlaybackParams().setSpeed(speed)); + } + }); } /** @@ -522,8 +550,12 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { */ @Override public void setPlayerVolume(float volume) { - // send notification - _setVolume(volume, volume); + addTask(new Task(CALL_COMPLETED_SET_PLAYER_VOLUME, false) { + @Override + void process() { + _setVolume(volume, volume); + } + }); } private native void _setVolume(float leftVolume, float rightVolume); @@ -630,7 +662,17 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { @Override public void notifyWhenCommandLabelReached(Object label) { - // TODO: create an entry in command queue + addTask(new Task(CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED, false) { + @Override + void process() { + synchronized (mEventCbLock) { + for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) { + cb.first.execute(() -> cb.second.onCommandLabelReached( + MediaPlayer2Impl.this, label)); + } + } + } + }); } /** @@ -683,12 +725,17 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { */ @Override public void setSurface(Surface surface) { - if (mScreenOnWhilePlaying && surface != null) { - Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective for Surface"); - } - mSurfaceHolder = null; - _setVideoSurface(surface); - updateSurfaceScreenOn(); + 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; + _setVideoSurface(surface); + updateSurfaceScreenOn(); + } + }); } /** @@ -712,20 +759,25 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { */ @Override public void setVideoScalingMode(int mode) { - if (!isVideoScalingModeSupported(mode)) { - final String msg = "Scaling mode " + mode + " is not supported"; - throw new IllegalArgumentException(msg); - } - Parcel request = Parcel.obtain(); - Parcel reply = Parcel.obtain(); - try { - request.writeInt(INVOKE_ID_SET_VIDEO_SCALE_MODE); - request.writeInt(mode); - invoke(request, reply); - } finally { - request.recycle(); - reply.recycle(); - } + addTask(new Task(CALL_COMPLETED_SET_VIDEO_SCALING_MODE, false) { + @Override + void process() { + if (!isVideoScalingModeSupported(mode)) { + final String msg = "Scaling mode " + mode + " is not supported"; + throw new IllegalArgumentException(msg); + } + Parcel request = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + try { + request.writeInt(INVOKE_ID_SET_VIDEO_SCALE_MODE); + request.writeInt(mode); + invoke(request, reply); + } finally { + request.recycle(); + reply.recycle(); + } + } + }); } /** @@ -735,6 +787,13 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { public void clearPendingCommands() { } + private void addTask(Task task) { + synchronized (mTaskLock) { + mPendingTasks.add(task); + processPendingTask_l(); + } + } + @GuardedBy("mTaskLock") private void processPendingTask_l() { if (mCurrentTask != null) { @@ -1332,7 +1391,17 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * @hide */ @Override - public native void setBufferingParams(@NonNull BufferingParams params); + public void setBufferingParams(@NonNull BufferingParams params) { + addTask(new Task(CALL_COMPLETED_SET_BUFFERING_PARAMS, false) { + @Override + void process() { + Preconditions.checkNotNull(params, "the BufferingParams cannot be null"); + _setBufferingParams(params); + } + }); + } + + private native void _setBufferingParams(@NonNull BufferingParams params); /** * Sets playback rate and audio mode. @@ -1386,7 +1455,17 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * @throws IllegalArgumentException if params is not supported. */ @Override - public native void setPlaybackParams(@NonNull PlaybackParams params); + public void setPlaybackParams(@NonNull PlaybackParams params) { + addTask(new Task(CALL_COMPLETED_SET_PLAYBACK_PARAMS, false) { + @Override + void process() { + Preconditions.checkNotNull(params, "the PlaybackParams cannot be null"); + _setPlaybackParams(params); + } + }); + } + + private native void _setPlaybackParams(@NonNull PlaybackParams params); /** * Gets the playback params, containing the current playback rate. @@ -1409,7 +1488,17 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * @throws IllegalArgumentException if params are not supported. */ @Override - public native void setSyncParams(@NonNull SyncParams params); + public void setSyncParams(@NonNull SyncParams params) { + addTask(new Task(CALL_COMPLETED_SET_SYNC_PARAMS, false) { + @Override + void process() { + Preconditions.checkNotNull(params, "the SyncParams cannot be null"); + _setSyncParams(params); + } + }); + } + + private native void _setSyncParams(@NonNull SyncParams params); /** * Gets the A/V sync mode. @@ -1454,20 +1543,28 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * @throws IllegalArgumentException if the mode is invalid. */ @Override - public void seekTo(long msec, @SeekMode int mode) { - 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. - if (msec > Integer.MAX_VALUE) { - Log.w(TAG, "seekTo offset " + msec + " is too large, cap to " + Integer.MAX_VALUE); - msec = Integer.MAX_VALUE; - } else if (msec < Integer.MIN_VALUE) { - Log.w(TAG, "seekTo offset " + msec + " is too small, cap to " + Integer.MIN_VALUE); - msec = Integer.MIN_VALUE; - } - _seekTo(msec, mode); + public void seekTo(final long msec, @SeekMode int mode) { + 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; + } + _seekTo(posMs, mode); + } + }); } private native final void _seekTo(long msec, int mode); @@ -1694,7 +1791,16 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * @throws IllegalArgumentException if the sessionId is invalid. */ @Override - public native void setAudioSessionId(int sessionId); + public void setAudioSessionId(int sessionId) { + 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. @@ -1720,7 +1826,16 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * @param effectId system wide unique id of the effect to attach */ @Override - public native void attachAuxEffect(int effectId); + public void attachAuxEffect(int effectId) { + 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. @@ -1736,7 +1851,12 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { */ @Override public void setAuxEffectSendLevel(float level) { - _setAuxEffectSendLevel(level); + addTask(new Task(CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL, false) { + @Override + void process() { + _setAuxEffectSendLevel(level); + } + }); } private native void _setAuxEffectSendLevel(float level); @@ -2475,7 +2595,12 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { */ @Override public void selectTrack(int index) { - selectOrDeselectTrack(index, true /* select */); + addTask(new Task(CALL_COMPLETED_SELECT_TRACK, false) { + @Override + void process() { + selectOrDeselectTrack(index, true /* select */); + } + }); } /** @@ -2494,7 +2619,12 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { */ @Override public void deselectTrack(int index) { - selectOrDeselectTrack(index, false /* select */); + addTask(new Task(CALL_COMPLETED_DESELECT_TRACK, false) { + @Override + void process() { + selectOrDeselectTrack(index, false /* select */); + } + }); } private void selectOrDeselectTrack(int index, boolean select) @@ -2697,8 +2827,11 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { } } synchronized (mTaskLock) { - if (mCurrentTask.mMediaCallType == MEDIA_CALL_PREPARE + if (mCurrentTask != null + && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE + && mCurrentTask.mDSD == dsd && mCurrentTask.mNeedToWaitForEventToComplete) { + mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR); mCurrentTask = null; processPendingTask_l(); } @@ -2803,10 +2936,13 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { case MEDIA_SEEK_COMPLETE: { - synchronized (mEventCbLock) { - for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) { - cb.first.execute(() -> cb.second.onCallComplete( - mMediaPlayer, mCurrentDSD, MEDIA_CALL_SEEK_TO, 0)); + synchronized (mTaskLock) { + if (mCurrentTask != null + && mCurrentTask.mMediaCallType == CALL_COMPLETED_SEEK_TO + && mCurrentTask.mNeedToWaitForEventToComplete) { + mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR); + mCurrentTask = null; + processPendingTask_l(); } } } @@ -3390,32 +3526,39 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { public void releaseDrm() throws NoDrmSchemeException { - Log.v(TAG, "releaseDrm:"); - - synchronized (mDrmLock) { - 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(); + addTask(new Task(CALL_COMPLETED_RELEASE_DRM, false) { + @Override + void process() 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."); + } - 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); + 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 } - } // synchronized + }); } @@ -3470,7 +3613,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { synchronized (mDrmLock) { if (!mActiveDrmScheme) { Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException"); - throw new NoDrmSchemeExceptionImpl("getDrmKeyRequest: Has to set a DRM scheme first."); + throw new NoDrmSchemeExceptionImpl( + "getDrmKeyRequest: Has to set a DRM scheme first."); } try { @@ -3531,7 +3675,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { if (!mActiveDrmScheme) { Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException"); - throw new NoDrmSchemeExceptionImpl("getDrmKeyRequest: Has to set a DRM scheme first."); + throw new NoDrmSchemeExceptionImpl( + "getDrmKeyRequest: Has to set a DRM scheme first."); } try { @@ -3541,8 +3686,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { byte[] keySetResult = mDrmObj.provideKeyResponse(scope, response); - Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + " response: " + response + - " --> " + keySetResult); + Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + " response: " + response + + " --> " + keySetResult); return keySetResult; @@ -3570,23 +3715,29 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { public void restoreDrmKeys(@NonNull byte[] keySetId) throws NoDrmSchemeException { - Log.v(TAG, "restoreDrmKeys: keySetId: " + keySetId); + addTask(new Task(CALL_COMPLETED_RESTORE_DRM_KEYS, false) { + @Override + void process() throws NoDrmSchemeException { + Log.v(TAG, "restoreDrmKeys: keySetId: " + keySetId); - synchronized (mDrmLock) { + synchronized (mDrmLock) { - if (!mActiveDrmScheme) { - Log.w(TAG, "restoreDrmKeys NoDrmSchemeException"); - throw new NoDrmSchemeExceptionImpl("restoreDrmKeys: Has to set a DRM scheme first."); - } + 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; - } + try { + mDrmObj.restoreKeys(mDrmSessionId, keySetId); + } catch (Exception e) { + Log.w(TAG, "restoreKeys Exception " + e); + throw e; + } - } // synchronized + } // synchronized + } + }); } @@ -3611,7 +3762,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { if (!mActiveDrmScheme && !mDrmConfigAllowed) { Log.w(TAG, "getDrmPropertyString NoDrmSchemeException"); - throw new NoDrmSchemeExceptionImpl("getDrmPropertyString: Has to prepareDrm() first."); + throw new NoDrmSchemeExceptionImpl( + "getDrmPropertyString: Has to prepareDrm() first."); } try { @@ -3649,7 +3801,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { if ( !mActiveDrmScheme && !mDrmConfigAllowed ) { Log.w(TAG, "setDrmPropertyString NoDrmSchemeException"); - throw new NoDrmSchemeExceptionImpl("setDrmPropertyString: Has to prepareDrm() first."); + throw new NoDrmSchemeExceptionImpl( + "setDrmPropertyString: Has to prepareDrm() first."); } try { @@ -4587,34 +4740,61 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { 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 int process(); + abstract void process() throws IOException, NoDrmSchemeException; @Override public void run() { - int status = process(); + int status = CALL_STATUS_NO_ERROR; + try { + 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 (Exception e) { + status = CALL_STATUS_ERROR_UNKNOWN; + } + synchronized (mSrcLock) { + mDSD = mCurrentDSD; + } + + // TODO: Make native implementations asynchronous and let them send notifications. + if (!mNeedToWaitForEventToComplete || status != CALL_STATUS_NO_ERROR) { + + sendCompleteNotification(status); - if (!mNeedToWaitForEventToComplete) { - final DataSourceDesc dsd; - synchronized (mSrcLock) { - dsd = mCurrentDSD; - } - synchronized (mEventCbLock) { - for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) { - cb.first.execute(() -> cb.second.onCallComplete( - MediaPlayer2Impl.this, dsd, mMediaCallType, 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()}. + if (mMediaCallType == CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED) { + return; + } + synchronized (mEventCbLock) { + for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) { + cb.first.execute(() -> cb.second.onCallCompleted( + MediaPlayer2Impl.this, mDSD, mMediaCallType, status)); + } + } + } }; } diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java index 65378b4658a3..e60c5f9b3b8c 100644 --- a/media/java/android/media/MediaSession2.java +++ b/media/java/android/media/MediaSession2.java @@ -16,6 +16,8 @@ package android.media; +import static android.media.MediaPlayerBase.PLAYER_STATE_IDLE; + import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; @@ -61,7 +63,7 @@ import java.util.concurrent.Executor; * sessions can be created to provide finer grain controls of media. * <p> * If you want to support background playback, {@link MediaSessionService2} is preferred - * instead. With it, your playback can be revived even after you've finished playback. See + * instead. With it, your playback can be revived even after playback is finished. See * {@link MediaSessionService2} for details. * <p> * A session can be obtained by {@link Builder}. The owner of the session may pass its session token @@ -254,7 +256,7 @@ public class MediaSession2 implements AutoCloseable { public static final int COMMAND_CODE_PLAYLIST_GET_LIST = 18; /** - * Command code for {@link MediaController2#setPlaylist(List, MediaMetadata2). + * Command code for {@link MediaController2#setPlaylist(List, MediaMetadata2)}. * <p> * Command would be sent directly to the playlist agent if the session doesn't reject the * request through the @@ -263,11 +265,8 @@ public class MediaSession2 implements AutoCloseable { public static final int COMMAND_CODE_PLAYLIST_SET_LIST = 19; /** - * Command code for {@link MediaController2#getPlaylistMetadata()} ()}. This will expose + * Command code for {@link MediaController2#getPlaylistMetadata()}. This will expose * metadata information to the controller. - * * - * Command code for {@link MediaController2#setPlaylist(List, MediaMetadata2)} and - * {@link MediaController2#updatePlaylistMetadata(MediaMetadata2)}. * <p> * Command would be sent directly to the playlist agent if the session doesn't reject the * request through the @@ -827,7 +826,11 @@ public class MediaSession2 implements AutoCloseable { @NonNull MediaPlayerBase player, @NonNull MediaItem2 item, @BuffState int state) { } /** - * Called when a playlist is changed. + * Called when a playlist is changed from the {@link MediaPlaylistAgent}. + * <p> + * This is called when the underlying agent has called + * {@link MediaPlaylistAgent.PlaylistEventCallback#onPlaylistChanged(MediaPlaylistAgent, + * List, MediaMetadata2)}. * * @param session the session for this event * @param playlistAgent playlist agent for this event @@ -1638,13 +1641,36 @@ public class MediaSession2 implements AutoCloseable { } /** - * Return the {@link PlaybackState2} from the player. + * Get the player state. + * + * @return player state + * @hide + */ + public @PlayerState int getPlayerState() { + // TODO(jaewan): implement this (b/74578458) + return PLAYER_STATE_IDLE; + } + + /** + * Get the current position. * - * @return playback state + * @return position * @hide */ - public PlaybackState2 getPlaybackState() { - return mProvider.getPlaybackState_impl(); + public long getCurrentPosition() { + // TODO(jaewan): implement this (b/74578458) + return -1; + } + + /** + * Get the buffered position. + * + * @return buffered position + * @hide + */ + public long getBufferedPosition() { + // TODO(jaewan): implement this (b/74578458) + return -1; } /** @@ -1681,7 +1707,7 @@ public class MediaSession2 implements AutoCloseable { * <p> * If it's not set, playback wouldn't happen for the item without data source descriptor. * <p> - * The helper will be run on the executor that you've specified by the + * The helper will be run on the executor that was specified by * {@link Builder#setSessionCallback(Executor, SessionCallback)}. * * @param helper a data source missing helper. @@ -1705,40 +1731,46 @@ public class MediaSession2 implements AutoCloseable { } /** - * Return the playlist which is lastly set. + * Returns the playlist from the {@link MediaPlaylistAgent}. + * <p> + * This list may differ with the list that was specified with + * {@link #setPlaylist(List, MediaMetadata2)} depending on the {@link MediaPlaylistAgent} + * implementation. Use media items returned here for other playlist agent APIs such as + * {@link MediaPlaylistAgent#skipToPlaylistItem(MediaItem2)}. * * @return playlist + * @see MediaPlaylistAgent#getPlaylist() + * @see SessionCallback#onPlaylistChanged( + * MediaSession2, MediaPlaylistAgent, List, MediaMetadata2) */ public List<MediaItem2> getPlaylist() { return mProvider.getPlaylist_impl(); } /** - * Set a list of {@link MediaItem2} as the current play list. - * - * @param playlist A list of {@link MediaItem2} objects to set as a play list. - * @throws IllegalArgumentException if given {@param playlist} is null. - * @hide - */ - // TODO(jaewan): Remove - public void setPlaylist(@NonNull List<MediaItem2> playlist) { - mProvider.setPlaylist_impl(playlist); - } - - /** - * Set a list of {@link MediaItem2} as the current play list. Ensure uniqueness in the - * {@link MediaItem2} in the playlist so session can uniquely identity individual items. + * Sets a list of {@link MediaItem2} to the {@link MediaPlaylistAgent}. Ensure uniqueness of + * each {@link MediaItem2} in the playlist so the session can uniquely identity individual + * items. + * <p> + * This may be an asynchronous call, and {@link MediaPlaylistAgent} may keep the copy of the + * list. Wait for {@link SessionCallback#onPlaylistChanged(MediaSession2, MediaPlaylistAgent, + * List, MediaMetadata2)} to know the operation finishes. * <p> - * You may specify a {@link MediaItem2} without {@link DataSourceDesc}. However, in that case, - * you should set {@link OnDataSourceMissingHelper} for player to prepare. + * You may specify a {@link MediaItem2} without {@link DataSourceDesc}. In that case, + * {@link MediaPlaylistAgent} has responsibility to dynamically query {@link DataSourceDesc} + * when such media item is ready for preparation or play. Default implementation needs + * {@link OnDataSourceMissingHelper} for such case. * * @param list A list of {@link MediaItem2} objects to set as a play list. - * @throws IllegalArgumentException if given list is {@code null}, or has duplicated media item. + * @throws IllegalArgumentException if given list is {@code null}, or has duplicated media + * items. + * @see MediaPlaylistAgent#setPlaylist(List, MediaMetadata2) + * @see SessionCallback#onPlaylistChanged( + * MediaSession2, MediaPlaylistAgent, List, MediaMetadata2) * @see #setOnDataSourceMissingHelper */ public void setPlaylist(@NonNull List<MediaItem2> list, @Nullable MediaMetadata2 metadata) { - // TODO(jaewan): Handle metadata here (b/74174649) - // TODO(jaewan): Handle list change (b/74326040) + mProvider.setPlaylist_impl(list, metadata); } /** @@ -1760,13 +1792,17 @@ public class MediaSession2 implements AutoCloseable { mProvider.skipToNextItem_impl(); } + /** + * Gets the playlist metadata from the {@link MediaPlaylistAgent}. + * + * @return the playlist metadata + */ public MediaMetadata2 getPlaylistMetadata() { - // TODO(jaewan): Implement (b/74174649) - return null; + return mProvider.getPlaylistMetadata_impl(); } /** - * Add the media item to the play list at position index. + * Adds the media item to the playlist at position index. * <p> * This will not change the currently playing media item. * If index is less than or equal to the current index of the play list, @@ -1774,26 +1810,25 @@ public class MediaSession2 implements AutoCloseable { * * @param index the index you want to add * @param item the media item you want to add - * @throws IndexOutOfBoundsException if index is outside play list range */ public void addPlaylistItem(int index, @NonNull MediaItem2 item) { mProvider.addPlaylistItem_impl(index, item); } /** - * Remove the media item in the play list. + * Removes the media item in the playlist. * <p> * If the item is the currently playing item of the playlist, current playback * will be stopped and playback moves to next source in the list. * - * @throws IllegalArgumentException if the play list is null + * @param item the media item you want to add */ public void removePlaylistItem(@NonNull MediaItem2 item) { mProvider.removePlaylistItem_impl(item); } /** - * Replace the media item at index in the playlist. This can be also used to update metadata of + * Replaces the media item at index in the playlist. This can be also used to update metadata of * an item. * * @param index the index of the item to replace @@ -1813,8 +1848,13 @@ public class MediaSession2 implements AutoCloseable { return mProvider.getCurrentPlaylistItem_impl(); } + /** + * Updates the playlist metadata to the {@link MediaPlaylistAgent}. + * + * @param metadata metadata of the playlist + */ public void updatePlaylistMetadata(@Nullable MediaMetadata2 metadata) { - // TODO(jaewan): Implement (b/74174649) + mProvider.updatePlaylistMetadata_impl(metadata); } public @RepeatMode int getRepeatMode() { diff --git a/media/java/android/media/PlaybackState2.java b/media/java/android/media/PlaybackState2.java deleted file mode 100644 index afc2bfa2f9a1..000000000000 --- a/media/java/android/media/PlaybackState2.java +++ /dev/null @@ -1,217 +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.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.Context; -import android.media.update.ApiLoader; -import android.media.update.PlaybackState2Provider; -import android.os.Bundle; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Playback state for a {@link MediaPlayerBase}, to be shared between {@link MediaSession2} and - * {@link MediaController2}. This includes a playback state {@link #STATE_PLAYING}, - * the current playback position and extra. - * @hide - */ -// TODO(jaewan): Remove this (b/73971431) -public final class PlaybackState2 { - // Similar to the PlaybackState with following changes - // - Not implement Parcelable and added from/toBundle() - // - Removed playback state that doesn't match with the MediaPlayer2 - // Full list should be finalized when the MediaPlayer2 has getter for the playback state. - // Here's table for the MP2 state and PlaybackState2.State. - // +----------------------------------------+----------------------------------------+ - // | MediaPlayer2 state | Matching PlaybackState2.State | - // | (Names are from MP2' Javadoc) | | - // +----------------------------------------+----------------------------------------+ - // | Idle: Just finished creating MP2 | STATE_NONE | - // | or reset() is called | | - // +----------------------------------------+----------------------------------------+ - // | Initialized: setDataSource/Playlist | N/A (Session/Controller don't | - // | | differentiate with Prepared) | - // +----------------------------------------+----------------------------------------+ - // | Prepared: Prepared after initialized | STATE_PAUSED | - // +----------------------------------------+----------------------------------------+ - // | Started: Started playback | STATE_PLAYING | - // +----------------------------------------+----------------------------------------+ - // | Paused: Paused playback | STATE_PAUSED | - // +----------------------------------------+----------------------------------------+ - // | PlaybackCompleted: Playback is done | STATE_PAUSED | - // +----------------------------------------+----------------------------------------+ - // | Stopped: MP2.stop() is called. | STATE_STOPPED | - // | prepare() is needed to play again | | - // | (Seemingly the same as initialized | | - // | because cannot set data source | | - // | after this) | | - // +----------------------------------------+----------------------------------------+ - // | Error: an API is called in a state | STATE_ERROR | - // | that the API isn't supported | | - // +----------------------------------------+----------------------------------------+ - // | End: MP2.close() is called to release | N/A (MediaSession will be gone) | - // | MP2. Cannot be reused anymore | | - // +----------------------------------------+----------------------------------------+ - // | Started, but | STATE_BUFFERING | - // | EventCallback.onBufferingUpdate() | | - // +----------------------------------------+----------------------------------------+ - // - Removed actions and custom actions. - // - Removed error string - // - Repeat mode / shuffle mode is now in the PlaylistParams - /** - * @hide - */ - @IntDef({STATE_NONE, STATE_STOPPED, STATE_PAUSED, STATE_PLAYING, STATE_BUFFERING, STATE_ERROR}) - @Retention(RetentionPolicy.SOURCE) - public @interface State {} - - /** - * This is the default playback state and indicates that no media has been - * added yet, or the performer has been reset and has no content to play. - */ - public final static int STATE_NONE = 0; - - /** - * State indicating this item is currently stopped. - */ - public final static int STATE_STOPPED = 1; - - /** - * State indicating this item is currently paused. - */ - public final static int STATE_PAUSED = 2; - - /** - * State indicating this item is currently playing. - */ - public final static int STATE_PLAYING = 3; - - /** - * State indicating this item is currently buffering and will begin playing - * when enough data has buffered. - */ - public final static int STATE_BUFFERING = 4; - - /** - * State indicating this item is currently in an error state. - */ - public final static int STATE_ERROR = 5; - - /** - * Use this value for the position to indicate the position is not known. - */ - public final static long PLAYBACK_POSITION_UNKNOWN = -1; - - private final PlaybackState2Provider mProvider; - - public PlaybackState2(@NonNull Context context, int state, long position, long updateTime, - float speed, long bufferedPosition, long activeItemId) { - mProvider = ApiLoader.getProvider(context).createPlaybackState2(context, this, state, - position, updateTime, speed, bufferedPosition, activeItemId); - } - - @Override - public String toString() { - return mProvider.toString_impl(); - } - - /** - * Get the current state of playback. One of the following: - * <ul> - * <li> {@link PlaybackState2#STATE_NONE}</li> - * <li> {@link PlaybackState2#STATE_STOPPED}</li> - * <li> {@link PlaybackState2#STATE_PAUSED}</li> - * <li> {@link PlaybackState2#STATE_PLAYING}</li> - * <li> {@link PlaybackState2#STATE_BUFFERING}</li> - * <li> {@link PlaybackState2#STATE_ERROR}</li> - * </ul> - */ - @State - public int getState() { - return mProvider.getState_impl(); - } - - /** - * Get the current playback position in ms. - */ - public long getPosition() { - return mProvider.getPosition_impl(); - } - - /** - * Get the current buffered position in ms. This is the farthest playback - * point that can be reached from the current position using only buffered - * content. - */ - public long getBufferedPosition() { - return mProvider.getBufferedPosition_impl(); - } - - /** - * Get the current playback speed as a multiple of normal playback. This - * should be negative when rewinding. A value of 1 means normal playback and - * 0 means paused. - * - * @return The current speed of playback. - */ - public float getPlaybackSpeed() { - return mProvider.getPlaybackSpeed_impl(); - } - - /** - * Get the elapsed real time at which position was last updated. If the - * position has never been set this will return 0; - * - * @return The last time the position was updated. - */ - public long getLastPositionUpdateTime() { - return mProvider.getLastPositionUpdateTime_impl(); - } - - /** - * Get the id of the currently active item in the playlist. - * - * @return The id of the currently active item in the queue - */ - public long getCurrentPlaylistItemIndex() { - return mProvider.getCurrentPlaylistItemIndex_impl(); - } - - /** - * Returns this object as a bundle to share between processes. - */ - public @NonNull Bundle toBundle() { - return mProvider.toBundle_impl(); - } - - /** - * Creates an instance from a bundle which is previously created by {@link #toBundle()}. - * - * @param context context - * @param bundle A bundle created by {@link #toBundle()}. - * @return A new {@link PlaybackState2} instance. Returns {@code null} if the given - * {@param bundle} is null, or if the {@param bundle} has no playback state parameters. - */ - public @Nullable static PlaybackState2 fromBundle(@NonNull Context context, - @Nullable Bundle bundle) { - return ApiLoader.getProvider(context).fromBundle_PlaybackState2(context, bundle); - } -} diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index 6b130cc1c402..051321c84d9a 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -775,8 +775,7 @@ public final class MediaSessionManager { public void run() { final Context context = mContext; if (context != null) { - ArrayList<MediaController> controllers - = new ArrayList<MediaController>(); + ArrayList<MediaController> controllers = new ArrayList<>(); int size = tokens.size(); for (int i = 0; i < size; i++) { controllers.add(new MediaController(context, tokens.get(i))); @@ -814,10 +813,16 @@ public final class MediaSessionManager { private final ISessionTokensListener.Stub mStub = new ISessionTokensListener.Stub() { @Override public void onSessionTokensChanged(final List<Bundle> bundles) { - mExecutor.execute(() -> { - List<SessionToken2> tokens = toTokenList(mContext, bundles); - mListener.onSessionTokensChanged(tokens); - }); + final Executor executor = mExecutor; + if (executor != null) { + executor.execute(() -> { + final Context context = mContext; + final OnSessionTokensChangedListener listener = mListener; + if (context != null && listener != null) { + listener.onSessionTokensChanged(toTokenList(context, bundles)); + } + }); + } } }; diff --git a/media/java/android/media/update/MediaController2Provider.java b/media/java/android/media/update/MediaController2Provider.java index 06e9544ef453..55672b68de7a 100644 --- a/media/java/android/media/update/MediaController2Provider.java +++ b/media/java/android/media/update/MediaController2Provider.java @@ -16,14 +16,13 @@ package android.media.update; -import android.annotation.NonNull; import android.app.PendingIntent; import android.media.AudioAttributes; import android.media.MediaController2.PlaybackInfo; import android.media.MediaItem2; +import android.media.MediaMetadata2; import android.media.MediaSession2.Command; import android.media.MediaSession2.PlaylistParams; -import android.media.PlaybackState2; import android.media.Rating2; import android.media.SessionToken2; import android.net.Uri; @@ -58,6 +57,9 @@ public interface MediaController2Provider extends TransportControlProvider { void setRating_impl(String mediaId, Rating2 rating); void sendCustomCommand_impl(Command command, Bundle args, ResultReceiver cb); List<MediaItem2> getPlaylist_impl(); + void setPlaylist_impl(List<MediaItem2> list, MediaMetadata2 metadata); + MediaMetadata2 getPlaylistMetadata_impl(); + void updatePlaylistMetadata_impl(MediaMetadata2 metadata); void addPlaylistItem_impl(int index, MediaItem2 item); void replacePlaylistItem_impl(int index, MediaItem2 item); @@ -65,7 +67,6 @@ public interface MediaController2Provider extends TransportControlProvider { PlaylistParams getPlaylistParams_impl(); void setPlaylistParams_impl(PlaylistParams params); - PlaybackState2 getPlaybackState_impl(); int getPlayerState_impl(); long getPosition_impl(); float getPlaybackSpeed_impl(); diff --git a/media/java/android/media/update/MediaItem2Provider.java b/media/java/android/media/update/MediaItem2Provider.java index b494f9e2af7f..47db22f22d98 100644 --- a/media/java/android/media/update/MediaItem2Provider.java +++ b/media/java/android/media/update/MediaItem2Provider.java @@ -35,6 +35,7 @@ public interface MediaItem2Provider { MediaMetadata2 getMetadata_impl(); String getMediaId_impl(); DataSourceDesc getDataSourceDesc_impl(); + boolean equals_impl(Object obj); interface BuilderProvider { Builder setMediaId_impl(String mediaId); diff --git a/media/java/android/media/update/MediaSession2Provider.java b/media/java/android/media/update/MediaSession2Provider.java index 142650a70d4f..84ea369336d7 100644 --- a/media/java/android/media/update/MediaSession2Provider.java +++ b/media/java/android/media/update/MediaSession2Provider.java @@ -47,6 +47,8 @@ public interface MediaSession2Provider extends TransportControlProvider { void updatePlayer_impl(MediaPlayerBase player, MediaPlaylistAgent playlistAgent, VolumeProvider2 volumeProvider); MediaPlayerBase getPlayer_impl(); + MediaMetadata2 getPlaylistMetadata_impl(); + void updatePlaylistMetadata_impl(MediaMetadata2 metadata); MediaPlaylistAgent getPlaylistAgent_impl(); VolumeProvider2 getVolumeProvider_impl(); SessionToken2 getToken_impl(); @@ -57,12 +59,11 @@ public interface MediaSession2Provider extends TransportControlProvider { void sendCustomCommand_impl(ControllerInfo controller, Command command, Bundle args, ResultReceiver receiver); void sendCustomCommand_impl(Command command, Bundle args); - void setPlaylist_impl(List<MediaItem2> playlist); void addPlaylistItem_impl(int index, MediaItem2 item); void removePlaylistItem_impl(MediaItem2 item); - void editPlaylistItem_impl(MediaItem2 item); void replacePlaylistItem_impl(int index, MediaItem2 item); List<MediaItem2> getPlaylist_impl(); + void setPlaylist_impl(List<MediaItem2> list, MediaMetadata2 metadata); MediaItem2 getCurrentPlaylistItem_impl(); void setPlaylistParams_impl(PlaylistParams params); PlaylistParams getPlaylistParams_impl(); diff --git a/media/java/android/media/update/MediaSessionService2Provider.java b/media/java/android/media/update/MediaSessionService2Provider.java index 8697e70634ca..5eb625464276 100644 --- a/media/java/android/media/update/MediaSessionService2Provider.java +++ b/media/java/android/media/update/MediaSessionService2Provider.java @@ -20,7 +20,6 @@ import android.app.Notification; import android.content.Intent; import android.media.MediaSession2; import android.media.MediaSessionService2.MediaNotification; -import android.media.PlaybackState2; import android.os.IBinder; /** diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java index 53ced9cd3246..f78d4a48f1ac 100644 --- a/media/java/android/media/update/StaticProvider.java +++ b/media/java/android/media/update/StaticProvider.java @@ -19,7 +19,6 @@ package android.media.update; import android.annotation.Nullable; import android.app.Notification; import android.content.Context; -import android.media.DataSourceDesc; import android.media.MediaBrowser2; import android.media.MediaBrowser2.BrowserCallback; import android.media.MediaController2; @@ -32,12 +31,10 @@ import android.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessio import android.media.MediaMetadata2; import android.media.MediaPlaylistAgent; import android.media.MediaSession2; -import android.media.MediaSession2.CommandButton.Builder; import android.media.MediaSession2.PlaylistParams; import android.media.MediaSession2.SessionCallback; import android.media.MediaSessionService2; import android.media.MediaSessionService2.MediaNotification; -import android.media.PlaybackState2; import android.media.Rating2; import android.media.SessionToken2; import android.media.VolumeProvider2; @@ -132,10 +129,6 @@ public interface StaticProvider { Rating2 newStarRating_Rating2(Context context, int starRatingStyle, float starRating); Rating2 newPercentageRating_Rating2(Context context, float percent); - PlaybackState2Provider createPlaybackState2(Context context, PlaybackState2 instance, int state, - long position, long updateTime, float speed, long bufferedPosition, long activeItemId); - PlaybackState2 fromBundle_PlaybackState2(Context context, Bundle bundle); - MediaPlaylistAgentProvider createMediaPlaylistAgent(Context context, MediaPlaylistAgent instance); } diff --git a/media/java/android/media/update/TransportControlProvider.java b/media/java/android/media/update/TransportControlProvider.java index a3fb0716624c..eb03ca7fc0e5 100644 --- a/media/java/android/media/update/TransportControlProvider.java +++ b/media/java/android/media/update/TransportControlProvider.java @@ -17,7 +17,6 @@ package android.media.update; import android.media.MediaItem2; -import android.media.PlaybackState2; /** * @hide @@ -34,6 +33,4 @@ public interface TransportControlProvider { void rewind_impl(); void seekTo_impl(long pos); void skipToPlaylistItem_impl(MediaItem2 item); - - PlaybackState2 getPlaybackState_impl(); } diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp index ff854c51c437..3a6714279bb7 100644 --- a/media/jni/android_media_MediaMetadataRetriever.cpp +++ b/media/jni/android_media_MediaMetadataRetriever.cpp @@ -25,6 +25,7 @@ #include <media/IMediaHTTPService.h> #include <media/mediametadataretriever.h> #include <media/mediascanner.h> +#include <nativehelper/ScopedLocalRef.h> #include <private/media/VideoFrame.h> #include "jni.h" @@ -45,6 +46,12 @@ struct fields_t { jmethodID createScaledBitmapMethod; jclass configClazz; // Must be a global ref jmethodID createConfigMethod; + jclass bitmapParamsClazz; // Must be a global ref + jfieldID inPreferredConfig; + jfieldID outActualConfig; + jclass arrayListClazz; // Must be a global ref + jmethodID arrayListInit; + jmethodID arrayListAdd; }; static fields_t fields; @@ -254,16 +261,18 @@ static void rotate(T *dst, const T *src, size_t width, size_t height, int angle) } static jobject getBitmapFromVideoFrame( - JNIEnv *env, VideoFrame *videoFrame, jint dst_width, jint dst_height) { + JNIEnv *env, VideoFrame *videoFrame, jint dst_width, jint dst_height, + SkColorType outColorType) { ALOGV("getBitmapFromVideoFrame: dimension = %dx%d and bytes = %d", videoFrame->mDisplayWidth, videoFrame->mDisplayHeight, videoFrame->mSize); - jobject config = env->CallStaticObjectMethod( - fields.configClazz, - fields.createConfigMethod, - GraphicsJNI::colorTypeToLegacyBitmapConfig(kRGB_565_SkColorType)); + ScopedLocalRef<jobject> config(env, + env->CallStaticObjectMethod( + fields.configClazz, + fields.createConfigMethod, + GraphicsJNI::colorTypeToLegacyBitmapConfig(outColorType))); uint32_t width, height, displayWidth, displayHeight; bool swapWidthAndHeight = false; @@ -285,7 +294,7 @@ static jobject getBitmapFromVideoFrame( fields.createBitmapMethod, width, height, - config); + config.get()); if (jBitmap == NULL) { if (env->ExceptionCheck()) { env->ExceptionClear(); @@ -297,11 +306,19 @@ static jobject getBitmapFromVideoFrame( SkBitmap bitmap; GraphicsJNI::getSkBitmap(env, jBitmap, &bitmap); - rotate((uint16_t*)bitmap.getPixels(), - (uint16_t*)((char*)videoFrame + sizeof(VideoFrame)), - videoFrame->mWidth, - videoFrame->mHeight, - videoFrame->mRotationAngle); + if (outColorType == kRGB_565_SkColorType) { + rotate((uint16_t*)bitmap.getPixels(), + (uint16_t*)((char*)videoFrame + sizeof(VideoFrame)), + videoFrame->mWidth, + videoFrame->mHeight, + videoFrame->mRotationAngle); + } else { + rotate((uint32_t*)bitmap.getPixels(), + (uint32_t*)((char*)videoFrame + sizeof(VideoFrame)), + videoFrame->mWidth, + videoFrame->mHeight, + videoFrame->mRotationAngle); + } if (dst_width <= 0 || dst_height <= 0) { dst_width = displayWidth; @@ -323,12 +340,46 @@ static jobject getBitmapFromVideoFrame( dst_width, dst_height, true); + + env->DeleteLocalRef(jBitmap); return scaledBitmap; } return jBitmap; } +static int getColorFormat(JNIEnv *env, jobject options) { + if (options == NULL) { + return HAL_PIXEL_FORMAT_RGBA_8888; + } + + ScopedLocalRef<jobject> inConfig(env, env->GetObjectField(options, fields.inPreferredConfig)); + SkColorType prefColorType = GraphicsJNI::getNativeBitmapColorType(env, inConfig.get()); + + if (prefColorType == kRGB_565_SkColorType) { + return HAL_PIXEL_FORMAT_RGB_565; + } + return HAL_PIXEL_FORMAT_RGBA_8888; +} + +static SkColorType setOutColorType(JNIEnv *env, int colorFormat, jobject options) { + SkColorType outColorType = kN32_SkColorType; + if (colorFormat == HAL_PIXEL_FORMAT_RGB_565) { + outColorType = kRGB_565_SkColorType; + } + + if (options != NULL) { + ScopedLocalRef<jobject> config(env, + env->CallStaticObjectMethod( + fields.configClazz, + fields.createConfigMethod, + GraphicsJNI::colorTypeToLegacyBitmapConfig(outColorType))); + + env->SetObjectField(options, fields.outActualConfig, config.get()); + } + return outColorType; +} + static jobject android_media_MediaMetadataRetriever_getFrameAtTime( JNIEnv *env, jobject thiz, jlong timeUs, jint option, jint dst_width, jint dst_height) { @@ -351,11 +402,11 @@ static jobject android_media_MediaMetadataRetriever_getFrameAtTime( return NULL; } - return getBitmapFromVideoFrame(env, videoFrame, dst_width, dst_height); + return getBitmapFromVideoFrame(env, videoFrame, dst_width, dst_height, kRGB_565_SkColorType); } static jobject android_media_MediaMetadataRetriever_getImageAtIndex( - JNIEnv *env, jobject thiz, jint index) + JNIEnv *env, jobject thiz, jint index, jobject params) { ALOGV("getImageAtIndex: index %d", index); sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz); @@ -364,9 +415,11 @@ static jobject android_media_MediaMetadataRetriever_getImageAtIndex( return NULL; } + int colorFormat = getColorFormat(env, params); + // Call native method to retrieve an image VideoFrame *videoFrame = NULL; - sp<IMemory> frameMemory = retriever->getImageAtIndex(index); + sp<IMemory> frameMemory = retriever->getImageAtIndex(index, colorFormat); if (frameMemory != 0) { // cast the shared structure to a VideoFrame object videoFrame = static_cast<VideoFrame *>(frameMemory->pointer()); } @@ -375,11 +428,13 @@ static jobject android_media_MediaMetadataRetriever_getImageAtIndex( return NULL; } - return getBitmapFromVideoFrame(env, videoFrame, -1, -1); + SkColorType outColorType = setOutColorType(env, colorFormat, params); + + return getBitmapFromVideoFrame(env, videoFrame, -1, -1, outColorType); } -static jobjectArray android_media_MediaMetadataRetriever_getFrameAtIndex( - JNIEnv *env, jobject thiz, jint frameIndex, jint numFrames) +static jobject android_media_MediaMetadataRetriever_getFrameAtIndex( + JNIEnv *env, jobject thiz, jint frameIndex, jint numFrames, jobject params) { ALOGV("getFrameAtIndex: frameIndex %d, numFrames %d", frameIndex, numFrames); sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz); @@ -389,31 +444,34 @@ static jobjectArray android_media_MediaMetadataRetriever_getFrameAtIndex( return NULL; } + int colorFormat = getColorFormat(env, params); + std::vector<sp<IMemory> > frames; - status_t err = retriever->getFrameAtIndex(&frames, frameIndex, numFrames); + status_t err = retriever->getFrameAtIndex(&frames, frameIndex, numFrames, colorFormat); if (err != OK || frames.size() == 0) { ALOGE("failed to get frames from retriever, err=%d, size=%zu", err, frames.size()); return NULL; } - - jobjectArray bitmapArrayObj = env->NewObjectArray( - frames.size(), fields.bitmapClazz, NULL); - if (bitmapArrayObj == NULL) { - ALOGE("can't create bitmap array object"); + jobject arrayList = env->NewObject(fields.arrayListClazz, fields.arrayListInit); + if (arrayList == NULL) { + ALOGE("can't create bitmap array list object"); return NULL; } + SkColorType outColorType = setOutColorType(env, colorFormat, params); + for (size_t i = 0; i < frames.size(); i++) { if (frames[i] == NULL || frames[i]->pointer() == NULL) { ALOGE("video frame at index %zu is a NULL pointer", frameIndex + i); continue; } VideoFrame *videoFrame = static_cast<VideoFrame *>(frames[i]->pointer()); - jobject bitmapObj = getBitmapFromVideoFrame(env, videoFrame, -1, -1); - env->SetObjectArrayElement(bitmapArrayObj, i, bitmapObj); + jobject bitmapObj = getBitmapFromVideoFrame(env, videoFrame, -1, -1, outColorType); + env->CallBooleanMethod(arrayList, fields.arrayListAdd, bitmapObj); + env->DeleteLocalRef(bitmapObj); } - return bitmapArrayObj; + return arrayList; } static jbyteArray android_media_MediaMetadataRetriever_getEmbeddedPicture( @@ -488,21 +546,21 @@ static void android_media_MediaMetadataRetriever_native_finalize(JNIEnv *env, jo // first time an instance of this class is used. static void android_media_MediaMetadataRetriever_native_init(JNIEnv *env) { - jclass clazz = env->FindClass(kClassPathName); - if (clazz == NULL) { + ScopedLocalRef<jclass> clazz(env, env->FindClass(kClassPathName)); + if (clazz.get() == NULL) { return; } - fields.context = env->GetFieldID(clazz, "mNativeContext", "J"); + fields.context = env->GetFieldID(clazz.get(), "mNativeContext", "J"); if (fields.context == NULL) { return; } - jclass bitmapClazz = env->FindClass("android/graphics/Bitmap"); - if (bitmapClazz == NULL) { + clazz.reset(env->FindClass("android/graphics/Bitmap")); + if (clazz.get() == NULL) { return; } - fields.bitmapClazz = (jclass) env->NewGlobalRef(bitmapClazz); + fields.bitmapClazz = (jclass) env->NewGlobalRef(clazz.get()); if (fields.bitmapClazz == NULL) { return; } @@ -521,11 +579,11 @@ static void android_media_MediaMetadataRetriever_native_init(JNIEnv *env) return; } - jclass configClazz = env->FindClass("android/graphics/Bitmap$Config"); - if (configClazz == NULL) { + clazz.reset(env->FindClass("android/graphics/Bitmap$Config")); + if (clazz.get() == NULL) { return; } - fields.configClazz = (jclass) env->NewGlobalRef(configClazz); + fields.configClazz = (jclass) env->NewGlobalRef(clazz.get()); if (fields.configClazz == NULL) { return; } @@ -535,6 +593,42 @@ static void android_media_MediaMetadataRetriever_native_init(JNIEnv *env) if (fields.createConfigMethod == NULL) { return; } + + clazz.reset(env->FindClass("android/media/MediaMetadataRetriever$BitmapParams")); + if (clazz.get() == NULL) { + return; + } + fields.bitmapParamsClazz = (jclass) env->NewGlobalRef(clazz.get()); + if (fields.bitmapParamsClazz == NULL) { + return; + } + fields.inPreferredConfig = env->GetFieldID(fields.bitmapParamsClazz, + "inPreferredConfig", "Landroid/graphics/Bitmap$Config;"); + if (fields.inPreferredConfig == NULL) { + return; + } + fields.outActualConfig = env->GetFieldID(fields.bitmapParamsClazz, + "outActualConfig", "Landroid/graphics/Bitmap$Config;"); + if (fields.outActualConfig == NULL) { + return; + } + + clazz.reset(env->FindClass("java/util/ArrayList")); + if (clazz.get() == NULL) { + return; + } + fields.arrayListClazz = (jclass) env->NewGlobalRef(clazz.get()); + if (fields.arrayListClazz == NULL) { + return; + } + fields.arrayListInit = env->GetMethodID(clazz.get(), "<init>", "()V"); + if (fields.arrayListInit == NULL) { + return; + } + fields.arrayListAdd = env->GetMethodID(clazz.get(), "add", "(Ljava/lang/Object;)Z"); + if (fields.arrayListAdd == NULL) { + return; + } } static void android_media_MediaMetadataRetriever_native_setup(JNIEnv *env, jobject thiz) @@ -556,17 +650,36 @@ static const JNINativeMethod nativeMethods[] = { (void *)android_media_MediaMetadataRetriever_setDataSourceAndHeaders }, - {"setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD}, - {"_setDataSource", "(Landroid/media/MediaDataSource;)V", (void *)android_media_MediaMetadataRetriever_setDataSourceCallback}, - {"_getFrameAtTime", "(JIII)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime}, - {"_getImageAtIndex", "(I)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getImageAtIndex}, - {"_getFrameAtIndex", "(II)[Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtIndex}, - {"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata}, - {"getEmbeddedPicture", "(I)[B", (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture}, - {"release", "()V", (void *)android_media_MediaMetadataRetriever_release}, - {"native_finalize", "()V", (void *)android_media_MediaMetadataRetriever_native_finalize}, - {"native_setup", "()V", (void *)android_media_MediaMetadataRetriever_native_setup}, - {"native_init", "()V", (void *)android_media_MediaMetadataRetriever_native_init}, + {"setDataSource", "(Ljava/io/FileDescriptor;JJ)V", + (void *)android_media_MediaMetadataRetriever_setDataSourceFD}, + {"_setDataSource", "(Landroid/media/MediaDataSource;)V", + (void *)android_media_MediaMetadataRetriever_setDataSourceCallback}, + {"_getFrameAtTime", "(JIII)Landroid/graphics/Bitmap;", + (void *)android_media_MediaMetadataRetriever_getFrameAtTime}, + { + "_getImageAtIndex", + "(ILandroid/media/MediaMetadataRetriever$BitmapParams;)Landroid/graphics/Bitmap;", + (void *)android_media_MediaMetadataRetriever_getImageAtIndex + }, + + { + "_getFrameAtIndex", + "(IILandroid/media/MediaMetadataRetriever$BitmapParams;)Ljava/util/List;", + (void *)android_media_MediaMetadataRetriever_getFrameAtIndex + }, + + {"extractMetadata", "(I)Ljava/lang/String;", + (void *)android_media_MediaMetadataRetriever_extractMetadata}, + {"getEmbeddedPicture", "(I)[B", + (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture}, + {"release", "()V", + (void *)android_media_MediaMetadataRetriever_release}, + {"native_finalize", "()V", + (void *)android_media_MediaMetadataRetriever_native_finalize}, + {"native_setup", "()V", + (void *)android_media_MediaMetadataRetriever_native_setup}, + {"native_init", "()V", + (void *)android_media_MediaMetadataRetriever_native_init}, }; // This function only registers the native methods, and is called from diff --git a/media/jni/android_media_MediaPlayer2.cpp b/media/jni/android_media_MediaPlayer2.cpp index 918b82b06570..918a375a6e83 100644 --- a/media/jni/android_media_MediaPlayer2.cpp +++ b/media/jni/android_media_MediaPlayer2.cpp @@ -1489,7 +1489,7 @@ static const JNINativeMethod gMethods[] = { {"nativePlayNextDataSource", "(J)V", (void *)android_media_MediaPlayer2_playNextDataSource}, {"_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}, + {"_setBufferingParams", "(Landroid/media/BufferingParams;)V", (void *)android_media_MediaPlayer2_setBufferingParams}, {"_prepare", "()V", (void *)android_media_MediaPlayer2_prepare}, {"_start", "()V", (void *)android_media_MediaPlayer2_start}, {"_stop", "()V", (void *)android_media_MediaPlayer2_stop}, @@ -1497,9 +1497,9 @@ static const JNINativeMethod gMethods[] = { {"getVideoWidth", "()I", (void *)android_media_MediaPlayer2_getVideoWidth}, {"getVideoHeight", "()I", (void *)android_media_MediaPlayer2_getVideoHeight}, {"native_getMetrics", "()Landroid/os/PersistableBundle;", (void *)android_media_MediaPlayer2_native_getMetrics}, - {"setPlaybackParams", "(Landroid/media/PlaybackParams;)V", (void *)android_media_MediaPlayer2_setPlaybackParams}, + {"_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}, + {"_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}, {"_notifyAt", "(J)V", (void *)android_media_MediaPlayer2_notifyAt}, @@ -1522,9 +1522,9 @@ static const JNINativeMethod gMethods[] = { {"native_setup", "(Ljava/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}, + {"_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}, + {"_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 }, diff --git a/packages/SettingsLib/common.mk b/packages/SettingsLib/common.mk index 3f9a0fd41c04..15b50d719291 100644 --- a/packages/SettingsLib/common.mk +++ b/packages/SettingsLib/common.mk @@ -34,21 +34,21 @@ LOCAL_RESOURCE_DIR += $(call my-dir)/res # Include support-v7-appcompat, if not already included ifeq (,$(findstring android-support-v7-appcompat,$(LOCAL_STATIC_JAVA_LIBRARIES))) -LOCAL_RESOURCE_DIR += frameworks/support/v7/appcompat/res +LOCAL_RESOURCE_DIR += prebuilts/sdk/current/support/v7/appcompat/res LOCAL_AAPT_FLAGS += --extra-packages android.support.v7.appcompat LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-appcompat endif # Include support-v7-recyclerview, if not already included ifeq (,$(findstring android-support-v7-recyclerview,$(LOCAL_STATIC_JAVA_LIBRARIES))) -LOCAL_RESOURCE_DIR += frameworks/support/v7/recyclerview/res +LOCAL_RESOURCE_DIR += prebuilts/sdk/current/support/v7/recyclerview/res LOCAL_AAPT_FLAGS += --extra-packages android.support.v7.recyclerview LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-recyclerview endif # Include android-support-v7-preference, if not already included ifeq (,$(findstring android-support-v7-preference,$(LOCAL_STATIC_JAVA_LIBRARIES))) -LOCAL_RESOURCE_DIR += frameworks/support/preference/res +LOCAL_RESOURCE_DIR += prebuilts/sdk/current/support/v7/preference/res LOCAL_AAPT_FLAGS += --extra-packages android.support.v7.preference LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-preference endif diff --git a/packages/SettingsLib/res/layout/zen_mode_duration_dialog.xml b/packages/SettingsLib/res/layout/zen_mode_duration_dialog.xml new file mode 100644 index 000000000000..6552296bc4e4 --- /dev/null +++ b/packages/SettingsLib/res/layout/zen_mode_duration_dialog.xml @@ -0,0 +1,52 @@ +<?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. +--> + +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/zen_duration_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport ="true" + android:orientation="vertical"> + + <LinearLayout + android:id="@+id/zen_duration_dialog_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <com.android.settingslib.notification.ZenRadioLayout + android:id="@+id/zen_duration_conditions" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:layout_marginEnd="4dp" + android:layout_marginStart="4dp" + android:paddingBottom="4dp" + android:orientation="horizontal"> + <RadioGroup + android:id="@+id/zen_radio_buttons" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + <LinearLayout + android:id="@+id/zen_radio_buttons_content" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical"/> + </com.android.settingslib.notification.ZenRadioLayout> + </LinearLayout> + +</ScrollView>
\ No newline at end of file diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 72d7bfad8628..e77db82a520c 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1062,10 +1062,13 @@ <!-- Content description of zen mode time condition minus button (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_manual_zen_less_time">Less time.</string> - <!-- Do not disturb: Label for button in enable zen dialog that will turn on zen mode. [CHAR LIMIT=30] --> - <string name="zen_mode_enable_dialog_turn_on">Turn on</string> <!-- Button label for generic cancel action [CHAR LIMIT=20] --> <string name="cancel">Cancel</string> + <!-- Button label for generic OK action [CHAR LIMIT=20] --> + <string name="okay">OK</string> + + <!-- Do not disturb: Label for button in enable zen dialog that will turn on zen mode. [CHAR LIMIT=30] --> + <string name="zen_mode_enable_dialog_turn_on">Turn on</string> <!-- Do not disturb: Title for the Do not Disturb dialog to turn on Do not disturb. [CHAR LIMIT=50]--> <string name="zen_mode_settings_turn_on_dialog_title">Turn on Do Not Disturb</string> <!-- Sound: Summary for the Do not Disturb option when there is no automatic rules turned on. [CHAR LIMIT=NONE]--> @@ -1083,4 +1086,8 @@ <!-- Alarm template for far in the future alarms [CHAR LIMIT=25] --> <string name="alarm_template_far">on <xliff:g id="when" example="Fri 7:00 AM">%1$s</xliff:g></string> + <!-- Do not disturb: Title for the dnd duration setting (user can specify how long dnd will last when toggling dnd on from qs or settings) [CHAR LIMIT=30] --> + <string name="zen_mode_duration_settings_title">Duration</string> + <!-- Do not disturb: Duration option to always prompt for the duration of dnd --> + <string name="zen_mode_duration_always_prompt_title">Ask every time</string> </resources> diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index 61e113b3e399..56a242aea6a1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -1,7 +1,6 @@ package com.android.settingslib; import android.annotation.ColorInt; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; @@ -142,7 +141,7 @@ public class Utils { public static Drawable getUserIcon(Context context, UserManager um, UserInfo user) { final int iconSize = UserIconDrawable.getSizeForList(context); if (user.isManagedProfile()) { - Drawable drawable = UserIconDrawable.getManagedUserBadgeDrawable(context); + Drawable drawable = UserIconDrawable.getManagedUserDrawable(context); drawable.setBounds(0, 0, iconSize, iconSize); return drawable; } diff --git a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java index 7f469b5e7497..54d1aba09ae3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java @@ -16,6 +16,7 @@ package com.android.settingslib.drawable; +import android.annotation.DrawableRes; import android.annotation.NonNull; import android.app.admin.DevicePolicyManager; import android.content.Context; @@ -36,6 +37,7 @@ import android.graphics.RectF; import android.graphics.Shader; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.os.UserHandle; import com.android.settingslib.R; @@ -69,15 +71,23 @@ public class UserIconDrawable extends Drawable implements Drawable.Callback { private float mBadgeMargin; /** - * Gets the system default managed-user badge as a drawable + * Gets the system default managed-user badge as a drawable. This drawable is tint-able. + * For badging purpose, consider + * {@link android.content.pm.PackageManager#getUserBadgedDrawableForDensity(Drawable, UserHandle, Rect, int)}. + * * @param context * @return drawable containing just the badge */ - public static Drawable getManagedUserBadgeDrawable(Context context) { - int displayDensity = context.getResources().getDisplayMetrics().densityDpi; + public static Drawable getManagedUserDrawable(Context context) { + return getDrawableForDisplayDensity + (context, com.android.internal.R.drawable.ic_corp_user_badge); + } + + private static Drawable getDrawableForDisplayDensity( + Context context, @DrawableRes int drawable) { + int density = context.getResources().getDisplayMetrics().densityDpi; return context.getResources().getDrawableForDensity( - com.android.internal.R.drawable.ic_corp_user_badge, - displayDensity, context.getTheme()); + drawable, density, context.getTheme()); } /** @@ -164,7 +174,8 @@ public class UserIconDrawable extends Drawable implements Drawable.Callback { boolean isManaged = context.getSystemService(DevicePolicyManager.class) .getProfileOwnerAsUser(userId) != null; if (isManaged) { - badge = getManagedUserBadgeDrawable(context); + badge = getDrawableForDisplayDensity( + context, com.android.internal.R.drawable.ic_corp_badge_case); } return setBadge(badge); } @@ -322,7 +333,6 @@ public class UserIconDrawable extends Drawable implements Drawable.Callback { mIntrinsicRadius, mIconPaint); canvas.restoreToCount(saveId); } - if (mFrameColor != null) { mFramePaint.setColor(mFrameColor.getColorForState(getState(), Color.TRANSPARENT)); } @@ -343,7 +353,6 @@ public class UserIconDrawable extends Drawable implements Drawable.Callback { final float borderRadius = mBadge.getBounds().width() * 0.5f + mBadgeMargin; canvas.drawCircle(badgeLeft + mBadgeRadius, badgeTop + mBadgeRadius, borderRadius, mClearPaint); - mBadge.draw(canvas); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java new file mode 100644 index 000000000000..7369ba8c7f1c --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java @@ -0,0 +1,321 @@ +/* + * 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.settingslib.notification; + +import android.app.ActivityManager; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.provider.Settings; +import android.service.notification.Condition; +import android.service.notification.ZenModeConfig; +import android.support.annotation.VisibleForTesting; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CompoundButton; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import android.widget.ScrollView; +import android.widget.TextView; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto; +import com.android.internal.policy.PhoneWindow; +import com.android.settingslib.R; + +import java.util.Arrays; + +public class ZenDurationDialog { + private static final int[] MINUTE_BUCKETS = ZenModeConfig.MINUTE_BUCKETS; + @VisibleForTesting protected static final int MIN_BUCKET_MINUTES = MINUTE_BUCKETS[0]; + @VisibleForTesting protected static final int MAX_BUCKET_MINUTES = + MINUTE_BUCKETS[MINUTE_BUCKETS.length - 1]; + private static final int DEFAULT_BUCKET_INDEX = Arrays.binarySearch(MINUTE_BUCKETS, 60); + @VisibleForTesting protected int mBucketIndex = -1; + + @VisibleForTesting protected static final int FOREVER_CONDITION_INDEX = 0; + @VisibleForTesting protected static final int COUNTDOWN_CONDITION_INDEX = 1; + @VisibleForTesting protected static final int ALWAYS_ASK_CONDITION_INDEX = 2; + + @VisibleForTesting protected Context mContext; + @VisibleForTesting protected LinearLayout mZenRadioGroupContent; + private RadioGroup mZenRadioGroup; + private int MAX_MANUAL_DND_OPTIONS = 3; + + @VisibleForTesting protected LayoutInflater mLayoutInflater; + + public ZenDurationDialog(Context context) { + mContext = context; + } + + public Dialog createDialog() { + int zenDuration = Settings.Global.getInt( + mContext.getContentResolver(), Settings.Global.ZEN_DURATION, + Settings.Global.ZEN_DURATION_FOREVER); + + final AlertDialog.Builder builder = new AlertDialog.Builder(mContext) + .setTitle(R.string.zen_mode_duration_settings_title) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.okay, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + updateZenDuration(zenDuration); + } + }); + + View contentView = getContentView(); + setupRadioButtons(zenDuration); + builder.setView(contentView); + return builder.create(); + } + + @VisibleForTesting + protected void updateZenDuration(int currZenDuration) { + final int checkedRadioButtonId = mZenRadioGroup.getCheckedRadioButtonId(); + + int newZenDuration = Settings.Global.getInt( + mContext.getContentResolver(), Settings.Global.ZEN_DURATION, + Settings.Global.ZEN_DURATION_FOREVER); + switch (checkedRadioButtonId) { + case FOREVER_CONDITION_INDEX: + newZenDuration = Settings.Global.ZEN_DURATION_FOREVER; + MetricsLogger.action(mContext, + MetricsProto.MetricsEvent. + NOTIFICATION_ZEN_MODE_DURATION_FOREVER); + break; + case COUNTDOWN_CONDITION_INDEX: + ConditionTag tag = getConditionTagAt(checkedRadioButtonId); + newZenDuration = tag.countdownZenDuration; + MetricsLogger.action(mContext, + MetricsProto.MetricsEvent. + NOTIFICATION_ZEN_MODE_DURATION_TIME, + newZenDuration); + break; + case ALWAYS_ASK_CONDITION_INDEX: + newZenDuration = Settings.Global.ZEN_DURATION_PROMPT; + MetricsLogger.action(mContext, + MetricsProto.MetricsEvent. + NOTIFICATION_ZEN_MODE_DURATION_PROMPT); + break; + } + + if (currZenDuration != newZenDuration) { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.ZEN_DURATION, newZenDuration); + } + } + + @VisibleForTesting + protected View getContentView() { + if (mLayoutInflater == null) { + mLayoutInflater = new PhoneWindow(mContext).getLayoutInflater(); + } + View contentView = mLayoutInflater.inflate(R.layout.zen_mode_duration_dialog, + null); + ScrollView container = (ScrollView) contentView.findViewById(R.id.zen_duration_container); + + mZenRadioGroup = container.findViewById(R.id.zen_radio_buttons); + mZenRadioGroupContent = container.findViewById(R.id.zen_radio_buttons_content); + + for (int i = 0; i < MAX_MANUAL_DND_OPTIONS; i++) { + final View radioButton = mLayoutInflater.inflate(R.layout.zen_mode_radio_button, + mZenRadioGroup, false); + mZenRadioGroup.addView(radioButton); + radioButton.setId(i); + + final View radioButtonContent = mLayoutInflater.inflate(R.layout.zen_mode_condition, + mZenRadioGroupContent, false); + radioButtonContent.setId(i + MAX_MANUAL_DND_OPTIONS); + mZenRadioGroupContent.addView(radioButtonContent); + } + + return contentView; + } + + @VisibleForTesting + protected void setupRadioButtons(int zenDuration) { + int checkedIndex = ALWAYS_ASK_CONDITION_INDEX; + if (zenDuration == 0) { + checkedIndex = FOREVER_CONDITION_INDEX; + } else if (zenDuration > 0) { + checkedIndex = COUNTDOWN_CONDITION_INDEX; + } + + bindTag(zenDuration, mZenRadioGroupContent.getChildAt(FOREVER_CONDITION_INDEX), + FOREVER_CONDITION_INDEX); + bindTag(zenDuration, mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX), + COUNTDOWN_CONDITION_INDEX); + bindTag(zenDuration, mZenRadioGroupContent.getChildAt(ALWAYS_ASK_CONDITION_INDEX), + ALWAYS_ASK_CONDITION_INDEX); + getConditionTagAt(checkedIndex).rb.setChecked(true); + } + + private void bindTag(final int currZenDuration, final View row, final int rowIndex) { + final ConditionTag tag = row.getTag() != null ? (ConditionTag) row.getTag() : + new ConditionTag(); + row.setTag(tag); + + if (tag.rb == null) { + tag.rb = (RadioButton) mZenRadioGroup.getChildAt(rowIndex); + } + + // if duration is set to forever or always prompt, then countdown time defaults to 1 hour + if (currZenDuration <= 0) { + tag.countdownZenDuration = MINUTE_BUCKETS[DEFAULT_BUCKET_INDEX]; + } else { + tag.countdownZenDuration = currZenDuration; + } + + tag.rb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + tag.rb.setChecked(true); + } + } + }); + + updateUi(tag, row, rowIndex); + } + + @VisibleForTesting + protected ConditionTag getConditionTagAt(int index) { + return (ConditionTag) mZenRadioGroupContent.getChildAt(index).getTag(); + } + + + private void setupUi(ConditionTag tag, View row) { + tag.lines = row.findViewById(android.R.id.content); + tag.line1 = (TextView) row.findViewById(android.R.id.text1); + + // text2 is not used in zen duration dialog + row.findViewById(android.R.id.text2).setVisibility(View.GONE); + + tag.lines.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + tag.rb.setChecked(true); + } + }); + } + + private void updateButtons(ConditionTag tag, View row, int rowIndex) { + // minus button + final ImageView button1 = (ImageView) row.findViewById(android.R.id.button1); + button1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onClickTimeButton(row, tag, false /*down*/, rowIndex); + } + }); + + // plus button + final ImageView button2 = (ImageView) row.findViewById(android.R.id.button2); + button2.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onClickTimeButton(row, tag, true /*up*/, rowIndex); + } + }); + + final long time = tag.countdownZenDuration; + if (rowIndex == COUNTDOWN_CONDITION_INDEX) { + button1.setVisibility(View.VISIBLE); + button2.setVisibility(View.VISIBLE); + + button1.setEnabled(time > MIN_BUCKET_MINUTES); + button2.setEnabled(tag.countdownZenDuration != MAX_BUCKET_MINUTES); + + button1.setAlpha(button1.isEnabled() ? 1f : .5f); + button2.setAlpha(button2.isEnabled() ? 1f : .5f); + } else { + button1.setVisibility(View.GONE); + button2.setVisibility(View.GONE); + } + } + + @VisibleForTesting + protected void updateUi(ConditionTag tag, View row, int rowIndex) { + if (tag.lines == null) { + setupUi(tag, row); + } + + updateButtons(tag, row, rowIndex); + + String radioContentText = ""; + switch (rowIndex) { + case FOREVER_CONDITION_INDEX: + radioContentText = mContext.getString( + com.android.internal.R.string.zen_mode_forever); + break; + case COUNTDOWN_CONDITION_INDEX: + Condition condition = ZenModeConfig.toTimeCondition(mContext, + tag.countdownZenDuration, ActivityManager.getCurrentUser(), false); + radioContentText = condition.line1; + break; + case ALWAYS_ASK_CONDITION_INDEX: + radioContentText = mContext.getString( + R.string.zen_mode_duration_always_prompt_title); + break; + } + + tag.line1.setText(radioContentText); + } + + @VisibleForTesting + protected void onClickTimeButton(View row, ConditionTag tag, boolean up, int rowId) { + int newDndTimeDuration = -1; + final int N = MINUTE_BUCKETS.length; + if (mBucketIndex == -1) { + // not on a known index, search for the next or prev bucket by time + final long time = tag.countdownZenDuration; + for (int i = 0; i < N; i++) { + int j = up ? i : N - 1 - i; + final int bucketMinutes = MINUTE_BUCKETS[j]; + if (up && bucketMinutes > time || !up && bucketMinutes < time) { + mBucketIndex = j; + newDndTimeDuration = bucketMinutes; + break; + } + } + if (newDndTimeDuration == -1) { + mBucketIndex = DEFAULT_BUCKET_INDEX; + newDndTimeDuration = MINUTE_BUCKETS[mBucketIndex]; + } + } else { + // on a known index, simply increment or decrement + mBucketIndex = Math.max(0, Math.min(N - 1, mBucketIndex + (up ? 1 : -1))); + newDndTimeDuration = MINUTE_BUCKETS[mBucketIndex]; + } + tag.countdownZenDuration = newDndTimeDuration; + bindTag(newDndTimeDuration, row, rowId); + tag.rb.setChecked(true); + } + + // used as the view tag on condition rows + @VisibleForTesting + protected static class ConditionTag { + public RadioButton rb; + public View lines; + public TextView line1; + public int countdownZenDuration; // only important for countdown radio button + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/ZenDurationDialogTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/ZenDurationDialogTest.java new file mode 100644 index 000000000000..9b491c200cdc --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/ZenDurationDialogTest.java @@ -0,0 +1,222 @@ +/* + * 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.settingslib.notification; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyObject; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.app.AlertDialog; +import android.app.Fragment; +import android.app.NotificationManager; +import android.content.Context; +import android.content.ContentResolver; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.net.Uri; +import android.provider.Settings; +import android.service.notification.Condition; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; + +import com.android.settingslib.SettingsLibRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; + +@RunWith(SettingsLibRobolectricTestRunner.class) +public class ZenDurationDialogTest { + private ZenDurationDialog mController; + + private Context mContext; + private LayoutInflater mLayoutInflater; + private Condition mCountdownCondition; + private Condition mAlarmCondition; + private ContentResolver mContentResolver; + + @Before + public void setup() { + mContext = RuntimeEnvironment.application; + mContentResolver = RuntimeEnvironment.application.getContentResolver(); + mLayoutInflater = LayoutInflater.from(mContext); + + mController = spy(new ZenDurationDialog(mContext)); + mController.mLayoutInflater = mLayoutInflater; + mController.getContentView(); + } + + @Test + public void testAlwaysPrompt() { + Settings.Global.putInt(mContentResolver, Settings.Global.ZEN_DURATION, + Settings.Global.ZEN_DURATION_PROMPT); + mController.createDialog(); + + assertFalse(mController.getConditionTagAt(ZenDurationDialog.FOREVER_CONDITION_INDEX).rb + .isChecked()); + assertFalse(mController.getConditionTagAt(ZenDurationDialog.COUNTDOWN_CONDITION_INDEX).rb + .isChecked()); + assertTrue(mController.getConditionTagAt( + ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX).rb.isChecked()); + } + + @Test + public void testForever() { + Settings.Global.putInt(mContentResolver, Settings.Global.ZEN_DURATION, + Settings.Global.ZEN_DURATION_FOREVER); + mController.createDialog(); + + assertTrue(mController.getConditionTagAt(ZenDurationDialog.FOREVER_CONDITION_INDEX).rb + .isChecked()); + assertFalse(mController.getConditionTagAt(ZenDurationDialog.COUNTDOWN_CONDITION_INDEX).rb + .isChecked()); + assertFalse(mController.getConditionTagAt( + ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX).rb.isChecked()); + } + + @Test + public void testSpecificDuration() { + Settings.Global.putInt(mContentResolver, Settings.Global.ZEN_DURATION, 45); + mController.createDialog(); + + assertFalse(mController.getConditionTagAt(ZenDurationDialog.FOREVER_CONDITION_INDEX).rb + .isChecked()); + assertTrue(mController.getConditionTagAt(ZenDurationDialog.COUNTDOWN_CONDITION_INDEX).rb + .isChecked()); + assertFalse(mController.getConditionTagAt( + ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX).rb.isChecked()); + } + + + @Test + public void testChooseAlwaysPromptSetting() { + Settings.Global.putInt(mContentResolver, Settings.Global.ZEN_DURATION, + Settings.Global.ZEN_DURATION_FOREVER); + + AlertDialog dialog = (AlertDialog) mController.createDialog(); + mController.getConditionTagAt(ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX).rb.setChecked( + true); + mController.updateZenDuration(Settings.Global.ZEN_DURATION_FOREVER); + + assertEquals(Settings.Global.ZEN_DURATION_PROMPT, Settings.Global.getInt(mContentResolver, + Settings.Global.ZEN_DURATION, Settings.Global.ZEN_DURATION_FOREVER)); + } + + @Test + public void testChooseForeverSetting() { + Settings.Global.putInt(mContentResolver, Settings.Global.ZEN_DURATION, + Settings.Global.ZEN_DURATION_PROMPT); + + AlertDialog dialog = (AlertDialog) mController.createDialog(); + mController.getConditionTagAt(ZenDurationDialog.FOREVER_CONDITION_INDEX).rb.setChecked( + true); + mController.updateZenDuration(Settings.Global.ZEN_DURATION_PROMPT); + + assertEquals(Settings.Global.ZEN_DURATION_FOREVER, Settings.Global.getInt(mContentResolver, + Settings.Global.ZEN_DURATION, Settings.Global.ZEN_DURATION_PROMPT)); + } + + @Test + public void testChooseTimeSetting() { + Settings.Global.putInt(mContentResolver, Settings.Global.ZEN_DURATION, + Settings.Global.ZEN_DURATION_PROMPT); + + AlertDialog dialog = (AlertDialog) mController.createDialog(); + mController.getConditionTagAt(ZenDurationDialog.COUNTDOWN_CONDITION_INDEX).rb.setChecked( + true); + mController.updateZenDuration(Settings.Global.ZEN_DURATION_PROMPT); + + // countdown defaults to 60 minutes: + assertEquals(60, Settings.Global.getInt(mContentResolver, + Settings.Global.ZEN_DURATION, Settings.Global.ZEN_DURATION_PROMPT)); + } + + @Test + public void testGetTimeFromBucket() { + Settings.Global.putInt(mContentResolver, Settings.Global.ZEN_DURATION, + Settings.Global.ZEN_DURATION_PROMPT); + + AlertDialog dialog = (AlertDialog) mController.createDialog(); + // click time button starts at 60 minutes + // - 1 hour to MAX_BUCKET_MINUTES (12 hours), increments by 1 hour + // - 0-60 minutes increments by 15 minutes + View view = mController.mZenRadioGroupContent.getChildAt( + ZenDurationDialog.COUNTDOWN_CONDITION_INDEX); + ZenDurationDialog.ConditionTag tag = mController.getConditionTagAt( + ZenDurationDialog.COUNTDOWN_CONDITION_INDEX); + + // test incrementing up: + mController.onClickTimeButton(view, tag, true, ZenDurationDialog.COUNTDOWN_CONDITION_INDEX); + assertEquals(120, tag.countdownZenDuration); // goes from 1 hour to 2 hours + + // try clicking up 50 times - should max out at ZenDurationDialog.MAX_BUCKET_MINUTES + for (int i = 0; i < 50; i++) { + mController.onClickTimeButton(view, tag, true, + ZenDurationDialog.COUNTDOWN_CONDITION_INDEX); + } + assertEquals(ZenDurationDialog.MAX_BUCKET_MINUTES, tag.countdownZenDuration); + + // reset, test incrementing down: + mController.mBucketIndex = -1; // reset current bucket index to reset countdownZenDuration + tag.countdownZenDuration = 60; // back to default + mController.onClickTimeButton(view, tag, false, + ZenDurationDialog.COUNTDOWN_CONDITION_INDEX); + assertEquals(45, tag.countdownZenDuration); // goes from 60 minutes to 45 minutes + + // try clicking down 50 times - should stop at MIN_BUCKET_MINUTES + for (int i = 0; i < 50; i++) { + mController.onClickTimeButton(view, tag, false, + ZenDurationDialog.COUNTDOWN_CONDITION_INDEX); + } + assertEquals(ZenDurationDialog.MIN_BUCKET_MINUTES, tag.countdownZenDuration); + + // reset countdownZenDuration to unbucketed number, should round change to nearest bucket + mController.mBucketIndex = -1; + tag.countdownZenDuration = 50; + mController.onClickTimeButton(view, tag, false, + ZenDurationDialog.COUNTDOWN_CONDITION_INDEX); + assertEquals(45, tag.countdownZenDuration); + + mController.mBucketIndex = -1; + tag.countdownZenDuration = 50; + mController.onClickTimeButton(view, tag, true, + ZenDurationDialog.COUNTDOWN_CONDITION_INDEX); + assertEquals(60, tag.countdownZenDuration); + + mController.mBucketIndex = -1; + tag.countdownZenDuration = 75; + mController.onClickTimeButton(view, tag, false, + ZenDurationDialog.COUNTDOWN_CONDITION_INDEX); + assertEquals(60, tag.countdownZenDuration); + + mController.mBucketIndex = -1; + tag.countdownZenDuration = 75; + mController.onClickTimeButton(view, tag, true, + ZenDurationDialog.COUNTDOWN_CONDITION_INDEX); + assertEquals(120, tag.countdownZenDuration); + } +}
\ No newline at end of file diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index c173225be5ce..1cd02f418352 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -200,4 +200,11 @@ <!-- Default for Settings.Secure.BACKUP_LOCAL_TRANSPORT_PARAMETERS --> <string name="def_backup_local_transport_parameters"></string> + + <!-- Default for Settings.Global.ZEN_DURATION + If 0, turning on dnd manually will last indefinitely. + Else if non-negative, turning on dnd manually will last for this many minutes. + Else (if negative), turning on dnd manually will surface a dialog that prompts + user to specify a duration.--> + <integer name="def_zen_duration">0</integer> </resources> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 11c869f49ac4..ccb4d8c866da 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1141,6 +1141,9 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.Global.APP_AUTO_RESTRICTION_ENABLED, GlobalSettingsProto.APP_AUTO_RESTRICTION_ENABLED); + dumpSetting(s, p, + Settings.Global.ZEN_DURATION, + GlobalSettingsProto.ZEN_DURATION); // Please insert new settings using the same order as in Settings.Global. } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index baf17cb0c463..639885847907 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -2938,7 +2938,7 @@ public class SettingsProvider extends ContentProvider { } private final class UpgradeController { - private static final int SETTINGS_VERSION = 156; + private static final int SETTINGS_VERSION = 157; private final int mUserId; @@ -3604,7 +3604,21 @@ public class SettingsProvider extends ContentProvider { currentVersion = 156; } + if (currentVersion == 156) { + // Version 156: Set a default value for zen duration + final SettingsState globalSettings = getGlobalSettingsLocked(); + final Setting currentSetting = globalSettings.getSettingLocked( + Global.ZEN_DURATION); + if (currentSetting.isNull()) { + String defaultZenDuration = Integer.toString(getContext() + .getResources().getInteger(R.integer.def_zen_duration)); + globalSettings.insertSettingLocked( + Global.ZEN_DURATION, defaultZenDuration, + null, true, SettingsState.SYSTEM_PACKAGE_NAME); + } + currentVersion = 157; + } // vXXX: Add new settings above this point. if (currentVersion != newVersion) { diff --git a/packages/StatementService/src/com/android/statementservice/DirectStatementService.java b/packages/StatementService/src/com/android/statementservice/DirectStatementService.java index 449738e9c605..659696e0e212 100644 --- a/packages/StatementService/src/com/android/statementservice/DirectStatementService.java +++ b/packages/StatementService/src/com/android/statementservice/DirectStatementService.java @@ -155,17 +155,20 @@ public final class DirectStatementService extends Service { @Override public void onDestroy() { super.onDestroy(); - if (mThread != null) { - mThread.quit(); - } - - try { - if (mHttpResponseCache != null) { - mHttpResponseCache.delete(); + final HttpResponseCache responseCache = mHttpResponseCache; + mHandler.post(new Runnable() { + public void run() { + try { + if (responseCache != null) { + responseCache.delete(); + } + } catch (IOException e) { + Log.i(TAG, "HTTP(S) response cache deletion failed:" + e); + } + Looper.myLooper().quit(); } - } catch (IOException e) { - Log.i(TAG, "HTTP(S) response cache deletion failed:" + e); - } + }); + mHttpResponseCache = null; } @Override diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_bg.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_bg.xml new file mode 100644 index 000000000000..221f1701187d --- /dev/null +++ b/packages/SystemUI/res/drawable/fingerprint_dialog_bg.xml @@ -0,0 +1,26 @@ +<?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 + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="@color/fingerprint_dialog_bg_color" /> + <corners android:radius="1dp" + android:topLeftRadius="@dimen/fingerprint_dialog_corner_size" + android:topRightRadius="@dimen/fingerprint_dialog_corner_size" + android:bottomLeftRadius="0dp" + android:bottomRightRadius="0dp"/> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/rounded_bg_bottom_background.xml b/packages/SystemUI/res/drawable/rounded_bg_bottom_background.xml index 8d03ce77f139..622226f25de3 100644 --- a/packages/SystemUI/res/drawable/rounded_bg_bottom_background.xml +++ b/packages/SystemUI/res/drawable/rounded_bg_bottom_background.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> - <solid android:color="@*android:color/material_grey_200" /> + <solid android:color="?android:attr/panelColorBackground" /> <corners android:bottomLeftRadius="@dimen/corner_size" android:topLeftRadius="0dp" diff --git a/packages/SystemUI/res/layout/car_fullscreen_user_switcher.xml b/packages/SystemUI/res/layout/car_fullscreen_user_switcher.xml index 478b65642689..bfabe520ef4d 100644 --- a/packages/SystemUI/res/layout/car_fullscreen_user_switcher.xml +++ b/packages/SystemUI/res/layout/car_fullscreen_user_switcher.xml @@ -39,23 +39,13 @@ android:theme="@android:style/Theme" android:layout_alignParentTop="true"/> - <!-- This progress bar is the countdown timer. --> - <ProgressBar - android:id="@+id/countdown_progress" - android:layout_width="match_parent" - android:layout_height="@dimen/car_user_switcher_progress_bar_height" - style="@style/CarUserSwitcher.ProgressBar" - android:layout_marginTop="@dimen/car_user_switcher_progress_bar_margin_top" - android:layout_alignParentTop="true"/> - <com.android.systemui.statusbar.car.UserGridView android:id="@+id/user_grid" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/car_margin" android:layout_marginRight="@dimen/car_margin" - android:layout_marginBottom="@dimen/car_user_grid_margin_bottom" - android:layout_centerInParent="true" /> + android:layout_centerInParent="true"/> <com.android.systemui.statusbar.car.PageIndicator android:id="@+id/user_switcher_page_indicator" diff --git a/packages/SystemUI/res/layout/car_qs_panel.xml b/packages/SystemUI/res/layout/car_qs_panel.xml index 7844cac43f9a..447970c95af2 100644 --- a/packages/SystemUI/res/layout/car_qs_panel.xml +++ b/packages/SystemUI/res/layout/car_qs_panel.xml @@ -42,7 +42,6 @@ android:layout_height="wrap_content" android:layout_marginLeft="@dimen/car_margin" android:layout_marginRight="@dimen/car_margin" - android:layout_marginBottom="@dimen/car_user_grid_margin_bottom" android:layout_above="@id/user_switcher_page_indicator" /> <com.android.systemui.statusbar.car.PageIndicator diff --git a/packages/SystemUI/res/layout/fingerprint_dialog.xml b/packages/SystemUI/res/layout/fingerprint_dialog.xml index f02c0ba35e77..1bdaf6e33544 100644 --- a/packages/SystemUI/res/layout/fingerprint_dialog.xml +++ b/packages/SystemUI/res/layout/fingerprint_dialog.xml @@ -26,115 +26,138 @@ <View android:id="@+id/space" android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="0dp" android:layout_weight="1" /> <LinearLayout - android:id="@+id/dialog" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:elevation="2dp" - android:background="@color/fingerprint_dialog_bg_color"> + android:layout_height="wrap_content"> - <TextView - android:id="@+id/title" - android:fontFamily="@*android:string/config_headlineFontFamilyMedium" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginEnd="24dp" - android:layout_marginStart="24dp" - android:layout_marginTop="24dp" - android:gravity="center" - android:textSize="20sp" - android:maxLines="1" - android:singleLine="true" - android:ellipsize="marquee" - android:marqueeRepeatLimit="marquee_forever" - android:textColor="@color/fingerprint_dialog_text_dark_color"/> + <!-- This is not a Space since Spaces cannot be clicked. The width of this changes depending + on horizontal/portrait orientation --> + <View + android:id="@+id/left_space" + android:layout_weight="1" + android:layout_width="0dp" + android:layout_height="match_parent"/> - <TextView - android:id="@+id/subtitle" - android:layout_width="match_parent" + <LinearLayout + android:id="@+id/dialog" + android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginTop="12dp" - android:layout_marginStart="24dp" - android:layout_marginEnd="24dp" - android:gravity="center_horizontal" - android:textSize="14sp" - android:maxLines="1" - android:singleLine="true" - android:ellipsize="marquee" - android:marqueeRepeatLimit="marquee_forever" - android:textColor="@color/fingerprint_dialog_text_light_color"/> + android:orientation="vertical" + android:elevation="2dp" + android:background="@drawable/fingerprint_dialog_bg"> - <TextView - android:id="@+id/description" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginEnd="24dp" - android:layout_marginStart="24dp" - android:paddingTop="24dp" - android:textSize="16sp" - android:maxLines="4" - android:textColor="@color/fingerprint_dialog_text_dark_color"/> + <TextView + android:id="@+id/title" + android:fontFamily="@*android:string/config_headlineFontFamilyMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginEnd="24dp" + android:layout_marginStart="24dp" + android:layout_marginTop="24dp" + android:gravity="@integer/fingerprint_dialog_text_gravity" + android:textSize="20sp" + android:maxLines="1" + android:singleLine="true" + android:ellipsize="marquee" + android:marqueeRepeatLimit="marquee_forever" + android:textColor="@color/fingerprint_dialog_text_dark_color"/> - <ImageView - android:id="@+id/fingerprint_icon" - android:layout_width="@dimen/fingerprint_dialog_fp_icon_size" - android:layout_height="@dimen/fingerprint_dialog_fp_icon_size" - android:layout_gravity="center_horizontal" - android:layout_marginTop="32dp" - android:scaleType="fitXY" - android:contentDescription="@string/accessibility_fingerprint_dialog_fingerprint_icon" /> + <TextView + android:id="@+id/subtitle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:layout_marginStart="24dp" + android:layout_marginEnd="24dp" + android:gravity="@integer/fingerprint_dialog_text_gravity" + android:textSize="16sp" + android:maxLines="1" + android:singleLine="true" + android:ellipsize="marquee" + android:marqueeRepeatLimit="marquee_forever" + android:textColor="@color/fingerprint_dialog_text_dark_color"/> - <TextView - android:id="@+id/error" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginEnd="24dp" - android:layout_marginStart="24dp" - android:paddingTop="16dp" - android:paddingBottom="24dp" - android:textSize="12sp" - android:gravity="center_horizontal" - android:accessibilityLiveRegion="polite" - android:text="@string/fingerprint_dialog_touch_sensor" - android:contentDescription="@string/accessibility_fingerprint_dialog_help_area" - android:textColor="@color/fingerprint_dialog_text_light_color"/> + <TextView + android:id="@+id/description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginEnd="24dp" + android:layout_marginStart="24dp" + android:gravity="@integer/fingerprint_dialog_text_gravity" + android:paddingTop="8dp" + android:textSize="16sp" + android:maxLines="4" + android:textColor="@color/fingerprint_dialog_text_dark_color"/> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="72dip" - android:paddingTop="16dp" - android:layout_gravity="center_vertical" - style="?android:attr/buttonBarStyle" - android:orientation="horizontal" - android:measureWithLargestChild="true"> - <Space android:id="@+id/leftSpacer" - android:layout_width="24dp" - android:layout_height="match_parent" - android:visibility="visible" /> - <!-- Negative Button --> - <Button android:id="@+id/button2" - android:layout_width="wrap_content" - android:layout_height="match_parent" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_marginStart="-12dp" - android:gravity="start|center_vertical" - android:maxLines="2" /> - <!-- Positive Button --> - <Button android:id="@+id/button1" - android:layout_width="wrap_content" - android:layout_height="match_parent" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_marginEnd="12dp" - android:maxLines="2" /> - <Space android:id="@+id/rightSpacer" - android:layout_width="24dip" - android:layout_height="match_parent" - android:visibility="gone" /> + <ImageView + android:id="@+id/fingerprint_icon" + android:layout_width="@dimen/fingerprint_dialog_fp_icon_size" + android:layout_height="@dimen/fingerprint_dialog_fp_icon_size" + android:layout_gravity="center_horizontal" + android:layout_marginTop="48dp" + android:scaleType="fitXY" + android:contentDescription="@string/accessibility_fingerprint_dialog_fingerprint_icon" /> + + <TextView + android:id="@+id/error" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginEnd="24dp" + android:layout_marginStart="24dp" + android:paddingTop="16dp" + android:paddingBottom="24dp" + android:textSize="12sp" + android:gravity="center_horizontal" + android:accessibilityLiveRegion="polite" + android:text="@string/fingerprint_dialog_touch_sensor" + android:contentDescription="@string/accessibility_fingerprint_dialog_help_area" + android:textColor="@color/fingerprint_dialog_text_light_color"/> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="72dip" + android:paddingTop="24dp" + android:layout_gravity="center_vertical" + style="?android:attr/buttonBarStyle" + android:orientation="horizontal" + android:measureWithLargestChild="true"> + <Space android:id="@+id/leftSpacer" + android:layout_width="24dp" + android:layout_height="match_parent" + android:visibility="visible" /> + <!-- Negative Button --> + <Button android:id="@+id/button2" + android:layout_width="wrap_content" + android:layout_height="match_parent" + style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" + android:layout_marginStart="-12dp" + android:gravity="start|center_vertical" + android:maxLines="2" /> + <!-- Positive Button --> + <Button android:id="@+id/button1" + android:layout_width="wrap_content" + android:layout_height="match_parent" + style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" + android:layout_marginEnd="12dp" + android:maxLines="2" /> + <Space android:id="@+id/rightSpacer" + android:layout_width="24dip" + android:layout_height="match_parent" + android:visibility="gone" /> + </LinearLayout> </LinearLayout> + + <!-- This is not a Space since Spaces cannot be clicked. The width of this changes depending + on horizontal/portrait orientation --> + <View + android:id="@+id/right_space" + android:layout_weight="1" + android:layout_width="0dp" + android:layout_height="match_parent" /> + </LinearLayout> </LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml index 0063f6af59fc..0a3f4eb690d5 100644 --- a/packages/SystemUI/res/layout/volume_dialog.xml +++ b/packages/SystemUI/res/layout/volume_dialog.xml @@ -73,17 +73,19 @@ <!-- volume rows added and removed here! :-) --> </LinearLayout> <FrameLayout - android:layout_height="wrap_content" + android:id="@+id/settings_container" android:layout_width="match_parent" + android:layout_height="wrap_content" android:background="@drawable/rounded_bg_bottom_background"> <com.android.keyguard.AlphaOptimizedImageButton android:id="@+id/settings" - android:src="@drawable/ic_settings" + android:src="@drawable/ic_settings_16dp" android:layout_width="@dimen/volume_dialog_tap_target_size" android:layout_height="@dimen/volume_dialog_tap_target_size" android:layout_gravity="center" + android:contentDescription="@string/accessibility_volume_settings" android:background="?android:selectableItemBackgroundBorderless" - android:tint="#8A000000" + android:tint="?android:attr/colorControlNormal" android:soundEffectsEnabled="false" /> </FrameLayout> </LinearLayout> diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml index def6f6b5f701..bcc369252c29 100644 --- a/packages/SystemUI/res/layout/volume_dialog_row.xml +++ b/packages/SystemUI/res/layout/volume_dialog_row.xml @@ -25,7 +25,6 @@ <LinearLayout android:layout_height="wrap_content" android:layout_width="match_parent" - android:layout_marginTop="@dimen/volume_dialog_slider_margin_top" android:gravity="center" android:layout_gravity="center" android:orientation="vertical" > @@ -42,6 +41,8 @@ <FrameLayout android:id="@+id/volume_row_slider_frame" android:layout_width="match_parent" + android:layout_marginTop="@dimen/volume_dialog_slider_margin_top" + android:layout_marginBottom="@dimen/volume_dialog_slider_margin_bottom" android:layoutDirection="rtl" android:layout_height="@dimen/volume_dialog_slider_height"> <SeekBar @@ -60,6 +61,7 @@ android:layout_width="@dimen/volume_dialog_tap_target_size" android:layout_height="@dimen/volume_dialog_tap_target_size" android:background="?android:selectableItemBackgroundBorderless" + android:layout_marginBottom="@dimen/volume_dialog_row_margin_bottom" android:tint="@color/accent_tint_color_selector" android:soundEffectsEnabled="false" /> </LinearLayout> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index e2a94dfd2c8b..5b038b1b42ff 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -160,12 +160,12 @@ <color name="smart_reply_button_background">#fff2f2f2</color> <!-- Fingerprint dialog colors --> - <color name="fingerprint_dialog_bg_color">#f4ffffff</color> <!-- 96% white --> - <color name="fingerprint_dialog_text_dark_color">#ff212121</color> - <color name="fingerprint_dialog_text_light_color">#ff757575</color> + <color name="fingerprint_dialog_bg_color">#ffffffff</color> <!-- 100% white --> + <color name="fingerprint_dialog_text_dark_color">#dd000000</color> <!-- 87% black --> + <color name="fingerprint_dialog_text_light_color">#89000000</color> <!-- 54% black --> <color name="fingerprint_dialog_dim_color">#80000000</color> <!-- 50% black --> - <color name="fingerprint_dialog_error_message_color">#ffff5722</color> - <color name="fingerprint_dialog_fingerprint_color">#ff009688</color> + <color name="fingerprint_dialog_error_message_color">#ffd93025</color> <!-- google red 600 --> + <color name="fingerprint_dialog_fingerprint_color">#ff008577</color> <!-- google blue 600 --> <!-- Logout button --> <color name="logout_button_bg_color">#ccffffff</color> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 0b65c70c7ef1..e30b86a6c4ef 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -270,7 +270,7 @@ <dimen name="volume_dialog_panel_width">64dp</dimen> - <dimen name="volume_dialog_slider_height">101dp</dimen> + <dimen name="volume_dialog_slider_height">108dp</dimen> <dimen name="volume_dialog_row_height">252dp</dimen> @@ -280,7 +280,13 @@ <dimen name="volume_dialog_spacer">4dp</dimen> - <dimen name="volume_dialog_slider_margin_top">13dp</dimen> + <dimen name="volume_dialog_slider_margin_top">22dp</dimen> + + <dimen name="volume_dialog_slider_margin_bottom">-2dp</dimen> + + <dimen name="volume_dialog_row_margin_bottom">8dp</dimen> + + <dimen name="volume_dialog_settings_icon_size">16dp</dimen> <!-- Gravity for the notification panel --> <integer name="notification_panel_layout_gravity">0x31</integer><!-- center_horizontal|top --> @@ -906,8 +912,9 @@ <dimen name="smart_reply_button_line_spacing_extra">6sp</dimen> <!-- Total line height 20sp. --> <!-- Fingerprint Dialog values --> - <dimen name="fingerprint_dialog_fp_icon_size">60dp</dimen> + <dimen name="fingerprint_dialog_fp_icon_size">64dp</dimen> <dimen name="fingerprint_dialog_animation_translation_offset">350dp</dimen> + <dimen name="fingerprint_dialog_corner_size">2dp</dimen> <!-- Wireless Charging Animation values --> <dimen name="wireless_charging_dots_radius_start">0dp</dimen> diff --git a/packages/SystemUI/res/values/dimens_car.xml b/packages/SystemUI/res/values/dimens_car.xml index f3c9f89bad41..5679dd2f1fb0 100644 --- a/packages/SystemUI/res/values/dimens_car.xml +++ b/packages/SystemUI/res/values/dimens_car.xml @@ -36,8 +36,6 @@ <dimen name="car_page_indicator_dot_diameter">12dp</dimen> <dimen name="car_page_indicator_margin_bottom">24dp</dimen> - <dimen name="car_user_switcher_progress_bar_height">6dp</dimen> - <dimen name="car_user_switcher_progress_bar_margin_top">@dimen/status_bar_height</dimen> <dimen name="car_start_driving_corner_radius">16dp</dimen> <dimen name="car_start_driving_padding_side">30dp</dimen> <dimen name="car_start_driving_height">80dp</dimen> @@ -57,7 +55,6 @@ <dimen name="car_user_switcher_container_height">420dp</dimen> <!-- This must be the negative of car_user_switcher_container_height for the animation. --> <dimen name="car_user_switcher_container_anim_height">-420dp</dimen> - <dimen name="car_user_grid_margin_bottom">28dp</dimen> <dimen name="car_body2_size">26sp</dimen> </resources> diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml new file mode 100644 index 000000000000..8f23283478b0 --- /dev/null +++ b/packages/SystemUI/res/values/integers.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<resources> + <integer name="fingerprint_dialog_text_gravity">8388611</integer> <!-- gravity start --> +</resources>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/integers_car.xml b/packages/SystemUI/res/values/integers_car.xml index f84dd4bd5f27..a462576c48d7 100644 --- a/packages/SystemUI/res/values/integers_car.xml +++ b/packages/SystemUI/res/values/integers_car.xml @@ -16,8 +16,5 @@ --> <resources xmlns:android="http://schemas.android.com/apk/res/android"> - <integer name="car_user_switcher_timeout_ms">15000</integer> - <!-- This values less than ProgressBar.PROGRESS_ANIM_DURATION for a smooth animation. --> - <integer name="car_user_switcher_anim_update_ms">60</integer> <integer name="car_user_switcher_anim_cascade_delay_ms">27</integer> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 4bd997d9b989..cce38f16143a 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -780,6 +780,9 @@ <string name="quick_settings_hotspot_label">Hotspot</string> <!-- QuickSettings: Hotspot. Secondary label shown when the hotspot is being enabled [CHAR LIMIT=NONE] --> <string name="quick_settings_hotspot_secondary_label_transient">Turning on…</string> + <!-- QuickSettings: Hotspot. Secondary label shown when Data Saver mode is enabled to explain to + the user why they can't toggle the hotspot tile. [CHAR LIMIT=20] --> + <string name="quick_settings_hotspot_secondary_label_data_saver_enabled">Data Saver is on</string> <!-- QuickSettings: Hotspot: Secondary label for how many devices are connected to the hotspot [CHAR LIMIT=NONE] --> <plurals name="quick_settings_hotspot_secondary_label_num_devices"> <item quantity="one">%d device</item> @@ -1268,6 +1271,9 @@ <!-- Button label for ending zen mode in the volume dialog --> <string name="volume_zen_end_now">Turn off now</string> + <!-- Content description for accessibility (not shown on the screen): volume dialog settings button. [CHAR LIMIT=NONE] --> + <string name="accessibility_volume_settings">Sound settings</string> + <!-- Content description for accessibility (not shown on the screen): volume dialog expand button. [CHAR LIMIT=NONE] --> <string name="accessibility_volume_expand">Expand</string> diff --git a/packages/SystemUI/res/values/styles_car.xml b/packages/SystemUI/res/values/styles_car.xml index c66792ca6a15..2aaef86296f3 100644 --- a/packages/SystemUI/res/values/styles_car.xml +++ b/packages/SystemUI/res/values/styles_car.xml @@ -16,14 +16,6 @@ --> <resources xmlns:android="http://schemas.android.com/apk/res/android"> - <style name="CarUserSwitcher.ProgressBar" parent="@android:style/Widget.ProgressBar.Horizontal"> - <item name="android:progressDrawable">@drawable/car_progress_bar</item> - <item name="android:min">0</item> - <item name="android:max">@integer/car_user_switcher_timeout_ms</item> - <item name="android:progress">0</item> - <item name="android:interpolator">@android:anim/linear_interpolator</item> - </style> - <style name="CarUserSwitcher.StartDrivingButton" parent="@android:style/Widget.Material.Button"> <item name="android:background">@drawable/car_round_button</item> <item name="android:textSize">@dimen/car_start_driving_text_size</item> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/IconLoader.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/IconLoader.java index 3bc1d9a0588c..14767f1c63bf 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/IconLoader.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/IconLoader.java @@ -103,12 +103,13 @@ public abstract class IconLoader { int userId = taskKey.userId; Bitmap tdIcon = desc.getInMemoryIcon(); if (tdIcon != null) { - return createDrawableFromBitmap(tdIcon, userId); + return createDrawableFromBitmap(tdIcon, userId, desc); } if (desc.getIconResource() != 0) { // TODO: Use task context here try { - return createBadgedDrawable(mContext.getDrawable(desc.getIconResource()), userId); + return createBadgedDrawable( + mContext.getDrawable(desc.getIconResource()), userId, desc); } catch (Resources.NotFoundException e) { Log.e(TAG, "Could not find icon drawable from resource", e); } @@ -117,13 +118,13 @@ public abstract class IconLoader { tdIcon = ActivityManager.TaskDescription.loadTaskDescriptionIcon( desc.getIconFilename(), userId); if (tdIcon != null) { - return createDrawableFromBitmap(tdIcon, userId); + return createDrawableFromBitmap(tdIcon, userId, desc); } // Load the icon from the activity info and cache it ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey); if (activityInfo != null) { - Drawable icon = getBadgedActivityIcon(activityInfo, userId); + Drawable icon = getBadgedActivityIcon(activityInfo, userId, desc); if (icon != null) { return icon; } @@ -135,16 +136,20 @@ public abstract class IconLoader { public abstract Drawable getDefaultIcon(int userId); - protected Drawable createDrawableFromBitmap(Bitmap icon, int userId) { - return createBadgedDrawable(new BitmapDrawable(mContext.getResources(), icon), userId); + protected Drawable createDrawableFromBitmap(Bitmap icon, int userId, + ActivityManager.TaskDescription desc) { + return createBadgedDrawable( + new BitmapDrawable(mContext.getResources(), icon), userId, desc); } - protected abstract Drawable createBadgedDrawable(Drawable icon, int userId); + protected abstract Drawable createBadgedDrawable(Drawable icon, int userId, + ActivityManager.TaskDescription desc); /** * @return the activity icon for the ActivityInfo for a user, badging if necessary. */ - protected abstract Drawable getBadgedActivityIcon(ActivityInfo info, int userId); + protected abstract Drawable getBadgedActivityIcon(ActivityInfo info, int userId, + ActivityManager.TaskDescription desc); public static class DefaultIconLoader extends IconLoader { @@ -168,7 +173,8 @@ public abstract class IconLoader { } @Override - protected Drawable createBadgedDrawable(Drawable icon, int userId) { + protected Drawable createBadgedDrawable(Drawable icon, int userId, + ActivityManager.TaskDescription desc) { if (userId != UserHandle.myUserId()) { icon = mContext.getPackageManager().getUserBadgedIcon(icon, new UserHandle(userId)); } @@ -176,7 +182,8 @@ public abstract class IconLoader { } @Override - protected Drawable getBadgedActivityIcon(ActivityInfo info, int userId) { + protected Drawable getBadgedActivityIcon(ActivityInfo info, int userId, + ActivityManager.TaskDescription desc) { return mDrawableFactory.getBadgedIcon(info, info.applicationInfo, userId); } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java index dd1763bb118b..924e85dec37a 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java @@ -31,6 +31,7 @@ public class ThumbnailData { public int orientation; public Rect insets; public boolean reducedResolution; + public boolean isRealSnapshot; public float scale; public ThumbnailData() { @@ -39,6 +40,7 @@ public class ThumbnailData { insets = new Rect(); reducedResolution = false; scale = 1f; + isRealSnapshot = true; } public ThumbnailData(TaskSnapshot snapshot) { @@ -47,5 +49,6 @@ public class ThumbnailData { orientation = snapshot.getOrientation(); reducedResolution = snapshot.isReducedResolution(); scale = snapshot.getScale(); + isRealSnapshot = snapshot.isRealSnapshot(); } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/LatencyTrackerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/LatencyTrackerCompat.java new file mode 100644 index 000000000000..0d5933e5f599 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/LatencyTrackerCompat.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.shared.system; + +import android.content.Context; + +import com.android.internal.util.LatencyTracker; + +/** + * @see LatencyTracker + */ +public class LatencyTrackerCompat { + public static boolean isEnabled(Context context) { + return LatencyTracker.isEnabled(context); + } + + public static void logToggleRecents(int duration) { + LatencyTracker.logAction(LatencyTracker.ACTION_TOGGLE_RECENTS, duration); + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 903f3aa69299..ec2390fbcfc6 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -31,6 +31,7 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.graphics.Region; import android.hardware.display.DisplayManager; import android.provider.Settings.Secure; import android.support.annotation.VisibleForTesting; @@ -312,6 +313,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { private final DisplayInfo mInfo = new DisplayInfo(); private final Paint mPaint = new Paint(); + private final Region mBounds = new Region(); private final Rect mBoundingRect = new Rect(); private final Path mBoundingPath = new Path(); private final int[] mLocation = new int[2]; @@ -370,11 +372,13 @@ public class ScreenDecorations extends SystemUI implements Tunable { private void update() { requestLayout(); getDisplay().getDisplayInfo(mInfo); + mBounds.setEmpty(); mBoundingRect.setEmpty(); mBoundingPath.reset(); int newVisible; if (hasCutout()) { - mBoundingRect.set(mInfo.displayCutout.getBoundingRect()); + mBounds.set(mInfo.displayCutout.getBounds()); + localBounds(mBoundingRect); mInfo.displayCutout.getBounds().getBoundaryPath(mBoundingPath); newVisible = VISIBLE; } else { @@ -402,7 +406,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (mBoundingRect.isEmpty()) { + if (mBounds.isEmpty()) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); return; } @@ -410,5 +414,50 @@ public class ScreenDecorations extends SystemUI implements Tunable { resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0), resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0)); } + + public static void boundsFromDirection(DisplayCutout displayCutout, int gravity, Rect out) { + Region bounds = displayCutout.getBounds(); + switch (gravity) { + case Gravity.TOP: + bounds.op(0, 0, Integer.MAX_VALUE, displayCutout.getSafeInsetTop(), + Region.Op.INTERSECT); + out.set(bounds.getBounds()); + break; + case Gravity.LEFT: + bounds.op(0, 0, displayCutout.getSafeInsetLeft(), Integer.MAX_VALUE, + Region.Op.INTERSECT); + out.set(bounds.getBounds()); + break; + case Gravity.BOTTOM: + bounds.op(0, displayCutout.getSafeInsetTop() + 1, Integer.MAX_VALUE, + Integer.MAX_VALUE, Region.Op.INTERSECT); + out.set(bounds.getBounds()); + break; + case Gravity.RIGHT: + bounds.op(displayCutout.getSafeInsetLeft() + 1, 0, Integer.MAX_VALUE, + Integer.MAX_VALUE, Region.Op.INTERSECT); + out.set(bounds.getBounds()); + break; + } + bounds.recycle(); + } + + private void localBounds(Rect out) { + final DisplayCutout displayCutout = mInfo.displayCutout; + + if (mStart) { + if (displayCutout.getSafeInsetLeft() > 0) { + boundsFromDirection(displayCutout, Gravity.LEFT, out); + } else if (displayCutout.getSafeInsetTop() > 0) { + boundsFromDirection(displayCutout, Gravity.TOP, out); + } + } else { + if (displayCutout.getSafeInsetRight() > 0) { + boundsFromDirection(displayCutout, Gravity.RIGHT, out); + } else if (displayCutout.getSafeInsetBottom() > 0) { + boundsFromDirection(displayCutout, Gravity.BOTTOM, out); + } + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogImpl.java b/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogImpl.java index 1d43b1d7d554..4b15fbcd2e84 100644 --- a/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogImpl.java @@ -180,8 +180,8 @@ public class FingerprintDialogImpl extends SystemUI implements CommandQueue.Call } } mReceiver = null; - mWindowManager.removeView(mDialogView); mDialogShowing = false; + mDialogView.startDismiss(); } private void handleButtonNegative() { diff --git a/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogView.java b/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogView.java index e828b2c097ea..ebdc70339d22 100644 --- a/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogView.java @@ -17,6 +17,7 @@ package com.android.systemui.fingerprint; import android.content.Context; +import android.content.res.Configuration; import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.PorterDuff; @@ -27,6 +28,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.util.DisplayMetrics; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -42,6 +44,7 @@ import android.widget.TextView; import com.android.systemui.Interpolators; import com.android.systemui.R; +import com.android.systemui.util.leak.RotationUtils; /** * This class loads the view for the system-provided dialog. The view consists of: @@ -61,7 +64,7 @@ public class FingerprintDialogView extends LinearLayout { private final IBinder mWindowToken = new Binder(); private final Interpolator mLinearOutSlowIn; - private final Interpolator mFastOutLinearIn; + private final WindowManager mWindowManager; private final float mAnimationTranslationOffset; private final int mErrorTextColor; private final int mTextColor; @@ -74,11 +77,13 @@ public class FingerprintDialogView extends LinearLayout { private final LinearLayout mDialog; private int mLastState; + private final float mDisplayWidth; + public FingerprintDialogView(Context context, Handler handler) { super(context); mHandler = handler; mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN; - mFastOutLinearIn = Interpolators.FAST_OUT_LINEAR_IN; + mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); mAnimationTranslationOffset = getResources() .getDimension(R.dimen.fingerprint_dialog_animation_translation_offset); mErrorTextColor = Color.parseColor( @@ -88,6 +93,10 @@ public class FingerprintDialogView extends LinearLayout { mFingerprintColor = Color.parseColor( getResources().getString(R.color.fingerprint_dialog_fingerprint_color)); + DisplayMetrics metrics = new DisplayMetrics(); + mWindowManager.getDefaultDisplay().getMetrics(metrics); + mDisplayWidth = metrics.widthPixels; + // Create the dialog LayoutInflater factory = LayoutInflater.from(getContext()); mLayout = (ViewGroup) factory.inflate(R.layout.fingerprint_dialog, this, false); @@ -117,15 +126,14 @@ public class FingerprintDialogView extends LinearLayout { }); final View space = mLayout.findViewById(R.id.space); + final View leftSpace = mLayout.findViewById(R.id.left_space); + final View rightSpace = mLayout.findViewById(R.id.right_space); final Button negative = mLayout.findViewById(R.id.button2); final Button positive = mLayout.findViewById(R.id.button1); - space.setClickable(true); - space.setOnTouchListener((View view, MotionEvent event) -> { - mHandler.obtainMessage(FingerprintDialogImpl.MSG_HIDE_DIALOG, true /* userCanceled */) - .sendToTarget(); - return true; - }); + setDismissesDialog(space); + setDismissesDialog(leftSpace); + setDismissesDialog(rightSpace); negative.setOnClickListener((View v) -> { mHandler.obtainMessage(FingerprintDialogImpl.MSG_BUTTON_NEGATIVE).sendToTarget(); @@ -149,6 +157,8 @@ public class FingerprintDialogView extends LinearLayout { final Button negative = mLayout.findViewById(R.id.button2); final Button positive = mLayout.findViewById(R.id.button1); + mDialog.getLayoutParams().width = (int) mDisplayWidth; + mLastState = STATE_NONE; updateFingerprintIcon(STATE_FINGERPRINT); @@ -189,6 +199,43 @@ public class FingerprintDialogView extends LinearLayout { }); } + private void setDismissesDialog(View v) { + v.setClickable(true); + v.setOnTouchListener((View view, MotionEvent event) -> { + mHandler.obtainMessage(FingerprintDialogImpl.MSG_HIDE_DIALOG, true /* userCanceled */) + .sendToTarget(); + return true; + }); + } + + public void startDismiss() { + final Runnable endActionRunnable = new Runnable() { + @Override + public void run() { + mWindowManager.removeView(FingerprintDialogView.this); + } + }; + + postOnAnimation(new Runnable() { + @Override + public void run() { + mLayout.animate() + .alpha(0f) + .setDuration(ANIMATION_DURATION) + .setInterpolator(mLinearOutSlowIn) + .withLayer() + .start(); + mDialog.animate() + .translationY(mAnimationTranslationOffset) + .setDuration(ANIMATION_DURATION) + .setInterpolator(mLinearOutSlowIn) + .withLayer() + .withEndAction(endActionRunnable) + .start(); + } + }); + } + public void setBundle(Bundle bundle) { mBundle = bundle; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 0548e698be33..a48dcc69ae9f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -32,6 +32,7 @@ import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.AttributeSet; +import android.util.Log; import android.view.View; import android.widget.RelativeLayout; import android.widget.TextView; @@ -61,6 +62,8 @@ import java.util.Locale; */ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue.Callbacks, View.OnClickListener, NextAlarmController.NextAlarmChangeCallback { + private static final String TAG = "QuickStatusBarHeader"; + private static final boolean DEBUG = false; /** Delay for auto fading out the long press tooltip after it's fully visible (in ms). */ private static final long AUTO_FADE_OUT_DELAY_MS = DateUtils.SECOND_IN_MILLIS * 6; @@ -293,6 +296,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue @Override public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) { mNextAlarmText = nextAlarm != null ? formatNextAlarm(nextAlarm) : null; + if (mNextAlarmText != null) { hideLongPressTooltip(true /* shouldFadeInAlarmText */); } else { @@ -352,6 +356,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { + if (DEBUG) Log.d(TAG, "hideLongPressTooltip: Hid long press tip"); mLongPressTooltipView.setVisibility(View.INVISIBLE); if (shouldShowAlarmText) { @@ -362,7 +367,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue .start(); } else { mLongPressTooltipView.setVisibility(View.INVISIBLE); - if (shouldShowAlarmText) { showAlarmText(); } @@ -378,9 +382,11 @@ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue mNextAlarmView.setVisibility(View.VISIBLE); mNextAlarmTextView.setText(mNextAlarmText); + // Animate the alarm back in. Make sure to clear the animator listener for the animation! mNextAlarmView.animate() .alpha(1f) .setDuration(FADE_ANIMATION_DURATION_MS) + .setListener(null) .start(); } @@ -395,6 +401,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { + if (DEBUG) Log.d(TAG, "hideAlarmText: Hid alarm text"); + // Reset the alpha regardless of how the animation ends for the next // time we show this view/want to animate it. mNextAlarmView.setVisibility(View.INVISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index 66823ca135cb..1cb89c472dbb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -92,9 +92,10 @@ public class TileLayout extends ViewGroup implements QSTileLayout { protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int numTiles = mRecords.size(); final int width = MeasureSpec.getSize(widthMeasureSpec); - final int rows = (numTiles + mColumns - 1) / mColumns; + final int numRows = (numTiles + mColumns - 1) / mColumns; mCellWidth = (width - (mCellMarginHorizontal * (mColumns + 1))) / mColumns; + // Measure each QS tile. View previousView = this; for (TileRecord record : mRecords) { if (record.tileView.getVisibility() == GONE) continue; @@ -104,9 +105,10 @@ public class TileLayout extends ViewGroup implements QSTileLayout { // Only include the top margin in our measurement if we have more than 1 row to show. // Otherwise, don't add the extra margin buffer at top. - int height = (mCellHeight + mCellMarginVertical) * rows + - (rows != 0 ? (mCellMarginTop - mCellMarginVertical) : 0); + int height = (mCellHeight + mCellMarginVertical) * numRows + + (numRows != 0 ? (mCellMarginTop - mCellMarginVertical) : 0); if (height < 0) height = 0; + setMeasuredDimension(width, height); } @@ -122,24 +124,30 @@ public class TileLayout extends ViewGroup implements QSTileLayout { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int w = getWidth(); - boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; + final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; int row = 0; int column = 0; + + // Layout each QS tile. for (int i = 0; i < mRecords.size(); i++, column++) { + // If we reached the last column available to layout a tile, wrap back to the next row. if (column == mColumns) { + column = 0; row++; - column -= mColumns; } - TileRecord record = mRecords.get(i); - int left = getColumnStart(column); + + final TileRecord record = mRecords.get(i); final int top = getRowTop(row); - int right; + final int right; + final int left; if (isRtl) { - right = w - left; + right = w - getColumnStart(column); left = right - mCellWidth; } else { + left = getColumnStart(column); right = left + mCellWidth; } + record.tileView.layout(left, top, right, top + record.tileView.getMeasuredHeight()); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index 2d31669db6c2..f3a2ae3770b6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -19,6 +19,7 @@ package com.android.systemui.qs.tiles; import static android.provider.Settings.Global.ZEN_MODE_ALARMS; import static android.provider.Settings.Global.ZEN_MODE_OFF; +import android.app.ActivityManager; import android.app.Dialog; import android.content.BroadcastReceiver; import android.content.Context; @@ -28,6 +29,7 @@ import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.net.Uri; import android.os.UserManager; import android.provider.Settings; import android.provider.Settings.Global; @@ -139,15 +141,29 @@ public class DndTile extends QSTileImpl<BooleanState> { @Override public void showDetail(boolean show) { - mUiHandler.post(() -> { - Dialog mDialog = new EnableZenModeDialog(mContext).createDialog(); - mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - SystemUIDialog.setShowForAllUsers(mDialog, true); - SystemUIDialog.registerDismissListener(mDialog); - SystemUIDialog.setWindowOnTop(mDialog); - mUiHandler.post(() -> mDialog.show()); - mHost.collapsePanels(); - }); + int zenDuration = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.ZEN_DURATION, 0); + switch (zenDuration) { + case Settings.Global.ZEN_DURATION_PROMPT: + mUiHandler.post(() -> { + Dialog mDialog = new EnableZenModeDialog(mContext).createDialog(); + mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + SystemUIDialog.setShowForAllUsers(mDialog, true); + SystemUIDialog.registerDismissListener(mDialog); + SystemUIDialog.setWindowOnTop(mDialog); + mUiHandler.post(() -> mDialog.show()); + mHost.collapsePanels(); + }); + break; + case Settings.Global.ZEN_DURATION_FOREVER: + mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG); + break; + default: + Uri conditionId = ZenModeConfig.toTimeCondition(mContext, zenDuration, + ActivityManager.getCurrentUser(), true).id; + mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, + conditionId, TAG); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java index 81e3d5ad17de..00d6bd0d307f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java @@ -137,7 +137,6 @@ public class HotspotTile extends QSTileImpl<AirplaneBooleanState> { state.icon = mEnabledStatic; state.label = mContext.getString(R.string.quick_settings_hotspot_label); - state.secondaryLabel = getSecondaryLabel(state.value, isTransient, numConnectedDevices); state.isAirplaneMode = mAirplaneMode.getValue() != 0; state.isTransient = isTransient; state.slash.isSlashed = !state.value && !state.isTransient; @@ -149,19 +148,26 @@ public class HotspotTile extends QSTileImpl<AirplaneBooleanState> { final boolean isTileUnavailable = (state.isAirplaneMode || isDataSaverEnabled); final boolean isTileActive = (state.value || state.isTransient); - state.state = isTileUnavailable - ? Tile.STATE_UNAVAILABLE - : isTileActive - ? Tile.STATE_ACTIVE - : Tile.STATE_INACTIVE; + + if (isTileUnavailable) { + state.state = Tile.STATE_UNAVAILABLE; + } else { + state.state = isTileActive ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; + } + + state.secondaryLabel = getSecondaryLabel( + isTileActive, isTransient, isDataSaverEnabled, numConnectedDevices); } @Nullable - private String getSecondaryLabel( - boolean enabled, boolean isTransient, int numConnectedDevices) { + private String getSecondaryLabel(boolean isActive, boolean isTransient, + boolean isDataSaverEnabled, int numConnectedDevices) { if (isTransient) { return mContext.getString(R.string.quick_settings_hotspot_secondary_label_transient); - } else if (numConnectedDevices > 0 && enabled) { + } else if (isDataSaverEnabled) { + return mContext.getString( + R.string.quick_settings_hotspot_secondary_label_data_saver_enabled); + } else if (numConnectedDevices > 0 && isActive) { return mContext.getResources().getQuantityString( R.plurals.quick_settings_hotspot_secondary_label_num_devices, numConnectedDevices, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java index 3ec89138d1c0..bc353f2dc0f9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java @@ -19,7 +19,6 @@ package com.android.systemui.statusbar.car; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.content.res.Resources; -import android.os.CountDownTimer; import android.view.View; import android.view.ViewStub; import android.widget.ProgressBar; @@ -36,16 +35,11 @@ public class FullscreenUserSwitcher { private final View mParent; private final UserGridView mUserGridView; private final UserSwitcherController mUserSwitcherController; - private final ProgressBar mProgressBar; private final ProgressBar mSwitchingUsers; - private final int mLoginTimeoutMs; - private final int mAnimUpdateIntervalMs; private final int mShortAnimDuration; private boolean mShowing; - private CountDownTimer mTimer; - public FullscreenUserSwitcher(StatusBar statusBar, UserSwitcherController userSwitcherController, ViewStub containerStub) { @@ -63,26 +57,13 @@ public class FullscreenUserSwitcher { PageIndicator pageIndicator = mContainer.findViewById(R.id.user_switcher_page_indicator); pageIndicator.setupWithViewPager(mUserGridView); - mProgressBar = mContainer.findViewById(R.id.countdown_progress); Resources res = mContainer.getResources(); - mLoginTimeoutMs = res.getInteger(R.integer.car_user_switcher_timeout_ms); - mAnimUpdateIntervalMs = res.getInteger(R.integer.car_user_switcher_anim_update_ms); mShortAnimDuration = res.getInteger(android.R.integer.config_shortAnimTime); mContainer.findViewById(R.id.start_driving).setOnClickListener(v -> { - cancelTimer(); automaticallySelectUser(); }); - // Any interaction with the screen should cancel the timer. - mContainer.setOnClickListener(v -> { - cancelTimer(); - }); - mUserGridView.setOnTouchListener((v, e) -> { - cancelTimer(); - return false; - }); - mSwitchingUsers = mParent.findViewById(R.id.switching_users); } @@ -127,44 +108,14 @@ public class FullscreenUserSwitcher { } mShowing = true; mParent.setVisibility(View.VISIBLE); - cancelTimer(); - - // This would be the case if we were in the middle of a switch. - if (mProgressBar.getVisibility() != View.VISIBLE) { - return; - } - - mTimer = new CountDownTimer(mLoginTimeoutMs, mAnimUpdateIntervalMs) { - @Override - public void onTick(long msUntilFinished) { - int elapsed = mLoginTimeoutMs - (int) msUntilFinished; - mProgressBar.setProgress((int) elapsed, true /* animate */); - } - - @Override - public void onFinish() { - mProgressBar.setProgress(mLoginTimeoutMs, true /* animate */); - automaticallySelectUser(); - } - }; - mTimer.start(); } public void hide() { mShowing = false; - cancelTimer(); toggleSwitchInProgress(false); mParent.setVisibility(View.GONE); } - private void cancelTimer() { - if (mTimer != null) { - mTimer.cancel(); - mTimer = null; - mProgressBar.setProgress(0, true /* animate */); - } - } - private void automaticallySelectUser() { // TODO: Switch according to some policy. This implementation just tries to drop the // keyguard for the current user. 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 ca66e987933c..0716b37ff8d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -55,6 +55,7 @@ import android.util.MathUtils; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; +import android.view.WindowInsets; import android.view.WindowManager; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; @@ -912,4 +913,16 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL return (secure && !canSkipBouncer) ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT; } } + + @Override + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + int bottom = insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetBottom() : 0; + if (isPaddingRelative()) { + setPaddingRelative(getPaddingStart(), getPaddingTop(), getPaddingEnd(), bottom); + } else { + setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), bottom); + } + return insets; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index 1361cc424422..bb929dd16310 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection; + import android.annotation.ColorInt; import android.content.Context; import android.content.res.Configuration; @@ -26,6 +28,7 @@ import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.TypedValue; import android.view.DisplayCutout; +import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; @@ -40,6 +43,7 @@ import com.android.systemui.BatteryMeterView; import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; +import com.android.systemui.ScreenDecorations; import com.android.systemui.qs.QSPanel; import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager; import com.android.systemui.statusbar.policy.BatteryController; @@ -258,10 +262,13 @@ public class KeyguardStatusBarView extends RelativeLayout updateLayoutParamsNoCutout(); } + Rect bounds = new Rect(); + boundsFromDirection(dc, Gravity.TOP, bounds); + mCutoutSpace.setVisibility(View.VISIBLE); RelativeLayout.LayoutParams lp = (LayoutParams) mCutoutSpace.getLayoutParams(); - lp.width = dc.getBoundingRect().width(); - lp.height = dc.getBoundingRect().height(); + lp.width = bounds.width(); + lp.height = bounds.height(); lp.addRule(RelativeLayout.CENTER_IN_PARENT); lp = (LayoutParams) mCarrierLabel.getLayoutParams(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index ca5a3503b548..03efbb29e88a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -50,6 +50,7 @@ import android.view.MotionEvent; import android.view.Surface; import android.view.View; import android.view.ViewGroup; +import android.view.WindowInsets; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.widget.FrameLayout; @@ -1035,6 +1036,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); + requestApplyInsets(); reorient(); onPluginDisconnected(null); // Create default gesture helper Dependency.get(PluginManager.class).addPluginListener(this, @@ -1112,6 +1114,13 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav pw.println(" }"); } + @Override + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + setPadding(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), + insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()); + return super.onApplyWindowInsets(insets); + } + private static void dumpButton(PrintWriter pw, String caption, ButtonDispatcher button) { pw.print(" " + caption + ": "); if (button == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index cf5c1c0d7934..5076404a5e4e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.phone; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection; + import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; @@ -25,6 +27,7 @@ import android.graphics.Rect; import android.util.AttributeSet; import android.util.EventLog; import android.view.DisplayCutout; +import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; @@ -299,8 +302,12 @@ public class PhoneStatusBarView extends PanelBar { mCutoutSpace.setVisibility(View.VISIBLE); LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mCutoutSpace.getLayoutParams(); - lp.width = mDisplayCutout.getBoundingRect().width(); - lp.height = mDisplayCutout.getBoundingRect().height(); + + Rect bounds = new Rect(); + boundsFromDirection(mDisplayCutout, Gravity.TOP, bounds); + + lp.width = bounds.width(); + lp.height = bounds.height(); } private void updateSafeInsets() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 71376a553cdf..f8b4e0768ceb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -126,6 +126,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, private float mExpansionFraction = 1f; private boolean mDarkenWhileDragging; + private boolean mExpansionAffectsAlpha = true; protected boolean mAnimateChange; private boolean mUpdatePending; private boolean mTracking; @@ -381,6 +382,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, } private void applyExpansionToAlpha() { + if (!mExpansionAffectsAlpha) { + return; + } + if (mState == ScrimState.UNLOCKED) { // Darken scrim as you pull down the shade when unlocked float behindFraction = getInterpolatedFraction(); @@ -912,6 +917,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, mScreenOn = false; } + public void setExpansionAffectsAlpha(boolean expansionAffectsAlpha) { + mExpansionAffectsAlpha = expansionAffectsAlpha; + } + public interface Callback { default void onStart() { } 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 a305bfc7b0ab..5c7752401b83 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -3688,6 +3688,7 @@ public class StatusBar extends SystemUI implements DemoMode, public void finishKeyguardFadingAway() { mKeyguardFadingAway = false; mKeyguardMonitor.notifyKeyguardDoneFading(); + mScrimController.setExpansionAffectsAlpha(true); } // TODO: Move this to NotificationLockscreenUserManager. @@ -4618,6 +4619,10 @@ public class StatusBar extends SystemUI implements DemoMode, final boolean wakeAndUnlocking = mFingerprintUnlockController.getMode() == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK; + // Do not animate the scrim expansion when it's triggered by the fingerprint sensor. + mScrimController.setExpansionAffectsAlpha(mFingerprintUnlockController.getMode() + != FingerprintUnlockController.MODE_UNLOCK); + if (mBouncerShowing) { mScrimController.transitionTo( mIsOccluded ? ScrimState.BOUNCER_OCCLUDED : ScrimState.BOUNCER); diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index fe21f8731d9a..22bf98378ccb 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -23,6 +23,8 @@ import static android.media.AudioManager.RINGER_MODE_NORMAL; import static android.media.AudioManager.RINGER_MODE_SILENT; import static android.media.AudioManager.RINGER_MODE_VIBRATE; import static android.media.AudioManager.STREAM_ACCESSIBILITY; +import static android.view.View.GONE; +import static android.view.View.VISIBLE; import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED; @@ -81,6 +83,7 @@ import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.VolumeDialogController.State; import com.android.systemui.plugins.VolumeDialogController.StreamState; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; import java.io.PrintWriter; import java.util.ArrayList; @@ -102,6 +105,7 @@ public class VolumeDialogImpl implements VolumeDialog { private final Context mContext; private final H mHandler = new H(); private final VolumeDialogController mController; + private final DeviceProvisionedController mDeviceProvisionedController; private Window mWindow; private CustomDialog mDialog; @@ -109,6 +113,7 @@ public class VolumeDialogImpl implements VolumeDialog { private ViewGroup mDialogRowsView; private ViewGroup mRinger; private ImageButton mRingerIcon; + private View mSettingsView; private ImageButton mSettingsIcon; private ImageView mZenIcon; private final List<VolumeRow> mRows = new ArrayList<>(); @@ -139,6 +144,7 @@ public class VolumeDialogImpl implements VolumeDialog { mAccessibilityMgr = Dependency.get(AccessibilityManagerWrapper.class); mActiveSliderTint = ColorStateList.valueOf(Utils.getColorAccent(mContext)); mInactiveSliderTint = loadColorStateList(R.color.volume_slider_inactive); + mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); } public void init(int windowType, Callback callback) { @@ -212,6 +218,7 @@ public class VolumeDialogImpl implements VolumeDialog { mRinger = mDialog.findViewById(R.id.ringer); mRingerIcon = mRinger.findViewById(R.id.ringer_icon); mZenIcon = mRinger.findViewById(R.id.dnd_icon); + mSettingsView = mDialog.findViewById(R.id.settings_container); mSettingsIcon = mDialog.findViewById(R.id.settings); if (mRows.isEmpty()) { @@ -387,6 +394,8 @@ public class VolumeDialogImpl implements VolumeDialog { } public void initSettingsH() { + mSettingsView.setVisibility( + mDeviceProvisionedController.isDeviceProvisioned() ? VISIBLE : GONE); mSettingsIcon.setOnClickListener(v -> { Intent intent = new Intent(Settings.ACTION_SOUND_SETTINGS); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); @@ -607,7 +616,7 @@ public class VolumeDialogImpl implements VolumeDialog { * @param enable whether to enable volume row views and hide dnd icon */ private void enableVolumeRowViewsH(VolumeRow row, boolean enable) { - row.dndIcon.setVisibility(enable ? View.GONE : View.VISIBLE); + row.dndIcon.setVisibility(enable ? GONE : VISIBLE); } /** @@ -617,7 +626,7 @@ public class VolumeDialogImpl implements VolumeDialog { */ private void enableRingerViewsH(boolean enable) { mRingerIcon.setEnabled(enable); - mZenIcon.setVisibility(enable ? View.GONE : View.VISIBLE); + mZenIcon.setVisibility(enable ? GONE : VISIBLE); } private void trimObsoleteH() { @@ -797,7 +806,7 @@ public class VolumeDialogImpl implements VolumeDialog { } final int progress = row.slider.getProgress(); final int level = getImpliedLevel(row.slider, progress); - final boolean rowVisible = row.view.getVisibility() == View.VISIBLE; + final boolean rowVisible = row.view.getVisibility() == VISIBLE; final boolean inGracePeriod = (SystemClock.uptimeMillis() - row.userAttempt) < USER_ATTEMPT_GRACE_PERIOD; mHandler.removeMessages(H.RECHECK, row); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index bfa469eba99f..6fbc0d710cc4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -206,6 +206,25 @@ public class ScrimControllerTest extends SysuiTestCase { } @Test + public void panelExpansionAffectsAlpha() { + mScrimController.setPanelExpansion(0f); + mScrimController.setPanelExpansion(0.5f); + mScrimController.transitionTo(ScrimState.UNLOCKED); + mScrimController.finishAnimationsImmediately(); + + final float scrimAlpha = mScrimBehind.getViewAlpha(); + mScrimController.setExpansionAffectsAlpha(false); + mScrimController.setPanelExpansion(0.8f); + Assert.assertEquals("Scrim opacity shouldn't change when setExpansionAffectsAlpha " + + "is false", scrimAlpha, mScrimBehind.getViewAlpha(), 0.01f); + + mScrimController.setExpansionAffectsAlpha(true); + mScrimController.setPanelExpansion(0.1f); + Assert.assertNotEquals("Scrim opacity should change when setExpansionAffectsAlpha " + + "is true", scrimAlpha, mScrimBehind.getViewAlpha(), 0.01f); + } + + @Test public void transitionToUnlockedFromAod() { // Simulate unlock with fingerprint mScrimController.transitionTo(ScrimState.AOD); diff --git a/packages/overlays/SysuiDarkThemeOverlay/res/values/styles.xml b/packages/overlays/SysuiDarkThemeOverlay/res/values/styles.xml index ddac1062a391..41a294092648 100644 --- a/packages/overlays/SysuiDarkThemeOverlay/res/values/styles.xml +++ b/packages/overlays/SysuiDarkThemeOverlay/res/values/styles.xml @@ -7,5 +7,6 @@ <item name="android:colorAccent">@*android:color/accent_device_default_dark</item> <item name="android:colorControlNormal">?android:attr/textColorPrimary</item> <item name="android:colorBackgroundFloating">@*android:color/material_grey_900</item> + <item name="android:panelColorBackground">@*android:color/material_grey_800</item> </style> </resources>
\ No newline at end of file diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index 4be7287ae7a5..07012d87bbb4 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -5363,7 +5363,6 @@ message MetricsEvent { // OS: P ACTION_PANEL_VIEW_EXPAND = 1328; - // FIELD: Rotation of the device // CATEGORY: GLOBAL_SYSTEM_UI // OS: P diff --git a/proto/src/task_snapshot.proto b/proto/src/task_snapshot.proto index c9d5c272d48c..490a59e770ec 100644 --- a/proto/src/task_snapshot.proto +++ b/proto/src/task_snapshot.proto @@ -27,4 +27,5 @@ int32 inset_top = 3; int32 inset_right = 4; int32 inset_bottom = 5; + bool is_real_snapshot = 6; }
\ No newline at end of file diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index b0b95868600f..ecd47e8dd39e 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -3129,6 +3129,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub boolean activeWindowGone = true; final int windowCount = windows.size(); + + // We'll clear accessibility focus if the window with focus is no longer visible to + // accessibility services + boolean shouldClearAccessibilityFocus = + mAccessibilityFocusedWindowId != INVALID_WINDOW_ID; if (windowCount > 0) { for (int i = 0; i < windowCount; i++) { WindowInfo windowInfo = windows.get(i); @@ -3166,6 +3171,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } if (window.getId() == mAccessibilityFocusedWindowId) { window.setAccessibilityFocused(true); + shouldClearAccessibilityFocus = false; } } } @@ -3176,6 +3182,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub for (int i = oldWindowCount - 1; i >= 0; i--) { oldWindowList.remove(i).recycle(); } + + if (shouldClearAccessibilityFocus) { + clearAccessibilityFocus(mAccessibilityFocusedWindowId); + } } private void sendEventsForChangedWindowsLocked(List<AccessibilityWindowInfo> oldWindows, diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 266b2d791991..a5339e0e6549 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -59,9 +59,7 @@ import android.service.autofill.UserData; import android.text.TextUtils; import android.text.TextUtils.SimpleStringSplitter; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.LocalLog; -import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -73,6 +71,7 @@ import android.view.autofill.IAutoFillManager; import android.view.autofill.IAutoFillManagerClient; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; import com.android.internal.os.BackgroundThread; import com.android.internal.os.IResultReceiver; @@ -88,8 +87,8 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.Objects; -import java.util.Set; /** * Entry point service for autofill management. @@ -105,6 +104,13 @@ public final class AutofillManagerService extends SystemService { static final String RECEIVER_BUNDLE_EXTRA_SESSIONS = "sessions"; private static final char COMPAT_PACKAGE_DELIMITER = ':'; + private static final char COMPAT_PACKAGE_URL_IDS_DELIMITER = ','; + private static final char COMPAT_PACKAGE_URL_IDS_BLOCK_BEGIN = '['; + private static final char COMPAT_PACKAGE_URL_IDS_BLOCK_END = ']'; + + // TODO(b/74445943): temporary work around until P Development Preview 3 is branched + private static final List<String> DEFAULT_BUTTONS = Arrays.asList("url_bar", + "location_bar_edit_text"); private final Context mContext; private final AutoFillUI mUi; @@ -326,7 +332,7 @@ public final class AutofillManagerService extends SystemService { if (service == null) { service = new AutofillManagerServiceImpl(mContext, mLock, mRequestsHistory, mUiLatencyHistory, mWtfHistory, resolvedUserId, mUi, - mDisabledUsers.get(resolvedUserId)); + mAutofillCompatState, mDisabledUsers.get(resolvedUserId)); mServicesCache.put(userId, service); addCompatibilityModeRequestsLocked(service, userId); } @@ -544,23 +550,24 @@ public final class AutofillManagerService extends SystemService { private void addCompatibilityModeRequestsLocked(@NonNull AutofillManagerServiceImpl service , int userId) { mAutofillCompatState.reset(); - final ArrayMap<String, Pair<Long, String>> compatPackages = + final ArrayMap<String, Long> compatPackages = service.getCompatibilityPackagesLocked(); if (compatPackages == null || compatPackages.isEmpty()) { return; } - final Set<String> whiteListedPackages = getWhitelistedCompatModePackages(); + + final Map<String, String[]> whiteListedPackages = getWhitelistedCompatModePackages(); final int compatPackageCount = compatPackages.size(); for (int i = 0; i < compatPackageCount; i++) { final String packageName = compatPackages.keyAt(i); - if (whiteListedPackages == null || !whiteListedPackages.contains(packageName)) { + if (whiteListedPackages == null || !whiteListedPackages.containsKey(packageName)) { Slog.w(TAG, "Ignoring not whitelisted compat package " + packageName); continue; } - final Long maxVersionCode = compatPackages.valueAt(i).first; + final Long maxVersionCode = compatPackages.valueAt(i); if (maxVersionCode != null) { mAutofillCompatState.addCompatibilityModeRequest(packageName, - maxVersionCode, userId); + maxVersionCode, whiteListedPackages.get(packageName), userId); } } } @@ -571,16 +578,60 @@ public final class AutofillManagerService extends SystemService { Settings.Global.AUTOFILL_COMPAT_ALLOWED_PACKAGES); } - private @Nullable Set<String> getWhitelistedCompatModePackages() { - final String compatPackagesSetting = getWhitelistedCompatModePackagesFromSettings(); - if (TextUtils.isEmpty(compatPackagesSetting)) { + @Nullable + private Map<String, String[]> getWhitelistedCompatModePackages() { + return getWhitelistedCompatModePackages(getWhitelistedCompatModePackagesFromSettings()); + } + + @Nullable + @VisibleForTesting + static Map<String, String[]> getWhitelistedCompatModePackages(String setting) { + if (TextUtils.isEmpty(setting)) { return null; } - final Set<String> compatPackages = new ArraySet<>(); + + final ArrayMap<String, String[]> compatPackages = new ArrayMap<>(); final SimpleStringSplitter splitter = new SimpleStringSplitter(COMPAT_PACKAGE_DELIMITER); - splitter.setString(compatPackagesSetting); + splitter.setString(setting); while (splitter.hasNext()) { - compatPackages.add(splitter.next()); + final String packageBlock = splitter.next(); + final int urlBlockIndex = packageBlock.indexOf(COMPAT_PACKAGE_URL_IDS_BLOCK_BEGIN); + final String packageName; + final List<String> urlBarIds; + if (urlBlockIndex == -1) { + packageName = packageBlock; + urlBarIds = DEFAULT_BUTTONS; // TODO(b/74445943): back to null + } else { + if (packageBlock.charAt(packageBlock.length() - 1) + != COMPAT_PACKAGE_URL_IDS_BLOCK_END) { + Slog.w(TAG, "Ignoring entry '" + packageBlock + "' on '" + setting + + "'because it does not end on '" + COMPAT_PACKAGE_URL_IDS_BLOCK_END + + "'"); + continue; + } + packageName = packageBlock.substring(0, urlBlockIndex); + urlBarIds = new ArrayList<>(); + final String urlBarIdsBlock = + packageBlock.substring(urlBlockIndex + 1, packageBlock.length() - 1); + if (sVerbose) { + Slog.v(TAG, "pkg:" + packageName + ": block:" + packageBlock + ": urls:" + + urlBarIds + ": block:" + urlBarIdsBlock + ":"); + } + final SimpleStringSplitter splitter2 = + new SimpleStringSplitter(COMPAT_PACKAGE_URL_IDS_DELIMITER); + splitter2.setString(urlBarIdsBlock); + while (splitter2.hasNext()) { + final String urlBarId = splitter2.next(); + urlBarIds.add(urlBarId); + } + } + if (urlBarIds == null) { + compatPackages.put(packageName, null); + } else { + final String[] urlBarIdsArray = new String[urlBarIds.size()]; + urlBarIds.toArray(urlBarIdsArray); + compatPackages.put(packageName, urlBarIdsArray); + } } return compatPackages; } @@ -598,13 +649,41 @@ public final class AutofillManagerService extends SystemService { return mAutofillCompatState.isCompatibilityModeRequested( packageName, versionCode, userId); } + } - private static final class AutofillCompatState { + /** + * Compatibility mode metadata per package. + */ + private static final class PackageCompatState { + private final long maxVersionCode; + private final String[] urlBarResourceIds; + + PackageCompatState(long maxVersionCode, String[] urlBarResourceIds) { + this.maxVersionCode = maxVersionCode; + this.urlBarResourceIds = urlBarResourceIds; + } + + @Override + public String toString() { + return "PackageCompatState: [maxVersionCode=" + maxVersionCode + + ", urlBarResourceIds=" + Arrays.toString(urlBarResourceIds) + "]"; + } + } + + /** + * Compatibility mode metadata associated with all services. + * + * <p>This object is defined here instead of on each {@link AutofillManagerServiceImpl} because + * it cannot hold a lock on the main lock when + * {@link AutofillCompatState#isCompatibilityModeRequested(String, long, int)} is called by + * external services. + */ + static final class AutofillCompatState { private final Object mLock = new Object(); @GuardedBy("mLock") - private SparseArray<ArrayMap<String, Long>> mUserSpecs; + private SparseArray<ArrayMap<String, PackageCompatState>> mUserSpecs; boolean isCompatibilityModeRequested(@NonNull String packageName, long versionCode, @UserIdInt int userId) { @@ -612,30 +691,49 @@ public final class AutofillManagerService extends SystemService { if (mUserSpecs == null) { return false; } - final ArrayMap<String, Long> userSpec = mUserSpecs.get(userId); + final ArrayMap<String, PackageCompatState> userSpec = mUserSpecs.get(userId); if (userSpec == null) { return false; } - final Long maxVersionCode = userSpec.get(packageName); - if (maxVersionCode == null) { + final PackageCompatState metadata = userSpec.get(packageName); + if (metadata == null) { return false; } - return versionCode <= maxVersionCode; + return versionCode <= metadata.maxVersionCode; + } + } + + @Nullable + String[] getUrlBarResourceIds(@NonNull String packageName, @UserIdInt int userId) { + synchronized (mLock) { + if (mUserSpecs == null) { + return null; + } + final ArrayMap<String, PackageCompatState> userSpec = mUserSpecs.get(userId); + if (userSpec == null) { + return null; + } + final PackageCompatState metadata = userSpec.get(packageName); + if (metadata == null) { + return null; + } + return metadata.urlBarResourceIds; } } void addCompatibilityModeRequest(@NonNull String packageName, - long versionCode, @UserIdInt int userId) { + long versionCode, @Nullable String[] urlBarResourceIds, @UserIdInt int userId) { synchronized (mLock) { if (mUserSpecs == null) { mUserSpecs = new SparseArray<>(); } - ArrayMap<String, Long> userSpec = mUserSpecs.get(userId); + ArrayMap<String, PackageCompatState> userSpec = mUserSpecs.get(userId); if (userSpec == null) { userSpec = new ArrayMap<>(); mUserSpecs.put(userId, userSpec); } - userSpec.put(packageName, versionCode); + userSpec.put(packageName, + new PackageCompatState(versionCode, urlBarResourceIds)); } } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 8622dbe820a6..54ea3ba4546b 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -64,7 +64,6 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.DebugUtils; import android.util.LocalLog; -import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; @@ -78,12 +77,12 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.server.LocalServices; +import com.android.server.autofill.AutofillManagerService.AutofillCompatState; import com.android.server.autofill.ui.AutoFillUI; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Random; /** @@ -164,12 +163,15 @@ final class AutofillManagerServiceImpl { @GuardedBy("mLock") private FillEventHistory mEventHistory; + /** Shared instance, doesn't need to be logged */ + private final AutofillCompatState mAutofillCompatState; + /** When was {@link PruneTask} last executed? */ private long mLastPrune = 0; AutofillManagerServiceImpl(Context context, Object lock, LocalLog requestsHistory, LocalLog uiLatencyHistory, LocalLog wtfHistory, int userId, AutoFillUI ui, - boolean disabled) { + AutofillCompatState autofillCompatState, boolean disabled) { mContext = context; mLock = lock; mRequestsHistory = requestsHistory; @@ -178,6 +180,7 @@ final class AutofillManagerServiceImpl { mUserId = userId; mUi = ui; mFieldClassificationStrategy = new FieldClassificationStrategy(context, userId); + mAutofillCompatState = autofillCompatState; updateLocked(disabled); } @@ -208,14 +211,9 @@ final class AutofillManagerServiceImpl { } - @GuardedBy("mLock") @Nullable - String getUrlBarResourceIdForCompatModeLocked(@NonNull String packageName) { - if (mInfo == null) { - Slog.w(TAG, "getUrlBarResourceIdForCompatModeLocked(): no mInfo"); - return null; - } - return mInfo.getUrlBarResourceId(packageName); + String[] getUrlBarResourceIdsForCompatMode(@NonNull String packageName) { + return mAutofillCompatState.getUrlBarResourceIds(packageName, mUserId); } @Nullable @@ -893,7 +891,7 @@ final class AutofillManagerServiceImpl { pw.print(prefix); pw.print("Field classification enabled: "); pw.println(isFieldClassificationEnabledLocked()); pw.print(prefix); pw.print("Compat pkgs: "); - final ArrayMap<String, Pair<Long, String>> compatPkgs = getCompatibilityPackagesLocked(); + final ArrayMap<String, Long> compatPkgs = getCompatibilityPackagesLocked(); if (compatPkgs == null) { pw.println("N/A"); } else { @@ -1022,7 +1020,7 @@ final class AutofillManagerServiceImpl { } @GuardedBy("mLock") - @Nullable ArrayMap<String, Pair<Long, String>> getCompatibilityPackagesLocked() { + @Nullable ArrayMap<String, Long> getCompatibilityPackagesLocked() { if (mInfo != null) { return mInfo.getCompatibilityPackages(); } diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java index 232dfdcf045b..5c41f3f869ce 100644 --- a/services/autofill/java/com/android/server/autofill/Helper.java +++ b/services/autofill/java/com/android/server/autofill/Helper.java @@ -30,6 +30,7 @@ import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.util.ArrayUtils; import java.io.PrintWriter; import java.util.ArrayList; @@ -168,21 +169,24 @@ public final class Helper { /** * Sanitize the {@code webDomain} property of the URL bar node on compat mode. + * + * @param structure Assist structure + * @param urlBarIds list of ids; only the first id found will be sanitized. */ public static void sanitizeUrlBar(@NonNull AssistStructure structure, - @NonNull String urlBarId) { + @NonNull String[] urlBarIds) { final ViewNode urlBarNode = findViewNode(structure, (node) -> { - return urlBarId.equals(node.getIdEntry()); + return ArrayUtils.contains(urlBarIds, node.getIdEntry()); }); if (urlBarNode != null) { final String domain = urlBarNode.getText().toString(); if (domain.isEmpty()) { - if (sDebug) Slog.d(TAG, "sanitizeUrlBar(): empty on " + urlBarId); + if (sDebug) Slog.d(TAG, "sanitizeUrlBar(): empty on " + urlBarNode.getIdEntry()); return; } urlBarNode.setWebDomain(domain); if (sDebug) { - Slog.d(TAG, "sanitizeUrlBar(): id=" + urlBarId + ", domain=" + Slog.d(TAG, "sanitizeUrlBar(): id=" + urlBarNode.getIdEntry() + ", domain=" + urlBarNode.getWebDomain()); } } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 4abf0f8e8f1a..26598a2e24bc 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -274,11 +274,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } if (mCompatMode) { // Sanitize URL bar, if needed - final String urlBarId = mService.getUrlBarResourceIdForCompatModeLocked( + final String[] urlBarIds = mService.getUrlBarResourceIdsForCompatMode( mComponentName.getPackageName()); - if (sDebug) Slog.d(TAG, "url_bar in compat mode: " + urlBarId); - if (urlBarId != null) { - Helper.sanitizeUrlBar(structure, urlBarId); + if (sDebug) { + Slog.d(TAG, "url_bars in compat mode: " + Arrays.toString(urlBarIds)); + } + if (urlBarIds != null) { + Helper.sanitizeUrlBar(structure, urlBarIds); } } structure.sanitizeForParceling(true); diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 3927ebd593bd..62e82a064db2 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -394,8 +394,10 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { + " callback.asBinder=" + callback.asBinder()); } + // TODO(b/70041899): Find a way to make this work for carrier-privileged callers. if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState( - mContext, callingPackage, "addOnSubscriptionsChangedListener")) { + mContext, SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage, + "addOnSubscriptionsChangedListener")) { return; } @@ -686,8 +688,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private boolean canReadPhoneState(String callingPackage, String message) { try { + // TODO(b/70041899): Find a way to make this work for carrier-privileged callers. return TelephonyPermissions.checkCallingOrSelfReadPhoneState( - mContext, callingPackage, message); + mContext, SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage, message); } catch (SecurityException e) { return false; } @@ -1735,8 +1738,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } if ((events & ENFORCE_PHONE_STATE_PERMISSION_MASK) != 0) { - if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState( - mContext, callingPackage, message)) { + // TODO(b/70041899): Find a way to make this work for carrier-privileged callers. + if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, + SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage, message)) { return false; } } diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 59f027aed7b8..e38be67f7b69 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -1750,8 +1750,11 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo // this when there is an activity waiting to become translucent as the extra binder // calls will lead to noticeable jank. A later call to // ActivityStack#ensureActivitiesVisibleLocked will bring the activity to the proper - // paused state. - if (isState(STOPPED, STOPPING) && stack.mTranslucentActivityWaiting == null) { + // paused state. We also avoid doing this for the activity the stack supervisor + // considers the resumed activity, as normal means will bring the activity from STOPPED + // to RESUMED. Adding PAUSING in this scenario will lead to double lifecycles. + if (isState(STOPPED, STOPPING) && stack.mTranslucentActivityWaiting == null + && mStackSupervisor.getResumedActivityLocked() != this) { // Capture reason before state change final String reason = getLifecycleDescription("makeVisibleIfNeeded"); diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 869c635b1c08..185897a93381 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -3375,11 +3375,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D stack.goToSleepIfPossible(false /* shuttingDown */); } else { stack.awakeFromSleepingLocked(); - if (isFocusedStack(stack) - && !mKeyguardController.isKeyguardActive(display.mDisplayId)) { - // If there is no keyguard on this display - resume immediately. Otherwise - // we'll wait for keyguard visibility callback and resume while ensuring - // activities visibility + if (isFocusedStack(stack) && !mKeyguardController.isKeyguardLocked()) { + // If the keyguard is unlocked - resume immediately. + // It is possible that the display will not be awake at the time we + // process the keyguard going away, which can happen before the sleep token + // is released. As a result, it is important we resume the activity here. resumeFocusedStackTopActivityLocked(); } } diff --git a/services/core/java/com/android/server/am/KeyguardController.java b/services/core/java/com/android/server/am/KeyguardController.java index 6b8b38073998..72882de21e09 100644 --- a/services/core/java/com/android/server/am/KeyguardController.java +++ b/services/core/java/com/android/server/am/KeyguardController.java @@ -86,16 +86,8 @@ class KeyguardController { * display, false otherwise */ boolean isKeyguardShowing(int displayId) { - return isKeyguardActive(displayId) && !mKeyguardGoingAway; - } - - /** - * @return true if Keyguard is showing and not occluded. We ignore whether it is going away or - * not here. - */ - boolean isKeyguardActive(int displayId) { - return mKeyguardShowing && (displayId == DEFAULT_DISPLAY ? !mOccluded - : displayId == mSecondaryDisplayShowing); + return mKeyguardShowing && !mKeyguardGoingAway && + (displayId == DEFAULT_DISPLAY ? !mOccluded : displayId == mSecondaryDisplayShowing); } /** diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 1e74263cb24a..f577d09cdb9f 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -39,6 +39,7 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothHearingAid; import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -256,6 +257,7 @@ public class AudioService extends IAudioService.Stub private static final int MSG_SET_A2DP_SINK_CONNECTION_STATE = 102; private static final int MSG_A2DP_DEVICE_CONFIG_CHANGE = 103; private static final int MSG_DISABLE_AUDIO_FOR_UID = 104; + private static final int MSG_SET_HEARING_AID_CONNECTION_STATE = 105; // end of messages handled under wakelock private static final int BTA2DP_DOCK_TIMEOUT_MILLIS = 8000; @@ -265,6 +267,8 @@ public class AudioService extends IAudioService.Stub // retry delay in case of failure to indicate system ready to AudioFlinger private static final int INDICATE_SYSTEM_READY_RETRY_DELAY_MS = 1000; + private static final int BT_HEARING_AID_GAIN_MIN = -128; + /** @see AudioSystemThread */ private AudioSystemThread mAudioSystemThread; /** @see AudioHandler */ @@ -573,7 +577,7 @@ public class AudioService extends IAudioService.Stub AudioSystem.DEVICE_OUT_HDMI_ARC | AudioSystem.DEVICE_OUT_SPDIF | AudioSystem.DEVICE_OUT_AUX_LINE; - int mFullVolumeDevices = 0; + int mFullVolumeDevices = AudioSystem.DEVICE_OUT_HEARING_AID; private final boolean mMonitorRotation; @@ -590,6 +594,10 @@ public class AudioService extends IAudioService.Stub private final MediaFocusControl mMediaFocusControl; + // Reference to BluetoothA2dp to query for volume. + private BluetoothHearingAid mHearingAid; + // lock always taken synchronized on mConnectedDevices + private final Object mHearingAidLock = new Object(); // Reference to BluetoothA2dp to query for AbsoluteVolume. private BluetoothA2dp mA2dp; // lock always taken synchronized on mConnectedDevices @@ -849,6 +857,8 @@ public class AudioService extends IAudioService.Stub if (adapter != null) { adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, BluetoothProfile.A2DP); + adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, + BluetoothProfile.HEARING_AID); } if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) { @@ -1607,6 +1617,20 @@ public class AudioService extends IAudioService.Stub } } + // Check if volume update should be send to Hearing Aid + if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) { + synchronized (mHearingAidLock) { + if (mHearingAid != null) { + //hearing aid expect volume value in range -128dB to 0dB + int gainDB = (int)AudioSystem.getStreamVolumeDB(streamType, newIndex/10, + AudioSystem.DEVICE_OUT_HEARING_AID); + if (gainDB < BT_HEARING_AID_GAIN_MIN) + gainDB = BT_HEARING_AID_GAIN_MIN; + mHearingAid.setVolume(gainDB); + } + } + } + // Check if volume update should be sent to Hdmi system audio. if (streamTypeAlias == AudioSystem.STREAM_MUSIC) { setSystemAudioVolume(oldIndex, newIndex, getStreamMaxVolume(streamType), flags); @@ -1852,6 +1876,19 @@ public class AudioService extends IAudioService.Stub } } + if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) { + synchronized (mHearingAidLock) { + if (mHearingAid != null) { + //hearing aid expect volume value in range -128dB to 0dB + int gainDB = (int)AudioSystem.getStreamVolumeDB(streamType, index/10, AudioSystem.DEVICE_OUT_HEARING_AID); + if (gainDB < BT_HEARING_AID_GAIN_MIN) + gainDB = BT_HEARING_AID_GAIN_MIN; + mHearingAid.setVolume(gainDB); + + } + } + } + if (streamTypeAlias == AudioSystem.STREAM_MUSIC) { setSystemAudioVolume(oldIndex, index, getStreamMaxVolume(streamType), flags); } @@ -3609,6 +3646,30 @@ public class AudioService extends IAudioService.Stub } break; + case BluetoothProfile.HEARING_AID: + synchronized (mConnectedDevices) { + synchronized (mHearingAidLock) { + mHearingAid = (BluetoothHearingAid) proxy; + deviceList = mHearingAid.getConnectedDevices(); + if (deviceList.size() > 0) { + btDevice = deviceList.get(0); + int state = mHearingAid.getConnectionState(btDevice); + int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0; + int delay = checkSendBecomingNoisyIntent( + AudioSystem.DEVICE_OUT_HEARING_AID, intState, + AudioSystem.DEVICE_NONE); + queueMsgUnderWakeLock(mAudioHandler, + MSG_SET_HEARING_AID_CONNECTION_STATE, + state, + 0 /* arg2 unused */, + btDevice, + delay); + } + } + } + + break; + default: break; } @@ -3628,6 +3689,10 @@ public class AudioService extends IAudioService.Stub disconnectHeadset(); break; + case BluetoothProfile.HEARING_AID: + disconnectHearingAid(); + break; + default: break; } @@ -3638,6 +3703,7 @@ public class AudioService extends IAudioService.Stub disconnectA2dp(); disconnectA2dpSink(); disconnectHeadset(); + disconnectHearingAid(); } void disconnectA2dp() { @@ -3689,6 +3755,29 @@ public class AudioService extends IAudioService.Stub } } + void disconnectHearingAid() { + synchronized (mConnectedDevices) { + synchronized (mHearingAidLock) { + ArraySet<String> toRemove = null; + // Disconnect ALL DEVICE_OUT_HEARING_AID devices + for (int i = 0; i < mConnectedDevices.size(); i++) { + DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i); + if (deviceSpec.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) { + toRemove = toRemove != null ? toRemove : new ArraySet<String>(); + toRemove.add(deviceSpec.mDeviceAddress); + } + } + if (toRemove != null) { + int delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_HEARING_AID, + 0, AudioSystem.DEVICE_NONE); + for (int i = 0; i < toRemove.size(); i++) { + makeHearingAidDeviceUnavailable(toRemove.valueAt(i) /*, delay*/); + } + } + } + } + } + private void onCheckMusicActive(String caller) { synchronized (mSafeMediaVolumeState) { if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) { @@ -4200,7 +4289,8 @@ public class AudioService extends IAudioService.Stub handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time); if (msg == MSG_SET_WIRED_DEVICE_CONNECTION_STATE || msg == MSG_SET_A2DP_SRC_CONNECTION_STATE || - msg == MSG_SET_A2DP_SINK_CONNECTION_STATE) { + msg == MSG_SET_A2DP_SINK_CONNECTION_STATE || + msg == MSG_SET_HEARING_AID_CONNECTION_STATE) { mLastDeviceConnectMsgTime = time; } } @@ -4303,6 +4393,33 @@ public class AudioService extends IAudioService.Stub @Override public void setHearingAidDeviceConnectionState(BluetoothDevice device, int state) { + Log.i(TAG, "setBluetoothHearingAidDeviceConnectionState"); + + setBluetoothHearingAidDeviceConnectionState( + device, state, false /* suppressNoisyIntent */, AudioSystem.DEVICE_NONE); + } + + public int setBluetoothHearingAidDeviceConnectionState( + BluetoothDevice device, int state, boolean suppressNoisyIntent, + int musicDevice) + { + int delay; + synchronized (mConnectedDevices) { + if (!suppressNoisyIntent) { + int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0; + delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_HEARING_AID, + intState, musicDevice); + } else { + delay = 0; + } + queueMsgUnderWakeLock(mAudioHandler, + MSG_SET_HEARING_AID_CONNECTION_STATE, + state, + 0 /* arg2 unused */, + device, + delay); + } + return delay; } public int setBluetoothA2dpDeviceConnectionState(BluetoothDevice device, int state, int profile) @@ -5244,6 +5361,11 @@ public class AudioService extends IAudioService.Stub mAudioEventWakeLock.release(); break; + case MSG_SET_HEARING_AID_CONNECTION_STATE: + onSetHearingAidConnectionState((BluetoothDevice)msg.obj, msg.arg1); + mAudioEventWakeLock.release(); + break; + case MSG_A2DP_DEVICE_CONFIG_CHANGE: onBluetoothA2dpDeviceConfigChange((BluetoothDevice)msg.obj); mAudioEventWakeLock.release(); @@ -5436,14 +5558,8 @@ public class AudioService extends IAudioService.Stub AudioSystem.DEVICE_STATE_UNAVAILABLE, address, ""); mConnectedDevices.remove( makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address)); - synchronized (mCurAudioRoutes) { - // Remove A2DP routes as well - if (mCurAudioRoutes.bluetoothName != null) { - mCurAudioRoutes.bluetoothName = null; - sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES, - SENDMSG_NOOP, 0, 0, null, 0); - } - } + // Remove A2DP routes as well + setCurrentAudioRouteName(null); } // must be called synchronized on mConnectedDevices @@ -5479,6 +5595,28 @@ public class AudioService extends IAudioService.Stub } // must be called synchronized on mConnectedDevices + private void makeHearingAidDeviceAvailable(String address, String name, String eventSource) { + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID, + AudioSystem.DEVICE_STATE_AVAILABLE, address, name); + mConnectedDevices.put( + makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address), + new DeviceListSpec(AudioSystem.DEVICE_OUT_HEARING_AID, name, + address)); + sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE, + AudioSystem.DEVICE_OUT_HEARING_AID, 0, null, 0); + } + + // must be called synchronized on mConnectedDevices + private void makeHearingAidDeviceUnavailable(String address) { + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID, + AudioSystem.DEVICE_STATE_UNAVAILABLE, address, ""); + mConnectedDevices.remove( + makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address)); + // Remove Hearing Aid routes as well + setCurrentAudioRouteName(null); + } + + // must be called synchronized on mConnectedDevices private void cancelA2dpDeviceTimeout() { mAudioHandler.removeMessages(MSG_BTA2DP_DOCK_TIMEOUT); } @@ -5519,13 +5657,7 @@ public class AudioService extends IAudioService.Stub } else { makeA2dpDeviceUnavailableNow(address); } - synchronized (mCurAudioRoutes) { - if (mCurAudioRoutes.bluetoothName != null) { - mCurAudioRoutes.bluetoothName = null; - sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES, - SENDMSG_NOOP, 0, 0, null, 0); - } - } + setCurrentAudioRouteName(null); } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { if (btDevice.isBluetoothDock()) { // this could be a reconnection after a transient disconnection @@ -5541,14 +5673,7 @@ public class AudioService extends IAudioService.Stub } makeA2dpDeviceAvailable(address, btDevice.getName(), "onSetA2dpSinkConnectionState"); - synchronized (mCurAudioRoutes) { - String name = btDevice.getAliasName(); - if (!TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) { - mCurAudioRoutes.bluetoothName = name; - sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES, - SENDMSG_NOOP, 0, 0, null, 0); - } - } + setCurrentAudioRouteName(btDevice.getAliasName()); } } } @@ -5579,6 +5704,46 @@ public class AudioService extends IAudioService.Stub } } + private void onSetHearingAidConnectionState(BluetoothDevice btDevice, int state) + { + if (DEBUG_DEVICES) { + Log.d(TAG, "onSetHearingAidConnectionState btDevice=" + btDevice+", state=" + state); + } + if (btDevice == null) { + return; + } + String address = btDevice.getAddress(); + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } + + synchronized (mConnectedDevices) { + final String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, + btDevice.getAddress()); + final DeviceListSpec deviceSpec = mConnectedDevices.get(key); + boolean isConnected = deviceSpec != null; + + if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { + makeHearingAidDeviceUnavailable(address); + setCurrentAudioRouteName(null); + } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { + makeHearingAidDeviceAvailable(address, btDevice.getName(), + "onSetHearingAidConnectionState"); + setCurrentAudioRouteName(btDevice.getAliasName()); + } + } + } + + private void setCurrentAudioRouteName(String name){ + synchronized (mCurAudioRoutes) { + if (!TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) { + mCurAudioRoutes.bluetoothName = name; + sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES, + SENDMSG_NOOP, 0, 0, null, 0); + } + } + } + private void onBluetoothA2dpDeviceConfigChange(BluetoothDevice btDevice) { if (DEBUG_DEVICES) { @@ -5714,6 +5879,7 @@ public class AudioService extends IAudioService.Stub if (mAudioHandler.hasMessages(MSG_SET_A2DP_SRC_CONNECTION_STATE) || mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE) || + mAudioHandler.hasMessages(MSG_SET_HEARING_AID_CONNECTION_STATE) || mAudioHandler.hasMessages(MSG_SET_WIRED_DEVICE_CONNECTION_STATE)) { synchronized (mLastDeviceConnectMsgTime) { long time = SystemClock.uptimeMillis(); diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index b7385d85d296..0d8ec6d23089 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -404,7 +404,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { && SystemProperties.getBoolean(PROPERTY_EMULATOR_CIRCULAR, false))) { mInfo.flags |= DisplayDeviceInfo.FLAG_ROUND; } - mInfo.displayCutout = DisplayCutout.fromResources(res, mInfo.width); + mInfo.displayCutout = DisplayCutout.fromResources(res, mInfo.width, + mInfo.height); mInfo.type = Display.TYPE_BUILT_IN; mInfo.densityDpi = (int)(phys.density * 160 + 0.5f); mInfo.xDpi = phys.xDpi; diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java index c3259c312cd7..ac85484e7f74 100644 --- a/services/core/java/com/android/server/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java @@ -26,8 +26,10 @@ import android.app.ActivityManager; import android.app.ActivityManager.RunningAppProcessInfo; import android.app.AlarmManager; import android.app.AppOpsManager; +import android.app.IActivityManager; import android.app.PendingIntent; import android.app.SynchronousUserSwitchObserver; +import android.app.TaskStackListener; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -137,6 +139,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death @GuardedBy("this") private IBiometricsFingerprint mDaemon; private IStatusBarService mStatusBarService; + private final IActivityManager mActivityManager; private final PowerManager mPowerManager; private final AlarmManager mAlarmManager; private final UserManager mUserManager; @@ -215,6 +218,30 @@ public class FingerprintService extends SystemService implements IHwBinder.Death } }; + private final TaskStackListener mTaskStackListener = new TaskStackListener() { + @Override + public void onTaskStackChanged() { + try { + if (!(mCurrentClient instanceof AuthenticationClient)) { + return; + } + if (isKeyguard(mCurrentClient.getOwnerString())) { + return; // Keyguard is always allowed + } + List<ActivityManager.RunningTaskInfo> runningTasks = mActivityManager.getTasks(1); + if (!runningTasks.isEmpty()) { + if (runningTasks.get(0).topActivity.getPackageName() + != mCurrentClient.getOwnerString()) { + mCurrentClient.stop(false /* initiatedByClient */); + Slog.e(TAG, "Stopping background authentication"); + } + } + } catch (RemoteException e) { + Slog.e(TAG, "Unable to get running tasks", e); + } + } + }; + public FingerprintService(Context context) { super(context); mContext = context; @@ -230,6 +257,13 @@ public class FingerprintService extends SystemService implements IHwBinder.Death mFailedAttempts = new SparseIntArray(); mStatusBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + mActivityManager = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)) + .getService(); + try { + mActivityManager.registerTaskStackListener(mTaskStackListener); + } catch (RemoteException e) { + Slog.e(TAG, "Could not register task stack listener", e); + } } @Override diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 468ec591caa2..74ebf3e44616 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -102,6 +102,7 @@ import com.android.internal.util.Preconditions; import com.android.internal.widget.ICheckCredentialProgressCallback; import com.android.internal.widget.ILockSettings; import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.LockSettingsInternal; import com.android.internal.widget.VerifyCredentialResponse; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -436,6 +437,8 @@ public class LockSettingsService extends ILockSettings.Stub { mStrongAuthTracker.register(mStrongAuth); mSpManager = injector.getSyntheticPasswordManager(mStorage); + + LocalServices.addService(LockSettingsInternal.class, new LocalService()); } /** @@ -1041,14 +1044,10 @@ public class LockSettingsService extends ILockSettings.Stub { private boolean isUserSecure(int userId) { synchronized (mSpManager) { - try { - if (isSyntheticPasswordBasedCredentialLocked(userId)) { - long handle = getSyntheticPasswordHandleLocked(userId); - return mSpManager.getCredentialType(handle, userId) != - LockPatternUtils.CREDENTIAL_TYPE_NONE; - } - } catch (RemoteException e) { - // fall through + if (isSyntheticPasswordBasedCredentialLocked(userId)) { + long handle = getSyntheticPasswordHandleLocked(userId); + return mSpManager.getCredentialType(handle, userId) != + LockPatternUtils.CREDENTIAL_TYPE_NONE; } } return mStorage.hasCredential(userId); @@ -2305,7 +2304,7 @@ public class LockSettingsService extends ILockSettings.Stub { SyntheticPasswordManager.DEFAULT_HANDLE, userId); } - private boolean isSyntheticPasswordBasedCredentialLocked(int userId) throws RemoteException { + private boolean isSyntheticPasswordBasedCredentialLocked(int userId) { if (userId == USER_FRP) { final int type = mStorage.readPersistentDataBlock().type; return type == PersistentData.TYPE_SP || type == PersistentData.TYPE_SP_WEAVER; @@ -2318,7 +2317,7 @@ public class LockSettingsService extends ILockSettings.Stub { } @VisibleForTesting - protected boolean shouldMigrateToSyntheticPasswordLocked(int userId) throws RemoteException { + protected boolean shouldMigrateToSyntheticPasswordLocked(int userId) { long handle = getSyntheticPasswordHandleLocked(userId); // This is a global setting long enabled = getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, @@ -2326,7 +2325,7 @@ public class LockSettingsService extends ILockSettings.Stub { return enabled != 0 && handle == SyntheticPasswordManager.DEFAULT_HANDLE; } - private void enableSyntheticPasswordLocked() throws RemoteException { + private void enableSyntheticPasswordLocked() { setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 1, UserHandle.USER_SYSTEM); } @@ -2525,9 +2524,7 @@ public class LockSettingsService extends ILockSettings.Stub { mRecoverableKeyStoreManager.lockScreenSecretChanged(credentialType, credential, userId); } - @Override - public long addEscrowToken(byte[] token, int userId) throws RemoteException { - ensureCallerSystemUid(); + private long addEscrowToken(byte[] token, int userId) throws RemoteException { if (DEBUG) Slog.d(TAG, "addEscrowToken: user=" + userId); synchronized (mSpManager) { enableSyntheticPasswordLocked(); @@ -2559,7 +2556,7 @@ public class LockSettingsService extends ILockSettings.Stub { } } - private void activateEscrowTokens(AuthenticationToken auth, int userId) throws RemoteException { + private void activateEscrowTokens(AuthenticationToken auth, int userId) { if (DEBUG) Slog.d(TAG, "activateEscrowTokens: user=" + userId); synchronized (mSpManager) { disableEscrowTokenOnNonManagedDevicesIfNeeded(userId); @@ -2570,17 +2567,13 @@ public class LockSettingsService extends ILockSettings.Stub { } } - @Override - public boolean isEscrowTokenActive(long handle, int userId) throws RemoteException { - ensureCallerSystemUid(); + private boolean isEscrowTokenActive(long handle, int userId) { synchronized (mSpManager) { return mSpManager.existsHandle(handle, userId); } } - @Override - public boolean removeEscrowToken(long handle, int userId) throws RemoteException { - ensureCallerSystemUid(); + private boolean removeEscrowToken(long handle, int userId) { synchronized (mSpManager) { if (handle == getSyntheticPasswordHandleLocked(userId)) { Slog.w(TAG, "Cannot remove password handle"); @@ -2598,10 +2591,8 @@ public class LockSettingsService extends ILockSettings.Stub { } } - @Override - public boolean setLockCredentialWithToken(String credential, int type, long tokenHandle, + private boolean setLockCredentialWithToken(String credential, int type, long tokenHandle, byte[] token, int requestedQuality, int userId) throws RemoteException { - ensureCallerSystemUid(); boolean result; synchronized (mSpManager) { if (!mSpManager.hasEscrowData(userId)) { @@ -2650,10 +2641,8 @@ public class LockSettingsService extends ILockSettings.Stub { return true; } - @Override - public void unlockUserWithToken(long tokenHandle, byte[] token, int userId) + private boolean unlockUserWithToken(long tokenHandle, byte[] token, int userId) throws RemoteException { - ensureCallerSystemUid(); AuthenticationResult authResult; synchronized (mSpManager) { if (!mSpManager.hasEscrowData(userId)) { @@ -2663,11 +2652,12 @@ public class LockSettingsService extends ILockSettings.Stub { tokenHandle, token, userId); if (authResult.authToken == null) { Slog.w(TAG, "Invalid escrow token supplied"); - return; + return false; } } unlockUser(userId, null, authResult.authToken.deriveDiskEncryptionKey()); onAuthTokenKnownForUser(userId, authResult.authToken); + return true; } @Override @@ -2732,20 +2722,11 @@ public class LockSettingsService extends ILockSettings.Stub { if (isSyntheticPasswordBasedCredentialLocked(userId)) { mSpManager.destroyEscrowData(userId); } - } catch (RemoteException e) { - Slog.e(TAG, "disableEscrowTokenOnNonManagedDevices", e); } finally { Binder.restoreCallingIdentity(ident); } } - private void ensureCallerSystemUid() throws SecurityException { - final int callingUid = mInjector.binderGetCallingUid(); - if (callingUid != Process.SYSTEM_UID) { - throw new SecurityException("Only system can call this API."); - } - } - private class DeviceProvisionedObserver extends ContentObserver { private final Uri mDeviceProvisionedUri = Settings.Global.getUriFor( Settings.Global.DEVICE_PROVISIONED); @@ -2834,4 +2815,46 @@ public class LockSettingsService extends ILockSettings.Stub { Settings.Global.DEVICE_PROVISIONED, 0) != 0; } } + + private final class LocalService extends LockSettingsInternal { + + @Override + public long addEscrowToken(byte[] token, int userId) { + try { + return LockSettingsService.this.addEscrowToken(token, userId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + @Override + public boolean removeEscrowToken(long handle, int userId) { + return LockSettingsService.this.removeEscrowToken(handle, userId); + } + + @Override + public boolean isEscrowTokenActive(long handle, int userId) { + return LockSettingsService.this.isEscrowTokenActive(handle, userId); + } + + @Override + public boolean setLockCredentialWithToken(String credential, int type, long tokenHandle, + byte[] token, int requestedQuality, int userId) { + try { + return LockSettingsService.this.setLockCredentialWithToken(credential, type, + tokenHandle, token, requestedQuality, userId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + @Override + public boolean unlockUserWithToken(long tokenHandle, byte[] token, int userId) { + try { + return LockSettingsService.this.unlockUserWithToken(tokenHandle, token, userId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index b68b98da9a2f..b9fb2e012e03 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1782,11 +1782,10 @@ public class NotificationManagerService extends SystemService { * Report to usage stats that the notification was seen. * @param r notification record */ + @GuardedBy("mNotificationLock") protected void reportSeen(NotificationRecord r) { - final int userId = r.sbn.getUserId(); mAppUsageStats.reportEvent(r.sbn.getPackageName(), - userId == UserHandle.USER_ALL ? USER_SYSTEM - : userId, + getRealUserId(r.sbn.getUserId()), UsageEvents.Event.NOTIFICATION_SEEN); } @@ -1858,17 +1857,30 @@ public class NotificationManagerService extends SystemService { return newSuppressedVisualEffects; } + // TODO: log visual differences, not just audible ones + @GuardedBy("mNotificationLock") + protected void maybeRecordInterruptionLocked(NotificationRecord r) { + if (r.isInterruptive()) { + mAppUsageStats.reportInterruptiveNotification(r.sbn.getPackageName(), + r.getChannel().getId(), + getRealUserId(r.sbn.getUserId())); + } + } + /** * Report to usage stats that the notification was clicked. * @param r notification record */ protected void reportUserInteraction(NotificationRecord r) { - final int userId = r.sbn.getUserId(); mAppUsageStats.reportEvent(r.sbn.getPackageName(), - userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM : userId, + getRealUserId(r.sbn.getUserId()), UsageEvents.Event.USER_INTERACTION); } + private int getRealUserId(int userId) { + return userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM : userId; + } + @VisibleForTesting NotificationManagerInternal getInternalService() { return mInternalService; @@ -4345,6 +4357,7 @@ public class NotificationManagerService extends SystemService { } buzzBeepBlinkLocked(r); + maybeRecordInterruptionLocked(r); } finally { int N = mEnqueuedNotifications.size(); for (int i = 0; i < N; i++) { @@ -4554,6 +4567,7 @@ public class NotificationManagerService extends SystemService { updateLightsLocked(); } if (buzz || beep || blink) { + record.setInterruptive(true); MetricsLogger.action(record.getLogMaker() .setCategory(MetricsEvent.NOTIFICATION_ALERT) .setType(MetricsEvent.TYPE_OPEN) diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 4404c4848c60..f1908bff2d94 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -145,6 +145,7 @@ public final class NotificationRecord { private final List<Adjustment> mAdjustments; private final NotificationStats mStats; private int mUserSentiment; + private boolean mIsInterruptive; @VisibleForTesting public NotificationRecord(Context context, StatusBarNotification sbn, @@ -519,6 +520,7 @@ public final class NotificationRecord { pw.println(prefix + "mLight= " + mLight); pw.println(prefix + "mShowBadge=" + mShowBadge); pw.println(prefix + "mColorized=" + notification.isColorized()); + pw.println(prefix + "mIsInterruptive=" + mIsInterruptive); pw.println(prefix + "effectiveNotificationChannel=" + getChannel()); if (getPeopleOverride() != null) { pw.println(prefix + "overridePeople= " + TextUtils.join(",", getPeopleOverride())); @@ -888,6 +890,14 @@ public final class NotificationRecord { return mPeopleOverride; } + public void setInterruptive(boolean interruptive) { + mIsInterruptive = interruptive; + } + + public boolean isInterruptive() { + return mIsInterruptive; + } + protected void setPeopleOverride(ArrayList<String> people) { mPeopleOverride = people; } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 87be0a21d7d3..2d4438debf7d 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -2680,8 +2680,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { attrs.flags &= ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; break; case TYPE_DREAM: - // Dreams don't have an app window token and can thus not be letterboxed. - // Hence always let them extend under the cutout. + case TYPE_WALLPAPER: + // Dreams and wallpapers don't have an app window token and can thus not be + // letterboxed. Hence always let them extend under the cutout. attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; break; case TYPE_STATUS_BAR: @@ -4490,7 +4491,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { displayWidth, displayHeight); outFrame.intersect(taskBounds); } - outDisplayCutout.set(displayFrames.mDisplayCutout.calculateRelativeTo(outFrame)); + outDisplayCutout.set(displayFrames.mDisplayCutout.calculateRelativeTo(outFrame) + .getDisplayCutout()); return mForceShowSystemBars; } else { if (layoutInScreen) { @@ -4756,7 +4758,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // It's a system nav bar or a portrait screen; nav bar goes on bottom. final int top = cutoutSafeUnrestricted.bottom - getNavigationBarHeight(rotation, uiMode); - mTmpNavigationFrame.set(0, top, displayWidth, cutoutSafeUnrestricted.bottom); + mTmpNavigationFrame.set(0, top, displayWidth, displayFrames.mUnrestricted.bottom); displayFrames.mStable.bottom = displayFrames.mStableFullscreen.bottom = top; if (transientNavBarShowing) { mNavigationBarController.setBarShowingLw(true); @@ -4779,7 +4781,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Landscape screen; nav bar goes to the right. final int left = cutoutSafeUnrestricted.right - getNavigationBarWidth(rotation, uiMode); - mTmpNavigationFrame.set(left, 0, cutoutSafeUnrestricted.right, displayHeight); + mTmpNavigationFrame.set(left, 0, displayFrames.mUnrestricted.right, displayHeight); displayFrames.mStable.right = displayFrames.mStableFullscreen.right = left; if (transientNavBarShowing) { mNavigationBarController.setBarShowingLw(true); @@ -4802,7 +4804,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Seascape screen; nav bar goes to the left. final int right = cutoutSafeUnrestricted.left + getNavigationBarWidth(rotation, uiMode); - mTmpNavigationFrame.set(cutoutSafeUnrestricted.left, 0, right, displayHeight); + mTmpNavigationFrame.set(displayFrames.mUnrestricted.left, 0, right, displayHeight); displayFrames.mStable.left = displayFrames.mStableFullscreen.left = right; if (transientNavBarShowing) { mNavigationBarController.setBarShowingLw(true); @@ -4831,8 +4833,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { mStatusBarLayer = mNavigationBar.getSurfaceLayer(); // And compute the final frame. mNavigationBar.computeFrameLw(mTmpNavigationFrame, mTmpNavigationFrame, - mTmpNavigationFrame, mTmpNavigationFrame, mTmpNavigationFrame, dcf, - mTmpNavigationFrame, mTmpNavigationFrame, displayFrames.mDisplayCutout); + mTmpNavigationFrame, displayFrames.mDisplayCutoutSafe, mTmpNavigationFrame, dcf, + mTmpNavigationFrame, displayFrames.mDisplayCutoutSafe, + displayFrames.mDisplayCutout); if (DEBUG_LAYOUT) Slog.i(TAG, "mNavigationBar frame: " + mTmpNavigationFrame); return mNavigationBarController.checkHiddenLw(); } @@ -4990,8 +4993,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { df.set(displayFrames.mDock); pf.set(displayFrames.mDock); // IM dock windows layout below the nav bar... - pf.bottom = df.bottom = of.bottom = Math.min(displayFrames.mUnrestricted.bottom, - displayFrames.mDisplayCutoutSafe.bottom); + pf.bottom = df.bottom = of.bottom = displayFrames.mUnrestricted.bottom; // ...with content insets above the nav bar cf.bottom = vf.bottom = displayFrames.mStable.bottom; if (mStatusBar != null && mFocusedWindow == mStatusBar && canReceiveInput(mStatusBar)) { @@ -5292,27 +5294,48 @@ public class PhoneWindowManager implements WindowManagerPolicy { final int cutoutMode = attrs.layoutInDisplayCutoutMode; final boolean attachedInParent = attached != null && !layoutInScreen; + final boolean requestedHideNavigation = + (requestedSysUiFl & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0; // Ensure that windows with a DEFAULT or NEVER display cutout mode are laid out in // the cutout safe zone. if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) { - final Rect displayCutoutSafeExceptMaybeTop = mTmpRect; - displayCutoutSafeExceptMaybeTop.set(displayFrames.mDisplayCutoutSafe); + final Rect displayCutoutSafeExceptMaybeBars = mTmpRect; + displayCutoutSafeExceptMaybeBars.set(displayFrames.mDisplayCutoutSafe); if (layoutInScreen && layoutInsetDecor && !requestedFullscreen && cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT) { // At the top we have the status bar, so apps that are // LAYOUT_IN_SCREEN | LAYOUT_INSET_DECOR but not FULLSCREEN // already expect that there's an inset there and we don't need to exclude // the window from that area. - displayCutoutSafeExceptMaybeTop.top = Integer.MIN_VALUE; + displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE; + } + if (layoutInScreen && layoutInsetDecor && !requestedHideNavigation + && cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT) { + // Same for the navigation bar. + switch (mNavigationBarPosition) { + case NAV_BAR_BOTTOM: + displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE; + break; + case NAV_BAR_RIGHT: + displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE; + break; + case NAV_BAR_LEFT: + displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE; + break; + } + } + if (type == TYPE_INPUT_METHOD && mNavigationBarPosition == NAV_BAR_BOTTOM) { + // The IME can always extend under the bottom cutout if the navbar is there. + displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE; } // Windows that are attached to a parent and laid out in said parent are already // avoidingthe cutout according to that parent and don't need to be further constrained. if (!attachedInParent) { - pf.intersectUnchecked(displayCutoutSafeExceptMaybeTop); + pf.intersectUnchecked(displayCutoutSafeExceptMaybeBars); } - // Make sure that NO_LIMITS windows clipped to the display don't extend into the display - // don't extend under the cutout. - df.intersectUnchecked(displayCutoutSafeExceptMaybeTop); + // Make sure that NO_LIMITS windows clipped to the display don't extend under the + // cutout. + df.intersectUnchecked(displayCutoutSafeExceptMaybeBars); } // Content should never appear in the cutout. diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 9bd508e60202..8ec8c5b3c77e 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -93,6 +93,7 @@ import android.view.animation.Animation; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IShortcutService; import com.android.server.wm.DisplayFrames; +import com.android.server.wm.utils.WmDisplayCutout; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -217,11 +218,11 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { * @param stableFrame The frame around which stable system decoration is positioned. * @param outsetFrame The frame that includes areas that aren't part of the surface but we * want to treat them as such. - * @param displayCutout the display displayCutout + * @param displayCutout the display cutout */ public void computeFrameLw(Rect parentFrame, Rect displayFrame, Rect overlayFrame, Rect contentFrame, Rect visibleFrame, Rect decorFrame, - Rect stableFrame, @Nullable Rect outsetFrame, DisplayCutout displayCutout); + Rect stableFrame, @Nullable Rect outsetFrame, WmDisplayCutout displayCutout); /** * Retrieve the current frame of the window that has been assigned by diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index d7d39226722c..055e6ea3f4a4 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -487,6 +487,9 @@ public final class PowerManagerService extends SystemService // The screen brightness to use while dozing. private int mDozeScreenBrightnessOverrideFromDreamManager = PowerManager.BRIGHTNESS_DEFAULT; + // Keep display state when dozing. + private boolean mDrawWakeLockOverrideFromSidekick; + // Time when we last logged a warning about calling userActivity() without permission. private long mLastWarningAboutUserActivityPermission = Long.MIN_VALUE; @@ -2423,7 +2426,8 @@ public final class PowerManagerService extends SystemService if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE) { mDisplayPowerRequest.dozeScreenState = mDozeScreenStateOverrideFromDreamManager; - if ((mWakeLockSummary & WAKE_LOCK_DRAW) != 0) { + if ((mWakeLockSummary & WAKE_LOCK_DRAW) != 0 + && !mDrawWakeLockOverrideFromSidekick) { if (mDisplayPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND) { mDisplayPowerRequest.dozeScreenState = Display.STATE_DOZE; } @@ -3176,6 +3180,16 @@ public final class PowerManagerService extends SystemService } } + private void setDrawWakeLockOverrideFromSidekickInternal(boolean keepState) { + synchronized (mLock) { + if (mDrawWakeLockOverrideFromSidekick != keepState) { + mDrawWakeLockOverrideFromSidekick = keepState; + mDirty |= DIRTY_SETTINGS; + updatePowerStateLocked(); + } + } + } + @VisibleForTesting void setVrModeEnabled(boolean enabled) { mIsVrModeEnabled = enabled; @@ -3381,6 +3395,7 @@ public final class PowerManagerService extends SystemService + mUserInactiveOverrideFromWindowManager); pw.println(" mDozeScreenStateOverrideFromDreamManager=" + mDozeScreenStateOverrideFromDreamManager); + pw.println(" mDrawWakeLockOverrideFromSidekick=" + mDrawWakeLockOverrideFromSidekick); pw.println(" mDozeScreenBrightnessOverrideFromDreamManager=" + mDozeScreenBrightnessOverrideFromDreamManager); pw.println(" mScreenBrightnessSettingMinimum=" + mScreenBrightnessSettingMinimum); @@ -3717,6 +3732,10 @@ public final class PowerManagerService extends SystemService mDozeScreenStateOverrideFromDreamManager); proto.write( PowerServiceSettingsAndConfigurationDumpProto + .DRAW_WAKE_LOCK_OVERRIDE_FROM_SIDEKICK, + mDrawWakeLockOverrideFromSidekick); + proto.write( + PowerServiceSettingsAndConfigurationDumpProto .DOZED_SCREEN_BRIGHTNESS_OVERRIDE_FROM_DREAM_MANAGER, mDozeScreenBrightnessOverrideFromDreamManager); @@ -4703,6 +4722,11 @@ public final class PowerManagerService extends SystemService } @Override + public void setDrawWakeLockOverrideFromSidekick(boolean keepState) { + setDrawWakeLockOverrideFromSidekickInternal(keepState); + } + + @Override public void setMaximumScreenOffTimeoutFromDeviceAdmin(@UserIdInt int userId, long timeMs) { setMaximumScreenOffTimeoutFromDeviceAdminInternal(userId, timeMs); } diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java index 64a2570c5631..954627bfaf98 100644 --- a/services/core/java/com/android/server/stats/StatsCompanionService.java +++ b/services/core/java/com/android/server/stats/StatsCompanionService.java @@ -73,6 +73,7 @@ import com.android.server.SystemService; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.TimeoutException; @@ -196,6 +197,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override public void sendSubscriberBroadcast(IBinder intentSenderBinder, long configUid, long configKey, long subscriptionId, long subscriptionRuleId, + String[] cookies, StatsDimensionsValue dimensionsValue) { enforceCallingPermission(); IntentSender intentSender = new IntentSender(intentSenderBinder); @@ -205,10 +207,17 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { .putExtra(StatsManager.EXTRA_STATS_SUBSCRIPTION_ID, subscriptionId) .putExtra(StatsManager.EXTRA_STATS_SUBSCRIPTION_RULE_ID, subscriptionRuleId) .putExtra(StatsManager.EXTRA_STATS_DIMENSIONS_VALUE, dimensionsValue); + + ArrayList<String> cookieList = new ArrayList<>(cookies.length); + for (String cookie : cookies) { cookieList.add(cookie); } + intent.putStringArrayListExtra( + StatsManager.EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES, cookieList); + if (DEBUG) { - Slog.d(TAG, String.format("Statsd sendSubscriberBroadcast with params {%d %d %d %d %s}", - configUid, configKey, subscriptionId, - subscriptionRuleId, dimensionsValue)); + Slog.d(TAG, String.format( + "Statsd sendSubscriberBroadcast with params {%d %d %d %d %s %s}", + configUid, configKey, subscriptionId, subscriptionRuleId, + Arrays.toString(cookies), dimensionsValue)); } try { intentSender.sendIntent(mContext, CODE_SUBSCRIBER_BROADCAST, intent, null, null); @@ -278,7 +287,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { return; // Keep only replacing or normal add and remove. } - Slog.i(TAG, "StatsCompanionService noticed an app was updated."); + if (DEBUG) Slog.d(TAG, "StatsCompanionService noticed an app was updated."); synchronized (sStatsdLock) { if (sStatsd == null) { Slog.w(TAG, "Could not access statsd to inform it of an app update"); diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java index 0ac853b030e1..60535122929e 100644 --- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java +++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java @@ -16,11 +16,12 @@ package com.android.server.textclassifier; -import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.pm.PackageManager; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; @@ -30,6 +31,7 @@ import android.service.textclassifier.ITextClassificationCallback; import android.service.textclassifier.ITextLinksCallback; import android.service.textclassifier.ITextSelectionCallback; import android.service.textclassifier.TextClassifierService; +import android.view.textclassifier.SelectionEvent; import android.view.textclassifier.TextClassification; import android.view.textclassifier.TextClassifier; import android.view.textclassifier.TextLinks; @@ -216,6 +218,23 @@ public final class TextClassificationManagerService extends ITextClassifierServi } } + @Override + public void onSelectionEvent(SelectionEvent event) throws RemoteException { + validateInput(event, mContext); + + synchronized (mLock) { + if (isBoundLocked()) { + mService.onSelectionEvent(event); + } else { + final Callable<Void> request = () -> { + onSelectionEvent(event); + return null; + }; + enqueueRequestLocked(request, null /* onServiceFailure */, null /* binder */); + } + } + } + /** * @return true if the service is bound or in the process of being bound. * Returns false otherwise. @@ -281,8 +300,8 @@ public final class TextClassificationManagerService extends ITextClassifierServi private final class PendingRequest implements IBinder.DeathRecipient { private final Callable<Void> mRequest; - private final Callable<Void> mOnServiceFailure; - private final IBinder mBinder; + @Nullable private final Callable<Void> mOnServiceFailure; + @Nullable private final IBinder mBinder; /** * Initializes a new pending request. @@ -292,15 +311,17 @@ public final class TextClassificationManagerService extends ITextClassifierServi * @param binder binder to the process that made this pending request */ PendingRequest( - @NonNull Callable<Void> request, @NonNull Callable<Void> onServiceFailure, - @NonNull IBinder binder) { + Callable<Void> request, @Nullable Callable<Void> onServiceFailure, + @Nullable IBinder binder) { mRequest = Preconditions.checkNotNull(request); - mOnServiceFailure = Preconditions.checkNotNull(onServiceFailure); - mBinder = Preconditions.checkNotNull(binder); - try { - mBinder.linkToDeath(this, 0); - } catch (RemoteException e) { - e.printStackTrace(); + mOnServiceFailure = onServiceFailure; + mBinder = binder; + if (mBinder != null) { + try { + mBinder.linkToDeath(this, 0); + } catch (RemoteException e) { + e.printStackTrace(); + } } } @@ -317,11 +338,13 @@ public final class TextClassificationManagerService extends ITextClassifierServi @GuardedBy("mLock") void notifyServiceFailureLocked() { removeLocked(); - try { - mOnServiceFailure.call(); - } catch (Exception e) { - Slog.d(LOG_TAG, "Error notifying callback of service failure: " - + e.getMessage()); + if (mOnServiceFailure != null) { + try { + mOnServiceFailure.call(); + } catch (Exception e) { + Slog.d(LOG_TAG, "Error notifying callback of service failure: " + + e.getMessage()); + } } } @@ -336,7 +359,9 @@ public final class TextClassificationManagerService extends ITextClassifierServi @GuardedBy("mLock") private void removeLocked() { mPendingRequests.remove(this); - mBinder.unlinkToDeath(this, 0); + if (mBinder != null) { + mBinder.unlinkToDeath(this, 0); + } } } @@ -359,4 +384,16 @@ public final class TextClassificationManagerService extends ITextClassifierServi throw new RemoteException(e.getMessage()); } } + + private static void validateInput(SelectionEvent event, Context context) + throws RemoteException { + try { + final int uid = context.getPackageManager() + .getPackageUid(event.getPackageName(), 0); + Preconditions.checkArgument(Binder.getCallingUid() == uid); + } catch (IllegalArgumentException | NullPointerException | + PackageManager.NameNotFoundException e) { + throw new RemoteException(e.getMessage()); + } + } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 7b5e8b8139b4..2dce9133d094 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -154,6 +154,7 @@ import com.android.internal.util.ToBooleanFunction; import com.android.internal.view.IInputMethodClient; import com.android.server.policy.WindowManagerPolicy; import com.android.server.wm.utils.RotationCache; +import com.android.server.wm.utils.WmDisplayCutout; import java.io.PrintWriter; import java.util.ArrayList; @@ -214,7 +215,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo int mInitialDisplayDensity = 0; DisplayCutout mInitialDisplayCutout; - private final RotationCache<DisplayCutout, DisplayCutout> mDisplayCutoutCache + private final RotationCache<DisplayCutout, WmDisplayCutout> mDisplayCutoutCache = new RotationCache<>(this::calculateDisplayCutoutForRotationUncached); /** @@ -735,7 +736,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo display.getDisplayInfo(mDisplayInfo); display.getMetrics(mDisplayMetrics); isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY; - mDisplayFrames = new DisplayFrames(mDisplayId, mDisplayInfo); + mDisplayFrames = new DisplayFrames(mDisplayId, mDisplayInfo, + calculateDisplayCutoutForRotation(mDisplayInfo.rotation)); initializeDisplayBaseInfo(); mDividerControllerLocked = new DockedStackDividerController(service, this); mPinnedStackControllerLocked = new PinnedStackController(service, this); @@ -1128,7 +1130,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mService.mPolicy.setInitialDisplaySize(getDisplay(), mBaseDisplayWidth, mBaseDisplayHeight, mBaseDisplayDensity); - mDisplayFrames.onDisplayInfoUpdated(mDisplayInfo); + mDisplayFrames.onDisplayInfoUpdated(mDisplayInfo, + calculateDisplayCutoutForRotation(mDisplayInfo.rotation)); } /** @@ -1161,8 +1164,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } // Update application display metrics. - final DisplayCutout displayCutout = calculateDisplayCutoutForRotation( - mRotation); + final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(mRotation); + final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout(); + final int appWidth = mService.mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation, uiMode, mDisplayId, displayCutout); final int appHeight = mService.mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation, uiMode, @@ -1177,7 +1181,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mDisplayInfo.getLogicalMetrics(mRealDisplayMetrics, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); } - mDisplayInfo.displayCutout = displayCutout; + mDisplayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout; mDisplayInfo.getAppMetrics(mDisplayMetrics); if (mDisplayScalingDisabled) { mDisplayInfo.flags |= Display.FLAG_SCALING_DISABLED; @@ -1199,24 +1203,25 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mDisplayInfo; } - DisplayCutout calculateDisplayCutoutForRotation(int rotation) { + WmDisplayCutout calculateDisplayCutoutForRotation(int rotation) { return mDisplayCutoutCache.getOrCompute(mInitialDisplayCutout, rotation); } - private DisplayCutout calculateDisplayCutoutForRotationUncached( + private WmDisplayCutout calculateDisplayCutoutForRotationUncached( DisplayCutout cutout, int rotation) { if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) { - return cutout; + return WmDisplayCutout.NO_CUTOUT; } if (rotation == ROTATION_0) { - return cutout.computeSafeInsets(mInitialDisplayWidth, mInitialDisplayHeight); + return WmDisplayCutout.computeSafeInsets( + cutout, mInitialDisplayWidth, mInitialDisplayHeight); } final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); final Path bounds = cutout.getBounds().getBoundaryPath(); transformPhysicalToLogicalCoordinates(rotation, mInitialDisplayWidth, mInitialDisplayHeight, mTmpMatrix); bounds.transform(mTmpMatrix); - return DisplayCutout.fromBounds(bounds).computeSafeInsets( + return WmDisplayCutout.computeSafeInsets(DisplayCutout.fromBounds(bounds), rotated ? mInitialDisplayHeight : mInitialDisplayWidth, rotated ? mInitialDisplayWidth : mInitialDisplayHeight); } @@ -1441,7 +1446,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int displayId, int rotation, int uiMode, int dw, int dh) { - final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(rotation); + final DisplayCutout displayCutout = calculateDisplayCutoutForRotation( + rotation).getDisplayCutout(); final int width = mService.mPolicy.getConfigDisplayWidth(dw, dh, rotation, uiMode, displayId, displayCutout); if (width < displayInfo.smallestNominalAppWidth) { @@ -2910,7 +2916,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo Slog.v(TAG, "performLayout: needed=" + isLayoutNeeded() + " dw=" + dw + " dh=" + dh); } - mDisplayFrames.onDisplayInfoUpdated(mDisplayInfo); + mDisplayFrames.onDisplayInfoUpdated(mDisplayInfo, + calculateDisplayCutoutForRotation(mDisplayInfo.rotation)); // TODO: Not sure if we really need to set the rotation here since we are updating from the // display info above... mDisplayFrames.mRotation = mRotation; diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java index 57ce15bc82f5..57693ac2b463 100644 --- a/services/core/java/com/android/server/wm/DisplayFrames.java +++ b/services/core/java/com/android/server/wm/DisplayFrames.java @@ -27,6 +27,8 @@ import android.util.proto.ProtoOutputStream; import android.view.DisplayCutout; import android.view.DisplayInfo; +import com.android.server.wm.utils.WmDisplayCutout; + import java.io.PrintWriter; /** @@ -97,10 +99,10 @@ public class DisplayFrames { public final Rect mDock = new Rect(); /** The display cutout used for layout (after rotation) */ - @NonNull public DisplayCutout mDisplayCutout = DisplayCutout.NO_CUTOUT; + @NonNull public WmDisplayCutout mDisplayCutout = WmDisplayCutout.NO_CUTOUT; /** The cutout as supplied by display info */ - @NonNull private DisplayCutout mDisplayInfoCutout = DisplayCutout.NO_CUTOUT; + @NonNull public WmDisplayCutout mDisplayInfoCutout = WmDisplayCutout.NO_CUTOUT; /** * During layout, the frame that is display-cutout safe, i.e. that does not intersect with it. @@ -114,19 +116,18 @@ public class DisplayFrames { public int mRotation; - public DisplayFrames(int displayId, DisplayInfo info) { + public DisplayFrames(int displayId, DisplayInfo info, WmDisplayCutout displayCutout) { mDisplayId = displayId; - onDisplayInfoUpdated(info); + onDisplayInfoUpdated(info, displayCutout); } - public void onDisplayInfoUpdated(DisplayInfo info) { + public void onDisplayInfoUpdated(DisplayInfo info, WmDisplayCutout displayCutout) { mDisplayWidth = info.logicalWidth; mDisplayHeight = info.logicalHeight; mRotation = info.rotation; mDisplayInfoOverscan.set( info.overscanLeft, info.overscanTop, info.overscanRight, info.overscanBottom); - mDisplayInfoCutout = info.displayCutout != null - ? info.displayCutout : DisplayCutout.NO_CUTOUT; + mDisplayInfoCutout = displayCutout != null ? displayCutout : WmDisplayCutout.NO_CUTOUT; } public void onBeginLayout() { @@ -171,8 +172,8 @@ public class DisplayFrames { mDisplayCutout = mDisplayInfoCutout; mDisplayCutoutSafe.set(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE); - if (!mDisplayCutout.isEmpty()) { - final DisplayCutout c = mDisplayCutout; + if (!mDisplayCutout.getDisplayCutout().isEmpty()) { + final DisplayCutout c = mDisplayCutout.getDisplayCutout(); if (c.getSafeInsetLeft() > 0) { mDisplayCutoutSafe.left = mRestrictedOverscan.left + c.getSafeInsetLeft(); } diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java index 1f1efc4ed77a..b99e85fed46b 100644 --- a/services/core/java/com/android/server/wm/DockedStackDividerController.java +++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java @@ -175,7 +175,7 @@ public class DockedStackDividerController { getContentWidth()); final DisplayCutout displayCutout = mDisplayContent.calculateDisplayCutoutForRotation( - rotation); + rotation).getDisplayCutout(); // Since we only care about feasible states, snap to the closest snap target, like it // would happen when actually rotating the screen. @@ -233,7 +233,7 @@ public class DockedStackDividerController { ? mDisplayContent.mBaseDisplayWidth : mDisplayContent.mBaseDisplayHeight; final DisplayCutout displayCutout = - mDisplayContent.calculateDisplayCutoutForRotation(rotation); + mDisplayContent.calculateDisplayCutoutForRotation(rotation).getDisplayCutout(); mService.mPolicy.getStableInsetsLw(rotation, dw, dh, displayCutout, mTmpRect); config.unset(); config.orientation = (dw <= dh) ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index a5a1ca5a9ffe..9310dc488c74 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -273,7 +273,8 @@ class TaskSnapshotController { } return new TaskSnapshot(buffer, top.getConfiguration().orientation, getInsetsFromTaskBounds(mainWindow, task), - isLowRamDevice /* reduced */, scaleFraction /* scale */); + isLowRamDevice /* reduced */, scaleFraction /* scale */, + true /* isRealSnapshot */); } private boolean shouldDisableSnapshots() { @@ -369,7 +370,8 @@ class TaskSnapshotController { } return new TaskSnapshot(hwBitmap.createGraphicBufferHandle(), topChild.getConfiguration().orientation, mainWindow.mStableInsets, - ActivityManager.isLowRamDeviceStatic() /* reduced */, 1.0f /* scale */); + ActivityManager.isLowRamDeviceStatic() /* reduced */, 1.0f /* scale */, + false /* isRealSnapshot */); } /** diff --git a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java index 537f31775951..31da5f3ff1c8 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java @@ -89,7 +89,8 @@ class TaskSnapshotLoader { } return new TaskSnapshot(buffer, proto.orientation, new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom), - reducedResolution, reducedResolution ? REDUCED_SCALE : 1f); + reducedResolution, reducedResolution ? REDUCED_SCALE : 1f, + proto.isRealSnapshot); } catch (IOException e) { Slog.w(TAG, "Unable to load task snapshot data for taskId=" + taskId); return null; diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java index 621bee7d17e0..086fffa9d621 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java @@ -318,6 +318,7 @@ class TaskSnapshotPersister { proto.insetTop = mSnapshot.getContentInsets().top; proto.insetRight = mSnapshot.getContentInsets().right; proto.insetBottom = mSnapshot.getContentInsets().bottom; + proto.isRealSnapshot = mSnapshot.isRealSnapshot(); final byte[] bytes = TaskSnapshotProto.toByteArray(proto); final File file = getProtoFile(mTaskId, mUserId); final AtomicFile atomicFile = new AtomicFile(file); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 1ded81d0351b..563d7928bb8d 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -1438,7 +1438,9 @@ public class WindowManagerService extends IWindowManager.Stub final DisplayFrames displayFrames = displayContent.mDisplayFrames; // TODO: Not sure if onDisplayInfoUpdated() call is needed. - displayFrames.onDisplayInfoUpdated(displayContent.getDisplayInfo()); + final DisplayInfo displayInfo = displayContent.getDisplayInfo(); + displayFrames.onDisplayInfoUpdated(displayInfo, + displayContent.calculateDisplayCutoutForRotation(displayInfo.rotation)); final Rect taskBounds; if (atoken != null && atoken.getTask() != null) { taskBounds = mTmpRect; @@ -2097,7 +2099,7 @@ public class WindowManagerService extends IWindowManager.Stub win.mLastRelayoutContentInsets.set(win.mContentInsets); outVisibleInsets.set(win.mVisibleInsets); outStableInsets.set(win.mStableInsets); - outCutout.set(win.mDisplayCutout); + outCutout.set(win.mDisplayCutout.getDisplayCutout()); outOutsets.set(win.mOutsets); outBackdropFrame.set(win.getBackdropFrame(win.mFrame)); if (localLOGV) Slog.v( diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index c4185fa9a599..f01dc205ff1c 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -202,6 +202,7 @@ import com.android.internal.util.ToBooleanFunction; import com.android.server.input.InputWindowHandle; import com.android.server.policy.WindowManagerPolicy; import com.android.server.wm.LocalAnimationAdapter.AnimationSpec; +import com.android.server.wm.utils.WmDisplayCutout; import java.io.PrintWriter; import java.lang.ref.WeakReference; @@ -351,8 +352,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP private boolean mOutsetsChanged = false; /** Part of the display that has been cut away. See {@link DisplayCutout}. */ - DisplayCutout mDisplayCutout = DisplayCutout.NO_CUTOUT; - private DisplayCutout mLastDisplayCutout = DisplayCutout.NO_CUTOUT; + WmDisplayCutout mDisplayCutout = WmDisplayCutout.NO_CUTOUT; + private WmDisplayCutout mLastDisplayCutout = WmDisplayCutout.NO_CUTOUT; private boolean mDisplayCutoutChanged; /** @@ -693,6 +694,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mOwnerCanAddInternalSystemWindow = ownerCanAddInternalSystemWindow; mWindowId = new WindowId(this); mAttrs.copyFrom(a); + mLastSurfaceInsets.set(mAttrs.surfaceInsets); mViewVisibility = viewVisibility; mPolicy = mService.mPolicy; mContext = mService.mContext; @@ -833,7 +835,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override public void computeFrameLw(Rect parentFrame, Rect displayFrame, Rect overscanFrame, Rect contentFrame, Rect visibleFrame, Rect decorFrame, Rect stableFrame, - Rect outsetFrame, DisplayCutout displayCutout) { + Rect outsetFrame, WmDisplayCutout displayCutout) { if (mWillReplaceWindow && (mAnimatingExit || !mReplacingRemoveRequested)) { // This window is being replaced and either already got information that it's being // removed or we are still waiting for some information. Because of this we don't @@ -2914,7 +2916,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final boolean reportDraw = mWinAnimator.mDrawState == DRAW_PENDING; final boolean reportOrientation = mReportOrientationChanged; final int displayId = getDisplayId(); - final DisplayCutout displayCutout = mDisplayCutout; + final DisplayCutout displayCutout = mDisplayCutout.getDisplayCutout(); if (mAttrs.type != WindowManager.LayoutParams.TYPE_APPLICATION_STARTING && mClient instanceof IWindow.Stub) { // To prevent deadlock simulate one-way call if win.mClient is a local object. @@ -3186,7 +3188,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mVisibleInsets.writeToProto(proto, VISIBLE_INSETS); mStableInsets.writeToProto(proto, STABLE_INSETS); mOutsets.writeToProto(proto, OUTSETS); - mDisplayCutout.writeToProto(proto, CUTOUT); + mDisplayCutout.getDisplayCutout().writeToProto(proto, CUTOUT); proto.write(REMOVE_ON_EXIT, mRemoveOnExit); proto.write(DESTROYING, mDestroying); proto.write(REMOVED, mRemoved); @@ -3332,7 +3334,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP pw.print(" stable="); mStableInsets.printShortString(pw); pw.print(" surface="); mAttrs.surfaceInsets.printShortString(pw); pw.print(" outsets="); mOutsets.printShortString(pw); - pw.print(" cutout=" + mDisplayCutout); + pw.print(" cutout=" + mDisplayCutout.getDisplayCutout()); pw.println(); pw.print(prefix); pw.print("Lst insets: overscan="); mLastOverscanInsets.printShortString(pw); diff --git a/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java b/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java new file mode 100644 index 000000000000..ea3f758fb209 --- /dev/null +++ b/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java @@ -0,0 +1,173 @@ +/* + * 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.wm.utils; + +import android.graphics.Rect; +import android.util.Size; +import android.view.DisplayCutout; +import android.view.Gravity; + +import java.util.List; +import java.util.Objects; + +/** + * Wrapper for DisplayCutout that also tracks the display size and using this allows (re)calculating + * safe insets. + */ +public class WmDisplayCutout { + + public static final WmDisplayCutout NO_CUTOUT = new WmDisplayCutout(DisplayCutout.NO_CUTOUT, + null); + + private final DisplayCutout mInner; + private final Size mFrameSize; + + public WmDisplayCutout(DisplayCutout inner, Size frameSize) { + mInner = inner; + mFrameSize = frameSize; + } + + public static WmDisplayCutout computeSafeInsets(DisplayCutout inner, + int displayWidth, int displayHeight) { + if (inner == DisplayCutout.NO_CUTOUT || inner.isBoundsEmpty()) { + return NO_CUTOUT; + } + + final Size displaySize = new Size(displayWidth, displayHeight); + final Rect safeInsets = computeSafeInsets(displaySize, inner); + return new WmDisplayCutout(inner.replaceSafeInsets(safeInsets), displaySize); + } + + /** + * Insets the reference frame of the cutout in the given directions. + * + * @return a copy of this instance which has been inset + * @hide + */ + public WmDisplayCutout inset(int insetLeft, int insetTop, int insetRight, int insetBottom) { + DisplayCutout newInner = mInner.inset(insetLeft, insetTop, insetRight, insetBottom); + + if (mInner == newInner) { + return this; + } + + Size frame = mFrameSize == null ? null : new Size( + mFrameSize.getWidth() - insetLeft - insetRight, + mFrameSize.getHeight() - insetTop - insetBottom); + + return new WmDisplayCutout(newInner, frame); + } + + /** + * Recalculates the cutout relative to the given reference frame. + * + * The safe insets must already have been computed, e.g. with {@link #computeSafeInsets}. + * + * @return a copy of this instance with the safe insets recalculated + * @hide + */ + public WmDisplayCutout calculateRelativeTo(Rect frame) { + if (mInner.isEmpty()) { + return this; + } + return inset(frame.left, frame.top, + mFrameSize.getWidth() - frame.right, mFrameSize.getHeight() - frame.bottom); + } + + /** + * Calculates the safe insets relative to the given display size. + * + * @return a copy of this instance with the safe insets calculated + * @hide + */ + public WmDisplayCutout computeSafeInsets(int width, int height) { + return computeSafeInsets(mInner, width, height); + } + + private static Rect computeSafeInsets(Size displaySize, DisplayCutout cutout) { + if (displaySize.getWidth() < displaySize.getHeight()) { + final List<Rect> boundingRects = cutout.replaceSafeInsets( + new Rect(0, displaySize.getHeight() / 2, 0, displaySize.getHeight() / 2)) + .getBoundingRects(); + int topInset = findInsetForSide(displaySize, boundingRects, Gravity.TOP); + int bottomInset = findInsetForSide(displaySize, boundingRects, Gravity.BOTTOM); + return new Rect(0, topInset, 0, bottomInset); + } else if (displaySize.getWidth() > displaySize.getHeight()) { + final List<Rect> boundingRects = cutout.replaceSafeInsets( + new Rect(displaySize.getWidth() / 2, 0, displaySize.getWidth() / 2, 0)) + .getBoundingRects(); + int leftInset = findInsetForSide(displaySize, boundingRects, Gravity.LEFT); + int right = findInsetForSide(displaySize, boundingRects, Gravity.RIGHT); + return new Rect(leftInset, 0, right, 0); + } else { + throw new UnsupportedOperationException("not implemented: display=" + displaySize + + " cutout=" + cutout); + } + } + + private static int findInsetForSide(Size display, List<Rect> boundingRects, int gravity) { + int inset = 0; + final int size = boundingRects.size(); + for (int i = 0; i < size; i++) { + Rect boundingRect = boundingRects.get(i); + switch (gravity) { + case Gravity.TOP: + if (boundingRect.top == 0) { + inset = Math.max(inset, boundingRect.bottom); + } + break; + case Gravity.BOTTOM: + if (boundingRect.bottom == display.getHeight()) { + inset = Math.max(inset, display.getHeight() - boundingRect.top); + } + break; + case Gravity.LEFT: + if (boundingRect.left == 0) { + inset = Math.max(inset, boundingRect.right); + } + break; + case Gravity.RIGHT: + if (boundingRect.right == display.getWidth()) { + inset = Math.max(inset, display.getWidth() - boundingRect.left); + } + break; + default: + throw new IllegalArgumentException("unknown gravity: " + gravity); + } + } + return inset; + } + + public DisplayCutout getDisplayCutout() { + return mInner; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof WmDisplayCutout)) { + return false; + } + WmDisplayCutout that = (WmDisplayCutout) o; + return Objects.equals(mInner, that.mInner) && + Objects.equals(mFrameSize, that.mFrameSize); + } + + @Override + public int hashCode() { + return Objects.hash(mInner, mFrameSize); + } +} diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk index 356e64b5dd7b..0ca0a1a104c8 100644 --- a/services/tests/servicestests/Android.mk +++ b/services/tests/servicestests/Android.mk @@ -15,6 +15,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ frameworks-base-testutils \ services.accessibility \ services.appwidget \ + services.autofill \ services.backup \ services.core \ services.devicepolicy \ diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java index 5b1f5c176f57..ac212ddc44e7 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java @@ -123,12 +123,20 @@ public class ActivityRecordTests extends ActivityTestsBase { } return null; }).when(mActivity.app.thread).scheduleTransaction(any()); + mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped"); + // The activity is in the focused stack so it should not move to paused. mActivity.makeVisibleIfNeeded(null /* starting */); + assertTrue(mActivity.isState(STOPPED)); + assertFalse(pauseFound.value); - assertTrue(mActivity.isState(PAUSING)); + // Clear focused stack + mActivity.mStackSupervisor.mFocusedStack = null; + // In the unfocused stack, the activity should move to paused. + mActivity.makeVisibleIfNeeded(null /* starting */); + assertTrue(mActivity.isState(PAUSING)); assertTrue(pauseFound.value); // Make sure that the state does not change for current non-stopping states. diff --git a/services/tests/servicestests/src/com/android/server/autofill/AutofillManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/autofill/AutofillManagerServiceTest.java new file mode 100644 index 000000000000..c348e70bb375 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/autofill/AutofillManagerServiceTest.java @@ -0,0 +1,114 @@ +/* + * 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.autofill; + +import static com.android.server.autofill.AutofillManagerService.getWhitelistedCompatModePackages; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.Map; + +@RunWith(JUnit4.class) +public class AutofillManagerServiceTest { + // TODO(b/74445943): temporary work around until P Development Preview 3 is branched + private static final boolean ADDS_DEFAULT_BUTTON = true; + + @Test + public void testGetWhitelistedCompatModePackages_null() { + assertThat(getWhitelistedCompatModePackages(null)).isNull(); + } + + @Test + public void testGetWhitelistedCompatModePackages_empty() { + assertThat(getWhitelistedCompatModePackages("")).isNull(); + } + + @Test + public void testGetWhitelistedCompatModePackages_onePackageNoUrls() { + if (ADDS_DEFAULT_BUTTON) { + final Map<String, String[]> result = + getWhitelistedCompatModePackages("one_is_the_loniest_package"); + assertThat(result).hasSize(1); + assertThat(result.get("one_is_the_loniest_package")).asList() + .containsExactly("url_bar", "location_bar_edit_text"); + } else { + assertThat(getWhitelistedCompatModePackages("one_is_the_loniest_package")) + .containsExactly("one_is_the_loniest_package", null); + } + } + + @Test + public void testGetWhitelistedCompatModePackages_onePackageMissingEndDelimiter() { + assertThat(getWhitelistedCompatModePackages("one_is_the_loniest_package[")).isEmpty(); + } + + @Test + public void testGetWhitelistedCompatModePackages_onePackageOneUrl() { + final Map<String, String[]> result = + getWhitelistedCompatModePackages("one_is_the_loniest_package[url]"); + assertThat(result).hasSize(1); + assertThat(result.get("one_is_the_loniest_package")).asList().containsExactly("url"); + } + + @Test + public void testGetWhitelistedCompatModePackages_onePackageMultipleUrls() { + final Map<String, String[]> result = + getWhitelistedCompatModePackages("one_is_the_loniest_package[4,5,8,15,16,23,42]"); + assertThat(result).hasSize(1); + assertThat(result.get("one_is_the_loniest_package")).asList() + .containsExactly("4", "5", "8", "15", "16", "23", "42"); + } + + @Test + public void testGetWhitelistedCompatModePackages_multiplePackagesOneInvalid() { + final Map<String, String[]> result = getWhitelistedCompatModePackages("one:two["); + assertThat(result).hasSize(1); + if (ADDS_DEFAULT_BUTTON) { + assertThat(result.get("one")).asList() + .containsExactly("url_bar", "location_bar_edit_text"); + } else { + assertThat(result.get("one")).isNull(); + } + } + + @Test + public void testGetWhitelistedCompatModePackages_multiplePackagesMultipleUrls() { + final Map<String, String[]> result = + getWhitelistedCompatModePackages("p1[p1u1]:p2:p3[p3u1,p3u2]"); + assertThat(result).hasSize(3); + assertThat(result.get("p1")).asList().containsExactly("p1u1"); + if (ADDS_DEFAULT_BUTTON) { + assertThat(result.get("p2")).asList() + .containsExactly("url_bar", "location_bar_edit_text"); + } else { + assertThat(result.get("p2")).isNull(); + } + assertThat(result.get("p3")).asList().containsExactly("p3u1", "p3u2"); + } + + @Test + public void testGetWhitelistedCompatModePackages_threePackagesOneInvalid() { + final Map<String, String[]> result = + getWhitelistedCompatModePackages("p1[p1u1]:p2[:p3[p3u1,p3u2]"); + assertThat(result).hasSize(2); + assertThat(result.get("p1")).asList().containsExactly("p1u1"); + assertThat(result.get("p3")).asList().containsExactly("p3u1", "p3u2"); + } +} diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java index e8648701bd0d..96f81606a985 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java @@ -43,6 +43,7 @@ import android.test.AndroidTestCase; import com.android.internal.widget.ILockSettings; import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.LockSettingsInternal; import com.android.server.LocalServices; import org.mockito.invocation.InvocationOnMock; @@ -67,6 +68,7 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { private ArrayList<UserInfo> mPrimaryUserProfiles = new ArrayList<>(); LockSettingsService mService; + LockSettingsInternal mLocalService; MockLockSettingsContext mContext; LockSettingsStorageTestable mStorage; @@ -95,6 +97,7 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { mDevicePolicyManager = mock(DevicePolicyManager.class); mDevicePolicyManagerInternal = mock(DevicePolicyManagerInternal.class); + LocalServices.removeServiceForTest(LockSettingsInternal.class); LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); LocalServices.addService(DevicePolicyManagerInternal.class, mDevicePolicyManagerInternal); @@ -146,6 +149,7 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { // Adding a fake Device Owner app which will enable escrow token support in LSS. when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser()).thenReturn( new ComponentName("com.dummy.package", ".FakeDeviceOwner")); + mLocalService = LocalServices.getService(LockSettingsInternal.class); } private UserInfo installChildProfile(int profileId) { diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java index 294c3e99ad7e..e9f9800f2198 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java @@ -300,14 +300,14 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID); final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID); - long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); - assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); + long handle = mLocalService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); + assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode(); - assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); + assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); - mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, + mLocalService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, handle, TOKEN.getBytes(), PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID); // Verify DPM gets notified about new device lock @@ -329,16 +329,16 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID); final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID); - long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); - assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); + long handle = mLocalService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); + assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode(); - assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); + assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); - mService.setLockCredentialWithToken(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, handle, - TOKEN.getBytes(), PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID); - mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, + mLocalService.setLockCredentialWithToken(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, + handle, TOKEN.getBytes(), PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID); + mLocalService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, handle, TOKEN.getBytes(), PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID); assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential( @@ -355,18 +355,19 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID); final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID); - long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); - assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); + long handle = mLocalService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); + assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode(); - assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); + assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); mService.setLockCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, PASSWORD, PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID); - mService.setLockCredentialWithToken(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, - handle, TOKEN.getBytes(), PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID); + mLocalService.setLockCredentialWithToken(NEWPASSWORD, + LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, TOKEN.getBytes(), + PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID); assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential( NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID) @@ -378,8 +379,8 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { throws RemoteException { final String TOKEN = "some-high-entropy-secure-token"; enableSyntheticPassword(); - long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); - assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); + long handle = mLocalService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); + assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); assertTrue(hasSyntheticPassword(PRIMARY_USER_ID)); } @@ -388,8 +389,8 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { throws RemoteException { final String TOKEN = "some-high-entropy-secure-token"; initializeCredentialUnderSP(null, PRIMARY_USER_ID); - long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); - assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); + long handle = mLocalService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); + assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); assertTrue(hasSyntheticPassword(PRIMARY_USER_ID)); } @@ -404,15 +405,15 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID); enableSyntheticPassword(); - long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); + long handle = mLocalService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); // Token not activated immediately since user password exists - assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); + assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); // Activate token (password gets migrated to SP at the same time) assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential( PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID) .getResponseCode()); // Verify token is activated - assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); + assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); } public void testPasswordData_serializeDeserialize() { diff --git a/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java b/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java index 5de393c7ae2b..f3539fed7aec 100644 --- a/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java +++ b/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java @@ -25,6 +25,8 @@ import android.view.DisplayCutout; import android.view.IApplicationToken; import android.view.WindowManager; +import com.android.server.wm.utils.WmDisplayCutout; + public class FakeWindowState implements WindowManagerPolicy.WindowState { public final Rect parentFrame = new Rect(); @@ -36,7 +38,7 @@ public class FakeWindowState implements WindowManagerPolicy.WindowState { public final Rect stableFrame = new Rect(); public Rect outsetFrame = new Rect(); - public DisplayCutout displayCutout; + public WmDisplayCutout displayCutout; public WindowManager.LayoutParams attrs; public int displayId; @@ -61,7 +63,7 @@ public class FakeWindowState implements WindowManagerPolicy.WindowState { @Override public void computeFrameLw(Rect parentFrame, Rect displayFrame, Rect overlayFrame, Rect contentFrame, Rect visibleFrame, Rect decorFrame, Rect stableFrame, - @Nullable Rect outsetFrame, DisplayCutout displayCutout) { + @Nullable Rect outsetFrame, WmDisplayCutout displayCutout) { this.parentFrame.set(parentFrame); this.displayFrame.set(displayFrame); this.overscanFrame.set(overlayFrame); diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java index 1d4348c0b6d4..195dd39758df 100644 --- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java +++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java @@ -41,6 +41,7 @@ import android.os.IBinder; import android.os.UserHandle; import android.support.test.InstrumentationRegistry; import android.testing.TestableResources; +import android.util.Pair; import android.view.Display; import android.view.DisplayCutout; import android.view.DisplayInfo; @@ -53,6 +54,7 @@ import android.view.accessibility.IAccessibilityManager; import com.android.server.policy.keyguard.KeyguardServiceDelegate; import com.android.server.wm.DisplayFrames; +import com.android.server.wm.utils.WmDisplayCutout; import org.junit.Before; @@ -98,8 +100,9 @@ public class PhoneWindowManagerTestBase { } private void updateDisplayFrames() { - DisplayInfo info = displayInfoForRotation(mRotation, mHasDisplayCutout); - mFrames = new DisplayFrames(Display.DEFAULT_DISPLAY, info); + Pair<DisplayInfo, WmDisplayCutout> info = displayInfoAndCutoutForRotation(mRotation, + mHasDisplayCutout); + mFrames = new DisplayFrames(Display.DEFAULT_DISPLAY, info.first, info.second); } public void addStatusBar() { @@ -146,19 +149,26 @@ public class PhoneWindowManagerTestBase { } public static DisplayInfo displayInfoForRotation(int rotation, boolean withDisplayCutout) { + return displayInfoAndCutoutForRotation(rotation, withDisplayCutout).first; + } + public static Pair<DisplayInfo, WmDisplayCutout> displayInfoAndCutoutForRotation(int rotation, + boolean withDisplayCutout) { DisplayInfo info = new DisplayInfo(); + WmDisplayCutout cutout = null; final boolean flippedDimensions = rotation == ROTATION_90 || rotation == ROTATION_270; info.logicalWidth = flippedDimensions ? DISPLAY_HEIGHT : DISPLAY_WIDTH; info.logicalHeight = flippedDimensions ? DISPLAY_WIDTH : DISPLAY_HEIGHT; info.rotation = rotation; if (withDisplayCutout) { - info.displayCutout = displayCutoutForRotation(rotation) - .computeSafeInsets(info.logicalWidth, info.logicalHeight); + cutout = WmDisplayCutout.computeSafeInsets( + displayCutoutForRotation(rotation), info.logicalWidth, + info.logicalHeight); + info.displayCutout = cutout.getDisplayCutout(); } else { info.displayCutout = null; } - return info; + return Pair.create(info, cutout); } private static DisplayCutout displayCutoutForRotation(int rotation) { 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 064e077ee9ed..ccde049b99ca 100644 --- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java @@ -50,6 +50,8 @@ import android.view.DisplayCutout; import android.view.MotionEvent; import android.view.Surface; +import com.android.server.wm.utils.WmDisplayCutout; + import java.util.Arrays; import java.util.LinkedList; import java.util.List; @@ -398,8 +400,9 @@ public class DisplayContentTests extends WindowTestsBase { dc.mInitialDisplayWidth = 200; dc.mInitialDisplayHeight = 400; Rect r = new Rect(80, 0, 120, 10); - final DisplayCutout cutout = fromBoundingRect(r.left, r.top, r.right, r.bottom) - .computeSafeInsets(200, 400); + final DisplayCutout cutout = new WmDisplayCutout( + fromBoundingRect(r.left, r.top, r.right, r.bottom), null) + .computeSafeInsets(200, 400).getDisplayCutout(); dc.mInitialDisplayCutout = cutout; dc.setRotation(Surface.ROTATION_0); @@ -416,16 +419,18 @@ public class DisplayContentTests extends WindowTestsBase { dc.mInitialDisplayWidth = 200; dc.mInitialDisplayHeight = 400; Rect r1 = new Rect(80, 0, 120, 10); - final DisplayCutout cutout = fromBoundingRect(r1.left, r1.top, r1.right, r1.bottom) - .computeSafeInsets(200, 400); + final DisplayCutout cutout = new WmDisplayCutout( + fromBoundingRect(r1.left, r1.top, r1.right, r1.bottom), null) + .computeSafeInsets(200, 400).getDisplayCutout(); dc.mInitialDisplayCutout = cutout; dc.setRotation(Surface.ROTATION_90); dc.computeScreenConfiguration(new Configuration()); // recomputes dc.mDisplayInfo. final Rect r = new Rect(0, 80, 10, 120); - assertEquals(fromBoundingRect(r.left, r.top, r.right, r.bottom) - .computeSafeInsets(400, 200), dc.getDisplayInfo().displayCutout); + assertEquals(new WmDisplayCutout( + fromBoundingRect(r.left, r.top, r.right, r.bottom), null) + .computeSafeInsets(400, 200).getDisplayCutout(), dc.getDisplayInfo().displayCutout); } } diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java index 96fbc1402296..80cbf2aae3f0 100644 --- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java +++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java @@ -42,7 +42,7 @@ import java.io.File; /** * Test class for {@link TaskSnapshotPersister} and {@link TaskSnapshotLoader} * - * runtest frameworks-services -c com.android.server.wm.TaskSnapshotPersisterLoaderTest + * atest FrameworksServicesTests:TaskSnapshotPersisterLoaderTest */ @MediumTest @Presubmit @@ -163,6 +163,23 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa } @Test + public void testIsRealSnapshotPersistAndLoadSnapshot() { + TaskSnapshot a = createSnapshot(1f /* scale */, true /* isRealSnapshot */); + TaskSnapshot b = createSnapshot(1f /* scale */, false /* isRealSnapshot */); + assertTrue(a.isRealSnapshot()); + assertFalse(b.isRealSnapshot()); + mPersister.persistSnapshot(1, mTestUserId, a); + mPersister.persistSnapshot(2, mTestUserId, b); + mPersister.waitForQueueEmpty(); + final TaskSnapshot snapshotA = mLoader.loadTask(1, mTestUserId, false /* reduced */); + final TaskSnapshot snapshotB = mLoader.loadTask(2, mTestUserId, false /* reduced */); + assertNotNull(snapshotA); + assertNotNull(snapshotB); + assertTrue(snapshotA.isRealSnapshot()); + assertFalse(snapshotB.isRealSnapshot()); + } + + @Test public void testRemoveObsoleteFiles() { mPersister.persistSnapshot(1, mTestUserId, createSnapshot()); mPersister.persistSnapshot(2, mTestUserId, createSnapshot()); diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java index b49a0fdf8f38..2ad5bf404052 100644 --- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java +++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java @@ -84,12 +84,16 @@ class TaskSnapshotPersisterTestBase extends WindowTestsBase { } TaskSnapshot createSnapshot(float scale) { + return createSnapshot(scale, true /* isRealSnapshot */); + } + + TaskSnapshot createSnapshot(float scale, boolean isRealSnapshot) { final GraphicBuffer buffer = GraphicBuffer.create(100, 100, PixelFormat.RGBA_8888, USAGE_HW_TEXTURE | USAGE_SW_READ_RARELY | USAGE_SW_READ_RARELY); Canvas c = buffer.lockCanvas(); c.drawColor(Color.RED); buffer.unlockCanvasAndPost(c); return new TaskSnapshot(buffer, ORIENTATION_PORTRAIT, TEST_INSETS, - scale < 1f /* reducedResolution */, scale); + scale < 1f /* reducedResolution */, scale, isRealSnapshot); } } diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java index 4288eac0faa9..d5334babc1a6 100644 --- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java +++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java @@ -60,7 +60,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { final GraphicBuffer buffer = GraphicBuffer.create(width, height, PixelFormat.RGBA_8888, GraphicBuffer.USAGE_SW_READ_NEVER | GraphicBuffer.USAGE_SW_WRITE_NEVER); final TaskSnapshot snapshot = new TaskSnapshot(buffer, - ORIENTATION_PORTRAIT, contentInsets, false, 1.0f); + ORIENTATION_PORTRAIT, contentInsets, false, 1.0f, true /* isRealSnapshot */); mSurface = new TaskSnapshotSurface(sWm, new Window(), new Surface(), snapshot, "Test", Color.WHITE, Color.RED, Color.BLUE, sysuiVis, windowFlags, 0, taskBounds, ORIENTATION_PORTRAIT); diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java index b5d5fc607732..bd212a909088 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java @@ -26,7 +26,6 @@ import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.Gravity; import android.view.IWindow; @@ -38,6 +37,8 @@ import static android.view.WindowManager.LayoutParams.FILL_PARENT; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import com.android.server.wm.utils.WmDisplayCutout; + /** * Tests for the {@link WindowState#computeFrameLw} method and other window frame machinery. * @@ -159,7 +160,7 @@ public class WindowFrameTests extends WindowTestsBase { // When mFrame extends past cf, the content insets are // the difference between mFrame and ContentFrame. Visible // and stable frames work the same way. - w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, DisplayCutout.NO_CUTOUT); + w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, WmDisplayCutout.NO_CUTOUT); assertRect(w.mFrame,0, 0, 1000, 1000); assertRect(w.mContentInsets, 0, topContentInset, 0, bottomContentInset); assertRect(w.mVisibleInsets, 0, topVisibleInset, 0, bottomVisibleInset); @@ -174,7 +175,7 @@ public class WindowFrameTests extends WindowTestsBase { w.mAttrs.width = 100; w.mAttrs.height = 100; //have to clear MATCH_PARENT w.mRequestedWidth = 100; w.mRequestedHeight = 100; - w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, DisplayCutout.NO_CUTOUT); + w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, WmDisplayCutout.NO_CUTOUT); assertRect(w.mFrame, 100, 100, 200, 200); assertRect(w.mContentInsets, 0, 0, 0, 0); // In this case the frames are shrunk to the window frame. @@ -195,7 +196,7 @@ public class WindowFrameTests extends WindowTestsBase { // Here the window has FILL_PARENT, FILL_PARENT // so we expect it to fill the entire available frame. - w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, DisplayCutout.NO_CUTOUT); + w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT); assertRect(w.mFrame, 0, 0, 1000, 1000); // It can select various widths and heights within the bounds. @@ -203,14 +204,14 @@ public class WindowFrameTests extends WindowTestsBase { // and we use mRequestedWidth/mRequestedHeight w.mAttrs.width = 300; w.mAttrs.height = 300; - w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, DisplayCutout.NO_CUTOUT); + w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT); // Explicit width and height without requested width/height // gets us nothing. assertRect(w.mFrame, 0, 0, 0, 0); w.mRequestedWidth = 300; w.mRequestedHeight = 300; - w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, DisplayCutout.NO_CUTOUT); + w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT); // With requestedWidth/Height we can freely choose our size within the // parent bounds. assertRect(w.mFrame, 0, 0, 300, 300); @@ -223,14 +224,14 @@ public class WindowFrameTests extends WindowTestsBase { w.mRequestedWidth = -1; w.mAttrs.width = 100; w.mAttrs.height = 100; - w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, DisplayCutout.NO_CUTOUT); + w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT); assertRect(w.mFrame, 0, 0, 100, 100); w.mAttrs.flags = 0; // But sizes too large will be clipped to the containing frame w.mRequestedWidth = 1200; w.mRequestedHeight = 1200; - w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, DisplayCutout.NO_CUTOUT); + w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT); assertRect(w.mFrame, 0, 0, 1000, 1000); // Before they are clipped though windows will be shifted @@ -238,7 +239,7 @@ public class WindowFrameTests extends WindowTestsBase { w.mAttrs.y = 300; w.mRequestedWidth = 1000; w.mRequestedHeight = 1000; - w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, DisplayCutout.NO_CUTOUT); + w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT); assertRect(w.mFrame, 0, 0, 1000, 1000); // If there is room to move around in the parent frame the window will be shifted according @@ -248,16 +249,16 @@ public class WindowFrameTests extends WindowTestsBase { w.mRequestedWidth = 300; w.mRequestedHeight = 300; w.mAttrs.gravity = Gravity.RIGHT | Gravity.TOP; - w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, DisplayCutout.NO_CUTOUT); + w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT); assertRect(w.mFrame, 700, 0, 1000, 300); w.mAttrs.gravity = Gravity.RIGHT | Gravity.BOTTOM; - w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, DisplayCutout.NO_CUTOUT); + w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT); assertRect(w.mFrame, 700, 700, 1000, 1000); // Window specified x and y are interpreted as offsets in the opposite // direction of gravity w.mAttrs.x = 100; w.mAttrs.y = 100; - w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, DisplayCutout.NO_CUTOUT); + w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT); assertRect(w.mFrame, 600, 600, 900, 900); } @@ -278,7 +279,7 @@ public class WindowFrameTests extends WindowTestsBase { w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight); - w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, null, DisplayCutout.NO_CUTOUT); + w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, null, WmDisplayCutout.NO_CUTOUT); // For non fullscreen tasks the containing frame is based off the // task bounds not the parent frame. assertRect(w.mFrame, taskLeft, taskTop, taskRight, taskBottom); @@ -290,7 +291,7 @@ public class WindowFrameTests extends WindowTestsBase { final int cfRight = logicalWidth / 2; final int cfBottom = logicalHeight / 2; final Rect cf = new Rect(0, 0, cfRight, cfBottom); - w.computeFrameLw(pf, pf, pf, cf, cf, pf, cf, null, DisplayCutout.NO_CUTOUT); + w.computeFrameLw(pf, pf, pf, cf, cf, pf, cf, null, WmDisplayCutout.NO_CUTOUT); assertRect(w.mFrame, taskLeft, taskTop, taskRight, taskBottom); int contentInsetRight = taskRight - cfRight; int contentInsetBottom = taskBottom - cfBottom; @@ -307,7 +308,7 @@ public class WindowFrameTests extends WindowTestsBase { final int insetRight = insetLeft + (taskRight - taskLeft); final int insetBottom = insetTop + (taskBottom - taskTop); task.mInsetBounds.set(insetLeft, insetTop, insetRight, insetBottom); - w.computeFrameLw(pf, pf, pf, cf, cf, pf, cf, null, DisplayCutout.NO_CUTOUT); + w.computeFrameLw(pf, pf, pf, cf, cf, pf, cf, null, WmDisplayCutout.NO_CUTOUT); assertRect(w.mFrame, taskLeft, taskTop, taskRight, taskBottom); contentInsetRight = insetRight - cfRight; contentInsetBottom = insetBottom - cfBottom; @@ -339,13 +340,13 @@ public class WindowFrameTests extends WindowTestsBase { final Rect policyCrop = new Rect(); - w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, DisplayCutout.NO_CUTOUT); + w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, WmDisplayCutout.NO_CUTOUT); w.calculatePolicyCrop(policyCrop); assertRect(policyCrop, 0, cf.top, logicalWidth, cf.bottom); dcf.setEmpty(); // Likewise with no decor frame we would get no crop - w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, DisplayCutout.NO_CUTOUT); + w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, WmDisplayCutout.NO_CUTOUT); w.calculatePolicyCrop(policyCrop); assertRect(policyCrop, 0, 0, logicalWidth, logicalHeight); @@ -358,7 +359,7 @@ public class WindowFrameTests extends WindowTestsBase { w.mAttrs.height = logicalHeight / 2; w.mRequestedWidth = logicalWidth / 2; w.mRequestedHeight = logicalHeight / 2; - w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, DisplayCutout.NO_CUTOUT); + w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT); w.calculatePolicyCrop(policyCrop); // Normally the crop is shrunk from the decor frame @@ -395,7 +396,7 @@ public class WindowFrameTests extends WindowTestsBase { final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight); w.computeFrameLw(pf /* parentFrame */, pf /* displayFrame */, pf /* overscanFrame */, pf /* contentFrame */, pf /* visibleFrame */, pf /* decorFrame */, - pf /* stableFrame */, null /* outsetFrame */, DisplayCutout.NO_CUTOUT); + pf /* stableFrame */, null /* outsetFrame */, WmDisplayCutout.NO_CUTOUT); // For non fullscreen tasks the containing frame is based off the // task bounds not the parent frame. assertRect(w.mFrame, taskLeft, taskTop, taskRight, taskBottom); @@ -414,7 +415,7 @@ public class WindowFrameTests extends WindowTestsBase { w.computeFrameLw(pf /* parentFrame */, pf /* displayFrame */, pf /* overscanFrame */, cf /* contentFrame */, cf /* visibleFrame */, pf /* decorFrame */, - cf /* stableFrame */, null /* outsetFrame */, DisplayCutout.NO_CUTOUT); + cf /* stableFrame */, null /* outsetFrame */, WmDisplayCutout.NO_CUTOUT); assertEquals(cf, w.mFrame); assertEquals(cf, w.getContentFrameLw()); assertRect(w.mContentInsets, 0, 0, 0, 0); @@ -427,17 +428,17 @@ public class WindowFrameTests extends WindowTestsBase { WindowState w = createWindow(task, FILL_PARENT, FILL_PARENT); w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; - final Rect pf = new Rect(0, 0, 1000, 1000); + final Rect pf = new Rect(0, 0, 1000, 2000); // Create a display cutout of size 50x50, aligned top-center - final DisplayCutout cutout = fromBoundingRect(500, 0, 550, 50) - .computeSafeInsets(pf.width(), pf.height()); + final WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( + fromBoundingRect(500, 0, 550, 50), pf.width(), pf.height()); w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, cutout); - assertEquals(w.mDisplayCutout.getSafeInsetTop(), 50); - assertEquals(w.mDisplayCutout.getSafeInsetBottom(), 0); - assertEquals(w.mDisplayCutout.getSafeInsetLeft(), 0); - assertEquals(w.mDisplayCutout.getSafeInsetRight(), 0); + assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetTop(), 50); + assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetBottom(), 0); + assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetLeft(), 0); + assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetRight(), 0); } private WindowStateWithTask createWindow(Task task, int width, int height) { diff --git a/services/tests/servicestests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java b/services/tests/servicestests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java new file mode 100644 index 000000000000..f7addf6c77f9 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java @@ -0,0 +1,157 @@ +/* + * 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.wm.utils; + + +import static android.view.DisplayCutout.NO_CUTOUT; +import static android.view.DisplayCutout.fromBoundingRect; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.Size; +import android.view.DisplayCutout; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; + +/** + * Tests for {@link WmDisplayCutout} + * + * Run with: atest WmDisplayCutoutTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class WmDisplayCutoutTest { + + private final DisplayCutout mCutoutTop = new DisplayCutout( + new Rect(0, 100, 0, 0), + Arrays.asList(new Rect(50, 0, 75, 100))); + + @Test + public void calculateRelativeTo_top() { + WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( + fromBoundingRect(0, 0, 100, 20), 200, 400) + .calculateRelativeTo(new Rect(5, 5, 95, 195)); + + assertEquals(new Rect(0, 15, 0, 0), cutout.getDisplayCutout().getSafeInsets()); + } + + @Test + public void calculateRelativeTo_left() { + WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( + fromBoundingRect(0, 0, 20, 100), 400, 200) + .calculateRelativeTo(new Rect(5, 5, 195, 95)); + + assertEquals(new Rect(15, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets()); + } + + @Test + public void calculateRelativeTo_bottom() { + WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( + fromBoundingRect(0, 180, 100, 200), 100, 200) + .calculateRelativeTo(new Rect(5, 5, 95, 195)); + + assertEquals(new Rect(0, 0, 0, 15), cutout.getDisplayCutout().getSafeInsets()); + } + + @Test + public void calculateRelativeTo_right() { + WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( + fromBoundingRect(180, 0, 200, 100), 200, 100) + .calculateRelativeTo(new Rect(5, 5, 195, 95)); + + assertEquals(new Rect(0, 0, 15, 0), cutout.getDisplayCutout().getSafeInsets()); + } + + @Test + public void calculateRelativeTo_bounds() { + WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( + fromBoundingRect(0, 0, 100, 20), 200, 400) + .calculateRelativeTo(new Rect(5, 10, 95, 180)); + + assertEquals(new Rect(-5, -10, 95, 10), cutout.getDisplayCutout().getBounds().getBounds()); + } + + @Test + public void computeSafeInsets_top() { + WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( + fromBoundingRect(0, 0, 100, 20), 200, 400); + + assertEquals(new Rect(0, 20, 0, 0), cutout.getDisplayCutout().getSafeInsets()); + } + + @Test + public void computeSafeInsets_left() { + WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( + fromBoundingRect(0, 0, 20, 100), 400, 200); + + assertEquals(new Rect(20, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets()); + } + + @Test + public void computeSafeInsets_bottom() { + WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( + fromBoundingRect(0, 180, 100, 200), 100, 200); + + assertEquals(new Rect(0, 0, 0, 20), cutout.getDisplayCutout().getSafeInsets()); + } + + @Test + public void computeSafeInsets_right() { + WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( + fromBoundingRect(180, 0, 200, 100), 200, 100); + + assertEquals(new Rect(0, 0, 20, 0), cutout.getDisplayCutout().getSafeInsets()); + } + + @Test + public void computeSafeInsets_bounds() { + DisplayCutout cutout = WmDisplayCutout.computeSafeInsets(mCutoutTop, 1000, + 2000).getDisplayCutout(); + + assertEquals(mCutoutTop.getBounds().getBounds(), + cutout.getBounds().getBounds()); + } + + @Test + public void test_equals() { + assertEquals(new WmDisplayCutout(NO_CUTOUT, null), new WmDisplayCutout(NO_CUTOUT, null)); + assertEquals(new WmDisplayCutout(mCutoutTop, new Size(1, 2)), + new WmDisplayCutout(mCutoutTop, new Size(1, 2))); + + assertNotEquals(new WmDisplayCutout(mCutoutTop, new Size(1, 2)), + new WmDisplayCutout(mCutoutTop, new Size(5, 6))); + assertNotEquals(new WmDisplayCutout(mCutoutTop, new Size(1, 2)), + new WmDisplayCutout(NO_CUTOUT, new Size(1, 2))); + } + + @Test + public void test_hashCode() { + assertEquals(new WmDisplayCutout(NO_CUTOUT, null).hashCode(), + new WmDisplayCutout(NO_CUTOUT, null).hashCode()); + assertEquals(new WmDisplayCutout(mCutoutTop, new Size(1, 2)).hashCode(), + new WmDisplayCutout(mCutoutTop, new Size(1, 2)).hashCode()); + } +}
\ No newline at end of file diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java index a92f7e7af5d8..cb64c9c5edd7 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java @@ -21,6 +21,7 @@ import static android.app.Notification.GROUP_ALERT_SUMMARY; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_MIN; +import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; @@ -389,6 +390,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); verifyLights(); + assertTrue(r.isInterruptive()); } @Test @@ -400,6 +402,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyBeepLooped(); verifyNeverVibrate(); verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt()); + assertTrue(r.isInterruptive()); } @Test @@ -409,6 +412,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); verifyBeep(); + assertTrue(r.isInterruptive()); } @Test @@ -418,6 +422,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); verifyNeverBeep(); + assertFalse(r.isInterruptive()); } @Test @@ -429,6 +434,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyNeverBeep(); verifyNeverVibrate(); + assertFalse(r.isInterruptive()); } @Test @@ -440,6 +446,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyNeverBeep(); verifyNeverVibrate(); + assertFalse(r.isInterruptive()); } @Test @@ -455,6 +462,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); verifyBeepLooped(); verify(mAccessibilityService, times(2)).sendAccessibilityEvent(any(), anyInt()); + assertTrue(r.isInterruptive()); } @Test @@ -482,6 +490,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); verifyNeverStopAudio(); + assertTrue(r.isInterruptive()); } @Test @@ -494,6 +503,8 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(s); verifyNeverStopAudio(); + assertTrue(r.isInterruptive()); + assertFalse(s.isInterruptive()); } @Test @@ -511,6 +522,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // should not stop noise, since we no longer own it mService.buzzBeepBlinkLocked(s); // this no longer owns the stream verifyNeverStopAudio(); + assertTrue(other.isInterruptive()); } @Test @@ -535,11 +547,13 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // set up internal state mService.buzzBeepBlinkLocked(r); + assertTrue(r.isInterruptive()); Mockito.reset(mRingtonePlayer); // quiet update should stop making noise mService.buzzBeepBlinkLocked(s); verifyStopAudio(); + assertFalse(s.isInterruptive()); } @Test @@ -550,11 +564,13 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // set up internal state mService.buzzBeepBlinkLocked(r); + assertTrue(r.isInterruptive()); Mockito.reset(mRingtonePlayer); // stop making noise - this is a weird corner case, but quiet should override once mService.buzzBeepBlinkLocked(s); verifyStopAudio(); + assertFalse(s.isInterruptive()); } @Test @@ -570,6 +586,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verify(mService, times(1)).playInCallNotification(); verifyNeverBeep(); // doesn't play normal beep + assertTrue(r.isInterruptive()); } @Test @@ -587,6 +604,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verify(mVibrator, timeout(MAX_VIBRATION_DELAY).times(1)).vibrate(anyInt(), anyString(), eq(effect), (AudioAttributes) anyObject()); + assertTrue(r.isInterruptive()); } @Test @@ -603,6 +621,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyNeverVibrate(); verifyBeepLooped(); + assertTrue(r.isInterruptive()); } @Test @@ -621,6 +640,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { eq(FALLBACK_VIBRATION), (AudioAttributes) anyObject()); verify(mRingtonePlayer, never()).playAsync (anyObject(), anyObject(), anyBoolean(), anyObject()); + assertTrue(r.isInterruptive()); } @Test @@ -636,6 +656,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); verifyDelayedVibrateLooped(); + assertTrue(r.isInterruptive()); } @Test @@ -646,18 +667,20 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyNeverBeep(); verifyVibrate(); + assertTrue(r.isInterruptive()); } @Test - public void testInsistentVibrate() throws Exception { + public void testInsistentVibrate() { NotificationRecord r = getInsistentBuzzyNotification(); mService.buzzBeepBlinkLocked(r); verifyVibrateLooped(); + assertTrue(r.isInterruptive()); } @Test - public void testVibrateTwice() throws Exception { + public void testVibrateTwice() { NotificationRecord r = getBuzzyNotification(); // set up internal state @@ -668,6 +691,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { r.isUpdate = true; mService.buzzBeepBlinkLocked(r); verifyVibrate(); + assertTrue(r.isInterruptive()); } @Test @@ -677,6 +701,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(child); verifyNeverBeep(); + assertFalse(child.isInterruptive()); } @Test @@ -687,6 +712,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(summary); verifyBeepLooped(); + assertTrue(summary.isInterruptive()); } @Test @@ -696,6 +722,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(nonGroup); verifyBeepLooped(); + assertTrue(nonGroup.isInterruptive()); } @Test @@ -706,6 +733,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(summary); verifyNeverBeep(); + assertFalse(summary.isInterruptive()); } @Test @@ -715,6 +743,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(child); verifyBeepLooped(); + assertTrue(child.isInterruptive()); } @Test @@ -724,6 +753,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(nonGroup); verifyBeepLooped(); + assertTrue(nonGroup.isInterruptive()); } @Test @@ -733,6 +763,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(group); verifyBeepLooped(); + assertTrue(group.isInterruptive()); } @Test @@ -744,10 +775,12 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // set up internal state mService.buzzBeepBlinkLocked(r); Mockito.reset(mVibrator); + assertTrue(r.isInterruptive()); // update should not beep mService.buzzBeepBlinkLocked(s); verifyNeverVibrate(); + assertFalse(s.isInterruptive()); } @Test @@ -759,6 +792,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); verifyNeverStopVibrate(); + assertTrue(r.isInterruptive()); } @Test @@ -771,6 +805,8 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(s); verifyNeverStopVibrate(); + assertTrue(r.isInterruptive()); + assertFalse(s.isInterruptive()); } @Test @@ -788,6 +824,9 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // should not stop vibrate, since we no longer own it mService.buzzBeepBlinkLocked(s); // this no longer owns the stream verifyNeverStopVibrate(); + assertTrue(r.isInterruptive()); + assertTrue(other.isInterruptive()); + assertFalse(s.isInterruptive()); } @Test @@ -802,10 +841,11 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // should not stop noise, since it does not own it mService.buzzBeepBlinkLocked(other); verifyNeverStopVibrate(); + assertFalse(other.isInterruptive()); } @Test - public void testQuietUpdateCancelsVibrate() throws Exception { + public void testQuietUpdateCancelsVibrate() { NotificationRecord r = getBuzzyNotification(); NotificationRecord s = getQuietNotification(); s.isUpdate = true; @@ -817,6 +857,8 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // quiet update should stop making noise mService.buzzBeepBlinkLocked(s); verifyStopVibrate(); + assertTrue(r.isInterruptive()); + assertFalse(s.isInterruptive()); } @Test @@ -832,6 +874,8 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // stop making noise - this is a weird corner case, but quiet should override once mService.buzzBeepBlinkLocked(s); verifyStopVibrate(); + assertTrue(r.isInterruptive()); + assertFalse(s.isInterruptive()); } @Test @@ -848,6 +892,8 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // quiet update should stop making noise mService.buzzBeepBlinkLocked(s); verifyStopVibrate(); + assertTrue(r.isInterruptive()); + assertFalse(s.isInterruptive()); } @Test @@ -864,6 +910,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); verifyNeverBeep(); + assertFalse(r.isInterruptive()); } @Test @@ -874,6 +921,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); verifyNeverBeep(); + assertFalse(r.isInterruptive()); } @Test @@ -906,6 +954,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); verifyNeverBeep(); + assertFalse(r.isInterruptive()); } @Test diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 3c371e565220..9be9f3fa254f 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -990,6 +990,25 @@ public class UsageStatsService extends SystemService implements } @Override + public void reportInterruptiveNotification(String packageName, String channelId, + int userId) { + if (packageName == null || channelId == null) { + Slog.w(TAG, "Event reported without a package name or a channel ID"); + return; + } + + UsageEvents.Event event = new UsageEvents.Event(); + event.mPackage = packageName.intern(); + event.mNotificationChannelId = channelId.intern(); + + // This will later be converted to system time. + event.mTimeStamp = SystemClock.elapsedRealtime(); + + event.mEventType = Event.NOTIFICATION_INTERRUPTION; + mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); + } + + @Override public void reportShortcutUsage(String packageName, String shortcutId, int userId) { if (packageName == null || shortcutId == null) { Slog.w(TAG, "Event reported without a package name or a shortcut ID"); diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index 836eb318e4dc..d9742825ac70 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -759,6 +759,8 @@ class UserUsageStatsService { return "NOTIFICATION_SEEN"; case UsageEvents.Event.STANDBY_BUCKET_CHANGED: return "STANDBY_BUCKET_CHANGED"; + case UsageEvents.Event.NOTIFICATION_INTERRUPTION: + return "NOTIFICATION_INTERRUPTION"; default: return "UNKNOWN"; } diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java index fb52ff704a14..4f78c4cc78ad 100644 --- a/telephony/java/android/provider/Telephony.java +++ b/telephony/java/android/provider/Telephony.java @@ -3380,7 +3380,7 @@ public final class Telephony { * on the given subscriptionId * <p> * Use this {@link Uri} with a {@link ContentObserver} to be notified of changes to the - * carrier identity {@link TelephonyManager#getAndroidCarrierIdForSubscription()} + * carrier identity {@link TelephonyManager#getSimCarrierId()} * while your app is running. You can also use a {@link JobService} to ensure your app * is notified of changes to the {@link Uri} even when it is not running. * Note, however, that using a {@link JobService} does not guarantee timely delivery of @@ -3396,14 +3396,14 @@ public final class Telephony { /** * A user facing carrier name. - * @see TelephonyManager#getAndroidCarrierNameForSubscription() + * @see TelephonyManager#getSimCarrierIdName() * <P>Type: TEXT </P> */ public static final String CARRIER_NAME = "carrier_name"; /** * A unique carrier id - * @see TelephonyManager#getAndroidCarrierIdForSubscription() + * @see TelephonyManager#getSimCarrierId() * <P>Type: INTEGER </P> */ public static final String CARRIER_ID = "carrier_id"; diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 4a6143788cc1..ef66ed7ae064 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -25,6 +25,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SuppressAutoDoc; import android.annotation.SystemApi; import android.annotation.SystemService; import android.app.BroadcastOptions; @@ -42,7 +43,6 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.ServiceManager.ServiceNotFoundException; import android.util.DisplayMetrics; import com.android.internal.telephony.IOnSubscriptionsChangedListener; @@ -59,9 +59,6 @@ import java.util.concurrent.TimeUnit; /** * SubscriptionManager is the application interface to SubscriptionController * and provides information about the current Telephony Subscriptions. - * <p> - * All SDK public methods require android.Manifest.permission.READ_PHONE_STATE unless otherwise - * specified. */ @SystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) public class SubscriptionManager { @@ -612,6 +609,8 @@ public class SubscriptionManager { * @param listener an instance of {@link OnSubscriptionsChangedListener} with * onSubscriptionsChanged overridden. */ + // TODO(b/70041899): Find a way to extend this to carrier-privileged apps. + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) { String pkgName = mContext != null ? mContext.getOpPackageName() : "<unknown>"; if (DBG) { @@ -660,9 +659,15 @@ public class SubscriptionManager { /** * Get the active SubscriptionInfo with the input subId. * + * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * or that the calling app has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges}). + * * @param subId The unique SubscriptionInfo key in database. * @return SubscriptionInfo, maybe null if its not active. */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public SubscriptionInfo getActiveSubscriptionInfo(int subId) { if (VDBG) logd("[getActiveSubscriptionInfo]+ subId=" + subId); if (!isValidSubscriptionId(subId)) { @@ -716,9 +721,16 @@ public class SubscriptionManager { /** * Get the active SubscriptionInfo associated with the slotIndex + * + * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * or that the calling app has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges}). + * * @param slotIndex the slot which the subscription is inserted * @return SubscriptionInfo, maybe null if its not active */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex) { if (VDBG) logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIndex=" + slotIndex); if (!isValidSlotIndex(slotIndex)) { @@ -770,6 +782,11 @@ public class SubscriptionManager { * Get the SubscriptionInfo(s) of the currently inserted SIM(s). The records will be sorted * by {@link SubscriptionInfo#getSimSlotIndex} then by {@link SubscriptionInfo#getSubscriptionId}. * + * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * or that the calling app has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges}). In the latter case, only records accessible + * to the calling app are returned. + * * @return Sorted list of the currently {@link SubscriptionInfo} records available on the device. * <ul> * <li> @@ -786,6 +803,8 @@ public class SubscriptionManager { * </li> * </ul> */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public List<SubscriptionInfo> getActiveSubscriptionInfoList() { List<SubscriptionInfo> result = null; @@ -928,10 +947,18 @@ public class SubscriptionManager { } /** + * + * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * or that the calling app has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges}). In the latter case, the count will include + * only those subscriptions accessible to the caller. + * * @return the current number of active subscriptions. There is no guarantee the value * returned by this method will be the same as the length of the list returned by * {@link #getActiveSubscriptionInfoList}. */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getActiveSubscriptionInfoCount() { int result = 0; diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index aa76e9d554b6..22795b194e46 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SuppressAutoDoc; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -53,7 +54,6 @@ import android.telephony.ims.aidl.IImsConfig; import android.telephony.ims.aidl.IImsMmTelFeature; import android.telephony.ims.aidl.IImsRcsFeature; import android.telephony.ims.aidl.IImsRegistration; -import android.telephony.ims.feature.ImsFeature; import android.telephony.ims.stub.ImsRegistrationImplBase; import android.util.Log; @@ -1082,7 +1082,7 @@ public class TelephonyManager { /** * An int extra used with {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} which indicates - * the updated carrier id {@link TelephonyManager#getAndroidCarrierIdForSubscription()} of + * the updated carrier id {@link TelephonyManager#getSimCarrierId()} of * the current subscription. * <p>Will be {@link TelephonyManager#UNKNOWN_CARRIER_ID} if the subscription is unavailable or * the carrier cannot be identified. @@ -1092,7 +1092,7 @@ public class TelephonyManager { /** * An string extra used with {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} which * indicates the updated carrier name of the current subscription. - * {@see TelephonyManager#getSubscriptionCarrierName()} + * {@see TelephonyManager#getSimCarrierIdName()} * <p>Carrier name is a user-facing name of the carrier id {@link #EXTRA_CARRIER_ID}, * usually the brand name of the subsidiary (e.g. T-Mobile). */ @@ -1114,7 +1114,11 @@ public class TelephonyManager { * Returns the software version number for the device, for example, * the IMEI/SV for GSM phones. Return null if the software version is * not available. + * + * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getDeviceSoftwareVersion() { return getDeviceSoftwareVersion(getSlotIndex()); @@ -1146,10 +1150,14 @@ public class TelephonyManager { * Returns the unique device ID, for example, the IMEI for GSM and the MEID * or ESN for CDMA phones. Return null if device ID is not available. * + * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). + * * @deprecated Use (@link getImei} which returns IMEI for GSM or (@link getMeid} which returns * MEID for CDMA. */ @Deprecated + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getDeviceId() { try { @@ -1168,12 +1176,16 @@ public class TelephonyManager { * Returns the unique device ID of a subscription, for example, the IMEI for * GSM and the MEID for CDMA phones. Return null if device ID is not available. * + * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). + * * @param slotIndex of which deviceID is returned * * @deprecated Use (@link getImei} which returns IMEI for GSM or (@link getMeid} which returns * MEID for CDMA. */ @Deprecated + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getDeviceId(int slotIndex) { // FIXME this assumes phoneId == slotIndex @@ -1192,7 +1204,11 @@ public class TelephonyManager { /** * Returns the IMEI (International Mobile Equipment Identity). Return null if IMEI is not * available. + * + * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getImei() { return getImei(getSlotIndex()); @@ -1202,8 +1218,12 @@ public class TelephonyManager { * Returns the IMEI (International Mobile Equipment Identity). Return null if IMEI is not * available. * + * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). + * * @param slotIndex of which IMEI is returned */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getImei(int slotIndex) { ITelephony telephony = getITelephony(); @@ -1220,7 +1240,11 @@ public class TelephonyManager { /** * Returns the MEID (Mobile Equipment Identifier). Return null if MEID is not available. + * + * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getMeid() { return getMeid(getSlotIndex()); @@ -1229,8 +1253,12 @@ public class TelephonyManager { /** * Returns the MEID (Mobile Equipment Identifier). Return null if MEID is not available. * + * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). + * * @param slotIndex of which MEID is returned */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getMeid(int slotIndex) { ITelephony telephony = getITelephony(); @@ -1247,10 +1275,11 @@ public class TelephonyManager { /** * Returns the Network Access Identifier (NAI). Return null if NAI is not available. - * <p> - * Requires Permission: - * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * + * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getNai() { return getNaiBySubscriberId(getSubId()); @@ -1751,6 +1780,7 @@ public class TelephonyManager { * @see #createForSubscriptionId(int) * @see #createForPhoneAccountHandle(PhoneAccountHandle) */ + // TODO(b/73136824, b/70041899): Permit carrier-privileged callers as well. @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public PersistableBundle getCarrierConfig() { @@ -1949,6 +1979,9 @@ public class TelephonyManager { * If this object has been created with {@link #createForSubscriptionId}, applies to the given * subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()} * + * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). + * * @return the network type * * @see #NETWORK_TYPE_UNKNOWN @@ -1968,6 +2001,7 @@ public class TelephonyManager { * @see #NETWORK_TYPE_EHRPD * @see #NETWORK_TYPE_HSPAP */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getDataNetworkType() { return getDataNetworkType(getSubId(SubscriptionManager.getDefaultDataSubscriptionId())); @@ -2002,7 +2036,11 @@ public class TelephonyManager { /** * Returns the NETWORK_TYPE_xxxx for voice + * + * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getVoiceNetworkType() { return getVoiceNetworkType(getSubId()); @@ -2588,7 +2626,11 @@ public class TelephonyManager { /** * Returns the serial number of the SIM, if applicable. Return null if it is * unavailable. + * + * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getSimSerialNumber() { return getSimSerialNumber(getSubId()); @@ -2713,7 +2755,11 @@ public class TelephonyManager { /** * Returns the unique subscriber ID, for example, the IMSI for a GSM phone. * Return null if it is unavailable. + * + * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getSubscriberId() { return getSubscriberId(getSubId()); @@ -2877,7 +2923,11 @@ public class TelephonyManager { /** * Returns the Group Identifier Level1 for a GSM phone. * Return null if it is unavailable. + * + * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getGroupIdLevel1() { try { @@ -2918,9 +2968,15 @@ public class TelephonyManager { /** * Returns the phone number string for line 1, for example, the MSISDN * for a GSM phone. Return null if it is unavailable. - * <p> - * The default SMS app can also use this. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}, + * {@link android.Manifest.permission#READ_SMS READ_SMS}, + * {@link android.Manifest.permission#READ_PHONE_NUMBERS READ_PHONE_NUMBERS}, + * that the caller is the default SMS app, + * or that the caller has carrier privileges (see {@link #hasCarrierPrivileges}). */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges or default SMS app @RequiresPermission(anyOf = { android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, @@ -2975,8 +3031,7 @@ public class TelephonyManager { * change the actual MSISDN/MDN. To unset alphatag or number, pass in a null * value. * - * <p>Requires that the calling app has carrier privileges. - * @see #hasCarrierPrivileges + * <p>Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param alphaTag alpha-tagging of the dailing nubmer * @param number The dialing number @@ -2992,8 +3047,7 @@ public class TelephonyManager { * change the actual MSISDN/MDN. To unset alphatag or number, pass in a null * value. * - * <p>Requires that the calling app has carrier privileges. - * @see #hasCarrierPrivileges + * <p>Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param subId the subscriber that the alphatag and dialing number belongs to. * @param alphaTag alpha-tagging of the dailing nubmer @@ -3112,7 +3166,11 @@ public class TelephonyManager { /** * Returns the voice mail number. Return null if it is unavailable. + * + * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailNumber() { return getVoiceMailNumber(getSubId()); @@ -3173,8 +3231,7 @@ public class TelephonyManager { /** * Sets the voice mail number. * - * <p>Requires that the calling app has carrier privileges. - * @see #hasCarrierPrivileges + * <p>Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param alphaTag The alpha tag to display. * @param number The voicemail number. @@ -3186,8 +3243,7 @@ public class TelephonyManager { /** * Sets the voicemail number for the given subscriber. * - * <p>Requires that the calling app has carrier privileges. - * @see #hasCarrierPrivileges + * <p>Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param subId The subscription id. * @param alphaTag The alpha tag to display. @@ -3208,9 +3264,9 @@ public class TelephonyManager { /** * Enables or disables the visual voicemail client for a phone account. * - * <p>Requires that the calling app is the default dialer, or has carrier privileges, or - * has permission {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}. - * @see #hasCarrierPrivileges + * <p>Requires that the calling app is the default dialer, or has carrier privileges (see + * {@link #hasCarrierPrivileges}), or has permission + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}. * * @param phoneAccountHandle the phone account to change the client state * @param enabled the new state of the client @@ -3273,11 +3329,15 @@ public class TelephonyManager { * to the TelephonyManager. Returns {@code null} when there is no package responsible for * processing visual voicemail for the subscription. * + * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). + * * @see #createForSubscriptionId(int) * @see #createForPhoneAccountHandle(PhoneAccountHandle) * @see VisualVoicemailService */ @Nullable + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVisualVoicemailPackageName() { try { @@ -3520,15 +3580,14 @@ public class TelephonyManager { * Sets the voice activation state * * <p>Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the + * calling app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param activationState The voice activation state * @see #SIM_ACTIVATION_STATE_UNKNOWN * @see #SIM_ACTIVATION_STATE_ACTIVATING * @see #SIM_ACTIVATION_STATE_ACTIVATED * @see #SIM_ACTIVATION_STATE_DEACTIVATED - * @see #hasCarrierPrivileges * @hide */ @SystemApi @@ -3541,8 +3600,8 @@ public class TelephonyManager { * Sets the voice activation state for the given subscriber. * * <p>Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the + * calling app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param subId The subscription id. * @param activationState The voice activation state of the given subscriber. @@ -3550,7 +3609,6 @@ public class TelephonyManager { * @see #SIM_ACTIVATION_STATE_ACTIVATING * @see #SIM_ACTIVATION_STATE_ACTIVATED * @see #SIM_ACTIVATION_STATE_DEACTIVATED - * @see #hasCarrierPrivileges * @hide */ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @@ -3568,8 +3626,8 @@ public class TelephonyManager { * Sets the data activation state * * <p>Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the + * calling app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param activationState The data activation state * @see #SIM_ACTIVATION_STATE_UNKNOWN @@ -3577,7 +3635,6 @@ public class TelephonyManager { * @see #SIM_ACTIVATION_STATE_ACTIVATED * @see #SIM_ACTIVATION_STATE_DEACTIVATED * @see #SIM_ACTIVATION_STATE_RESTRICTED - * @see #hasCarrierPrivileges * @hide */ @SystemApi @@ -3590,8 +3647,8 @@ public class TelephonyManager { * Sets the data activation state for the given subscriber. * * <p>Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the + * calling app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param subId The subscription id. * @param activationState The data activation state of the given subscriber. @@ -3600,7 +3657,6 @@ public class TelephonyManager { * @see #SIM_ACTIVATION_STATE_ACTIVATED * @see #SIM_ACTIVATION_STATE_DEACTIVATED * @see #SIM_ACTIVATION_STATE_RESTRICTED - * @see #hasCarrierPrivileges * @hide */ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @@ -3618,15 +3674,14 @@ public class TelephonyManager { * Returns the voice activation state * * <p>Requires Permission: - * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} - * Or the calling app has carrier privileges. + * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @return voiceActivationState * @see #SIM_ACTIVATION_STATE_UNKNOWN * @see #SIM_ACTIVATION_STATE_ACTIVATING * @see #SIM_ACTIVATION_STATE_ACTIVATED * @see #SIM_ACTIVATION_STATE_DEACTIVATED - * @see #hasCarrierPrivileges * @hide */ @SystemApi @@ -3639,8 +3694,8 @@ public class TelephonyManager { * Returns the voice activation state for the given subscriber. * * <p>Requires Permission: - * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} - * Or the calling app has carrier privileges. + * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param subId The subscription id. * @@ -3649,7 +3704,6 @@ public class TelephonyManager { * @see #SIM_ACTIVATION_STATE_ACTIVATING * @see #SIM_ACTIVATION_STATE_ACTIVATED * @see #SIM_ACTIVATION_STATE_DEACTIVATED - * @see #hasCarrierPrivileges * @hide */ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @@ -3668,8 +3722,8 @@ public class TelephonyManager { * Returns the data activation state * * <p>Requires Permission: - * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} - * Or the calling app has carrier privileges. + * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @return dataActivationState for the given subscriber * @see #SIM_ACTIVATION_STATE_UNKNOWN @@ -3677,7 +3731,6 @@ public class TelephonyManager { * @see #SIM_ACTIVATION_STATE_ACTIVATED * @see #SIM_ACTIVATION_STATE_DEACTIVATED * @see #SIM_ACTIVATION_STATE_RESTRICTED - * @see #hasCarrierPrivileges * @hide */ @SystemApi @@ -3690,8 +3743,8 @@ public class TelephonyManager { * Returns the data activation state for the given subscriber. * * <p>Requires Permission: - * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} - * Or the calling app has carrier privileges. + * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param subId The subscription id. * @@ -3701,7 +3754,6 @@ public class TelephonyManager { * @see #SIM_ACTIVATION_STATE_ACTIVATED * @see #SIM_ACTIVATION_STATE_DEACTIVATED * @see #SIM_ACTIVATION_STATE_RESTRICTED - * @see #hasCarrierPrivileges * @hide */ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @@ -3749,7 +3801,11 @@ public class TelephonyManager { /** * Retrieves the alphabetic identifier associated with the voice * mail number. + * + * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailAlphaTag() { return getVoiceMailAlphaTag(getSubId()); @@ -3778,9 +3834,8 @@ public class TelephonyManager { } /** - * Send the special dialer code. The IPC caller must be the current default dialer or has - * carrier privileges. - * @see #hasCarrierPrivileges + * Send the special dialer code. The IPC caller must be the current default dialer or have + * carrier privileges (see {@link #hasCarrierPrivileges}). * * @param inputCode The special dialer code to send * @@ -4304,8 +4359,8 @@ public class TelephonyManager { * Input parameters equivalent to TS 27.007 AT+CCHO command. * * <p>Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param AID Application id. See ETSI 102.221 and 101.220. * @return an IccOpenLogicalChannelResponse object. @@ -4322,8 +4377,8 @@ public class TelephonyManager { * Input parameters equivalent to TS 27.007 AT+CCHO command. * * <p>Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param AID Application id. See ETSI 102.221 and 101.220. * @param p2 P2 parameter (described in ISO 7816-4). @@ -4339,8 +4394,8 @@ public class TelephonyManager { * Input parameters equivalent to TS 27.007 AT+CCHO command. * * <p>Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param subId The subscription to use. * @param AID Application id. See ETSI 102.221 and 101.220. @@ -4365,8 +4420,8 @@ public class TelephonyManager { * Input parameters equivalent to TS 27.007 AT+CCHC command. * * <p>Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param channel is the channel id to be closed as retruned by a successful * iccOpenLogicalChannel. @@ -4382,8 +4437,8 @@ public class TelephonyManager { * Input parameters equivalent to TS 27.007 AT+CCHC command. * * <p>Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param subId The subscription to use. * @param channel is the channel id to be closed as retruned by a successful @@ -4408,8 +4463,8 @@ public class TelephonyManager { * Input parameters equivalent to TS 27.007 AT+CGLA command. * * <p>Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param channel is the channel id to be closed as returned by a successful * iccOpenLogicalChannel. @@ -4435,8 +4490,8 @@ public class TelephonyManager { * Input parameters equivalent to TS 27.007 AT+CGLA command. * * <p>Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param subId The subscription to use. * @param channel is the channel id to be closed as returned by a successful @@ -4471,8 +4526,8 @@ public class TelephonyManager { * Input parameters equivalent to TS 27.007 AT+CSIM command. * * <p>Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param cla Class of the APDU command. * @param instruction Instruction of the APDU command. @@ -4496,8 +4551,8 @@ public class TelephonyManager { * Input parameters equivalent to TS 27.007 AT+CSIM command. * * <p>Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param subId The subscription to use. * @param cla Class of the APDU command. @@ -4528,8 +4583,8 @@ public class TelephonyManager { * Returns the response APDU for a command APDU sent through SIM_IO. * * <p>Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param fileID * @param command @@ -4548,8 +4603,8 @@ public class TelephonyManager { * Returns the response APDU for a command APDU sent through SIM_IO. * * <p>Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param subId The subscription to use. * @param fileID @@ -4577,8 +4632,8 @@ public class TelephonyManager { * Send ENVELOPE to the SIM and return the response. * * <p>Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param content String containing SAT/USAT response in hexadecimal * format starting with command tag. See TS 102 223 for @@ -4595,8 +4650,8 @@ public class TelephonyManager { * Send ENVELOPE to the SIM and return the response. * * <p>Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param subId The subscription to use. * @param content String containing SAT/USAT response in hexadecimal @@ -4621,10 +4676,10 @@ public class TelephonyManager { /** * Read one of the NV items defined in com.android.internal.telephony.RadioNVItems. * Used for device configuration by some CDMA operators. - * <p> - * Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param itemID the ID of the item to read. * @return the NV item as a String, or null on any failure. @@ -4647,10 +4702,10 @@ public class TelephonyManager { /** * Write one of the NV items defined in com.android.internal.telephony.RadioNVItems. * Used for device configuration by some CDMA operators. - * <p> - * Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param itemID the ID of the item to read. * @param itemValue the value to write, as a String. @@ -4674,10 +4729,10 @@ public class TelephonyManager { /** * Update the CDMA Preferred Roaming List (PRL) in the radio NV storage. * Used for device configuration by some CDMA operators. - * <p> - * Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param preferredRoamingList byte array containing the new PRL. * @return true on success; false on any failure. @@ -4701,10 +4756,10 @@ public class TelephonyManager { * Perform the specified type of NV config reset. The radio will be taken offline * and the device must be rebooted after the operation. Used for device * configuration by some CDMA operators. - * <p> - * Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param resetType reset type: 1: reload NV reset, 2: erase NV reset, 3: factory NV reset * @return true on success; false on any failure. @@ -5052,8 +5107,8 @@ public class TelephonyManager { * Returns the response of authentication for the default subscription. * Returns null if the authentication hasn't been successful * - * <p>Requires that the calling app has carrier privileges or READ_PRIVILEGED_PHONE_STATE - * permission. + * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param appType the icc application type, like {@link #APPTYPE_USIM} * @param authType the authentication type, {@link #AUTHTYPE_EAP_AKA} or @@ -5061,9 +5116,10 @@ public class TelephonyManager { * @param data authentication challenge data, base64 encoded. * See 3GPP TS 31.102 7.1.2 for more details. * @return the response of authentication, or null if not available - * - * @see #hasCarrierPrivileges */ + // TODO(b/73660190): This should probably require MODIFY_PHONE_STATE, not + // READ_PRIVILEGED_PHONE_STATE. It certainly shouldn't reference the permission in Javadoc since + // it's not public API. public String getIccAuthentication(int appType, int authType, String data) { return getIccAuthentication(getSubId(), appType, authType, data); } @@ -5072,7 +5128,7 @@ public class TelephonyManager { * Returns the response of USIM Authentication for specified subId. * Returns null if the authentication hasn't been successful * - * <p>Requires that the calling app has carrier privileges. + * <p>Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param subId subscription ID used for authentication * @param appType the icc application type, like {@link #APPTYPE_USIM} @@ -5081,8 +5137,6 @@ public class TelephonyManager { * @param data authentication challenge data, base64 encoded. * See 3GPP TS 31.102 7.1.2 for more details. * @return the response of authentication, or null if not available - * - * @see #hasCarrierPrivileges * @hide */ public String getIccAuthentication(int subId, int appType, int authType, String data) { @@ -5103,8 +5157,12 @@ public class TelephonyManager { * Returns an array of Forbidden PLMNs from the USIM App * Returns null if the query fails. * + * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). + * * @return an array of forbidden PLMNs or null if not available */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String[] getForbiddenPlmns() { return getForbiddenPlmns(getSubId(), APPTYPE_USIM); @@ -5310,10 +5368,10 @@ public class TelephonyManager { /** * Get 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 the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @return the preferred network type, defined in RILConstants.java. * @hide @@ -5333,11 +5391,12 @@ public class TelephonyManager { /** * Sets the network selection mode to automatic. - * <p> - * Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setNetworkSelectionModeAutomatic() { try { @@ -5353,15 +5412,14 @@ public class TelephonyManager { } /** - * Perform a radio scan and return the list of avialble networks. + * Perform a radio scan and return the list of available networks. * * The return value is a list of the OperatorInfo of the networks found. Note that this * scan can take a long time (sometimes minutes) to happen. * - * <p> - * Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @hide * TODO: Add an overload that takes no args. @@ -5385,17 +5443,16 @@ public class TelephonyManager { * This method is asynchronous, so the network scan results will be returned by callback. * The returned NetworkScan will contain a callback method which can be used to stop the scan. * - * <p> - * Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. - * @see #hasCarrierPrivileges() + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param request Contains all the RAT with bands/channels that need to be scanned. * @param executor The executor through which the callback should be invoked. * @param callback Returns network scan results or errors. * @return A NetworkScan obj which contains a callback which can be used to stop the scan. */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public NetworkScan requestNetworkScan( NetworkScanRequest request, Executor executor, @@ -5423,10 +5480,9 @@ public class TelephonyManager { /** * Ask the radio to connect to the input network and change selection mode to manual. * - * <p> - * Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param operatorNumeric the PLMN ID of the network to select. * @param persistSelection whether the selection will persist until reboot. If true, only allows @@ -5434,6 +5490,7 @@ public class TelephonyManager { * normal network selection next time. * @return true on success; false on any failure. */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setNetworkSelectionModeManual(String operatorNumeric, boolean persistSelection) { try { @@ -5453,10 +5510,10 @@ 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 the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param subId the id of the subscription to set the preferred network type for. * @param networkType the preferred network type, defined in RILConstants.java. @@ -5480,9 +5537,7 @@ public class TelephonyManager { /** * Set the preferred network type to global mode which includes LTE, CDMA, EvDo and GSM/WCDMA. * - * <p> - * Requires that the calling app has carrier privileges. - * @see #hasCarrierPrivileges + * <p>Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @return true on success; false on any failure. */ @@ -5493,9 +5548,7 @@ public class TelephonyManager { /** * Set the preferred network type to global mode which includes LTE, CDMA, EvDo and GSM/WCDMA. * - * <p> - * Requires that the calling app has carrier privileges. - * @see #hasCarrierPrivileges + * <p>Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @return true on success; false on any failure. * @hide @@ -5585,8 +5638,7 @@ public class TelephonyManager { * brand value input. To unset the value, the same function should be * called with a null brand value. * - * <p>Requires that the calling app has carrier privileges. - * @see #hasCarrierPrivileges + * <p>Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param brand The brand name to display/set. * @return true if the operation was executed correctly. @@ -5603,8 +5655,7 @@ public class TelephonyManager { * brand value input. To unset the value, the same function should be * called with a null brand value. * - * <p>Requires that the calling app has carrier privileges. - * @see #hasCarrierPrivileges + * <p>Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param subId The subscription to use. * @param brand The brand name to display/set. @@ -6258,15 +6309,15 @@ public class TelephonyManager { * subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()} * * <p>Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the - * calling app has carrier privileges. + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @param enable Whether to enable mobile data. * - * @see #hasCarrierPrivileges * @deprecated use {@link #setUserMobileDataEnabled(boolean)} instead. */ @Deprecated + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(boolean enable) { setUserMobileDataEnabled(enable); @@ -6303,7 +6354,7 @@ public class TelephonyManager { * <p>Requires one of the following permissions: * {@link android.Manifest.permission#ACCESS_NETWORK_STATE ACCESS_NETWORK_STATE}, * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}, or that the - * calling app has carrier privileges. + * calling app has carrier privileges (see {@link #hasCarrierPrivileges}). * * <p>Note that this does not take into account any data restrictions that may be present on the * calling app. Such restrictions may be inspected with @@ -6311,7 +6362,6 @@ public class TelephonyManager { * * @return true if mobile data is enabled. * - * @see #hasCarrierPrivileges * @deprecated use {@link #isUserMobileDataEnabled()} instead. */ @Deprecated @@ -7082,7 +7132,11 @@ public class TelephonyManager { /** * Returns the current {@link ServiceState} information. + * + * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public ServiceState getServiceState() { return getServiceStateForSubscriber(getSubId()); @@ -7128,14 +7182,14 @@ public class TelephonyManager { /** * Sets the per-account voicemail ringtone. * - * <p>Requires that the calling app is the default dialer, or has carrier privileges, or has - * permission {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}. + * <p>Requires that the calling app is the default dialer, or has carrier privileges (see + * {@link #hasCarrierPrivileges}, or has permission + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}. * * @param phoneAccountHandle The handle for the {@link PhoneAccount} for which to set the * voicemail ringtone. * @param uri The URI for the ringtone to play when receiving a voicemail from a specific * PhoneAccount. - * @see #hasCarrierPrivileges * * @deprecated Use {@link android.provider.Settings#ACTION_CHANNEL_NOTIFICATION_SETTINGS} * instead. @@ -7173,14 +7227,14 @@ public class TelephonyManager { /** * Sets the per-account preference whether vibration is enabled for voicemail notifications. * - * <p>Requires that the calling app is the default dialer, or has carrier privileges, or has - * permission {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}. + * <p>Requires that the calling app is the default dialer, or has carrier privileges (see + * {@link #hasCarrierPrivileges}, or has permission + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}. * * @param phoneAccountHandle The handle for the {@link PhoneAccount} for which to set the * voicemail vibration setting. * @param enabled Whether to enable or disable vibration for voicemail notifications from a * specific PhoneAccount. - * @see #hasCarrierPrivileges * * @deprecated Use {@link android.provider.Settings#ACTION_CHANNEL_NOTIFICATION_SETTINGS} * instead. @@ -7201,8 +7255,8 @@ public class TelephonyManager { /** * Returns carrier id of the current subscription. * <p>To recognize a carrier (including MVNO) as a first-class identity, Android assigns each - * carrier with a canonical integer a.k.a. android carrier id. The Android carrier ID is an - * Android platform-wide identifier for a carrier. AOSP maintains carrier ID assignments in + * carrier with a canonical integer a.k.a. carrier id. The carrier ID is an Android + * platform-wide identifier for a carrier. AOSP maintains carrier ID assignments in * <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/carrier_list.textpb">here</a> * * <p>Apps which have carrier-specific configurations or business logic can use the carrier id @@ -7211,7 +7265,7 @@ public class TelephonyManager { * @return Carrier id of the current subscription. Return {@link #UNKNOWN_CARRIER_ID} if the * subscription is unavailable or the carrier cannot be identified. */ - public int getAndroidCarrierIdForSubscription() { + public int getSimCarrierId() { try { ITelephony service = getITelephony(); if (service != null) { @@ -7224,18 +7278,18 @@ public class TelephonyManager { } /** - * Returns carrier name of the current subscription. - * <p>Carrier name is a user-facing name of carrier id - * {@link #getAndroidCarrierIdForSubscription()}, usually the brand name of the subsidiary + * Returns carrier id name of the current subscription. + * <p>Carrier id name is a user-facing name of carrier id + * {@link #getSimCarrierId()}, usually the brand name of the subsidiary * (e.g. T-Mobile). Each carrier could configure multiple {@link #getSimOperatorName() SPN} but * should have a single carrier name. Carrier name is not a canonical identity, - * use {@link #getAndroidCarrierIdForSubscription()} instead. + * use {@link #getSimCarrierId()} instead. * <p>The returned carrier name is unlocalized. * * @return Carrier name of the current subscription. Return {@code null} if the subscription is * unavailable or the carrier cannot be identified. */ - public CharSequence getAndroidCarrierNameForSubscription() { + public CharSequence getSimCarrierIdName() { try { ITelephony service = getITelephony(); if (service != null) { @@ -7601,12 +7655,10 @@ public class TelephonyManager { * Otherwise, it applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()} * * <p>Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the - * calling app has carrier privileges. + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}. * * @param enable Whether to enable mobile data. - * - * @see #hasCarrierPrivileges */ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUserMobileDataEnabled(boolean enable) { @@ -7624,15 +7676,13 @@ public class TelephonyManager { * <p>Requires one of the following permissions: * {@link android.Manifest.permission#ACCESS_NETWORK_STATE ACCESS_NETWORK_STATE}, * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}, or that the - * calling app has carrier privileges. + * calling app has carrier privileges (see {@link #hasCarrierPrivileges}. * * <p>Note that this does not take into account any data restrictions that may be present on the * calling app. Such restrictions may be inspected with * {@link ConnectivityManager#getRestrictBackgroundStatus}. * * @return true if mobile data is enabled. - * - * @see #hasCarrierPrivileges */ @RequiresPermission(anyOf = { android.Manifest.permission.ACCESS_NETWORK_STATE, diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java index 27e5f943982b..350dfe3a8d08 100644 --- a/telephony/java/android/telephony/ims/ImsCallProfile.java +++ b/telephony/java/android/telephony/ims/ImsCallProfile.java @@ -296,7 +296,10 @@ public final class ImsCallProfile implements Parcelable { readFromParcel(in); } - /** @hide */ + /** + * Default Constructor that initializes the call profile with service type + * {@link #SERVICE_TYPE_NORMAL} and call type {@link #CALL_TYPE_VIDEO_N_VOICE} + */ public ImsCallProfile() { mServiceType = SERVICE_TYPE_NORMAL; mCallType = CALL_TYPE_VOICE_N_VIDEO; @@ -304,7 +307,25 @@ public final class ImsCallProfile implements Parcelable { mMediaProfile = new ImsStreamMediaProfile(); } - /** @hide */ + /** + * Constructor. + * + * @param serviceType the service type for the call. Can be one of the following: + * {@link #SERVICE_TYPE_NONE}, + * {@link #SERVICE_TYPE_NORMAL}, + * {@link #SERVICE_TYPE_EMERGENCY} + * @param callType the call type. Can be one of the following: + * {@link #CALL_TYPE_VOICE_N_VIDEO}, + * {@link #CALL_TYPE_VOICE}, + * {@link #CALL_TYPE_VIDEO_N_VOICE}, + * {@link #CALL_TYPE_VT}, + * {@link #CALL_TYPE_VT_TX}, + * {@link #CALL_TYPE_VT_RX}, + * {@link #CALL_TYPE_VT_NODIR}, + * {@link #CALL_TYPE_VS}, + * {@link #CALL_TYPE_VS_TX}, + * {@link #CALL_TYPE_VS_RX} + */ public ImsCallProfile(int serviceType, int callType) { mServiceType = serviceType; mCallType = callType; @@ -312,6 +333,35 @@ public final class ImsCallProfile implements Parcelable { mMediaProfile = new ImsStreamMediaProfile(); } + /** + * Constructor. + * + * @param serviceType the service type for the call. Can be one of the following: + * {@link #SERVICE_TYPE_NONE}, + * {@link #SERVICE_TYPE_NORMAL}, + * {@link #SERVICE_TYPE_EMERGENCY} + * @param callType the call type. Can be one of the following: + * {@link #CALL_TYPE_VOICE_N_VIDEO}, + * {@link #CALL_TYPE_VOICE}, + * {@link #CALL_TYPE_VIDEO_N_VOICE}, + * {@link #CALL_TYPE_VT}, + * {@link #CALL_TYPE_VT_TX}, + * {@link #CALL_TYPE_VT_RX}, + * {@link #CALL_TYPE_VT_NODIR}, + * {@link #CALL_TYPE_VS}, + * {@link #CALL_TYPE_VS_TX}, + * {@link #CALL_TYPE_VS_RX} + * @param callExtras A bundle with the call extras. + * @param mediaProfile The IMS stream media profile. + */ + public ImsCallProfile(int serviceType, int callType, Bundle callExtras, + ImsStreamMediaProfile mediaProfile) { + mServiceType = serviceType; + mCallType = callType; + mCallExtras = callExtras; + mMediaProfile = mediaProfile; + } + public String getCallExtra(String name) { return getCallExtra(name, ""); } @@ -375,6 +425,16 @@ public final class ImsCallProfile implements Parcelable { mCallExtras = (Bundle) profile.mCallExtras.clone(); } + /** + * Updates the media profile for the call. + * + * @param profile Call profile with new media profile. + */ + public void updateMediaProfile(ImsCallProfile profile) { + mMediaProfile = profile.mMediaProfile; + } + + @Override public String toString() { return "{ serviceType=" + mServiceType + diff --git a/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java b/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java index 243352bdd180..137106a011b0 100644 --- a/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java +++ b/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java @@ -99,6 +99,62 @@ public final class ImsStreamMediaProfile implements Parcelable { readFromParcel(in); } + /** + * Constructor. + * + * @param audioQuality The audio quality. Can be one of the following: + * {@link #AUDIO_QUALITY_AMR}, + * {@link #AUDIO_QUALITY_AMR_WB}, + * {@link #AUDIO_QUALITY_QCELP13K}, + * {@link #AUDIO_QUALITY_EVRC}, + * {@link #AUDIO_QUALITY_EVRC_B}, + * {@link #AUDIO_QUALITY_EVRC_WB}, + * {@link #AUDIO_QUALITY_EVRC_NW}, + * {@link #AUDIO_QUALITY_GSM_EFR}, + * {@link #AUDIO_QUALITY_GSM_FR}, + * {@link #AUDIO_QUALITY_GSM_HR}, + * {@link #AUDIO_QUALITY_G711U}, + * {@link #AUDIO_QUALITY_G723}, + * {@link #AUDIO_QUALITY_G711A}, + * {@link #AUDIO_QUALITY_G722}, + * {@link #AUDIO_QUALITY_G711AB}, + * {@link #AUDIO_QUALITY_G729}, + * {@link #AUDIO_QUALITY_EVS_NB}, + * {@link #AUDIO_QUALITY_EVS_WB}, + * {@link #AUDIO_QUALITY_EVS_SWB}, + * {@link #AUDIO_QUALITY_EVS_FB}, + * @param audioDirection The audio direction. Can be one of the following: + * {@link #DIRECTION_INVALID}, + * {@link #DIRECTION_INACTIVE}, + * {@link #DIRECTION_RECEIVE}, + * {@link #DIRECTION_SEND}, + * {@link #DIRECTION_SEND_RECEIVE}, + * @param videoQuality The video quality. Can be one of the following: + * {@link #VIDEO_QUALITY_NONE}, + * {@link #VIDEO_QUALITY_QCIF}, + * {@link #VIDEO_QUALITY_QVGA_LANDSCAPE}, + * {@link #VIDEO_QUALITY_QVGA_PORTRAIT}, + * {@link #VIDEO_QUALITY_VGA_LANDSCAPE}, + * {@link #VIDEO_QUALITY_VGA_PORTRAIT}, + * @param videoDirection The video direction. Can be one of the following: + * {@link #DIRECTION_INVALID}, + * {@link #DIRECTION_INACTIVE}, + * {@link #DIRECTION_RECEIVE}, + * {@link #DIRECTION_SEND}, + * {@link #DIRECTION_SEND_RECEIVE}, + * @param rttMode The rtt mode. Can be one of the following: + * {@link #RTT_MODE_DISABLED}, + * {@link #RTT_MODE_FULL} + */ + public ImsStreamMediaProfile(int audioQuality, int audioDirection, + int videoQuality, int videoDirection, int rttMode) { + mAudioQuality = audioQuality; + mAudioDirection = audioDirection; + mVideoQuality = videoQuality; + mVideoDirection = videoDirection; + mRttMode = rttMode; + } + /** @hide */ public ImsStreamMediaProfile() { mAudioQuality = AUDIO_QUALITY_NONE; diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index a941a5671f13..4002d3c94db7 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1358,10 +1358,10 @@ interface ITelephony { /** * Returns carrier name of the given subscription. - * <p>Carrier name is a user-facing name of carrier id {@link #getSubscriptionCarrierId(int)}, + * <p>Carrier name is a user-facing name of carrier id {@link #getSimCarrierId(int)}, * usually the brand name of the subsidiary (e.g. T-Mobile). Each carrier could configure * multiple {@link #getSimOperatorName() SPN} but should have a single carrier name. - * Carrier name is not canonical identity, use {@link #getSubscriptionCarrierId(int)} instead. + * Carrier name is not canonical identity, use {@link #getSimCarrierId(int)} instead. * <p>Returned carrier name is unlocalized. * * @return Carrier name of given subscription id. return {@code null} if subscription is diff --git a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java index da8471fa19ed..a182f2be421f 100644 --- a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java @@ -26,7 +26,8 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.telephony.ITelephony; + +import java.util.function.Supplier; /** Utility class for Telephony permission enforcement. */ public final class TelephonyPermissions { @@ -34,6 +35,9 @@ public final class TelephonyPermissions { private static final boolean DBG = false; + private static final Supplier<ITelephony> TELEPHONY_SUPPLIER = () -> + ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE)); + private TelephonyPermissions() {} /** @@ -41,8 +45,8 @@ public final class TelephonyPermissions { * * <p>This method behaves in one of the following ways: * <ul> - * <li>return true: if the caller has either the READ_PRIVILEGED_PHONE_STATE permission or the - * READ_PHONE_STATE runtime permission. + * <li>return true: if the caller has the READ_PRIVILEGED_PHONE_STATE permission, the + * READ_PHONE_STATE runtime permission, or carrier privileges on the given subId. * <li>throw SecurityException: if the caller didn't declare any of these permissions, or, for * apps which support runtime permissions, if the caller does not currently have any of * these permissions. @@ -51,20 +55,30 @@ public final class TelephonyPermissions { * manually (via AppOps). In this case we can't throw as it would break app compatibility, * so we return false to indicate that the calling function should return dummy data. * </ul> + * + * <p>Note: for simplicity, this method always returns false for callers using legacy + * permissions and who have had READ_PHONE_STATE revoked, even if they are carrier-privileged. + * Such apps should migrate to runtime permissions or stop requiring READ_PHONE_STATE on P+ + * devices. + * + * @param subId the subId of the relevant subscription; used to check carrier privileges. May be + * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} to skip this check for cases + * where it isn't relevant (hidden APIs, or APIs which are otherwise okay to leave + * inaccesible to carrier-privileged apps). */ public static boolean checkCallingOrSelfReadPhoneState( - Context context, String callingPackage, String message) { - return checkReadPhoneState(context, Binder.getCallingPid(), Binder.getCallingUid(), + Context context, int subId, String callingPackage, String message) { + return checkReadPhoneState(context, subId, Binder.getCallingPid(), Binder.getCallingUid(), callingPackage, message); } /** * Check whether the app with the given pid/uid can read phone state. * - * <p>This method behaves in one of the following ways: + * <p>This method behaves in one of the following ways: * <ul> - * <li>return true: if the caller has either the READ_PRIVILEGED_PHONE_STATE permission or the - * READ_PHONE_STATE runtime permission. + * <li>return true: if the caller has the READ_PRIVILEGED_PHONE_STATE permission, the + * READ_PHONE_STATE runtime permission, or carrier privileges on the given subId. * <li>throw SecurityException: if the caller didn't declare any of these permissions, or, for * apps which support runtime permissions, if the caller does not currently have any of * these permissions. @@ -73,9 +87,22 @@ public final class TelephonyPermissions { * manually (via AppOps). In this case we can't throw as it would break app compatibility, * so we return false to indicate that the calling function should return dummy data. * </ul> + * + * <p>Note: for simplicity, this method always returns false for callers using legacy + * permissions and who have had READ_PHONE_STATE revoked, even if they are carrier-privileged. + * Such apps should migrate to runtime permissions or stop requiring READ_PHONE_STATE on P+ + * devices. */ public static boolean checkReadPhoneState( - Context context, int pid, int uid, String callingPackage, String message) { + Context context, int subId, int pid, int uid, String callingPackage, String message) { + return checkReadPhoneState( + context, TELEPHONY_SUPPLIER, subId, pid, uid, callingPackage, message); + } + + @VisibleForTesting + public static boolean checkReadPhoneState( + Context context, Supplier<ITelephony> telephonySupplier, int subId, int pid, int uid, + String callingPackage, String message) { try { context.enforcePermission( android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, pid, uid, message); @@ -83,8 +110,18 @@ public final class TelephonyPermissions { // SKIP checking for run-time permission since caller has PRIVILEGED permission return true; } catch (SecurityException privilegedPhoneStateException) { - context.enforcePermission( - android.Manifest.permission.READ_PHONE_STATE, pid, uid, message); + try { + context.enforcePermission( + android.Manifest.permission.READ_PHONE_STATE, pid, uid, message); + } catch (SecurityException phoneStateException) { + // If we don't have the runtime permission, but do have carrier privileges, that + // suffices for reading phone state. + if (SubscriptionManager.isValidSubscriptionId(subId)) { + enforceCarrierPrivilege(telephonySupplier, subId, uid, message); + return true; + } + throw phoneStateException; + } } // We have READ_PHONE_STATE permission, so return true as long as the AppOps bit hasn't been @@ -101,14 +138,16 @@ public final class TelephonyPermissions { * default SMS app and apps with READ_SMS or READ_PHONE_NUMBERS can also read phone numbers. */ public static boolean checkCallingOrSelfReadPhoneNumber( - Context context, String callingPackage, String message) { + Context context, int subId, String callingPackage, String message) { return checkReadPhoneNumber( - context, Binder.getCallingPid(), Binder.getCallingUid(), callingPackage, message); + context, TELEPHONY_SUPPLIER, subId, Binder.getCallingPid(), Binder.getCallingUid(), + callingPackage, message); } @VisibleForTesting public static boolean checkReadPhoneNumber( - Context context, int pid, int uid, String callingPackage, String message) { + Context context, Supplier<ITelephony> telephonySupplier, int subId, int pid, int uid, + String callingPackage, String message) { // Default SMS app can always read it. AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); if (appOps.noteOp(AppOpsManager.OP_WRITE_SMS, uid, callingPackage) == @@ -121,7 +160,8 @@ public final class TelephonyPermissions { // First, check if we can read the phone state. try { - return checkReadPhoneState(context, pid, uid, callingPackage, message); + return checkReadPhoneState( + context, telephonySupplier, subId, pid, uid, callingPackage, message); } catch (SecurityException readPhoneStateSecurityException) { } // Can be read with READ_SMS too. @@ -186,16 +226,21 @@ public final class TelephonyPermissions { } private static void enforceCarrierPrivilege(int subId, int uid, String message) { - if (getCarrierPrivilegeStatus(subId, uid) != + enforceCarrierPrivilege(TELEPHONY_SUPPLIER, subId, uid, message); + } + + private static void enforceCarrierPrivilege( + Supplier<ITelephony> telephonySupplier, int subId, int uid, String message) { + if (getCarrierPrivilegeStatus(telephonySupplier, subId, uid) != TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { if (DBG) Rlog.e(LOG_TAG, "No Carrier Privilege."); throw new SecurityException(message); } } - private static int getCarrierPrivilegeStatus(int subId, int uid) { - ITelephony telephony = - ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE)); + private static int getCarrierPrivilegeStatus( + Supplier<ITelephony> telephonySupplier, int subId, int uid) { + ITelephony telephony = telephonySupplier.get(); try { if (telephony != null) { return telephony.getCarrierPrivilegeStatusForUid(subId, uid); |